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

///|
/// Glyph outline.
///
/// Ported from upstream `swash/src/scale/outline.rs` (Apache-2.0 OR MIT).
///
/// Note: MoonBit doesn't currently expose slice borrowing like Rust, so the
/// `Layer` accessors return index ranges into the shared points/verbs arrays.
pub struct LayerData {
  points_start : Int
  points_end : Int
  verbs_start : Int
  verbs_end : Int
  color_index : UInt16?
}

///|
/// Scaled glyph outline represented as a collection of layers and a sequence
/// of points and verbs.
pub struct Outline {
  layers : Array[LayerData]
  points : Array[Point]
  verbs : Array[Verb]
  mut is_color : Bool
}

///|

///|
/// Reference to a layer in a scaled outline.
///
/// MoonBit does not currently expose slice borrows, so this is materialized as
/// owned point/verb arrays.
pub struct Layer {
  points : Array[Point]
  verbs : Array[Verb]
  color_index : UInt16?
}

///|
pub fn Layer::points(self : Layer) -> Array[Point] {
  self.points
}

///|
pub fn Layer::verbs(self : Layer) -> Array[Verb] {
  self.verbs
}

///|
pub fn Layer::path(self : Layer) -> (Array[Point], Array[Verb]) {
  (self.points, self.verbs)
}

///|
pub fn Layer::bounds(self : Layer) -> Bounds {
  Bounds::from_points(self.points)
}

///|
pub fn Layer::color_index(self : Layer) -> UInt16? {
  self.color_index
}

///|
pub fn Outline::Outline() -> Outline {
  Outline::{ layers: [], points: [], verbs: [], is_color: false }
}

///|
pub fn Outline::is_color(self : Outline) -> Bool {
  self.is_color
}

///|
pub fn Outline::len(self : Outline) -> Int {
  self.layers.length()
}

///|
pub fn Outline::is_empty(self : Outline) -> Bool {
  self.layers.length() == 0
}

///|
pub fn Outline::layer_data(self : Outline, index : Int) -> LayerData? {
  if index < 0 || index >= self.layers.length() {
    None
  } else {
    Some(self.layers[index])
  }
}

///|
pub fn Outline::get(self : Outline, index : Int) -> Layer? {
  match self.layer_data(index) {
    None => None
    Some(layer) => {
      let ps = layer.points_start
      let pe = layer.points_end
      let vs = layer.verbs_start
      let ve = layer.verbs_end
      guard ps >= 0 &&
        pe >= ps &&
        pe <= self.points.length() &&
        vs >= 0 &&
        ve >= vs &&
        ve <= self.verbs.length() else {
        None
      }
      let points : Array[Point] = []
      for i in ps.. Array[Point] {
  self.points
}

///|
pub fn Outline::verbs(self : Outline) -> Array[Verb] {
  self.verbs
}

///|
/// Returns path data for the outline.
pub fn Outline::path(self : Outline) -> (Array[Point], Array[Verb]) {
  (self.points, self.verbs)
}

///|
pub fn Outline::bounds(self : Outline) -> Bounds {
  Bounds::from_points(self.points)
}

///|
pub fn Outline::transform(self : Outline, transform : Transform) -> Unit {
  for i in 0.. UInt {
  if end <= start {
    return 0
  }
  let last = end - 1
  let mut area = 0.0
  let mut prev = points[last]
  for i in start.. 0.0 {
    1
  } else {
    0
  }
}

///|
fn embolden_contour(
  points : Array[Point],
  start : Int,
  end : Int,
  winding : UInt,
  x_strength : Double,
  y_strength : Double,
) -> Unit {
  if end <= start {
    return
  }
  let len = end - start
  if len <= 0 {
    return
  }
  let last = len - 1
  let mut i = last
  let mut j = 0
  let mut k = -1
  let mut out_len = 0.0
  let mut in_len = 0.0
  let mut anchor_len = 0.0
  let mut anchor = Vector::zero()
  let mut out = Vector::zero()
  let mut in_ = Vector::zero()
  while j != i && i != k {
    if j != k {
      out = points[start + j] - points[start + i]
      out_len = out.length()
      if out_len == 0.0 {
        j = if j < last { j + 1 } else { 0 }
        continue
      } else {
        out = out.div_scalar(out_len)
      }
    } else {
      out = anchor
      out_len = anchor_len
    }
    if in_len != 0.0 {
      if k == -1 {
        k = i
        anchor = in_
        anchor_len = in_len
      }
      let mut d = in_.dot(out)
      let shift = if d > -0.9396 {
        d = d + 1.0
        let mut sx = in_.y() + out.y()
        let mut sy = in_.x() + out.x()
        if winding == 0 {
          sx = -sx
        } else {
          sy = -sy
        }
        let mut q = out.x() * in_.y() - out.y() * in_.x()
        if winding == 0 {
          q = -q
        }
        let l = if in_len < out_len { in_len } else { out_len }
        if x_strength * q <= l * d {
          sx = sx * x_strength / d
        } else {
          sx = sx * l / q
        }
        if y_strength * q <= l * d {
          sy = sy * y_strength / d
        } else {
          sy = sy * l / q
        }
        Vector::Vector(sx, sy)
      } else {
        Vector::zero()
      }
      while i != j {
        let abs_i = start + i
        let p = points[abs_i]
        points[abs_i] = Vector::Vector(
          p.x() + x_strength + shift.x(),
          p.y() + y_strength + shift.y(),
        )
        i = if i < last { i + 1 } else { 0 }
      }
    } else {
      i = j
    }
    in_ = out
    in_len = out_len
    j = if j < last { j + 1 } else { 0 }
  }
}

///|
/// Applies a faux bold to the outline with the specified strengths in the x and y directions.
pub fn Outline::embolden(
  self : Outline,
  x_strength : Double,
  y_strength : Double,
) -> Unit {
  for li in 0..= pe || vs >= ve {
      continue
    }
    let winding = compute_winding(self.points, ps, pe)
    let mut point_start = ps
    let mut pos = ps
    for vi in vs.. {
          let is_move_to = match verb {
            Verb::MoveTo => true
            _ => false
          }
          if pos > point_start {
            embolden_contour(
              self.points,
              point_start,
              pos,
              winding,
              x_strength,
              y_strength,
            )
          }
          point_start = pos
          if is_move_to {
            pos = pos + 1
          }
        }
        Verb::LineTo => pos = pos + 1
        Verb::QuadTo => pos = pos + 2
        Verb::CurveTo => pos = pos + 3
      }
    }
    if pos > point_start {
      embolden_contour(
        self.points,
        point_start,
        pos,
        winding,
        x_strength,
        y_strength,
      )
    }
  }
}

///|
pub fn Outline::clear(self : Outline) -> Unit {
  self.points.clear()
  self.verbs.clear()
  self.layers.clear()
  self.is_color = false
}

///|
// ---- internal builders (ported from `pub(super)` APIs) ----

///|
/// Internal builder method (upstream: `pub(super)`).
pub fn Outline::set_color(self : Outline, color : Bool) -> Unit {
  self.is_color = color
}

///|
fn Outline::maybe_close(self : Outline) -> Unit {
  if self.verbs.length() == 0 {
    return
  }
  match self.verbs[self.verbs.length() - 1] {
    Verb::Close => return
    _ => ()
  }
  // Close current contour at layer boundary.
  self.verbs.push(Verb::Close)
}

///|
/// Internal builder method (upstream: `pub(super)`).
pub fn Outline::begin_layer(self : Outline, color_index : UInt16?) -> Unit {
  let points_end = self.points.length()
  let verbs_end = self.verbs.length()
  if self.layers.length() > 0 {
    let last = self.layers[self.layers.length() - 1]
    self.layers[self.layers.length() - 1] = LayerData::{
      points_start: last.points_start,
      points_end,
      verbs_start: last.verbs_start,
      verbs_end,
      color_index: last.color_index,
    }
  }
  self.layers.push(LayerData::{
    points_start: points_end,
    points_end,
    verbs_start: verbs_end,
    verbs_end,
    color_index,
  })
}

///|
/// Internal builder method (upstream: `pub(super)`).
pub fn Outline::finish(self : Outline) -> Unit {
  let points_end = self.points.length()
  let verbs_end = self.verbs.length()
  if self.layers.length() > 0 {
    let last = self.layers[self.layers.length() - 1]
    self.layers[self.layers.length() - 1] = LayerData::{
      points_start: last.points_start,
      points_end,
      verbs_start: last.verbs_start,
      verbs_end,
      color_index: last.color_index,
    }
  } else {
    self.layers.push(LayerData::{
      points_start: 0,
      points_end,
      verbs_start: 0,
      verbs_end,
      color_index: None,
    })
  }
}

///|
/// Internal builder method (upstream: `pub(super)`).
pub fn Outline::move_to(self : Outline, p : Point) -> Unit {
  self.maybe_close()
  self.points.push(p)
  self.verbs.push(Verb::MoveTo)
}

///|
/// Internal builder method (upstream: `pub(super)`).
pub fn Outline::line_to(self : Outline, p : Point) -> Unit {
  self.points.push(p)
  self.verbs.push(Verb::LineTo)
}

///|
/// Internal builder method (upstream: `pub(super)`).
pub fn Outline::quad_to(self : Outline, p0 : Point, p1 : Point) -> Unit {
  self.points.push(p0)
  self.points.push(p1)
  self.verbs.push(Verb::QuadTo)
}

///|
/// Internal builder method (upstream: `pub(super)`).
pub fn Outline::curve_to(
  self : Outline,
  p0 : Point,
  p1 : Point,
  p2 : Point,
) -> Unit {
  self.points.push(p0)
  self.points.push(p1)
  self.points.push(p2)
  self.verbs.push(Verb::CurveTo)
}

///|
/// Internal builder method (upstream: `pub(super)`).
pub fn Outline::close(self : Outline) -> Unit {
  self.verbs.push(Verb::Close)
}

///|
test "Outline basic path recording" {
  let o = Outline::Outline()
  o.set_color(false)
  o.begin_layer(None)
  o.move_to(Vector::Vector(0.0, 0.0))
  o.line_to(Vector::Vector(10.0, 0.0))
  o.line_to(Vector::Vector(10.0, 10.0))
  o.quad_to(Vector::Vector(0.0, 10.0), Vector::Vector(0.0, 0.0))
  o.curve_to(
    Vector::Vector(1.0, 1.0),
    Vector::Vector(2.0, 2.0),
    Vector::Vector(3.0, 3.0),
  )
  o.close()
  o.finish()
  inspect(o.points().length(), content="8")
  inspect(o.verbs().length(), content="6")
  inspect(o.len(), content="1")
  inspect(o.bounds().width().to_int(), content="10")
  inspect(o.bounds().height().to_int(), content="10")
}