///|
pub(all) struct Simulation {
  clock : @clock.Clock
  event_manager : @event.EventManager
  priv resources : Map[String, @resource.Resource]
  priv summaries : Map[String, @stats.TimeWeightedSummary]
  priv sla_monitors : Map[String, @stats.SlaMonitor]
  priv percentile_trackers : Map[String, @stats.PercentileTracker]
  priv rolling_summaries : Map[String, @stats.RollingSummary]
}

///|
pub fn Simulation::new() -> Simulation {
  let clock = @clock.Clock::new()
  let event_manager = @event.EventManager::new(clock)
  {
    clock,
    event_manager,
    resources: Map::from_array([]),
    summaries: Map::from_array([]),
    sla_monitors: Map::from_array([]),
    percentile_trackers: Map::from_array([]),
    rolling_summaries: Map::from_array([]),
  }
}

///|
pub fn Simulation::clock(self : Simulation) -> @clock.Clock {
  self.clock
}

///|
pub fn Simulation::events(self : Simulation) -> @event.EventManager {
  self.event_manager
}

///|
pub fn Simulation::now(self : Simulation) -> Double {
  self.clock.now_seconds()
}

///|
pub fn Simulation::active_event_count(self : Simulation) -> Int {
  self.clock.active_count()
}

///|
pub fn Simulation::schedule(
  self : Simulation,
  delta_seconds : Double,
  priority : Int,
  action : () -> Unit,
) -> @clock.EventId {
  self.event_manager.schedule_action(delta_seconds, priority, action)
}

///|
pub fn Simulation::schedule_at(
  self : Simulation,
  target_time : Double,
  priority : Int,
  action : () -> Unit,
) -> @clock.EventId {
  let delta = target_time - self.now()
  let safe_delta = if delta < 0.0 { 0.0 } else { delta }
  self.event_manager.schedule_action(safe_delta, priority, action)
}

///|
pub fn Simulation::register_process(
  self : Simulation,
  name : String,
  steps : Array[(@event.ProcessContext) -> Double?],
) -> Int {
  self.event_manager.register_process(name, steps)
}

///|
pub fn Simulation::start_process(
  self : Simulation,
  proc_id : Int,
  delay : Double,
  priority : Int,
) -> @clock.EventId? {
  self.event_manager.start_process(proc_id, delay, priority)
}

///|
pub fn Simulation::create_resource(
  self : Simulation,
  name : String,
  capacity : Int,
) -> @resource.Resource {
  let res = @resource.Resource::new(name, capacity)
  self.resources.set(name, res)
  res
}

///|
pub fn Simulation::get_resource(
  self : Simulation,
  name : String,
) -> @resource.Resource? {
  self.resources.get(name)
}

///|
pub fn Simulation::create_summary(
  self : Simulation,
  name : String,
  initial_value : Double,
) -> @stats.TimeWeightedSummary {
  let sum = @stats.TimeWeightedSummary::new(name, self.now(), initial_value)
  self.summaries.set(name, sum)
  sum
}

///|
pub fn Simulation::get_summary(
  self : Simulation,
  name : String,
) -> @stats.TimeWeightedSummary? {
  self.summaries.get(name)
}

///|
pub fn Simulation::create_sla_monitor(
  self : Simulation,
  name : String,
  threshold : Double,
) -> @stats.SlaMonitor {
  let mon = @stats.SlaMonitor::new(name, threshold)
  self.sla_monitors.set(name, mon)
  mon
}

///|
pub fn Simulation::create_percentile_tracker(
  self : Simulation,
  name : String,
) -> @stats.PercentileTracker {
  let tracker = @stats.PercentileTracker::new(name)
  self.percentile_trackers.set(name, tracker)
  tracker
}

///|
pub fn Simulation::create_rolling_summary(
  self : Simulation,
  name : String,
  window_size : Double,
) -> @stats.RollingSummary {
  let roll = @stats.RollingSummary::new(name, window_size)
  self.rolling_summaries.set(name, roll)
  roll
}

///|
pub fn[T] Simulation::create_network_topology(
  self : Simulation,
  seed : Int,
) -> @network.NetworkTopology[T] {
  @network.NetworkTopology::new(self.event_manager, seed)
}

///|
pub fn Simulation::step(self : Simulation) -> Bool {
  self.event_manager.step()
}

///|
pub fn Simulation::run_until(
  self : Simulation,
  target_seconds : Double,
) -> Unit {
  self.event_manager.run_until(target_seconds)
}

///|
pub fn Simulation::run(self : Simulation) -> Unit {
  self.event_manager.run()
}

///|
pub fn Simulation::create_scheduler(
  self : Simulation,
  name : String,
  executors : Int,
  discipline : @scheduler.QueueDiscipline,
  on_start : (@scheduler.Job) -> Unit,
  on_complete : (@scheduler.Job) -> Unit,
  on_cancel : (@scheduler.Job) -> Unit,
  on_deadline_miss : (@scheduler.Job) -> Unit,
) -> @scheduler.Scheduler {
  @scheduler.Scheduler::new(
    name,
    executors,
    discipline,
    self.event_manager,
    on_start,
    on_complete,
    on_cancel,
    on_deadline_miss,
  )
}

///|
pub fn Simulation::create_tracer(
  self : Simulation,
  name : String,
) -> @trace.Tracer {
  let _ = self
  @trace.Tracer::new(name)
}

///|
pub fn Simulation::reset(self : Simulation) -> Unit {
  self.clock.reset()
}