///|
/// Reproducibility and provenance records for application runs.
/// Audit events contain no credentials and are suitable for a local export or
/// a structured log sink supplied by the host application.
pub(all) enum AuditEventKind {
  AuditInputReceived
  AuditInputRejected
  AuditNormalization
  AuditQualityGate
  AuditAnalysis
  AuditDecision
  AuditExport
  AuditWarning
  AuditCompletion
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) enum AuditOutcome {
  AuditSuccess
  AuditAcceptedWithWarning
  AuditRejected
  AuditError
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct AuditEvent {
  ordinal : Int
  run_id : String
  timestamp : String
  kind : AuditEventKind
  outcome : AuditOutcome
  component : String
  message : String
  input_count : Int
  output_count : Int
  quality_score : Double
  checksum : String
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct AuditTrail {
  run_id : String
  started_at : String
  mut finished_at : String
  events : Array[AuditEvent]
  mut outcome : AuditOutcome
  input_checksum : String
  mut output_checksum : String
  mut feature_count : Int
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct AuditFilter {
  run_id : String?
  component : String?
  kind : AuditEventKind?
  outcome : AuditOutcome?
  minimum_quality : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
pub(all) struct AuditSummary {
  event_count : Int
  success_count : Int
  warning_count : Int
  rejection_count : Int
  error_count : Int
  component_count : Int
  mean_quality : Double
  last_outcome : AuditOutcome
} derive(FromJson, ToJson, Debug, Eq)

///|
pub fn audit_kind_name(kind : AuditEventKind) -> String {
  match kind {
    AuditInputReceived => "input_received"
    AuditInputRejected => "input_rejected"
    AuditNormalization => "normalization"
    AuditQualityGate => "quality_gate"
    AuditAnalysis => "analysis"
    AuditDecision => "decision"
    AuditExport => "export"
    AuditWarning => "warning"
    AuditCompletion => "completion"
  }
}

///|
pub fn audit_outcome_name(outcome : AuditOutcome) -> String {
  match outcome {
    AuditSuccess => "success"
    AuditAcceptedWithWarning => "accepted_with_warning"
    AuditRejected => "rejected"
    AuditError => "error"
  }
}

///|
fn audit_outcome_rank(outcome : AuditOutcome) -> Int {
  match outcome {
    AuditSuccess => 0
    AuditAcceptedWithWarning => 1
    AuditRejected => 2
    AuditError => 3
  }
}

///|
fn audit_bound(value : Double) -> Double {
  if value.is_nan() || value.is_inf() {
    0.0
  } else {
    value.clamp(min=0.0, max=1.0)
  }
}

///|
fn audit_unique(values : Array[String]) -> Array[String] {
  let result = []
  for value in values {
    if !result.any(item => item == value) {
      result.push(value)
    }
  }
  result
}

///|
pub fn make_audit_event(
  ordinal : Int,
  run_id : String,
  timestamp : String,
  kind : AuditEventKind,
  outcome : AuditOutcome,
  component : String,
  message : String,
  input_count : Int,
  output_count : Int,
  quality_score : Double,
  checksum : String,
) -> AuditEvent {
  {
    ordinal: ordinal.max(0),
    run_id,
    timestamp,
    kind,
    outcome,
    component,
    message,
    input_count: input_count.max(0),
    output_count: output_count.max(0),
    quality_score: audit_bound(quality_score),
    checksum,
  }
}

///|
pub fn AuditTrail::new(
  run_id : String,
  started_at : String,
  input_checksum : String,
) -> AuditTrail {
  {
    run_id,
    started_at,
    finished_at: "",
    events: [],
    outcome: AuditSuccess,
    input_checksum,
    output_checksum: "",
    feature_count: 0,
  }
}

///|
pub fn AuditTrail::append(
  self : AuditTrail,
  kind : AuditEventKind,
  outcome : AuditOutcome,
  component : String,
  message : String,
  input_count : Int,
  output_count : Int,
  quality_score : Double,
  checksum : String,
  timestamp : String,
) -> Unit {
  let event = make_audit_event(
    self.events.length(),
    self.run_id,
    timestamp,
    kind,
    outcome,
    component,
    message,
    input_count,
    output_count,
    quality_score,
    checksum,
  )
  self.events.push(event)
  if audit_outcome_rank(outcome) > audit_outcome_rank(self.outcome) {
    self.outcome = outcome
  }
}

///|
pub fn AuditTrail::finish(
  self : AuditTrail,
  finished_at : String,
  output_checksum : String,
  feature_count : Int,
) -> Unit {
  self.finished_at = finished_at
  self.output_checksum = output_checksum
  self.feature_count = feature_count.max(0)
  self.append(
    AuditCompletion,
    self.outcome,
    "audit",
    "run completed",
    self.events.length(),
    self.feature_count,
    mean_value(self.events.map(event => event.quality_score)),
    output_checksum,
    finished_at,
  )
}

///|
pub fn AuditTrail::event_count(self : AuditTrail) -> Int {
  self.events.length()
}

///|
pub fn AuditTrail::has_errors(self : AuditTrail) -> Bool {
  self.outcome == AuditError ||
  self.events.any(event => event.outcome == AuditError)
}

///|
pub fn AuditTrail::has_warnings(self : AuditTrail) -> Bool {
  self.outcome == AuditAcceptedWithWarning ||
  self.events.any(event => {
    event.outcome == AuditAcceptedWithWarning || event.kind == AuditWarning
  })
}

///|
pub fn AuditTrail::is_reproducible(self : AuditTrail) -> Bool {
  self.run_id.length() > 0 &&
  self.input_checksum.length() > 0 &&
  self.output_checksum.length() > 0 &&
  self.feature_count >= 0 &&
  self.events.length() > 0
}

///|
fn audit_matches(event : AuditEvent, filter : AuditFilter) -> Bool {
  let run_ok = match filter.run_id {
    Some(value) => event.run_id == value
    None => true
  }
  let component_ok = match filter.component {
    Some(value) => event.component == value
    None => true
  }
  let kind_ok = match filter.kind {
    Some(value) => event.kind == value
    None => true
  }
  let outcome_ok = match filter.outcome {
    Some(value) => event.outcome == value
    None => true
  }
  run_ok &&
  component_ok &&
  kind_ok &&
  outcome_ok &&
  event.quality_score >= filter.minimum_quality
}

///|
pub fn audit_filter_all() -> AuditFilter {
  {
    run_id: None,
    component: None,
    kind: None,
    outcome: None,
    minimum_quality: 0.0,
  }
}

///|
pub fn audit_filter_component(component : String) -> AuditFilter {
  {
    run_id: None,
    component: Some(component),
    kind: None,
    outcome: None,
    minimum_quality: 0.0,
  }
}

///|
pub fn audit_query(
  trails : Array[AuditTrail],
  filter : AuditFilter,
) -> Array[AuditEvent] {
  let result = []
  for trail in trails {
    for event in trail.events {
      if audit_matches(event, filter) {
        result.push(event)
      }
    }
  }
  result.sort_by((left, right) => left.ordinal - right.ordinal)
  result
}

///|
pub fn audit_summary(events : Array[AuditEvent]) -> AuditSummary {
  let components = audit_unique(events.map(event => event.component))
  let qualities = events.map(event => event.quality_score)
  let mut last = AuditSuccess
  for event in events {
    if audit_outcome_rank(event.outcome) > audit_outcome_rank(last) {
      last = event.outcome
    }
  }
  {
    event_count: events.length(),
    success_count: events
    .filter(event => event.outcome == AuditSuccess)
    .length(),
    warning_count: events
    .filter(event => {
      event.outcome == AuditAcceptedWithWarning || event.kind == AuditWarning
    })
    .length(),
    rejection_count: events
    .filter(event => event.outcome == AuditRejected)
    .length(),
    error_count: events.filter(event => event.outcome == AuditError).length(),
    component_count: components.length(),
    mean_quality: mean_value(qualities),
    last_outcome: last,
  }
}

///|
pub fn audit_trails_summary(trails : Array[AuditTrail]) -> AuditSummary {
  let events = []
  for trail in trails {
    for event in trail.events {
      events.push(event)
    }
  }
  audit_summary(events)
}

///|
pub fn audit_event_row(event : AuditEvent) -> Array[String] {
  [
    event.ordinal.to_string(),
    event.run_id,
    event.timestamp,
    audit_kind_name(event.kind),
    audit_outcome_name(event.outcome),
    event.component,
    event.message,
    event.input_count.to_string(),
    event.output_count.to_string(),
    event.quality_score.to_string(),
    event.checksum,
  ]
}

///|
pub fn audit_events_csv(events : Array[AuditEvent]) -> String {
  let grid = [
    [
      "ordinal", "run_id", "timestamp", "kind", "outcome", "component", "message",
      "input_count", "output_count", "quality_score", "checksum",
    ],
  ]
  for event in events {
    grid.push(audit_event_row(event))
  }
  to_csv(grid)
}

///|
pub fn audit_trail_csv(trail : AuditTrail) -> String {
  audit_events_csv(trail.events)
}

///|
pub fn audit_summary_csv(summary : AuditSummary) -> String {
  to_csv([
    [
      "event_count", "success_count", "warning_count", "rejection_count", "error_count",
      "component_count", "mean_quality", "last_outcome",
    ],
    [
      summary.event_count.to_string(),
      summary.success_count.to_string(),
      summary.warning_count.to_string(),
      summary.rejection_count.to_string(),
      summary.error_count.to_string(),
      summary.component_count.to_string(),
      summary.mean_quality.to_string(),
      audit_outcome_name(summary.last_outcome),
    ],
  ])
}

///|
pub fn audit_trail_feature_vector(trail : AuditTrail) -> Array[Double] {
  let summary = audit_summary(trail.events)
  [
    trail.events.length().to_double(),
    summary.success_count.to_double(),
    summary.warning_count.to_double(),
    summary.rejection_count.to_double(),
    summary.error_count.to_double(),
    summary.mean_quality,
    trail.feature_count.to_double(),
    if trail.is_reproducible() {
      1.0
    } else {
      0.0
    },
  ]
}

///|
pub fn audit_trail_status_message(trail : AuditTrail) -> String {
  match trail.outcome {
    AuditSuccess => "run completed successfully"
    AuditAcceptedWithWarning => "run completed with warnings"
    AuditRejected => "run was rejected by a quality or policy gate"
    AuditError => "run encountered an error"
  }
}

///|
pub fn audit_latest_event(trail : AuditTrail) -> AuditEvent? {
  if trail.events.length() == 0 {
    None
  } else {
    Some(trail.events[trail.events.length() - 1])
  }
}

///|
pub fn audit_event_for_component(
  trail : AuditTrail,
  component : String,
) -> Array[AuditEvent] {
  trail.events.filter(event => event.component == component)
}

///|
pub fn audit_quality_floor_events(
  trail : AuditTrail,
  floor : Double,
) -> Array[AuditEvent] {
  trail.events.filter(event => event.quality_score < floor)
}

///|
pub fn audit_is_complete(trail : AuditTrail) -> Bool {
  trail.events.any(event => event.kind == AuditCompletion) &&
  trail.finished_at.length() > 0
}

///|
pub fn audit_has_input_event(trail : AuditTrail) -> Bool {
  trail.events.any(event => event.kind == AuditInputReceived)
}

///|
pub fn audit_has_decision_event(trail : AuditTrail) -> Bool {
  trail.events.any(event => event.kind == AuditDecision)
}

///|
pub fn audit_merge(left : AuditTrail, right : AuditTrail) -> AuditTrail {
  let result = AuditTrail::new(
    if left.run_id.length() > 0 {
      left.run_id
    } else {
      right.run_id
    },
    if left.started_at.length() > 0 {
      left.started_at
    } else {
      right.started_at
    },
    if left.input_checksum.length() > 0 {
      left.input_checksum
    } else {
      right.input_checksum
    },
  )
  for event in left.events {
    result.events.push(event)
  }
  for event in right.events {
    result.events.push(event)
  }
  result.events.sort_by((a, b) => a.ordinal - b.ordinal)
  result.outcome = if audit_outcome_rank(left.outcome) >=
    audit_outcome_rank(right.outcome) {
    left.outcome
  } else {
    right.outcome
  }
  result.finished_at = if right.finished_at.length() > 0 {
    right.finished_at
  } else {
    left.finished_at
  }
  result.output_checksum = if right.output_checksum.length() > 0 {
    right.output_checksum
  } else {
    left.output_checksum
  }
  result.feature_count = left.feature_count.max(right.feature_count)
  result
}