///|
/// Descriptive statistics used by reports and scenario comparisons.
pub struct DistributionStats {
  count : Int
  minimum : Double
  maximum : Double
  mean : Double
  median : Double
  percentile_90 : Double
} derive(Eq, Debug)

///|
/// Static planned use of one exit.
pub struct ExitUsage {
  exit_id : String
  assigned_groups : Int
  assigned_people : Int
  share : Double
} derive(Eq, Debug)

///|
/// Resilience observation derived from topology and planned routes.
pub struct ResilienceFinding {
  code : String
  severity : Severity
  subject : String
  value : Double
  message : String
} derive(Eq, Debug)

///|
/// Pairwise comparison of two simulation results.
pub struct ScenarioDelta {
  baseline_name : String
  candidate_name : String
  evacuation_step_delta : Int
  remaining_delta : Int
  peak_stalled_delta : Int
  peak_risk_delta : Double
  score_delta : Double
  improved : Bool
} derive(Eq, Debug)

///|
/// Ranked scenario entry.
pub struct ScenarioRanking {
  name : String
  score : Double
  completed : Bool
  rank : Int
} derive(Eq, Debug)

///|
fn copy_doubles(values : Array[Double]) -> Array[Double] {
  let result : Array[Double] = []
  for value in values {
    result.push(value)
  }
  result
}

///|
fn sort_doubles(values : Array[Double]) -> Array[Double] {
  let result = copy_doubles(values)
  for i = 1; i < result.length(); i = i + 1 {
    let value = result[i]
    let mut j = i - 1
    while j >= 0 && result[j] > value {
      result[j + 1] = result[j]
      j = j - 1
    }
    result[j + 1] = value
  }
  result
}

///|
/// Calculate a linearly interpolated percentile from numeric values.
pub fn percentile(values : Array[Double], probability : Double) -> Double {
  if values.length() == 0 {
    return 0.0
  }
  let sorted = sort_doubles(values)
  let p = if probability < 0.0 {
    0.0
  } else if probability > 1.0 {
    1.0
  } else {
    probability
  }
  let position = p * (sorted.length() - 1).to_double()
  let lower = position.to_int()
  let upper = if lower + 1 < sorted.length() { lower + 1 } else { lower }
  let fraction = position - lower.to_double()
  sorted[lower] * (1.0 - fraction) + sorted[upper] * fraction
}

///|
/// Compute descriptive statistics for an array.
pub fn distribution_stats(values : Array[Double]) -> DistributionStats {
  if values.length() == 0 {
    return {
      count: 0,
      minimum: 0.0,
      maximum: 0.0,
      mean: 0.0,
      median: 0.0,
      percentile_90: 0.0,
    }
  }
  let mut minimum = values[0]
  let mut maximum = values[0]
  let mut sum = 0.0
  for value in values {
    if value < minimum {
      minimum = value
    }
    if value > maximum {
      maximum = value
    }
    sum = sum + value
  }
  {
    count: values.length(),
    minimum,
    maximum,
    mean: sum / values.length().to_double(),
    median: percentile(values, 0.5),
    percentile_90: percentile(values, 0.9),
  }
}

///|
/// Convert integer observations to descriptive statistics.
pub fn integer_distribution_stats(values : Array[Int]) -> DistributionStats {
  let converted : Array[Double] = []
  for value in values {
    converted.push(value.to_double())
  }
  distribution_stats(converted)
}

///|
/// Find the first snapshot meeting an evacuation percentage.
pub fn evacuation_threshold_step(
  result : SimulationResult,
  fraction : Double,
) -> Int {
  if result.total_people <= 0 {
    return 0
  }
  let target = result.total_people.to_double() *
    (if fraction < 0.0 { 0.0 } else if fraction > 1.0 { 1.0 } else { fraction })
  for snapshot in result.snapshots {
    if snapshot.evacuated.to_double() >= target {
      return snapshot.step
    }
  }
  -1
}

///|
/// Estimate evacuation progress at an arbitrary step using the last observation.
pub fn evacuation_progress_at(result : SimulationResult, step : Int) -> Double {
  if result.total_people <= 0 {
    return 1.0
  }
  let mut evacuated = 0
  for snapshot in result.snapshots {
    if snapshot.step <= step {
      evacuated = snapshot.evacuated
    }
  }
  evacuated.to_double() / result.total_people.to_double()
}

///|
/// Collect snapshot queue totals.
pub fn total_queue_timeline(result : SimulationResult) -> Array[Int] {
  let totals : Array[Int] = []
  for snapshot in result.snapshots {
    let mut total = 0
    for queue in snapshot.passage_queues {
      total = total + queue
    }
    totals.push(total)
  }
  totals
}

///|
/// Collect maximum passage queue at every snapshot.
pub fn worst_queue_timeline(result : SimulationResult) -> Array[Int] {
  let values : Array[Int] = []
  for snapshot in result.snapshots {
    let mut maximum = 0
    for queue in snapshot.passage_queues {
      if queue > maximum {
        maximum = queue
      }
    }
    values.push(maximum)
  }
  values
}

///|
/// Compute planned exit usage before dynamic events occur.
pub fn analyze_exit_usage(plan : EvacuationPlan) -> Array[ExitUsage] {
  let exits = plan.exit_indices()
  let people = Array::make(exits.length(), 0)
  let groups = Array::make(exits.length(), 0)
  let routes = planned_group_routes(
    plan,
    Array::make(plan.places.length(), 0.0),
    Array::make(plan.passages.length(), 0.0),
  )
  let count = if routes.length() < plan.populations.length() {
    routes.length()
  } else {
    plan.populations.length()
  }
  for i = 0; i < count; i = i + 1 {
    if routes[i].reachable && routes[i].place_indices.length() > 0 {
      let destination = routes[i].place_indices[routes[i].place_indices.length() -
        1]
      for e = 0; e < exits.length(); e = e + 1 {
        if exits[e] == destination {
          groups[e] = groups[e] + 1
          people[e] = people[e] + plan.populations[i].count
        }
      }
    }
  }
  let result : Array[ExitUsage] = []
  let total = plan.population_count()
  for i = 0; i < exits.length(); i = i + 1 {
    result.push({
      exit_id: plan.places[exits[i]].id,
      assigned_groups: groups[i],
      assigned_people: people[i],
      share: if total > 0 {
        people[i].to_double() / total.to_double()
      } else {
        0.0
      },
    })
  }
  result
}

///|
/// Shannon entropy of exit use, normalized to 0..1.
pub fn exit_use_balance(usages : Array[ExitUsage]) -> Double {
  if usages.length() <= 1 {
    return 0.0
  }
  let mut concentration = 0.0
  for usage in usages {
    concentration = concentration + usage.share * usage.share
  }
  let minimum_concentration = 1.0 / usages.length().to_double()
  let denominator = 1.0 - minimum_concentration
  if denominator <= 0.0 {
    0.0
  } else {
    (1.0 - concentration) / denominator
  }
}

///|
/// Perform static resilience checks that complement validation.
pub fn resilience_findings(plan : EvacuationPlan) -> Array[ResilienceFinding] {
  let result : Array[ResilienceFinding] = []
  let usages = analyze_exit_usage(plan)
  let balance = exit_use_balance(usages)
  if usages.length() < 2 {
    result.push({
      code: "EXIT_COUNT_LOW",
      severity: Warning,
      subject: "plan",
      value: usages.length().to_double(),
      message: "The plan has fewer than two usable exits.",
    })
  }
  if usages.length() >= 2 && balance < 0.35 {
    result.push({
      code: "EXIT_USE_IMBALANCED",
      severity: Warning,
      subject: "plan",
      value: balance,
      message: "Planned routes concentrate most occupants at one exit.",
    })
  }
  for usage in usages {
    if usage.share > 0.7 {
      result.push({
        code: "EXIT_OVERCONCENTRATION",
        severity: Warning,
        subject: usage.exit_id,
        value: usage.share,
        message: "This exit receives more than seventy percent of planned occupants.",
      })
    }
    if usage.assigned_people == 0 {
      result.push({
        code: "EXIT_UNUSED",
        severity: Info,
        subject: usage.exit_id,
        value: 0.0,
        message: "No group selects this exit under baseline costs.",
      })
    }
  }
  let critical = critical_places(plan)
  for place_id in critical {
    result.push({
      code: "CRITICAL_PLACE",
      severity: Warning,
      subject: place_id,
      value: 1.0,
      message: "Removing this place disconnects at least one occupied origin from all exits.",
    })
  }
  let routes = planned_group_routes(
    plan,
    Array::make(plan.places.length(), 0.0),
    Array::make(plan.passages.length(), 0.0),
  )
  let pressures = analyze_passage_pressure(plan, routes, 90.0)
  for pressure in pressures {
    if pressure.critical {
      result.push({
        code: "PASSAGE_PRESSURE_HIGH",
        severity: Warning,
        subject: pressure.passage_id,
        value: pressure.utilization,
        message: "Baseline assigned flow exceeds the configured ninety-second service window.",
      })
    }
  }
  result
}

///|
/// Compare a candidate result against a named baseline.
pub fn scenario_delta(
  baseline_name : String,
  baseline : SimulationResult,
  candidate_name : String,
  candidate : SimulationResult,
) -> ScenarioDelta {
  let delta = result_score(candidate) - result_score(baseline)
  {
    baseline_name,
    candidate_name,
    evacuation_step_delta: candidate.steps - baseline.steps,
    remaining_delta: candidate.remaining_people - baseline.remaining_people,
    peak_stalled_delta: candidate.peak_stalled_people -
    baseline.peak_stalled_people,
    peak_risk_delta: candidate.peak_risk - baseline.peak_risk,
    score_delta: delta,
    improved: delta < 0.0,
  }
}

///|
/// Rank named scenario results using the stable result score.
pub fn rank_scenarios(
  names : Array[String],
  results : Array[SimulationResult],
) -> Array[ScenarioRanking] {
  let count = if names.length() < results.length() {
    names.length()
  } else {
    results.length()
  }
  let ranking : Array[ScenarioRanking] = []
  for i = 0; i < count; i = i + 1 {
    ranking.push({
      name: names[i],
      score: result_score(results[i]),
      completed: results[i].completed,
      rank: 0,
    })
  }
  for i = 1; i < ranking.length(); i = i + 1 {
    let value = ranking[i]
    let mut j = i - 1
    while j >= 0 && ranking[j].score > value.score {
      ranking[j + 1] = ranking[j]
      j = j - 1
    }
    ranking[j + 1] = value
  }
  let final_ranking : Array[ScenarioRanking] = []
  for i = 0; i < ranking.length(); i = i + 1 {
    let entry = ranking[i]
    final_ranking.push({
      name: entry.name,
      score: entry.score,
      completed: entry.completed,
      rank: i + 1,
    })
  }
  final_ranking
}

///|
/// Create a concise Markdown resilience section.
pub fn resilience_markdown(plan : EvacuationPlan) -> String {
  let usages = analyze_exit_usage(plan)
  let findings = resilience_findings(plan)
  let mut text = "# MoonEvac resilience analysis\n\n"
  text = text + "- Plan: **\{plan.name}**\n"
  text = text + "- Exit balance score: \{exit_use_balance(usages)}\n"
  text = text + "- Findings: \{findings.length()}\n\n"
  text = text + "## Planned exit use\n\n"
  text = text + "| Exit | Groups | People | Share |\n|---|---:|---:|---:|\n"
  for usage in usages {
    text = text + "| \{usage.exit_id} | \{usage.assigned_groups} | "
    text = text + "\{usage.assigned_people} | \{usage.share} |\n"
  }
  text = text + "\n## Findings\n\n"
  if findings.length() == 0 {
    text = text + "No static resilience findings.\n"
  } else {
    for finding in findings {
      text = text + "- **\{finding.code}** (`\{finding.subject}`): "
      text = text + "\{finding.message} Value=\{finding.value}.\n"
    }
  }
  text
}