///|
pub(all) enum RollbackWindowGate {
  WindowConfigured
  LegacyReadOnly
  MoonfishPrimaryReady
  ReplayAndExportReady
  EscalationPathReady
  ArchiveEvidenceReady
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) enum RollbackWindowStatus {
  RollbackPass
  RollbackBlocked
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct RollbackWindowFinding {
  gate : RollbackWindowGate
  status : RollbackWindowStatus
  evidence_path : String
  message : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct RollbackWindowInput {
  product_status : @product.MoonfishProductStatus
  release_certificate : @release.ReleaseCertificate
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct RollbackWindowReport {
  ready : Bool
  run_id : String
  rollback_days : Int
  legacy_app_ref : String
  pass_count : Int
  blocked_count : Int
  findings : Array[RollbackWindowFinding]
  summary : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub fn rollback_window_input(
  product_status : @product.MoonfishProductStatus,
  release_certificate : @release.ReleaseCertificate,
) -> RollbackWindowInput {
  { product_status, release_certificate }
}

///|
pub fn rollback_window_finding(
  gate : RollbackWindowGate,
  status : RollbackWindowStatus,
  evidence_path : String,
  message : String,
) -> RollbackWindowFinding {
  { gate, status, evidence_path, message }
}

///|
fn release_pass(
  certificate : @release.ReleaseCertificate,
  gate : @release.ReleaseGate,
) -> Bool {
  certificate.findings.any(fn(finding) {
    finding.gate == gate && finding.status is ReleasePass
  })
}

///|
fn checklist_step(
  status : @product.MoonfishProductStatus,
  kind : @cutover.CutoverStepKind,
  path : String,
) -> Bool {
  status.cutover_plan.checklist.any(fn(step) {
    step.kind == kind && step.path == path && step.status is Planned
  })
}

///|
fn rollback_step(
  status : @product.MoonfishProductStatus,
  kind : @cutover.CutoverStepKind,
  path : String,
) -> Bool {
  status.cutover_plan.rollback_plan.any(fn(step) {
    step.kind == kind && step.path == path && step.status is Planned
  })
}

///|
fn window_configured_finding(
  status : @product.MoonfishProductStatus,
) -> RollbackWindowFinding {
  if status.cutover_plan.rollback_days >= 1 &&
    rollback_step(status, MonitorRollbackWindow, "../paa") &&
    rollback_step(
      status,
      ArchiveLegacyNotes,
      "records/evaluations/rollback-report.json",
    ) {
    rollback_window_finding(
      WindowConfigured,
      RollbackPass,
      "cutover-plan.rollback_plan",
      "rollback window has duration, legacy read-only monitor, and rollback-report evidence path",
    )
  } else {
    rollback_window_finding(
      WindowConfigured,
      RollbackBlocked,
      "cutover-plan.rollback_plan",
      "rollback window is missing duration or monitor evidence paths",
    )
  }
}

///|
fn legacy_read_only_finding(
  status : @product.MoonfishProductStatus,
  certificate : @release.ReleaseCertificate,
) -> RollbackWindowFinding {
  if status.decommission_report.legacy_app_ref == "../paa" &&
    checklist_step(status, FreezeLegacyApp, "../paa") &&
    release_pass(certificate, LegacyAppArchived) {
    rollback_window_finding(
      LegacyReadOnly,
      RollbackPass,
      "../paa",
      "PA Agent is frozen and retained only as read-only rollback evidence",
    )
  } else {
    rollback_window_finding(
      LegacyReadOnly,
      RollbackBlocked,
      "../paa",
      "PA Agent read-only rollback boundary is incomplete",
    )
  }
}

///|
fn moonfish_primary_finding(
  status : @product.MoonfishProductStatus,
  certificate : @release.ReleaseCertificate,
) -> RollbackWindowFinding {
  if certificate.ready &&
    release_pass(certificate, ProductStatusReady) &&
    release_pass(certificate, RehearsalReady) &&
    checklist_step(status, SwitchOperatorSurface, "apps/moondesk-price-action") {
    rollback_window_finding(
      MoonfishPrimaryReady,
      RollbackPass,
      certificate.release_path,
      "Moonfish release certificate and cutover checklist make the app-tool the primary workflow",
    )
  } else {
    rollback_window_finding(
      MoonfishPrimaryReady,
      RollbackBlocked,
      "release-certificate",
      "Moonfish is not yet proven as the primary rollback-window workflow",
    )
  }
}

///|
fn replay_export_finding(
  status : @product.MoonfishProductStatus,
  certificate : @release.ReleaseCertificate,
) -> RollbackWindowFinding {
  if status.workspace.replay_plan.can_replay &&
    !status.workspace.replay_plan.live_market_required &&
    status.workspace.export_manifest.item_count > 0 &&
    release_pass(certificate, ExportEvidenceReady) {
    rollback_window_finding(
      ReplayAndExportReady,
      RollbackPass,
      status.workspace.export_manifest.destination_path,
      "Moonfish evidence can be exported and replayed without live market data during rollback",
    )
  } else {
    rollback_window_finding(
      ReplayAndExportReady,
      RollbackBlocked,
      "export-manifest",
      "rollback evidence lacks replay or export readiness",
    )
  }
}

///|
fn escalation_path_finding(
  status : @product.MoonfishProductStatus,
) -> RollbackWindowFinding {
  if rollback_step(status, SwitchOperatorSurface, "apps/moondesk-price-action") &&
    rollback_step(
      status,
      ArchiveLegacyNotes,
      "records/evaluations/rollback-report.json",
    ) {
    rollback_window_finding(
      EscalationPathReady,
      RollbackPass,
      "records/evaluations/rollback-report.json",
      "rollback plan records fallback reason, affected runs, and required Moonfish fixes",
    )
  } else {
    rollback_window_finding(
      EscalationPathReady,
      RollbackBlocked,
      "cutover-plan.rollback_plan",
      "rollback escalation path is missing",
    )
  }
}

///|
fn archive_evidence_finding(
  status : @product.MoonfishProductStatus,
  certificate : @release.ReleaseCertificate,
) -> RollbackWindowFinding {
  if status.decommission_report.ready &&
    status.decommission_report.archive_items.all(fn(item) {
      item.retained && item.path != ""
    }) &&
    release_pass(certificate, HistoricalRecordsAvailable) {
    rollback_window_finding(
      ArchiveEvidenceReady,
      RollbackPass,
      status.decommission_report.archive_root,
      "decommission archive and historical record evidence are retained for rollback audit",
    )
  } else {
    rollback_window_finding(
      ArchiveEvidenceReady,
      RollbackBlocked,
      status.decommission_report.archive_root,
      "rollback archive evidence is incomplete",
    )
  }
}

///|
fn count_status(
  findings : Array[RollbackWindowFinding],
  status : RollbackWindowStatus,
) -> Int {
  findings.fold(init=0, fn(count, finding) {
    if finding.status == status {
      count + 1
    } else {
      count
    }
  })
}

///|
pub fn prepare_rollback_window_report(
  input : RollbackWindowInput,
) -> RollbackWindowReport {
  let status = input.product_status
  let certificate = input.release_certificate
  let findings = [
    window_configured_finding(status),
    legacy_read_only_finding(status, certificate),
    moonfish_primary_finding(status, certificate),
    replay_export_finding(status, certificate),
    escalation_path_finding(status),
    archive_evidence_finding(status, certificate),
  ]
  let blocked_count = count_status(findings, RollbackBlocked)
  let pass_count = count_status(findings, RollbackPass)
  let ready = blocked_count == 0
  {
    ready,
    run_id: status.workspace.bundle.request.run_id,
    rollback_days: status.cutover_plan.rollback_days,
    legacy_app_ref: status.decommission_report.legacy_app_ref,
    pass_count,
    blocked_count,
    findings,
    summary: if ready {
      "rollback window ready for \{status.cutover_plan.rollback_days} day(s) with \{pass_count}/\{findings.length()} gate(s)"
    } else {
      "rollback window blocked by \{blocked_count} gate(s)"
    },
  }
}