import { FontStyle } from '@/styles/fonts';
import { getUserLang } from './misc';

function parseUnicodeString(dataView: DataView, offset: number, length: number): string {
  const chars: string[] = [];
  for (let i = 0; i < length; i += 2) {
    const charCode = dataView.getUint16(offset + i, false);
    if (charCode !== 0) {
      chars.push(String.fromCharCode(charCode));
    }
  }
  return chars.join('');
}

function parseMacintoshString(dataView: DataView, offset: number, length: number): string {
  const chars: string[] = [];
  for (let i = 0; i < length; i++) {
    const charCode = dataView.getUint8(offset + i);
    chars.push(String.fromCharCode(charCode));
  }
  return chars.join('');
}

const NO_STYLE_LANGUAGE_IDS = new Set([0x0404, 0x0804, 0x0c04, 0x1004, 19, 33]);

function getLanguagePriority(platformID: number, languageID: number, userLanguage: string): number {
  let priority = 0;

  // Base priority by platform (Unicode/Microsoft preferred)
  if (platformID === 0)
    priority += 100; // Unicode
  else if (platformID === 3)
    priority += 90; // Microsoft
  else if (platformID === 1) priority += 50; // Macintosh

  // Language-specific priorities
  const userLang = userLanguage.toLowerCase();

  if (platformID === 0 || platformID === 3) {
    if (userLang.startsWith('zh')) {
      if (languageID === 0x0804)
        priority += 50; // Simplified Chinese
      else if (languageID === 0x0404)
        priority += 45; // Traditional Chinese
      else if (languageID === 0x0c04)
        priority += 40; // Traditional Chinese
      else if (languageID === 0x1004) priority += 35; // Simplified Chinese
    } else if (userLang.startsWith('ja')) {
      if (languageID === 0x0411) priority += 50; // Japanese
    } else if (userLang.startsWith('ko')) {
      if (languageID === 0x0412) priority += 50; // Korean
    } else if (userLang.startsWith('en')) {
      if (languageID === 0x0409)
        priority += 50; // English (US)
      else if (languageID === 0x0809) priority += 45; // English (UK)
    }

    // Fallback: English
    if (languageID === 0x0409) priority += 10; // English fallback
  } else if (platformID === 1) {
    // Macintosh platform language codes
    if (userLang.startsWith('zh')) {
      if (languageID === 33)
        priority += 50; // Chinese (Simplified)
      else if (languageID === 19) priority += 45; // Chinese (Traditional)
    } else if (userLang.startsWith('ja')) {
      if (languageID === 11) priority += 50; // Japanese
    } else if (userLang.startsWith('ko')) {
      if (languageID === 23) priority += 50; // Korean
    } else if (userLang.startsWith('en')) {
      if (languageID === 0) priority += 50; // English
    }

    // Fallback: English
    if (languageID === 0) priority += 10; // English fallback
  }

  return priority;
}

function parseOS2Weight(dataView: DataView, os2TableOffset: number): number {
  // OS/2 table usWeightClass is at offset 4
  return dataView.getUint16(os2TableOffset + 4, false);
}

function parseOS2Selection(dataView: DataView, os2TableOffset: number): number {
  // OS/2 table fsSelection is at offset 62
  return dataView.getUint16(os2TableOffset + 62, false);
}

interface VariableFontAxis {
  tag: string;
  minValue: number;
  defaultValue: number;
  maxValue: number;
  name?: string;
}

function parseVariableFontAxes(dataView: DataView, fvarTableOffset: number): VariableFontAxis[] {
  try {
    // fvar table structure:
    // version (4 bytes) + axisCount (2 bytes) + axisSize (2 bytes) + instanceCount (2 bytes) + instanceSize (2 bytes)
    const axisCount = dataView.getUint16(fvarTableOffset + 4, false);
    const axisSize = dataView.getUint16(fvarTableOffset + 6, false);

    const axes: VariableFontAxis[] = [];

    // Each axis record starts at offset 16 from table start
    let axisOffset = fvarTableOffset + 16;

    for (let i = 0; i < axisCount; i++) {
      // Axis record structure:
      // axisTag (4 bytes) + minValue (4 bytes) + defaultValue (4 bytes) + maxValue (4 bytes) + flags (2 bytes) + axisNameID (2 bytes)

      const tag = String.fromCharCode(
        dataView.getUint8(axisOffset),
        dataView.getUint8(axisOffset + 1),
        dataView.getUint8(axisOffset + 2),
        dataView.getUint8(axisOffset + 3),
      );

      // Fixed-point values (16.16 format)
      const minValue = dataView.getInt32(axisOffset + 4, false) / 65536;
      const defaultValue = dataView.getInt32(axisOffset + 8, false) / 65536;
      const maxValue = dataView.getInt32(axisOffset + 12, false) / 65536;

      axes.push({
        tag,
        minValue,
        defaultValue,
        maxValue,
      });

      axisOffset += axisSize;
    }

    return axes;
  } catch (error) {
    console.warn('Failed to parse fvar table:', error);
    return [];
  }
}

function weightClassToCSSWeight(weightClass: number): number {
  // Map OpenType weight class to CSS weight
  if (weightClass >= 1 && weightClass <= 100) return 100;
  if (weightClass >= 101 && weightClass <= 200) return 200;
  if (weightClass >= 201 && weightClass <= 300) return 300;
  if (weightClass >= 301 && weightClass <= 400) return 400;
  if (weightClass >= 401 && weightClass <= 500) return 500;
  if (weightClass >= 501 && weightClass <= 600) return 600;
  if (weightClass >= 601 && weightClass <= 700) return 700;
  if (weightClass >= 701 && weightClass <= 800) return 800;
  if (weightClass >= 801 && weightClass <= 900) return 900;
  return 400; // Default to normal weight
}

function inferWeightFromStyleName(styleName: string): number {
  const lowerStyle = styleName.toLowerCase();

  // Check for specific weight keywords
  if (lowerStyle.includes('thin') || lowerStyle.includes('hairline')) return 100;
  if (lowerStyle.includes('extralight') || lowerStyle.includes('ultralight')) return 200;
  if (
    lowerStyle.includes('light') &&
    !lowerStyle.includes('extralight') &&
    !lowerStyle.includes('ultralight')
  )
    return 300;
  if (lowerStyle.includes('medium')) return 500;
  if (lowerStyle.includes('semibold') || lowerStyle.includes('demibold')) return 600;
  if (lowerStyle.includes('extrabold') || lowerStyle.includes('ultrabold')) return 800;
  if (lowerStyle.includes('black') || lowerStyle.includes('heavy')) return 900;
  if (
    lowerStyle.includes('bold') &&
    !lowerStyle.includes('semibold') &&
    !lowerStyle.includes('extrabold') &&
    !lowerStyle.includes('ultrabold')
  )
    return 700;

  return 400; // Default to normal weight
}

function inferStyleFromName(
  styleName: string,
  fsSelection: number,
): 'normal' | 'italic' | 'oblique' {
  const lowerStyle = styleName.toLowerCase();

  // Check fsSelection flags first (bit 0 = italic, bit 9 = oblique)
  if (fsSelection & 0x200) return 'oblique'; // Bit 9
  if (fsSelection & 0x1) return 'italic'; // Bit 0

  // Fallback to style name analysis
  if (lowerStyle.includes('oblique')) return 'oblique';
  if (lowerStyle.includes('italic') || lowerStyle.includes('slant')) return 'italic';

  return 'normal';
}

type FontNameType = {
  name: string;
  platformID: number;
  languageID: number;
  priority: number;
};

export const parseFontInfo = (fontData: ArrayBuffer, filename: string) => {
  const fallbackName = filename.replace(/\.[^/.]+$/, '');
  try {
    const dataView = new DataView(fontData);
    const signature = dataView.getUint32(0, false);
    if (signature !== 0x00010000 && signature !== 0x74727565 && signature !== 0x4f54544f) {
      throw new Error('Unsupported font format');
    }
    const numTables = dataView.getUint16(4, false);
    let nameTableOffset = 0;
    let os2TableOffset = 0;
    let fvarTableOffset = 0;
    for (let i = 0; i < numTables; i++) {
      const tableOffset = 12 + i * 16;
      const tag = String.fromCharCode(
        dataView.getUint8(tableOffset),
        dataView.getUint8(tableOffset + 1),
        dataView.getUint8(tableOffset + 2),
        dataView.getUint8(tableOffset + 3),
      );

      if (tag === 'name') {
        nameTableOffset = dataView.getUint32(tableOffset + 8, false);
      } else if (tag === 'OS/2') {
        os2TableOffset = dataView.getUint32(tableOffset + 8, false);
      } else if (tag === 'fvar') {
        fvarTableOffset = dataView.getUint32(tableOffset + 8, false);
      }
    }

    if (nameTableOffset === 0) {
      throw new Error('Name table not found');
    }

    const count = dataView.getUint16(nameTableOffset + 2, false);
    const stringOffset = dataView.getUint16(nameTableOffset + 4, false);

    const userLanguage = getUserLang();
    const fontFamilyNames: Array<FontNameType> = [];
    const fontStyleNames: Array<FontNameType> = [];
    const preferredFamilyNames: Array<FontNameType> = [];
    const preferredStyleNames: Array<FontNameType> = [];
    for (let i = 0; i < count; i++) {
      const recordOffset = nameTableOffset + 6 + i * 12;
      const platformID = dataView.getUint16(recordOffset, false);
      const languageID = dataView.getUint16(recordOffset + 4, false);
      const nameID = dataView.getUint16(recordOffset + 6, false);
      const nameLength = dataView.getUint16(recordOffset + 8, false);
      const nameOffsetInTable = dataView.getUint16(recordOffset + 10, false);

      // nameID 1 = Font Family name, nameID 2 = Font Subfamily name (style)
      // nameID 16 = Typographic Family name, nameID 17 = Typographic Subfamily name
      if (nameID === 1 || nameID === 2 || nameID === 16 || nameID === 17) {
        const stringStart = nameTableOffset + stringOffset + nameOffsetInTable;
        let fontName = '';

        if (platformID === 0 || platformID === 3) {
          // Unicode/Microsoft platform
          fontName = parseUnicodeString(dataView, stringStart, nameLength);
        } else if (platformID === 1) {
          // Macintosh platform
          fontName = parseMacintoshString(dataView, stringStart, nameLength);
        }

        if (fontName && fontName.trim()) {
          const priority = getLanguagePriority(platformID, languageID, userLanguage);
          const nameEntry = {
            name: fontName.trim(),
            platformID,
            languageID,
            priority,
          };

          if (nameID === 1) {
            fontFamilyNames.push(nameEntry);
          } else if (nameID === 2) {
            fontStyleNames.push(nameEntry);
          } else if (nameID === 16) {
            preferredFamilyNames.push(nameEntry);
          } else if (nameID === 17) {
            preferredStyleNames.push(nameEntry);
          }
        }
      }
    }
    if (fontFamilyNames.length === 0) {
      throw new Error('Font family name not found');
    }
    fontFamilyNames.sort((a, b) => b.priority - a.priority);
    fontStyleNames.sort((a, b) => b.priority - a.priority);
    preferredFamilyNames.sort((a, b) => b.priority - a.priority);
    preferredStyleNames.sort((a, b) => b.priority - a.priority);

    // Prefer typographic names if available
    const familyName = (preferredFamilyNames[0] || fontFamilyNames[0])!.name;
    const fontStyleName = preferredStyleNames[0] || fontStyleNames[0];
    const styleName = fontStyleName?.name || '';

    // Parse weight and style information
    let fontWeight = 400;
    let fontStyle: FontStyle = 'normal';
    let fsSelection = 0;

    if (os2TableOffset > 0) {
      try {
        const weightClass = parseOS2Weight(dataView, os2TableOffset);
        fontWeight = weightClassToCSSWeight(weightClass);
        fsSelection = parseOS2Selection(dataView, os2TableOffset);
      } catch {
        console.warn('Failed to parse OS/2 table, falling back to style name analysis');
      }
    }

    let isVariable = false;
    if (fvarTableOffset > 0) {
      const axes = parseVariableFontAxes(dataView, fvarTableOffset);
      if (axes && axes.length > 0) {
        isVariable = true;
      }
    }

    // If OS/2 table weight is default (400) or unavailable, try to infer from style name
    if (fontWeight === 400 && styleName) {
      const inferredWeight = inferWeightFromStyleName(styleName);
      if (inferredWeight !== 400) {
        fontWeight = inferredWeight;
      }
    }

    fontStyle = inferStyleFromName(styleName, fsSelection);

    return {
      name:
        fontStyleName && !NO_STYLE_LANGUAGE_IDS.has(fontStyleName.languageID)
          ? `${familyName} ${styleName}`
          : familyName,
      family: familyName,
      weight: fontWeight,
      style: fontStyle,
      variable: isVariable,
    };
  } catch (error) {
    console.warn(`Failed to parse font: ${error}`);
    return {
      name: fallbackName,
      family: fallbackName,
      weight: 400,
      style: 'normal' as FontStyle,
      variable: false,
    };
  }
};

export const isFontType = (mimeType: string) => {
  const fontMimeTypes = [
    'font/woff',
    'font/woff2',
    'font/ttf',
    'font/otf',
    'application/font-woff',
    'application/font-woff2',
    'application/x-font-woff',
    'application/x-font-woff2',
    'application/x-font-ttf',
    'application/x-font-otf',
  ];
  return fontMimeTypes.includes(mimeType);
};
