///|
pub(all) struct StackedBarChart {
  title : String
  x_labels : Array[String]
  series_list : Array[Series]
  width : Float
  height : Float
  config : ChartConfig
}

///|
pub fn StackedBarChart::new() -> StackedBarChart {
  {
    title: "",
    x_labels: [],
    series_list: [],
    width: 800.0,
    height: 400.0,
    config: ChartConfig::default(),
  }
}

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

///|
pub fn StackedBarChart::x_labels(
  self : StackedBarChart,
  labels : Array[String],
) -> StackedBarChart {
  { ..self, x_labels: labels }
}

///|
pub fn StackedBarChart::series(
  self : StackedBarChart,
  s : Series,
) -> StackedBarChart {
  let new_list = self.series_list
  new_list.push(s)
  { ..self, series_list: new_list }
}

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

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

///|
pub fn StackedBarChart::config(
  self : StackedBarChart,
  c : ChartConfig,
) -> StackedBarChart {
  { ..self, config: c }
}

// Compute the sum of values across all series for a single category

///|
fn compute_category_sum(
  series_list : Array[Series],
  ci : Int,
  si : Int,
  acc : Float,
) -> Float {
  if si < series_list.length() {
    compute_category_sum(
      series_list,
      ci,
      si + 1,
      acc + series_list[si].values[ci],
    )
  } else {
    acc
  }
}

// Find the maximum total (stacked) value across all categories

///|
fn find_max_category_sum(
  series_list : Array[Series],
  num_categories : Int,
) -> Float {
  fn loop_category(ci : Int, max_sum : Float) -> Float {
    if ci < num_categories {
      let sum = compute_category_sum(series_list, ci, 0, 0.0)
      let new_max = if sum > max_sum { sum } else { max_sum }
      loop_category(ci + 1, new_max)
    } else {
      max_sum
    }
  }
  loop_category(0, 0.0)
}

// Render stacked rectangles for a single category (recursive over series)

///|
fn render_category_stacked(
  acc : String,
  series_list : Array[Series],
  num_series : Int,
  ci : Int,
  bar_x : Float,
  bar_w : Float,
  y_min : Float,
  y_max : Float,
  chart_h : Float,
  si : Int,
  cumulative_y : Float,
  config : ChartConfig,
) -> String {
  if si < num_series {
    let val = series_list[si].values[ci]
    let bar_h = (val - y_min) / (y_max - y_min) * chart_h
    let bar_y = cumulative_y - bar_h
    let color_str = get_chart_color(config, si)
    let rect_str = rect(bar_x, bar_y, bar_w, bar_h, color_str)
    render_category_stacked(
      acc + rect_str,
      series_list,
      num_series,
      ci,
      bar_x,
      bar_w,
      y_min,
      y_max,
      chart_h,
      si + 1,
      cumulative_y - bar_h,
      config,
    )
  } else {
    acc
  }
}

// Render stacked bars and x-axis labels (recursive over categories)

///|
fn render_stacked_bars_and_labels(
  acc : String,
  series_list : Array[Series],
  x_labels : Array[String],
  num_categories : Int,
  num_series : Int,
  bar_w : Float,
  group_w : Float,
  y_min : Float,
  y_max : Float,
  height : Float,
  mt : Float,
  mb : Float,
  ml : Float,
  mr : Float,
  chart_width : Float,
  baseline_y : Float,
  ci : Int,
  config : ChartConfig,
) -> String {
  if ci < num_categories {
    let group_x = x_to_svg(
      Float::from_int(ci),
      Float::from_int(num_categories),
      chart_width,
      ml,
      mr,
    )
    let chart_h = height - mt - mb
    let bar_x = group_x + (group_w - bar_w) / 2.0
    let with_bars = render_category_stacked(
      acc, series_list, num_series, ci, bar_x, bar_w, y_min, y_max, chart_h, 0, baseline_y,
      config,
    )
    let label_x = group_x + group_w / 2.0
    let with_label = with_bars +
      text(
        label_x,
        baseline_y + 16.0,
        x_labels[ci],
        config.axis_font_size,
        "middle",
      )
    render_stacked_bars_and_labels(
      with_label,
      series_list,
      x_labels,
      num_categories,
      num_series,
      bar_w,
      group_w,
      y_min,
      y_max,
      height,
      mt,
      mb,
      ml,
      mr,
      chart_width,
      baseline_y,
      ci + 1,
      config,
    )
  } else {
    acc
  }
}

///|
pub fn StackedBarChart::render(self : StackedBarChart) -> String {
  let mt : Float = 40.0
  let mb : Float = 60.0
  let ml : Float = 60.0
  let mr : Float = 30.0

  let num_categories = min_categories(
    self.series_list,
    self.x_labels.length(),
    0,
  )
  let num_series = self.series_list.length()

  // Compute y-axis range: from 0 to max total sum across all categories
  let max_sum = if num_categories > 0 && num_series > 0 {
    find_max_category_sum(self.series_list, num_categories)
  } else {
    0.0
  }
  let data_max = if max_sum > 0.0 { max_sum } else { 1.0 }
  let axis = compute_y_axis(0.0, data_max, self.height, mt, mb)

  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,
      self.config.title_font_size,
      "middle",
    )
  } else {
    base
  }

  // Y-axis grid lines and labels
  let with_y_grid = render_y_grid(
    with_title,
    axis.ticks,
    axis.tick_labels,
    axis.y_min,
    axis.y_max,
    self.height,
    mt,
    mb,
    ml,
    mr,
    self.width,
    0,
    self.config,
  )

  // X-axis baseline
  let baseline_y = y_to_svg(
    axis.y_min,
    axis.y_min,
    axis.y_max,
    self.height,
    mt,
    mb,
  )
  let with_baseline = with_y_grid +
    line(ml, baseline_y, self.width - mr, baseline_y, "#888888", 1.0)

  // Stacked bars and x labels
  let group_w = if num_categories > 0 {
    (self.width - ml - mr) / Float::from_int(num_categories)
  } else {
    0.0
  }
  let bar_w = group_w * 0.6
  let with_bars = if num_categories > 0 && num_series > 0 {
    render_stacked_bars_and_labels(
      with_baseline,
      self.series_list,
      self.x_labels,
      num_categories,
      num_series,
      bar_w,
      group_w,
      axis.y_min,
      axis.y_max,
      self.height,
      mt,
      mb,
      ml,
      mr,
      self.width,
      baseline_y,
      0,
      self.config,
    )
  } else {
    with_baseline
  }

  // Legend
  let names : Array[String] = []
  for i = 0; i < self.series_list.length(); i = i + 1 {
    names.push(self.series_list[i].name)
  }
  let with_legend = with_bars +
    render_legend(names, self.width, self.height - 10.0, self.config)

  with_legend + svg_close()
}