///|
struct PieChart {
  title : String
  slices : Array[Slice]
  width : Float
  height : Float
}

///|
pub fn PieChart::new() -> PieChart {
  { title: "", slices: [], width: 500.0, height: 400.0 }
}

///|
pub fn PieChart::title(self : PieChart, t : String) -> PieChart {
  { ..self, title: t }
}

///|
pub fn PieChart::slice(
  self : PieChart,
  name : String,
  value : Float,
) -> PieChart {
  let new_slices = self.slices
  new_slices.push(Slice::new(name, value))
  { ..self, slices: new_slices }
}

///|
pub fn PieChart::width(self : PieChart, w : Float) -> PieChart {
  { ..self, width: w }
}

///|
pub fn PieChart::height(self : PieChart, h : Float) -> PieChart {
  { ..self, height: h }
}

// Normalize angle to [0, 2π) range

///|
fn normalize_angle(angle : Float) -> Float {
  let tau : Float = 2.0 * 3.1415926535
  let mut a = angle
  while a < 0.0 {
    a = a + tau
  }
  while a >= tau {
    a = a - tau
  }
  a
}

// Taylor series sin approximation with angle reduction to [0, π/2]

///|
fn sin_approx(x : Float) -> Float {
  let pi : Float = 3.1415926535
  let x_norm = normalize_angle(x)
  let sign : Float = if x_norm > pi { -1.0 } else { 1.0 }
  let x_reduced : Float = if x_norm > pi { x_norm - pi } else { x_norm }
  let x_final = if x_reduced > pi / 2.0 { pi - x_reduced } else { x_reduced }
  let x2 = x_final * x_final
  let x3 = x2 * x_final
  let x5 = x3 * x2
  let x7 = x5 * x2
  let f6 : Float = 6.0
  let f120 : Float = 120.0
  let f5040 : Float = 5040.0
  sign * (x_final - x3 / f6 + x5 / f120 - x7 / f5040)
}

// Taylor series cos approximation with angle reduction to [0, π/2]

///|
fn cos_approx(x : Float) -> Float {
  let pi : Float = 3.1415926535
  let x_norm = normalize_angle(x)
  let sign : Float = if x_norm > pi { -1.0 } else { 1.0 }
  let x_reduced : Float = if x_norm > pi { x_norm - pi } else { x_norm }
  let x_final = if x_reduced > pi / 2.0 { pi - x_reduced } else { x_reduced }
  let x2 = x_final * x_final
  let x4 = x2 * x2
  let x6 = x4 * x2
  let one : Float = 1.0
  let two : Float = 2.0
  let f24 : Float = 24.0
  let f720 : Float = 720.0
  sign * (one - x2 / two + x4 / f24 - x6 / f720)
}

///|
fn polar_to_cartesian(
  cx : Float,
  cy : Float,
  r : Float,
  angle_rad : Float,
) -> (Float, Float) {
  let x = cx + r * cos_approx(angle_rad)
  let y = cy + r * sin_approx(angle_rad)
  (x, y)
}

///|
fn slice_path(
  cx : Float,
  cy : Float,
  r : Float,
  start_angle : Float,
  end_angle : Float,
) -> String {
  let (x1, y1) = polar_to_cartesian(cx, cy, r, end_angle)
  let (x2, y2) = polar_to_cartesian(cx, cy, r, start_angle)
  let pi : Float = 3.1415926535
  let large_arc = if end_angle - start_angle > pi { "1" } else { "0" }
  "M " +
  cx.to_string() +
  " " +
  cy.to_string() +
  " L " +
  x1.to_string() +
  " " +
  y1.to_string() +
  " A " +
  r.to_string() +
  " " +
  r.to_string() +
  " 0 " +
  large_arc +
  " 0 " +
  x2.to_string() +
  " " +
  y2.to_string() +
  " Z"
}

// Recursive sum of slice values

///|
fn sum_slice_values(slices : Array[Slice], idx : Int, acc : Float) -> Float {
  if idx >= slices.length() {
    acc
  } else {
    sum_slice_values(slices, idx + 1, acc + slices[idx].value)
  }
}

// Format percentage as integer + "%"

///|
fn format_percent(pct : Float) -> String {
  let int_pct = pct.to_int()
  let pct_str = int_pct.to_string()
  pct_str + "%"
}

// Render pie slices recursively

///|
fn render_pie_slices(
  result : String,
  slices : Array[Slice],
  total : Float,
  cx : Float,
  cy : Float,
  r : Float,
  tau : Float,
  idx : Int,
  start_angle : Float,
) -> String {
  if idx >= slices.length() {
    result
  } else {
    let fraction = slices[idx].value / total
    let sweep = fraction * tau
    let end_angle = start_angle + sweep
    let color = get_color(idx)

    let with_path = result +
      path(slice_path(cx, cy, r, start_angle, end_angle), color, "#ffffff")

    // Percentage label at mid-angle
    let mid_angle = start_angle + sweep / 2.0
    let label_r = r * 0.68
    let (lx, ly) = polar_to_cartesian(cx, cy, label_r, mid_angle)
    let pct = fraction * 100.0
    let pct_str = format_percent(pct)
    let with_label = with_path + text(lx, ly + 4.0, pct_str, 11, "middle")

    render_pie_slices(
      with_label,
      slices,
      total,
      cx,
      cy,
      r,
      tau,
      idx + 1,
      end_angle,
    )
  }
}

///|
pub fn PieChart::render(self : PieChart) -> String {
  let mt : Float = 40.0
  let mb : Float = 20.0
  let base = svg_open(self.width, self.height)

  // Title
  let with_title = if self.title != "" {
    base + text(self.width / 2.0, mt - 12.0, self.title, 16, "middle")
  } else {
    base
  }

  let num_slices = self.slices.length()
  if num_slices == 0 {
    with_title + svg_close()
  } else {
    // Calculate total value
    let total = sum_slice_values(self.slices, 0, 0.0)

    // Pie layout: center the pie in the remaining area
    let cx = self.width / 2.0
    let chart_h = self.height - mt - mb - 40.0
    let cy = mt + chart_h / 2.0
    let r = if self.width < self.height {
      self.width / 3.0
    } else {
      chart_h / 2.5
    }

    let tau : Float = 3.1415926535 * 2.0

    // Render slices
    let with_slices = render_pie_slices(
      with_title,
      self.slices,
      total,
      cx,
      cy,
      r,
      tau,
      0,
      0.0,
    )

    // Legend
    let names : Array[String] = []
    for i = 0; i < num_slices; i = i + 1 {
      names.push(self.slices[i].name)
    }
    let with_legend = with_slices +
      render_legend(
        names,
        self.width,
        self.height - 10.0,
        ChartConfig::default(),
      )

    with_legend + svg_close()
  }
}