///|
pub(all) enum PdfContentPartialPath {
  PdfContentNoPartialPath
  PdfContentPartialPath(PdfContentPartialPathState)
} derive(Debug, Eq, ToJson)

///|
pub(all) struct PdfContentPartialPathState {
  start_point : Point2
  current_point : Point2
  segments : Array[PdfPathSegment]
  subpaths : Array[PdfPathSubpath]
} derive(Debug, Eq, ToJson)

///|
pub(all) struct PdfContentPathOpResult {
  partial_path : PdfContentPartialPath
  drawn_path : PdfDrawnPath?
} derive(Debug, Eq, ToJson)

///|
pub(all) struct PdfContentClippingOpResult {
  partial_path : PdfContentPartialPath
  clipping_path : Array[PdfPath]
} derive(Debug, Eq, ToJson)

///|
pub fn pdf_content_no_partial_path() -> PdfContentPartialPath {
  PdfContentNoPartialPath
}

///|
fn pdf_content_path_empty_result(
  partial_path : PdfContentPartialPath,
) -> PdfContentPathOpResult {
  { partial_path, drawn_path: None }
}

///|
fn pdf_content_path_copy_segments(
  segments : ArrayView[PdfPathSegment],
) -> Array[PdfPathSegment] {
  [
    for segment in segments => segment
  ]
}

///|
fn pdf_content_path_append_segment(
  segments : ArrayView[PdfPathSegment],
  segment : PdfPathSegment,
) -> Array[PdfPathSegment] {
  let output = pdf_content_path_copy_segments(segments)
  output.push(segment)
  output
}

///|
fn pdf_content_path_copy_subpaths(
  subpaths : ArrayView[PdfPathSubpath],
) -> Array[PdfPathSubpath] {
  [
    for subpath in subpaths => subpath
  ]
}

///|
fn pdf_content_path_reverse_subpaths(
  subpaths : ArrayView[PdfPathSubpath],
) -> Array[PdfPathSubpath] {
  pdf_content_path_copy_subpaths(subpaths).rev()
}

///|
fn pdf_content_path_append_subpath(
  subpaths : ArrayView[PdfPathSubpath],
  subpath : PdfPathSubpath,
) -> Array[PdfPathSubpath] {
  let output = pdf_content_path_copy_subpaths(subpaths)
  output.push(subpath)
  output
}

///|
fn pdf_content_path_subpath(
  closure : PdfPathClosure,
  segments : ArrayView[PdfPathSegment],
) -> PdfPathSubpath {
  {
    hole: PdfPathNotHole,
    closure,
    segments: pdf_content_path_copy_segments(segments),
  }
}

///|
fn pdf_content_partial_path_clear(
  state : PdfContentPartialPathState,
) -> PdfContentPartialPath {
  PdfContentPartialPath({ ..state, segments: [], subpaths: [] })
}

///|
fn pdf_content_path_copy_paths(paths : ArrayView[PdfPath]) -> Array[PdfPath] {
  [
    for path in paths => path
  ]
}

///|
fn pdf_content_path_prepend_path(
  paths : ArrayView[PdfPath],
  path : PdfPath,
) -> Array[PdfPath] {
  let output = [path]
  for existing in paths {
    output.push(existing)
  }
  output
}

///|
fn PdfContentPartialPath::content_move_to(
  self : PdfContentPartialPath,
  point : Point2,
) -> PdfContentPartialPath {
  match self {
    PdfContentPartialPath(state) if state.segments.length() == 0 =>
      PdfContentPartialPath({
        start_point: point,
        current_point: point,
        segments: [],
        subpaths: state.subpaths,
      })
    PdfContentPartialPath(state) =>
      PdfContentPartialPath({
        start_point: point,
        current_point: point,
        segments: [],
        subpaths: pdf_content_path_append_subpath(
          state.subpaths,
          pdf_content_path_subpath(PdfPathOpen, state.segments),
        ),
      })
    PdfContentNoPartialPath =>
      PdfContentPartialPath({
        start_point: point,
        current_point: point,
        segments: [],
        subpaths: [],
      })
  }
}

///|
fn PdfContentPartialPath::content_line_to(
  self : PdfContentPartialPath,
  point : Point2,
) -> PdfContentPartialPath {
  match self {
    PdfContentPartialPath(state) =>
      PdfContentPartialPath({
        ..state,
        current_point: point,
        segments: pdf_content_path_append_segment(
          state.segments,
          PdfPathStraight(state.current_point, point),
        ),
      })
    PdfContentNoPartialPath => self
  }
}

///|
fn PdfContentPartialPath::content_curve_to(
  self : PdfContentPartialPath,
  control1 : Point2,
  control2 : Point2,
  finish : Point2,
) -> PdfContentPartialPath {
  match self {
    PdfContentPartialPath(state) =>
      PdfContentPartialPath({
        ..state,
        current_point: finish,
        segments: pdf_content_path_append_segment(
          state.segments,
          PdfPathBezier(state.current_point, control1, control2, finish),
        ),
      })
    PdfContentNoPartialPath => self
  }
}

///|
fn PdfContentPartialPath::content_close_path(
  self : PdfContentPartialPath,
) -> PdfContentPartialPath {
  match self {
    PdfContentPartialPath(state) =>
      PdfContentPartialPath({
        ..state,
        segments: [],
        subpaths: pdf_content_path_append_subpath(
          state.subpaths,
          pdf_content_path_subpath(PdfPathClosed, state.segments),
        ),
      })
    PdfContentNoPartialPath => self
  }
}

///|
fn PdfContentPartialPath::content_rectangle(
  self : PdfContentPartialPath,
  x : Double,
  y : Double,
  width : Double,
  height : Double,
) -> PdfContentPartialPath {
  self
  .content_move_to(point2(x, y))
  .content_line_to(point2(x + width, y))
  .content_line_to(point2(x + width, y + height))
  .content_line_to(point2(x, y + height))
  .content_close_path()
}

///|
fn pdf_content_path_finish_subpaths(
  state : PdfContentPartialPathState,
  close_current : Bool,
) -> Array[PdfPathSubpath] {
  if state.segments.length() == 0 {
    pdf_content_path_copy_subpaths(state.subpaths)
  } else {
    pdf_content_path_append_subpath(
      state.subpaths,
      pdf_content_path_subpath(
        if close_current {
          PdfPathClosed
        } else {
          PdfPathOpen
        },
        state.segments,
      ),
    )
  }
}

///|
fn PdfContentPartialPath::content_paint(
  self : PdfContentPartialPath,
  winding_rule : PdfPathWindingRule,
  filled : Bool,
  stroked : Bool,
  close_current : Bool,
) -> PdfContentPathOpResult {
  match self {
    PdfContentPartialPath(state) => {
      let subpaths = pdf_content_path_finish_subpaths(state, close_current)
      {
        partial_path: pdf_content_partial_path_clear(state),
        drawn_path: Some({ stroked, filled, path: { winding_rule, subpaths } }),
      }
    }
    PdfContentNoPartialPath => pdf_content_path_empty_result(self)
  }
}

///|
fn PdfContentPartialPath::content_clipping_path(
  self : PdfContentPartialPath,
  winding_rule : PdfPathWindingRule,
) -> PdfPath? {
  match self {
    PdfContentPartialPath(state) =>
      if state.segments.length() == 0 && state.subpaths.length() == 0 {
        None
      } else {
        let subpaths = if state.segments.length() == 0 {
          pdf_content_path_reverse_subpaths(state.subpaths)
        } else {
          let output = [pdf_content_path_subpath(PdfPathClosed, state.segments)]
          for subpath in pdf_content_path_reverse_subpaths(state.subpaths) {
            output.push(subpath)
          }
          output
        }
        Some({ winding_rule, subpaths })
      }
    PdfContentNoPartialPath => None
  }
}

///|
fn PdfContentPartialPath::content_clip(
  self : PdfContentPartialPath,
  clipping_path : ArrayView[PdfPath],
  winding_rule : PdfPathWindingRule,
) -> PdfContentClippingOpResult {
  match self.content_clipping_path(winding_rule) {
    Some(path) =>
      {
        partial_path: self,
        clipping_path: pdf_content_path_prepend_path(clipping_path, path),
      }
    None =>
      {
        partial_path: self,
        clipping_path: pdf_content_path_copy_paths(clipping_path),
      }
  }
}

///|
/// Apply cpdfcontent path-construction and path-painting operator semantics.
///
/// Non-path operators are ignored. Painting operators return the drawn path and
/// clear the accumulated subpaths the same way `process_op` resets
/// `partial_path` after emitting a path.
pub fn PdfContentPartialPath::content_apply_path_op(
  self : PdfContentPartialPath,
  op : PdfContentOp,
) -> PdfContentPathOpResult {
  match op {
    Op_m(x, y) =>
      pdf_content_path_empty_result(self.content_move_to(point2(x, y)))
    Op_l(x, y) =>
      pdf_content_path_empty_result(self.content_line_to(point2(x, y)))
    Op_c(a, b, c, d, e, f) =>
      pdf_content_path_empty_result(
        self.content_curve_to(point2(a, b), point2(c, d), point2(e, f)),
      )
    Op_v(a, b, c, d) => {
      let next = match self {
        PdfContentPartialPath(state) =>
          self.content_curve_to(state.current_point, point2(a, b), point2(c, d))
        PdfContentNoPartialPath => self
      }
      pdf_content_path_empty_result(next)
    }
    Op_y(a, b, c, d) => {
      let finish = point2(c, d)
      let next = self.content_curve_to(point2(a, b), finish, finish)
      pdf_content_path_empty_result(next)
    }
    Op_h => pdf_content_path_empty_result(self.content_close_path())
    Op_re(x, y, width, height) =>
      pdf_content_path_empty_result(self.content_rectangle(x, y, width, height))
    Op_s =>
      self.content_apply_path_op(Op_h).partial_path.content_paint(
        PdfPathEvenOdd,
        false,
        true,
        false,
      )
    Op_b =>
      self.content_apply_path_op(Op_h).partial_path.content_paint(
        PdfPathNonZero,
        true,
        true,
        false,
      )
    Op_bstar =>
      self.content_apply_path_op(Op_h).partial_path.content_apply_path_op(
        Op_Bstar,
      )
    Op_f | Op_F =>
      self.content_apply_path_op(Op_h).partial_path.content_paint(
        PdfPathNonZero,
        true,
        false,
        false,
      )
    Op_S => self.content_paint(PdfPathEvenOdd, false, true, false)
    Op_B => self.content_paint(PdfPathNonZero, true, true, false)
    Op_Bstar =>
      self.content_apply_path_op(Op_h).partial_path.content_paint(
        PdfPathEvenOdd,
        true,
        true,
        false,
      )
    Op_fstar => self.content_paint(PdfPathEvenOdd, true, false, false)
    Op_n => { partial_path: PdfContentNoPartialPath, drawn_path: None }
    _ => pdf_content_path_empty_result(self)
  }
}

///|
/// Compatibility wrapper for `PdfContentPartialPath::content_apply_path_op`.
pub fn pdf_content_apply_path_op(
  partial_path : PdfContentPartialPath,
  op : PdfContentOp,
) -> PdfContentPathOpResult {
  partial_path.content_apply_path_op(op)
}

///|
/// Apply cpdfcontent clipping operator semantics for `W` and `W*`.
///
/// The partial path is intentionally preserved. In PDF content streams, `n`
/// commonly follows a clipping operator and clears the current path separately.
pub fn PdfContentPartialPath::content_apply_clipping_op(
  self : PdfContentPartialPath,
  clipping_path : ArrayView[PdfPath],
  op : PdfContentOp,
) -> PdfContentClippingOpResult {
  match op {
    Op_W => self.content_clip(clipping_path, PdfPathNonZero)
    Op_Wstar => self.content_clip(clipping_path, PdfPathEvenOdd)
    _ =>
      {
        partial_path: self,
        clipping_path: pdf_content_path_copy_paths(clipping_path),
      }
  }
}

///|
/// Compatibility wrapper for `PdfContentPartialPath::content_apply_clipping_op`.
pub fn pdf_content_apply_clipping_op(
  partial_path : PdfContentPartialPath,
  clipping_path : ArrayView[PdfPath],
  op : PdfContentOp,
) -> PdfContentClippingOpResult {
  partial_path.content_apply_clipping_op(clipping_path, op)
}