///|
/// Text contracts — GPU text rendering wrapper over mizchi/font.
///
/// GPU-independent types and logic are in mizchi/font.
/// This module adds GPU-specific rendering (draw commands, batch building).

///|
pub trait FontEngine {
  measure(Self, run : @font.TextRun) -> @font.TextMetrics
  shape(Self, run : @font.TextRun) -> Array[@font.GlyphQuad] raise
}

///|
pub trait TextBatchBuilder {
  build_draw_commands(
    Self,
    target : @gfx.ImageHandle,
    glyphs : Array[@font.GlyphQuad],
    shader : @gfx.ShaderHandle,
  ) -> Array[@gfx.DrawTrianglesCommand] raise
}

///|
/// Parse font from raw bytes (auto-detects TTF/OTF/WOFF1/WOFF2).
pub fn parse_font_bytes(data : Bytes) -> @font.TTFont? {
  @font.parse_font(data)
}

///|
/// Load font engine from raw bytes.
pub fn load_font_engine_from_bytes(data : Bytes) -> SimpleFontEngine? {
  match @font.parse_font(data) {
    Some(font) => Some(SimpleFontEngine::new(font))
    None => None
  }
}

///|
/// GPU-aware text renderer wrapping @font.TextRenderer.
/// Adds render_text() which produces @gfx.DrawTrianglesCommand.
pub struct TextRenderer {
  inner : @font.TextRenderer
}

///|
pub fn TextRenderer::new(
  font : @font.TTFont,
  atlas_size : Int,
  base_page_id : Int,
  pipeline_id : Int,
) -> TextRenderer {
  {
    inner: @font.TextRenderer::new(font, atlas_size, base_page_id, pipeline_id),
  }
}

///|
pub fn TextRenderer::new_with_options(
  font : @font.TTFont,
  atlas_size : Int,
  base_page_id : Int,
  pipeline_id : Int,
  max_pages : Int,
) -> TextRenderer {
  {
    inner: @font.TextRenderer::new_with_options(
      font, atlas_size, base_page_id, pipeline_id, max_pages,
    ),
  }
}

///|
/// Render text at the given position, returning draw commands ready
/// for the graphics pipeline. Also rasterizes any new glyphs into the atlas.
pub fn TextRenderer::render_text(
  self : TextRenderer,
  text : String,
  size_px : Double,
  x : Double,
  y : Double,
  dst : @gfx.ImageHandle,
  shader : @gfx.ShaderHandle,
  blend : @gfx.BlendMode,
  uniform_dwords : Array[Int],
) -> Array[@gfx.DrawTrianglesCommand] {
  let font = self.inner.font
  let scale = size_px / font.units_per_em.to_double()
  let positions = font.layout_text(text, size_px)
  // Group: page_index -> Array[GlyphQuad]
  let page_quads : Map[Int, Array[@font.GlyphQuad]] = {}
  for pos in positions {
    if pos.glyph_id == 0 {
      continue
    }
    match self.inner.ensure_glyph(pos.codepoint, size_px) {
      None => continue
      Some((page_idx, entry)) => {
        let metrics = font.glyph_metrics(pos.glyph_id)
        let bbox = metrics.bbox
        let cache_key = @font.make_glyph_cache_key(pos.glyph_id, size_px)
        let dst_x = pos.x_offset +
          metrics.left_side_bearing.to_double() * scale +
          x
        let dst_y = -(bbox.y_max.to_double() * scale) + y
        let quad : @font.GlyphQuad = {
          glyph_id: cache_key,
          atlas_x: entry.atlas_x,
          atlas_y: entry.atlas_y,
          atlas_w: entry.atlas_w,
          atlas_h: entry.atlas_h,
          dst_x,
          dst_y,
          dst_w: entry.atlas_w,
          dst_h: entry.atlas_h,
        }
        match page_quads.get(page_idx) {
          Some(arr) => arr.push(quad)
          None => page_quads.set(page_idx, [quad])
        }
      }
    }
  }
  // Build draw commands per page
  let all_commands : Array[@gfx.DrawTrianglesCommand] = []
  page_quads.each(fn(page_idx, quads) {
    if quads.length() == 0 {
      return
    }
    let page = self.inner.get_page(page_idx)
    let builder = SimpleTextBatchBuilder::new(
      page.get_cache(),
      self.inner.pipeline_id,
    )
    let commands = builder.build_draw_commands(dst, quads, shader) catch {
      _ => return
    }
    if uniform_dwords.length() > 0 {
      for cmd in commands {
        all_commands.push(
          @gfx.new_draw_triangles_command(
            cmd.dst,
            cmd.shader,
            cmd.dst_regions,
            cmd.index_offset,
            cmd.pipeline_id,
            cmd.uniform_hash,
            blend,
            cmd.vertex_data,
            cmd.indices,
            cmd.src_image_ids,
            uniform_dwords,
          ),
        )
      }
    } else {
      for cmd in commands {
        all_commands.push(cmd)
      }
    }
  })
  all_commands
}

///|
/// Measure text dimensions without rasterizing.
pub fn TextRenderer::measure(
  self : TextRenderer,
  text : String,
  size_px : Double,
) -> @font.TextMetrics {
  self.inner.measure(text, size_px)
}

///|
pub fn TextRenderer::get_atlas(self : TextRenderer) -> @font.GlyphAtlas {
  self.inner.get_atlas()
}

///|
pub fn TextRenderer::get_page_id(self : TextRenderer) -> Int {
  self.inner.base_page_id
}

///|
pub fn TextRenderer::glyph_count(self : TextRenderer) -> Int {
  self.inner.glyph_count()
}

///|
pub fn TextRenderer::clear(self : TextRenderer) -> Unit {
  self.inner.clear()
}

///|
pub fn TextRenderer::page_count(self : TextRenderer) -> Int {
  self.inner.page_count()
}

///|
pub fn TextRenderer::get_page(
  self : TextRenderer,
  index : Int,
) -> @font.GlyphAtlas {
  self.inner.get_page(index)
}

///|
pub fn TextRenderer::get_page_ids(self : TextRenderer) -> Array[Int] {
  self.inner.get_page_ids()
}

///|
pub fn TextRenderer::flush_dirty_pages(
  self : TextRenderer,
) -> Array[@font.AtlasPageInfo] {
  self.inner.flush_dirty_pages()
}

// ---------------------------------------------------------------------------
// Font Load Hooks — pluggable font binary loading for platform integration
// ---------------------------------------------------------------------------

///|
pub struct FontLoadHooks {
  load_font_data : (String) -> Array[Int]?
}

///|
pub fn new_font_load_hooks(
  load_font_data : (String) -> Array[Int]?,
) -> FontLoadHooks {
  { load_font_data, }
}

///|
fn default_load_font_data(_name : String) -> Array[Int]? {
  None
}

///|
fn default_font_load_hooks() -> FontLoadHooks {
  { load_font_data: default_load_font_data }
}

///|
let font_load_hooks : Ref[FontLoadHooks] = Ref(default_font_load_hooks())

///|
pub fn set_font_load_hooks(hooks : FontLoadHooks) -> Unit {
  font_load_hooks.val = hooks
}

///|
pub fn reset_font_load_hooks() -> Unit {
  font_load_hooks.val = default_font_load_hooks()
}

///|
pub fn font_data_to_bytes(data : Array[Int]) -> Bytes {
  let arr : FixedArray[Byte] = FixedArray::make(data.length(), b'\x00')
  for i, v in data {
    arr[i] = v.to_byte()
  }
  Bytes::from_array(arr)
}

///|
pub fn load_font(name : String) -> @font.TTFont? {
  match (font_load_hooks.val.load_font_data)(name) {
    None => None
    Some(data) => {
      let bytes = font_data_to_bytes(data)
      parse_font_bytes(bytes)
    }
  }
}

///|
pub fn load_text_renderer(
  name : String,
  atlas_size : Int,
  page_id : Int,
  pipeline_id : Int,
) -> TextRenderer? {
  match load_font(name) {
    None => None
    Some(font) =>
      Some(TextRenderer::new(font, atlas_size, page_id, pipeline_id))
  }
}