///|
/// Colour operands carried by cpdfcontent's path drawing state.
pub(all) enum PdfContentColourValues {
  PdfContentColourFloats(Array[Double])
  PdfContentColourNamed(String, Array[Double])
  PdfContentColourPattern
} derive(Debug, Eq, ToJson)

///|
/// Dash pattern carried by cpdfcontent's path drawing state.
pub(all) struct PdfContentDashPattern {
  array : Array[Double]
  phase : Double
} derive(Debug, Eq, ToJson)

///|
/// Path drawing state exported by cpdfcontent's JSON view.
pub(all) struct PdfContentPathState {
  clipping_path : Array[PdfPath]
  colourspace_stroke : PdfColourSpace
  colourspace_non_stroke : PdfColourSpace
  colour_stroke : PdfContentColourValues
  colour_non_stroke : PdfContentColourValues
  line_width : Double
  line_cap : Int
  line_join : Int
  dash_pattern : PdfContentDashPattern
  rendering_intent : String
} derive(Debug, Eq, ToJson)

///|
/// Clipping-only state exported for inline images, images, and shadings.
pub(all) struct PdfContentClippingState {
  clipping_path : Array[PdfPath]
} derive(Debug, Eq, ToJson)

///|
/// Text state fields exported alongside glyph content objects.
pub(all) struct PdfContentGlyphState {
  rendering_mode : Int
  knockout : Bool
  font : String
  font_size : Double
  clipping_path : Array[PdfPath]
} derive(Debug, Eq, ToJson)

///|
/// Content objects emitted by cpdfcontent's JSON exporter.
pub(all) enum PdfContentObject {
  PdfContentGlyphObject(Int, String, String)
  PdfContentInlineImageObject
  PdfContentImageObject(String)
  PdfContentPathObject(PdfDrawnPath)
  PdfContentShadingObject(String)
} derive(Debug, Eq, ToJson)

///|
/// Content state variant carried by one cpdfcontent JSON entry.
pub(all) enum PdfContentState {
  PdfContentStateGlyph(PdfContentGlyphState)
  PdfContentStatePath(PdfContentPathState)
  PdfContentStateClipping(PdfContentClippingState)
} derive(Debug, Eq, ToJson)

///|
/// Eight-point cpdfcontent bounding box quad.
pub(all) struct PdfContentBoundingBox {
  x0 : Double
  y0 : Double
  x1 : Double
  y1 : Double
  x2 : Double
  y2 : Double
  x3 : Double
  y3 : Double
} derive(Debug, Eq, ToJson)

///|
/// One item in cpdfcontent's JSON content list.
pub(all) struct PdfContentJsonEntry {
  object : PdfContentObject
  state : PdfContentState
  bounding_box : PdfContentBoundingBox
} derive(Debug, Eq, ToJson)

///|
/// Return cpdfcontent's initial page-boundary clipping path.
pub fn pdf_content_initial_clipping_path(
  minx : Double,
  miny : Double,
  maxx : Double,
  maxy : Double,
) -> Array[PdfPath] {
  [
    {
      winding_rule: PdfPathEvenOdd,
      subpaths: [
        {
          hole: PdfPathNotHole,
          closure: PdfPathClosed,
          segments: [
            PdfPathStraight(point2(minx, miny), point2(minx, maxy)),
            PdfPathStraight(point2(minx, maxy), point2(maxx, maxy)),
            PdfPathStraight(point2(maxx, maxy), point2(maxx, miny)),
            PdfPathStraight(point2(maxx, miny), point2(minx, miny)),
          ],
        },
      ],
    },
  ]
}

///|
/// Return cpdfcontent's initial drawn-path placeholder.
pub fn pdf_content_initial_drawn_path() -> PdfDrawnPath {
  {
    stroked: false,
    filled: false,
    path: { winding_rule: PdfPathEvenOdd, subpaths: [] },
  }
}

///|
fn pdf_content_initial_dash_pattern() -> PdfContentDashPattern {
  { array: [], phase: 0.0 }
}

///|
fn pdf_content_initial_colour_values(
  space : PdfColourSpace,
) -> PdfContentColourValues {
  match space {
    PdfDeviceGray | PdfCalGray(_) | PdfPattern => PdfContentColourFloats([0.0])
    PdfDeviceRGB | PdfCalRGB(_) => PdfContentColourFloats([0.0, 0.0, 0.0])
    PdfLab(_) => PdfContentColourFloats([0.0, 0.0, 0.0, 0.0])
    PdfDeviceCMYK => PdfContentColourFloats([0.0, 0.0, 0.0, 1.0])
    PdfICCBased(components, _, _, _, _) =>
      PdfContentColourFloats([ for _ in 0.. 0.0 ])
    PdfIndexed(_) => PdfContentColourFloats([0.0])
    PdfPatternWithBaseColourSpace(base)
    | PdfSeparation(_, base, _)
    | PdfDeviceN(_, base, _, _) => pdf_content_initial_colour_values(base)
  }
}

///|
/// Return cpdfcontent's initial path drawing state for a page boundary.
pub fn pdf_content_initial_path_state(
  minx : Double,
  miny : Double,
  maxx : Double,
  maxy : Double,
) -> PdfContentPathState {
  {
    clipping_path: pdf_content_initial_clipping_path(minx, miny, maxx, maxy),
    colourspace_stroke: PdfDeviceGray,
    colourspace_non_stroke: PdfDeviceGray,
    colour_stroke: PdfContentColourFloats([1.0]),
    colour_non_stroke: PdfContentColourFloats([1.0]),
    line_width: 1.0,
    line_cap: 0,
    line_join: 0,
    dash_pattern: pdf_content_initial_dash_pattern(),
    rendering_intent: "/RelativeColorimetric",
  }
}

///|
/// Return cpdfcontent's initial clipping-only state for a page boundary.
pub fn pdf_content_initial_clipping_state(
  minx : Double,
  miny : Double,
  maxx : Double,
  maxy : Double,
) -> PdfContentClippingState {
  { clipping_path: pdf_content_initial_clipping_path(minx, miny, maxx, maxy) }
}

///|
/// Return cpdfcontent's initial glyph state fields for a page boundary.
pub fn pdf_content_initial_glyph_state(
  minx : Double,
  miny : Double,
  maxx : Double,
  maxy : Double,
) -> PdfContentGlyphState {
  {
    rendering_mode: 0,
    knockout: true,
    font: "",
    font_size: 12.0,
    clipping_path: pdf_content_initial_clipping_path(minx, miny, maxx, maxy),
  }
}

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

///|
/// Return a clipping-only content state from the current clipping path stack.
pub fn pdf_content_clipping_state_of_paths(
  clipping_path : ArrayView[PdfPath],
) -> PdfContentClippingState {
  { clipping_path: pdf_content_state_copy_paths(clipping_path) }
}

///|
/// Return this path state with a replaced clipping path stack.
pub fn PdfContentPathState::content_with_clipping_path(
  self : PdfContentPathState,
  clipping_path : ArrayView[PdfPath],
) -> PdfContentPathState {
  { ..self, clipping_path: pdf_content_state_copy_paths(clipping_path) }
}

///|
/// Compatibility wrapper for `PdfContentPathState::content_with_clipping_path`.
pub fn pdf_content_path_state_with_clipping_path(
  state : PdfContentPathState,
  clipping_path : ArrayView[PdfPath],
) -> PdfContentPathState {
  state.content_with_clipping_path(clipping_path)
}

///|
/// Return the JSON-facing glyph state for a text state and clipping path.
pub fn PdfContentTextState::content_glyph_state(
  self : PdfContentTextState,
  clipping_path : ArrayView[PdfPath],
) -> PdfContentGlyphState {
  {
    rendering_mode: self.rendering_mode,
    knockout: self.knockout,
    font: self.font,
    font_size: self.font_size,
    clipping_path: pdf_content_state_copy_paths(clipping_path),
  }
}

///|
/// Compatibility wrapper for `PdfContentTextState::content_glyph_state`.
pub fn pdf_content_glyph_state_of_text_state(
  state : PdfContentTextState,
  clipping_path : ArrayView[PdfPath],
) -> PdfContentGlyphState {
  state.content_glyph_state(clipping_path)
}

///|
fn pdf_content_state_copy_doubles(values : ArrayView[Double]) -> Array[Double] {
  [
    for value in values => value
  ]
}

///|
fn pdf_content_state_name_text(name : PdfName) -> String {
  @ascii.decode_lossy(name.bytes())
}

///|
/// Apply the cpdfcontent path-state operators that do not require resources.
pub fn PdfContentPathState::content_apply_path_state_op(
  self : PdfContentPathState,
  op : PdfContentOp,
) -> PdfContentPathState {
  match op {
    Op_w(width) => { ..self, line_width: width }
    Op_J(cap) => { ..self, line_cap: cap }
    Op_j(join) => { ..self, line_join: join }
    Op_d(dashes, phase) =>
      {
        ..self,
        dash_pattern: { array: pdf_content_state_copy_doubles(dashes), phase },
      }
    Op_ri(intent) =>
      { ..self, rendering_intent: pdf_content_state_name_text(intent) }
    Op_CS(name) =>
      match pdf_colour_space_of_device_name(name) {
        Some(space) =>
          {
            ..self,
            colourspace_stroke: space,
            colour_stroke: pdf_content_initial_colour_values(space),
          }
        None => self
      }
    Op_cs(name) =>
      match pdf_colour_space_of_device_name(name) {
        Some(space) =>
          {
            ..self,
            colourspace_non_stroke: space,
            colour_non_stroke: pdf_content_initial_colour_values(space),
          }
        None => self
      }
    Op_SC(values) | Op_SCN(values) =>
      {
        ..self,
        colour_stroke: PdfContentColourFloats(
          pdf_content_state_copy_doubles(values),
        ),
      }
    Op_sc(values) | Op_scn(values) =>
      {
        ..self,
        colour_non_stroke: PdfContentColourFloats(
          pdf_content_state_copy_doubles(values),
        ),
      }
    Op_SCNName(name, values) =>
      {
        ..self,
        colour_non_stroke: PdfContentColourNamed(
          pdf_content_state_name_text(name),
          pdf_content_state_copy_doubles(values),
        ),
      }
    Op_scnName(name, values) =>
      {
        ..self,
        colour_stroke: PdfContentColourNamed(
          pdf_content_state_name_text(name),
          pdf_content_state_copy_doubles(values),
        ),
      }
    Op_G(value) =>
      {
        ..self,
        colourspace_stroke: PdfDeviceGray,
        colour_stroke: PdfContentColourFloats([value]),
      }
    Op_g(value) =>
      {
        ..self,
        colourspace_non_stroke: PdfDeviceGray,
        colour_non_stroke: PdfContentColourFloats([value]),
      }
    Op_RG(red, green, blue) =>
      {
        ..self,
        colourspace_stroke: PdfDeviceRGB,
        colour_stroke: PdfContentColourFloats([red, green, blue]),
      }
    Op_rg(red, green, blue) =>
      {
        ..self,
        colourspace_non_stroke: PdfDeviceRGB,
        colour_non_stroke: PdfContentColourFloats([red, green, blue]),
      }
    Op_K(cyan, magenta, yellow, black) =>
      {
        ..self,
        colourspace_stroke: PdfDeviceCMYK,
        colour_stroke: PdfContentColourFloats([cyan, magenta, yellow, black]),
      }
    Op_k(cyan, magenta, yellow, black) =>
      {
        ..self,
        colourspace_non_stroke: PdfDeviceCMYK,
        colour_non_stroke: PdfContentColourFloats([cyan, magenta, yellow, black]),
      }
    _ => self
  }
}

///|
/// Compatibility wrapper for `PdfContentPathState::content_apply_path_state_op`.
pub fn pdf_content_apply_path_state_op(
  state : PdfContentPathState,
  op : PdfContentOp,
) -> PdfContentPathState {
  state.content_apply_path_state_op(op)
}

///|
fn pdf_colour_space_content_name(space : PdfColourSpace) -> String {
  match space {
    PdfDeviceGray => "DeviceGray"
    PdfDeviceRGB => "DeviceRGB"
    PdfDeviceCMYK => "DeviceCMYK"
    PdfCalGray(_) => "CalGray"
    PdfCalRGB(_) => "CalRGB"
    PdfLab(_) => "Lab"
    PdfICCBased(_) => "ICCBased"
    PdfIndexed(_) => "Indexed"
    PdfPattern => "Pattern"
    PdfPatternWithBaseColourSpace(_) => "PatternWithBaseColourspace"
    PdfSeparation(_) => "Separation"
    PdfDeviceN(_) => "DeviceN"
  }
}

///|
/// Return cpdfcontent's JSON colour-space name.
pub fn PdfColourSpace::content_json(self : PdfColourSpace) -> Json {
  Json::string(pdf_colour_space_content_name(self))
}

///|
/// Compatibility wrapper for `PdfColourSpace::content_json`.
pub fn pdf_colour_space_content_json(space : PdfColourSpace) -> Json {
  space.content_json()
}

///|
/// Return cpdfcontent's JSON shape for colour operands.
pub fn PdfContentColourValues::content_json(
  self : PdfContentColourValues,
) -> Json {
  match self {
    PdfContentColourFloats(values) =>
      Json::array([ for value in values => Json::number(value) ])
    PdfContentColourNamed(name, values) => {
      let output : Array[Json] = [Json::string(name)]
      for value in values {
        output.push(Json::number(value))
      }
      Json::array(output)
    }
    PdfContentColourPattern => Json::string("pattern")
  }
}

///|
/// Compatibility wrapper for `PdfContentColourValues::content_json`.
pub fn pdf_content_colour_values_content_json(
  values : PdfContentColourValues,
) -> Json {
  values.content_json()
}

///|
/// Return cpdfcontent's JSON shape for a path dash pattern.
pub fn PdfContentDashPattern::content_json(
  self : PdfContentDashPattern,
) -> Json {
  Json::object(
    Map([
      ("array", Json::array([ for value in self.array => Json::number(value) ])),
      ("phase", Json::number(self.phase)),
    ]),
  )
}

///|
/// Compatibility wrapper for `PdfContentDashPattern::content_json`.
pub fn pdf_content_dash_pattern_content_json(
  pattern : PdfContentDashPattern,
) -> Json {
  pattern.content_json()
}

///|
fn pdf_content_clipping_path_content_json(paths : ArrayView[PdfPath]) -> Json {
  Json::array(
    [
      for path in paths => {
        let drawn : PdfDrawnPath = { stroked: false, filled: false, path }
        drawn.content_json()
      }
    ],
  )
}

///|
/// Return cpdfcontent's JSON shape for path drawing state.
pub fn PdfContentPathState::content_json(self : PdfContentPathState) -> Json {
  Json::object(
    Map([
      (
        "clipping path",
        pdf_content_clipping_path_content_json(self.clipping_path),
      ),
      ("colourspace stroke", self.colourspace_stroke.content_json()),
      ("colourspace non stroke", self.colourspace_non_stroke.content_json()),
      ("colour stroke", self.colour_stroke.content_json()),
      ("colour non stroke", self.colour_non_stroke.content_json()),
      ("line width", Json::number(self.line_width)),
      ("line cap", pdf_util_json_number(self.line_cap)),
      ("line join", pdf_util_json_number(self.line_join)),
      ("dash pattern", self.dash_pattern.content_json()),
      ("rendering intent", Json::string(self.rendering_intent)),
    ]),
  )
}

///|
/// Compatibility wrapper for `PdfContentPathState::content_json`.
pub fn pdf_content_path_state_content_json(state : PdfContentPathState) -> Json {
  state.content_json()
}

///|
/// Return cpdfcontent's JSON shape for clipping-only state.
pub fn PdfContentClippingState::content_json(
  self : PdfContentClippingState,
) -> Json {
  Json::object(
    Map([
      (
        "clipping path",
        pdf_content_clipping_path_content_json(self.clipping_path),
      ),
    ]),
  )
}

///|
/// Compatibility wrapper for `PdfContentClippingState::content_json`.
pub fn pdf_content_clipping_state_content_json(
  state : PdfContentClippingState,
) -> Json {
  state.content_json()
}

///|
/// Return cpdfcontent's JSON shape for glyph state.
pub fn PdfContentGlyphState::content_json(self : PdfContentGlyphState) -> Json {
  Json::object(
    Map([
      ("rendering mode", pdf_util_json_number(self.rendering_mode)),
      ("knockout", Json::boolean(self.knockout)),
      ("font", Json::string(self.font)),
      ("font size", Json::number(self.font_size)),
      (
        "clipping path",
        pdf_content_clipping_path_content_json(self.clipping_path),
      ),
    ]),
  )
}

///|
/// Compatibility wrapper for `PdfContentGlyphState::content_json`.
pub fn pdf_content_glyph_state_content_json(
  state : PdfContentGlyphState,
) -> Json {
  state.content_json()
}

///|
/// Return cpdfcontent's JSON shape for a content object.
pub fn PdfContentObject::content_json(self : PdfContentObject) -> Json {
  match self {
    PdfContentGlyphObject(charcode, bytes, extracted_text) =>
      Json::object(
        Map([
          ("obj", Json::string("glyph")),
          ("charcode", pdf_util_json_number(charcode)),
          ("bytes", Json::string(bytes)),
          ("extracted text", Json::string(extracted_text)),
        ]),
      )
    PdfContentInlineImageObject =>
      Json::object(
        Map([
          ("obj", Json::string("inline image")),
          ("inline image", Json::null()),
        ]),
      )
    PdfContentImageObject(name) =>
      Json::object(
        Map([("obj", Json::string("image")), ("image", Json::string(name))]),
      )
    PdfContentPathObject(path) =>
      Json::object(
        Map([("obj", Json::string("path")), ("path", path.content_json())]),
      )
    PdfContentShadingObject(name) =>
      Json::object(
        Map([("obj", Json::string("shading")), ("shading", Json::string(name))]),
      )
  }
}

///|
/// Compatibility wrapper for `PdfContentObject::content_json`.
pub fn pdf_content_object_content_json(object : PdfContentObject) -> Json {
  object.content_json()
}

///|
/// Return cpdfcontent's JSON shape for a content state variant.
pub fn PdfContentState::content_json(self : PdfContentState) -> Json {
  match self {
    PdfContentStateGlyph(state) => state.content_json()
    PdfContentStatePath(state) => state.content_json()
    PdfContentStateClipping(state) => state.content_json()
  }
}

///|
/// Compatibility wrapper for `PdfContentState::content_json`.
pub fn pdf_content_state_content_json(state : PdfContentState) -> Json {
  state.content_json()
}

///|
/// Return cpdfcontent's eight-number bbox array.
pub fn PdfContentBoundingBox::content_json(
  self : PdfContentBoundingBox,
) -> Json {
  Json::array([
    Json::number(self.x0),
    Json::number(self.y0),
    Json::number(self.x1),
    Json::number(self.y1),
    Json::number(self.x2),
    Json::number(self.y2),
    Json::number(self.x3),
    Json::number(self.y3),
  ])
}

///|
/// Compatibility wrapper for `PdfContentBoundingBox::content_json`.
pub fn pdf_content_bounding_box_content_json(
  bbox : PdfContentBoundingBox,
) -> Json {
  bbox.content_json()
}

///|
/// Return cpdfcontent's JSON shape for one content-list entry.
pub fn PdfContentJsonEntry::content_json(self : PdfContentJsonEntry) -> Json {
  Json::object(
    Map([
      ("object", self.object.content_json()),
      ("state", self.state.content_json()),
      ("bbox", self.bounding_box.content_json()),
    ]),
  )
}

///|
/// Compatibility wrapper for `PdfContentJsonEntry::content_json`.
pub fn pdf_content_json_entry_content_json(entry : PdfContentJsonEntry) -> Json {
  entry.content_json()
}

///|
/// Return cpdfcontent's JSON shape for a content-list entry array.
pub fn pdf_content_json_entries_content_json(
  entries : ArrayView[PdfContentJsonEntry],
) -> Json {
  Json::array([ for entry in entries => entry.content_json() ])
}