///|
/// Parse cmap table: returns Map[codepoint -> glyph_id]
/// Prefers Format 12 (full Unicode) over Format 4 (BMP only)
pub fn parse_cmap(reader : BinaryReader, offset : Int) -> Map[Int, Int] {
  let result : Map[Int, Int] = {}
  reader.seek(offset)
  let _version = reader.read_uint16()
  let num_tables = reader.read_uint16()
  // Collect candidate subtable offsets
  let mut format12_offset = -1
  let mut format4_offset = -1
  for i = 0; i < num_tables; i = i + 1 {
    let platform_id = reader.read_uint16()
    let encoding_id = reader.read_uint16()
    let sub_offset = reader.read_uint32()
    let abs_offset = offset + sub_offset
    // Format 12 candidates: platformID=0/encodingID>=3, or platformID=3/encodingID=10
    if (platform_id == 0 && encoding_id >= 3) ||
      (platform_id == 3 && encoding_id == 10) {
      format12_offset = abs_offset
    }
    // Format 4 candidates: platformID=0, or platformID=3/encodingID=1
    if platform_id == 0 || (platform_id == 3 && encoding_id == 1) {
      format4_offset = abs_offset
    }
    ignore(i)
  }
  // Prefer Format 12
  if format12_offset >= 0 {
    reader.seek(format12_offset)
    let format = reader.read_uint16()
    if format == 12 {
      parse_cmap_format12(reader, format12_offset, result)
      return result
    }
  }
  // Fallback to Format 4
  if format4_offset >= 0 {
    reader.seek(format4_offset)
    let format = reader.read_uint16()
    if format == 4 {
      parse_cmap_format4(reader, format4_offset, result)
      return result
    }
  }
  // Fallback: scan for Format 0 or Format 6
  for i = 0; i < num_tables; i = i + 1 {
    reader.seek(offset + 4 + i * 8)
    let _pid = reader.read_uint16()
    let _eid = reader.read_uint16()
    let sub_offset = reader.read_uint32()
    let abs_offset = offset + sub_offset
    reader.seek(abs_offset)
    let format = reader.read_uint16()
    if format == 0 && result.is_empty() {
      parse_cmap_format0(reader, abs_offset, result)
      return result
    }
    if format == 6 && result.is_empty() {
      parse_cmap_format6(reader, abs_offset, result)
      return result
    }
  }
  result
}

///|
fn parse_cmap_format12(
  reader : BinaryReader,
  subtable_offset : Int,
  result : Map[Int, Int],
) -> Unit {
  // Format 12: format(u16=12) + reserved(u16) + length(u32) + language(u32) + numGroups(u32)
  reader.seek(subtable_offset + 4) // skip format(u16) + reserved(u16)
  let _length = reader.read_uint32()
  let _language = reader.read_uint32()
  let num_groups = reader.read_uint32()
  for i = 0; i < num_groups; i = i + 1 {
    let start_char_code = reader.read_uint32()
    let end_char_code = reader.read_uint32()
    let start_glyph_id = reader.read_uint32()
    for cp = start_char_code; cp <= end_char_code; cp = cp + 1 {
      let gid = start_glyph_id + (cp - start_char_code)
      if gid != 0 {
        result[cp] = gid
      }
    }
    ignore(i)
  }
}

///|
fn parse_cmap_format4(
  reader : BinaryReader,
  subtable_offset : Int,
  result : Map[Int, Int],
) -> Unit {
  reader.seek(subtable_offset + 2)
  let _length = reader.read_uint16()
  let _language = reader.read_uint16()
  let seg_count_x2 = reader.read_uint16()
  let seg_count = seg_count_x2 / 2
  let _search_range = reader.read_uint16()
  let _entry_selector = reader.read_uint16()
  let _range_shift = reader.read_uint16()
  let end_codes = Array::new(capacity=seg_count)
  for i = 0; i < seg_count; i = i + 1 {
    end_codes.push(reader.read_uint16())
    ignore(i)
  }
  let _reserved_pad = reader.read_uint16()
  let start_codes = Array::new(capacity=seg_count)
  for i = 0; i < seg_count; i = i + 1 {
    start_codes.push(reader.read_uint16())
    ignore(i)
  }
  let id_deltas = Array::new(capacity=seg_count)
  for i = 0; i < seg_count; i = i + 1 {
    id_deltas.push(reader.read_int16())
    ignore(i)
  }
  let id_range_offset_base = reader.position()
  let id_range_offsets = Array::new(capacity=seg_count)
  for i = 0; i < seg_count; i = i + 1 {
    id_range_offsets.push(reader.read_uint16())
    ignore(i)
  }
  for seg = 0; seg < seg_count; seg = seg + 1 {
    let start = start_codes[seg]
    let end = end_codes[seg]
    if start == 0xFFFF {
      // skip sentinel segment (avoid `continue seg` - MoonBit JS codegen bug)
    } else {
      for cp = start; cp <= end; cp = cp + 1 {
        let glyph_id = if id_range_offsets[seg] == 0 {
          (cp + id_deltas[seg]) % 65536
        } else {
          let range_offset_addr = id_range_offset_base + seg * 2
          let glyph_data_offset = range_offset_addr +
            id_range_offsets[seg] +
            (cp - start) * 2
          let gid = reader.peek_uint16_at(glyph_data_offset)
          if gid != 0 {
            (gid + id_deltas[seg]) % 65536
          } else {
            0
          }
        }
        if glyph_id != 0 {
          result[cp] = glyph_id
        }
      }
    }
  }
}

///|
/// Format 0: 256-byte mapping table (Mac Roman)
fn parse_cmap_format0(
  reader : BinaryReader,
  subtable_offset : Int,
  result : Map[Int, Int],
) -> Unit {
  // format(u16=0) + length(u16) + language(u16) + glyphIdArray[256]
  reader.seek(subtable_offset + 6) // skip format + length + language
  for cp = 0; cp < 256; cp = cp + 1 {
    let gid = reader.read_uint8()
    if gid != 0 {
      result[cp] = gid
    }
  }
}

///|
/// Format 6: trimmed table mapping
fn parse_cmap_format6(
  reader : BinaryReader,
  subtable_offset : Int,
  result : Map[Int, Int],
) -> Unit {
  // format(u16=6) + length(u16) + language(u16) + firstCode(u16) + entryCount(u16) + glyphIdArray
  reader.seek(subtable_offset + 6) // skip format + length + language
  let first_code = reader.read_uint16()
  let entry_count = reader.read_uint16()
  for i = 0; i < entry_count; i = i + 1 {
    let gid = reader.read_uint16()
    if gid != 0 {
      result[first_code + i] = gid
    }
  }
}