///|
/// `ParamPair` represents an X,Y absolute coordinate pair of parameters.
pub(all) struct ParamPair {
  x : Double
  y : Double
} derive(Debug, Eq)

///|
/// `to_string` returns a string representation of a `ParamPair`.
pub fn ParamPair::to_string(self : ParamPair) -> String {
  "(\{svg_num(self.x)},\{svg_num(self.y)})"
}

///|
pub impl Show for ParamPair with fn output(self, logger) {
  logger.write_string(self.to_string())
}

///|
/// `PathCmd` represents an individual absolute SVG command.
pub(all) struct PathCmd {
  cmd : AbsoluteCmd
  gerber_lp : GerberLP
  params : Array[ParamPair]
} derive(Debug, Eq)

///|
pub impl Show for PathCmd with fn output(self, logger) {
  let { cmd, gerber_lp, params } = self
  logger.write_string(
    (
      $|{cmd: \{cmd}, gerber_lp: \{gerber_lp}, params: \{Repr(params)}}
    ),
  )
}

///|
test "PathCmd show interface" {
  let cmd = { cmd: M, gerber_lp: Dark, params: [{ x: 10.0, y: 20.0 }] }
  inspect(
    cmd,
    content=(
      #|{cmd: M, gerber_lp: Dark, params: [{ x: 10, y: 20 }]}
    ),
  )
}

///|
/// `from_svg_cmd` creates a `PathCmd` from a raw SVG command and a `GerberLP`.
pub fn PathCmd::from_svg_cmd(
  svg_cmd : Cmd,
  gerber_lp : GerberLP,
) -> PathCmd raise FontError {
  let cmd = match svg_cmd.c {
    "M" => M
    "L" => L
    "C" => C
    "Q" => Q
    "Z" => Z
    c =>
      raise FontError("PathCmd::from_svg_cmd: unsupported SVG command '\{c}'")
  }
  if svg_cmd.p.0.length() % 2 != 0 {
    raise FontError(
      "PathCmd::from_svg_cmd: unexpected odd params length in svg_cmd: \{svg_cmd}",
    )
  }

  //
  let params : Array[ParamPair] = Array::makei(svg_cmd.p.0.length() / 2, fn(i) {
    let x = svg_cmd.p.0[i * 2]
    let y = svg_cmd.p.0[i * 2 + 1]
    { x, y }
  })

  //
  { cmd, gerber_lp, params }
}

///|
/// `to_svg_cmd` converts a `PathCmd` back to a raw SVG command and its `GerberLP` code.
pub fn PathCmd::to_svg_cmd(self : PathCmd) -> (Cmd, String) {
  let gerber_lp = match self.cmd {
    M =>
      match self.gerber_lp {
        Dark => "d"
        Clear => "c"
      }
    _ => ""
  }
  let p : Array[Double] = Array::makei(self.params.length() * 2, fn(i) {
    let pp = self.params[i / 2]
    if i % 2 == 0 {
      pp.x
    } else {
      pp.y
    }
  })
  let c = "\{self.cmd}"
  let cmd = { c, p }
  (cmd, gerber_lp)
}

///|
/// `bbox` returns the minimum bounding box of a `PathCmd`.
pub fn PathCmd::bbox(self : PathCmd) -> @geom.BoundingBox {
  let r = @geom.BoundingBox::max_reversed()
  for index, pp in self.params {
    if index == 0 {
      r.min.x = pp.x
      r.min.y = pp.y
      r.max.x = pp.x
      r.max.y = pp.y
    } else {
      let _ = r.expand_to_include_point(@geom.pt(pp.x, pp.y))
    }
  }
  r
}

///|
/// `PathCmdFn` represents a function that processes or transforms individual commands.
/// The first argument is the index within the command.
pub(all) struct PathCmdFn((Int, PathCmd) -> PathCmd)

///|
/// `clone` makes a deep copy of a `PathCmd`.
pub fn PathCmd::clone(self : PathCmd) -> PathCmd {
  let params = Array::makei(self.params.length(), fn(i) {
    let param = self.params[i]
    { x: param.x, y: param.y }
  })
  { cmd: self.cmd, gerber_lp: self.gerber_lp, params }
}