///|
pub(all) struct ScoreSnapshot {
  instrument : String
  score : Int
  severity : Severity
  timestamp_minutes : Int
  source : String
} derive(Debug, Eq)

///|
pub fn snapshot(
  instrument : String,
  score : Int,
  severity : Severity,
  timestamp_minutes : Int,
  source : String,
) -> ScoreSnapshot {
  { instrument, score, severity, timestamp_minutes, source }
}

///|
pub fn snapshot_from_report(
  report : ScoreReport,
  timestamp_minutes : Int,
  source : String,
) -> ScoreSnapshot {
  snapshot(
    report.instrument,
    report.score,
    report.severity,
    timestamp_minutes,
    source,
  )
}

///|
pub fn snapshot_age_minutes(now : Int, item : ScoreSnapshot) -> Int {
  if now <= item.timestamp_minutes {
    0
  } else {
    now - item.timestamp_minutes
  }
}

///|
pub fn snapshot_is_fresh(
  now : Int,
  item : ScoreSnapshot,
  window_minutes : Int,
) -> Bool {
  snapshot_age_minutes(now, item) <= window_minutes
}

///|
pub(all) enum SnapshotChange {
  Increased(Int)
  Decreased(Int)
  Unchanged
  InstrumentChanged
} derive(Debug, Eq)

///|
pub fn compare_snapshots(
  left : ScoreSnapshot,
  right : ScoreSnapshot,
) -> SnapshotChange {
  if left.instrument != right.instrument {
    InstrumentChanged
  } else if right.score > left.score {
    Increased(right.score - left.score)
  } else if right.score < left.score {
    Decreased(left.score - right.score)
  } else {
    Unchanged
  }
}

///|
pub fn snapshot_change_label(change : SnapshotChange) -> String {
  match change {
    Increased(_) => "increased"
    Decreased(_) => "decreased"
    Unchanged => "unchanged"
    InstrumentChanged => "instrument-changed"
  }
}

///|
pub fn snapshot_change_amount(change : SnapshotChange) -> Int {
  match change {
    Increased(amount) | Decreased(amount) => amount
    Unchanged | InstrumentChanged => 0
  }
}

///|
pub(all) struct AssessmentLedger {
  snapshots : Array[ScoreSnapshot]
  last_timestamp_minutes : Int
} derive(Debug, Eq)

///|
pub fn empty_ledger() -> AssessmentLedger {
  { snapshots: [], last_timestamp_minutes: 0 }
}

///|
pub fn ledger_size(ledger : AssessmentLedger) -> Int {
  ledger.snapshots.length()
}

///|
pub fn ledger_add(
  ledger : AssessmentLedger,
  item : ScoreSnapshot,
) -> AssessmentLedger {
  let snapshots = ledger.snapshots.copy()
  snapshots.push(item)
  let timestamp = if item.timestamp_minutes > ledger.last_timestamp_minutes {
    item.timestamp_minutes
  } else {
    ledger.last_timestamp_minutes
  }
  { snapshots, last_timestamp_minutes: timestamp }
}

///|
pub fn ledger_latest(ledger : AssessmentLedger) -> ScoreSnapshot? {
  if ledger.snapshots.length() == 0 {
    None
  } else {
    Some(ledger.snapshots[ledger.snapshots.length() - 1])
  }
}

///|
pub fn ledger_latest_instrument(
  ledger : AssessmentLedger,
  instrument : String,
) -> ScoreSnapshot? {
  let mut latest : ScoreSnapshot? = None
  for item in ledger.snapshots {
    if item.instrument == instrument {
      latest = Some(item)
    }
  }
  latest
}

///|
pub fn ledger_count_instrument(
  ledger : AssessmentLedger,
  instrument : String,
) -> Int {
  let mut count = 0
  for item in ledger.snapshots {
    if item.instrument == instrument {
      count += 1
    }
  }
  count
}

///|
pub fn ledger_total_score(ledger : AssessmentLedger) -> Int {
  ledger.snapshots.fold(init=0, (total, item) => total + item.score)
}

///|
pub fn ledger_highest_severity(ledger : AssessmentLedger) -> Severity {
  let mut result = Low
  for item in ledger.snapshots {
    match (result, item.severity) {
      (Critical, _) | (_, Critical) => result = Critical
      (High, _) | (_, High) => result = High
      (Medium, _) | (_, Medium) => result = Medium
      _ => result = Low
    }
  }
  result
}

///|
pub fn ledger_scores(ledger : AssessmentLedger) -> Array[Int] {
  ledger.snapshots.map(item => item.score)
}

///|
pub fn ledger_instruments(ledger : AssessmentLedger) -> Array[String] {
  ledger.snapshots.map(item => item.instrument)
}

///|
pub fn render_snapshot(item : ScoreSnapshot) -> String {
  "\{item.instrument}: score=\{item.score}, severity=\{severity_label(item.severity)}, time=\{item.timestamp_minutes}, source=\{item.source}"
}

///|
pub fn render_ledger(ledger : AssessmentLedger) -> String {
  ledger.snapshots.map(render_snapshot).join("; ")
}

///|
pub fn render_change(left : ScoreSnapshot, right : ScoreSnapshot) -> String {
  let change = compare_snapshots(left, right)
  "\{left.instrument} \{snapshot_change_label(change)}"
}

///|
pub(all) struct AssessmentSummary {
  snapshots : Int
  instruments : Int
  total_score : Int
  highest : Severity
  latest_timestamp_minutes : Int
} derive(Debug, Eq)

///|
pub fn assessment_summary(ledger : AssessmentLedger) -> AssessmentSummary {
  let names : Array[String] = []
  for item in ledger.snapshots {
    if !names.contains(item.instrument) {
      names.push(item.instrument)
    }
  }
  {
    snapshots: ledger_size(ledger),
    instruments: names.length(),
    total_score: ledger_total_score(ledger),
    highest: ledger_highest_severity(ledger),
    latest_timestamp_minutes: match ledger_latest(ledger) {
      Some(item) => item.timestamp_minutes
      None => 0
    },
  }
}