///|
priv struct SkiaGlyphRun {
  glyphs : Array[@skia_native.GlyphId]
  positions : Array[@moui_skia.Point]
  clusters : Array[Int]
  width : Double
}

///|
fn skia_measure_text_width(font : @skia_native.Font, text : String) -> Double {
  font.measure_text_utf8(@utf8.encode(text[:], bom=false)).to_double()
}

///|
fn skia_draw_text_glyph_run(
  font : @skia_native.Font,
  text : String,
) -> SkiaGlyphRun {
  skia_glyph_run_with_shaper(font, text, use_shaper=false)
}

///|
fn skia_glyph_run_for_resolution(
  font : @skia_native.Font,
  text : String,
  resolution : SkiaFontResolution,
) -> SkiaGlyphRun {
  match resolution {
    SkiaFontResolution::SystemFontMgr =>
      skia_glyph_run_with_shaper(font, text, use_shaper=true)
    SkiaFontResolution::EmptyTypeface => skia_draw_text_glyph_run(font, text)
  }
}

///|
fn skia_glyph_run_with_shaper(
  font : @skia_native.Font,
  text : String,
  use_shaper~ : Bool,
) -> SkiaGlyphRun {
  let bytes = @utf8.encode(text[:], bom=false)
  if use_shaper && @skia_native.skia_shaper_available() {
    match font.shape_text_utf8(bytes) {
      Some(run) => {
        let glyphs = run.glyphs()
        let positions = run.positions()
        if !glyphs.is_empty() && glyphs.length() == positions.length() {
          let clusters = run.clusters()
          if clusters.length() == glyphs.length() {
            return {
              glyphs,
              positions,
              clusters,
              width: run.advance().x.to_double(),
            }
          }
        }
      }
      None => ()
    }
  }
  let glyphs = font.text_to_glyphs_utf8(bytes)
  {
    glyphs,
    positions: font.glyph_positions(glyphs),
    clusters: [],
    width: font.measure_text_utf8(bytes).to_double(),
  }
}

///|
fn skia_glyph_run_has_visible_glyphs(run : SkiaGlyphRun) -> Bool {
  if run.glyphs.is_empty() || run.width <= 0.0 {
    return false
  }
  run.glyphs.any(glyph => glyph.to_int() != 0)
}

///|
fn skia_glyph_run_missing_glyph_count(run : SkiaGlyphRun) -> Int {
  let mut count = 0
  for glyph in run.glyphs {
    if glyph.to_int() == 0 {
      count = count + 1
    }
  }
  count
}

///|
fn skia_glyph_run_can_draw_text(run : SkiaGlyphRun) -> Bool {
  skia_glyph_run_has_visible_glyphs(run) &&
  skia_glyph_run_missing_glyph_count(run) == 0
}

///|
fn skia_glyph_run_improves_missing_glyphs(
  primary_run : SkiaGlyphRun,
  fallback_run : SkiaGlyphRun,
) -> Bool {
  if !skia_glyph_run_has_visible_glyphs(fallback_run) {
    return false
  }
  if !skia_glyph_run_has_visible_glyphs(primary_run) {
    return true
  }
  skia_glyph_run_missing_glyph_count(fallback_run) <
  skia_glyph_run_missing_glyph_count(primary_run)
}

///|
fn skia_fallback_draw_payload(
  font : @core.FontSpec,
  text : String,
  resolution : SkiaFontResolution,
  primary_run : SkiaGlyphRun,
) -> (@skia_native.Font, SkiaGlyphRun)? {
  match skia_emoji_fallback_draw_font(font, text, resolution) {
    Some(emoji_font) =>
      match skia_complete_fallback_payload(emoji_font, text, resolution) {
        Some(payload) => return Some(payload)
        None => ()
      }
    None => ()
  }
  match skia_default_fallback_draw_font(font) {
    Some(fallback_font) =>
      skia_recovering_fallback_payload(
        fallback_font, text, resolution, primary_run,
      )
    None => None
  }
}

///|
fn skia_complete_fallback_payload(
  font : @skia_native.Font,
  text : String,
  resolution : SkiaFontResolution,
) -> (@skia_native.Font, SkiaGlyphRun)? {
  let fallback_run = skia_glyph_run_for_resolution(font, text, resolution)
  if skia_glyph_run_can_draw_text(fallback_run) {
    Some((font, fallback_run))
  } else {
    None
  }
}

///|
fn skia_recovering_fallback_payload(
  font : @skia_native.Font,
  text : String,
  resolution : SkiaFontResolution,
  primary_run : SkiaGlyphRun,
) -> (@skia_native.Font, SkiaGlyphRun)? {
  let fallback_run = skia_glyph_run_for_resolution(font, text, resolution)
  if skia_glyph_run_can_draw_text(fallback_run) ||
    skia_glyph_run_improves_missing_glyphs(primary_run, fallback_run) {
    Some((font, fallback_run))
  } else {
    None
  }
}

///|
fn skia_emoji_fallback_draw_font(
  font : @core.FontSpec,
  text : String,
  resolution : SkiaFontResolution,
) -> @skia_native.Font? {
  match resolution {
    SkiaFontResolution::SystemFontMgr =>
      if skia_text_contains_emoji_hint(text) {
        skia_system_emoji_font(font)
      } else {
        None
      }
    SkiaFontResolution::EmptyTypeface => None
  }
}

///|
fn skia_system_emoji_font(font : @core.FontSpec) -> @skia_native.Font? {
  match @skia_native.FontMgr::default() {
    Some(font_mgr) => {
      let emoji_font = @core.FontSpec::new(
        families=@core.FontFamilyStack::new([@core.FontFamily::Emoji]),
        size=font.size,
        weight=font.weight,
        style=font.style,
      )
      match skia_typeface_from_font_mgr(font_mgr, emoji_font) {
        Some(typeface) =>
          @skia_native.Font::from_typeface(
            typeface,
            size=Float::from_double(font.size),
          )
        None => None
      }
    }
    None => None
  }
}

///|
fn skia_default_fallback_draw_font(font : @core.FontSpec) -> @skia_native.Font? {
  @skia_native.Font::default(size=Float::from_double(font.size))
}

///|
fn skia_text_contains_emoji_hint(text : String) -> Bool {
  for ch in text {
    if @render_common.codepoint_is_emoji_hint(ch.to_int()) {
      return true
    }
  }
  false
}

///|
fn skia_codepoint_is_hebrew_script(codepoint : Int) -> Bool {
  codepoint >= 0x0590 && codepoint <= 0x05FF
}

///|
fn skia_codepoint_is_arabic_script(codepoint : Int) -> Bool {
  (codepoint >= 0x0600 && codepoint <= 0x08FF) ||
  (codepoint >= 0xFB50 && codepoint <= 0xFDFF) ||
  (codepoint >= 0xFE70 && codepoint <= 0xFEFF)
}

///|
fn skia_codepoint_is_devanagari_script(codepoint : Int) -> Bool {
  codepoint >= 0x0900 && codepoint <= 0x097F
}

///|
fn skia_codepoint_is_thai_script(codepoint : Int) -> Bool {
  codepoint >= 0x0E00 && codepoint <= 0x0E7F
}

///|
fn skia_codepoint_is_lao_script(codepoint : Int) -> Bool {
  codepoint >= 0x0E80 && codepoint <= 0x0EFF
}

///|
fn skia_codepoint_is_sinhala_script(codepoint : Int) -> Bool {
  codepoint >= 0x0D80 && codepoint <= 0x0DFF
}

///|
fn skia_codepoint_is_khmer_script(codepoint : Int) -> Bool {
  codepoint >= 0x1780 && codepoint <= 0x17FF
}

///|
fn skia_codepoint_is_myanmar_script(codepoint : Int) -> Bool {
  codepoint >= 0x1000 && codepoint <= 0x109F
}

///|
fn skia_codepoint_is_latin_script(codepoint : Int) -> Bool {
  (codepoint >= 0x0041 && codepoint <= 0x005A) ||
  (codepoint >= 0x0061 && codepoint <= 0x007A) ||
  (codepoint >= 0x00C0 && codepoint <= 0x024F) ||
  (codepoint >= 0x1E00 && codepoint <= 0x1EFF)
}

///|
fn skia_font_with_resolution_for_text(
  font : @core.FontSpec,
  text : String,
  resolution : SkiaFontResolution,
) -> @skia_native.Font? {
  match resolution {
    SkiaFontResolution::EmptyTypeface =>
      @skia_native.Font::empty(size=Float::from_double(font.size))
    SkiaFontResolution::SystemFontMgr =>
      match skia_typeface_for_text(font, text) {
        Some(typeface) => {
          let typeface = match
            typeface.with_variations(skia_variation_pairs(font)) {
            Some(varied) => varied
            None => typeface
          }
          @skia_native.Font::from_typeface(
            typeface,
            size=Float::from_double(font.size),
          )
        }
        None => @skia_native.Font::default(size=Float::from_double(font.size))
      }
  }
}

///|
fn skia_typeface_for_text(
  font : @core.FontSpec,
  text : String,
) -> @skia_native.Typeface? {
  match skia_typeface_from_embedded(font) {
    Some(typeface) => return Some(typeface)
    None => ()
  }
  match skia_typeface_from_font_files(font) {
    Some(typeface) => return Some(typeface)
    None => ()
  }
  match @skia_native.FontMgr::default() {
    Some(font_mgr) => skia_typeface_from_font_mgr_for_text(font_mgr, font, text)
    None => None
  }
}

///|
fn skia_typeface_from_font_mgr_for_text(
  font_mgr : @skia_native.FontMgr,
  font : @core.FontSpec,
  text : String,
) -> @skia_native.Typeface? {
  let request = skia_font_fallback_request(font, text)
  if !request.is_empty() {
    match font_mgr.match_fallback_request(request) {
      Some(typeface) => return Some(typeface)
      None => ()
    }
  }
  skia_typeface_from_font_mgr(font_mgr, font)
}

///|
fn skia_typeface_from_font_mgr(
  font_mgr : @skia_native.FontMgr,
  font : @core.FontSpec,
) -> @skia_native.Typeface? {
  for family in font.families.families {
    for family_name in skia_font_family_candidates(family) {
      match
        font_mgr.match_family_style(
          @utf8.encode(family_name[:], bom=false),
          weight=font.weight,
          width=5,
          slant=skia_font_slant(font.style),
        ) {
        Some(typeface) => return Some(typeface)
        None => ()
      }
    }
  }
  None
}

///|
fn skia_font_fallback_request(
  font : @core.FontSpec,
  text : String,
) -> @moui_skia.FontFallbackRequest {
  @moui_skia.FontFallbackRequest::new(
    families=skia_font_fallback_chain(font),
    bcp47=skia_font_fallback_language_bytes(text),
    character=skia_representative_fallback_codepoint(text),
    style=skia_font_style_request(font),
  )
}

///|
fn skia_font_fallback_language_bytes(text : String) -> Array[Bytes] {
  let tags = skia_font_fallback_language_tags(text)
  let bytes : Array[Bytes] = []
  for tag in tags {
    bytes.push(@utf8.encode(tag[:], bom=false))
  }
  bytes
}

///|
fn skia_font_fallback_language_tags(text : String) -> Array[String] {
  let tags : Array[String] = []
  let representative = skia_representative_fallback_codepoint(text)
  match skia_script_language_tag_for_codepoint(representative) {
    Some(tag) => skia_push_language_tag(tags, tag)
    None => ()
  }
  if representative > 0 {
    skia_push_language_tag(tags, "en")
  }
  tags
}

///|
fn skia_push_language_tag(tags : Array[String], tag : String) -> Unit {
  if tag != "" && !tags.contains(tag) {
    tags.push(tag)
  }
}

///|
fn skia_script_language_tag_for_codepoint(codepoint : Int) -> String? {
  if codepoint <= 0 {
    None
  } else if @render_common.codepoint_is_emoji_hint(codepoint) {
    Some("und-Zsye")
  } else if @render_common.codepoint_is_kana(codepoint) {
    Some("ja")
  } else if @render_common.codepoint_is_cjk_ideograph(codepoint) {
    Some("zh-Hans")
  } else if @render_common.codepoint_is_hangul_cluster_codepoint(codepoint) ||
    @render_common.codepoint_is_hangul_compatibility_jamo(codepoint) {
    Some("ko")
  } else if skia_codepoint_is_hebrew_script(codepoint) {
    Some("he")
  } else if skia_codepoint_is_arabic_script(codepoint) {
    Some("ar")
  } else if skia_codepoint_is_devanagari_script(codepoint) {
    Some("hi")
  } else if skia_codepoint_is_thai_script(codepoint) {
    Some("th")
  } else if skia_codepoint_is_lao_script(codepoint) {
    Some("lo")
  } else if skia_codepoint_is_sinhala_script(codepoint) {
    Some("si")
  } else if skia_codepoint_is_khmer_script(codepoint) {
    Some("km")
  } else if skia_codepoint_is_myanmar_script(codepoint) {
    Some("my")
  } else if skia_codepoint_is_latin_script(codepoint) {
    Some("en")
  } else {
    None
  }
}

///|
fn skia_font_fallback_chain(
  font : @core.FontSpec,
) -> @moui_skia.FontFallbackChain {
  let families = skia_preflight_family_candidates(font)
  let primary_family = if families.is_empty() {
    b"system-ui"
  } else {
    @utf8.encode(families[0][:], bom=false)
  }
  let fallback_families : Array[Bytes] = []
  if families.length() > 1 {
    for index in 1.. @moui_skia.FontStyleRequest {
  @moui_skia.FontStyleRequest::new(
    weight=font.weight,
    width=5,
    slant=skia_font_slant(font.style),
  )
}

///|
fn skia_font_family_candidates(family : @core.FontFamily) -> Array[String] {
  match family {
    Name(name) => [name]
    SystemUi | UiSansSerif | SansSerif => skia_system_sans_families()
    UiSerif | Serif => skia_serif_families()
    UiMonospace | Monospace => skia_monospace_families()
    Emoji => skia_emoji_families()
    Math => skia_serif_families()
    FangSong => skia_fangsong_families()
  }
}

///|
fn skia_system_sans_families() -> Array[String] {
  [
    "Helvetica Neue", "Segoe UI", "Arial", "HarmonyOS Sans", "HarmonyOS Sans SC",
    "DejaVu Sans", "Noto Sans", "Noto Sans CJK SC", "Liberation Sans",
  ]
}

///|
fn skia_serif_families() -> Array[String] {
  [
    "Times New Roman", "Georgia", "DejaVu Serif", "Noto Serif", "Liberation Serif",
  ]
}

///|
fn skia_monospace_families() -> Array[String] {
  [
    "Menlo", "Consolas", "Courier New", "DejaVu Sans Mono", "Noto Sans Mono", "Liberation Mono",
  ]
}

///|
fn skia_emoji_families() -> Array[String] {
  [
    "Apple Color Emoji", "Segoe UI Emoji", "HMOS Color Emoji", "HarmonyOS Color Emoji",
    "Noto Color Emoji",
  ]
}

///|
fn skia_fangsong_families() -> Array[String] {
  ["STFangsong", "FangSong", "FangSong SC", "SimSun"]
}

///|
fn skia_font_slant(style : @core.FontStyle) -> Int {
  match style {
    Normal => 0
    Italic => 1
  }
}