///|
pub(all) enum CancellationCheck {
  CancelledOutcome
  CancellationEvent
  PartialRecordPersisted
  StageBoundaryPreserved
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) enum CancellationStatus {
  Pass
  NeedsWork
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct CancellationFinding {
  run_id : @domain.RunId
  check : CancellationCheck
  status : CancellationStatus
  path : String
  message : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct CancellationReport {
  run_id : @domain.RunId
  ready : Bool
  cancel_phase : @routine.RoutinePhase?
  artifact_count : Int
  findings : Array[CancellationFinding]
  summary : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub fn cancellation_finding(
  run_id : @domain.RunId,
  check : CancellationCheck,
  status : CancellationStatus,
  path : String,
  message : String,
) -> CancellationFinding {
  { run_id, check, status, path, message }
}

///|
fn cancellation_phase(
  result : @orchestration.TwoStageRoutineResult,
) -> @routine.RoutinePhase? {
  for event in result.events {
    if event.kind is RoutineCancelled {
      return Some(event.phase)
    }
  }
  None
}

///|
fn outcome_finding(
  result : @orchestration.TwoStageRoutineResult,
) -> CancellationFinding {
  if result.outcome is Cancelled &&
    result.failure_kind is Some(OperatorCancelled) {
    cancellation_finding(
      result.request.run_id,
      CancelledOutcome,
      Pass,
      "two-stage-routine-result.outcome",
      "routine terminates as an operator cancellation",
    )
  } else {
    cancellation_finding(
      result.request.run_id,
      CancelledOutcome,
      NeedsWork,
      "two-stage-routine-result.outcome",
      "routine result is not an operator cancellation",
    )
  }
}

///|
fn event_finding(
  result : @orchestration.TwoStageRoutineResult,
  phase : @routine.RoutinePhase?,
) -> CancellationFinding {
  match phase {
    Some(cancel_phase) =>
      cancellation_finding(
        result.request.run_id,
        CancellationEvent,
        Pass,
        "two-stage-routine-result.events",
        "routine emits cancellation event at \{cancel_phase.label()}",
      )
    None =>
      cancellation_finding(
        result.request.run_id,
        CancellationEvent,
        NeedsWork,
        "two-stage-routine-result.events",
        "routine did not emit a cancellation event",
      )
  }
}

///|
fn partial_record_finding(
  result : @orchestration.TwoStageRoutineResult,
) -> CancellationFinding {
  let has_analysis_record = result.artifact_paths.contains(
    "records/analyses/\{result.request.run_id}.json",
  )
  if result.partial_record_available && has_analysis_record {
    cancellation_finding(
      result.request.run_id,
      PartialRecordPersisted,
      Pass,
      "two-stage-routine-result.artifact_paths",
      "cancelled routine preserves partial analysis record evidence",
    )
  } else {
    cancellation_finding(
      result.request.run_id,
      PartialRecordPersisted,
      NeedsWork,
      "two-stage-routine-result.artifact_paths",
      "cancelled routine does not expose a partial analysis record path",
    )
  }
}

///|
fn boundary_ok_for_phase(
  result : @orchestration.TwoStageRoutineResult,
  phase : @routine.RoutinePhase,
) -> Bool {
  match phase {
    Stage1Validation =>
      result.record.stage1 is Some(_) &&
      result.record.stage2 is None &&
      result.stage2_prompt is None &&
      result.stage2_model_request is None
    Stage2Validation =>
      result.record.stage1 is Some(_) &&
      result.record.stage2 is Some(_) &&
      result.stage2_model_request is Some(_)
    _ => true
  }
}

///|
fn boundary_finding(
  result : @orchestration.TwoStageRoutineResult,
  phase : @routine.RoutinePhase?,
) -> CancellationFinding {
  match phase {
    Some(cancel_phase) =>
      if boundary_ok_for_phase(result, cancel_phase) {
        cancellation_finding(
          result.request.run_id,
          StageBoundaryPreserved,
          Pass,
          "two-stage-routine-result.record",
          "cancelled routine stops at the requested phase without fabricating later-stage evidence",
        )
      } else {
        cancellation_finding(
          result.request.run_id,
          StageBoundaryPreserved,
          NeedsWork,
          "two-stage-routine-result.record",
          "cancelled routine contains evidence from a later phase",
        )
      }
    None =>
      cancellation_finding(
        result.request.run_id,
        StageBoundaryPreserved,
        NeedsWork,
        "two-stage-routine-result.events",
        "stage boundary cannot be checked without a cancellation phase",
      )
  }
}

///|
fn report_ready(findings : Array[CancellationFinding]) -> Bool {
  findings.all(fn(finding) { finding.status is Pass })
}

///|
fn report_summary(report : CancellationReport) -> String {
  let pass_count = report.findings.fold(init=0, fn(count, finding) {
    if finding.status is Pass {
      count + 1
    } else {
      count
    }
  })
  if report.ready {
    "cancellation report passed \{pass_count}/\{report.findings.length()} checks with \{report.artifact_count} artifact(s)"
  } else {
    "cancellation report has \{report.findings.length() - pass_count} check(s) needing work"
  }
}

///|
pub fn prepare_cancellation_report(
  result : @orchestration.TwoStageRoutineResult,
) -> CancellationReport {
  let phase = cancellation_phase(result)
  let findings = [
    outcome_finding(result),
    event_finding(result, phase),
    partial_record_finding(result),
    boundary_finding(result, phase),
  ]
  let report : CancellationReport = {
    run_id: result.request.run_id,
    ready: report_ready(findings),
    cancel_phase: phase,
    artifact_count: result.artifact_paths.length(),
    findings,
    summary: "",
  }
  { ..report, summary: report_summary(report) }
}