// 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.

///|
/// MVP subset ported from `cosmic-text/src/attrs.rs`.
///
/// NOTE: upstream `Attrs` contains font family/style/weight/features etc.
/// We start with `metadata` only to support BufferLine behavior tests, and will
/// extend it incrementally to match upstream.

///|
/// Text color (ARGB packed, aligned with upstream).
pub struct Color {
  value : UInt
}

///|
pub impl Eq for Color with equal(self, other) {
  self.value == other.value
}

///|
pub fn Color::rgba(r : Byte, g : Byte, b : Byte, a : Byte) -> Color {
  let av = a.to_int().reinterpret_as_uint()
  let rv = r.to_int().reinterpret_as_uint()
  let gv = g.to_int().reinterpret_as_uint()
  let bv = b.to_int().reinterpret_as_uint()
  Color::{ value: (av << 24) | (rv << 16) | (gv << 8) | bv }
}

///|
pub fn Color::rgb(r : Byte, g : Byte, b : Byte) -> Color {
  Color::rgba(r, g, b, 0xFF)
}

///|
pub fn Color::r(self : Color) -> Byte {
  ((self.value & 0x00FF0000) >> 16).reinterpret_as_int().to_byte()
}

///|
pub fn Color::g(self : Color) -> Byte {
  ((self.value & 0x0000FF00) >> 8).reinterpret_as_int().to_byte()
}

///|
pub fn Color::b(self : Color) -> Byte {
  (self.value & 0x000000FF).reinterpret_as_int().to_byte()
}

///|
pub fn Color::a(self : Color) -> Byte {
  ((self.value & 0xFF000000) >> 24).reinterpret_as_int().to_byte()
}

///|
pub fn Color::as_rgba(self : Color) -> (Byte, Byte, Byte, Byte) {
  (self.r(), self.g(), self.b(), self.a())
}

///|
/// Font family selector (mirrors `fontdb::Family` in cosmic-text).
pub(all) enum Family {
  Name(String)
  Serif
  SansSerif
  Cursive
  Fantasy
  Monospace
}

///|
pub impl Eq for Family with equal(self, other) {
  match (self, other) {
    (Name(a), Name(b)) => a == b
    (Serif, Serif) => true
    (SansSerif, SansSerif) => true
    (Cursive, Cursive) => true
    (Fantasy, Fantasy) => true
    (Monospace, Monospace) => true
    _ => false
  }
}

///|
pub impl Hash for Family with hash_combine(self, hasher) {
  match self {
    Name(name) => {
      hasher.combine_int(0)
      hasher.combine_string(name)
    }
    Serif => hasher.combine_int(1)
    SansSerif => hasher.combine_int(2)
    Cursive => hasher.combine_int(3)
    Fantasy => hasher.combine_int(4)
    Monospace => hasher.combine_int(5)
  }
}

///|
pub impl Show for Family with output(self, logger) {
  match self {
    Name(name) => {
      logger.write_string("Name(\"")
      logger.write_string(name)
      logger.write_string("\")")
    }
    Serif => logger.write_string("Serif")
    SansSerif => logger.write_string("SansSerif")
    Cursive => logger.write_string("Cursive")
    Fantasy => logger.write_string("Fantasy")
    Monospace => logger.write_string("Monospace")
  }
}

///|
/// Font weight (1..=1000). This is a simplified stand-in for `fontdb::Weight`.
pub struct Weight {
  value : Int
}

///|
/// Metrics, but implementing Eq and Hash using u32 representation of f32.
///
/// This mirrors upstream `cosmic-text` CacheMetrics, enabling per-span metrics
/// overrides (font size / line height) while keeping Attrs hashable.
pub struct CacheMetrics {
  font_size_bits : UInt
  line_height_bits : UInt
}

///|
fn attrs_u32_from_be_bytes(b : Bytes) -> UInt {
  let b0 = if b.get(0) is Some(v) { v } else { 0x00 }
  let b1 = if b.get(1) is Some(v) { v } else { 0x00 }
  let b2 = if b.get(2) is Some(v) { v } else { 0x00 }
  let b3 = if b.get(3) is Some(v) { v } else { 0x00 }
  let u0 = b0.to_int().reinterpret_as_uint()
  let u1 = b1.to_int().reinterpret_as_uint()
  let u2 = b2.to_int().reinterpret_as_uint()
  let u3 = b3.to_int().reinterpret_as_uint()
  (u0 << 24) | (u1 << 16) | (u2 << 8) | u3
}

///|
fn attrs_f32_from_bits(bits : UInt) -> Float {
  Float::reinterpret_from_uint(bits)
}

///|
fn float_to_bits(v : Float) -> UInt {
  attrs_u32_from_be_bytes(Float::to_be_bytes(v))
}

///|
pub fn CacheMetrics::from_metrics(metrics : Metrics) -> CacheMetrics {
  CacheMetrics::{
    font_size_bits: float_to_bits(metrics.font_size),
    line_height_bits: float_to_bits(metrics.line_height),
  }
}

///|
pub fn CacheMetrics::to_metrics(self : CacheMetrics) -> Metrics {
  Metrics::new(
    attrs_f32_from_bits(self.font_size_bits),
    attrs_f32_from_bits(self.line_height_bits),
  )
}

///|
pub fn CacheMetrics::font_size(self : CacheMetrics) -> Float {
  attrs_f32_from_bits(self.font_size_bits)
}

///|
pub fn CacheMetrics::line_height(self : CacheMetrics) -> Float {
  attrs_f32_from_bits(self.line_height_bits)
}

///|
pub impl Eq for CacheMetrics with equal(self, other) {
  self.font_size_bits == other.font_size_bits &&
  self.line_height_bits == other.line_height_bits
}

///|
pub impl Hash for CacheMetrics with hash_combine(self, hasher) {
  hasher.combine_uint(self.font_size_bits)
  hasher.combine_uint(self.line_height_bits)
}

///|
pub impl Show for CacheMetrics with output(self, logger) {
  logger.write_string("CacheMetrics{font_size_bits=")
  logger.write_string(self.font_size_bits.to_string())
  logger.write_string(", line_height_bits=")
  logger.write_string(self.line_height_bits.to_string())
  logger.write_string("}")
}

///|
pub fn Weight::new(value : Int) -> Weight {
  Weight::{ value, }
}

///|
pub fn Weight::normal() -> Weight {
  Weight::new(400)
}

///|
pub fn Weight::bold() -> Weight {
  Weight::new(700)
}

///|
pub fn Weight::value(self : Weight) -> Int {
  self.value
}

///|
pub impl Eq for Weight with equal(self, other) {
  self.value == other.value
}

///|
pub impl Hash for Weight with hash_combine(self, hasher) {
  hasher.combine_int(self.value)
}

///|
pub impl Show for Weight with output(self, logger) {
  logger.write_string(self.value.to_string())
}

///|
pub struct Attrs {
  metadata : Int
  family : Family
  weight : Weight
  stretch : @moon_swash.Stretch
  style : @moon_swash.Style
  metrics_opt : CacheMetrics?
}

///|
pub fn Attrs::new() -> Attrs {
  Attrs::with_metadata(0)
}

///|
pub fn Attrs::with_metadata(metadata : Int) -> Attrs {
  Attrs::{
    metadata,
    family: Family::SansSerif,
    weight: Weight::normal(),
    stretch: @moon_swash.Stretch::normal(),
    style: @moon_swash.Style::Normal,
    metrics_opt: None,
  }
}

///|
pub fn Attrs::metadata(self : Attrs) -> Int {
  self.metadata
}

///|
pub fn Attrs::family(self : Attrs, family : Family) -> Attrs {
  Attrs::{ ..self, family, }
}

///|
pub fn Attrs::weight(self : Attrs, weight : Weight) -> Attrs {
  Attrs::{ ..self, weight, }
}

///|
pub fn Attrs::stretch(self : Attrs, stretch : @moon_swash.Stretch) -> Attrs {
  Attrs::{ ..self, stretch, }
}

///|
pub fn Attrs::style(self : Attrs, style : @moon_swash.Style) -> Attrs {
  Attrs::{ ..self, style, }
}

///|
/// Set Metrics, overriding values in Buffer for this span.
pub fn Attrs::metrics(self : Attrs, metrics : Metrics) -> Attrs {
  Attrs::{ ..self, metrics_opt: Some(CacheMetrics::from_metrics(metrics)) }
}

///|
/// Return optional Metrics override.
pub fn Attrs::metrics_opt(self : Attrs) -> Metrics? {
  match self.metrics_opt {
    Option::None => Option::None
    Option::Some(cm) => Option::Some(cm.to_metrics())
  }
}

///|
pub fn Attrs::family_value(self : Attrs) -> Family {
  self.family
}

///|
pub fn Attrs::weight_value(self : Attrs) -> Weight {
  self.weight
}

///|
pub fn Attrs::stretch_value(self : Attrs) -> @moon_swash.Stretch {
  self.stretch
}

///|
pub fn Attrs::style_value(self : Attrs) -> @moon_swash.Style {
  self.style
}

///|
pub impl Eq for Attrs with equal(self, other) {
  self.metadata == other.metadata &&
  self.family == other.family &&
  self.weight == other.weight &&
  self.stretch == other.stretch &&
  self.style == other.style &&
  self.metrics_opt == other.metrics_opt
}

///|
pub impl Hash for Attrs with hash_combine(self, hasher) {
  hasher.combine_int(self.metadata)
  self.family.hash_combine(hasher)
  self.weight.hash_combine(hasher)
  hasher.combine_int(self.stretch.raw().to_int())
  hasher.combine_string(self.style.to_string())
  match self.metrics_opt {
    Option::None => hasher.combine_int(0)
    Option::Some(cm) => {
      hasher.combine_int(1)
      cm.hash_combine(hasher)
    }
  }
}

///|
pub struct AttrsSpan {
  start : Int
  end : Int
  attrs : Attrs
}

///|
pub impl Eq for AttrsSpan with equal(self, other) {
  self.start == other.start &&
  self.end == other.end &&
  self.attrs == other.attrs
}

///|
pub impl Hash for AttrsSpan with hash_combine(self, hasher) {
  hasher.combine_int(self.start)
  hasher.combine_int(self.end)
  self.attrs.hash_combine(hasher)
}

///|
pub struct AttrsList {
  defaults : Attrs
  spans : Array[AttrsSpan]
}

///|
pub fn AttrsList::new(defaults : Attrs) -> AttrsList {
  AttrsList::{ defaults, spans: [] }
}

///|
pub fn AttrsList::defaults(self : AttrsList) -> Attrs {
  self.defaults
}

///|
pub fn AttrsList::spans_iter(self : AttrsList) -> Array[AttrsSpan] {
  self.spans
}

///|
pub impl Eq for AttrsList with equal(self, other) {
  if self.defaults != other.defaults ||
    self.spans.length() != other.spans.length() {
    return false
  }
  for i in 0.. Array[AttrsSpan] {
  if spans.length() <= 1 {
    return spans
  }
  let merged : Array[AttrsSpan] = []
  for span in spans {
    if merged.length() == 0 {
      merged.push(span)
      continue
    }
    let last = merged[merged.length() - 1]
    if last.end == span.start && last.attrs == span.attrs {
      merged.set(merged.length() - 1, AttrsSpan::{
        start: last.start,
        end: span.end,
        attrs: last.attrs,
      })
    } else {
      merged.push(span)
    }
  }
  merged
}

///|
pub fn AttrsList::add_span(
  self : AttrsList,
  start : Int,
  end : Int,
  attrs : Attrs,
) -> AttrsList {
  if end <= start {
    return self
  }

  // Align with RangeMap insert semantics:
  // new span overwrites overlapping ranges and preserves non-overlapping pieces.
  let spans0 : Array[AttrsSpan] = []
  let mut inserted = false
  for span in self.spans {
    // Fully before new range.
    if span.end <= start {
      spans0.push(span)
      continue
    }
    // Fully after new range.
    if span.start >= end {
      if !inserted {
        spans0.push(AttrsSpan::{ start, end, attrs })
        inserted = true
      }
      spans0.push(span)
      continue
    }
    // Overlap: keep left remainder.
    if span.start < start {
      spans0.push(AttrsSpan::{
        start: span.start,
        end: start,
        attrs: span.attrs,
      })
    }
    // Overlap with right remainder means this old span crosses `end`.
    if span.end > end {
      if !inserted {
        spans0.push(AttrsSpan::{ start, end, attrs })
        inserted = true
      }
      spans0.push(AttrsSpan::{ start: end, end: span.end, attrs: span.attrs })
    }
  }
  if !inserted {
    spans0.push(AttrsSpan::{ start, end, attrs })
  }
  let spans = merge_adjacent_spans(spans0)
  AttrsList::{ ..self, spans, }
}

///|
pub fn AttrsList::get_span(self : AttrsList, pos : Int) -> Attrs {
  let mut found : Attrs? = None
  for span in self.spans {
    if pos >= span.start && pos < span.end {
      found = Some(span.attrs)
    }
  }
  if found is Some(attrs) {
    attrs
  } else {
    self.defaults
  }
}

///|
fn clamp_nonneg(v : Int) -> Int {
  if v < 0 {
    0
  } else {
    v
  }
}

///|
/// Splits spans at `index` and returns (left, right).
pub fn AttrsList::split_off(
  self : AttrsList,
  index : Int,
) -> (AttrsList, AttrsList) {
  let index = clamp_nonneg(index)
  let left : Array[AttrsSpan] = []
  let right : Array[AttrsSpan] = []
  for span in self.spans {
    if span.end <= index {
      left.push(span)
    } else if span.start >= index {
      right.push(AttrsSpan::{
        start: span.start - index,
        end: span.end - index,
        attrs: span.attrs,
      })
    } else {
      // Span crosses the split point.
      left.push(AttrsSpan::{ start: span.start, end: index, attrs: span.attrs })
      right.push(AttrsSpan::{
        start: 0,
        end: span.end - index,
        attrs: span.attrs,
      })
    }
  }
  (
    AttrsList::{ defaults: self.defaults, spans: left },
    AttrsList::{ defaults: self.defaults, spans: right },
  )
}

///|
pub fn AttrsList::eq(self : AttrsList, other : AttrsList) -> Bool {
  if !self.defaults.equal(other.defaults) {
    return false
  }
  if self.spans.length() != other.spans.length() {
    return false
  }
  let n = self.spans.length()
  for i in 0..