///|
/// A normalized transaction used by every detector in the library.
pub(all) struct Transaction {
  id : String
  customer_id : String
  account_id : String
  amount : Int
  currency : String
  region : String
  occurred_at : Int
}

///|
pub fn Transaction::new(
  id~ : String,
  customer_id~ : String,
  account_id~ : String,
  amount~ : Int,
  currency~ : String,
  region~ : String,
  occurred_at~ : Int,
) -> Transaction {
  { id, customer_id, account_id, amount, currency, region, occurred_at }
}

///|
pub fn Transaction::is_debit(self : Transaction) -> Bool {
  self.amount >= 0
}

///|
pub fn Transaction::age_at(self : Transaction, now : Int) -> Int {
  if now < self.occurred_at {
    0
  } else {
    now - self.occurred_at
  }
}

///|
pub fn Transaction::same_customer(
  self : Transaction,
  other : Transaction,
) -> Bool {
  self.customer_id == other.customer_id
}

///|
pub fn Transaction::same_account(
  self : Transaction,
  other : Transaction,
) -> Bool {
  self.account_id == other.account_id
}

///|
pub fn Transaction::within(self : Transaction, start : Int, end : Int) -> Bool {
  self.occurred_at >= start && self.occurred_at <= end
}

///|
pub enum AlertSeverity {
  Info
  Low
  Medium
  High
  Critical
} derive(Eq, Debug)

///|
pub fn AlertSeverity::rank(self : AlertSeverity) -> Int {
  match self {
    Info => 0
    Low => 1
    Medium => 2
    High => 3
    Critical => 4
  }
}

///|
pub fn AlertSeverity::from_score(score : Int) -> AlertSeverity {
  if score >= 90 {
    Critical
  } else if score >= 70 {
    High
  } else if score >= 40 {
    Medium
  } else if score > 0 {
    Low
  } else {
    Info
  }
}

///|
pub enum AlertStatus {
  Open
  Triaged
  Escalated
  Resolved
  Dismissed
} derive(Eq, Debug)

///|
pub fn AlertStatus::is_terminal(self : AlertStatus) -> Bool {
  match self {
    Open => false
    Triaged => false
    Escalated => false
    Resolved => true
    Dismissed => true
  }
}

///|
pub fn AlertStatus::from_stage(stage : Int) -> AlertStatus {
  if stage <= 0 {
    Open
  } else if stage == 1 {
    Triaged
  } else if stage == 2 {
    Escalated
  } else if stage == 3 {
    Resolved
  } else {
    Dismissed
  }
}

///|
pub(all) struct Alert {
  rule_id : String
  transaction_id : String
  evidence : Array[String]
  score : Int
  severity : AlertSeverity
  status : AlertStatus
}

///|
pub fn Alert::new(
  rule_id : String,
  transaction_id : String,
  evidence : Array[String],
) -> Alert {
  { rule_id, transaction_id, evidence, score: 0, severity: Info, status: Open }
}

///|
pub fn Alert::with_score(self : Alert, score : Int) -> Alert {
  { ..self, score, severity: AlertSeverity::from_score(score) }
}

///|
pub fn Alert::transition(self : Alert, status : AlertStatus) -> Alert {
  { ..self, status, }
}

///|
pub fn Alert::has_evidence(self : Alert) -> Bool {
  self.evidence.length() > 0
}

///|
pub fn Alert::is_actionable(self : Alert) -> Bool {
  self.status != Resolved &&
  self.status != Dismissed &&
  self.severity.rank() >= 2
}