///|
pub(all) enum ReadinessGate {
  ToolCoverage
  WorkspaceExecution
  SafetyEvidence
  ParityEvidence
  CutoverComparisonEvidence
  PromptSkillMigration
  ReplayEvidence
  ImportCoverage
  OperatorSurface
  ExportRedaction
} derive(Debug, Eq, ToJson, FromJson)

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

///|
pub(all) struct ReadinessFinding {
  gate : ReadinessGate
  status : ReadinessStatus
  path : String
  message : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct CutoverEvaluationInput {
  workspace : @workspace.AnalysisWorkspace
  import_plan : @migration.LegacyImportPlan
  parity_report : @parity.ParityReport
  comparison_report : @comparison.CutoverComparisonReport
  skill_migration_plan : @skills.SkillMigrationPlan
  manifest : @pack.PackManifest
} derive(Debug, Eq, ToJson, FromJson)

///|
pub(all) struct CutoverReadinessReport {
  ready : Bool
  pass_count : Int
  needs_work_count : Int
  findings : Array[ReadinessFinding]
  summary : String
} derive(Debug, Eq, ToJson, FromJson)

///|
pub fn cutover_evaluation_input(
  workspace : @workspace.AnalysisWorkspace,
  import_plan : @migration.LegacyImportPlan,
  parity_report : @parity.ParityReport,
  comparison_report : @comparison.CutoverComparisonReport,
  skill_migration_plan? : @skills.SkillMigrationPlan = @skills.prepare_skill_migration_plan(),
  manifest? : @pack.PackManifest = @pack.price_action_manifest(),
) -> CutoverEvaluationInput {
  {
    workspace,
    import_plan,
    parity_report,
    comparison_report,
    skill_migration_plan,
    manifest,
  }
}

///|
fn finding(
  gate : ReadinessGate,
  status : ReadinessStatus,
  path : String,
  message : String,
) -> ReadinessFinding {
  { gate, status, path, message }
}

///|
fn required_tool_names() -> Array[String] {
  [
    "prepare_analysis_workspace", "prepare_analysis_plan", "prepare_analysis_job",
    "prepare_retry_decision", "prepare_legacy_import_plan", "prepare_market_refresh_plan",
    "prepare_context_budget_plan", "fetch_kline_snapshot", "compute_indicators",
    "compute_structure_levels", "classify_market_structure", "route_strategy_files",
    "validate_stage_json", "evaluate_decision_policy", "write_analysis_record", "prepare_book_commit_report",
    "queue_analysis_review", "prepare_followup_context", "prepare_followup_session_plan",
    "prepare_followup_response", "prepare_replay_plan", "prepare_export_manifest",
    "prepare_safety_report", "prepare_redaction_audit", "prepare_cancellation_report",
    "prepare_provider_failure_report", "prepare_record_integrity_report", "prepare_moondesk_app_smoke_report",
    "render_operator_run_view", "render_run_history", "prepare_cutover_comparison_report",
    "prepare_product_launch_manifest", "prepare_product_rehearsal_report", "prepare_release_certificate",
    "prepare_rollback_window_report", "prepare_operations_readiness_report", "prepare_skill_migration_plan",
  ]
}

///|
fn tool_declared(manifest : @pack.PackManifest, name : String) -> Bool {
  manifest.tools.any(fn(tool) { tool.name == name })
}

///|
fn missing_required_tool_count(manifest : @pack.PackManifest) -> Int {
  required_tool_names().fold(init=0, fn(count, name) {
    if tool_declared(manifest, name) {
      count
    } else {
      count + 1
    }
  })
}

///|
fn tool_coverage_finding(manifest : @pack.PackManifest) -> ReadinessFinding {
  let missing = missing_required_tool_count(manifest)
  if missing == 0 {
    finding(
      ToolCoverage,
      Ready,
      "pack.tools",
      "required analysis, import, replay, export, safety, and operator tools are declared",
    )
  } else {
    finding(
      ToolCoverage,
      NeedsWork,
      "pack.tools",
      "\{missing} required tool contract(s) are missing",
    )
  }
}

///|
fn workspace_execution_finding(
  workspace : @workspace.AnalysisWorkspace,
) -> ReadinessFinding {
  if workspace.bundle.status is Completed &&
    workspace.job_state.outcome is Completed &&
    workspace.book_artifact_count > 0 &&
    workspace.chart_candle_count > 0 {
    finding(
      WorkspaceExecution,
      Ready,
      "analysis-workspace",
      "fixture workspace completes and produces chart plus MoonBook artifacts",
    )
  } else {
    finding(
      WorkspaceExecution,
      NeedsWork,
      "analysis-workspace",
      "workspace does not yet prove a complete analysis path",
    )
  }
}

///|
fn safety_finding(workspace : @workspace.AnalysisWorkspace) -> ReadinessFinding {
  if workspace.safety_report.ok {
    finding(
      SafetyEvidence,
      Ready,
      "safety-report",
      "safety report passes broker, context budget, export, replay, and ownership checks",
    )
  } else {
    finding(
      SafetyEvidence,
      NeedsWork,
      "safety-report",
      "one or more safety checks failed",
    )
  }
}

///|
fn replay_finding(workspace : @workspace.AnalysisWorkspace) -> ReadinessFinding {
  if workspace.replay_plan.can_replay &&
    !workspace.replay_plan.live_market_required {
    finding(
      ReplayEvidence,
      Ready,
      "replay-plan",
      "replay can run from persisted evidence without live market data",
    )
  } else {
    finding(
      ReplayEvidence,
      NeedsWork,
      "replay-plan",
      "replay still lacks persisted evidence or requires live market data",
    )
  }
}

///|
fn parity_finding(report : @parity.ParityReport) -> ReadinessFinding {
  if report.review_count == 0 &&
    report.untraced_case_count == 0 &&
    report.fixture_case_count == report.cases.length() &&
    report.pass_count == report.cases.length() {
    finding(
      ParityEvidence,
      Ready,
      "parity-report",
      "deterministic parity groups pass with traced PA Agent fixtures and Moonfish evidence",
    )
  } else {
    finding(
      ParityEvidence,
      NeedsWork,
      "parity-report",
      "one or more parity groups still need review or fixture traceability",
    )
  }
}

///|
fn import_finding(plan : @migration.LegacyImportPlan) -> ReadinessFinding {
  if plan.ready_count > 0 && plan.review_count == 0 && plan.skipped_count == 0 {
    finding(
      ImportCoverage,
      Ready,
      "legacy-import-plan",
      "legacy PA Agent sources have idempotent MoonBook import targets",
    )
  } else {
    finding(
      ImportCoverage,
      NeedsWork,
      "legacy-import-plan",
      "legacy import plan still has review or skipped items",
    )
  }
}

///|
fn skill_migration_finding(
  plan : @skills.SkillMigrationPlan,
) -> ReadinessFinding {
  if plan.source_count > 0 &&
    plan.mapped_count == plan.source_count &&
    plan.review_count == 0 &&
    plan.catalog.assets.length() > 0 &&
    plan.sources.all(fn(source) { source.target_book_path != "" }) {
    finding(
      PromptSkillMigration,
      Ready,
      "skill-migration-plan",
      "PA prompt files map to inspectable MoonBook skill, strategy-note, and concept targets",
    )
  } else {
    finding(
      PromptSkillMigration,
      NeedsWork,
      "skill-migration-plan",
      "PA prompt migration still has unmapped sources or missing MoonBook targets",
    )
  }
}

///|
fn comparison_finding(
  report : @comparison.CutoverComparisonReport,
) -> ReadinessFinding {
  if report.ready && report.golden_case_count > 0 && report.live_case_count > 0 {
    finding(
      CutoverComparisonEvidence,
      Ready,
      "cutover-comparison-report",
      "golden fixture and live snapshot rehearsal comparisons are ready",
    )
  } else {
    finding(
      CutoverComparisonEvidence,
      NeedsWork,
      "cutover-comparison-report",
      "golden fixture or live snapshot comparison evidence still needs review",
    )
  }
}

///|
fn operator_surface_finding(
  workspace : @workspace.AnalysisWorkspace,
) -> ReadinessFinding {
  let actions = workspace.run_view.actions
  if actions.contains(StartFollowUp) &&
    actions.contains(ReplayRun) &&
    actions.contains(ExportRun) &&
    workspace.history.total_count > 0 {
    finding(
      OperatorSurface,
      Ready,
      "operator-run-view",
      "operator view exposes follow-up, replay, export, and run history",
    )
  } else {
    finding(
      OperatorSurface,
      NeedsWork,
      "operator-run-view",
      "operator surface lacks a cutover workflow action or history row",
    )
  }
}

///|
fn public_export_finding(
  workspace : @workspace.AnalysisWorkspace,
) -> ReadinessFinding {
  let redaction_ok = workspace.safety_report.findings.any(fn(item) {
    item.kind is PublicExportRedaction && item.status is Pass
  })
  if redaction_ok {
    finding(
      ExportRedaction,
      Ready,
      "export.public-summary",
      "public exports omit prompt packets and raw model exchanges",
    )
  } else {
    finding(
      ExportRedaction,
      NeedsWork,
      "export.public-summary",
      "public export redaction is not proven",
    )
  }
}

///|
fn count_status(
  items : Array[ReadinessFinding],
  status : ReadinessStatus,
) -> Int {
  items.fold(init=0, fn(count, item) {
    if item.status == status {
      count + 1
    } else {
      count
    }
  })
}

///|
pub fn prepare_cutover_readiness_report(
  input : CutoverEvaluationInput,
) -> CutoverReadinessReport {
  let findings = [
    tool_coverage_finding(input.manifest),
    workspace_execution_finding(input.workspace),
    safety_finding(input.workspace),
    parity_finding(input.parity_report),
    comparison_finding(input.comparison_report),
    skill_migration_finding(input.skill_migration_plan),
    replay_finding(input.workspace),
    import_finding(input.import_plan),
    operator_surface_finding(input.workspace),
    public_export_finding(input.workspace),
  ]
  let needs_work = count_status(findings, NeedsWork)
  let pass_count = count_status(findings, Ready)
  let ready = needs_work == 0
  {
    ready,
    pass_count,
    needs_work_count: needs_work,
    findings,
    summary: if ready {
      "cutover readiness passed \{pass_count}/\{findings.length()} gates"
    } else {
      "cutover readiness has \{needs_work} gate(s) needing work"
    },
  }
}