///|
/// Version reported by CLI integrations.
pub let mbmath_version : String = "0.3.1"

///|
/// Version string used by CLI-style integrations.
pub fn cli_version() -> String {
  mbmath_version
}

///|
/// Parse and evaluate a numeric literal in CLI compatibility mode.
///
/// This mirrors the non-interactive literal path used by mpmath's CLI tests.
pub fn MPContext::cli_eval_literal(
  self : MPContext,
  literal : String,
  dps? : Int = 32,
) -> String raise MPError {
  self.to_string(self.make_float(literal), dps~)
}

///|
/// Evaluate a simple integer division expression (`numer/denom`) in CLI mode.
pub fn MPContext::cli_eval_division(
  self : MPContext,
  numer : Int,
  denom : Int,
  dps? : Int = 32,
) -> String raise MPError {
  let out = self.div(self.make_int(numer), self.make_int(denom))
  self.to_string(out, dps~)
}

///|
/// Minimal axis container used by `plot/cplot` APIs.
pub struct PlotAxes {
  mut xlabel : String
  mut ylabel : String
} derive(Debug, Eq)

///|
pub impl Show for PlotAxes with fn output(self, logger) {
  logger.write_object(self.to_repr())
}

///|
/// Create an empty plot axes object.
pub fn plot_axes() -> PlotAxes {
  { xlabel: "", ylabel: "" }
}

///|
type UnaryMpcFn = (@mpc.RawMpc) -> @mpc.RawMpc

///|
fn plot_linspace(
  start : @mpf.RawMpf,
  end_ : @mpf.RawMpf,
  samples : Int,
  prec : Int,
) -> Array[@mpf.RawMpf] raise MPError {
  if samples < 2 {
    raise ValueError("plot: samples must be >= 2")
  }
  let span = @mpf.mpf_sub(end_, start, prec, @mpf.round_nearest)
  let step = @mpf.mpf_div(
    span,
    @mpf.from_int(samples - 1),
    prec,
    @mpf.round_nearest,
  ) catch {
    err => raise from_mpf_error(err)
  }
  let out : Array[@mpf.RawMpf] = []
  for i in 0.. Array[(@mpf.RawMpf, @mpf.RawMpf)] raise MPError {
  let (a, b) = interval
  let wp = if self.precision() > 0 { self.precision() + 24 } else { 80 }
  let xs = plot_linspace(a, b, samples, wp)
  axes.xlabel = "x"
  axes.ylabel = "f(x)"
  let out : Array[(@mpf.RawMpf, @mpf.RawMpf)] = []
  for x in xs {
    let y = f(x)
    out.push(
      (
        @mpf.mpf_pos(x, self.precision(), self.round_mode()),
        @mpf.mpf_pos(y, self.precision(), self.round_mode()),
      ),
    )
  }
  out
}

///|
/// Sample `f` on a rectangular complex grid and attach mpmath-style labels.
pub fn MPContext::cplot(
  self : MPContext,
  f : UnaryMpcFn,
  re_interval : (@mpf.RawMpf, @mpf.RawMpf),
  im_interval : (@mpf.RawMpf, @mpf.RawMpf),
  axes : PlotAxes,
  samples? : Int = 17,
) -> Array[(@mpc.RawMpc, @mpc.RawMpc)] raise MPError {
  let (rx0, rx1) = re_interval
  let (iy0, iy1) = im_interval
  let wp = if self.precision() > 0 { self.precision() + 24 } else { 80 }
  let rs = plot_linspace(rx0, rx1, samples, wp)
  let imags = plot_linspace(iy0, iy1, samples, wp)
  axes.xlabel = "Re(z)"
  axes.ylabel = "Im(z)"
  let out : Array[(@mpc.RawMpc, @mpc.RawMpc)] = []
  for re in rs {
    for im in imags {
      let z = @mpc.from_parts(re, im)
      let w = f(z)
      out.push(
        (
          @mpc.mpc_pos(z, self.precision(), self.round_mode()),
          @mpc.mpc_pos(w, self.precision(), self.round_mode()),
        ),
      )
    }
  }
  out
}