///|
/// Dependency-free SVG output for ternary diagrams and audit plots.
pub(all) struct SvgCanvas {
  width : Int
  height : Int
  elements : Array[String]
} derive(Debug, Eq)

///|
pub fn SvgCanvas::new(
  width~ : Int,
  height~ : Int,
) -> SvgCanvas raise @domain.ExtractionError {
  if width < 1 || height < 1 {
    raise @domain.ExtractionError::InvalidArgument(
      message="SVG dimensions must be positive",
    )
  }
  { width, height, elements: [] }
}

///|
pub fn SvgCanvas::line(
  self : SvgCanvas,
  from : @geometry.Point,
  to : @geometry.Point,
  stroke~ : String,
  width? : Double = 1.0,
) -> SvgCanvas raise @domain.ExtractionError {
  @domain.require_positive("stroke width", width)
  let elements = self.elements.copy()
  elements.push(
    "",
  )
  { width: self.width, height: self.height, elements }
}

///|
pub fn SvgCanvas::circle(
  self : SvgCanvas,
  center : @geometry.Point,
  radius~ : Double,
  fill~ : String,
) -> SvgCanvas raise @domain.ExtractionError {
  @domain.require_positive("circle radius", radius)
  let elements = self.elements.copy()
  elements.push(
    "",
  )
  { width: self.width, height: self.height, elements }
}

///|
pub fn SvgCanvas::polyline(
  self : SvgCanvas,
  points : ArrayView[@geometry.Point],
  stroke~ : String,
  fill? : String = "none",
) -> SvgCanvas raise @domain.ExtractionError {
  if points.length() < 2 {
    raise @domain.ExtractionError::InsufficientData(
      required=2,
      actual=points.length(),
    )
  }
  let coordinates = [ for point in points => "\{point.x},\{point.y}" ].join(" ")
  let elements = self.elements.copy()
  elements.push(
    "",
  )
  { width: self.width, height: self.height, elements }
}

///|
pub fn SvgCanvas::render(self : SvgCanvas) -> String {
  "" +
  self.elements.join("") +
  ""
}

///|
fn escape(value : String) -> String {
  value
  .replace(old="&", new="&")
  .replace(old="\"", new=""")
  .replace(old="<", new="<")
}

///|
pub fn ternary_triangle(
  width : Int,
  height : Int,
) -> SvgCanvas raise @domain.ExtractionError {
  let canvas = SvgCanvas::new(width~, height~)
  let margin = width.to_double() * 0.08
  let left = @geometry.Point::new(x=margin, y=height.to_double() - margin)
  let right = @geometry.Point::new(
    x=width.to_double() - margin,
    y=height.to_double() - margin,
  )
  let top = @geometry.Point::new(x=width.to_double() / 2.0, y=margin)
  canvas.polyline([left, right, top, left], stroke="#333333", fill="none")
}

///|
pub fn map_unit_triangle(
  point : @geometry.Point,
  width : Double,
  height : Double,
) -> @geometry.Point raise @domain.ExtractionError {
  @domain.require_positive("canvas width", width)
  @domain.require_positive("canvas height", height)
  @geometry.Point::new(x=point.x * width, y=(1.0 - point.y) * height)
}