use serde_json::{Map, Value, json};
use std::collections::BTreeSet;

const SCHEMA_VERSION: &str = "0.1";
const REPORT_KIND: &str = "policy_operations";
const LIMITS_NOTE: &str = "Read-only advisory policy operations report over explicit existing artifacts. Promotion requires separate manual review and configuration changes; this report never mutates config, baselines, suppressions, workflows, CI defaults, or preview-language eligibility.";

pub(crate) const DEFAULT_POLICY_OPERATIONS_OUT: &str = "target/ripr/reports/policy-operations.json";
pub(crate) const DEFAULT_POLICY_OPERATIONS_MD_OUT: &str =
    "target/ripr/reports/policy-operations.md";

const TARGET_MODES: [&str; 4] = [
    "visible-only",
    "acknowledgeable",
    "baseline-check",
    "calibrated-gate",
];

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct PolicyOperationsInput {
    pub(crate) root: String,
    pub(crate) generated_at: String,
    pub(crate) policy_readiness_path: Option<String>,
    pub(crate) waiver_aging_path: Option<String>,
    pub(crate) suppression_health_path: Option<String>,
    pub(crate) baseline_delta_path: Option<String>,
    pub(crate) gate_decision_path: Option<String>,
    pub(crate) recommendation_calibration_path: Option<String>,
    pub(crate) mutation_calibration_path: Option<String>,
    pub(crate) preview_boundary_path: Option<String>,
    pub(crate) policy_readiness_json: Option<Result<String, String>>,
    pub(crate) waiver_aging_json: Option<Result<String, String>>,
    pub(crate) suppression_health_json: Option<Result<String, String>>,
    pub(crate) baseline_delta_json: Option<Result<String, String>>,
    pub(crate) gate_decision_json: Option<Result<String, String>>,
    pub(crate) recommendation_calibration_json: Option<Result<String, String>>,
    pub(crate) mutation_calibration_json: Option<Result<String, String>>,
    pub(crate) preview_boundary_json: Option<Result<String, String>>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct PolicyOperationsReport {
    root: String,
    generated_at: String,
    current_policy_ceiling: String,
    recommended_next_action: String,
    safe_to_promote_to: Vec<PromotionAssessment>,
    not_safe_to_promote_to: Vec<PromotionAssessment>,
    promotion_blockers: Vec<PromotionBlocker>,
    baseline_actions: Vec<String>,
    waiver_actions: Vec<String>,
    suppression_actions: Vec<String>,
    calibration_actions: Vec<String>,
    preview_boundary_actions: Vec<String>,
    warnings: Vec<Notice>,
    unknowns: Vec<Notice>,
    input_artifacts: Vec<InputArtifact>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
struct PromotionAssessment {
    mode: String,
    allowed_now: bool,
    reason: String,
    blockers: Vec<String>,
    source_artifacts: Vec<String>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
struct PromotionBlocker {
    kind: String,
    severity: String,
    message: String,
    target_modes: Vec<String>,
    source_artifact: Option<String>,
    repair_action: String,
}

#[derive(Clone, Debug, Eq, PartialEq)]
struct Notice {
    kind: String,
    message: String,
    source_artifact: Option<String>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
struct InputArtifact {
    kind: String,
    path: Option<String>,
    status: String,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ArtifactStatus {
    Omitted,
    Read,
    Missing,
    Malformed,
}

#[derive(Clone, Debug)]
struct ParsedArtifact {
    kind: &'static str,
    path: Option<String>,
    status: ArtifactStatus,
    value: Option<Value>,
    notice: Option<Notice>,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct BaselineFacts {
    still_present: usize,
    resolved: usize,
    new_policy_eligible: usize,
    acknowledged: usize,
    suppressed: usize,
    stale: usize,
    invalid: usize,
    missing_input: usize,
    auto_adopt_new: bool,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
struct PreviewFacts {
    languages: Vec<String>,
    visible: usize,
    gate_eligible: usize,
    ripr_zero_blocking: usize,
    calibrated_confidence: usize,
    missing_language_status: usize,
    static_limits_seen: usize,
}

pub(crate) fn build_policy_operations_report(
    input: PolicyOperationsInput,
) -> PolicyOperationsReport {
    let policy_readiness = parse_artifact(
        "policy_readiness",
        input.policy_readiness_path.clone(),
        input.policy_readiness_json,
    );
    let waiver_aging = parse_artifact(
        "waiver_aging",
        input.waiver_aging_path.clone(),
        input.waiver_aging_json,
    );
    let suppression_health = parse_artifact(
        "suppression_health",
        input.suppression_health_path.clone(),
        input.suppression_health_json,
    );
    let baseline_delta = parse_artifact(
        "baseline_delta",
        input.baseline_delta_path.clone(),
        input.baseline_delta_json,
    );
    let gate_decision = parse_artifact(
        "gate_decision",
        input.gate_decision_path.clone(),
        input.gate_decision_json,
    );
    let recommendation_calibration = parse_artifact(
        "recommendation_calibration",
        input.recommendation_calibration_path.clone(),
        input.recommendation_calibration_json,
    );
    let mutation_calibration = parse_artifact(
        "mutation_calibration",
        input.mutation_calibration_path.clone(),
        input.mutation_calibration_json,
    );
    let preview_boundary = parse_artifact(
        "preview_boundary",
        input.preview_boundary_path.clone(),
        input.preview_boundary_json,
    );

    let artifacts = [
        &policy_readiness,
        &waiver_aging,
        &suppression_health,
        &baseline_delta,
        &gate_decision,
        &recommendation_calibration,
        &mutation_calibration,
        &preview_boundary,
    ];

    let mut warnings = artifacts
        .iter()
        .filter_map(|artifact| artifact.notice.clone())
        .filter(|notice| {
            !matches!(
                notice.kind.as_str(),
                "omitted_policy_readiness" | "omitted_preview_boundary"
            )
        })
        .collect::<Vec<_>>();
    let mut unknowns = Vec::new();

    let current_policy_ceiling = policy_readiness
        .value
        .as_ref()
        .and_then(|value| string_path(value, &["status"]))
        .or_else(|| {
            policy_readiness
                .value
                .as_ref()
                .and_then(|value| string_path(value, &["current_policy_ceiling"]))
        })
        .unwrap_or_else(|| "config_error".to_string());
    let readiness_next_action = policy_readiness
        .value
        .as_ref()
        .and_then(|value| string_path(value, &["next_policy_action"]))
        .or_else(|| {
            policy_readiness
                .value
                .as_ref()
                .and_then(|value| string_path(value, &["recommended_next_action"]))
        });

    add_input_unknowns(
        &mut unknowns,
        &policy_readiness,
        "policy_readiness",
        "Policy readiness input is required before policy operations can recommend promotion.",
    );
    add_recommended_unknown(
        &mut unknowns,
        &waiver_aging,
        "Waiver-aging input not supplied; acknowledgement confidence is limited.",
    );
    add_recommended_unknown(
        &mut unknowns,
        &suppression_health,
        "Suppression-health input not supplied; durable exception health is unknown.",
    );
    add_recommended_unknown(
        &mut unknowns,
        &baseline_delta,
        "Baseline-delta input not supplied; baseline-check is blocked.",
    );
    add_recommended_unknown(
        &mut unknowns,
        &gate_decision,
        "Gate-decision input not supplied; visible policy state is limited to readiness.",
    );
    add_recommended_unknown(
        &mut unknowns,
        &recommendation_calibration,
        "Recommendation calibration input not supplied; calibrated-gate is blocked.",
    );
    if mutation_calibration.status == ArtifactStatus::Omitted {
        warnings.push(Notice {
            kind: "missing_optional_input".to_string(),
            message: "No mutation calibration input was supplied.".to_string(),
            source_artifact: None,
        });
    }
    if preview_boundary.status == ArtifactStatus::Omitted {
        unknowns.push(Notice {
            kind: "preview_boundary_not_supplied".to_string(),
            message: "Preview boundary details came only from policy readiness when available."
                .to_string(),
            source_artifact: policy_readiness.path.clone(),
        });
    }

    let baseline_facts = baseline_delta
        .value
        .as_ref()
        .map(baseline_facts)
        .unwrap_or_default();
    let preview_facts = preview_facts(
        preview_boundary
            .value
            .as_ref()
            .or(policy_readiness.value.as_ref()),
    );

    let mut promotion_blockers = Vec::new();
    collect_input_blockers(
        &mut promotion_blockers,
        &policy_readiness,
        &[
            "visible-only",
            "acknowledgeable",
            "baseline-check",
            "calibrated-gate",
        ],
        "policy_readiness_unavailable",
        "Policy readiness must be readable before promotion review.",
        "Run `ripr policy readiness` and supply the generated JSON.",
    );
    collect_input_blockers(
        &mut promotion_blockers,
        &waiver_aging,
        &["acknowledgeable", "baseline-check", "calibrated-gate"],
        "waiver_aging_unavailable",
        "Waiver aging must be readable before acknowledgement policy.",
        "Run `ripr policy waiver-aging` and review repeated PR-time acknowledgements.",
    );
    collect_input_blockers(
        &mut promotion_blockers,
        &suppression_health,
        &["acknowledgeable", "baseline-check", "calibrated-gate"],
        "suppression_health_unavailable",
        "Suppression health must be readable before stricter policy.",
        "Run `ripr policy suppression-health` and repair metadata gaps.",
    );
    collect_input_blockers(
        &mut promotion_blockers,
        &baseline_delta,
        &["baseline-check", "calibrated-gate"],
        "baseline_delta_unavailable",
        "Baseline debt delta must be readable before baseline-check.",
        "Run `ripr baseline diff` against the reviewed baseline.",
    );
    collect_input_blockers(
        &mut promotion_blockers,
        &recommendation_calibration,
        &["calibrated-gate"],
        "recommendation_calibration_unavailable",
        "Same-class recommendation calibration is required before calibrated-gate.",
        "Collect same-class recommendation calibration receipts for the target class.",
    );
    collect_malformed_optional_blocker(
        &mut promotion_blockers,
        &gate_decision,
        &[
            "visible-only",
            "acknowledgeable",
            "baseline-check",
            "calibrated-gate",
        ],
        "gate_decision_malformed",
        "Supplied gate-decision input is malformed.",
        "Repair or omit the gate-decision input before promotion review.",
    );
    collect_malformed_optional_blocker(
        &mut promotion_blockers,
        &mutation_calibration,
        &["calibrated-gate"],
        "mutation_calibration_malformed",
        "Supplied mutation calibration input is malformed.",
        "Repair or omit mutation calibration before calibrated-gate review.",
    );
    collect_malformed_optional_blocker(
        &mut promotion_blockers,
        &preview_boundary,
        &["baseline-check", "calibrated-gate"],
        "preview_boundary_malformed",
        "Supplied preview-boundary input is malformed.",
        "Repair preview-boundary input or use policy-readiness preview metadata.",
    );

    collect_ceiling_blockers(&mut promotion_blockers, &current_policy_ceiling);
    collect_status_blockers(
        &mut promotion_blockers,
        &waiver_aging,
        "waiver_aging_warning",
        "Waiver-aging status requires review before acknowledgement policy.",
        &["acknowledgeable", "baseline-check", "calibrated-gate"],
        "Review repeated PR-time acknowledgements before tightening policy.",
    );
    collect_status_blockers(
        &mut promotion_blockers,
        &suppression_health,
        "suppression_health_warnings",
        "Suppression health has warnings or config errors.",
        &["acknowledgeable", "baseline-check", "calibrated-gate"],
        "Repair durable suppression metadata before tightening policy.",
    );
    collect_baseline_blockers(&mut promotion_blockers, &baseline_delta, &baseline_facts);
    collect_preview_blockers(&mut promotion_blockers, &policy_readiness, &preview_facts);

    let baseline_actions = baseline_actions(&baseline_delta, &baseline_facts);
    let waiver_actions = waiver_actions(&waiver_aging);
    let suppression_actions = suppression_actions(&suppression_health);
    let calibration_actions =
        calibration_actions(&recommendation_calibration, &mutation_calibration);
    let preview_boundary_actions = preview_boundary_actions(&preview_facts);

    let input_artifacts = artifacts
        .iter()
        .map(|artifact| InputArtifact {
            kind: artifact.kind.to_string(),
            path: artifact.path.clone(),
            status: artifact_status_label(artifact.status).to_string(),
        })
        .collect::<Vec<_>>();

    let mut safe_to_promote_to = Vec::new();
    let mut not_safe_to_promote_to = Vec::new();
    for mode in TARGET_MODES {
        let mode_blockers = promotion_blockers
            .iter()
            .filter(|blocker| blocker.target_modes.iter().any(|target| target == mode))
            .collect::<Vec<_>>();
        let allowed_now = mode_blockers.is_empty();
        let assessment = if allowed_now {
            PromotionAssessment {
                mode: mode.to_string(),
                allowed_now,
                reason: safe_reason(mode).to_string(),
                blockers: Vec::new(),
                source_artifacts: safe_sources(mode, &artifacts),
            }
        } else {
            PromotionAssessment {
                mode: mode.to_string(),
                allowed_now,
                reason: not_safe_reason(&mode_blockers),
                blockers: mode_blockers
                    .iter()
                    .map(|blocker| blocker.kind.clone())
                    .collect(),
                source_artifacts: blocker_sources(&mode_blockers),
            }
        };
        if assessment.allowed_now {
            safe_to_promote_to.push(assessment);
        } else {
            not_safe_to_promote_to.push(assessment);
        }
    }

    let recommended_next_action = promotion_blockers
        .first()
        .map(|blocker| blocker.repair_action.clone())
        .or(readiness_next_action)
        .unwrap_or_else(|| {
            "Keep policy advisory until a promotion packet is reviewed.".to_string()
        });

    PolicyOperationsReport {
        root: input.root,
        generated_at: input.generated_at,
        current_policy_ceiling,
        recommended_next_action,
        safe_to_promote_to,
        not_safe_to_promote_to,
        promotion_blockers,
        baseline_actions,
        waiver_actions,
        suppression_actions,
        calibration_actions,
        preview_boundary_actions,
        warnings,
        unknowns,
        input_artifacts,
    }
}

pub(crate) fn render_policy_operations_json(
    report: &PolicyOperationsReport,
) -> Result<String, String> {
    serde_json::to_string_pretty(&json!({
        "schema_version": SCHEMA_VERSION,
        "tool": "ripr",
        "kind": REPORT_KIND,
        "root": report.root,
        "generated_at": report.generated_at,
        "current_policy_ceiling": report.current_policy_ceiling,
        "recommended_next_action": report.recommended_next_action,
        "safe_to_promote_to": report.safe_to_promote_to.iter().map(assessment_json).collect::<Vec<_>>(),
        "not_safe_to_promote_to": report.not_safe_to_promote_to.iter().map(assessment_json).collect::<Vec<_>>(),
        "promotion_blockers": report.promotion_blockers.iter().map(blocker_json).collect::<Vec<_>>(),
        "baseline_actions": report.baseline_actions,
        "waiver_actions": report.waiver_actions,
        "suppression_actions": report.suppression_actions,
        "calibration_actions": report.calibration_actions,
        "preview_boundary_actions": report.preview_boundary_actions,
        "warnings": report.warnings.iter().map(notice_json).collect::<Vec<_>>(),
        "unknowns": report.unknowns.iter().map(notice_json).collect::<Vec<_>>(),
        "input_artifacts": report.input_artifacts.iter().map(input_artifact_json).collect::<Vec<_>>(),
        "limits_note": LIMITS_NOTE,
    }))
    .map_err(|err| format!("failed to render policy operations JSON: {err}"))
}

pub(crate) fn render_policy_operations_markdown(report: &PolicyOperationsReport) -> String {
    let mut out = String::new();
    out.push_str("# RIPR Policy Operations\n\n");
    out.push_str(&format!(
        "Current ceiling: {}\n",
        report.current_policy_ceiling
    ));
    out.push_str(&format!(
        "Next safe action: {}\n",
        report.recommended_next_action
    ));

    out.push_str("\n## Can Promote To\n\n");
    if report.safe_to_promote_to.is_empty() {
        out.push_str("- none: no\n");
    } else {
        for assessment in &report.safe_to_promote_to {
            out.push_str(&format!("- {}: yes\n", assessment.mode));
        }
    }

    out.push_str("\n## Cannot Promote To\n\n");
    if report.not_safe_to_promote_to.is_empty() {
        out.push_str("- none\n");
    } else {
        for assessment in &report.not_safe_to_promote_to {
            out.push_str(&format!(
                "- {}: no, {}\n",
                assessment.mode, assessment.reason
            ));
        }
    }

    out.push_str("\n## Top Blockers\n\n");
    if report.promotion_blockers.is_empty() {
        out.push_str("- none\n");
    } else {
        for blocker in report.promotion_blockers.iter().take(5) {
            out.push_str(&format!("- {}: {}\n", blocker.kind, blocker.repair_action));
        }
    }

    out.push_str("\n## Actions\n\n");
    render_action_group(&mut out, "Baseline actions", &report.baseline_actions);
    render_action_group(&mut out, "Waiver actions", &report.waiver_actions);
    render_action_group(&mut out, "Suppression actions", &report.suppression_actions);
    render_action_group(&mut out, "Calibration actions", &report.calibration_actions);
    render_action_group(
        &mut out,
        "Preview evidence boundary",
        &report.preview_boundary_actions,
    );

    if !report.warnings.is_empty() {
        out.push_str("\n## Warnings\n\n");
        for warning in &report.warnings {
            out.push_str(&format!("- {}: {}\n", warning.kind, warning.message));
        }
    }
    if !report.unknowns.is_empty() {
        out.push_str("\n## Unknowns\n\n");
        for unknown in &report.unknowns {
            out.push_str(&format!("- {}: {}\n", unknown.kind, unknown.message));
        }
    }

    out.push_str("\n## Input Artifacts\n\n");
    for artifact in &report.input_artifacts {
        out.push_str(&format!(
            "- {}: {}",
            artifact.kind.replace('_', "-"),
            artifact.status
        ));
        if let Some(path) = artifact.path.as_deref() {
            out.push_str(&format!(" ({})", markdown_text(path)));
        }
        out.push('\n');
    }

    out.push_str("\nLimits:\n");
    out.push_str(LIMITS_NOTE);
    out.push('\n');
    out
}

pub(crate) fn policy_operations_current_ceiling(report: &PolicyOperationsReport) -> &str {
    &report.current_policy_ceiling
}

pub(crate) fn policy_operations_next_action(report: &PolicyOperationsReport) -> &str {
    &report.recommended_next_action
}

pub(crate) use crate::output::path::display_path;

fn parse_artifact(
    kind: &'static str,
    path: Option<String>,
    text: Option<Result<String, String>>,
) -> ParsedArtifact {
    let Some(path_value) = path.clone() else {
        return ParsedArtifact {
            kind,
            path,
            status: ArtifactStatus::Omitted,
            value: None,
            notice: None,
        };
    };
    let Some(text) = text else {
        return ParsedArtifact {
            kind,
            path,
            status: ArtifactStatus::Missing,
            value: None,
            notice: Some(Notice {
                kind: format!("{kind}_missing"),
                message: format!("{kind} path {path_value} was supplied but no text was loaded."),
                source_artifact: Some(path_value),
            }),
        };
    };
    let text = match text {
        Ok(text) => text,
        Err(error) => {
            let status = if looks_like_missing_file(&error) {
                ArtifactStatus::Missing
            } else {
                ArtifactStatus::Malformed
            };
            return ParsedArtifact {
                kind,
                path,
                status,
                value: None,
                notice: Some(Notice {
                    kind: if status == ArtifactStatus::Missing {
                        format!("{kind}_missing")
                    } else {
                        format!("{kind}_unreadable")
                    },
                    message: format!("{kind} input {path_value} could not be read: {error}"),
                    source_artifact: Some(path_value),
                }),
            };
        }
    };
    match serde_json::from_str::<Value>(&text) {
        Ok(value) => ParsedArtifact {
            kind,
            path,
            status: ArtifactStatus::Read,
            value: Some(value),
            notice: None,
        },
        Err(error) => ParsedArtifact {
            kind,
            path,
            status: ArtifactStatus::Malformed,
            value: None,
            notice: Some(Notice {
                kind: format!("{kind}_malformed"),
                message: format!("{kind} input {path_value} is invalid JSON: {error}"),
                source_artifact: Some(path_value),
            }),
        },
    }
}

fn looks_like_missing_file(error: &str) -> bool {
    error.contains("os error 2")
        || error.contains("No such file")
        || error.contains("cannot find the file")
}

fn add_input_unknowns(
    unknowns: &mut Vec<Notice>,
    artifact: &ParsedArtifact,
    kind: &str,
    message: &str,
) {
    if artifact.status == ArtifactStatus::Omitted || artifact.status == ArtifactStatus::Missing {
        unknowns.push(Notice {
            kind: format!("{kind}_not_supplied"),
            message: message.to_string(),
            source_artifact: artifact.path.clone(),
        });
    }
}

fn add_recommended_unknown(unknowns: &mut Vec<Notice>, artifact: &ParsedArtifact, message: &str) {
    if artifact.status == ArtifactStatus::Omitted || artifact.status == ArtifactStatus::Missing {
        unknowns.push(Notice {
            kind: format!("{}_not_supplied", artifact.kind),
            message: message.to_string(),
            source_artifact: artifact.path.clone(),
        });
    }
}

fn collect_input_blockers(
    blockers: &mut Vec<PromotionBlocker>,
    artifact: &ParsedArtifact,
    target_modes: &[&str],
    kind: &str,
    message: &str,
    repair_action: &str,
) {
    match artifact.status {
        ArtifactStatus::Read => {}
        ArtifactStatus::Omitted | ArtifactStatus::Missing | ArtifactStatus::Malformed => {
            let severity = if artifact.status == ArtifactStatus::Malformed {
                "config_error"
            } else {
                "warning"
            };
            blockers.push(PromotionBlocker {
                kind: kind.to_string(),
                severity: severity.to_string(),
                message: message.to_string(),
                target_modes: target_modes
                    .iter()
                    .map(|mode| (*mode).to_string())
                    .collect(),
                source_artifact: artifact.path.clone(),
                repair_action: repair_action.to_string(),
            });
        }
    }
}

fn collect_malformed_optional_blocker(
    blockers: &mut Vec<PromotionBlocker>,
    artifact: &ParsedArtifact,
    target_modes: &[&str],
    kind: &str,
    message: &str,
    repair_action: &str,
) {
    if artifact.status == ArtifactStatus::Malformed {
        blockers.push(PromotionBlocker {
            kind: kind.to_string(),
            severity: "config_error".to_string(),
            message: message.to_string(),
            target_modes: target_modes
                .iter()
                .map(|mode| (*mode).to_string())
                .collect(),
            source_artifact: artifact.path.clone(),
            repair_action: repair_action.to_string(),
        });
    }
}

fn collect_ceiling_blockers(blockers: &mut Vec<PromotionBlocker>, ceiling: &str) {
    for mode in TARGET_MODES {
        if ceiling_rank(ceiling) < mode_rank(mode) {
            blockers.push(PromotionBlocker {
                kind: format!("current_ceiling_below_{}", mode.replace('-', "_")),
                severity: "warning".to_string(),
                message: format!("Current policy ceiling {ceiling} does not allow {mode}."),
                target_modes: vec![mode.to_string()],
                source_artifact: None,
                repair_action: format!("Fix lower-mode blockers before reviewing {mode}."),
            });
        }
    }
}

fn collect_status_blockers(
    blockers: &mut Vec<PromotionBlocker>,
    artifact: &ParsedArtifact,
    kind: &str,
    message: &str,
    target_modes: &[&str],
    repair_action: &str,
) {
    let Some(value) = artifact.value.as_ref() else {
        return;
    };
    let status = string_path(value, &["status"]).unwrap_or_else(|| "unknown".to_string());
    let healthy = match artifact.kind {
        "waiver_aging" => matches!(status.as_str(), "advisory" | "no_waivers"),
        "suppression_health" => matches!(status.as_str(), "healthy" | "no_suppressions"),
        _ => true,
    };
    if !healthy {
        blockers.push(PromotionBlocker {
            kind: kind.to_string(),
            severity: if status == "config_error" {
                "config_error"
            } else {
                "warning"
            }
            .to_string(),
            message: format!("{message} Status: {status}."),
            target_modes: target_modes
                .iter()
                .map(|mode| (*mode).to_string())
                .collect(),
            source_artifact: artifact.path.clone(),
            repair_action: repair_action.to_string(),
        });
    }
}

fn collect_baseline_blockers(
    blockers: &mut Vec<PromotionBlocker>,
    artifact: &ParsedArtifact,
    facts: &BaselineFacts,
) {
    if artifact.status != ArtifactStatus::Read {
        return;
    }
    if facts.auto_adopt_new {
        blockers.push(PromotionBlocker {
            kind: "baseline_auto_adopt_enabled".to_string(),
            severity: "config_error".to_string(),
            message: "Baseline auto-adopt is enabled; promotion requires reviewed shrink-only baseline behavior.".to_string(),
            target_modes: vec!["baseline-check".to_string(), "calibrated-gate".to_string()],
            source_artifact: artifact.path.clone(),
            repair_action: "Disable auto-adopt-new and review baseline movement manually.".to_string(),
        });
    }
    if facts.stale > 0 {
        blockers.push(PromotionBlocker {
            kind: "baseline_stale_entries".to_string(),
            severity: "warning".to_string(),
            message: format!("Baseline contains {} stale entries.", facts.stale),
            target_modes: vec!["baseline-check".to_string(), "calibrated-gate".to_string()],
            source_artifact: artifact.path.clone(),
            repair_action: "Run shrink-only baseline review and remove resolved entries."
                .to_string(),
        });
    }
    if facts.invalid > 0 || facts.missing_input > 0 {
        blockers.push(PromotionBlocker {
            kind: "baseline_invalid_or_missing_input".to_string(),
            severity: "config_error".to_string(),
            message: format!(
                "Baseline delta has invalid entries={} and missing inputs={}.",
                facts.invalid, facts.missing_input
            ),
            target_modes: vec!["baseline-check".to_string(), "calibrated-gate".to_string()],
            source_artifact: artifact.path.clone(),
            repair_action: "Repair baseline inputs before baseline-check.".to_string(),
        });
    }
}

fn collect_preview_blockers(
    blockers: &mut Vec<PromotionBlocker>,
    policy_readiness: &ParsedArtifact,
    facts: &PreviewFacts,
) {
    if facts.missing_language_status > 0 {
        blockers.push(PromotionBlocker {
            kind: "preview_language_status_missing".to_string(),
            severity: "warning".to_string(),
            message: format!(
                "{} preview-language findings are missing language_status metadata.",
                facts.missing_language_status
            ),
            target_modes: vec![
                "visible-only".to_string(),
                "acknowledgeable".to_string(),
                "baseline-check".to_string(),
                "calibrated-gate".to_string(),
            ],
            source_artifact: policy_readiness.path.clone(),
            repair_action: "Add preview status and static-limit metadata before promotion review."
                .to_string(),
        });
    }
    if facts.gate_eligible > 0 || facts.ripr_zero_blocking > 0 || facts.calibrated_confidence > 0 {
        blockers.push(PromotionBlocker {
            kind: "preview_boundary_violation".to_string(),
            severity: "config_error".to_string(),
            message: format!(
                "Preview evidence reports gate_eligible={}, ripr_zero_blocking={}, calibrated_confidence={}.",
                facts.gate_eligible, facts.ripr_zero_blocking, facts.calibrated_confidence
            ),
            target_modes: vec!["baseline-check".to_string(), "calibrated-gate".to_string()],
            source_artifact: policy_readiness.path.clone(),
            repair_action: "Keep preview-language evidence advisory until a separate promotion packet is reviewed.".to_string(),
        });
    }
}

fn ceiling_rank(ceiling: &str) -> usize {
    match ceiling {
        "ready_for_calibrated_gate" => 4,
        "ready_for_baseline_check" => 3,
        "ready_for_acknowledgeable" => 2,
        "ready_for_visible_only" => 1,
        _ => 0,
    }
}

fn mode_rank(mode: &str) -> usize {
    match mode {
        "visible-only" => 1,
        "acknowledgeable" => 2,
        "baseline-check" => 3,
        "calibrated-gate" => 4,
        _ => usize::MAX,
    }
}

fn baseline_actions(artifact: &ParsedArtifact, facts: &BaselineFacts) -> Vec<String> {
    if artifact.status != ArtifactStatus::Read {
        return vec!["Supply baseline debt delta before baseline-check.".to_string()];
    }
    let mut actions = Vec::new();
    if facts.stale > 0 {
        actions.push("Review stale baseline entries.".to_string());
    }
    if facts.invalid > 0 || facts.missing_input > 0 {
        actions.push("Repair invalid or missing baseline inputs.".to_string());
    }
    if facts.auto_adopt_new {
        actions.push("Disable automatic baseline adoption before promotion.".to_string());
    }
    actions.push("Use shrink-only refresh for resolved debt.".to_string());
    actions
}

fn waiver_actions(artifact: &ParsedArtifact) -> Vec<String> {
    if artifact.status != ArtifactStatus::Read {
        return vec![
            "Supply waiver-aging report before acknowledgement policy.".to_string(),
            "Keep PR-time acknowledgements separate from durable suppressions.".to_string(),
        ];
    }
    let status = artifact
        .value
        .as_ref()
        .and_then(|value| string_path(value, &["status"]))
        .unwrap_or_else(|| "unknown".to_string());
    if status == "config_error" || status == "incomplete" {
        vec!["Repair waiver-aging input before acknowledgement policy.".to_string()]
    } else {
        vec![
            "Review repeated PR-time acknowledgements before requiring acknowledgement."
                .to_string(),
            "Keep waivers visible and do not convert them to suppressions automatically."
                .to_string(),
        ]
    }
}

fn suppression_actions(artifact: &ParsedArtifact) -> Vec<String> {
    if artifact.status != ArtifactStatus::Read {
        return vec!["Supply suppression-health report before stricter policy.".to_string()];
    }
    let status = artifact
        .value
        .as_ref()
        .and_then(|value| string_path(value, &["status"]))
        .unwrap_or_else(|| "unknown".to_string());
    if status == "healthy" || status == "no_suppressions" {
        vec![
            "Keep durable suppressions visible with owner, reason, scope, and review metadata."
                .to_string(),
        ]
    } else {
        vec!["Repair suppression-health warnings before tightening policy.".to_string()]
    }
}

fn calibration_actions(recommendation: &ParsedArtifact, mutation: &ParsedArtifact) -> Vec<String> {
    let mut actions = Vec::new();
    if recommendation.status == ArtifactStatus::Read {
        actions.push(
            "Use calibrated-gate only for eligible stable Rust classes with same-class calibration."
                .to_string(),
        );
    } else {
        actions.push(
            "Collect same-class recommendation calibration before calibrated-gate.".to_string(),
        );
    }
    if mutation.status == ArtifactStatus::Omitted {
        actions.push(
            "Optional mutation calibration was not supplied; keep runtime confirmation separate from static evidence.".to_string(),
        );
    }
    actions
}

fn preview_boundary_actions(facts: &PreviewFacts) -> Vec<String> {
    if facts.visible > 0 || !facts.languages.is_empty() {
        let languages = if facts.languages.is_empty() {
            "preview-language".to_string()
        } else {
            facts.languages.join(", ")
        };
        vec![format!(
            "Keep {languages} preview evidence visible/advisory and excluded from gate eligibility, RIPR Zero blocking debt, and calibrated confidence."
        )]
    } else {
        vec![
            "Keep preview-language evidence advisory until an explicit promotion packet exists."
                .to_string(),
        ]
    }
}

fn safe_reason(mode: &str) -> &'static str {
    match mode {
        "visible-only" => {
            "Policy readiness and supplied inputs allow visible-only advisory display."
        }
        "acknowledgeable" => {
            "Waivers are visible PR-time acknowledgements and suppression health is readable."
        }
        "baseline-check" => {
            "Baseline debt delta is readable, reviewed, and free of stale or invalid entries."
        }
        "calibrated-gate" => {
            "Same-class stable Rust calibration inputs support calibrated-gate review."
        }
        _ => "Policy operations found no blockers for this mode.",
    }
}

fn not_safe_reason(blockers: &[&PromotionBlocker]) -> String {
    let mut parts = blockers
        .iter()
        .take(3)
        .map(|blocker| blocker.message.clone())
        .collect::<Vec<_>>();
    if parts.is_empty() {
        parts.push("Promotion is blocked by unavailable policy evidence.".to_string());
    }
    parts.join(" ")
}

fn safe_sources(mode: &str, artifacts: &[&ParsedArtifact]) -> Vec<String> {
    let wanted = match mode {
        "visible-only" => &["policy_readiness", "gate_decision"][..],
        "acknowledgeable" => &["policy_readiness", "waiver_aging", "suppression_health"][..],
        "baseline-check" => &[
            "policy_readiness",
            "waiver_aging",
            "suppression_health",
            "baseline_delta",
            "gate_decision",
        ][..],
        "calibrated-gate" => &[
            "policy_readiness",
            "waiver_aging",
            "suppression_health",
            "baseline_delta",
            "gate_decision",
            "recommendation_calibration",
            "mutation_calibration",
        ][..],
        _ => &["policy_readiness"][..],
    };
    artifacts
        .iter()
        .filter(|artifact| wanted.contains(&artifact.kind))
        .filter(|artifact| artifact.status == ArtifactStatus::Read)
        .filter_map(|artifact| artifact.path.clone())
        .collect()
}

fn blocker_sources(blockers: &[&PromotionBlocker]) -> Vec<String> {
    let mut sources = BTreeSet::new();
    for blocker in blockers {
        if let Some(source) = blocker.source_artifact.as_deref() {
            sources.insert(source.to_string());
        }
    }
    sources.into_iter().collect()
}

fn baseline_facts(value: &Value) -> BaselineFacts {
    BaselineFacts {
        still_present: usize_path(value, &["delta", "still_present"])
            + usize_path(value, &["debt_delta", "still_present"]),
        resolved: usize_path(value, &["delta", "resolved"])
            + usize_path(value, &["debt_delta", "resolved"]),
        new_policy_eligible: usize_path(value, &["delta", "new_policy_eligible"])
            + usize_path(value, &["debt_delta", "new_policy_eligible"]),
        acknowledged: usize_path(value, &["delta", "acknowledged"])
            + usize_path(value, &["debt_delta", "acknowledged"]),
        suppressed: usize_path(value, &["delta", "suppressed"])
            + usize_path(value, &["debt_delta", "suppressed"]),
        stale: usize_path(value, &["delta", "stale_baseline_entry"])
            + usize_path(value, &["debt_delta", "stale"]),
        invalid: usize_path(value, &["delta", "invalid_baseline_entry"])
            + usize_path(value, &["debt_delta", "invalid"]),
        missing_input: usize_path(value, &["delta", "missing_current_input"])
            + usize_path(value, &["debt_delta", "missing_input"]),
        auto_adopt_new: bool_path(value, &["baseline", "auto_adopt_new"])
            || bool_path(value, &["policy", "auto_adopt_new"]),
    }
}

fn preview_facts(value: Option<&Value>) -> PreviewFacts {
    let Some(value) = value else {
        return PreviewFacts::default();
    };
    let boundary = value.get("preview_evidence_boundary").unwrap_or(value);
    let mut facts = PreviewFacts {
        languages: string_array_path(boundary, &["preview_languages"]),
        visible: usize_path(boundary, &["preview_findings_visible"]),
        gate_eligible: usize_path(boundary, &["preview_findings_gate_eligible"]),
        ripr_zero_blocking: usize_path(boundary, &["preview_findings_ripr_zero_blocking"]),
        calibrated_confidence: usize_path(boundary, &["preview_findings_calibrated_confidence"]),
        missing_language_status: usize_path(boundary, &["missing_language_status"]),
        static_limits_seen: usize_path(boundary, &["static_limits_seen"]),
    };
    let mut scan = PreviewScan::default();
    scan_preview(value, &mut scan);
    for language in scan.languages {
        if !facts.languages.iter().any(|existing| existing == &language) {
            facts.languages.push(language);
        }
    }
    facts.languages.sort();
    facts.languages.dedup();
    facts.visible = facts.visible.max(scan.preview_findings);
    facts.missing_language_status = facts
        .missing_language_status
        .max(scan.missing_language_status);
    facts.static_limits_seen = facts.static_limits_seen.max(scan.static_limits_seen);
    facts
}

#[derive(Default)]
struct PreviewScan {
    languages: Vec<String>,
    preview_findings: usize,
    missing_language_status: usize,
    static_limits_seen: usize,
}

fn scan_preview(value: &Value, scan: &mut PreviewScan) {
    match value {
        Value::Object(map) => {
            let language = map.get("language").and_then(Value::as_str);
            let language_status = map.get("language_status").and_then(Value::as_str);
            if language_status == Some("preview") {
                scan.preview_findings += 1;
                if let Some(language) = language {
                    scan.languages.push(language.to_string());
                }
                if map
                    .get("static_limit_kind")
                    .and_then(Value::as_str)
                    .is_some()
                {
                    scan.static_limits_seen += 1;
                }
            } else if matches!(language, Some("typescript" | "javascript" | "python")) {
                scan.missing_language_status += 1;
                if let Some(language) = language {
                    scan.languages.push(language.to_string());
                }
            }
            for child in map.values() {
                scan_preview(child, scan);
            }
        }
        Value::Array(items) => {
            for item in items {
                scan_preview(item, scan);
            }
        }
        _ => {}
    }
}

fn assessment_json(assessment: &PromotionAssessment) -> Value {
    let mut object = Map::new();
    object.insert("mode".to_string(), json!(assessment.mode));
    object.insert("allowed_now".to_string(), json!(assessment.allowed_now));
    object.insert("reason".to_string(), json!(assessment.reason));
    if !assessment.blockers.is_empty() {
        object.insert("blockers".to_string(), json!(assessment.blockers));
    }
    object.insert(
        "source_artifacts".to_string(),
        json!(assessment.source_artifacts),
    );
    Value::Object(object)
}

fn blocker_json(blocker: &PromotionBlocker) -> Value {
    json!({
        "kind": blocker.kind,
        "severity": blocker.severity,
        "message": blocker.message,
        "target_modes": blocker.target_modes,
        "source_artifact": blocker.source_artifact,
        "repair_action": blocker.repair_action,
    })
}

fn notice_json(notice: &Notice) -> Value {
    json!({
        "kind": notice.kind,
        "message": notice.message,
        "source_artifact": notice.source_artifact,
    })
}

fn input_artifact_json(artifact: &InputArtifact) -> Value {
    json!({
        "kind": artifact.kind,
        "path": artifact.path,
        "status": artifact.status,
    })
}

fn artifact_status_label(status: ArtifactStatus) -> &'static str {
    match status {
        ArtifactStatus::Omitted => "omitted",
        ArtifactStatus::Read => "read",
        ArtifactStatus::Missing => "missing",
        ArtifactStatus::Malformed => "malformed",
    }
}

fn render_action_group(out: &mut String, title: &str, actions: &[String]) {
    out.push_str(&format!("{title}:\n"));
    if actions.is_empty() {
        out.push_str("- none\n\n");
    } else {
        for action in actions {
            out.push_str(&format!("- {action}\n"));
        }
        out.push('\n');
    }
}

fn markdown_text(value: &str) -> String {
    value.replace('|', "\\|").replace('\n', " ")
}

fn string_path(value: &Value, path: &[&str]) -> Option<String> {
    path_value(value, path).and_then(|value| value.as_str().map(ToString::to_string))
}

fn string_array_path(value: &Value, path: &[&str]) -> Vec<String> {
    path_value(value, path)
        .and_then(Value::as_array)
        .map(|items| {
            items
                .iter()
                .filter_map(Value::as_str)
                .map(ToString::to_string)
                .collect()
        })
        .unwrap_or_default()
}

fn usize_path(value: &Value, path: &[&str]) -> usize {
    path_value(value, path)
        .and_then(Value::as_u64)
        .and_then(|value| usize::try_from(value).ok())
        .unwrap_or(0)
}

fn bool_path(value: &Value, path: &[&str]) -> bool {
    path_value(value, path)
        .and_then(Value::as_bool)
        .unwrap_or(false)
}

fn path_value<'a>(value: &'a Value, path: &[&str]) -> Option<&'a Value> {
    let mut current = value;
    for key in path {
        current = current.get(*key)?;
    }
    Some(current)
}

#[cfg(test)]
mod tests {
    use super::*;

    fn input(readiness: Option<Result<String, String>>) -> PolicyOperationsInput {
        PolicyOperationsInput {
            root: ".".to_string(),
            generated_at: "unix_ms:1".to_string(),
            policy_readiness_path: readiness
                .as_ref()
                .map(|_| "policy-readiness.json".to_string()),
            waiver_aging_path: None,
            suppression_health_path: None,
            baseline_delta_path: None,
            gate_decision_path: None,
            recommendation_calibration_path: None,
            mutation_calibration_path: None,
            preview_boundary_path: None,
            policy_readiness_json: readiness,
            waiver_aging_json: None,
            suppression_health_json: None,
            baseline_delta_json: None,
            gate_decision_json: None,
            recommendation_calibration_json: None,
            mutation_calibration_json: None,
            preview_boundary_json: None,
        }
    }

    fn readiness(status: &str) -> String {
        format!(
            r#"{{
              "schema_version": "0.1",
              "kind": "policy_readiness",
              "status": "{status}",
              "next_policy_action": "Review the next policy mode.",
              "preview_evidence_boundary": {{
                "state": "healthy",
                "preview_languages": ["typescript"],
                "preview_findings_visible": 1,
                "preview_findings_gate_eligible": 0,
                "preview_findings_ripr_zero_blocking": 0,
                "preview_findings_calibrated_confidence": 0,
                "missing_language_status": 0,
                "static_limits_seen": 1
              }}
            }}"#
        )
    }

    fn healthy_waiver() -> Result<String, String> {
        Ok(r#"{"schema_version":"0.1","kind":"waiver_aging","status":"advisory","summary":{"waiver_count":1}}"#.to_string())
    }

    fn healthy_suppression() -> Result<String, String> {
        Ok(r#"{"schema_version":"0.1","kind":"suppression_health","status":"healthy","summary":{"warnings":0,"config_errors":0}}"#.to_string())
    }

    fn baseline(stale: usize) -> Result<String, String> {
        Ok(format!(
            r#"{{
              "schema_version": "0.1",
              "kind": "baseline_debt_delta",
              "delta": {{
                "still_present": 1,
                "resolved": 0,
                "new_policy_eligible": 0,
                "acknowledged": 0,
                "suppressed": 0,
                "stale_baseline_entry": {stale},
                "invalid_baseline_entry": 0,
                "missing_current_input": 0
              }}
            }}"#
        ))
    }

    fn with_core_inputs(mut input: PolicyOperationsInput) -> PolicyOperationsInput {
        input.waiver_aging_path = Some("waiver-aging.json".to_string());
        input.waiver_aging_json = Some(healthy_waiver());
        input.suppression_health_path = Some("suppression-health.json".to_string());
        input.suppression_health_json = Some(healthy_suppression());
        input.baseline_delta_path = Some("baseline-debt-delta.json".to_string());
        input.baseline_delta_json = Some(baseline(0));
        input.gate_decision_path = Some("gate-decision.json".to_string());
        input.gate_decision_json = Some(Ok(
            r#"{"schema_version":"0.1","kind":"gate_decision","status":"advisory"}"#.to_string(),
        ));
        input
    }

    #[test]
    fn policy_operations_acknowledgeable_ceiling_blocks_baseline() {
        let mut input = with_core_inputs(input(Some(Ok(readiness("ready_for_acknowledgeable")))));
        input.baseline_delta_json = Some(baseline(1));

        let report = build_policy_operations_report(input);

        assert_eq!(report.current_policy_ceiling, "ready_for_acknowledgeable");
        assert!(
            report
                .safe_to_promote_to
                .iter()
                .any(|assessment| assessment.mode == "visible-only")
        );
        assert!(
            report
                .safe_to_promote_to
                .iter()
                .any(|assessment| assessment.mode == "acknowledgeable")
        );
        assert!(
            report
                .not_safe_to_promote_to
                .iter()
                .any(|assessment| assessment.mode == "baseline-check")
        );
        assert!(
            report
                .promotion_blockers
                .iter()
                .any(|blocker| blocker.kind == "baseline_stale_entries")
        );
    }

    #[test]
    fn policy_operations_baseline_check_ready_allows_baseline() {
        let report = build_policy_operations_report(with_core_inputs(input(Some(Ok(readiness(
            "ready_for_baseline_check",
        ))))));

        assert!(
            report
                .safe_to_promote_to
                .iter()
                .any(|assessment| assessment.mode == "baseline-check")
        );
        assert!(
            report
                .not_safe_to_promote_to
                .iter()
                .any(|assessment| assessment.mode == "calibrated-gate")
        );
    }

    #[test]
    fn policy_operations_calibrated_gate_accepts_missing_optional_mutation() {
        let mut input = with_core_inputs(input(Some(Ok(readiness("ready_for_calibrated_gate")))));
        input.recommendation_calibration_path = Some("recommendation-calibration.json".to_string());
        input.recommendation_calibration_json = Some(Ok(
            r#"{"schema_version":"0.1","kind":"recommendation_calibration","status":"complete"}"#
                .to_string(),
        ));

        let report = build_policy_operations_report(input);

        assert!(
            report
                .safe_to_promote_to
                .iter()
                .any(|assessment| assessment.mode == "calibrated-gate")
        );
        assert!(
            report
                .warnings
                .iter()
                .any(|warning| warning.kind == "missing_optional_input")
        );
    }

    #[test]
    fn policy_operations_missing_readiness_is_config_error() {
        let report = build_policy_operations_report(input(None));

        assert_eq!(report.current_policy_ceiling, "config_error");
        assert!(report.safe_to_promote_to.is_empty());
        assert!(
            report
                .promotion_blockers
                .iter()
                .any(|blocker| blocker.kind == "policy_readiness_unavailable")
        );
    }

    #[test]
    fn policy_operations_malformed_readiness_blocks_all_modes() {
        let report = build_policy_operations_report(input(Some(Ok("not json".to_string()))));

        assert!(report.safe_to_promote_to.is_empty());
        assert_eq!(
            report.input_artifacts[0],
            InputArtifact {
                kind: "policy_readiness".to_string(),
                path: Some("policy-readiness.json".to_string()),
                status: "malformed".to_string(),
            }
        );
        assert!(
            report
                .warnings
                .iter()
                .any(|warning| warning.kind == "policy_readiness_malformed")
        );
    }

    #[test]
    fn policy_operations_preview_boundary_violation_blocks_stricter_modes() {
        let mut input = with_core_inputs(input(Some(Ok(readiness("ready_for_calibrated_gate")))));
        input.recommendation_calibration_path = Some("recommendation-calibration.json".to_string());
        input.recommendation_calibration_json = Some(Ok(
            r#"{"schema_version":"0.1","kind":"recommendation_calibration","status":"complete"}"#
                .to_string(),
        ));
        input.preview_boundary_path = Some("preview-boundary.json".to_string());
        input.preview_boundary_json = Some(Ok(r#"{
              "schema_version": "0.1",
              "kind": "preview_boundary",
              "preview_languages": ["typescript"],
              "preview_findings_visible": 1,
              "preview_findings_gate_eligible": 1,
              "preview_findings_ripr_zero_blocking": 0,
              "preview_findings_calibrated_confidence": 0
            }"#
        .to_string()));

        let report = build_policy_operations_report(input);

        assert!(
            report
                .safe_to_promote_to
                .iter()
                .any(|assessment| assessment.mode == "acknowledgeable")
        );
        assert!(
            report
                .not_safe_to_promote_to
                .iter()
                .any(|assessment| assessment.mode == "baseline-check")
        );
        assert!(
            report
                .promotion_blockers
                .iter()
                .any(|blocker| blocker.kind == "preview_boundary_violation")
        );
    }

    #[test]
    fn policy_operations_supplied_but_unloaded_readiness_is_missing() {
        let mut input = input(None);
        input.policy_readiness_path = Some("policy-readiness.json".to_string());

        let report = build_policy_operations_report(input);

        assert_eq!(report.current_policy_ceiling, "config_error");
        assert_eq!(
            report.input_artifacts[0],
            InputArtifact {
                kind: "policy_readiness".to_string(),
                path: Some("policy-readiness.json".to_string()),
                status: "missing".to_string(),
            }
        );
        assert!(
            report
                .warnings
                .iter()
                .any(|warning| warning.kind == "policy_readiness_missing")
        );
        assert!(
            report
                .promotion_blockers
                .iter()
                .any(|blocker| blocker.kind == "policy_readiness_unavailable")
        );
    }

    #[test]
    fn policy_operations_read_failures_and_optional_malformed_inputs_are_reported() {
        let mut input = with_core_inputs(input(Some(Err(
            "No such file or directory (os error 2)".to_string(),
        ))));
        input.policy_readiness_path = Some("policy-readiness.json".to_string());
        input.waiver_aging_json = Some(Err("permission denied".to_string()));
        input.gate_decision_json = Some(Ok("not json".to_string()));
        input.mutation_calibration_path = Some("mutation-calibration.json".to_string());
        input.mutation_calibration_json = Some(Ok("not json".to_string()));
        input.preview_boundary_path = Some("preview-boundary.json".to_string());
        input.preview_boundary_json = Some(Ok("not json".to_string()));

        let report = build_policy_operations_report(input);

        assert_eq!(report.input_artifacts[0].status, "missing");
        assert!(
            report
                .warnings
                .iter()
                .any(|warning| warning.kind == "policy_readiness_missing")
        );
        assert!(
            report
                .warnings
                .iter()
                .any(|warning| warning.kind == "waiver_aging_unreadable")
        );
        for kind in [
            "gate_decision_malformed",
            "mutation_calibration_malformed",
            "preview_boundary_malformed",
        ] {
            assert!(
                report
                    .promotion_blockers
                    .iter()
                    .any(|blocker| blocker.kind == kind),
                "missing blocker {kind}"
            );
        }
    }

    #[test]
    fn policy_operations_unhealthy_policy_artifacts_block_stricter_modes() {
        let mut input = with_core_inputs(input(Some(Ok(readiness("ready_for_baseline_check")))));
        input.waiver_aging_json = Some(Ok(
            r#"{"schema_version":"0.1","kind":"waiver_aging","status":"config_error"}"#.to_string(),
        ));
        input.suppression_health_json = Some(Ok(
            r#"{"schema_version":"0.1","kind":"suppression_health","status":"warnings"}"#
                .to_string(),
        ));
        input.baseline_delta_json = Some(Ok(r#"{
              "schema_version": "0.1",
              "kind": "baseline_debt_delta",
              "baseline": {"auto_adopt_new": true},
              "delta": {
                "still_present": 1,
                "resolved": 0,
                "new_policy_eligible": 0,
                "acknowledged": 0,
                "suppressed": 0,
                "stale_baseline_entry": 0,
                "invalid_baseline_entry": 2,
                "missing_current_input": 1
              }
            }"#
        .to_string()));

        let report = build_policy_operations_report(input);

        for kind in [
            "waiver_aging_warning",
            "suppression_health_warnings",
            "baseline_auto_adopt_enabled",
            "baseline_invalid_or_missing_input",
        ] {
            assert!(
                report
                    .promotion_blockers
                    .iter()
                    .any(|blocker| blocker.kind == kind),
                "missing blocker {kind}"
            );
        }
        assert!(
            report
                .baseline_actions
                .iter()
                .any(|action| action == "Repair invalid or missing baseline inputs.")
        );
        assert!(
            report
                .baseline_actions
                .iter()
                .any(|action| action == "Disable automatic baseline adoption before promotion.")
        );
        assert_eq!(
            report.waiver_actions,
            vec!["Repair waiver-aging input before acknowledgement policy.".to_string()]
        );
        assert_eq!(
            report.suppression_actions,
            vec!["Repair suppression-health warnings before tightening policy.".to_string()]
        );
    }

    #[test]
    fn policy_operations_scans_nested_preview_records() {
        let input = with_core_inputs(input(Some(Ok(r#"{
              "schema_version": "0.1",
              "kind": "policy_readiness",
              "status": "ready_for_acknowledgeable",
              "findings": [
                {
                  "language": "typescript",
                  "language_status": "preview",
                  "static_limit_kind": "syntax_only"
                },
                {
                  "language": "python"
                }
              ]
            }"#
        .to_string()))));

        let report = build_policy_operations_report(input);

        assert!(
            report
                .promotion_blockers
                .iter()
                .any(|blocker| blocker.kind == "preview_language_status_missing")
        );
        assert!(
            report
                .preview_boundary_actions
                .iter()
                .any(|action| action.contains("python, typescript"))
        );
    }

    #[test]
    fn policy_operations_no_blockers_uses_default_next_action() {
        let mut input = with_core_inputs(input(Some(Ok(r#"{
              "schema_version": "0.1",
              "kind": "policy_readiness",
              "status": "ready_for_calibrated_gate",
              "preview_evidence_boundary": {
                "state": "healthy",
                "preview_findings_visible": 0,
                "preview_findings_gate_eligible": 0,
                "preview_findings_ripr_zero_blocking": 0,
                "preview_findings_calibrated_confidence": 0,
                "missing_language_status": 0,
                "static_limits_seen": 0
              }
            }"#
        .to_string()))));
        input.recommendation_calibration_path = Some("recommendation-calibration.json".to_string());
        input.recommendation_calibration_json = Some(Ok(
            r#"{"schema_version":"0.1","kind":"recommendation_calibration","status":"complete"}"#
                .to_string(),
        ));

        let report = build_policy_operations_report(input);
        let markdown = render_policy_operations_markdown(&report);

        assert_eq!(
            report.recommended_next_action,
            "Keep policy advisory until a promotion packet is reviewed."
        );
        assert!(report.promotion_blockers.is_empty());
        assert!(markdown.contains("## Cannot Promote To\n\n- none"));
        assert!(markdown.contains("## Top Blockers\n\n- none"));
    }

    #[test]
    fn policy_operations_json_and_markdown_are_structured() -> Result<(), String> {
        let report = build_policy_operations_report(with_core_inputs(input(Some(Ok(readiness(
            "ready_for_baseline_check",
        ))))));

        let json = render_policy_operations_json(&report)?;
        let markdown = render_policy_operations_markdown(&report);

        assert!(json.contains("\"kind\": \"policy_operations\""));
        assert!(json.contains("\"current_policy_ceiling\": \"ready_for_baseline_check\""));
        assert!(json.contains("\"input_artifacts\""));
        assert!(markdown.contains("# RIPR Policy Operations"));
        assert!(markdown.contains("Current ceiling: ready_for_baseline_check"));
        assert!(markdown.contains("## Input Artifacts"));
        Ok(())
    }
}
