///|
pub(all) enum ProductRehearsalCheck {
  LaunchSurfaceReady
  DailyWorkflowExecutable
  ExportReplayEvidence
  GoldenAndLiveComparison
  LegacyReadOnlyBoundary
  ArchiveReady
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) enum ProductRehearsalStatus {
  RehearsalPass
  RehearsalBlocked
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct ProductRehearsalFinding {
  check : ProductRehearsalCheck
  status : ProductRehearsalStatus
  evidence_path : String
  message : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct ProductRehearsalReport {
  ready : Bool
  run_id : @domain.RunId
  launch_path : String
  pass_count : Int
  blocked_count : Int
  findings : Array[ProductRehearsalFinding]
  summary : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub fn product_rehearsal_finding(
  check : ProductRehearsalCheck,
  status : ProductRehearsalStatus,
  evidence_path : String,
  message : String,
) -> ProductRehearsalFinding {
  { check, status, evidence_path, message }
}

///|
fn enabled_action(
  manifest : ProductLaunchManifest,
  action : ProductLaunchAction,
) -> Bool {
  manifest.actions.contains(action)
}

///|
fn ready_panel(manifest : ProductLaunchManifest, id : String) -> Bool {
  manifest.panels.any(fn(panel) { panel.id == id && panel.ready })
}

///|
fn app_surface_ready(
  status : MoonfishProductStatus,
  surface_id : String,
) -> Bool {
  status.workspace.app_tool_manifest.surfaces.any(fn(surface) {
    surface.id == surface_id && surface.enabled
  })
}

///|
fn comparison_pass(
  status : MoonfishProductStatus,
  kind : @comparison.CutoverComparisonKind,
) -> Bool {
  status.comparison_report.cases.any(fn(item) {
    item.kind == kind && item.status is ComparisonPass
  })
}

///|
fn decommission_pass(
  status : MoonfishProductStatus,
  gate : @decommission.DecommissionGate,
) -> Bool {
  status.decommission_report.findings.any(fn(item) {
    item.gate == gate && item.status is DecommissionPass
  })
}

///|
fn launch_surface_finding(
  status : MoonfishProductStatus,
  manifest : ProductLaunchManifest,
) -> ProductRehearsalFinding {
  if manifest.ready &&
    manifest.blocking_gate_count == 0 &&
    manifest.status_path ==
    "records/product-status/\{status.workspace.bundle.request.run_id}.json" &&
    ready_panel(manifest, "launch-overview") &&
    ready_panel(manifest, "cutover-checklist") &&
    ready_panel(manifest, "handoff-runbook") &&
    enabled_action(manifest, OpenOperatorWorkspace) &&
    enabled_action(manifest, RunCutoverChecklist) &&
    enabled_action(manifest, OpenHandoffRunbook) &&
    enabled_action(manifest, ExportLaunchEvidence) {
    product_rehearsal_finding(
      LaunchSurfaceReady,
      RehearsalPass,
      manifest.status_path,
      "product launch surface is ready and exposes workspace, checklist, handoff, and export actions",
    )
  } else {
    product_rehearsal_finding(
      LaunchSurfaceReady,
      RehearsalBlocked,
      manifest.status_path,
      "product launch surface is blocked or missing a required operator action",
    )
  }
}

///|
fn daily_workflow_finding(
  status : MoonfishProductStatus,
) -> ProductRehearsalFinding {
  if status.handoff_report.ready &&
    status.handoff_report.runbook.length() >= 5 &&
    app_surface_ready(status, "run-analysis") &&
    app_surface_ready(status, "inspect-chart") &&
    app_surface_ready(status, "inspect-events") &&
    app_surface_ready(status, "start-follow-up") &&
    app_surface_ready(status, "export-run") &&
    app_surface_ready(status, "replay-run") &&
    status.workspace.run_view.event_count > 0 &&
    status.workspace.run_view.followup_topic_count > 0 {
    product_rehearsal_finding(
      DailyWorkflowExecutable,
      RehearsalPass,
      status.handoff_report.launch_path,
      "operator handoff runbook can drive run, inspect, follow-up, export, and replay surfaces",
    )
  } else {
    product_rehearsal_finding(
      DailyWorkflowExecutable,
      RehearsalBlocked,
      status.handoff_report.launch_path,
      "operator daily workflow is missing launch runbook or app-tool surfaces",
    )
  }
}

///|
fn export_replay_finding(
  status : MoonfishProductStatus,
) -> ProductRehearsalFinding {
  if status.workspace.replay_plan.can_replay &&
    !status.workspace.replay_plan.live_market_required &&
    status.workspace.export_manifest.item_count > 0 &&
    status.workspace.export_manifest.items.any(fn(item) {
      item.kind is ReplayPlanArtifact && item.required
    }) &&
    status.workspace.export_manifest.items.any(fn(item) {
      item.kind is ExportManifestArtifact && item.required
    }) {
    product_rehearsal_finding(
      ExportReplayEvidence,
      RehearsalPass,
      status.workspace.export_manifest.destination_path,
      "export manifest includes replay evidence and can replay without live market data",
    )
  } else {
    product_rehearsal_finding(
      ExportReplayEvidence,
      RehearsalBlocked,
      status.workspace.export_manifest.destination_path,
      "export or replay evidence is incomplete for launch rehearsal",
    )
  }
}

///|
fn comparison_rehearsal_finding(
  status : MoonfishProductStatus,
) -> ProductRehearsalFinding {
  if status.comparison_report.ready &&
    comparison_pass(status, GoldenFixtureRun) &&
    comparison_pass(status, LiveSnapshotRehearsal) {
    product_rehearsal_finding(
      GoldenAndLiveComparison,
      RehearsalPass,
      "records/comparisons/live-snapshot-rehearsal.json",
      "golden fixture and live snapshot rehearsal evidence both pass",
    )
  } else {
    product_rehearsal_finding(
      GoldenAndLiveComparison,
      RehearsalBlocked,
      "cutover-comparison-report",
      "golden fixture or live snapshot comparison is not ready",
    )
  }
}

///|
fn legacy_boundary_finding(
  status : MoonfishProductStatus,
) -> ProductRehearsalFinding {
  if status.cutover_plan.checklist.any(fn(step) {
      step.kind is FreezeLegacyApp &&
      step.path == "../paa" &&
      step.status is Planned
    }) &&
    status.cutover_plan.rollback_plan.any(fn(step) {
      step.kind is MonitorRollbackWindow &&
      step.path == "../paa" &&
      step.status is Planned
    }) &&
    comparison_pass(status, LegacyReadOnlyRollback) {
    product_rehearsal_finding(
      LegacyReadOnlyBoundary,
      RehearsalPass,
      "../paa",
      "legacy PA Agent is retained only as a read-only rollback and audit reference",
    )
  } else {
    product_rehearsal_finding(
      LegacyReadOnlyBoundary,
      RehearsalBlocked,
      "../paa",
      "legacy PA Agent is not proven read-only during launch rehearsal",
    )
  }
}

///|
fn archive_ready_finding(
  status : MoonfishProductStatus,
) -> ProductRehearsalFinding {
  if status.decommission_report.ready &&
    decommission_pass(status, ArchiveManifestReady) &&
    decommission_pass(status, ImportedRecordsPreserved) &&
    status.decommission_report.archive_items.all(fn(item) {
      item.retained && item.path != ""
    }) {
    product_rehearsal_finding(
      ArchiveReady,
      RehearsalPass,
      status.decommission_report.archive_root,
      "decommission archive retains migration, parity, imported data, handoff, rollback, and legacy references",
    )
  } else {
    product_rehearsal_finding(
      ArchiveReady,
      RehearsalBlocked,
      status.decommission_report.archive_root,
      "decommission archive is incomplete for launch rehearsal",
    )
  }
}

///|
fn count_rehearsal_status(
  findings : Array[ProductRehearsalFinding],
  status : ProductRehearsalStatus,
) -> Int {
  findings.fold(init=0, fn(count, item) {
    if item.status == status {
      count + 1
    } else {
      count
    }
  })
}

///|
pub fn prepare_product_rehearsal_report(
  status : MoonfishProductStatus,
) -> ProductRehearsalReport {
  let manifest = prepare_product_launch_manifest(status)
  let findings = [
    launch_surface_finding(status, manifest),
    daily_workflow_finding(status),
    export_replay_finding(status),
    comparison_rehearsal_finding(status),
    legacy_boundary_finding(status),
    archive_ready_finding(status),
  ]
  let blocked_count = count_rehearsal_status(findings, RehearsalBlocked)
  let pass_count = count_rehearsal_status(findings, RehearsalPass)
  let ready = blocked_count == 0
  {
    ready,
    run_id: status.workspace.bundle.request.run_id,
    launch_path: manifest.mount_path,
    pass_count,
    blocked_count,
    findings,
    summary: if ready {
      "product rehearsal ready with \{pass_count}/\{findings.length()} check(s)"
    } else {
      "product rehearsal blocked by \{blocked_count} check(s)"
    },
  }
}