///|
pub fn format_money_micro(value : Int) -> String {
  let sign = if value < 0 { "-" } else { "" }
  let magnitude = absolute(value)
  let whole = magnitude / 1000000
  let cents = magnitude % 1000000 / 10000
  let cents_text = if cents < 10 { "0\{cents}" } else { cents.to_string() }
  "\{sign}\{whole}.\{cents_text}"
}

///|
pub fn format_energy_wh(value : Int) -> String {
  if absolute(value) >= 1000 {
    let whole = value / 1000
    let decimal = absolute(value % 1000) / 100
    "\{whole}.\{decimal} kWh"
  } else {
    "\{value} Wh"
  }
}

///|
pub fn format_slot(slot : Int, slot_minutes : Int) -> String {
  let total_minutes = slot * slot_minutes
  let day = total_minutes / 1440
  let in_day = total_minutes % 1440
  let hour = in_day / 60
  let minute = in_day % 60
  let hour_text = if hour < 10 { "0\{hour}" } else { hour.to_string() }
  let minute_text = if minute < 10 { "0\{minute}" } else { minute.to_string() }
  let day_text = if day > 0 { "D\{day} " } else { "" }
  "\{day_text}\{hour_text}:\{minute_text}"
}

///|
fn repeat_character(character : String, count : Int) -> String {
  let buffer = StringBuilder()
  for index = 0; index < count; index = index + 1 {
    buffer <+ "\{character}"
  }
  buffer.to_string()
}

///|
fn bar(value : Int, maximum_value : Int, width : Int) -> String {
  if maximum_value <= 0 || value <= 0 {
    repeat_character(".", width)
  } else {
    let filled = clamp(value * width / maximum_value, 0, width)
    repeat_character("#", filled) + repeat_character(".", width - filled)
  }
}

///|
pub fn plan_timeline(result : PlanResult) -> String {
  let buffer = StringBuilder()
  buffer <+ "slot  time   load     grid     solar    missing  battery\n"
  buffer <+ "----  -----  -------  -------  -------  -------  -------\n"
  for slot = 0; slot < result.horizon_slots; slot = slot + 1 {
    let battery = if slot + 1 < result.battery_state_wh.length() {
      result.battery_state_wh[slot + 1]
    } else {
      0
    }
    let time = format_slot(slot, result.slot_minutes)
    let load = result.load_w[slot]
    let grid = result.grid_w[slot]
    let solar = result.solar_used_w[slot]
    let missing = result.unserved_w[slot]
    buffer <+
      "\{slot}\t\{time}\t\{load}\t\{grid}\t\{solar}\t\{missing}\t\{battery}\n"
  }
  buffer.to_string()
}

///|
pub fn plan_ascii_chart(result : PlanResult, width? : Int = 28) -> String {
  let buffer = StringBuilder()
  let maximum_load = result.load_w.fold(init=1, fn(current, value) {
    maximum(current, value)
  })
  buffer <+ "Energy profile (each bar scaled to peak load)\n"
  for slot = 0; slot < result.horizon_slots; slot = slot + 1 {
    let time = format_slot(slot, result.slot_minutes)
    let chart = bar(result.load_w[slot], maximum_load, width)
    let load = result.load_w[slot]
    buffer <+ "\{time} \{chart} \{load} W"
    if result.unserved_w[slot] > 0 {
      let missing = result.unserved_w[slot]
      buffer <+ "  ! missing \{missing} W"
    }
    buffer <+ "\n"
  }
  buffer.to_string()
}

///|
fn schedule_markdown(
  schedule : Array[ScheduleEntry],
  slot_minutes : Int,
) -> String {
  let buffer = StringBuilder()
  buffer <+ "| Task | Priority | Start | End | Power | Energy | Reason |\n"
  buffer <+ "|---|---:|---:|---:|---:|---:|---|\n"
  for entry in schedule {
    let priority = entry.priority.label()
    let start = format_slot(entry.start_slot, slot_minutes)
    let end = format_slot(entry.end_slot, slot_minutes)
    let energy = format_energy_wh(entry.energy_wh)
    buffer <+
      "| \{entry.task_name} | \{priority} | \{start} | \{end} | \{entry.power_w} W | \{energy} | \{entry.reason} |\n"
  }
  buffer.to_string()
}

///|
fn metrics_markdown(metrics : PlanMetrics) -> String {
  let buffer = StringBuilder()
  let cost = format_money_micro(metrics.net_cost_micro())
  let imported = format_energy_wh(metrics.imported_energy_wh)
  let solar = format_energy_wh(metrics.solar_used_wh)
  let curtailed = format_energy_wh(metrics.solar_curtailed_wh)
  let charged = format_energy_wh(metrics.battery_charged_wh)
  let discharged = format_energy_wh(metrics.battery_discharged_wh)
  let unserved = format_energy_wh(metrics.unserved_energy_wh)
  let critical = format_energy_wh(metrics.critical_unserved_wh)
  buffer <+ "| Metric | Value |\n"
  buffer <+ "|---|---:|\n"
  buffer <+ "| Net energy cost | \{cost} |\n"
  buffer <+ "| Grid import | \{imported} |\n"
  buffer <+ "| Solar used | \{solar} |\n"
  buffer <+ "| Solar curtailed | \{curtailed} |\n"
  buffer <+ "| Battery charged | \{charged} |\n"
  buffer <+ "| Battery discharged | \{discharged} |\n"
  buffer <+ "| Carbon | \{metrics.carbon_g} g |\n"
  buffer <+ "| Peak grid power | \{metrics.peak_grid_w} W |\n"
  buffer <+ "| Unserved energy | \{unserved} |\n"
  buffer <+ "| Critical unserved | \{critical} |\n"
  buffer <+ "| Resilience | \{metrics.resilience_permille} permille |\n"
  buffer <+ "| Objective score | \{metrics.score} |\n"
  buffer.to_string()
}

///|
fn explanation_markdown(explanations : Array[Explanation]) -> String {
  let buffer = StringBuilder()
  for item in explanations {
    let slot_text = match item.slot {
      Some(slot) => " (slot \{slot})"
      None => ""
    }
    buffer <+
      "- **\{item.severity} / \{item.code}** - \{item.subject}\{slot_text}: \{item.message}\n"
  }
  buffer.to_string()
}

///|
/// Render a complete review-friendly Markdown plan report.
pub fn plan_markdown(result : PlanResult) -> String {
  let buffer = StringBuilder()
  let status = result.status.label()
  let metrics = metrics_markdown(result.metrics)
  let schedule = schedule_markdown(result.schedule, result.slot_minutes)
  let explanations = explanation_markdown(result.explanations)
  buffer <+ "# \{result.title}\n\n"
  buffer <+ "- Status: **\{status}**\n"
  buffer <+
    "- Horizon: \{result.horizon_slots} slots x \{result.slot_minutes} minutes\n"
  buffer <+ "- Scheduled entries: \{result.schedule.length()}\n"
  buffer <+ "- Skipped tasks: \{result.skipped_task_ids.length()}\n\n"
  buffer <+ "## Metrics\n\n\{metrics}"
  buffer <+ "\n## Schedule\n\n\{schedule}"
  buffer <+ "\n## Decision explanations\n\n\{explanations}"
  buffer.to_string()
}

///|
pub fn plan_csv(result : PlanResult) -> String {
  let buffer = StringBuilder()
  buffer <+ "slot,time,load_w,grid_w,solar_used_w,unserved_w,battery_state_wh\n"
  for slot = 0; slot < result.horizon_slots; slot = slot + 1 {
    let state = if slot + 1 < result.battery_state_wh.length() {
      result.battery_state_wh[slot + 1]
    } else {
      0
    }
    let time = format_slot(slot, result.slot_minutes)
    let load = result.load_w[slot]
    let grid = result.grid_w[slot]
    let solar = result.solar_used_w[slot]
    let missing = result.unserved_w[slot]
    buffer <+ "\{slot},\{time},\{load},\{grid},\{solar},\{missing},\{state}\n"
  }
  buffer.to_string()
}

///|
fn distribution_row(
  name : String,
  summary : DistributionSummary,
  unit : String,
) -> String {
  "| \{name} | \{summary.minimum} | \{summary.median} | \{summary.p90} | \{summary.p95} | \{summary.maximum} | \{summary.mean} | \{unit} |\n"
}

///|
pub fn simulation_markdown(summary : SimulationSummary) -> String {
  let buffer = StringBuilder()
  let risk = summary.risk_band.label()
  let cost = distribution_row("Cost", summary.cost_micro, "micro")
  let carbon = distribution_row("Carbon", summary.carbon_g, "g")
  let unserved = distribution_row(
    "Unserved energy",
    summary.unserved_energy_wh,
    "Wh",
  )
  let critical = distribution_row(
    "Critical unserved",
    summary.critical_unserved_wh,
    "Wh",
  )
  let peak = distribution_row("Peak grid", summary.peak_grid_w, "W")
  let resilience = distribution_row(
    "Resilience",
    summary.resilience_permille,
    "permille",
  )
  buffer <+ "# \{summary.title}\n\n"
  buffer <+ "- Risk band: **\{risk}**\n"
  buffer <+ "- Completed runs: \{summary.completed_runs}\n"
  buffer <+ "- Feasibility: \{summary.feasibility_permille()} permille\n"
  buffer <+ "- Worst sample: \{summary.worst_run_index}\n\n"
  buffer <+ "## Distributions\n\n"
  buffer <+ "| Metric | Min | Median | P90 | P95 | Max | Mean | Unit |\n"
  buffer <+ "|---|---:|---:|---:|---:|---:|---:|---|\n"
  buffer <+ "\{cost}\{carbon}\{unserved}\{critical}\{peak}\{resilience}"
  buffer <+ "\n## Recommendations\n\n"
  for recommendation in summary.recommendations {
    buffer <+ "- \{recommendation}\n"
  }
  buffer.to_string()
}

///|
pub fn comparison_markdown(comparison : PlanComparison) -> String {
  let buffer = StringBuilder()
  let baseline_cost = format_money_micro(
    comparison.baseline.metrics.net_cost_micro(),
  )
  let optimized_cost = format_money_micro(
    comparison.optimized.metrics.net_cost_micro(),
  )
  let saving = format_money_micro(comparison.cost_saving_micro)
  buffer <+ "# Baseline comparison\n\n"
  buffer <+ "| Metric | Preferred baseline | Optimized | Difference |\n"
  buffer <+ "|---|---:|---:|---:|\n"
  buffer <+
    "| Net cost | \{baseline_cost} | \{optimized_cost} | \{saving} saved |\n"
  buffer <+
    "| Carbon | \{comparison.baseline.metrics.carbon_g} g | \{comparison.optimized.metrics.carbon_g} g | \{comparison.carbon_saving_g} g saved |\n"
  buffer <+
    "| Peak grid | \{comparison.baseline.metrics.peak_grid_w} W | \{comparison.optimized.metrics.peak_grid_w} W | \{comparison.peak_reduction_w} W reduced |\n"
  buffer <+
    "| Resilience | \{comparison.baseline.metrics.resilience_permille} | \{comparison.optimized.metrics.resilience_permille} | \{comparison.resilience_gain_permille} gain |\n\n"
  buffer <+ "**Recommendation:** \{comparison.recommendation}\n"
  buffer.to_string()
}

///|
pub fn validation_markdown(report : ValidationReport) -> String {
  let buffer = StringBuilder()
  buffer <+ "# Input validation\n\n"
  buffer <+ "- Valid: **\{report.is_valid()}**\n"
  buffer <+ "- Errors: \{report.error_count()}\n"
  buffer <+ "- Warnings: \{report.warning_count()}\n"
  buffer <+ "- Advice: \{report.advice_count()}\n\n"
  for issue in report.issues {
    let code = issue.code.label()
    let level = issue.level.label()
    buffer <+
      "- `\{code}` **\{level}** at `\{issue.path}`: \{issue.message} _\{issue.hint}_\n"
  }
  buffer.to_string()
}