///|
/// The direction of a distributional change.
pub(all) enum ChangeDirection {
  Increase
  Decrease
  VarianceIncrease
  VarianceDecrease
  DistributionShift
  Unknown
}

///|
/// The severity used by alerting integrations.
pub(all) enum AlertSeverity {
  Informational
  Warning
  Critical
}

///|
/// A normalized result returned by online and offline detectors.
pub struct DetectionResult {
  changed : Bool
  score : Double
  confidence : Double
  direction : ChangeDirection
  index : Int
  evidence : Double
}

///|
/// Builds a result while keeping confidence and evidence in a safe range.
pub fn DetectionResult::new(
  changed : Bool,
  score : Double,
  confidence : Double,
  direction : ChangeDirection,
  index : Int,
  evidence? : Double = 0.0,
) -> DetectionResult {
  let safe_confidence = clamp_probability(confidence)
  let safe_score = if !is_finite(score) || score < 0.0 { 0.0 } else { score }
  {
    changed,
    score: safe_score,
    confidence: safe_confidence,
    direction,
    index,
    evidence,
  }
}

///|
/// Returns a result that represents an ordinary observation.
pub fn DetectionResult::quiet(index? : Int = 0) -> DetectionResult {
  DetectionResult::new(false, 0.0, 0.0, Unknown, index)
}

///|
/// Converts a score into a stable three-level severity.
pub fn severity_from_score(score : Double) -> AlertSeverity {
  if score >= 0.9 {
    Critical
  } else if score >= 0.6 {
    Warning
  } else {
    Informational
  }
}

///|
/// Returns a human-readable direction label.
pub fn direction_name(direction : ChangeDirection) -> String {
  match direction {
    Increase => "increase"
    Decrease => "decrease"
    VarianceIncrease => "variance-increase"
    VarianceDecrease => "variance-decrease"
    DistributionShift => "distribution-shift"
    Unknown => "unknown"
  }
}

///|
/// Returns a human-readable severity label.
pub fn severity_name(severity : AlertSeverity) -> String {
  match severity {
    Informational => "informational"
    Warning => "warning"
    Critical => "critical"
  }
}

///|
/// Produces a compact representation suitable for logs.
pub fn DetectionResult::summary(self : DetectionResult) -> String {
  "changed=\{self.changed},score=\{self.score},confidence=\{self.confidence},direction=\{direction_name(self.direction)},index=\{self.index}"
}

///|
/// A point in a time series. Timestamps are integer ticks supplied by the caller.
pub struct SignalPoint {
  timestamp : Int64
  value : Double
  sequence : Int
}

///|
/// Creates a signal point.
pub fn SignalPoint::new(
  timestamp : Int64,
  value : Double,
  sequence? : Int = 0,
) -> SignalPoint {
  { timestamp, value, sequence }
}

///|
/// A detected change with enough context for a downstream alert or dashboard.
pub struct ChangePoint {
  timestamp : Int64
  index : Int
  score : Double
  confidence : Double
  severity : AlertSeverity
  direction : ChangeDirection
  detector : String
  baseline : Double
  observed : Double
}

///|
/// Creates a change point from an online result.
pub fn ChangePoint::from_result(
  point : SignalPoint,
  result : DetectionResult,
  detector : String,
  baseline : Double,
) -> ChangePoint {
  {
    timestamp: point.timestamp,
    index: result.index,
    score: result.score,
    confidence: result.confidence,
    severity: severity_from_score(result.score),
    direction: result.direction,
    detector,
    baseline,
    observed: point.value,
  }
}

///|
/// Returns a stable log line for a change point.
pub fn ChangePoint::summary(self : ChangePoint) -> String {
  "\{self.detector}@\{self.timestamp}: \{direction_name(self.direction)} score=\{self.score} confidence=\{self.confidence} severity=\{severity_name(self.severity)}"
}

///|
/// A compact summary of a numeric sample.
pub struct StatsSummary {
  count : Int
  mean : Double
  variance : Double
  standard_deviation : Double
  minimum : Double
  maximum : Double
  median : Double
  first : Double
  last : Double
}

///|
/// Creates an empty summary.
pub fn StatsSummary::empty() -> StatsSummary {
  {
    count: 0,
    mean: 0.0,
    variance: 0.0,
    standard_deviation: 0.0,
    minimum: 0.0,
    maximum: 0.0,
    median: 0.0,
    first: 0.0,
    last: 0.0,
  }
}

///|
/// A named alert emitted by a multi-series pipeline.
pub struct AlertEvent {
  metric : String
  point : ChangePoint
  suppressed : Bool
  ordinal : Int
}

///|
/// Constructs an alert event.
pub fn AlertEvent::new(
  metric : String,
  point : ChangePoint,
  suppressed? : Bool = false,
  ordinal? : Int = 0,
) -> AlertEvent {
  { metric, point, suppressed, ordinal }
}

///|
/// Clamps a probability-like number to the closed unit interval.
pub fn clamp_probability(value : Double) -> Double {
  if value != value {
    0.0
  } else if value < 0.0 {
    0.0
  } else if value > 1.0 {
    1.0
  } else {
    value
  }
}

///|
/// Returns the absolute value without exposing math implementation details.
pub fn absolute(value : Double) -> Double {
  if value < 0.0 {
    -value
  } else {
    value
  }
}

///|
/// Returns the sign of a number.
pub fn sign(value : Double) -> Int {
  if value > 0.0 {
    1
  } else if value < 0.0 {
    -1
  } else {
    0
  }
}

///|
/// Returns whether a value is finite enough for detector state.
pub fn is_finite(value : Double) -> Bool {
  value == value &&
  value < 1.7976931348623157e308 &&
  value > -1.7976931348623157e308
}