///|
/// Fill rule used when rasterizing a path.
pub(all) enum PathFillType {
  Winding
  EvenOdd
  InverseWinding
  InverseEvenOdd
} derive(Debug, Eq)

///|
pub impl Default for PathFillType with fn default() {
  Winding
}

///|
pub fn PathFillType::is_inverse(self : PathFillType) -> Bool {
  self is (InverseWinding | InverseEvenOdd)
}

///|
pub fn PathFillType::to_skia_ordinal(self : PathFillType) -> Int {
  match self {
    Winding => 0
    EvenOdd => 1
    InverseWinding => 2
    InverseEvenOdd => 3
  }
}

///|
/// Direction used when adding closed contours.
pub(all) enum PathDirection {
  CW
  CCW
} derive(Debug, Eq)

///|
pub impl Default for PathDirection with fn default() {
  CW
}

///|
pub fn PathDirection::to_skia_ordinal(self : PathDirection) -> Int {
  match self {
    CW => 0
    CCW => 1
  }
}

///|
/// How path appends connect the source path to the destination path.
pub(all) enum AddPathMode {
  Append
  Extend
} derive(Debug, Eq)

///|
pub impl Default for AddPathMode with fn default() {
  Append
}

///|
/// Cross-platform path verb data modeled after Skia path commands.
pub(all) enum PathVerb {
  MoveTo(Point)
  LineTo(Point)
  QuadTo(Point, Point)
  ConicTo(Point, Point, Float)
  CubicTo(Point, Point, Point)
  Close
} derive(Debug, Eq)

///|
/// A single line segment recognized from a path.
pub(all) struct PathLine {
  start : Point
  end : Point
} derive(Debug, Eq)

///|
pub fn PathLine::new(start : Point, end : Point) -> PathLine {
  { start, end }
}

///|
/// Rectangle recognized from a path, plus Skia's closed and direction metadata.
pub(all) struct PathRect {
  rect : Rect
  is_closed : Bool
  direction : PathDirection
} derive(Debug, Eq)

///|
pub fn PathRect::new(
  rect : Rect,
  is_closed : Bool,
  direction : PathDirection,
) -> PathRect {
  { rect, is_closed, direction }
}

///|
/// Bitmask of path segment kinds, matching Skia's public segment-mask values.
pub(all) struct PathSegmentMask(Int) derive(Debug, Eq)

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

///|
pub fn PathSegmentMask::empty() -> PathSegmentMask {
  PathSegmentMask::new(0)
}

///|
pub fn PathSegmentMask::line() -> PathSegmentMask {
  PathSegmentMask::new(1)
}

///|
pub fn PathSegmentMask::quad() -> PathSegmentMask {
  PathSegmentMask::new(2)
}

///|
pub fn PathSegmentMask::conic() -> PathSegmentMask {
  PathSegmentMask::new(4)
}

///|
pub fn PathSegmentMask::cubic() -> PathSegmentMask {
  PathSegmentMask::new(8)
}

///|
pub fn PathSegmentMask::bits(self : PathSegmentMask) -> Int {
  let PathSegmentMask(value) = self
  value
}

///|
pub fn PathSegmentMask::contains(
  self : PathSegmentMask,
  other : PathSegmentMask,
) -> Bool {
  (self.bits() & other.bits()) == other.bits()
}

///|
fn PathSegmentMask::union(
  self : PathSegmentMask,
  other : PathSegmentMask,
) -> PathSegmentMask {
  PathSegmentMask::new(self.bits() | other.bits())
}

///|
/// Cross-platform path value.
///
/// The native package owns real `SkPath` handles. This value layer keeps a
/// portable path command stream that can be built, inspected, and tested on all
/// MoonBit targets before being converted at a native boundary.
pub(all) struct Path {
  fill_type : PathFillType
  verbs : Array[PathVerb]
  current_point : Point?
} derive(Debug, Eq)

///|
pub fn Path::new(fill_type? : PathFillType = Winding) -> Path {
  { fill_type, verbs: Array::new(), current_point: None }
}

///|
pub impl Default for Path with fn default() {
  Path::new()
}

///|
pub fn Path::is_empty(self : Path) -> Bool {
  self.verbs.is_empty()
}

///|
pub fn Path::verb_count(self : Path) -> Int {
  self.verbs.length()
}

///|
pub fn Path::count_verbs(self : Path) -> Int {
  self.verb_count()
}

///|
pub fn Path::count_points(self : Path) -> Int {
  let initial_count = 0
  for count = initial_count, i = 0; i < self.verbs.length(); i = i + 1 {
    let increment = match self.verbs[i] {
      MoveTo(_) => 1
      LineTo(_) => 1
      QuadTo(_, _) => 2
      ConicTo(_, _, _) => 2
      CubicTo(_, _, _) => 3
      Close => 0
    }
    continue count + increment, i + 1
  } nobreak {
    count
  }
}

///|
pub fn Path::segment_masks(self : Path) -> PathSegmentMask {
  let initial_mask = PathSegmentMask::empty()
  for mask = initial_mask, i = 0; i < self.verbs.length(); i = i + 1 {
    let mask = match self.verbs[i] {
      MoveTo(_) | Close => mask
      LineTo(_) => mask.union(PathSegmentMask::line())
      QuadTo(_, _) => mask.union(PathSegmentMask::quad())
      ConicTo(_, _, _) => mask.union(PathSegmentMask::conic())
      CubicTo(_, _, _) => mask.union(PathSegmentMask::cubic())
    }
    continue mask, i + 1
  } nobreak {
    mask
  }
}

///|
fn path_scalar_is_finite(value : Scalar) -> Bool {
  value == value && value - value == 0.0
}

///|
fn path_point_is_finite(point : Point) -> Bool {
  path_scalar_is_finite(point.x) && path_scalar_is_finite(point.y)
}

///|
pub fn Path::is_finite(self : Path) -> Bool {
  for finite = true, i = 0; i < self.verbs.length(); i = i + 1 {
    let verb_finite = match self.verbs[i] {
      MoveTo(point) | LineTo(point) => path_point_is_finite(point)
      QuadTo(control, end) =>
        path_point_is_finite(control) && path_point_is_finite(end)
      ConicTo(control, end, weight) =>
        path_point_is_finite(control) &&
        path_point_is_finite(end) &&
        path_scalar_is_finite(weight)
      CubicTo(control0, control1, end) =>
        path_point_is_finite(control0) &&
        path_point_is_finite(control1) &&
        path_point_is_finite(end)
      Close => true
    }
    continue finite && verb_finite, i + 1
  } nobreak {
    finite
  }
}

///|
pub fn Path::is_inverse_fill_type(self : Path) -> Bool {
  self.fill_type.is_inverse()
}

///|
pub fn Path::is_last_contour_closed(self : Path) -> Bool {
  self.verbs.length() > 0 && self.verbs[self.verbs.length() - 1] is Close
}

///|
pub fn Path::last_point(self : Path) -> Point? {
  self.current_point
}

///|
pub fn Path::with_fill_type(self : Path, fill_type : PathFillType) -> Path {
  { ..self, fill_type, }
}