// Minimal sfnt (TTF/OTF) reader for the `font()` compile macro: family name from the `name` table,
// weight/italic from `OS/2` (with a `head.macStyle` fallback). Reads only what the macro needs —
// this is not a font sanitizer or validator.

export type SfntInfo = { family: string, weight: number, italic: boolean }

const TAG_TTF = 0x00010000
const TAG_OTTO = 0x4f54544f // 'OTTO' (CFF outlines)
const TAG_TTCF = 0x74746366 // 'ttcf' (collection)
const TAG_WOFF = 0x774f4646 // 'wOFF'
const TAG_WOF2 = 0x774f4632 // 'wOF2'

/** Parse a .ttf/.otf. Throws a message meant to surface verbatim in a compile error. */
export const parseSfnt = (bytes: Uint8Array): SfntInfo => {
  const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
  if (bytes.byteLength < 12) throw new Error("not a font file (truncated)")
  const version = dv.getUint32(0)
  if (version === TAG_TTCF) throw new Error("font collections (.ttc) aren't supported — export a single face")
  if (version === TAG_WOFF || version === TAG_WOF2) throw new Error("WOFF/WOFF2 project fonts aren't supported — use the .ttf/.otf")
  if (version !== TAG_TTF && version !== TAG_OTTO) throw new Error("not a TTF/OTF font file")

  const numTables = dv.getUint16(4)
  const tables = new Map<string, { offset: number, length: number }>()
  for (let i = 0; i < numTables; i++) {
    const rec = 12 + i * 16
    if (rec + 16 > bytes.byteLength) throw new Error("not a font file (truncated table directory)")
    const tag = String.fromCharCode(bytes[rec], bytes[rec + 1], bytes[rec + 2], bytes[rec + 3])
    tables.set(tag, { offset: dv.getUint32(rec + 8), length: dv.getUint32(rec + 12) })
  }

  const family = readFamily(dv, bytes, tables.get("name"))
  if (!family) throw new Error("no family name in the font's `name` table")

  let weight = 400
  let italic = false
  const os2 = tables.get("OS/2")
  if (os2 && os2.offset + 64 <= bytes.byteLength) {
    weight = dv.getUint16(os2.offset + 4) // usWeightClass
    italic = (dv.getUint16(os2.offset + 62) & 1) === 1 // fsSelection ITALIC
  } else {
    const head = tables.get("head")
    if (head && head.offset + 46 <= bytes.byteLength) {
      const macStyle = dv.getUint16(head.offset + 44)
      if (macStyle & 1) weight = 700
      italic = (macStyle & 2) === 2
    }
  }
  if (weight < 1 || weight > 1000) weight = 400

  return { family, weight, italic }
}

/** Best family name: typographic family (nameID 16) over legacy family (1); Windows-English
 *  records over the rest. Returns "" when the table yields nothing decodable. */
const readFamily = (dv: DataView, bytes: Uint8Array, name?: { offset: number, length: number }): string => {
  if (!name || name.offset + 6 > bytes.byteLength) return ""
  const base = name.offset
  const count = dv.getUint16(base + 2)
  const stringOffset = base + dv.getUint16(base + 4)

  let best = ""
  let bestScore = -1
  for (let i = 0; i < count; i++) {
    const rec = base + 6 + i * 12
    if (rec + 12 > bytes.byteLength) break
    const platformID = dv.getUint16(rec)
    const languageID = dv.getUint16(rec + 4)
    const nameID = dv.getUint16(rec + 6)
    const length = dv.getUint16(rec + 8)
    const offset = stringOffset + dv.getUint16(rec + 10)
    if (nameID !== 16 && nameID !== 1) continue
    if (offset + length > bytes.byteLength) continue

    // nameID 16 beats 1; within an id, Windows-English > Windows > Unicode > Mac.
    let score = nameID === 16 ? 100 : 0
    if (platformID === 3) score += languageID === 0x409 ? 30 : 20
    else if (platformID === 0) score += 10
    if (score <= bestScore) continue

    const text = platformID === 1 ? decodeLatin1(bytes, offset, length) : decodeUtf16be(bytes, offset, length)
    if (!text.trim()) continue
    best = text.trim()
    bestScore = score
  }
  return best
}

const decodeUtf16be = (bytes: Uint8Array, offset: number, length: number): string => {
  let out = ""
  for (let i = 0; i + 1 < length; i += 2) out += String.fromCharCode((bytes[offset + i] << 8) | bytes[offset + i + 1])
  return out
}

const decodeLatin1 = (bytes: Uint8Array, offset: number, length: number): string => {
  let out = ""
  for (let i = 0; i < length; i++) out += String.fromCharCode(bytes[offset + i])
  return out
}
