//! Runtime effect handlers for the Blueprint Planning loop.

use std::{
    collections::BTreeSet,
    fs, io,
    path::{Path, PathBuf},
};

use serde::Serialize;
use serde_json::Value;

use crate::{
    compiler::CompiledRunPlan,
    contracts::{
        ArtifactFormat, BlueprintCritiqueDocument, BlueprintDraftDocument,
        BlueprintEvaluationDecision, BlueprintEvaluationDocument, BlueprintManifestDocument,
        BlueprintPacketDocument, BlueprintPromotionRecord, BlueprintRepairAction,
        BlueprintRepairDecisionDocument, BlueprintRepairMutationPhase, BlueprintSourceWorkItemKind,
        RuntimeEffectMutationPhase, RuntimeEffectRuleDefinition, RuntimeJsonContract,
        RuntimeJsonError, StageResultEnvelope, TaskDocument, WorkItemKind,
    },
    runtime::RuntimeStartupSession,
    work_documents::{parse_task_document_with_source, parse_task_json_import_with_source},
    workspace::{
        QueueStoreError, SourceLifecycleAction, SourceLifecycleIntent, WorkspacePaths,
        enqueue_blueprint_draft, enqueue_task, move_candidate_blueprint_packet,
        persist_blueprint_critique, persist_blueprint_evaluation, persist_blueprint_packet,
        persist_blueprint_promotion, read_active_blueprint_draft, read_blueprint_draft,
        read_blueprint_manifest, update_active_blueprint_draft, write_blueprint_manifest,
    },
};

use super::{
    RuntimeEffectDecision, RuntimeEffectResult, RuntimeTickResult,
    lifecycle::source_lifecycle_intent_for_effect,
};

pub(crate) const MANAGER_BLUEPRINT_HANDLER_ID: &str =
    "manager_blueprint_manifest_to_blueprint_drafts";
pub(crate) const CONTRACTOR_BLUEPRINT_HANDLER_ID: &str = "contractor_blueprint_candidate_persist";
pub(crate) const EVALUATOR_BLUEPRINT_APPROVAL_HANDLER_ID: &str =
    "evaluator_blueprint_approved_to_task";
pub(crate) const EVALUATOR_BLUEPRINT_REJECTION_HANDLER_ID: &str =
    "evaluator_blueprint_rejected_to_draft_revision";
pub(crate) const MECHANIC_BLUEPRINT_REPAIR_HANDLER_ID: &str = "mechanic_blueprint_repair_apply";

/// Dispatch a packaged Blueprint runtime-effect handler.
pub(crate) fn run_blueprint_effect_handler(
    session: &RuntimeStartupSession,
    stage_result: &StageResultEnvelope,
    run_dir: &Path,
    rule: &RuntimeEffectRuleDefinition,
) -> RuntimeTickResult<Option<RuntimeEffectResult>> {
    let Some(handler_id) = rule.handler_id.as_deref() else {
        return Ok(None);
    };
    let result = match handler_id {
        MANAGER_BLUEPRINT_HANDLER_ID => {
            manager_blueprint_manifest_to_blueprint_drafts(&session.paths, stage_result, run_dir)
        }
        CONTRACTOR_BLUEPRINT_HANDLER_ID => {
            contractor_blueprint_candidate_persist(&session.paths, stage_result, run_dir)
        }
        EVALUATOR_BLUEPRINT_APPROVAL_HANDLER_ID => evaluator_blueprint_approved_to_task(
            &session.paths,
            &session.compiled_plan,
            stage_result,
            run_dir,
        ),
        EVALUATOR_BLUEPRINT_REJECTION_HANDLER_ID => evaluator_blueprint_rejected_to_draft_revision(
            &session.paths,
            &session.compiled_plan,
            stage_result,
            run_dir,
        ),
        MECHANIC_BLUEPRINT_REPAIR_HANDLER_ID => mechanic_blueprint_repair_apply(
            &session.paths,
            &session.compiled_plan,
            stage_result,
            run_dir,
        ),
        _ => return Ok(None),
    };
    Ok(Some(result))
}

fn manager_blueprint_manifest_to_blueprint_drafts(
    paths: &WorkspacePaths,
    stage_result: &StageResultEnvelope,
    run_dir: &Path,
) -> RuntimeEffectResult {
    let mut created_paths = Vec::new();
    let manifest = match read_manager_json_model::<BlueprintManifestDocument>(
        &run_dir.join("blueprint_manifest.json"),
        "blueprint_manifest_missing",
        "blueprint_manifest_parse_error",
        "blueprint_manifest_schema_invalid",
    ) {
        Ok(manifest) => manifest,
        Err(error) => {
            return manager_failure(
                stage_result,
                error.class,
                error.message,
                created_paths,
                true,
            );
        }
    };
    let drafts = match read_manager_json_model_list::<BlueprintDraftDocument>(
        &run_dir.join("blueprint_drafts.json"),
        "blueprint_drafts_missing",
        "blueprint_drafts_parse_error",
        "blueprint_drafts_schema_invalid",
    ) {
        Ok(drafts) => drafts,
        Err(error) => {
            return manager_failure(
                stage_result,
                error.class,
                error.message,
                created_paths,
                true,
            );
        }
    };
    if let Err(message) = validate_manager_output(stage_result, &manifest, &drafts) {
        return manager_failure(
            stage_result,
            "blueprint_manifest_draft_mismatch".to_owned(),
            message,
            created_paths,
            true,
        );
    }

    let manifest_exists = match manager_manifest_exists_equivalent(paths, &manifest) {
        Ok(exists) => exists,
        Err(error) => {
            return manager_failure(
                stage_result,
                error.class,
                error.message,
                created_paths,
                true,
            );
        }
    };
    let mut existing_draft_ids = BTreeSet::new();
    for draft in &drafts {
        match manager_draft_exists_equivalent(paths, draft) {
            Ok(true) => {
                existing_draft_ids.insert(draft.draft_id.clone());
            }
            Ok(false) => {}
            Err(error) => {
                return manager_failure(
                    stage_result,
                    error.class,
                    error.message,
                    created_paths,
                    true,
                );
            }
        }
    }

    let source_state = manager_source_lifecycle_state(paths, stage_result);
    let all_outputs_exist = manifest_exists && existing_draft_ids.len() == drafts.len();
    if all_outputs_exist {
        return match source_state {
            SourceState::Active => manager_success(
                created_paths,
                Some(source_lifecycle_intent_for_effect(
                    stage_result,
                    complete_lifecycle_plan_id(stage_result.work_item_kind),
                    SourceLifecycleAction::Complete,
                )),
                format!("queued {} blueprint draft(s)", drafts.len()),
            ),
            SourceState::Target => manager_success(
                created_paths,
                None,
                format!(
                    "blueprint draft output already exists for {}",
                    manifest.manifest_id
                ),
            ),
            SourceState::Invalid => manager_failure(
                stage_result,
                "blueprint_source_lifecycle_invalid".to_owned(),
                format!(
                    "source work item is not active: {}",
                    stage_result.work_item_id
                ),
                created_paths,
                false,
            ),
        };
    }
    if source_state != SourceState::Active {
        return manager_failure(
            stage_result,
            "blueprint_source_lifecycle_invalid".to_owned(),
            format!(
                "source work item is not active: {}",
                stage_result.work_item_id
            ),
            created_paths,
            false,
        );
    }

    if !manifest_exists {
        match write_blueprint_manifest(paths, &manifest) {
            Ok(path) => created_paths.push(effect_path(paths, &path)),
            Err(error) => {
                return manager_failure(
                    stage_result,
                    manager_write_failure_class(&error, &created_paths),
                    error.to_string(),
                    created_paths,
                    true,
                );
            }
        }
    }
    for draft in &drafts {
        if existing_draft_ids.contains(&draft.draft_id) {
            continue;
        }
        match enqueue_blueprint_draft(paths, draft) {
            Ok(path) => created_paths.push(effect_path(paths, &path)),
            Err(error) => {
                return manager_failure(
                    stage_result,
                    manager_write_failure_class(&error, &created_paths),
                    error.to_string(),
                    created_paths,
                    true,
                );
            }
        }
    }

    manager_success(
        created_paths,
        Some(source_lifecycle_intent_for_effect(
            stage_result,
            complete_lifecycle_plan_id(stage_result.work_item_kind),
            SourceLifecycleAction::Complete,
        )),
        format!("queued {} blueprint draft(s)", drafts.len()),
    )
}

fn contractor_blueprint_candidate_persist(
    paths: &WorkspacePaths,
    stage_result: &StageResultEnvelope,
    run_dir: &Path,
) -> RuntimeEffectResult {
    let mut created_paths = Vec::new();
    let result = (|| -> Result<RuntimeEffectResult, BlueprintEffectError> {
        let mut draft = active_draft_for_stage_result(paths, stage_result).map_err(|error| {
            BlueprintEffectError::new("blueprint_candidate_invalid", error.to_string())
        })?;
        let packet =
            read_json_model::<BlueprintPacketDocument>(&run_dir.join("blueprint_packet.json"))
                .map_err(|error| {
                    BlueprintEffectError::new("blueprint_candidate_invalid", error.to_string())
                })?;
        packet.ensure_matches_draft(&draft).map_err(|error| {
            BlueprintEffectError::new("blueprint_candidate_invalid", error.to_string())
        })?;
        validate_packet_critique_reference(&draft, &packet)
            .map_err(|error| BlueprintEffectError::new("blueprint_candidate_invalid", error))?;

        let packet_exists = candidate_packet_exists_equivalent(paths, &packet)?;
        let markdown_exists =
            candidate_markdown_exists_equivalent(paths, &packet.blueprint_id, run_dir)?;

        if !packet_exists {
            let packet_path =
                persist_blueprint_packet(paths, &packet, "candidates").map_err(|error| {
                    BlueprintEffectError::new("blueprint_candidate_invalid", error.to_string())
                })?;
            created_paths.push(effect_path(paths, &packet_path));
        }
        if !markdown_exists {
            let markdown_path = persist_candidate_markdown(paths, &packet.blueprint_id, run_dir)
                .map_err(|error| {
                    BlueprintEffectError::new("blueprint_candidate_invalid", error.to_string())
                })?;
            created_paths.push(effect_path(paths, &markdown_path));
        }

        draft.latest_blueprint_id = Some(packet.blueprint_id.clone());
        update_active_blueprint_draft(paths, &draft).map_err(|error| {
            BlueprintEffectError::new("blueprint_candidate_invalid", error.to_string())
        })?;
        Ok(RuntimeEffectResult {
            handler_id: Some(CONTRACTOR_BLUEPRINT_HANDLER_ID.to_owned()),
            decision: RuntimeEffectDecision::ContinueRoute,
            created_paths: created_paths.clone(),
            source_lifecycle_intent: None,
            failure_class: None,
            message: Some(if created_paths.is_empty() {
                format!(
                    "candidate blueprint {} already persisted",
                    packet.blueprint_id
                )
            } else {
                format!("persisted candidate blueprint {}", packet.blueprint_id)
            }),
            mutation_phase: RuntimeEffectMutationPhase::Unknown,
        })
    })();

    result.unwrap_or_else(|message| {
        blueprint_failure(
            CONTRACTOR_BLUEPRINT_HANDLER_ID,
            stage_result,
            if created_paths.is_empty() {
                message.class.as_str()
            } else {
                "blueprint_partial_mutation"
            },
            message.message,
            created_paths,
        )
    })
}

fn evaluator_blueprint_approved_to_task(
    paths: &WorkspacePaths,
    compiled_plan: &CompiledRunPlan,
    stage_result: &StageResultEnvelope,
    run_dir: &Path,
) -> RuntimeEffectResult {
    let mut created_paths = Vec::new();
    let mut failure_source_state = None;
    let result = (|| -> Result<RuntimeEffectResult, BlueprintEffectError> {
        let (draft, source_state) =
            approval_draft_for_stage_result(paths, stage_result).map_err(|error| {
                BlueprintEffectError::new("blueprint_evaluation_invalid", error.to_string())
            })?;
        failure_source_state = Some(source_state);
        let packet = approval_packet_for_draft(paths, &draft)?;
        let evaluation = read_json_model::<BlueprintEvaluationDocument>(
            &run_dir.join("blueprint_evaluation.json"),
        )
        .map_err(|error| {
            BlueprintEffectError::new("blueprint_evaluation_invalid", error.to_string())
        })?;
        validate_approval(&evaluation, &packet).map_err(|message| {
            BlueprintEffectError::new("blueprint_evaluation_invalid", message)
        })?;
        let mut task = read_generated_task(compiled_plan, run_dir)?;
        validate_generated_task(&task, &draft, &packet)?;
        promote_approved_blueprint_task(
            paths,
            EVALUATOR_BLUEPRINT_APPROVAL_HANDLER_ID,
            &draft,
            source_state,
            &packet,
            &evaluation,
            &mut task,
            run_dir,
            &mut created_paths,
        )
    })();

    result.unwrap_or_else(|error| {
        let mut failure = blueprint_failure(
            EVALUATOR_BLUEPRINT_APPROVAL_HANDLER_ID,
            stage_result,
            approval_failure_class(&error, &created_paths),
            error.message,
            created_paths,
        );
        if failure_source_state == Some(BlueprintApprovalSourceState::Approved) {
            failure.source_lifecycle_intent = None;
        }
        failure
    })
}

fn mechanic_blueprint_repair_apply(
    paths: &WorkspacePaths,
    compiled_plan: &CompiledRunPlan,
    stage_result: &StageResultEnvelope,
    run_dir: &Path,
) -> RuntimeEffectResult {
    let mut created_paths = Vec::new();
    let mut failure_source_state = None;
    let result = (|| -> Result<RuntimeEffectResult, BlueprintEffectError> {
        let decision = read_blueprint_repair_decision(compiled_plan, run_dir)?;
        validate_mechanic_repair_stage_result(&decision, stage_result)?;
        if decision.repair_action == BlueprintRepairAction::BlockForOperator {
            return Err(BlueprintEffectError::new(
                "blueprint_repair_requested_operator",
                decision
                    .operator_reason
                    .clone()
                    .unwrap_or_else(|| "Mechanic Blueprint requested operator review".to_owned()),
            ));
        }
        if decision.repair_action != BlueprintRepairAction::ApplyRepairedGeneratedTask {
            return Err(BlueprintEffectError::new(
                "blueprint_repair_context_mismatch",
                format!(
                    "repair action {:?} is not runtime-applied by this handler",
                    decision.repair_action
                ),
            ));
        }

        let _failed_context = failed_runtime_effect_context_for_decision(run_dir, &decision)?;
        let (draft, source_state) =
            approval_draft_for_stage_result(paths, stage_result).map_err(|error| {
                BlueprintEffectError::new("blueprint_repair_context_mismatch", error.to_string())
            })?;
        failure_source_state = Some(source_state);
        validate_repair_decision_matches_draft(&decision, &draft)?;
        let packet = approval_packet_for_draft(paths, &draft).map_err(|error| {
            BlueprintEffectError::new("blueprint_repair_context_mismatch", error.to_string())
        })?;
        let evaluation = read_repair_evaluation(run_dir)?;
        validate_repair_decision_matches_approval_context(&decision, &packet, &evaluation)?;
        let mut task = read_repaired_generated_task(compiled_plan, run_dir)?;
        validate_repaired_generated_task(&task, &decision, &draft, &packet)?;

        promote_approved_blueprint_task(
            paths,
            MECHANIC_BLUEPRINT_REPAIR_HANDLER_ID,
            &draft,
            source_state,
            &packet,
            &evaluation,
            &mut task,
            run_dir,
            &mut created_paths,
        )
    })();

    result.unwrap_or_else(|error| {
        let mut failure = blueprint_failure(
            MECHANIC_BLUEPRINT_REPAIR_HANDLER_ID,
            stage_result,
            if created_paths.is_empty() {
                error.class.as_str()
            } else {
                "blueprint_partial_mutation"
            },
            error.message,
            created_paths,
        );
        if failure_source_state == Some(BlueprintApprovalSourceState::Approved) {
            failure.source_lifecycle_intent = None;
        }
        failure
    })
}

fn evaluator_blueprint_rejected_to_draft_revision(
    paths: &WorkspacePaths,
    compiled_plan: &CompiledRunPlan,
    stage_result: &StageResultEnvelope,
    run_dir: &Path,
) -> RuntimeEffectResult {
    let mut created_paths = Vec::new();
    let result = (|| -> Result<RuntimeEffectResult, BlueprintEffectError> {
        let mut draft = active_draft_for_stage_result(paths, stage_result).map_err(|error| {
            BlueprintEffectError::new("blueprint_critique_invalid", error.to_string())
        })?;
        let packet = candidate_packet_for_draft(paths, &draft).map_err(|error| {
            BlueprintEffectError::new("blueprint_critique_invalid", error.to_string())
        })?;
        let evaluation = read_json_model::<BlueprintEvaluationDocument>(
            &run_dir.join("blueprint_evaluation.json"),
        )
        .map_err(|error| {
            BlueprintEffectError::new("blueprint_critique_invalid", error.to_string())
        })?;
        let critique = read_blueprint_critique(compiled_plan, run_dir)?;
        validate_rejection(&evaluation, &critique, &packet)?;

        let evaluation_path = persist_blueprint_evaluation(paths, &evaluation)?;
        created_paths.push(effect_path(paths, &evaluation_path));
        let rejected_packet_path =
            move_candidate_blueprint_packet(paths, &packet.blueprint_id, "rejected")?;
        created_paths.push(effect_path(paths, &rejected_packet_path));
        if let Some(markdown_path) =
            move_candidate_markdown(paths, &packet.blueprint_id, "rejected")?
        {
            created_paths.push(effect_path(paths, &markdown_path));
        }
        let critique_path = persist_blueprint_critique(paths, &critique, "open")?;
        created_paths.push(effect_path(paths, &critique_path));

        draft.current_revision = packet.revision;
        draft.latest_critique_id = Some(critique.critique_id.clone());
        draft.latest_blueprint_id = Some(packet.blueprint_id.clone());
        draft.updated_at = Some(evaluation.created_at.clone());
        update_active_blueprint_draft(paths, &draft)?;
        Ok(RuntimeEffectResult {
            handler_id: Some(EVALUATOR_BLUEPRINT_REJECTION_HANDLER_ID.to_owned()),
            decision: RuntimeEffectDecision::ContinueRoute,
            created_paths: created_paths.clone(),
            source_lifecycle_intent: None,
            failure_class: None,
            message: Some(format!(
                "recorded rejection critique {}",
                critique.critique_id
            )),
            mutation_phase: RuntimeEffectMutationPhase::Unknown,
        })
    })();

    result.unwrap_or_else(|error| {
        blueprint_failure(
            EVALUATOR_BLUEPRINT_REJECTION_HANDLER_ID,
            stage_result,
            if created_paths.is_empty() {
                error.class.as_str()
            } else {
                "blueprint_partial_mutation"
            },
            error.message,
            created_paths,
        )
    })
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct BlueprintEffectError {
    class: String,
    message: String,
}

impl BlueprintEffectError {
    fn new(class: impl Into<String>, message: impl Into<String>) -> Self {
        Self {
            class: class.into(),
            message: message.into(),
        }
    }
}

impl std::fmt::Display for BlueprintEffectError {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(formatter, "{}: {}", self.class, self.message)
    }
}

impl From<QueueStoreError> for BlueprintEffectError {
    fn from(value: QueueStoreError) -> Self {
        Self::new("blueprint_state_error", value.to_string())
    }
}

fn read_manager_json_model<T>(
    path: &Path,
    missing_class: &str,
    parse_class: &str,
    schema_class: &str,
) -> Result<T, BlueprintEffectError>
where
    T: RuntimeJsonContract,
{
    if !path.exists() {
        return Err(BlueprintEffectError::new(
            missing_class,
            format!(
                "required Blueprint artifact is missing: {}",
                path_name(path)
            ),
        ));
    }
    let raw = fs::read_to_string(path).map_err(|error| {
        BlueprintEffectError::new(
            missing_class,
            format!(
                "required Blueprint artifact could not be read: {}: {error}",
                path_name(path)
            ),
        )
    })?;
    T::from_json_str(&raw).map_err(|error| {
        let class = if matches!(error, RuntimeJsonError::Json { .. }) {
            parse_class
        } else {
            schema_class
        };
        BlueprintEffectError::new(
            class,
            format!("{} failed schema validation: {error}", path_name(path)),
        )
    })
}

fn read_manager_json_model_list<T>(
    path: &Path,
    missing_class: &str,
    parse_class: &str,
    schema_class: &str,
) -> Result<Vec<T>, BlueprintEffectError>
where
    T: RuntimeJsonContract,
{
    if !path.exists() {
        return Err(BlueprintEffectError::new(
            missing_class,
            format!(
                "required Blueprint artifact is missing: {}",
                path_name(path)
            ),
        ));
    }
    let raw = fs::read_to_string(path).map_err(|error| {
        BlueprintEffectError::new(
            missing_class,
            format!(
                "required Blueprint artifact could not be read: {}: {error}",
                path_name(path)
            ),
        )
    })?;
    let payload: Value = serde_json::from_str(&raw).map_err(|error| {
        BlueprintEffectError::new(
            parse_class,
            format!("{} is not valid JSON: {error}", path_name(path)),
        )
    })?;
    let Value::Array(values) = payload else {
        return Err(BlueprintEffectError::new(
            schema_class,
            format!("{} must be a JSON list", path_name(path)),
        ));
    };
    values
        .into_iter()
        .map(|value| {
            T::from_json_value(value).map_err(|error| {
                BlueprintEffectError::new(
                    schema_class,
                    format!("{} failed schema validation: {error}", path_name(path)),
                )
            })
        })
        .collect()
}

fn read_json_model<T>(path: &Path) -> Result<T, BlueprintEffectError>
where
    T: RuntimeJsonContract,
{
    if !path.exists() {
        return Err(BlueprintEffectError::new(
            "artifact_missing",
            format!(
                "required Blueprint artifact is missing: {}",
                path_name(path)
            ),
        ));
    }
    let raw = fs::read_to_string(path).map_err(|error| {
        BlueprintEffectError::new("artifact_missing", format!("{}: {error}", path.display()))
    })?;
    T::from_json_str(&raw)
        .map_err(|error| BlueprintEffectError::new("json_model_parse", error.to_string()))
}

fn validate_manager_output(
    stage_result: &StageResultEnvelope,
    manifest: &BlueprintManifestDocument,
    drafts: &[BlueprintDraftDocument],
) -> Result<(), String> {
    let expected_kind = match stage_result.work_item_kind {
        WorkItemKind::Spec => BlueprintSourceWorkItemKind::Spec,
        WorkItemKind::Incident => BlueprintSourceWorkItemKind::Incident,
        _ => return Err("manifest source_work_item_kind does not match active source".to_owned()),
    };
    if manifest.source_work_item_kind != expected_kind {
        return Err("manifest source_work_item_kind does not match active source".to_owned());
    }
    if manifest.source_work_item_id != stage_result.work_item_id {
        return Err("manifest source_work_item_id does not match active source".to_owned());
    }
    if drafts
        .iter()
        .map(|draft| draft.draft_id.clone())
        .collect::<Vec<_>>()
        != manifest.draft_ids
    {
        return Err("draft order must match manifest draft_ids".to_owned());
    }
    if drafts.len() as u64 != manifest.draft_count {
        return Err("draft count must match manifest".to_owned());
    }
    let mut previous_ids = BTreeSet::new();
    let mut previous_id: Option<String> = None;
    for (index, draft) in drafts.iter().enumerate() {
        let expected_index = (index + 1) as u64;
        if draft.draft_index != expected_index {
            return Err("draft indexes must be contiguous starting at 1".to_owned());
        }
        ensure_draft_matches_manifest(draft, manifest)?;
        if draft.draft_index == 1 && !draft.depends_on_draft_ids.is_empty() {
            return Err("first Blueprint draft cannot declare dependencies".to_owned());
        }
        if draft.draft_index > 1
            && draft.depends_on_draft_ids != vec![previous_id.clone().unwrap_or_default()]
        {
            return Err(
                "strict Blueprint sequence requires dependency on previous draft".to_owned(),
            );
        }
        if !draft
            .depends_on_draft_ids
            .iter()
            .all(|id| previous_ids.contains(id))
        {
            return Err("Blueprint draft dependencies must refer to earlier drafts".to_owned());
        }
        previous_ids.insert(draft.draft_id.clone());
        previous_id = Some(draft.draft_id.clone());
    }
    Ok(())
}

fn ensure_draft_matches_manifest(
    draft: &BlueprintDraftDocument,
    manifest: &BlueprintManifestDocument,
) -> Result<(), String> {
    if draft.manifest_id != manifest.manifest_id {
        return Err("draft manifest_id does not match manifest".to_owned());
    }
    if draft.root_spec_id != manifest.root_spec_id {
        return Err("draft root_spec_id does not match manifest".to_owned());
    }
    if draft.root_idea_id != manifest.root_idea_id {
        return Err("draft root_idea_id does not match manifest".to_owned());
    }
    if draft.source_spec_id != manifest.source_spec_id {
        return Err("draft source_spec_id does not match manifest".to_owned());
    }
    Ok(())
}

fn manager_manifest_exists_equivalent(
    paths: &WorkspacePaths,
    manifest: &BlueprintManifestDocument,
) -> Result<bool, BlueprintEffectError> {
    match read_blueprint_manifest(paths, &manifest.manifest_id) {
        Ok(existing) => {
            if normalized_json(&existing)? != normalized_json(manifest)? {
                return Err(BlueprintEffectError::new(
                    "blueprint_manifest_duplicate",
                    format!(
                        "blueprint_manifest_duplicate: manifest_id={}",
                        manifest.manifest_id
                    ),
                ));
            }
            Ok(true)
        }
        Err(error) if error.to_string().contains("blueprint_manifest_missing") => Ok(false),
        Err(error) => Err(BlueprintEffectError::new(
            "blueprint_manifest_duplicate",
            error.to_string(),
        )),
    }
}

fn manager_draft_exists_equivalent(
    paths: &WorkspacePaths,
    draft: &BlueprintDraftDocument,
) -> Result<bool, BlueprintEffectError> {
    let mut entries = Vec::new();
    for state in [
        "queue",
        "active",
        "approved",
        "blocked",
        "canceled",
        "superseded",
    ] {
        let path = paths
            .runtime_root
            .join("blueprints")
            .join("drafts")
            .join(state)
            .join(format!("{}.json", draft.draft_id));
        if !path.exists() {
            continue;
        }
        let existing = read_blueprint_draft(&path).map_err(|error| {
            BlueprintEffectError::new(
                "blueprint_draft_duplicate",
                format!(
                    "existing draft {} cannot be validated: {error}",
                    draft.draft_id
                ),
            )
        })?;
        if normalized_draft_identity(&existing)? != normalized_draft_identity(draft)? {
            return Err(BlueprintEffectError::new(
                "blueprint_draft_duplicate",
                format!("blueprint_draft_duplicate: draft_id={}", draft.draft_id),
            ));
        }
        entries.push(path);
    }
    if entries.len() > 1 {
        let locations = entries
            .iter()
            .map(|path| path.to_string_lossy().replace('\\', "/"))
            .collect::<Vec<_>>()
            .join(", ");
        return Err(BlueprintEffectError::new(
            "blueprint_draft_duplicate",
            format!(
                "blueprint_draft_duplicate: draft_id={} in {locations}",
                draft.draft_id
            ),
        ));
    }
    Ok(!entries.is_empty())
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SourceState {
    Active,
    Target,
    Invalid,
}

fn manager_source_lifecycle_state(
    paths: &WorkspacePaths,
    stage_result: &StageResultEnvelope,
) -> SourceState {
    match stage_result.work_item_kind {
        WorkItemKind::Spec => {
            if paths
                .specs_active_dir
                .join(format!("{}.md", stage_result.work_item_id))
                .exists()
            {
                SourceState::Active
            } else if paths
                .specs_done_dir
                .join(format!("{}.md", stage_result.work_item_id))
                .exists()
            {
                SourceState::Target
            } else {
                SourceState::Invalid
            }
        }
        WorkItemKind::Incident => {
            if paths
                .incidents_active_dir
                .join(format!("{}.md", stage_result.work_item_id))
                .exists()
            {
                SourceState::Active
            } else if paths
                .incidents_resolved_dir
                .join(format!("{}.md", stage_result.work_item_id))
                .exists()
            {
                SourceState::Target
            } else {
                SourceState::Invalid
            }
        }
        _ => SourceState::Invalid,
    }
}

fn manager_success(
    created_paths: Vec<String>,
    source_lifecycle_intent: Option<SourceLifecycleIntent>,
    message: String,
) -> RuntimeEffectResult {
    RuntimeEffectResult {
        handler_id: Some(MANAGER_BLUEPRINT_HANDLER_ID.to_owned()),
        decision: RuntimeEffectDecision::RequestCompleteSource,
        created_paths,
        source_lifecycle_intent,
        failure_class: None,
        message: Some(message),
        mutation_phase: RuntimeEffectMutationPhase::Unknown,
    }
}

fn manager_failure(
    stage_result: &StageResultEnvelope,
    failure_class: String,
    message: String,
    created_paths: Vec<String>,
    include_source_lifecycle_intent: bool,
) -> RuntimeEffectResult {
    RuntimeEffectResult {
        handler_id: Some(MANAGER_BLUEPRINT_HANDLER_ID.to_owned()),
        decision: RuntimeEffectDecision::RequestBlockSource,
        created_paths: created_paths.clone(),
        source_lifecycle_intent: include_source_lifecycle_intent.then(|| {
            source_lifecycle_intent_for_effect(
                stage_result,
                block_lifecycle_plan_id(stage_result.work_item_kind),
                SourceLifecycleAction::Block,
            )
        }),
        failure_class: Some(failure_class),
        message: Some(message),
        mutation_phase: if created_paths.is_empty() {
            RuntimeEffectMutationPhase::PreMutation
        } else {
            RuntimeEffectMutationPhase::PartialMutation
        },
    }
}

fn manager_write_failure_class(error: &QueueStoreError, created_paths: &[String]) -> String {
    if !created_paths.is_empty() {
        return "blueprint_partial_mutation".to_owned();
    }
    let message = error.to_string();
    if message.contains("blueprint_manifest_duplicate")
        || (message.contains("Blueprint artifact already exists") && message.contains("manifests"))
    {
        return "blueprint_manifest_duplicate".to_owned();
    }
    if (message.contains("blueprint draft") && message.contains("already exists"))
        || message.contains("blueprint_draft_duplicate")
    {
        return "blueprint_draft_duplicate".to_owned();
    }
    "blueprint_partial_mutation".to_owned()
}

fn active_draft_for_stage_result(
    paths: &WorkspacePaths,
    stage_result: &StageResultEnvelope,
) -> Result<BlueprintDraftDocument, QueueStoreError> {
    if stage_result.work_item_kind != WorkItemKind::BlueprintDraft {
        return Err(QueueStoreError::InvalidState {
            message: format!(
                "Blueprint handler requires blueprint_draft source, got {}",
                stage_result.work_item_kind.as_str()
            ),
        });
    }
    read_active_blueprint_draft(paths, &stage_result.work_item_id)
}

fn validate_packet_critique_reference(
    draft: &BlueprintDraftDocument,
    packet: &BlueprintPacketDocument,
) -> Result<(), String> {
    let Some(critique_id) = draft.latest_critique_id.as_deref() else {
        return Ok(());
    };
    if packet
        .references
        .iter()
        .any(|reference| reference.contains(critique_id))
    {
        Ok(())
    } else {
        Err("candidate Blueprint must reference latest open critique".to_owned())
    }
}

fn candidate_packet_exists_equivalent(
    paths: &WorkspacePaths,
    packet: &BlueprintPacketDocument,
) -> Result<bool, BlueprintEffectError> {
    let path = blueprint_packet_path(paths, &packet.blueprint_id, "candidates");
    if !path.exists() {
        return Ok(false);
    }
    let existing = read_json_model::<BlueprintPacketDocument>(&path).map_err(|error| {
        BlueprintEffectError::new(
            "blueprint_candidate_duplicate_conflict",
            format!(
                "existing candidate packet {} cannot be validated: {}",
                packet.blueprint_id, error.message
            ),
        )
    })?;
    if normalized_json(&existing)? != normalized_json(packet)? {
        return Err(BlueprintEffectError::new(
            "blueprint_candidate_duplicate_conflict",
            format!(
                "blueprint_candidate_duplicate_conflict: blueprint_id={}",
                packet.blueprint_id
            ),
        ));
    }
    Ok(true)
}

fn candidate_markdown_exists_equivalent(
    paths: &WorkspacePaths,
    blueprint_id: &str,
    run_dir: &Path,
) -> Result<bool, BlueprintEffectError> {
    let source = candidate_markdown_source(run_dir)?;
    let destination = candidate_markdown_path(paths, blueprint_id);
    if !destination.exists() {
        return Ok(false);
    }
    let source_content = fs::read_to_string(&source).map_err(|error| {
        BlueprintEffectError::new(
            "blueprint_candidate_markdown_conflict",
            format!("candidate markdown {blueprint_id} cannot be read: {error}"),
        )
    })?;
    let existing_content = fs::read_to_string(&destination).map_err(|error| {
        BlueprintEffectError::new(
            "blueprint_candidate_markdown_conflict",
            format!("existing candidate markdown {blueprint_id} cannot be validated: {error}"),
        )
    })?;
    if normalized_markdown_content(&existing_content)
        != normalized_markdown_content(&source_content)
    {
        return Err(BlueprintEffectError::new(
            "blueprint_candidate_markdown_conflict",
            format!("blueprint_candidate_markdown_conflict: blueprint_id={blueprint_id}"),
        ));
    }
    Ok(true)
}

fn persist_candidate_markdown(
    paths: &WorkspacePaths,
    blueprint_id: &str,
    run_dir: &Path,
) -> io::Result<PathBuf> {
    let source = candidate_markdown_source(run_dir)
        .map_err(|error| io::Error::new(io::ErrorKind::NotFound, error.message))?;
    let destination = candidate_markdown_path(paths, blueprint_id);
    copy_unique_file(&source, &destination)?;
    Ok(destination)
}

fn candidate_markdown_source(run_dir: &Path) -> Result<PathBuf, BlueprintEffectError> {
    let source = run_dir.join("blueprint.md");
    if source.exists() {
        Ok(source)
    } else {
        Err(BlueprintEffectError::new(
            "artifact_missing",
            "required Blueprint artifact is missing: blueprint.md",
        ))
    }
}

fn candidate_markdown_path(paths: &WorkspacePaths, blueprint_id: &str) -> PathBuf {
    paths
        .runtime_root
        .join("blueprints/packets/candidates")
        .join(format!("{blueprint_id}.md"))
}

fn normalized_markdown_content(content: &str) -> String {
    content.replace("\r\n", "\n").replace('\r', "\n")
}

fn candidate_packet_for_draft(
    paths: &WorkspacePaths,
    draft: &BlueprintDraftDocument,
) -> Result<BlueprintPacketDocument, BlueprintEffectError> {
    let blueprint_id = draft.latest_blueprint_id.as_deref().ok_or_else(|| {
        BlueprintEffectError::new(
            "blueprint_evaluation_invalid",
            "active draft has no latest candidate blueprint",
        )
    })?;
    let path = paths
        .runtime_root
        .join("blueprints/packets/candidates")
        .join(format!("{blueprint_id}.json"));
    let packet = read_json_model::<BlueprintPacketDocument>(&path)?;
    packet.ensure_matches_draft(draft).map_err(|error| {
        BlueprintEffectError::new("blueprint_evaluation_invalid", error.to_string())
    })?;
    Ok(packet)
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BlueprintApprovalSourceState {
    Active,
    Approved,
}

fn approval_draft_for_stage_result(
    paths: &WorkspacePaths,
    stage_result: &StageResultEnvelope,
) -> Result<(BlueprintDraftDocument, BlueprintApprovalSourceState), QueueStoreError> {
    if stage_result.work_item_kind != WorkItemKind::BlueprintDraft {
        return Err(QueueStoreError::InvalidState {
            message: format!(
                "Blueprint handler requires blueprint_draft source, got {}",
                stage_result.work_item_kind.as_str()
            ),
        });
    }
    let mut entries = Vec::new();
    for (state, source_state) in [
        ("active", BlueprintApprovalSourceState::Active),
        ("approved", BlueprintApprovalSourceState::Approved),
    ] {
        let path = paths
            .runtime_root
            .join("blueprints/drafts")
            .join(state)
            .join(format!("{}.json", stage_result.work_item_id));
        if path.exists() {
            entries.push((source_state, read_blueprint_draft(&path)?));
        }
    }
    match entries.as_slice() {
        [] => Err(QueueStoreError::InvalidState {
            message: format!(
                "active or approved blueprint draft {} not found",
                stage_result.work_item_id
            ),
        }),
        [(state, draft)] => Ok((draft.clone(), *state)),
        _ => Err(QueueStoreError::InvalidState {
            message: format!(
                "blueprint draft {} exists in active and approved",
                stage_result.work_item_id
            ),
        }),
    }
}

fn approval_packet_for_draft(
    paths: &WorkspacePaths,
    draft: &BlueprintDraftDocument,
) -> Result<BlueprintPacketDocument, BlueprintEffectError> {
    let blueprint_id = draft.latest_blueprint_id.as_deref().ok_or_else(|| {
        BlueprintEffectError::new(
            "blueprint_evaluation_invalid",
            "active draft has no latest candidate blueprint",
        )
    })?;
    let mut entries = Vec::new();
    for state in ["candidates", "approved"] {
        let path = blueprint_packet_path(paths, blueprint_id, state);
        if !path.exists() {
            continue;
        }
        let packet = read_json_model::<BlueprintPacketDocument>(&path).map_err(|error| {
            BlueprintEffectError::new(
                "blueprint_approved_packet_conflict",
                format!("existing {state} packet {blueprint_id} cannot be validated: {error}"),
            )
        })?;
        packet.ensure_matches_draft(draft).map_err(|error| {
            BlueprintEffectError::new(
                "blueprint_approved_packet_conflict",
                format!("existing {state} packet {blueprint_id} cannot be validated: {error}"),
            )
        })?;
        entries.push(packet);
    }
    if entries.is_empty() {
        return Err(BlueprintEffectError::new(
            "blueprint_evaluation_invalid",
            format!("candidate or approved blueprint packet {blueprint_id} not found"),
        ));
    }
    let expected = normalized_json(&entries[0])?;
    for packet in entries.iter().skip(1) {
        if normalized_json(packet)? != expected {
            return Err(BlueprintEffectError::new(
                "blueprint_approved_packet_conflict",
                format!("blueprint_approved_packet_conflict: blueprint_id={blueprint_id}"),
            ));
        }
    }
    Ok(entries.remove(0))
}

fn validate_approval(
    evaluation: &BlueprintEvaluationDocument,
    packet: &BlueprintPacketDocument,
) -> Result<(), String> {
    if evaluation.decision != BlueprintEvaluationDecision::Approved {
        return Err("approval handler requires decision=approved".to_owned());
    }
    evaluation
        .ensure_matches_packet(packet)
        .map_err(|error| error.to_string())
}

fn validate_rejection(
    evaluation: &BlueprintEvaluationDocument,
    critique: &BlueprintCritiqueDocument,
    packet: &BlueprintPacketDocument,
) -> Result<(), BlueprintEffectError> {
    if evaluation.decision != BlueprintEvaluationDecision::Rejected {
        return Err(BlueprintEffectError::new(
            "blueprint_critique_invalid",
            "rejection handler requires decision=rejected",
        ));
    }
    evaluation.ensure_matches_packet(packet).map_err(|error| {
        BlueprintEffectError::new("blueprint_critique_invalid", error.to_string())
    })?;
    critique.ensure_matches_packet(packet).map_err(|error| {
        BlueprintEffectError::new("blueprint_critique_invalid", error.to_string())
    })?;
    if evaluation.critique_id.as_deref() != Some(critique.critique_id.as_str()) {
        return Err(BlueprintEffectError::new(
            "blueprint_critique_invalid",
            "evaluation critique_id must match critique packet",
        ));
    }
    Ok(())
}

fn read_generated_task(
    plan: &CompiledRunPlan,
    run_dir: &Path,
) -> Result<TaskDocument, BlueprintEffectError> {
    read_task_artifact_with_agreement(
        plan,
        "generated_task",
        run_dir,
        "generated_task_missing",
        "generated_task_invalid",
        "generated_task",
    )
}

fn read_blueprint_critique(
    plan: &CompiledRunPlan,
    run_dir: &Path,
) -> Result<BlueprintCritiqueDocument, BlueprintEffectError> {
    let resolved = resolve_run_artifact(plan, "blueprint_critique", run_dir)?;
    if resolved.format != ArtifactFormat::Json {
        return Err(BlueprintEffectError::new(
            "blueprint_critique_invalid",
            "blueprint_critique must be JSON",
        ));
    }
    read_json_model::<BlueprintCritiqueDocument>(&resolved.path)
        .map_err(|error| BlueprintEffectError::new("blueprint_critique_invalid", error.message))
}

fn validate_generated_task(
    task: &TaskDocument,
    draft: &BlueprintDraftDocument,
    packet: &BlueprintPacketDocument,
) -> Result<(), BlueprintEffectError> {
    if task.root_spec_id.as_deref() != Some(draft.root_spec_id.as_str()) {
        return Err(BlueprintEffectError::new(
            "generated_task_invalid",
            "generated task root_spec_id must match Blueprint draft",
        ));
    }
    if task.root_idea_id.as_deref() != Some(draft.root_idea_id.as_str()) {
        return Err(BlueprintEffectError::new(
            "generated_task_invalid",
            "generated task root_idea_id must match Blueprint draft",
        ));
    }
    if task.spec_id.as_deref() != Some(draft.source_spec_id.as_str()) {
        return Err(BlueprintEffectError::new(
            "generated_task_invalid",
            "generated task spec_id must match Blueprint source spec",
        ));
    }
    ensure_contains_all(
        &task.acceptance,
        &packet.task_acceptance,
        "generated task acceptance",
    )?;
    ensure_contains_all(
        &task.required_checks,
        &packet.required_checks,
        "generated task required checks",
    )?;
    let allowed = packet
        .intended_files
        .iter()
        .chain(draft.target_paths.iter())
        .cloned()
        .collect::<BTreeSet<_>>();
    if !task.target_paths.iter().all(|path| allowed.contains(path)) {
        return Err(BlueprintEffectError::new(
            "generated_task_invalid",
            "generated task target_paths must stay within Blueprint scope",
        ));
    }
    Ok(())
}

fn read_blueprint_repair_decision(
    plan: &CompiledRunPlan,
    run_dir: &Path,
) -> Result<BlueprintRepairDecisionDocument, BlueprintEffectError> {
    let resolved =
        resolve_run_artifact(plan, "blueprint_repair_decision", run_dir).map_err(|error| {
            let class = if error.class == "artifact_missing" {
                "blueprint_repair_decision_missing"
            } else {
                "blueprint_repair_decision_invalid"
            };
            BlueprintEffectError::new(class, error.message)
        })?;
    if resolved.format != ArtifactFormat::Json {
        return Err(BlueprintEffectError::new(
            "blueprint_repair_decision_invalid",
            "blueprint_repair_decision must be JSON",
        ));
    }
    read_json_model::<BlueprintRepairDecisionDocument>(&resolved.path).map_err(|error| {
        BlueprintEffectError::new("blueprint_repair_decision_invalid", error.message)
    })
}

fn read_repaired_generated_task(
    plan: &CompiledRunPlan,
    run_dir: &Path,
) -> Result<TaskDocument, BlueprintEffectError> {
    read_task_artifact_with_agreement(
        plan,
        "repaired_generated_task",
        run_dir,
        "blueprint_repaired_generated_task_missing",
        "blueprint_repaired_generated_task_invalid",
        "repaired_generated_task",
    )
}

fn read_repair_evaluation(
    run_dir: &Path,
) -> Result<BlueprintEvaluationDocument, BlueprintEffectError> {
    read_json_model::<BlueprintEvaluationDocument>(&run_dir.join("blueprint_evaluation.json"))
        .map_err(|error| {
            BlueprintEffectError::new(
                "blueprint_repair_context_mismatch",
                format!(
                    "failed approval evaluation cannot be verified: {}",
                    error.message
                ),
            )
        })
}

fn validate_mechanic_repair_stage_result(
    decision: &BlueprintRepairDecisionDocument,
    stage_result: &StageResultEnvelope,
) -> Result<(), BlueprintEffectError> {
    ensure_repair_context_equal(
        "stage_kind_id",
        &stage_result.stage_kind_id,
        "mechanic_blueprint",
    )?;
    ensure_repair_context_equal(
        "terminal_result",
        stage_result.terminal_result.as_str(),
        "MECHANIC_BLUEPRINT_COMPLETE",
    )?;
    ensure_repair_context_equal(
        "work_item_family_id",
        stage_result.work_item_kind.as_str(),
        &decision.work_item_family_id,
    )?;
    ensure_repair_context_equal(
        "work_item_id",
        &stage_result.work_item_id,
        &decision.work_item_id,
    )
}

fn failed_runtime_effect_context_for_decision(
    run_dir: &Path,
    decision: &BlueprintRepairDecisionDocument,
) -> Result<StageResultEnvelope, BlueprintEffectError> {
    let stage_results_dir = run_dir.join("stage_results");
    let entries = fs::read_dir(&stage_results_dir).map_err(|error| {
        BlueprintEffectError::new(
            "blueprint_repair_context_mismatch",
            format!(
                "failed runtime-effect stage results cannot be read from {}: {error}",
                stage_results_dir.display()
            ),
        )
    })?;
    for entry in entries {
        let Ok(entry) = entry else {
            continue;
        };
        let path = entry.path();
        if path.extension().and_then(|value| value.to_str()) != Some("json") {
            continue;
        }
        let Ok(raw) = fs::read_to_string(&path) else {
            continue;
        };
        let Ok(stage_result) = StageResultEnvelope::from_json_str(&raw) else {
            continue;
        };
        if stage_result_matches_repair_decision(&stage_result, decision) {
            return Ok(stage_result);
        }
    }
    Err(BlueprintEffectError::new(
        "blueprint_repair_context_mismatch",
        "no failed runtime-effect stage result matches blueprint_repair_decision.json",
    ))
}

fn stage_result_matches_repair_decision(
    stage_result: &StageResultEnvelope,
    decision: &BlueprintRepairDecisionDocument,
) -> bool {
    stage_result.run_id == decision.failed_run_id
        && stage_result.stage_kind_id == decision.failed_stage_kind_id
        && stage_result.node_id == decision.failed_node_id
        && stage_result.terminal_result.as_str() == decision.failed_terminal_result
        && stage_result.work_item_kind.as_str() == decision.work_item_family_id
        && stage_result.work_item_id == decision.work_item_id
        && metadata_string(stage_result, "runtime_effect_handler_id")
            == Some(decision.failed_handler_id.as_str())
        && metadata_string(stage_result, "runtime_effect_decision") == Some("request_block_source")
        && metadata_string(stage_result, "runtime_effect_failure_class")
            == Some(decision.failure_class.as_str())
        && metadata_string(stage_result, "runtime_effect_mutation_phase")
            == Some(repair_mutation_phase_str(decision.mutation_phase))
}

fn validate_repair_decision_matches_draft(
    decision: &BlueprintRepairDecisionDocument,
    draft: &BlueprintDraftDocument,
) -> Result<(), BlueprintEffectError> {
    ensure_repair_context_equal("work_item_id", &decision.work_item_id, &draft.draft_id)?;
    ensure_repair_context_equal("draft_id", &decision.draft_id, &draft.draft_id)?;
    ensure_repair_context_equal("manifest_id", &decision.manifest_id, &draft.manifest_id)?;
    ensure_repair_context_equal("root_spec_id", &decision.root_spec_id, &draft.root_spec_id)?;
    ensure_repair_context_equal("root_idea_id", &decision.root_idea_id, &draft.root_idea_id)
}

fn validate_repair_decision_matches_approval_context(
    decision: &BlueprintRepairDecisionDocument,
    packet: &BlueprintPacketDocument,
    evaluation: &BlueprintEvaluationDocument,
) -> Result<(), BlueprintEffectError> {
    decision
        .ensure_matches_packet(packet)
        .and_then(|()| decision.ensure_matches_evaluation(evaluation))
        .map_err(|error| {
            BlueprintEffectError::new("blueprint_repair_context_mismatch", error.to_string())
        })
}

fn validate_repaired_generated_task(
    task: &TaskDocument,
    decision: &BlueprintRepairDecisionDocument,
    draft: &BlueprintDraftDocument,
    packet: &BlueprintPacketDocument,
) -> Result<(), BlueprintEffectError> {
    decision
        .ensure_matches_repaired_generated_task(task)
        .map_err(|error| {
            BlueprintEffectError::new(
                "blueprint_repaired_generated_task_invalid",
                error.to_string(),
            )
        })?;
    validate_generated_task(task, draft, packet).map_err(|error| {
        BlueprintEffectError::new("blueprint_repaired_generated_task_invalid", error.message)
    })
}

fn ensure_repair_context_equal(
    field_name: &'static str,
    actual: &str,
    expected: &str,
) -> Result<(), BlueprintEffectError> {
    if actual == expected {
        Ok(())
    } else {
        Err(BlueprintEffectError::new(
            "blueprint_repair_context_mismatch",
            format!("{field_name} mismatch"),
        ))
    }
}

#[allow(clippy::too_many_arguments)]
fn promote_approved_blueprint_task(
    paths: &WorkspacePaths,
    handler_id: &str,
    draft: &BlueprintDraftDocument,
    source_state: BlueprintApprovalSourceState,
    packet: &BlueprintPacketDocument,
    evaluation: &BlueprintEvaluationDocument,
    task: &mut TaskDocument,
    run_dir: &Path,
    created_paths: &mut Vec<String>,
) -> Result<RuntimeEffectResult, BlueprintEffectError> {
    let evaluation_path = blueprint_evaluation_path(paths, &evaluation.evaluation_id);
    let approved_packet_path = blueprint_packet_path(paths, &packet.blueprint_id, "approved");
    let expected_markdown_content =
        expected_approved_markdown_content(paths, &packet.blueprint_id, run_dir)?;
    let approved_markdown_exists = approved_markdown_exists_equivalent(
        paths,
        &packet.blueprint_id,
        expected_markdown_content.as_deref(),
    )?;
    if !approved_markdown_exists && !candidate_markdown_path(paths, &packet.blueprint_id).exists() {
        return Err(BlueprintEffectError::new(
            "blueprint_approved_markdown_conflict",
            format!(
                "approved markdown {} cannot be verified or reconstructed",
                packet.blueprint_id
            ),
        ));
    }

    task_with_blueprint_refs(task, paths, &approved_packet_path, &evaluation_path);
    let task_path = paths.tasks_queue_dir.join(format!("{}.md", task.task_id));
    let promotion = promotion_record_for_approval(BlueprintPromotionApprovalInput {
        paths,
        draft,
        packet,
        evaluation,
        task,
        task_path: &task_path,
        approved_packet_path: &approved_packet_path,
        evaluation_path: &evaluation_path,
    });

    let evaluation_exists = blueprint_evaluation_exists_equivalent(paths, evaluation)?;
    let approved_packet_exists = approved_packet_exists_equivalent(paths, packet)?;
    let task_exists = generated_task_exists_equivalent(paths, task)?;
    let promotion_exists = promotion_exists_equivalent(paths, &promotion)?;

    if !evaluation_exists {
        let path = persist_blueprint_evaluation(paths, evaluation)?;
        created_paths.push(effect_path(paths, &path));
    }
    if !approved_packet_exists {
        let path = move_candidate_blueprint_packet(paths, &packet.blueprint_id, "approved")?;
        created_paths.push(effect_path(paths, &path));
    }
    if !approved_markdown_exists {
        if let Some(path) = move_candidate_markdown(paths, &packet.blueprint_id, "approved")? {
            created_paths.push(effect_path(paths, &path));
        }
        if !approved_markdown_exists_equivalent(
            paths,
            &packet.blueprint_id,
            expected_markdown_content.as_deref(),
        )? {
            return Err(BlueprintEffectError::new(
                "blueprint_approved_markdown_conflict",
                format!(
                    "approved markdown {} cannot be verified or reconstructed",
                    packet.blueprint_id
                ),
            ));
        }
    }
    if !task_exists {
        let path = enqueue_task(paths, task)?;
        created_paths.push(effect_path(paths, &path));
    }
    if !promotion_exists {
        let path = persist_blueprint_promotion(paths, &promotion)?;
        created_paths.push(effect_path(paths, &path));
    }

    Ok(RuntimeEffectResult {
        handler_id: Some(handler_id.to_owned()),
        decision: RuntimeEffectDecision::RequestCompleteSource,
        created_paths: created_paths.clone(),
        source_lifecycle_intent: (source_state == BlueprintApprovalSourceState::Active).then(
            || {
                SourceLifecycleIntent::for_builtin(
                    "approve_blueprint_draft_after_effect",
                    SourceLifecycleAction::Complete,
                    WorkItemKind::BlueprintDraft,
                    draft.draft_id.clone(),
                )
            },
        ),
        failure_class: None,
        message: Some(format!(
            "promoted blueprint {} to task {}",
            packet.blueprint_id, task.task_id
        )),
        mutation_phase: RuntimeEffectMutationPhase::Unknown,
    })
}

fn blueprint_evaluation_path(paths: &WorkspacePaths, evaluation_id: &str) -> PathBuf {
    paths
        .runtime_root
        .join("blueprints/evaluations")
        .join(format!("{evaluation_id}.json"))
}

fn blueprint_promotion_path(paths: &WorkspacePaths, promotion_id: &str) -> PathBuf {
    paths
        .runtime_root
        .join("blueprints/promotions")
        .join(format!("{promotion_id}.json"))
}

fn blueprint_packet_path(
    paths: &WorkspacePaths,
    blueprint_id: &str,
    packet_state: &str,
) -> PathBuf {
    paths
        .runtime_root
        .join("blueprints/packets")
        .join(packet_state)
        .join(format!("{blueprint_id}.json"))
}

fn approved_markdown_path(paths: &WorkspacePaths, blueprint_id: &str) -> PathBuf {
    paths
        .runtime_root
        .join("blueprints/packets/approved")
        .join(format!("{blueprint_id}.md"))
}

fn blueprint_evaluation_exists_equivalent(
    paths: &WorkspacePaths,
    evaluation: &BlueprintEvaluationDocument,
) -> Result<bool, BlueprintEffectError> {
    let path = blueprint_evaluation_path(paths, &evaluation.evaluation_id);
    if !path.exists() {
        return Ok(false);
    }
    let existing = read_json_model::<BlueprintEvaluationDocument>(&path).map_err(|error| {
        BlueprintEffectError::new(
            "blueprint_evaluation_duplicate_conflict",
            format!(
                "existing evaluation {} cannot be validated: {}",
                evaluation.evaluation_id, error.message
            ),
        )
    })?;
    if normalized_json(&existing)? != normalized_json(evaluation)? {
        return Err(BlueprintEffectError::new(
            "blueprint_evaluation_duplicate_conflict",
            format!(
                "blueprint_evaluation_duplicate_conflict: evaluation_id={}",
                evaluation.evaluation_id
            ),
        ));
    }
    Ok(true)
}

fn approved_packet_exists_equivalent(
    paths: &WorkspacePaths,
    packet: &BlueprintPacketDocument,
) -> Result<bool, BlueprintEffectError> {
    let path = blueprint_packet_path(paths, &packet.blueprint_id, "approved");
    if !path.exists() {
        return Ok(false);
    }
    let existing = read_json_model::<BlueprintPacketDocument>(&path).map_err(|error| {
        BlueprintEffectError::new(
            "blueprint_approved_packet_conflict",
            format!(
                "existing approved packet {} cannot be validated: {}",
                packet.blueprint_id, error.message
            ),
        )
    })?;
    if normalized_json(&existing)? != normalized_json(packet)? {
        return Err(BlueprintEffectError::new(
            "blueprint_approved_packet_conflict",
            format!(
                "blueprint_approved_packet_conflict: blueprint_id={}",
                packet.blueprint_id
            ),
        ));
    }
    Ok(true)
}

fn approved_markdown_exists_equivalent(
    paths: &WorkspacePaths,
    blueprint_id: &str,
    expected_content: Option<&str>,
) -> Result<bool, BlueprintEffectError> {
    let destination = approved_markdown_path(paths, blueprint_id);
    if !destination.exists() {
        return Ok(false);
    }
    let Some(expected_content) = expected_content else {
        return Err(BlueprintEffectError::new(
            "blueprint_approved_markdown_conflict",
            format!(
                "approved markdown {blueprint_id} cannot be verified without an expected source"
            ),
        ));
    };
    let destination_content = fs::read_to_string(&destination).map_err(|error| {
        BlueprintEffectError::new(
            "blueprint_approved_markdown_conflict",
            format!("existing approved markdown {blueprint_id} cannot be validated: {error}"),
        )
    })?;
    if normalized_markdown_content(&destination_content)
        != normalized_markdown_content(expected_content)
    {
        return Err(BlueprintEffectError::new(
            "blueprint_approved_markdown_conflict",
            format!("blueprint_approved_markdown_conflict: blueprint_id={blueprint_id}"),
        ));
    }
    Ok(true)
}

fn expected_approved_markdown_content(
    paths: &WorkspacePaths,
    blueprint_id: &str,
    run_dir: &Path,
) -> Result<Option<String>, BlueprintEffectError> {
    for source in [
        candidate_markdown_path(paths, blueprint_id),
        run_dir.join("blueprint.md"),
    ] {
        if !source.exists() {
            continue;
        }
        let content = fs::read_to_string(&source).map_err(|error| {
            BlueprintEffectError::new(
                "blueprint_approved_markdown_conflict",
                format!("expected approved markdown {blueprint_id} cannot be read: {error}"),
            )
        })?;
        return Ok(Some(content));
    }
    Ok(None)
}

fn generated_task_exists_equivalent(
    paths: &WorkspacePaths,
    task: &TaskDocument,
) -> Result<bool, BlueprintEffectError> {
    let filename = format!("{}.md", task.task_id);
    let mut entries = Vec::new();
    for directory in [
        &paths.tasks_queue_dir,
        &paths.tasks_active_dir,
        &paths.tasks_done_dir,
        &paths.tasks_blocked_dir,
    ] {
        let path = directory.join(&filename);
        if !path.exists() {
            continue;
        }
        let raw = fs::read_to_string(&path).map_err(|error| {
            BlueprintEffectError::new(
                "blueprint_task_duplicate",
                format!(
                    "existing generated task {} cannot be read: {error}",
                    task.task_id
                ),
            )
        })?;
        let existing = parse_task_document_with_source(&raw, &path.display().to_string()).map_err(
            |error| {
                BlueprintEffectError::new(
                    "blueprint_task_duplicate",
                    format!(
                        "existing generated task {} cannot be validated: {error}",
                        task.task_id
                    ),
                )
            },
        )?;
        entries.push((path, existing));
    }
    match entries.as_slice() {
        [] => Ok(false),
        [(_path, existing)] => {
            if normalized_json(existing)? != normalized_json(task)? {
                return Err(BlueprintEffectError::new(
                    "blueprint_task_duplicate",
                    format!("blueprint_task_duplicate: task_id={}", task.task_id),
                ));
            }
            Ok(true)
        }
        entries => {
            let locations = entries
                .iter()
                .map(|(path, _task)| path.to_string_lossy().replace('\\', "/"))
                .collect::<Vec<_>>()
                .join(", ");
            Err(BlueprintEffectError::new(
                "blueprint_task_duplicate",
                format!(
                    "blueprint_task_duplicate: task_id={} in {locations}",
                    task.task_id
                ),
            ))
        }
    }
}

struct BlueprintPromotionApprovalInput<'a> {
    paths: &'a WorkspacePaths,
    draft: &'a BlueprintDraftDocument,
    packet: &'a BlueprintPacketDocument,
    evaluation: &'a BlueprintEvaluationDocument,
    task: &'a TaskDocument,
    task_path: &'a Path,
    approved_packet_path: &'a Path,
    evaluation_path: &'a Path,
}

fn promotion_record_for_approval(
    input: BlueprintPromotionApprovalInput<'_>,
) -> BlueprintPromotionRecord {
    BlueprintPromotionRecord {
        schema_version: "1.0".to_owned(),
        kind: "blueprint_promotion".to_owned(),
        promotion_id: promotion_id(&input.evaluation.evaluation_id),
        blueprint_id: input.packet.blueprint_id.clone(),
        evaluation_id: input.evaluation.evaluation_id.clone(),
        draft_id: input.draft.draft_id.clone(),
        manifest_id: input.draft.manifest_id.clone(),
        root_spec_id: input.draft.root_spec_id.clone(),
        root_idea_id: input.draft.root_idea_id.clone(),
        generated_task_id: input.task.task_id.clone(),
        generated_task_path: effect_path(input.paths, input.task_path),
        approved_blueprint_path: effect_path(input.paths, input.approved_packet_path),
        evaluation_path: effect_path(input.paths, input.evaluation_path),
        promoted_at: input.evaluation.created_at.clone(),
        promoted_by: "runtime".to_owned(),
    }
}

fn promotion_exists_equivalent(
    paths: &WorkspacePaths,
    promotion: &BlueprintPromotionRecord,
) -> Result<bool, BlueprintEffectError> {
    let path = blueprint_promotion_path(paths, &promotion.promotion_id);
    if !path.exists() {
        return Ok(false);
    }
    let existing = read_json_model::<BlueprintPromotionRecord>(&path).map_err(|error| {
        BlueprintEffectError::new(
            "blueprint_promotion_duplicate_conflict",
            format!(
                "existing promotion {} cannot be validated: {}",
                promotion.promotion_id, error.message
            ),
        )
    })?;
    if normalized_json(&existing)? != normalized_json(promotion)? {
        return Err(BlueprintEffectError::new(
            "blueprint_promotion_duplicate_conflict",
            format!(
                "blueprint_promotion_duplicate_conflict: promotion_id={}",
                promotion.promotion_id
            ),
        ));
    }
    Ok(true)
}

fn task_with_blueprint_refs(
    task: &mut TaskDocument,
    paths: &WorkspacePaths,
    approved_blueprint_path: &Path,
    evaluation_path: &Path,
) {
    add_unique_ref(
        &mut task.references,
        effect_path(paths, approved_blueprint_path),
    );
    add_unique_ref(&mut task.references, effect_path(paths, evaluation_path));
}

fn metadata_string<'a>(stage_result: &'a StageResultEnvelope, key: &str) -> Option<&'a str> {
    stage_result.metadata.get(key).and_then(Value::as_str)
}

fn repair_mutation_phase_str(phase: BlueprintRepairMutationPhase) -> &'static str {
    match phase {
        BlueprintRepairMutationPhase::PreMutation => "pre_mutation",
        BlueprintRepairMutationPhase::PartialMutation => "partial_mutation",
        BlueprintRepairMutationPhase::Unknown => "unknown",
    }
}

fn ensure_contains_all(
    actual: &[String],
    expected: &[String],
    field_name: &str,
) -> Result<(), BlueprintEffectError> {
    let missing = expected
        .iter()
        .filter(|item| !actual.contains(item))
        .cloned()
        .collect::<Vec<_>>();
    if missing.is_empty() {
        Ok(())
    } else {
        Err(BlueprintEffectError::new(
            "generated_task_invalid",
            format!(
                "{field_name} missing Blueprint item(s): {}",
                missing.join(", ")
            ),
        ))
    }
}

fn move_candidate_markdown(
    paths: &WorkspacePaths,
    blueprint_id: &str,
    target_state: &str,
) -> Result<Option<PathBuf>, QueueStoreError> {
    let source = paths
        .runtime_root
        .join("blueprints/packets/candidates")
        .join(format!("{blueprint_id}.md"));
    if !source.exists() {
        return Ok(None);
    }
    let destination = paths
        .runtime_root
        .join("blueprints/packets")
        .join(target_state)
        .join(format!("{blueprint_id}.md"));
    if destination.exists() {
        return Err(QueueStoreError::InvalidState {
            message: format!("blueprint markdown {blueprint_id} already exists at destination"),
        });
    }
    if let Some(parent) = destination.parent() {
        fs::create_dir_all(parent).map_err(|error| QueueStoreError::Io {
            path: parent.to_path_buf(),
            message: error.to_string(),
        })?;
    }
    fs::rename(&source, &destination).map_err(|error| QueueStoreError::Io {
        path: source.clone(),
        message: error.to_string(),
    })?;
    Ok(Some(destination))
}

fn blueprint_failure(
    handler_id: &str,
    stage_result: &StageResultEnvelope,
    failure_class: &str,
    message: String,
    created_paths: Vec<String>,
) -> RuntimeEffectResult {
    RuntimeEffectResult {
        handler_id: Some(handler_id.to_owned()),
        decision: RuntimeEffectDecision::RequestBlockSource,
        created_paths: created_paths.clone(),
        source_lifecycle_intent: Some(source_lifecycle_intent_for_effect(
            stage_result,
            block_lifecycle_plan_id(stage_result.work_item_kind),
            SourceLifecycleAction::Block,
        )),
        failure_class: Some(failure_class.to_owned()),
        message: Some(message),
        mutation_phase: if created_paths.is_empty() {
            RuntimeEffectMutationPhase::PreMutation
        } else {
            RuntimeEffectMutationPhase::PartialMutation
        },
    }
}

fn approval_failure_class(error: &BlueprintEffectError, created_paths: &[String]) -> &'static str {
    if !created_paths.is_empty() {
        return "blueprint_partial_mutation";
    }
    match error.class.as_str() {
        "generated_task_missing" => "generated_task_missing",
        "generated_task_invalid" => "generated_task_invalid",
        "blueprint_task_duplicate" => "blueprint_task_duplicate",
        "blueprint_evaluation_duplicate_conflict" => "blueprint_evaluation_duplicate_conflict",
        "blueprint_approved_packet_conflict" => "blueprint_approved_packet_conflict",
        "blueprint_approved_markdown_conflict" => "blueprint_approved_markdown_conflict",
        "blueprint_promotion_duplicate_conflict" => "blueprint_promotion_duplicate_conflict",
        _ if error.message.contains("generated task") => "generated_task_invalid",
        _ => "blueprint_evaluation_invalid",
    }
}

fn complete_lifecycle_plan_id(kind: WorkItemKind) -> &'static str {
    match kind {
        WorkItemKind::Spec => "complete_spec_source_after_blueprint_effect",
        WorkItemKind::Incident => "complete_incident_source_after_blueprint_effect",
        WorkItemKind::BlueprintDraft => "approve_blueprint_draft_after_effect",
        _ => "complete_source_after_effect",
    }
}

fn block_lifecycle_plan_id(kind: WorkItemKind) -> &'static str {
    match kind {
        WorkItemKind::Spec => "block_spec_source_after_blueprint_effect",
        WorkItemKind::Incident => "block_incident_source_after_blueprint_effect",
        WorkItemKind::BlueprintDraft => "block_blueprint_draft_after_effect",
        _ => "block_source_after_effect",
    }
}

struct ResolvedRunArtifact {
    path: PathBuf,
    format: ArtifactFormat,
}

struct ExistingRunArtifact {
    filename: String,
    path: PathBuf,
    format: ArtifactFormat,
}

fn read_task_artifact_with_agreement(
    plan: &CompiledRunPlan,
    artifact_id: &str,
    run_dir: &Path,
    missing_class: &str,
    invalid_class: &str,
    label: &str,
) -> Result<TaskDocument, BlueprintEffectError> {
    let contract = artifact_contract(plan, artifact_id)
        .map_err(|error| BlueprintEffectError::new(invalid_class, error.message))?;
    let existing = existing_run_artifacts(contract, run_dir)
        .map_err(|error| BlueprintEffectError::new(invalid_class, error.message))?;
    if existing.is_empty() {
        return Err(BlueprintEffectError::new(
            missing_class,
            format!(
                "no declared run artifact found in {}; expected {}",
                run_dir.display(),
                artifact_filenames(contract).join(", ")
            ),
        ));
    }

    let checks_json_markdown_agreement = existing
        .iter()
        .any(|artifact| artifact.format == ArtifactFormat::Json)
        && existing
            .iter()
            .any(|artifact| artifact.format == ArtifactFormat::Markdown);
    let mut parsed = Vec::new();
    for artifact in existing {
        let task = parse_task_artifact(
            &artifact,
            invalid_class,
            label,
            checks_json_markdown_agreement,
        )?;
        parsed.push((artifact, task));
    }

    if checks_json_markdown_agreement {
        let expected = normalized_json(&parsed[0].1)
            .map_err(|error| BlueprintEffectError::new(invalid_class, error.message))?;
        for (artifact, task) in parsed.iter().skip(1) {
            let actual = normalized_json(task)
                .map_err(|error| BlueprintEffectError::new(invalid_class, error.message))?;
            if actual != expected {
                return Err(BlueprintEffectError::new(
                    invalid_class,
                    format!(
                        "{label} JSON/markdown disagreement between {} and {}",
                        parsed[0].0.filename, artifact.filename
                    ),
                ));
            }
        }
    }

    Ok(parsed.remove(0).1)
}

fn parse_task_artifact(
    artifact: &ExistingRunArtifact,
    invalid_class: &str,
    label: &str,
    checks_json_markdown_agreement: bool,
) -> Result<TaskDocument, BlueprintEffectError> {
    let raw = fs::read_to_string(&artifact.path)
        .map_err(|error| BlueprintEffectError::new(invalid_class, error.to_string()))?;
    let source_name = artifact.path.display().to_string();
    let parsed = match artifact.format {
        ArtifactFormat::Json => parse_task_json_import_with_source(&raw, &source_name),
        ArtifactFormat::Markdown => parse_task_document_with_source(&raw, &source_name),
        _ => {
            return Err(BlueprintEffectError::new(
                invalid_class,
                format!("{label} must be JSON or markdown"),
            ));
        }
    };
    parsed.map_err(|error| {
        let message = if checks_json_markdown_agreement {
            format!(
                "{label} JSON/markdown disagreement: {} could not be parsed: {error}",
                artifact.filename
            )
        } else {
            error.to_string()
        };
        BlueprintEffectError::new(invalid_class, message)
    })
}

fn resolve_run_artifact(
    plan: &CompiledRunPlan,
    artifact_id: &str,
    run_dir: &Path,
) -> Result<ResolvedRunArtifact, BlueprintEffectError> {
    let contract = artifact_contract(plan, artifact_id)?;
    let canonical = run_dir.join(&contract.canonical_filename);
    if canonical.exists() {
        return Ok(ResolvedRunArtifact {
            path: canonical,
            format: adapter_format(contract, &contract.canonical_filename)?,
        });
    }
    for filename in &contract.accepted_filenames {
        let candidate = run_dir.join(filename);
        if candidate.exists() {
            return Ok(ResolvedRunArtifact {
                path: candidate,
                format: adapter_format(contract, filename)?,
            });
        }
    }
    let class = if artifact_id == "generated_task" {
        "generated_task_missing"
    } else {
        "artifact_missing"
    };
    Err(BlueprintEffectError::new(
        class,
        format!(
            "no declared run artifact found in {}; expected {}",
            run_dir.display(),
            artifact_filenames(contract).join(", ")
        ),
    ))
}

fn artifact_contract<'a>(
    plan: &'a CompiledRunPlan,
    artifact_id: &str,
) -> Result<&'a crate::contracts::ArtifactContractDefinition, BlueprintEffectError> {
    plan.workflow_primitives
        .artifact_contracts
        .iter()
        .find(|contract| contract.artifact_id == artifact_id)
        .ok_or_else(|| {
            BlueprintEffectError::new(
                format!("{artifact_id}_invalid"),
                "compiled plan does not declare artifact contract",
            )
        })
}

fn existing_run_artifacts(
    contract: &crate::contracts::ArtifactContractDefinition,
    run_dir: &Path,
) -> Result<Vec<ExistingRunArtifact>, BlueprintEffectError> {
    artifact_filenames(contract)
        .into_iter()
        .filter_map(|filename| {
            let path = run_dir.join(&filename);
            path.exists().then_some((filename, path))
        })
        .map(|(filename, path)| {
            let format = adapter_format(contract, &filename)?;
            Ok(ExistingRunArtifact {
                filename,
                path,
                format,
            })
        })
        .collect()
}

fn adapter_format(
    contract: &crate::contracts::ArtifactContractDefinition,
    filename: &str,
) -> Result<ArtifactFormat, BlueprintEffectError> {
    contract
        .filename_adapters
        .iter()
        .find(|adapter| adapter.filename == filename)
        .map(|adapter| adapter.format)
        .ok_or_else(|| {
            BlueprintEffectError::new(
                "artifact_filename_unsupported",
                format!("filename {filename} is not declared by contract"),
            )
        })
}

fn artifact_filenames(contract: &crate::contracts::ArtifactContractDefinition) -> Vec<String> {
    let mut filenames = vec![contract.canonical_filename.clone()];
    filenames.extend(contract.accepted_filenames.clone());
    filenames
}

fn copy_unique_file(source: &Path, destination: &Path) -> io::Result<()> {
    if destination.exists() {
        return Err(io::Error::new(
            io::ErrorKind::AlreadyExists,
            format!(
                "Blueprint artifact already exists: {}",
                destination.display()
            ),
        ));
    }
    if let Some(parent) = destination.parent() {
        fs::create_dir_all(parent)?;
    }
    let temp = destination.with_file_name(format!(
        ".{}.tmp",
        destination
            .file_name()
            .and_then(|value| value.to_str())
            .unwrap_or("blueprint")
    ));
    fs::copy(source, &temp)?;
    fs::rename(temp, destination)
}

fn add_unique_ref(references: &mut Vec<String>, reference: String) {
    if !references.contains(&reference) {
        references.push(reference);
    }
}

fn effect_path(paths: &WorkspacePaths, path: &Path) -> String {
    path.strip_prefix(&paths.root)
        .unwrap_or(path)
        .to_string_lossy()
        .replace('\\', "/")
}

fn path_name(path: &Path) -> String {
    path.file_name()
        .and_then(|value| value.to_str())
        .unwrap_or("<artifact>")
        .to_owned()
}

fn promotion_id(evaluation_id: &str) -> String {
    format!("promotion-{evaluation_id}")
}

fn normalized_json<T>(document: &T) -> Result<String, BlueprintEffectError>
where
    T: Serialize,
{
    serde_json::to_string(
        &serde_json::to_value(document)
            .map_err(|error| BlueprintEffectError::new("json_model_parse", error.to_string()))?,
    )
    .map_err(|error| BlueprintEffectError::new("json_model_parse", error.to_string()))
}

fn normalized_draft_identity(
    draft: &BlueprintDraftDocument,
) -> Result<String, BlueprintEffectError> {
    let mut value = serde_json::to_value(draft)
        .map_err(|error| BlueprintEffectError::new("json_model_parse", error.to_string()))?;
    if let Value::Object(map) = &mut value {
        for key in [
            "status",
            "current_revision",
            "latest_blueprint_id",
            "latest_critique_id",
            "updated_at",
        ] {
            map.remove(key);
        }
    }
    serde_json::to_string(&value)
        .map_err(|error| BlueprintEffectError::new("json_model_parse", error.to_string()))
}
