// 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
  raster_scale : Double
}

///|
extern "js" fn webgpu_text_raster_scale() -> Double =
  #| () => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   return Math.max(1, Number(rt?.outputScale) || Number(globalThis.devicePixelRatio) || 1);
  #| }

///|
extern "js" fn webgpu_clear_text_textures() -> Unit =
  #| () => {
  #|   const rt = globalThis.__selene_webgpu_runtime;
  #|   if (!rt?.textCache) return;
  #|   for (const entry of rt.textCache.values()) {
  #|     try { entry?.texture?.destroy?.(); } catch (_err) {}
  #|   }
  #|   rt.textCache.clear();
  #| }

///|
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([])

///|
let current_text_raster_scale : Ref[Double] = Ref(1.0)

///|
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 text_cache_key(
  command : @render2d_types.TextDrawCommand2D,
  raster_scale : Double,
) -> String {
  let color = command.style.color
  let a = alpha_to_byte(color.a).to_int()
  let align = match command.style.align {
    Left => "left"
    Center => "center"
    Right => "right"
  }
  let mut key = "t:" +
    command.text.length().to_string() +
    ":" +
    command.text +
    "|f:" +
    command.style.family.length().to_string() +
    ":" +
    command.style.family +
    "|s:" +
    command.style.size.to_string() +
    "|w:" +
    command.style.weight.value().to_string() +
    "|rs:" +
    raster_scale.to_string() +
    "|r:" +
    color.r.to_string() +
    "|g:" +
    color.g.to_string() +
    "|b:" +
    color.b.to_string() +
    "|a:" +
    a.to_string() +
    "|lh:" +
    command.style.line_height.to_string() +
    "|ls:" +
    command.style.letter_spacing.to_string() +
    "|mw:" +
    command.max_width.map_or("none", fn(value) { value.to_string() }) +
    "|mh:" +
    command.max_height.map_or("none", fn(value) { value.to_string() }) +
    "|wrap:" +
    command.wrap.to_string() +
    "|ellipsis:" +
    command.ellipsis.to_string() +
    "|align:" +
    align
  for section in command.sections {
    key = key +
      "|section:" +
      section.text.length().to_string() +
      ":" +
      section.text +
      ":" +
      section.style.family +
      ":" +
      section.style.size.to_string() +
      ":" +
      section.style.weight.value().to_string() +
      ":" +
      section.style.line_height.to_string() +
      ":" +
      section.style.letter_spacing.to_string() +
      ":" +
      section.style.color.r.to_string() +
      ":" +
      section.style.color.g.to_string() +
      ":" +
      section.style.color.b.to_string() +
      ":" +
      section.style.color.a.to_string() +
      ":" +
      section.underline.to_string()
  }
  key
}

///|
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()
  webgpu_clear_text_textures()
  true
}

///|
fn rasterize_with_cosmic(
  command : @render2d_types.TextDrawCommand2D,
  raster_scale : Double,
) -> CosmicRasterImage? {
  if !cosmic_loaded_families.contains(command.style.family) {
    return None
  }
  for section in command.sections {
    if !cosmic_loaded_families.contains(section.style.family) {
      return None
    }
  }
  let buffer = @text_pipeline.compute_text_block(
    command,
    cosmic_font_system.val,
    raster_scale,
  ).buffer()
  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, 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 (r, g, b, a) = color.as_rgba()
      points.push((x, y, r.to_int(), g.to_int(), b.to_int(), 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) = (point.2, point.3, point.4, point.5)
    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 == "" && command.sections.is_empty() {
    return true
  }
  if !ensure_font_loaded(command.style.family) &&
    !cosmic_loaded_families.contains(command.style.family) {
    return false
  }
  for section in command.sections {
    if !ensure_font_loaded(section.style.family) &&
      !cosmic_loaded_families.contains(section.style.family) {
      return false
    }
  }
  let raster_scale = webgpu_text_raster_scale()
  if (raster_scale - current_text_raster_scale.val).abs() > 0.00001 {
    cosmic_text_cache.clear()
    webgpu_clear_text_textures()
    cosmic_swash_cache.val = @cosmic.SwashCache::new()
    current_text_raster_scale.val = raster_scale
  }
  let key = text_cache_key(command, raster_scale)
  if !cosmic_text_cache.contains(key) {
    match rasterize_with_cosmic(command, raster_scale) {
      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,
          raster_scale,
        })
      }
      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.raster_scale,
          entry.height.to_double() / entry.raster_scale,
        ),
        texture_pipeline2d_code(1, blend_mode_to_code(command.blend_mode)),
      )
      true
    }
    None => false
  }
}