///|
/// A composable filter for operational journal queries.
pub(all) struct JournalFilter {
  ticket_id : String?
  minimum_level : JournalLevel?
  include_acknowledged : Bool
  event_prefix : String?
}

///|
/// Creates a filter that returns every unacknowledged incident.
pub fn JournalFilter::open_incidents() -> JournalFilter {
  {
    ticket_id: None,
    minimum_level: Some(Error),
    include_acknowledged: false,
    event_prefix: None,
  }
}

///|
/// Creates a filter for one ticket and one event family.
pub fn JournalFilter::ticket_events(
  ticket_id : String,
  event_prefix : String,
) -> JournalFilter {
  {
    ticket_id: Some(ticket_id),
    minimum_level: None,
    include_acknowledged: true,
    event_prefix: Some(event_prefix),
  }
}

///|
fn level_rank(level : JournalLevel) -> Int {
  match level {
    Info => 0
    Warning => 1
    Error => 2
    Critical => 3
  }
}

///|
fn starts_with(value : String, prefix : String) -> Bool {
  let value_chars = value.to_array()
  let prefix_chars = prefix.to_array()
  if prefix_chars.length() > value_chars.length() {
    return false
  }
  for index in 0.. Bool {
  let ticket_matches = match filter.ticket_id {
    None => true
    Some(ticket) => entry.ticket_id == ticket
  }
  let level_matches = match filter.minimum_level {
    None => true
    Some(level) => level_rank(entry.level) >= level_rank(level)
  }
  let acknowledgement_matches = filter.include_acknowledged ||
    !entry.acknowledged
  let event_matches = match filter.event_prefix {
    None => true
    Some(prefix) => starts_with(entry.event, prefix)
  }
  ticket_matches && level_matches && acknowledgement_matches && event_matches
}

///|
/// Executes a filter against a journal without changing the journal.
pub fn query_journal(
  journal : WorkflowJournal,
  filter : JournalFilter,
) -> Array[JournalEntry] {
  let result = []
  for entry in journal.entries {
    if matches_filter(entry, filter) {
      result.push(entry)
    }
  }
  result
}

///|
/// Returns the latest matching entry, if one exists.
pub fn latest_matching(
  journal : WorkflowJournal,
  filter : JournalFilter,
) -> JournalEntry? {
  let matches = query_journal(journal, filter)
  matches.get(matches.length() - 1)
}

///|
/// A ticket-level operational summary for dashboards.
pub(all) struct TicketJournalSummary {
  ticket_id : String
  total_entries : Int
  accepted_events : Int
  warnings : Int
  incidents : Int
  critical : Int
  unacknowledged : Int
  last_state : String?
}

///|
/// Summarizes one ticket using the journal's stable sequence order.
pub fn summarize_ticket(
  journal : WorkflowJournal,
  ticket_id : String,
) -> TicketJournalSummary {
  let entries = journal.for_ticket(ticket_id)
  let mut accepted = 0
  let mut warnings = 0
  let mut incidents = 0
  let mut critical = 0
  let mut unacknowledged = 0
  for entry in entries {
    if entry.level == Info {
      accepted += 1
    }
    if entry.level == Warning {
      warnings += 1
    }
    if entry.level == Error {
      incidents += 1
    }
    if entry.level == Critical {
      critical += 1
    }
    if !entry.acknowledged && entry.level != Info {
      unacknowledged += 1
    }
  }
  let last_state = match journal.latest_for_ticket(ticket_id) {
    Some(entry) => Some(entry.state)
    None => None
  }
  {
    ticket_id,
    total_entries: entries.length(),
    accepted_events: accepted,
    warnings,
    incidents,
    critical,
    unacknowledged,
    last_state,
  }
}

///|
/// Finds the final journal entry belonging to a ticket.
pub fn WorkflowJournal::latest_for_ticket(
  self : WorkflowJournal,
  ticket_id : String,
) -> JournalEntry? {
  let mut latest : JournalEntry? = None
  for entry in self.entries {
    if entry.ticket_id == ticket_id {
      latest = Some(entry)
    }
  }
  latest
}

///|
/// Returns whether a journal has any unacknowledged entry at or above a level.
pub fn has_open_at_least(
  journal : WorkflowJournal,
  minimum : JournalLevel,
) -> Bool {
  for entry in journal.entries {
    if !entry.acknowledged && level_rank(entry.level) >= level_rank(minimum) {
      return true
    }
  }
  false
}

///|
/// Produces a count table in Info, Warning, Error, Critical order.
pub fn level_counts(journal : WorkflowJournal) -> Array[Int] {
  [
    journal.count_level(Info),
    journal.count_level(Warning),
    journal.count_level(Error),
    journal.count_level(Critical),
  ]
}