///|
pub(all) enum ShaderRegistryStatus {
  ShaderReady
  ShaderFallback
} derive(Eq, Debug, ToJson)

///|
pub extend ShaderRegistryStatus with Debug::{to_repr}

///|
pub extend ShaderRegistryStatus with Eq::{not_equal, equal}

///|
pub extend ShaderRegistryStatus with ToJson::{to_json}

///|
pub struct ShaderRegistryResult {
  priv name : String
  priv status : ShaderRegistryStatus
  priv fallback : @core.Brush
  priv diagnostic : String
} derive(Eq, Debug, ToJson)

///|
pub extend ShaderRegistryResult with Debug::{to_repr}

///|
pub extend ShaderRegistryResult with Eq::{not_equal, equal}

///|
pub extend ShaderRegistryResult with ToJson::{to_json}

///|
pub struct PathVertex {
  priv x : Double
  priv y : Double
  priv brush_kind : Double
  priv start_x : Double
  priv start_y : Double
  priv end_x : Double
  priv end_y : Double
  priv r : Double
  priv g : Double
  priv b : Double
  priv a : Double
  priv r1 : Double
  priv g1 : Double
  priv b1 : Double
  priv a1 : Double
} derive(Eq, Debug, ToJson)

///|
pub extend PathVertex with Debug::{to_repr}

///|
pub extend PathVertex with Eq::{not_equal, equal}

///|
pub extend PathVertex with ToJson::{to_json}

///|
pub struct PathMesh {
  priv vertices : Array[PathVertex]
  priv diagnostics : Array[String]
} derive(Eq, Debug, ToJson)

///|
pub extend PathMesh with Debug::{to_repr}

///|
pub extend PathMesh with Eq::{not_equal, equal}

///|
pub extend PathMesh with ToJson::{to_json}

///|
priv struct PathBrushPayload {
  kind : Double
  start : @core.Point
  end : @core.Point
  color0 : @core.Color
  color1 : @core.Color
}

///|
pub fn builtin_shader_names() -> Array[String] {
  ["solid", "checker", "linear-gradient-debug", "vignette"]
}

///|
pub fn ShaderRegistryResult::name(self : ShaderRegistryResult) -> String {
  self.name
}

///|
pub fn ShaderRegistryResult::status(
  self : ShaderRegistryResult,
) -> ShaderRegistryStatus {
  self.status
}

///|
pub fn ShaderRegistryResult::fallback(
  self : ShaderRegistryResult,
) -> @core.Brush {
  self.fallback
}

///|
pub fn ShaderRegistryResult::diagnostic(self : ShaderRegistryResult) -> String {
  self.diagnostic
}

///|
pub fn PathVertex::components(self : PathVertex) -> Array[Double] {
  [
    self.x,
    self.y,
    self.brush_kind,
    self.start_x,
    self.start_y,
    self.end_x,
    self.end_y,
    self.r,
    self.g,
    self.b,
    self.a,
    self.r1,
    self.g1,
    self.b1,
    self.a1,
  ]
}

///|
pub fn PathMesh::vertices(self : PathMesh) -> Array[PathVertex] {
  self.vertices
}

///|
pub fn PathMesh::diagnostics(self : PathMesh) -> Array[String] {
  self.diagnostics
}

///|
pub fn resolve_shader_effect(
  spec : @core.ShaderEffectSpec,
) -> ShaderRegistryResult {
  if builtin_shader_names().any(name => name == spec.name) {
    {
      name: spec.name,
      status: ShaderReady,
      fallback: spec.fallback,
      diagnostic: "",
    }
  } else {
    {
      name: spec.name,
      status: ShaderFallback,
      fallback: spec.fallback,
      diagnostic: "unknown shader effect: " + spec.name,
    }
  }
}

///|
pub fn tessellate_path(spec : @core.PathSpec) -> PathMesh {
  let mesh = PathMesh::{ vertices: [], diagnostics: [], }
  let path = zeno_path_from_spec(spec, mesh)
  append_zeno_fill_mesh(mesh, path, spec.fill)
  ignore(append_flattened_stroke_mesh(mesh, spec))
  mesh
}

///|
fn zeno_path_from_spec(
  spec : @core.PathSpec,
  mesh : PathMesh,
) -> Array[@zeno.Command] {
  let path : Array[@zeno.Command] = []
  let mut has_current = false
  for verb in spec.verbs {
    match verb {
      MoveTo(point) => {
        @zeno.PathBuilder::move_to(path, zeno_vector(point))
        has_current = true
      }
      LineTo(point) =>
        if has_current {
          @zeno.PathBuilder::line_to(path, zeno_vector(point))
        } else {
          mesh.diagnostics.push("LineTo ignored before MoveTo")
        }
      QuadTo(control, point) =>
        if has_current {
          @zeno.PathBuilder::quad_to(
            path,
            zeno_vector(control),
            zeno_vector(point),
          )
        } else {
          mesh.diagnostics.push("QuadTo ignored before MoveTo")
        }
      CubicTo(c1, c2, point) =>
        if has_current {
          @zeno.PathBuilder::curve_to(
            path,
            zeno_vector(c1),
            zeno_vector(c2),
            zeno_vector(point),
          )
        } else {
          mesh.diagnostics.push("CubicTo ignored before MoveTo")
        }
      Close =>
        if has_current {
          @zeno.PathBuilder::close(path)
          has_current = false
        } else {
          mesh.diagnostics.push("Close ignored before MoveTo")
        }
    }
  }
  path
}

///|
fn append_zeno_fill_mesh(
  mesh : PathMesh,
  path : Array[@zeno.Command],
  fill : @core.Brush?,
) -> Unit {
  match fill {
    Some(brush) =>
      append_zeno_vertices(
        mesh.vertices,
        path,
        Fill(NonZero),
        brush_to_path_payload(brush),
      )
    None => ()
  }
}

///|
fn append_flattened_stroke_mesh(mesh : PathMesh, spec : @core.PathSpec) -> Bool {
  match spec.stroke {
    Some(brush) =>
      if spec.stroke_width > 0.0 {
        let payload = brush_to_path_payload(brush)
        let vertices : Array[PathVertex] = []
        let mut current : @core.Point? = None
        let mut subpath_start : @core.Point? = None
        let mut emitted = false
        for verb in spec.verbs {
          match verb {
            MoveTo(point) => {
              current = Some(point)
              subpath_start = Some(point)
            }
            LineTo(point) => {
              match current {
                Some(start) =>
                  if append_line_stroke_vertices(
                      vertices,
                      start,
                      point,
                      spec.stroke_width,
                      payload,
                    ) {
                    emitted = true
                  }
                None => ()
              }
              current = Some(point)
            }
            QuadTo(control, point) => {
              match current {
                Some(start) =>
                  if append_quadratic_stroke_vertices(
                      vertices,
                      start,
                      control,
                      point,
                      spec.stroke_width,
                      payload,
                    ) {
                    emitted = true
                  }
                None => ()
              }
              current = Some(point)
            }
            CubicTo(c1, c2, point) => {
              match current {
                Some(start) =>
                  if append_cubic_stroke_vertices(
                      vertices,
                      start,
                      c1,
                      c2,
                      point,
                      spec.stroke_width,
                      payload,
                    ) {
                    emitted = true
                  }
                None => ()
              }
              current = Some(point)
            }
            Close => {
              match (current, subpath_start) {
                (Some(start), Some(point)) =>
                  if append_line_stroke_vertices(
                      vertices,
                      start,
                      point,
                      spec.stroke_width,
                      payload,
                    ) {
                    emitted = true
                  }
                _ => ()
              }
              current = None
              subpath_start = None
            }
          }
        }
        if emitted {
          for vertex in vertices {
            mesh.vertices.push(vertex)
          }
        }
        emitted
      } else {
        false
      }
    None => false
  }
}

///|
fn append_quadratic_stroke_vertices(
  vertices : Array[PathVertex],
  start : @core.Point,
  control : @core.Point,
  end : @core.Point,
  stroke_width : Double,
  payload : PathBrushPayload,
) -> Bool {
  let segments = 12
  let mut previous = start
  let mut emitted = false
  for step in 1..<=segments {
    let t = step.to_double() / segments.to_double()
    let point = quadratic_point(start, control, end, t)
    if append_line_stroke_vertices(
        vertices, previous, point, stroke_width, payload,
      ) {
      emitted = true
    }
    previous = point
  }
  emitted
}

///|
fn append_cubic_stroke_vertices(
  vertices : Array[PathVertex],
  start : @core.Point,
  c1 : @core.Point,
  c2 : @core.Point,
  end : @core.Point,
  stroke_width : Double,
  payload : PathBrushPayload,
) -> Bool {
  let segments = 16
  let mut previous = start
  let mut emitted = false
  for step in 1..<=segments {
    let t = step.to_double() / segments.to_double()
    let point = cubic_point(start, c1, c2, end, t)
    if append_line_stroke_vertices(
        vertices, previous, point, stroke_width, payload,
      ) {
      emitted = true
    }
    previous = point
  }
  emitted
}

///|
fn quadratic_point(
  start : @core.Point,
  control : @core.Point,
  end : @core.Point,
  t : Double,
) -> @core.Point {
  let mt = 1.0 - t
  @core.Point::new(
    x=mt * mt * start.x + 2.0 * mt * t * control.x + t * t * end.x,
    y=mt * mt * start.y + 2.0 * mt * t * control.y + t * t * end.y,
  )
}

///|
fn cubic_point(
  start : @core.Point,
  c1 : @core.Point,
  c2 : @core.Point,
  end : @core.Point,
  t : Double,
) -> @core.Point {
  let mt = 1.0 - t
  let mt2 = mt * mt
  let t2 = t * t
  @core.Point::new(
    x=mt2 * mt * start.x +
      3.0 * mt2 * t * c1.x +
      3.0 * mt * t2 * c2.x +
      t2 * t * end.x,
    y=mt2 * mt * start.y +
      3.0 * mt2 * t * c1.y +
      3.0 * mt * t2 * c2.y +
      t2 * t * end.y,
  )
}

///|
fn append_line_stroke_vertices(
  vertices : Array[PathVertex],
  start : @core.Point,
  end : @core.Point,
  stroke_width : Double,
  payload : PathBrushPayload,
) -> Bool {
  let dx = end.x - start.x
  let dy = end.y - start.y
  let length = (dx * dx + dy * dy).sqrt()
  if length <= 0.000001 {
    return false
  }
  let half_width = stroke_width / 2.0
  let nx = -dy / length * half_width
  let ny = dx / length * half_width
  push_path_vertex(vertices, Vector(start.x + nx, start.y + ny), payload)
  push_path_vertex(vertices, Vector(start.x - nx, start.y - ny), payload)
  push_path_vertex(vertices, Vector(end.x - nx, end.y - ny), payload)
  push_path_vertex(vertices, Vector(start.x + nx, start.y + ny), payload)
  push_path_vertex(vertices, Vector(end.x - nx, end.y - ny), payload)
  push_path_vertex(vertices, Vector(end.x + nx, end.y + ny), payload)
  true
}

///|
/// 把填充路径命令流展平为轮廓点集。QuadTo/CubicTo 的分段数与描边展平
/// 保持同一精度约定;Close 后轮廓隐式闭合(填充语义不要求显式回加起点)。
fn path_fill_contours(
  path : Array[@zeno.Command],
) -> Array[Array[@zeno.Vector]] {
  let contours : Array[Array[@zeno.Vector]] = []
  let mut current : Array[@zeno.Vector]? = None
  for command in path {
    match command {
      MoveTo(point) => {
        let contour : Array[@zeno.Vector] = []
        contour.push(Vector(point.x, point.y))
        contours.push(contour)
        current = Some(contour)
      }
      LineTo(point) =>
        match current {
          Some(contour) => contour.push(Vector(point.x, point.y))
          None => ()
        }
      QuadTo(control, point) =>
        match current {
          Some(contour) => {
            let start = contour[contour.length() - 1]
            let segments = 12
            for step in 1..<(segments + 1) {
              let t = step.to_double() / segments.to_double()
              let mt = 1.0 - t
              contour.push(
                Vector(
                  mt * mt * start.x + 2.0 * mt * t * control.x + t * t * point.x,
                  mt * mt * start.y + 2.0 * mt * t * control.y + t * t * point.y,
                ),
              )
            }
          }
          None => ()
        }
      CurveTo(c1, c2, point) =>
        match current {
          Some(contour) => {
            let start = contour[contour.length() - 1]
            let segments = 16
            for step in 1..<(segments + 1) {
              let t = step.to_double() / segments.to_double()
              let mt = 1.0 - t
              let mt2 = mt * mt
              let t2 = t * t
              contour.push(
                Vector(
                  mt2 * mt * start.x +
                  3.0 * mt2 * t * c1.x +
                  3.0 * mt * t2 * c2.x +
                  t2 * t * point.x,
                  mt2 * mt * start.y +
                  3.0 * mt2 * t * c1.y +
                  3.0 * mt * t2 * c2.y +
                  t2 * t * point.y,
                ),
              )
            }
          }
          None => ()
        }
      Close => current = None
    }
  }
  contours
}

///|
fn emit_normalized_triangle(
  emit : (Int, Int, Int) -> Unit,
  i0 : Int,
  i1 : Int,
  i2 : Int,
) -> Unit {
  // 以最小原始索引为旋转基准,保持绕序不变并让三角形发射顺序稳定。
  if i0 <= i1 && i0 <= i2 {
    emit(i0, i1, i2)
  } else if i1 <= i0 && i1 <= i2 {
    emit(i1, i2, i0)
  } else {
    emit(i2, i0, i1)
  }
}

///|
/// 耳切三角化:简单多边形(单轮廓、无洞)的填充三角化。
/// NonZero 洞语义暂不支持——当前填充用例(图形路径、图标)均为单轮廓,
/// 多轮廓按独立多边形分别处理。
fn triangulate_contour(
  contour : Array[@zeno.Vector],
  emit : (Int, Int, Int) -> Unit,
) -> Unit {
  let n = contour.length()
  if n < 3 {
    return
  }
  let indices : Array[Int] = []
  for i in 0.. 0.0
  let eps = 1.0e-9
  let clip_budget = n * n + 16
  let mut clips = 0
  while indices.length() > 2 && clips < clip_budget {
    clips += 1
    let mut clipped = false
    let m = indices.length()
    let mut i = 0
    while i < m {
      let i0 = indices[(i + m - 1) % m]
      let i1 = indices[i]
      let i2 = indices[(i + 1) % m]
      let a = contour[i0]
      let b = contour[i1]
      let c = contour[i2]
      let turn = (b.x - a.x) * (c.y - b.y) - (b.y - a.y) * (c.x - b.x)
      let is_convex = if positive { turn > -eps } else { turn < eps }
      if is_convex {
        let mut contains_other = false
        for j in indices {
          if j != i0 && j != i1 && j != i2 {
            let p = contour[j]
            let d1 = (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x)
            let d2 = (c.x - b.x) * (p.y - b.y) - (c.y - b.y) * (p.x - b.x)
            let d3 = (a.x - c.x) * (p.y - c.y) - (a.y - c.y) * (p.x - c.x)
            let inside = if positive {
              d1 > eps && d2 > eps && d3 > eps
            } else {
              d1 < -eps && d2 < -eps && d3 < -eps
            }
            if inside {
              contains_other = true
              break
            }
          }
        }
        if !contains_other {
          emit_normalized_triangle(emit, i0, i1, i2)
          ignore(indices.remove(i))
          clipped = true
          break
        }
      }
      i += 1
    }
    if !clipped {
      // 数值兜底:剪不出耳朵时退化为扇形三角化,保证不丢轮廓不挂死
      let last = indices.length()
      for k in 1..<(last - 1) {
        emit_normalized_triangle(emit, indices[0], indices[k], indices[k + 1])
      }
      break
    }
  }
}

///|
fn append_zeno_vertices(
  vertices : Array[PathVertex],
  path : Array[@zeno.Command],
  style : @zeno.Style,
  payload : PathBrushPayload,
) -> Unit {
  // 仅 Fill 走此处;Stroke 由 append_flattened_stroke_mesh 处理。
  match style {
    Fill(_) =>
      for contour in path_fill_contours(path) {
        triangulate_contour(contour, fn(i0, i1, i2) {
          push_path_vertex(vertices, contour[i0], payload)
          push_path_vertex(vertices, contour[i1], payload)
          push_path_vertex(vertices, contour[i2], payload)
        })
      }
    _ => ()
  }
}

///|
fn brush_to_path_payload(brush : @core.Brush) -> PathBrushPayload {
  match brush {
    Solid(color) =>
      {
        kind: 0.0,
        start: @core.Point::new(x=0.0, y=0.0),
        end: @core.Point::new(x=1.0, y=0.0),
        color0: color,
        color1: color,
      }
    LinearGradient(gradient) =>
      {
        kind: 0.0,
        start: gradient.start,
        end: gradient.end,
        color0: gradient.start_color,
        color1: gradient.end_color,
      }
    RadialGradient(gradient) =>
      {
        kind: 1.0,
        start: gradient.center,
        end: @core.Point::new(x=gradient.radius.max(1.0), y=0.0),
        color0: gradient.center_color,
        color1: gradient.edge_color,
      }
  }
}

///|
fn push_path_vertex(
  vertices : Array[PathVertex],
  point : @zeno.Vector,
  payload : PathBrushPayload,
) -> Unit {
  vertices.push({
    x: point.x(),
    y: point.y(),
    brush_kind: payload.kind,
    start_x: payload.start.x,
    start_y: payload.start.y,
    end_x: payload.end.x,
    end_y: payload.end.y,
    r: payload.color0.r,
    g: payload.color0.g,
    b: payload.color0.b,
    a: payload.color0.a,
    r1: payload.color1.r,
    g1: payload.color1.g,
    b1: payload.color1.b,
    a1: payload.color1.a,
  })
}

///|
fn zeno_vector(point : @core.Point) -> @zeno.Vector {
  Vector(point.x, point.y)
}