///|
/// A semantic category for a location in an evacuation map.
pub(all) enum PlaceKind {
  Room
  Corridor
  Stair
  Assembly
  Exit
} derive(Eq, Debug)

///|
/// A directed connection category.
pub(all) enum PassageKind {
  Door
  Hallway
  Stairway
  Ramp
  Outdoor
} derive(Eq, Debug)

///|
/// Severity used by validation diagnostics.
pub(all) enum Severity {
  Info
  Warning
  Error
} derive(Eq, Debug)

///|
/// Mobility profile of an evacuee group.
pub(all) enum Mobility {
  Fast
  Standard
  Slow
  Assisted
} derive(Eq, Debug)

///|
/// A location is a graph vertex with geometry and holding capacity.
pub struct Place {
  id : String
  label : String
  kind : PlaceKind
  floor : Int
  x : Double
  y : Double
  capacity : Int
  initial_risk : Double
} derive(Eq, Debug)

///|
/// A directed graph edge used by routing and simulation.
pub struct Passage {
  id : String
  from_id : String
  to_id : String
  kind : PassageKind
  length_m : Double
  width_m : Double
  flow_per_second : Double
  bidirectional : Bool
  accessible : Bool
  enabled : Bool
} derive(Eq, Debug)

///|
/// Population initially located at one place.
pub struct PopulationGroup {
  id : String
  place_id : String
  count : Int
  mobility : Mobility
  response_delay : Int
  familiarity : Double
} derive(Eq, Debug)

///|
/// A time-based change applied to the simulation.
pub(all) enum ScenarioEvent {
  ClosePassage(String, Int)
  OpenPassage(String, Int)
  Ignite(String, Int, Double)
  ClearRisk(String, Int)
} derive(Eq, Debug)

///|
/// Complete immutable input to validation and simulation.
pub struct EvacuationPlan {
  name : String
  mut timestep_seconds : Double
  places : Array[Place]
  passages : Array[Passage]
  populations : Array[PopulationGroup]
  events : Array[ScenarioEvent]
} derive(Eq, Debug)

///|
/// A diagnostic produced while validating a plan.
pub struct Diagnostic {
  code : String
  severity : Severity
  subject : String
  message : String
  hint : String
} derive(Eq, Debug)

///|
/// A compact point used by SVG rendering.
pub struct Point {
  x : Double
  y : Double
} derive(Eq, Debug)

///|
/// Construct a place with explicit properties.
pub fn Place::new(
  id : String,
  label : String,
  kind : PlaceKind,
  floor : Int,
  x : Double,
  y : Double,
  capacity : Int,
) -> Place {
  { id, label, kind, floor, x, y, capacity, initial_risk: 0.0 }
}

///|
/// Construct a directed passage with practical defaults.
pub fn Passage::new(
  id : String,
  from_id : String,
  to_id : String,
  kind : PassageKind,
  length_m : Double,
  width_m : Double,
) -> Passage {
  {
    id,
    from_id,
    to_id,
    kind,
    length_m,
    width_m,
    flow_per_second: width_m * 1.25,
    bidirectional: true,
    accessible: kind != Stairway,
    enabled: true,
  }
}

///|
/// Construct a population group.
pub fn PopulationGroup::new(
  id : String,
  place_id : String,
  count : Int,
  mobility : Mobility,
) -> PopulationGroup {
  { id, place_id, count, mobility, response_delay: 0, familiarity: 1.0 }
}

///|
/// Construct an empty plan.
pub fn EvacuationPlan::new(name : String) -> EvacuationPlan {
  {
    name,
    timestep_seconds: 1.0,
    places: [],
    passages: [],
    populations: [],
    events: [],
  }
}

///|
/// Return the speed multiplier of a mobility profile.
pub fn Mobility::speed_factor(self : Mobility) -> Double {
  match self {
    Fast => 1.25
    Standard => 1.0
    Slow => 0.65
    Assisted => 0.45
  }
}

///|
/// Whether a mobility profile requires accessible passages.
pub fn Mobility::requires_accessible(self : Mobility) -> Bool {
  match self {
    Assisted => true
    _ => false
  }
}

///|
/// Stable textual name used in machine-readable reports.
pub fn PlaceKind::code(self : PlaceKind) -> String {
  match self {
    Room => "room"
    Corridor => "corridor"
    Stair => "stair"
    Assembly => "assembly"
    Exit => "exit"
  }
}

///|
/// Stable textual name used in machine-readable reports.
pub fn PassageKind::code(self : PassageKind) -> String {
  match self {
    Door => "door"
    Hallway => "hallway"
    Stairway => "stairway"
    Ramp => "ramp"
    Outdoor => "outdoor"
  }
}

///|
/// Stable textual name used in reports.
pub fn Severity::code(self : Severity) -> String {
  match self {
    Info => "info"
    Warning => "warning"
    Error => "error"
  }
}

///|
/// Stable textual name used in reports.
pub fn Mobility::code(self : Mobility) -> String {
  match self {
    Fast => "fast"
    Standard => "standard"
    Slow => "slow"
    Assisted => "assisted"
  }
}

///|
/// Find a place index using a deterministic linear scan.
pub fn EvacuationPlan::place_index(self : EvacuationPlan, id : String) -> Int {
  for i = 0; i < self.places.length(); i = i + 1 {
    if self.places[i].id == id {
      return i
    }
  }
  -1
}

///|
/// Find a passage index using a deterministic linear scan.
pub fn EvacuationPlan::passage_index(self : EvacuationPlan, id : String) -> Int {
  for i = 0; i < self.passages.length(); i = i + 1 {
    if self.passages[i].id == id {
      return i
    }
  }
  -1
}

///|
/// Return all exit indices.
pub fn EvacuationPlan::exit_indices(self : EvacuationPlan) -> Array[Int] {
  let result : Array[Int] = []
  for i = 0; i < self.places.length(); i = i + 1 {
    if self.places[i].kind == Exit {
      result.push(i)
    }
  }
  result
}

///|
/// Total number of people in the plan.
pub fn EvacuationPlan::population_count(self : EvacuationPlan) -> Int {
  let mut total = 0
  for group in self.populations {
    total = total + group.count
  }
  total
}

///|
/// Number of locations of a given type.
pub fn EvacuationPlan::place_kind_count(
  self : EvacuationPlan,
  kind : PlaceKind,
) -> Int {
  let mut total = 0
  for place in self.places {
    if place.kind == kind {
      total = total + 1
    }
  }
  total
}

///|
/// Create a diagnostic value.
pub fn diagnostic(
  code : String,
  severity : Severity,
  subject : String,
  message : String,
  hint : String,
) -> Diagnostic {
  { code, severity, subject, message, hint }
}