// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
priv struct CosmicRasterImage {
  width : Int
  height : Int
  pixels : Array[Int]
}

///|
priv struct CosmicTextCacheEntry {
  width : Int
  height : Int
}

///|
let cosmic_font_system : Ref[@cosmic.FontSystem] = Ref(
  @cosmic.FontSystem::new(),
)

///|
let cosmic_swash_cache : Ref[@cosmic.SwashCache] = Ref(
  @cosmic.SwashCache::new(),
)

///|
let cosmic_loaded_families : Map[String, Bool] = Map([])

///|
let cosmic_text_cache : Map[String, CosmicTextCacheEntry] = Map([])

///|
fn saturating_byte(value : Int) -> Byte {
  if value < 0 {
    Int::to_byte(0)
  } else if value > 255 {
    Int::to_byte(255)
  } else {
    value.to_byte()
  }
}

///|
fn alpha_to_byte(alpha : Double) -> Byte {
  saturating_byte((alpha * 255.0).round().to_int())
}

///|
fn to_cosmic_color(color : @render.Color) -> @cosmic.Color {
  @cosmic.Color::rgba(
    color.r.to_byte(),
    color.g.to_byte(),
    color.b.to_byte(),
    alpha_to_byte(color.a),
  )
}

///|
fn color_channel_to_int(channel : UInt) -> Int {
  saturating_byte(channel.reinterpret_as_int()).to_int()
}

///|
fn styled_text_pixel_rgba(
  color : @render.Color,
  coverage_alpha : Int,
) -> (Int, Int, Int, Int) {
  (
    color_channel_to_int(color.r),
    color_channel_to_int(color.g),
    color_channel_to_int(color.b),
    saturating_byte(coverage_alpha).to_int(),
  )
}

///|
fn text_cache_key(command : @render2d_types.TextDrawCommand2D) -> String {
  let color = command.style.color
  let a = alpha_to_byte(color.a).to_int()
  "t:" +
  command.text.length().to_string() +
  ":" +
  command.text +
  "|f:" +
  command.style.family.length().to_string() +
  ":" +
  command.style.family +
  "|s:" +
  command.style.size.to_string() +
  "|r:" +
  color.r.to_string() +
  "|g:" +
  color.g.to_string() +
  "|b:" +
  color.b.to_string() +
  "|a:" +
  a.to_string()
}

///|
fn ensure_font_loaded(family : String) -> Bool {
  if cosmic_loaded_families.contains(family) {
    return true
  }
  if webgpu_font_bytes_status(family) != 1 {
    return false
  }
  let raw_bytes = webgpu_take_font_bytes(family)
  if raw_bytes.is_empty() {
    return false
  }
  let bytes = Bytes::from_iter(
    raw_bytes.iter().map(fn(value) { saturating_byte(value) }),
  )
  cosmic_font_system.val = cosmic_font_system.val.load_font_data(bytes)
  cosmic_loaded_families.set(family, true)
  cosmic_text_cache.clear()
  true
}

///|
fn measure_text_with_cosmic(
  style : @render2d_types.TextStyle2D,
  text : String,
) -> @smath.Vec2? {
  guard ensure_font_loaded(style.family) else { return None }
  let font_size = if style.size < 1.0 { 1.0 } else { style.size }
  if text == "" {
    return Some(Vec2(0.0, font_size))
  }
  let metrics = @cosmic.Metrics::new(
    Float::from_double(font_size),
    Float::from_double(font_size * 1.25),
  )
  let attrs = @cosmic.Attrs::new().family(Name(style.family))
  let attrs_list = @cosmic.AttrsList::new(attrs)
  let buffer = @cosmic.Buffer::new(metrics)
    .set_wrap(None)
    .set_text(text, attrs_list, Advanced)
    .set_size(None, None)
    .layout_all_with_font_system(cosmic_font_system.val)
  let mut width = 0.0
  let mut height = 0.0
  for run in buffer.layout_runs() {
    if run.line_w.to_double() > width {
      width = run.line_w.to_double()
    }
    let run_bottom = (run.line_top + run.line_height).to_double()
    if run_bottom > height {
      height = run_bottom
    }
  }
  if height <= 0.0 {
    height = font_size
  }
  Some(Vec2(width, height))
}

///|
fn rasterize_with_cosmic(
  command : @render2d_types.TextDrawCommand2D,
) -> CosmicRasterImage? {
  let family = command.style.family
  if !cosmic_loaded_families.contains(family) {
    return None
  }
  let font_size = if command.style.size < 1.0 {
    1.0
  } else {
    command.style.size
  }
  let metrics = @cosmic.Metrics::new(
    Float::from_double(font_size),
    Float::from_double(font_size * 1.25),
  )
  let attrs = @cosmic.Attrs::new().family(Name(family))
  let attrs_list = @cosmic.AttrsList::new(attrs)
  let buffer = @cosmic.Buffer::new(metrics)
    .set_wrap(None)
    .set_text(command.text, attrs_list, Advanced)
    .set_size(None, None)
  let mut min_x = 0
  let mut min_y = 0
  let mut max_x = -1
  let mut max_y = -1
  let points : Array[(Int, Int, Int)] = []
  let (drawn_buffer, swash_cache) = buffer.draw(
    cosmic_font_system.val,
    cosmic_swash_cache.val,
    to_cosmic_color(command.style.color),
    fn(x, y, _w, _h, color) {
      let (_, _, _, a) = color.as_rgba()
      points.push((x, y, a.to_int()))
      if max_x < min_x {
        min_x = x
        max_x = x
        min_y = y
        max_y = y
      } else {
        if x < min_x {
          min_x = x
        }
        if x > max_x {
          max_x = x
        }
        if y < min_y {
          min_y = y
        }
        if y > max_y {
          max_y = y
        }
      }
    },
  )
  ignore(drawn_buffer)
  cosmic_swash_cache.val = swash_cache
  if points.is_empty() {
    return None
  }
  let width = max_x - min_x + 1
  let height = max_y - min_y + 1
  let pixels = Array::make(width * height * 4, 0)
  for point in points {
    let x = point.0
    let y = point.1
    let (r, g, b, a) = styled_text_pixel_rgba(command.style.color, point.2)
    let local_x = x - min_x
    let local_y = y - min_y
    let idx = (local_y * width + local_x) * 4
    pixels[idx] = r
    pixels[idx + 1] = g
    pixels[idx + 2] = b
    pixels[idx + 3] = a
  }
  Some({ width, height, pixels })
}

///|
fn draw_text_with_cosmic(command : @render2d_types.TextDrawCommand2D) -> Bool {
  if command.text == "" {
    return true
  }
  if !ensure_font_loaded(command.style.family) &&
    !cosmic_loaded_families.contains(command.style.family) {
    return false
  }
  let key = text_cache_key(command)
  if !cosmic_text_cache.contains(key) {
    match rasterize_with_cosmic(command) {
      Some(image) => {
        if !webgpu_upload_text_texture(
            key,
            image.width,
            image.height,
            image.pixels,
          ) {
          return false
        }
        cosmic_text_cache.set(key, { width: image.width, height: image.height })
      }
      None => return false
    }
  }
  match cosmic_text_cache.get(key) {
    Some(entry) => {
      webgpu_push_cached_text_vertices_2d(
        key,
        pack_text_vertices2d(
          command,
          entry.width.to_double(),
          entry.height.to_double(),
        ),
        texture_pipeline2d_code(1, blend_mode_to_code(command.blend_mode)),
      )
      true
    }
    None => false
  }
}