///|
pub(all) struct RouteLockResult {
  is_success : Bool
  route_id : RouteId
  message : String
} derive(Debug)

///|
fn InterlockingTable::rollback_route_resources(
  _self : InterlockingTable,
  station : @topology.Station,
  rule : InterlockingRule,
  acquired_turnouts : Array[@topology.TurnoutId],
  acquired_tracks : Array[@topology.TrackId],
  signal_locked : Bool,
) -> Unit {
  let _ = station.set_signal_aspect(rule.entry_signal, Red)
  if signal_locked {
    let _ = station.unlock_signal(rule.entry_signal)
  }
  for turnout_id in acquired_turnouts {
    let _ = station.unlock_turnout(turnout_id)
  }
  for track_id in acquired_tracks {
    let _ = station.set_track_state(track_id, Clear, "")
  }
}

///|
fn InterlockingTable::reset_fast_scratch(self : InterlockingTable) -> Unit {
  self.acquired_turnout_count = 0
  self.acquired_track_count = 0
}

///|
fn InterlockingTable::rollback_route_resources_fast(
  self : InterlockingTable,
  station : @topology.Station,
  rule : InterlockingRule,
  signal_locked : Bool,
) -> Unit {
  let _ = station.set_signal_aspect(rule.entry_signal, Red)
  if signal_locked {
    let _ = station.unlock_signal(rule.entry_signal)
  }
  let mut turnout_index = 0
  while turnout_index < self.acquired_turnout_count {
    let _ = station.unlock_turnout(self.acquired_turnout_scratch[turnout_index])
    turnout_index = turnout_index + 1
  }
  let mut track_index = 0
  while track_index < self.acquired_track_count {
    let _ = station.set_track_state(
      self.acquired_track_scratch[track_index],
      Clear,
      "",
    )
    track_index = track_index + 1
  }
  self.reset_fast_scratch()
}

///|
pub fn InterlockingTable::request_and_lock_route_fast(
  self : InterlockingTable,
  station : @topology.Station,
  route_id : RouteId,
) -> Bool {
  match self.get_rule(route_id) {
    None => false
    Some(rule) => {
      if self.is_route_reserved(route_id) {
        return false
      }
      match station.graph.get_signal(rule.entry_signal) {
        None => return false
        Some(signal) => if signal.is_locked { return false }
      }
      for hostile_id in rule.hostile_route_ids {
        if self.is_route_reserved(hostile_id) {
          return false
        }
      }
      for track_id in rule.track_sections {
        match station.graph.get_track(track_id) {
          None => return false
          Some(track) =>
            if track.state == Occupied ||
              track.state == Locked ||
              track.state == Fault {
              return false
            }
        }
      }

      self.reset_fast_scratch()
      let mut signal_locked = false
      for switch_requirement in rule.switches {
        if self.acquired_turnout_count >= 256 {
          self.rollback_route_resources_fast(station, rule, signal_locked)
          return false
        }
        match
          station.set_turnout_position(
            switch_requirement.turnout_id,
            switch_requirement.required_position,
          ) {
          Err(_) => {
            self.rollback_route_resources_fast(station, rule, signal_locked)
            return false
          }
          Ok(_) => ()
        }
        match station.lock_turnout(switch_requirement.turnout_id) {
          Err(_) => {
            self.rollback_route_resources_fast(station, rule, signal_locked)
            return false
          }
          Ok(_) => {
            self.acquired_turnout_scratch[self.acquired_turnout_count] = switch_requirement.turnout_id
            self.acquired_turnout_count = self.acquired_turnout_count + 1
          }
        }
      }
      for flank in rule.flank_switches {
        if self.acquired_turnout_count >= 256 {
          self.rollback_route_resources_fast(station, rule, signal_locked)
          return false
        }
        match
          station.set_turnout_position(
            flank.turnout_id,
            flank.required_position,
          ) {
          Err(_) => {
            self.rollback_route_resources_fast(station, rule, signal_locked)
            return false
          }
          Ok(_) => ()
        }
        match station.lock_turnout(flank.turnout_id) {
          Err(_) => {
            self.rollback_route_resources_fast(station, rule, signal_locked)
            return false
          }
          Ok(_) => {
            self.acquired_turnout_scratch[self.acquired_turnout_count] = flank.turnout_id
            self.acquired_turnout_count = self.acquired_turnout_count + 1
          }
        }
      }
      for track_id in rule.track_sections {
        if self.acquired_track_count >= 256 {
          self.rollback_route_resources_fast(station, rule, signal_locked)
          return false
        }
        match station.set_track_state(track_id, Locked, "") {
          Err(_) => {
            self.rollback_route_resources_fast(station, rule, signal_locked)
            return false
          }
          Ok(_) => {
            self.acquired_track_scratch[self.acquired_track_count] = track_id
            self.acquired_track_count = self.acquired_track_count + 1
          }
        }
      }

      self.activate_route(route_id, rule)
      let safety_summary = self.scan_safety_summary(station)
      if !safety_summary.can_clear_signal {
        self.deactivate_route(route_id)
        self.rollback_route_resources_fast(station, rule, signal_locked)
        return false
      }

      match station.set_signal_aspect(rule.entry_signal, rule.target_aspect) {
        Err(_) => {
          self.deactivate_route(route_id)
          self.rollback_route_resources_fast(station, rule, signal_locked)
          return false
        }
        Ok(_) => ()
      }
      match station.lock_signal(rule.entry_signal) {
        Err(_) => {
          self.deactivate_route(route_id)
          self.rollback_route_resources_fast(station, rule, signal_locked)
          return false
        }
        Ok(_) => signal_locked = true
      }
      self.reset_fast_scratch()
      true
    }
  }
}

///|
pub fn InterlockingTable::request_and_lock_route(
  self : InterlockingTable,
  station : @topology.Station,
  route_id : RouteId,
) -> RouteLockResult {
  match self.get_rule(route_id) {
    None =>
      {
        is_success: false,
        route_id,
        message: "Route rule not found: " + route_id.id.to_string(),
      }
    Some(rule) => {
      // 1. Check if route is already active
      if self.is_route_reserved(route_id) {
        return {
          is_success: false,
          route_id,
          message: "Route " + rule.name + " is already active!",
        }
      }

      match station.graph.get_signal(rule.entry_signal) {
        None =>
          return {
            is_success: false,
            route_id,
            message: "Entry signal is missing!",
          }
        Some(signal) =>
          if signal.is_locked {
            return {
              is_success: false,
              route_id,
              message: "Entry signal is already locked!",
            }
          }
      }

      let acquired_turnouts : Array[@topology.TurnoutId] = []
      let acquired_tracks : Array[@topology.TrackId] = []
      let mut signal_locked = false

      // 2. Check hostile route conflicts
      for h_id in rule.hostile_route_ids {
        if self.is_route_reserved(h_id) {
          return {
            is_success: false,
            route_id,
            message: "Hostile route " +
            h_id.id.to_string() +
            " is active! Route " +
            rule.name +
            " rejected.",
          }
        }
      }

      // 3. Verify all track sections are clear
      for tr_id in rule.track_sections {
        match station.graph.get_track(tr_id) {
          None =>
            return {
              is_success: false,
              route_id,
              message: "Track segment " + tr_id.id.to_string() + " missing!",
            }
          Some(tr) => {
            if tr.state == Occupied {
              return {
                is_success: false,
                route_id,
                message: "Track " + tr.name + " is OCCUPIED!",
              }
            }
            if tr.state == Fault {
              return {
                is_success: false,
                route_id,
                message: "Track " + tr.name + " is FAULTED!",
              }
            }
            if tr.state == Locked {
              return {
                is_success: false,
                route_id,
                message: "Track " + tr.name + " is LOCKED by another route!",
              }
            }
          }
        }
      }

      // 4. Throw switches to target positions and lock them
      for sw in rule.switches {
        let throw_res = station.set_turnout_position(
          sw.turnout_id,
          sw.required_position,
        )
        match throw_res {
          Err(err) => {
            self.rollback_route_resources(
              station, rule, acquired_turnouts, acquired_tracks, signal_locked,
            )
            return {
              is_success: false,
              route_id,
              message: "Failed throwing switch: " + err,
            }
          }
          Ok(_) => ()
        }
        let lock_res = station.lock_turnout(sw.turnout_id)
        match lock_res {
          Err(err) => {
            self.rollback_route_resources(
              station, rule, acquired_turnouts, acquired_tracks, signal_locked,
            )
            return {
              is_success: false,
              route_id,
              message: "Failed locking switch: " + err,
            }
          }
          Ok(_) => acquired_turnouts.push(sw.turnout_id)
        }
      }

      // 5. Throw and lock flank protection switches
      for flank in rule.flank_switches {
        let flank_throw = station.set_turnout_position(
          flank.turnout_id,
          flank.required_position,
        )
        match flank_throw {
          Err(err) => {
            self.rollback_route_resources(
              station, rule, acquired_turnouts, acquired_tracks, signal_locked,
            )
            return {
              is_success: false,
              route_id,
              message: "Failed throwing flank protection: " + err,
            }
          }
          Ok(_) => ()
        }
        let flank_lock = station.lock_turnout(flank.turnout_id)
        match flank_lock {
          Err(err) => {
            self.rollback_route_resources(
              station, rule, acquired_turnouts, acquired_tracks, signal_locked,
            )
            return {
              is_success: false,
              route_id,
              message: "Failed locking flank protection: " + err,
            }
          }
          Ok(_) => acquired_turnouts.push(flank.turnout_id)
        }
      }

      // 6. Lock track sections
      for tr_id in rule.track_sections {
        match station.set_track_state(tr_id, Locked, "") {
          Err(err) => {
            self.rollback_route_resources(
              station, rule, acquired_turnouts, acquired_tracks, signal_locked,
            )
            return {
              is_success: false,
              route_id,
              message: "Failed locking track: " + err,
            }
          }
          Ok(_) => acquired_tracks.push(tr_id)
        }
      }

      // 7. Register the tentative active route and run the fixed-field safety
      // scan before any proceed aspect is allowed to be displayed.
      self.activate_route(route_id, rule)

      let safety_summary = self.scan_safety_summary(station)
      if !safety_summary.can_clear_signal {
        self.deactivate_route(route_id)
        self.rollback_route_resources(
          station, rule, acquired_turnouts, acquired_tracks, signal_locked,
        )
        return {
          is_success: false,
          route_id,
          message: "Safety audit rejected route before signal clearing",
        }
      }

      // 8. Open entry signal
      let sig_res = station.set_signal_aspect(
        rule.entry_signal,
        rule.target_aspect,
      )
      match sig_res {
        Err(err) => {
          self.deactivate_route(route_id)
          self.rollback_route_resources(
            station, rule, acquired_turnouts, acquired_tracks, signal_locked,
          )
          return {
            is_success: false,
            route_id,
            message: "Failed setting entry signal: " + err,
          }
        }
        Ok(_) => ()
      }

      match station.lock_signal(rule.entry_signal) {
        Err(err) => {
          self.deactivate_route(route_id)
          self.rollback_route_resources(
            station, rule, acquired_turnouts, acquired_tracks, signal_locked,
          )
          return {
            is_success: false,
            route_id,
            message: "Failed locking entry signal: " + err,
          }
        }
        Ok(_) => signal_locked = true
      }

      {
        is_success: true,
        route_id,
        message: "Route " + rule.name + " successfully locked and signal open!",
      }
    }
  }
}

///|
pub fn InterlockingTable::cancel_route(
  self : InterlockingTable,
  station : @topology.Station,
  route_id : RouteId,
) -> Result[Unit, String] {
  match self.get_active_route(route_id) {
    None => Err("Route is not active!")
    Some(active_route) => {
      let rule = active_route.rule

      // Set signal to red
      let _ = station.set_signal_aspect(rule.entry_signal, Red)
      let _ = station.unlock_signal(rule.entry_signal)

      // Unlock switches
      for sw in rule.switches {
        let _ = station.unlock_turnout(sw.turnout_id)
      }
      for flank in rule.flank_switches {
        let _ = station.unlock_turnout(flank.turnout_id)
      }

      // Unlock tracks
      for tr_id in rule.track_sections {
        let _ = station.set_track_state(tr_id, Clear, "")
      }

      active_route.state = RouteInactive
      self.deactivate_route(route_id)
      Ok(())
    }
  }
}