///|
/// Export a detailed CSV with run id, status, all parameters, and all metrics
/// for an experiment.
///
/// The CSV has the following columns:
/// - run_id
/// - status
/// - start_time
/// - end_time
/// - one column per parameter key (value as string)
/// - one column per metric key (latest value as number)
pub fn TrackingStore::detailed_csv(
  self : TrackingStore,
  experiment_id : String,
) -> Result[String, TrackingError] {
  match self.get_experiment(experiment_id) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  let runs = self.runs_for_experiment(experiment_id)
  // Collect all unique parameter keys
  let param_keys : Array[String] = []
  for run in runs {
    for p in run.parameters() {
      if !array_contains_string(param_keys, p.key()) {
        param_keys.push(p.key())
      }
    }
  }
  // Collect all unique metric keys
  let metric_keys : Array[String] = []
  for run in runs {
    for m in run.metrics() {
      if !array_contains_string(metric_keys, m.key()) {
        metric_keys.push(m.key())
      }
    }
  }
  let out = StringBuilder()
  // Header
  out <+ "run_id,status,start_time,end_time"
  for key in param_keys {
    out <+ ",param_\{key}"
  }
  for key in metric_keys {
    out <+ ",metric_\{key}"
  }
  out <+ "\n"
  // Rows
  for run in runs {
    out <+
      "\{run.id()},\{run.status().label()},\{run.start_time()},\{run.end_time()}"
    for key in param_keys {
      match run.find_param(key) {
        Some(p) => out <+ ",\{p.value()}"
        None => out <+ ","
      }
    }
    for key in metric_keys {
      match run.latest_metric(key) {
        Some(m) => out <+ ",\{m.value()}"
        None => out <+ ","
      }
    }
    out <+ "\n"
  }
  Ok(out.to_string())
}

///|
/// Export a multi-experiment comparison report as Markdown.
///
/// For each experiment, the report shows:
/// - Experiment name and id
/// - Number of runs
/// - Best run by the specified metric (if any runs exist)
/// - Run status summary
pub fn TrackingStore::comparison_report(
  self : TrackingStore,
  metric_key : String,
) -> String {
  let out = StringBuilder()
  out <+ "# Multi-Experiment Comparison Report\n\n"
  if self.experiments.is_empty() {
    out <+ "No experiments recorded.\n"
    return out.to_string()
  }
  for exp in self.experiments {
    out <+ "## \{exp.name()}\n\n"
    out <+ "- **ID**: `\{exp.id()}`\n"
    out <+ "- **Runs**: \{exp.run_count()}\n"
    let runs = self.runs_for_experiment(exp.id())
    if runs.is_empty() {
      out <+ "- **Best run**: N/A (no runs)\n"
    } else {
      match self.best_run(exp.id(), metric_key) {
        Ok(best) => {
          let val = match best.latest_metric(metric_key) {
            Some(m) => m.value()
            None => 0.0
          }
          out <+ "- **Best run**: `\{best.id()}` (\{metric_key}=\{val})\n"
        }
        Err(_) => out <+ "- **Best run**: N/A (metric not found)\n"
      }
    }
    // Status summary
    let mut completed = 0
    let mut failed = 0
    let mut running = 0
    let mut other = 0
    for run in runs {
      match run.status() {
        Completed => completed += 1
        Failed => failed += 1
        Running => running += 1
        _ => other += 1
      }
    }
    out <+
      "- **Status**: \{completed} completed, \{failed} failed, \{running} running, \{other} other\n\n"
  }
  out.to_string()
}

///|
/// Export a run lineage tree as a Markdown nested list.
pub fn TrackingStore::lineage_tree_markdown(
  self : TrackingStore,
  lineage : LineageTracker,
  experiment_id : String,
) -> Result[String, TrackingError] {
  match self.get_experiment(experiment_id) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  let runs = self.runs_for_experiment(experiment_id)
  let out = StringBuilder()
  out <+ "## Run Lineage Tree\n\n"
  // Find root runs (no parent)
  let roots : Array[String] = []
  for run in runs {
    if !lineage.has_parent(run.id()) {
      roots.push(run.id())
    }
  }
  if roots.is_empty() {
    out <+ "_No root runs found._\n"
    return Ok(out.to_string())
  }
  for root in roots {
    render_lineage_node(root, lineage, out, 0)
  }
  Ok(out.to_string())
}

///|
/// Recursively render a lineage node as a Markdown list item.
fn render_lineage_node(
  run_id : String,
  lineage : LineageTracker,
  out : StringBuilder,
  depth : Int,
) -> Unit {
  for i = 0; i < depth; i = i + 1 {
    out <+ "  "
  }
  out <+ "- `\{run_id}`\n"
  let kids = lineage.children(run_id)
  for kid in kids {
    render_lineage_node(kid, lineage, out, depth + 1)
  }
}

///|
/// Export a summary table of all experiments as Markdown.
pub fn TrackingStore::experiments_table_markdown(
  self : TrackingStore,
) -> String {
  let out = StringBuilder()
  out <+ "## Experiments Summary\n\n"
  out <+ "| ID | Name | Runs | Created |\n"
  out <+ "|----|------|------|---------|\n"
  for exp in self.experiments {
    out <+
      "| `\{exp.id()}` | \{exp.name()} | \{exp.run_count()} | \{exp.created_at()} |\n"
  }
  out.to_string()
}

///|
/// Export a JSON representation of the lineage tree.
pub fn TrackingStore::lineage_json(
  self : TrackingStore,
  lineage : LineageTracker,
  experiment_id : String,
) -> Result[String, TrackingError] {
  match self.get_experiment(experiment_id) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  let runs = self.runs_for_experiment(experiment_id)
  let out = StringBuilder()
  out <+ "{\n"
  out <+ "  \"experiment_id\": \"\{escape_json(experiment_id)}\",\n"
  out <+ "  \"lineage\": [\n"
  let roots : Array[String] = []
  for run in runs {
    if !lineage.has_parent(run.id()) {
      roots.push(run.id())
    }
  }
  let mut first = true
  for root in roots {
    if !first {
      out <+ ",\n"
    }
    first = false
    render_lineage_json_node(root, lineage, out, 1)
  }
  out <+ "\n  ]\n"
  out <+ "}"
  Ok(out.to_string())
}

///|
/// Recursively render a lineage node as JSON.
fn render_lineage_json_node(
  run_id : String,
  lineage : LineageTracker,
  out : StringBuilder,
  indent : Int,
) -> Unit {
  let pad = "  ".repeat(indent)
  out <+ "\{pad}{\n"
  out <+ "\{pad}  \"run_id\": \"\{escape_json(run_id)}\",\n"
  let kids = lineage.children(run_id)
  if kids.is_empty() {
    out <+ "\{pad}  \"children\": []\n"
  } else {
    out <+ "\{pad}  \"children\": [\n"
    let mut first = true
    for kid in kids {
      if !first {
        out <+ ",\n"
      }
      first = false
      render_lineage_json_node(kid, lineage, out, indent + 2)
    }
    out <+ "\n\{pad}  ]\n"
  }
  out <+ "\{pad}}"
}