///|
/// Lifecycle state tracked independently from telemetry threshold evaluation.
pub enum AlarmLifecycle {
  AlarmActive
  AlarmAcknowledged
  AlarmShelved
  AlarmCleared
  AlarmEscalated
} derive(Eq, Debug)

///|
pub struct AlarmRecord {
  key : String
  station : CommonAddress
  address : InformationAddress
  name : String
  severity : AlarmSeverity
  mut lifecycle : AlarmLifecycle
  first_seen : Int
  mut last_seen : Int
  mut occurrences : Int
  mut acknowledged_by : String?
  mut acknowledged_at : Int?
  mut shelf_until : Int?
  mut last_value : Float
  message : String
} derive(Debug)

///|
pub fn AlarmRecord::key(self : AlarmRecord) -> String {
  self.key
}

///|
pub fn AlarmRecord::station(self : AlarmRecord) -> CommonAddress {
  self.station
}

///|
pub fn AlarmRecord::address(self : AlarmRecord) -> InformationAddress {
  self.address
}

///|
pub fn AlarmRecord::name(self : AlarmRecord) -> String {
  self.name
}

///|
pub fn AlarmRecord::severity(self : AlarmRecord) -> AlarmSeverity {
  self.severity
}

///|
pub fn AlarmRecord::lifecycle(self : AlarmRecord) -> AlarmLifecycle {
  self.lifecycle
}

///|
pub fn AlarmRecord::first_seen(self : AlarmRecord) -> Int {
  self.first_seen
}

///|
pub fn AlarmRecord::last_seen(self : AlarmRecord) -> Int {
  self.last_seen
}

///|
pub fn AlarmRecord::occurrences(self : AlarmRecord) -> Int {
  self.occurrences
}

///|
pub fn AlarmRecord::acknowledged_by(self : AlarmRecord) -> String? {
  self.acknowledged_by
}

///|
pub fn AlarmRecord::acknowledged_at(self : AlarmRecord) -> Int? {
  self.acknowledged_at
}

///|
pub fn AlarmRecord::shelf_until(self : AlarmRecord) -> Int? {
  self.shelf_until
}

///|
pub fn AlarmRecord::last_value(self : AlarmRecord) -> Float {
  self.last_value
}

///|
pub fn AlarmRecord::message(self : AlarmRecord) -> String {
  self.message
}

///|
pub enum AlarmJournalAction {
  AlarmOpened
  AlarmUpdated
  AlarmAcknowledgedAction
  AlarmShelvedAction
  AlarmUnshelvedAction
  AlarmEscalatedAction
  AlarmClearedAction
} derive(Eq, Debug)

///|
pub struct AlarmJournalEvent {
  sequence : Int
  timestamp : Int
  key : String
  action : AlarmJournalAction
  operator : String
  detail : String
} derive(Eq, Debug)

///|
pub fn AlarmJournalEvent::sequence(self : AlarmJournalEvent) -> Int {
  self.sequence
}

///|
pub fn AlarmJournalEvent::timestamp(self : AlarmJournalEvent) -> Int {
  self.timestamp
}

///|
pub fn AlarmJournalEvent::key(self : AlarmJournalEvent) -> String {
  self.key
}

///|
pub fn AlarmJournalEvent::action(
  self : AlarmJournalEvent,
) -> AlarmJournalAction {
  self.action
}

///|
pub fn AlarmJournalEvent::operator(self : AlarmJournalEvent) -> String {
  self.operator
}

///|
pub fn AlarmJournalEvent::detail(self : AlarmJournalEvent) -> String {
  self.detail
}

///|
pub struct AlarmJournalMetrics {
  mut opened : Int
  mut acknowledged : Int
  mut shelved : Int
  mut escalated : Int
  mut cleared : Int
} derive(Eq, Debug)

///|
pub fn AlarmJournalMetrics::new() -> AlarmJournalMetrics {
  { opened: 0, acknowledged: 0, shelved: 0, escalated: 0, cleared: 0 }
}

///|
pub fn AlarmJournalMetrics::opened(self : AlarmJournalMetrics) -> Int {
  self.opened
}

///|
pub fn AlarmJournalMetrics::acknowledged(self : AlarmJournalMetrics) -> Int {
  self.acknowledged
}

///|
pub fn AlarmJournalMetrics::shelved(self : AlarmJournalMetrics) -> Int {
  self.shelved
}

///|
pub fn AlarmJournalMetrics::escalated(self : AlarmJournalMetrics) -> Int {
  self.escalated
}

///|
pub fn AlarmJournalMetrics::cleared(self : AlarmJournalMetrics) -> Int {
  self.cleared
}

///|
/// Bounded alarm journal with acknowledge, shelf and escalation workflows.
pub struct AlarmJournal {
  active : Map[String, AlarmRecord]
  events : Array[AlarmJournalEvent]
  event_limit : Int
  mut next_sequence : Int
  metrics : AlarmJournalMetrics
} derive(Debug)

///|
pub fn AlarmJournal::new(
  event_limit? : Int = 8192,
) -> Result[AlarmJournal, String] {
  if event_limit < 1 {
    Err("alarm journal event limit must be positive")
  } else {
    Ok({
      active: {},
      events: [],
      event_limit,
      next_sequence: 1,
      metrics: AlarmJournalMetrics::new(),
    })
  }
}

///|
pub fn AlarmJournal::metrics(self : AlarmJournal) -> AlarmJournalMetrics {
  self.metrics
}

///|
pub fn AlarmJournal::active_count(self : AlarmJournal) -> Int {
  self.active.length()
}

///|
pub fn AlarmJournal::events(self : AlarmJournal) -> Array[AlarmJournalEvent] {
  self.events.copy()
}

///|
pub fn AlarmJournal::all(self : AlarmJournal) -> Array[AlarmRecord] {
  let result : Array[AlarmRecord] = []
  for _, record in self.active {
    result.push(record)
  }
  result.sort_by((left, right) => {
    if left.severity().weight() != right.severity().weight() {
      right.severity().weight() - left.severity().weight()
    } else {
      left.key().compare(right.key())
    }
  })
  result
}

///|
fn AlarmJournal::record_event(
  self : AlarmJournal,
  timestamp : Int,
  key : String,
  action : AlarmJournalAction,
  operator : String,
  detail : String,
) -> Unit {
  self.events.push({
    sequence: self.next_sequence,
    timestamp,
    key,
    action,
    operator,
    detail,
  })
  self.next_sequence += 1
  while self.events.length() > self.event_limit {
    ignore(self.events.remove(0))
  }
}

///|
fn alarm_record_key(event : AlarmEvent) -> String {
  "\{event.station().number()}:\{event.address().number()}:\{event.name()}"
}

///|
pub fn AlarmJournal::ingest(
  self : AlarmJournal,
  event : AlarmEvent,
) -> Result[AlarmRecord, String] {
  let key = alarm_record_key(event)
  if event.active() {
    match self.active.get(key) {
      None => {
        let record = {
          key,
          station: event.station(),
          address: event.address(),
          name: event.name(),
          severity: event.severity(),
          lifecycle: AlarmActive,
          first_seen: event.timestamp(),
          last_seen: event.timestamp(),
          occurrences: 1,
          acknowledged_by: None,
          acknowledged_at: None,
          shelf_until: None,
          last_value: event.value(),
          message: event.message(),
        }
        self.active[key] = record
        self.metrics.opened += 1
        self.record_event(
          event.timestamp(),
          key,
          AlarmOpened,
          "system",
          event.message(),
        )
        Ok(record)
      }
      Some(record) => {
        record.last_seen = event.timestamp()
        record.last_value = event.value()
        record.occurrences += 1
        if record.lifecycle() == AlarmCleared {
          record.lifecycle = AlarmActive
        }
        self.active[key] = record
        self.record_event(
          event.timestamp(),
          key,
          AlarmUpdated,
          "system",
          event.message(),
        )
        Ok(record)
      }
    }
  } else {
    match self.active.get(key) {
      None => Err("cannot clear an unknown alarm")
      Some(record) => {
        record.lifecycle = AlarmCleared
        record.last_seen = event.timestamp()
        record.last_value = event.value()
        self.active.remove(key)
        self.metrics.cleared += 1
        self.record_event(
          event.timestamp(),
          key,
          AlarmClearedAction,
          "system",
          event.message(),
        )
        Ok(record)
      }
    }
  }
}

///|
pub fn AlarmJournal::get(self : AlarmJournal, key : String) -> AlarmRecord? {
  self.active.get(key)
}

///|
pub fn AlarmJournal::acknowledge(
  self : AlarmJournal,
  key : String,
  operator : String,
  timestamp : Int,
) -> Result[AlarmRecord, String] {
  match self.active.get(key) {
    None => Err("alarm does not exist")
    Some(record) =>
      if record.lifecycle() == AlarmAcknowledged {
        Ok(record)
      } else if operator.length() == 0 {
        Err("acknowledging operator is empty")
      } else {
        record.lifecycle = AlarmAcknowledged
        record.acknowledged_by = Some(operator)
        record.acknowledged_at = Some(timestamp)
        self.active[key] = record
        self.metrics.acknowledged += 1
        self.record_event(
          timestamp,
          key,
          AlarmAcknowledgedAction,
          operator,
          "alarm acknowledged",
        )
        Ok(record)
      }
  }
}

///|
pub fn AlarmJournal::shelve(
  self : AlarmJournal,
  key : String,
  operator : String,
  until : Int,
  timestamp : Int,
) -> Result[AlarmRecord, String] {
  if until <= timestamp {
    Err("alarm shelf deadline must be in the future")
  } else {
    match self.active.get(key) {
      None => Err("alarm does not exist")
      Some(record) => {
        record.lifecycle = AlarmShelved
        record.shelf_until = Some(until)
        self.active[key] = record
        self.metrics.shelved += 1
        self.record_event(
          timestamp,
          key,
          AlarmShelvedAction,
          operator,
          "alarm shelved",
        )
        Ok(record)
      }
    }
  }
}

///|
pub fn AlarmJournal::escalate(
  self : AlarmJournal,
  key : String,
  operator : String,
  timestamp : Int,
) -> Result[AlarmRecord, String] {
  match self.active.get(key) {
    None => Err("alarm does not exist")
    Some(record) => {
      record.lifecycle = AlarmEscalated
      self.active[key] = record
      self.metrics.escalated += 1
      self.record_event(
        timestamp,
        key,
        AlarmEscalatedAction,
        operator,
        "alarm escalated",
      )
      Ok(record)
    }
  }
}

///|
pub fn AlarmJournal::unshelve_expired(
  self : AlarmJournal,
  timestamp : Int,
) -> Int {
  let mut count = 0
  for key, record in self.active {
    match record.shelf_until() {
      Some(until) =>
        if timestamp >= until {
          record.shelf_until = None
          record.lifecycle = AlarmActive
          self.active[key] = record
          self.record_event(
            timestamp,
            key,
            AlarmUnshelvedAction,
            "system",
            "alarm shelf expired",
          )
          count += 1
        }
      None => ()
    }
  }
  count
}

///|
pub fn alarm_lifecycle_examples() -> Array[AlarmLifecycle] {
  [AlarmActive, AlarmAcknowledged, AlarmShelved, AlarmCleared, AlarmEscalated]
}

///|
pub fn alarm_journal_action_examples() -> Array[AlarmJournalAction] {
  [
    AlarmOpened,
    AlarmUpdated,
    AlarmAcknowledgedAction,
    AlarmShelvedAction,
    AlarmUnshelvedAction,
    AlarmEscalatedAction,
    AlarmClearedAction,
  ]
}