use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::ExitCode;

use anyhow::{bail, Context, Result};
use serde::Serialize;
use tracing::debug;

use crate::memory::entries::StructuredMemoryEntry;
use crate::memory::file::{append_block_to_contents, render_entry_block, rewrite_entry_blocks};
use crate::memory::{governance, queue};
use crate::output::CommandReport;
use crate::paths::state::StateLayout;
use crate::profile;
use crate::repo::marker as repo_marker;
use crate::repo::registry as repo_registry;
use crate::state::protected_write::ExclusiveWriteOptions;
use crate::state::runtime::{self as runtime_state, RuntimeMemoryEntry};
use crate::state::session;
use crate::timestamps;

const PROMOTABLE_TYPES: &[&str] = &["rule", "constraint", "heuristic", "attempt"];

pub(crate) fn is_promotable_entry_type(entry_type: &str) -> bool {
    PROMOTABLE_TYPES.contains(&entry_type)
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PromoteDestination {
    BranchMemory,
    RepoMemory,
    ProfileMemory,
    ProjectTruth { target_file: PathBuf },
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SourceOutcome {
    Active,
    Superseded,
    LinkOnly,
}

impl SourceOutcome {
    fn action(self) -> &'static str {
        match self {
            Self::Active => "active",
            Self::Superseded => "superseded",
            Self::LinkOnly => "link_only",
        }
    }

    fn resulting_source_state(self, current_state: &str) -> String {
        match self {
            Self::Active => "active".to_owned(),
            Self::Superseded => "superseded".to_owned(),
            Self::LinkOnly => current_state.to_owned(),
        }
    }
}

#[derive(Serialize)]
pub struct MemoryPromoteReport {
    command: &'static str,
    ok: bool,
    path: String,
    profile: String,
    project_id: String,
    locality_id: String,
    mode: &'static str,
    source: MemorySurfaceView,
    destination: MemorySurfaceView,
    selected_entry: StructuredMemoryEntry,
    destination_preview: DestinationPreviewView,
    source_outcome: SourceOutcomeView,
    governance: governance::GovernanceDecisionView,
    #[serde(skip_serializing_if = "Option::is_none")]
    write_result: Option<WriteResultView>,
    #[serde(skip_serializing_if = "Option::is_none")]
    staged_write: Option<queue::StagedMemoryOpView>,
    #[serde(skip_serializing_if = "Option::is_none")]
    message: Option<String>,
    warnings: Vec<String>,
}

#[derive(Serialize)]
struct MemorySurfaceView {
    scope: &'static str,
    path: String,
    status: &'static str,
}

#[derive(Serialize)]
struct DestinationPreviewView {
    append_mode: &'static str,
    file_status: &'static str,
    provenance_link: String,
    entry_block: String,
    note: String,
}

#[derive(Serialize)]
struct SourceOutcomeView {
    action: &'static str,
    current_state: String,
    resulting_state: String,
    linkage_target: String,
    note: &'static str,
}

#[derive(Serialize)]
struct WriteResultView {
    destination_action: &'static str,
    destination_path: String,
    source_action: &'static str,
    source_path: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    written_destination_entry: Option<StructuredMemoryEntry>,
    #[serde(skip_serializing_if = "Option::is_none")]
    written_source_entry: Option<StructuredMemoryEntry>,
}

impl CommandReport for MemoryPromoteReport {
    fn exit_code(&self) -> ExitCode {
        if self.ok {
            ExitCode::SUCCESS
        } else {
            ExitCode::from(1)
        }
    }

    fn render_text(&self) {
        if !self.ok {
            println!(
                "{}",
                self.message
                    .as_deref()
                    .unwrap_or("Queued memory promotion was rejected.")
            );
            return;
        }

        let source_scope = self.source.scope.replace('_', " ");
        let destination_scope = self.destination.scope.replace('_', " ");
        match &self.write_result {
            Some(_) => println!(
                "Applied memory promotion for entry {}.",
                self.selected_entry.id
            ),
            None => println!(
                "Prepared memory promotion preview for entry {}.",
                self.selected_entry.id
            ),
        }
        println!("Mode: {}", self.mode);
        println!(
            "Source: {} ({}, {})",
            self.source.path, self.source.status, source_scope
        );
        println!(
            "Destination: {} ({}, {})",
            self.destination.path, self.destination.status, destination_scope
        );
        println!("Entry type: {}", self.selected_entry.entry_type);
        println!("Current state: {}", self.selected_entry.state);
        println!(
            "Source outcome: {} ({})",
            self.source_outcome.action, self.source_outcome.note
        );
        println!(
            "Destination provenance: {}",
            self.destination_preview.provenance_link
        );
        println!(
            "Governance: {} ({})",
            self.governance.action.as_str(),
            self.governance.status.as_str()
        );

        if let Some(write_result) = &self.write_result {
            println!(
                "Destination write: {} ({})",
                write_result.destination_action, write_result.destination_path
            );
            println!(
                "Source write: {} ({})",
                write_result.source_action, write_result.source_path
            );
        }
        if let Some(staged_write) = &self.staged_write {
            println!("Queued op: {}", staged_write.op_id);
        }

        for warning in &self.warnings {
            println!("Warning: {warning}");
        }

        println!();
        println!("Destination block:");
        println!("{}", self.destination_preview.entry_block);
        println!();
        println!("{}", self.destination_preview.note);
    }
}

#[allow(clippy::too_many_arguments)]
pub fn run(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    entry_id: &str,
    write: bool,
    requested_source_outcome: Option<SourceOutcome>,
    destination: PromoteDestination,
    write_options: ExclusiveWriteOptions,
    op_id_hint: Option<&str>,
) -> Result<MemoryPromoteReport> {
    let profile = profile::resolve(explicit_profile)?;
    let layout = StateLayout::resolve(repo_root, profile.clone())?;
    ensure_profile_exists(&layout)?;

    let marker = repo_marker::load(repo_root)?.ok_or_else(|| {
        anyhow::anyhow!(
            "repo is not linked: {} is missing; run `ccd attach --path {}` or `ccd link --path {}` first",
            repo_root.join(repo_marker::MARKER_FILE).display(),
            repo_root.display(),
            repo_root.display()
        )
    })?;
    let locality_id = marker.locality_id;
    ensure_repo_registry_exists(&layout, repo_root, &locality_id)?;
    ensure_repo_overlay_exists(&layout, &locality_id)?;

    let staged_request = if should_stage_via_queue(write, &destination, &layout)? {
        let request_key = PromoteQueueRequestKey {
            command: "memory-promote",
            entry_id,
            destination: destination_queue_label(&destination),
            source_outcome: requested_source_outcome.map(SourceOutcome::action),
        };
        let request_fingerprint = queue::fingerprint(&request_key)?;
        let op_id = queue::stable_op_id("promote", &request_fingerprint, op_id_hint);
        Some(QueuedWriteRequest {
            request_fingerprint,
            op_id,
        })
    } else {
        None
    };

    match destination {
        PromoteDestination::BranchMemory => {
            if runtime_state::resolve_active_branch_memory_path(repo_root, &layout, &locality_id)?
                .is_none()
            {
                bail!(
                    "work-stream memory is only available on active non-trunk named branches; switch to a feature branch before using `ccd memory promote --destination work-stream-memory`"
                );
            }

            let clone_memory = runtime_state::load_clone_memory_surface(&layout)?;
            let (selected_runtime_entry, selected_entry) = load_selected_entry(
                entry_id,
                &clone_memory,
                "workspace-memory",
                "`ccd memory promote`",
            )?;

            validate_promotable_entry(&selected_entry)?;

            run_branch_memory(
                repo_root,
                &layout,
                &locality_id,
                &clone_memory,
                profile.as_str(),
                &selected_entry,
                &selected_runtime_entry,
                write,
                requested_source_outcome,
                None,
                &write_options,
            )
        }
        PromoteDestination::RepoMemory => {
            let (source_memory, selected_runtime_entry, selected_entry, source_scope, mode_prefix) =
                load_repo_promotion_source(repo_root, &layout, &locality_id, entry_id)?;

            validate_promotable_entry(&selected_entry)?;

            run_repo_memory(
                repo_root,
                &layout,
                &locality_id,
                &source_memory,
                source_scope,
                mode_prefix,
                profile.as_str(),
                &selected_entry,
                &selected_runtime_entry,
                write,
                requested_source_outcome,
                staged_request.as_ref(),
                &write_options,
            )
        }
        PromoteDestination::ProfileMemory | PromoteDestination::ProjectTruth { .. } => {
            let repo_memory = runtime_state::load_locality_memory_surface(&layout, &locality_id)?;
            let (selected_runtime_entry, selected_entry) = load_selected_entry(
                entry_id,
                &repo_memory,
                "project-memory",
                "`ccd memory promote`",
            )?;

            validate_promotable_entry(&selected_entry)?;

            match destination {
                PromoteDestination::ProfileMemory => run_profile_memory(
                    repo_root,
                    &layout,
                    &locality_id,
                    &repo_memory,
                    profile.as_str(),
                    &selected_entry,
                    &selected_runtime_entry,
                    write,
                    requested_source_outcome,
                    staged_request.as_ref(),
                    &write_options,
                ),
                PromoteDestination::ProjectTruth { ref target_file } => run_project_truth(
                    repo_root,
                    &layout,
                    &locality_id,
                    &repo_memory,
                    profile.as_str(),
                    &selected_entry,
                    &selected_runtime_entry,
                    write,
                    requested_source_outcome,
                    target_file,
                    None,
                    &write_options,
                ),
                PromoteDestination::BranchMemory | PromoteDestination::RepoMemory => {
                    unreachable!("handled above")
                }
            }
        }
    }
}

#[derive(Clone, Debug)]
struct QueuedWriteRequest {
    request_fingerprint: String,
    op_id: String,
}

#[derive(Serialize)]
struct PromoteQueueRequestKey<'a> {
    command: &'static str,
    entry_id: &'a str,
    destination: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    source_outcome: Option<&'static str>,
}

pub(crate) fn load_selected_entry(
    entry_id: &str,
    memory: &runtime_state::LoadedRuntimeMemorySurface,
    scope_label: &str,
    command_name: &str,
) -> Result<(RuntimeMemoryEntry, StructuredMemoryEntry)> {
    if !memory
        .entries
        .iter()
        .any(|entry| entry.as_structured_entry().is_some())
    {
        bail!(
            "no structured {} entries are available at {}; add a `ccd-memory` block before running {}",
            scope_label,
            memory.source.path.display(),
            command_name
        );
    }

    let selected_runtime_entry = memory
        .entries
        .iter()
        .find(|entry| entry.structured_id() == Some(entry_id))
        .cloned()
        .ok_or_else(|| {
            let available_ids = memory
                .entries
                .iter()
                .filter_map(RuntimeMemoryEntry::structured_id)
                .collect::<Vec<_>>()
                .join(", ");
            anyhow::anyhow!(
                "structured {} entry `{}` was not found in {}; available ids: {}",
                scope_label,
                entry_id,
                memory.source.path.display(),
                available_ids
            )
        })?;
    let selected_entry = selected_runtime_entry
        .as_structured_entry()
        .expect("structured memory entry");
    Ok((selected_runtime_entry, selected_entry))
}

fn validate_promotable_entry(selected_entry: &StructuredMemoryEntry) -> Result<()> {
    if !is_promotable_entry_type(&selected_entry.entry_type) {
        bail!(
            "entry `{}` has type `{}`, which is viewable but not promotable in the first cut",
            selected_entry.id,
            selected_entry.entry_type
        );
    }
    Ok(())
}

fn load_repo_promotion_source(
    repo_root: &Path,
    layout: &StateLayout,
    locality_id: &str,
    entry_id: &str,
) -> Result<(
    runtime_state::LoadedRuntimeMemorySurface,
    RuntimeMemoryEntry,
    StructuredMemoryEntry,
    &'static str,
    &'static str,
)> {
    if runtime_state::resolve_active_branch_memory_path(repo_root, layout, locality_id)?.is_some() {
        let branch_memory =
            runtime_state::load_branch_memory_surface(repo_root, layout, locality_id)?;
        if let Some(selected_runtime_entry) = branch_memory
            .entries
            .iter()
            .find(|entry| entry.structured_id() == Some(entry_id))
            .cloned()
        {
            let selected_entry = selected_runtime_entry
                .as_structured_entry()
                .expect("branch memory structured entry");
            return Ok((
                branch_memory,
                selected_runtime_entry,
                selected_entry,
                "work_stream_memory",
                "work_stream_memory_to_project",
            ));
        }
    }

    let clone_memory = runtime_state::load_clone_memory_surface(layout)?;
    let (selected_runtime_entry, selected_entry) = load_selected_entry(
        entry_id,
        &clone_memory,
        "workspace-memory",
        "`ccd memory promote`",
    )?;
    Ok((
        clone_memory,
        selected_runtime_entry,
        selected_entry,
        "workspace_memory",
        "workspace_memory_to_project",
    ))
}

#[allow(clippy::too_many_arguments)]
fn run_profile_memory(
    repo_root: &Path,
    layout: &StateLayout,
    locality_id: &str,
    repo_memory: &runtime_state::LoadedRuntimeMemorySurface,
    profile: &str,
    selected_entry: &StructuredMemoryEntry,
    selected_runtime_entry: &RuntimeMemoryEntry,
    write: bool,
    requested_source_outcome: Option<SourceOutcome>,
    staged_request: Option<&QueuedWriteRequest>,
    write_options: &ExclusiveWriteOptions,
) -> Result<MemoryPromoteReport> {
    let profile_memory = runtime_state::load_profile_memory_surface(layout)?;
    if profile_memory
        .entries
        .iter()
        .any(|entry| entry.structured_id() == Some(selected_entry.id.as_str()))
    {
        bail!(
            "profile memory already contains entry id `{}` at {}; refusing to write or preview an unexpected overwrite",
            selected_entry.id,
            profile_memory.source.path.display()
        );
    }

    debug!(destination = "profile", path = %profile_memory.source.path.display(), write, "promoting memory entry");
    let source_outcome = resolve_source_outcome(write, requested_source_outcome)?;
    let preserves_existing_provenance = preserves_existing_source_provenance(selected_entry);
    if write && selected_entry.source_ref.is_some() && !preserves_existing_provenance {
        bail!(
            "entry `{}` already carries `source_ref`; refusing to apply a write that would discard or overwrite existing provenance in the first write slice",
            selected_entry.id
        );
    }
    let autonomous_session_id = current_autonomous_session_id(layout)?;
    let autonomous = autonomous_session_id.is_some();
    let mut governance = if selected_entry.entry_type == "attempt" {
        governance::GovernanceDecisionView::new(
            governance::GovernanceDecisionInit {
                action: governance::GovernanceAction::Escalate,
                status: governance::GovernanceStatus::Escalated,
                target_scope: "profile_memory".to_owned(),
                source_scope: Some("project_memory".to_owned()),
                entry_type: selected_entry.entry_type.clone(),
                rationale: "profile promotion needs stronger cross-project evidence than an `attempt` entry currently carries".to_owned(),
                evidence_summary: vec![
                    "attempt entries default to work-stream or project scope".to_owned(),
                    "profile memory should only absorb broader lessons with stronger reuse evidence".to_owned(),
                ],
                pressure_signals: vec![governance::signal(
                    "entry_type_scope_mismatch",
                    "attempt entries do not qualify for autonomous profile promotion by default",
                )],
                duplicate_digest: Some(governance::normalized_duplicate_digest(selected_entry)),
                rate_limit: None,
            },
        )
    } else {
        build_promote_governance(PromoteGovernanceContext {
            layout,
            source_scope: "project_memory",
            target_scope: "profile_memory",
            selected_entry,
            destination_entries: &profile_memory.entries,
            autonomous,
            session_id: autonomous_session_id.as_deref(),
            current_op_id: staged_request.map(|request| request.op_id.as_str()),
            action: governance::GovernanceAction::StageProfilePromotion,
            rationale:
                "profile-scope mutation stays on the governed promotion path until broader reuse is proven",
            evidence_summary: vec![
                format!("entry type `{}` is eligible for broad promotion", selected_entry.entry_type),
                "profile memory mutations remain reversible and reviewable".to_owned(),
            ],
            pressure_signals: Vec::new(),
        })?
    };

    let provenance_link = format!(
        "{}#{}",
        repo_memory.source.path.display(),
        selected_entry.id
    );
    let linkage_target = format!(
        "{}#{}",
        profile_memory.source.path.display(),
        selected_entry.id
    );
    let source_linkage_target = source_linkage_target(
        selected_entry,
        &linkage_target,
        preserves_existing_provenance,
    );
    let written_destination_runtime =
        promoted_destination_entry(selected_runtime_entry, &provenance_link);
    let written_source_runtime = linked_source_entry(
        selected_runtime_entry,
        source_outcome,
        source_linkage_target.clone(),
    )?;
    let written_destination_entry = written_destination_runtime
        .as_structured_entry()
        .expect("promoted destination entry");
    let written_source_entry = written_source_runtime
        .as_structured_entry()
        .expect("linked source entry");
    let destination_preview = DestinationPreviewView {
        append_mode: "append_block",
        file_status: profile_memory.source.status.as_str(),
        provenance_link: provenance_link.clone(),
        entry_block: render_entry_block(&written_destination_entry),
        note: promote_note(
            write,
            autonomous,
            &governance,
            "Applied with explicit approval. The block above was appended to profile memory.",
            "Preview only. No files were changed. Rerun with `--write --source-outcome <value>` to append this block and update the source entry.",
        ),
    };

    let mut warnings = Vec::new();
    if !write && selected_entry.source_ref.is_some() && !preserves_existing_provenance {
        warnings.push(
            "selected entry already carries `source_ref`; the current write path will refuse it until multi-hop provenance is modeled explicitly"
                .to_owned(),
        );
    }

    let next_profile_contents = write.then(|| {
        append_block_to_contents(
            &profile_memory.source.content,
            &destination_preview.entry_block,
        )
    });
    let next_repo_contents = if write {
        Some(rewrite_entry_blocks(
            &repo_memory.source.content,
            &HashMap::from([(
                selected_entry.id.clone(),
                render_entry_block(&written_source_entry),
            )]),
        )?)
    } else {
        None
    };
    let write_result = write.then(|| WriteResultView {
        destination_action: "append_block",
        destination_path: profile_memory.source.path.display().to_string(),
        source_action: "update_selected_block",
        source_path: repo_memory.source.path.display().to_string(),
        written_destination_entry: Some(written_destination_entry.clone()),
        written_source_entry: Some(written_source_entry.clone()),
    });

    let report = MemoryPromoteReport {
        command: "memory-promote",
        ok: true,
        path: repo_root.display().to_string(),
        profile: profile.to_owned(),
        project_id: locality_id.to_owned(),
        locality_id: locality_id.to_owned(),
        mode: if write {
            "project_memory_to_profile_write"
        } else {
            "project_memory_to_profile_preview"
        },
        source: MemorySurfaceView {
            scope: "project_memory",
            path: repo_memory.source.path.display().to_string(),
            status: repo_memory.source.status.as_str(),
        },
        destination: MemorySurfaceView {
            scope: "profile_memory",
            path: profile_memory.source.path.display().to_string(),
            status: profile_memory.source.status.as_str(),
        },
        selected_entry: selected_entry.clone(),
        destination_preview,
        source_outcome: SourceOutcomeView {
            action: source_outcome.action(),
            current_state: selected_entry.state.clone(),
            resulting_state: source_outcome.resulting_source_state(&selected_entry.state),
            linkage_target: source_linkage_target,
            note: source_outcome_note(
                source_outcome,
                write,
                requested_source_outcome.is_some(),
                "profile",
                preserves_existing_provenance,
            ),
        },
        governance: governance.clone(),
        write_result,
        staged_write: None,
        message: None,
        warnings,
    };

    let mut report = report;
    if write {
        let mutation_id = staged_request
            .map(|request| request.op_id.as_str())
            .unwrap_or("memory-promote-direct");
        governance = governance.with_rollback(
            mutation_id,
            write_options.actor_id.as_deref(),
            write_options
                .session_id
                .as_deref()
                .or(autonomous_session_id.as_deref()),
            vec![selected_entry.id.clone()],
            &profile_memory.source.content,
            Some(&repo_memory.source.content),
        );
        report.governance = governance.clone();
    }
    if write && governance.blocked_for_write(autonomous) {
        report.ok = false;
        report.write_result = None;
        report.message = Some(governance.blocked_write_message());
        return Ok(report);
    }

    finalize_promote_report(
        repo_root,
        layout,
        report,
        write.then(|| queue::MemoryOpPlan {
            target: queue::MemoryFileMutation {
                scope: "profile_memory".to_owned(),
                path: profile_memory.source.path.display().to_string(),
                next_contents: next_profile_contents.expect("write checked"),
            },
            source: Some(queue::MemoryFileMutation {
                scope: "project_memory".to_owned(),
                path: repo_memory.source.path.display().to_string(),
                next_contents: next_repo_contents.expect("write checked"),
            }),
            refresh_locality_id: Some(locality_id.to_owned()),
            authored_entry_ids: vec![selected_entry.id.clone()],
            governance: Some(governance),
        }),
        staged_request,
        write_options,
    )
}

#[allow(clippy::too_many_arguments)]
fn run_repo_memory(
    repo_root: &Path,
    layout: &StateLayout,
    locality_id: &str,
    source_memory: &runtime_state::LoadedRuntimeMemorySurface,
    source_scope: &'static str,
    mode_prefix: &'static str,
    profile: &str,
    selected_entry: &StructuredMemoryEntry,
    selected_runtime_entry: &RuntimeMemoryEntry,
    write: bool,
    requested_source_outcome: Option<SourceOutcome>,
    staged_request: Option<&QueuedWriteRequest>,
    write_options: &ExclusiveWriteOptions,
) -> Result<MemoryPromoteReport> {
    let repo_memory = runtime_state::load_locality_memory_surface(layout, locality_id)?;
    if repo_memory
        .entries
        .iter()
        .any(|entry| entry.structured_id() == Some(selected_entry.id.as_str()))
    {
        bail!(
            "project memory already contains entry id `{}` at {}; refusing to write or preview an unexpected overwrite",
            selected_entry.id,
            repo_memory.source.path.display()
        );
    }

    debug!(destination = "project", path = %repo_memory.source.path.display(), write, "promoting memory entry");
    let source_outcome = resolve_source_outcome(write, requested_source_outcome)?;
    let preserves_existing_provenance = preserves_existing_source_provenance(selected_entry);
    if write && selected_entry.source_ref.is_some() && !preserves_existing_provenance {
        bail!(
            "entry `{}` already carries `source_ref`; refusing to apply a write that would discard or overwrite existing provenance in the first write slice",
            selected_entry.id
        );
    }
    let autonomous_session_id = current_autonomous_session_id(layout)?;
    let autonomous = autonomous_session_id.is_some();
    let mut governance = build_promote_governance(PromoteGovernanceContext {
        layout,
        source_scope,
        target_scope: "project_memory",
        selected_entry,
        destination_entries: &repo_memory.entries,
        autonomous,
        session_id: autonomous_session_id.as_deref(),
        current_op_id: staged_request.map(|request| request.op_id.as_str()),
        action: governance::GovernanceAction::StageProjectPromotion,
        rationale: "project-scope mutation stays staged as a governed promotion candidate",
        evidence_summary: vec![
            format!(
                "entry type `{}` is eligible for project promotion",
                selected_entry.entry_type
            ),
            format!("source scope `{source_scope}` was validated"),
        ],
        pressure_signals: Vec::new(),
    })?;

    let provenance_link = format!(
        "{}#{}",
        source_memory.source.path.display(),
        selected_entry.id
    );
    let linkage_target = format!(
        "{}#{}",
        repo_memory.source.path.display(),
        selected_entry.id
    );
    let source_linkage_target = source_linkage_target(
        selected_entry,
        &linkage_target,
        preserves_existing_provenance,
    );
    let written_destination_runtime =
        promoted_destination_entry(selected_runtime_entry, &provenance_link);
    let written_source_runtime = linked_source_entry(
        selected_runtime_entry,
        source_outcome,
        source_linkage_target.clone(),
    )?;
    let written_destination_entry = written_destination_runtime
        .as_structured_entry()
        .expect("promoted destination entry");
    let written_source_entry = written_source_runtime
        .as_structured_entry()
        .expect("linked source entry");
    let destination_preview = DestinationPreviewView {
        append_mode: "append_block",
        file_status: repo_memory.source.status.as_str(),
        provenance_link: provenance_link.clone(),
        entry_block: render_entry_block(&written_destination_entry),
        note: promote_note(
            write,
            autonomous,
            &governance,
            "Applied with explicit approval. The block above was appended to project memory.",
            "Preview only. No files were changed. Rerun with `--write --source-outcome <value>` to append this block and update the source entry.",
        ),
    };

    let mut warnings = Vec::new();
    if !write && selected_entry.source_ref.is_some() && !preserves_existing_provenance {
        warnings.push(
            "selected entry already carries `source_ref`; the current write path will refuse it until multi-hop provenance is modeled explicitly"
                .to_owned(),
        );
    }

    let next_repo_contents = write.then(|| {
        append_block_to_contents(
            &repo_memory.source.content,
            &destination_preview.entry_block,
        )
    });
    let next_source_contents = if write {
        Some(rewrite_entry_blocks(
            &source_memory.source.content,
            &HashMap::from([(
                selected_entry.id.clone(),
                render_entry_block(&written_source_entry),
            )]),
        )?)
    } else {
        None
    };
    let write_result = write.then(|| WriteResultView {
        destination_action: "append_block",
        destination_path: repo_memory.source.path.display().to_string(),
        source_action: "update_selected_block",
        source_path: source_memory.source.path.display().to_string(),
        written_destination_entry: Some(written_destination_entry.clone()),
        written_source_entry: Some(written_source_entry.clone()),
    });

    let report = MemoryPromoteReport {
        command: "memory-promote",
        ok: true,
        path: repo_root.display().to_string(),
        profile: profile.to_owned(),
        project_id: locality_id.to_owned(),
        locality_id: locality_id.to_owned(),
        mode: match (write, mode_prefix) {
            (true, "work_stream_memory_to_project") => "work_stream_memory_to_project_write",
            (true, _) => "workspace_memory_to_project_write",
            (false, "work_stream_memory_to_project") => "work_stream_memory_to_project_preview",
            (false, _) => "workspace_memory_to_project_preview",
        },
        source: MemorySurfaceView {
            scope: source_scope,
            path: source_memory.source.path.display().to_string(),
            status: source_memory.source.status.as_str(),
        },
        destination: MemorySurfaceView {
            scope: "project_memory",
            path: repo_memory.source.path.display().to_string(),
            status: repo_memory.source.status.as_str(),
        },
        selected_entry: selected_entry.clone(),
        destination_preview,
        source_outcome: SourceOutcomeView {
            action: source_outcome.action(),
            current_state: selected_entry.state.clone(),
            resulting_state: source_outcome.resulting_source_state(&selected_entry.state),
            linkage_target: source_linkage_target,
            note: source_outcome_note(
                source_outcome,
                write,
                requested_source_outcome.is_some(),
                "project",
                preserves_existing_provenance,
            ),
        },
        governance: governance.clone(),
        write_result,
        staged_write: None,
        message: None,
        warnings,
    };

    let mut report = report;
    if write {
        let mutation_id = staged_request
            .map(|request| request.op_id.as_str())
            .unwrap_or("memory-promote-direct");
        governance = governance.with_rollback(
            mutation_id,
            write_options.actor_id.as_deref(),
            write_options
                .session_id
                .as_deref()
                .or(autonomous_session_id.as_deref()),
            vec![selected_entry.id.clone()],
            &repo_memory.source.content,
            Some(&source_memory.source.content),
        );
        report.governance = governance.clone();
    }
    if write && governance.blocked_for_write(autonomous) {
        report.ok = false;
        report.write_result = None;
        report.message = Some(governance.blocked_write_message());
        return Ok(report);
    }

    finalize_promote_report(
        repo_root,
        layout,
        report,
        write.then(|| queue::MemoryOpPlan {
            target: queue::MemoryFileMutation {
                scope: "project_memory".to_owned(),
                path: repo_memory.source.path.display().to_string(),
                next_contents: next_repo_contents.expect("write checked"),
            },
            source: Some(queue::MemoryFileMutation {
                scope: source_scope.to_owned(),
                path: source_memory.source.path.display().to_string(),
                next_contents: next_source_contents.expect("write checked"),
            }),
            refresh_locality_id: Some(locality_id.to_owned()),
            authored_entry_ids: vec![selected_entry.id.clone()],
            governance: Some(governance),
        }),
        staged_request,
        write_options,
    )
}

#[allow(clippy::too_many_arguments)]
fn run_branch_memory(
    repo_root: &Path,
    layout: &StateLayout,
    locality_id: &str,
    clone_memory: &runtime_state::LoadedRuntimeMemorySurface,
    profile: &str,
    selected_entry: &StructuredMemoryEntry,
    selected_runtime_entry: &RuntimeMemoryEntry,
    write: bool,
    requested_source_outcome: Option<SourceOutcome>,
    staged_request: Option<&QueuedWriteRequest>,
    write_options: &ExclusiveWriteOptions,
) -> Result<MemoryPromoteReport> {
    let branch_memory = runtime_state::load_branch_memory_surface(repo_root, layout, locality_id)?;
    if branch_memory
        .entries
        .iter()
        .any(|entry| entry.structured_id() == Some(selected_entry.id.as_str()))
    {
        bail!(
            "work-stream memory already contains entry id `{}` at {}; refusing to write or preview an unexpected overwrite",
            selected_entry.id,
            branch_memory.source.path.display()
        );
    }

    debug!(destination = "work_stream", path = %branch_memory.source.path.display(), write, "promoting memory entry");
    let source_outcome = resolve_source_outcome(write, requested_source_outcome)?;
    let preserves_existing_provenance = preserves_existing_source_provenance(selected_entry);
    if write && selected_entry.source_ref.is_some() && !preserves_existing_provenance {
        bail!(
            "entry `{}` already carries `source_ref`; refusing to apply a write that would discard or overwrite existing provenance in the first write slice",
            selected_entry.id
        );
    }
    let autonomous_session_id = current_autonomous_session_id(layout)?;
    let autonomous = autonomous_session_id.is_some();
    let repo_memory = runtime_state::load_locality_memory_surface(layout, locality_id)?;
    let mut governance = if let Some(duplicate) =
        governance::find_runtime_duplicate(&repo_memory.entries, selected_entry)
    {
        governance::GovernanceDecisionView::new(
            governance::GovernanceDecisionInit {
                action: governance::GovernanceAction::Discard,
                status: governance::GovernanceStatus::Discarded,
                target_scope: "work_stream_memory".to_owned(),
                source_scope: Some("workspace_memory".to_owned()),
                entry_type: selected_entry.entry_type.clone(),
                rationale: format!(
                    "work-stream capture overlaps existing project memory entry `{}`",
                    duplicate.entry_id
                ),
                evidence_summary: vec![
                    "work-stream admission stays local only when project memory does not already contain the lesson"
                        .to_owned(),
                ],
                pressure_signals: vec![governance::signal(
                    "broader_scope_overlap",
                    format!(
                        "project_memory already carries normalized duplicate `{}`",
                        duplicate.entry_id
                    ),
                )],
                duplicate_digest: Some(duplicate.digest),
                rate_limit: None,
            },
        )
    } else {
        build_promote_governance(PromoteGovernanceContext {
            layout,
            source_scope: "workspace_memory",
            target_scope: "work_stream_memory",
            selected_entry,
            destination_entries: &branch_memory.entries,
            autonomous,
            session_id: autonomous_session_id.as_deref(),
            current_op_id: staged_request.map(|request| request.op_id.as_str()),
            action: governance::GovernanceAction::AdmitWorkStream,
            rationale:
                "work-stream admission is allowed for focused lessons on an active non-trunk work stream",
            evidence_summary: vec![
                format!("entry type `{}` is eligible for work-stream admission", selected_entry.entry_type),
                "no project-memory overlap was detected".to_owned(),
            ],
            pressure_signals: Vec::new(),
        })?
    };

    let provenance_link = format!(
        "{}#{}",
        clone_memory.source.path.display(),
        selected_entry.id
    );
    let linkage_target = format!(
        "{}#{}",
        branch_memory.source.path.display(),
        selected_entry.id
    );
    let source_linkage_target = source_linkage_target(
        selected_entry,
        &linkage_target,
        preserves_existing_provenance,
    );
    let written_destination_runtime =
        promoted_destination_entry(selected_runtime_entry, &provenance_link);
    let written_source_runtime = linked_source_entry(
        selected_runtime_entry,
        source_outcome,
        source_linkage_target.clone(),
    )?;
    let written_destination_entry = written_destination_runtime
        .as_structured_entry()
        .expect("promoted destination entry");
    let written_source_entry = written_source_runtime
        .as_structured_entry()
        .expect("linked source entry");
    let destination_preview = DestinationPreviewView {
        append_mode: "append_block",
        file_status: branch_memory.source.status.as_str(),
        provenance_link: provenance_link.clone(),
        entry_block: render_entry_block(&written_destination_entry),
        note: promote_note(
            write,
            autonomous,
            &governance,
            "Applied with explicit approval. The block above was appended to work-stream memory.",
            "Preview only. No files were changed. Rerun with `--write --source-outcome <value>` to append this block and update the source entry.",
        ),
    };

    let mut warnings = Vec::new();
    if !write && selected_entry.source_ref.is_some() && !preserves_existing_provenance {
        warnings.push(
            "selected entry already carries `source_ref`; the current write path will refuse it until multi-hop provenance is modeled explicitly"
                .to_owned(),
        );
    }

    let next_branch_contents = write.then(|| {
        append_block_to_contents(
            &branch_memory.source.content,
            &destination_preview.entry_block,
        )
    });
    let next_clone_contents = if write {
        Some(rewrite_entry_blocks(
            &clone_memory.source.content,
            &HashMap::from([(
                selected_entry.id.clone(),
                render_entry_block(&written_source_entry),
            )]),
        )?)
    } else {
        None
    };
    let write_result = write.then(|| WriteResultView {
        destination_action: "append_block",
        destination_path: branch_memory.source.path.display().to_string(),
        source_action: "update_selected_block",
        source_path: clone_memory.source.path.display().to_string(),
        written_destination_entry: Some(written_destination_entry.clone()),
        written_source_entry: Some(written_source_entry.clone()),
    });

    let report = MemoryPromoteReport {
        command: "memory-promote",
        ok: true,
        path: repo_root.display().to_string(),
        profile: profile.to_owned(),
        project_id: locality_id.to_owned(),
        locality_id: locality_id.to_owned(),
        mode: if write {
            "workspace_memory_to_work_stream_write"
        } else {
            "workspace_memory_to_work_stream_preview"
        },
        source: MemorySurfaceView {
            scope: "workspace_memory",
            path: clone_memory.source.path.display().to_string(),
            status: clone_memory.source.status.as_str(),
        },
        destination: MemorySurfaceView {
            scope: "work_stream_memory",
            path: branch_memory.source.path.display().to_string(),
            status: branch_memory.source.status.as_str(),
        },
        selected_entry: selected_entry.clone(),
        destination_preview,
        source_outcome: SourceOutcomeView {
            action: source_outcome.action(),
            current_state: selected_entry.state.clone(),
            resulting_state: source_outcome.resulting_source_state(&selected_entry.state),
            linkage_target: source_linkage_target,
            note: source_outcome_note(
                source_outcome,
                write,
                requested_source_outcome.is_some(),
                "work stream",
                preserves_existing_provenance,
            ),
        },
        governance: governance.clone(),
        write_result,
        staged_write: None,
        message: None,
        warnings,
    };

    let mut report = report;
    if write {
        let mutation_id = staged_request
            .map(|request| request.op_id.as_str())
            .unwrap_or("memory-promote-direct");
        governance = governance.with_rollback(
            mutation_id,
            write_options.actor_id.as_deref(),
            write_options
                .session_id
                .as_deref()
                .or(autonomous_session_id.as_deref()),
            vec![selected_entry.id.clone()],
            &branch_memory.source.content,
            Some(&clone_memory.source.content),
        );
        report.governance = governance.clone();
    }
    if write && governance.blocked_for_write(autonomous) {
        report.ok = false;
        report.write_result = None;
        report.message = Some(governance.blocked_write_message());
        return Ok(report);
    }

    finalize_promote_report(
        repo_root,
        layout,
        report,
        write.then(|| queue::MemoryOpPlan {
            target: queue::MemoryFileMutation {
                scope: "work_stream_memory".to_owned(),
                path: branch_memory.source.path.display().to_string(),
                next_contents: next_branch_contents.expect("write checked"),
            },
            source: Some(queue::MemoryFileMutation {
                scope: "workspace_memory".to_owned(),
                path: clone_memory.source.path.display().to_string(),
                next_contents: next_clone_contents.expect("write checked"),
            }),
            refresh_locality_id: Some(locality_id.to_owned()),
            authored_entry_ids: vec![selected_entry.id.clone()],
            governance: Some(governance),
        }),
        staged_request,
        write_options,
    )
}

#[allow(clippy::too_many_arguments)]
fn run_project_truth(
    repo_root: &Path,
    layout: &StateLayout,
    locality_id: &str,
    repo_memory: &runtime_state::LoadedRuntimeMemorySurface,
    profile: &str,
    selected_entry: &StructuredMemoryEntry,
    selected_runtime_entry: &RuntimeMemoryEntry,
    write: bool,
    requested_source_outcome: Option<SourceOutcome>,
    target_file: &Path,
    staged_request: Option<&QueuedWriteRequest>,
    write_options: &ExclusiveWriteOptions,
) -> Result<MemoryPromoteReport> {
    if requested_source_outcome.is_some() {
        bail!(
            "`--source-outcome` is not applicable for `--destination project-truth`; the source entry is always linked without a state change"
        );
    }

    debug!(destination = "project_truth", path = %target_file.display(), write, "promoting memory entry");
    let autonomous_session_id = current_autonomous_session_id(layout)?;
    let autonomous = autonomous_session_id.is_some();
    let provenance_link = format!(
        "{}#{}",
        repo_memory.source.path.display(),
        selected_entry.id
    );
    let linkage_target = format!("{}#{}", target_file.display(), selected_entry.id);
    let preserves_existing_provenance = preserves_existing_source_provenance(selected_entry);
    let source_linkage_target = source_linkage_target(
        selected_entry,
        &linkage_target,
        preserves_existing_provenance,
    );
    let mut governance = governance::GovernanceDecisionView::new(
        governance::GovernanceDecisionInit {
            action: governance::GovernanceAction::DraftProjectTruth,
            status: governance::GovernanceStatus::Allowed,
            target_scope: "project_truth".to_owned(),
            source_scope: Some("project_memory".to_owned()),
            entry_type: selected_entry.entry_type.clone(),
            rationale: "project-truth promotion only drafts a reviewable section and never directly widens canonical truth without normal project workflow".to_owned(),
            evidence_summary: vec![
                "project-truth output remains a draft or recommendation".to_owned(),
                "the source memory entry is only linked back to the draft".to_owned(),
            ],
            pressure_signals: Vec::new(),
            duplicate_digest: Some(governance::normalized_duplicate_digest(selected_entry)),
            rate_limit: None,
        },
    );

    let draft_section = project_truth_draft_section(selected_entry, &provenance_link);

    // For preview, report the target file path and status without requiring it to exist.
    let file_status = if target_file.exists() {
        "loaded"
    } else {
        "missing"
    };

    let destination_preview = DestinationPreviewView {
        append_mode: "append_plain_section",
        file_status,
        provenance_link: provenance_link.clone(),
        entry_block: draft_section.clone(),
        note: promote_note(
            write,
            autonomous,
            &governance,
            "Applied with explicit approval. The draft section above was appended to the target file.",
            "Preview only. No files were changed. Rerun with `--write` to append the draft section to the target file.",
        ),
    };

    let source_outcome = SourceOutcome::LinkOnly;
    let mut canonical_destination_path = None;
    let mut current_target_contents = None;
    let next_target_contents = if write {
        let canonical_repo = fs::canonicalize(repo_root)
            .with_context(|| format!("cannot resolve repo root `{}`", repo_root.display()))?;
        let canonical_file = fs::canonicalize(target_file).with_context(|| {
            format!(
                "target file `{}` does not exist or cannot be resolved; create it before running with `--write`",
                target_file.display()
            )
        })?;
        if !canonical_file.starts_with(&canonical_repo) {
            bail!(
                "target file `{}` is outside the repo root `{}`; refusing to write outside the repo boundary",
                target_file.display(),
                repo_root.display()
            );
        }

        let current_content = fs::read_to_string(&canonical_file)
            .with_context(|| format!("cannot read target file `{}`", canonical_file.display()))?;
        let draft_marker = format!("<!-- ccd-memory-draft: {} ", selected_entry.id);
        if current_content.contains(&draft_marker) {
            bail!(
                "target file `{}` already contains a draft for entry `{}`; remove the existing draft before re-promoting",
                canonical_file.display(),
                selected_entry.id
            );
        }
        let next_content = append_plain_section(&current_content, &draft_section);
        current_target_contents = Some(current_content);
        canonical_destination_path = Some(canonical_file);
        Some(next_content)
    } else {
        None
    };

    let next_repo_contents = if write {
        let canonical_file = canonical_destination_path
            .as_ref()
            .expect("canonical target path when write is enabled");
        let canonical_linkage_target =
            format!("{}#{}", canonical_file.display(), selected_entry.id);
        let canonical_source_linkage_target = if preserves_existing_provenance {
            selected_entry
                .source_ref
                .clone()
                .expect("existing source_ref when preserving provenance")
        } else {
            canonical_linkage_target
        };
        let canonical_source_runtime = linked_source_entry(
            selected_runtime_entry,
            source_outcome,
            canonical_source_linkage_target,
        )?;
        let canonical_source_entry = canonical_source_runtime
            .as_structured_entry()
            .expect("canonical source entry");

        Some((
            rewrite_entry_blocks(
                &repo_memory.source.content,
                &HashMap::from([(
                    selected_entry.id.clone(),
                    render_entry_block(&canonical_source_entry),
                )]),
            )?,
            canonical_source_entry,
        ))
    } else {
        None
    };

    let write_result = if write {
        let canonical_file = canonical_destination_path
            .as_ref()
            .expect("canonical target path when write is enabled");
        let canonical_source_entry = next_repo_contents
            .as_ref()
            .map(|(_, entry)| entry.clone())
            .expect("canonical source entry when write is enabled");
        Some(WriteResultView {
            destination_action: "append_plain_section",
            destination_path: canonical_file.display().to_string(),
            source_action: "update_selected_block",
            source_path: repo_memory.source.path.display().to_string(),
            written_destination_entry: None,
            written_source_entry: Some(canonical_source_entry),
        })
    } else {
        None
    };

    let report = MemoryPromoteReport {
        command: "memory-promote",
        ok: true,
        path: repo_root.display().to_string(),
        profile: profile.to_owned(),
        project_id: locality_id.to_owned(),
        locality_id: locality_id.to_owned(),
        mode: if write {
            "project_memory_to_project_truth_write"
        } else {
            "project_memory_to_project_truth_preview"
        },
        source: MemorySurfaceView {
            scope: "project_memory",
            path: repo_memory.source.path.display().to_string(),
            status: repo_memory.source.status.as_str(),
        },
        destination: MemorySurfaceView {
            scope: "project_truth",
            path: target_file.display().to_string(),
            status: file_status,
        },
        selected_entry: selected_entry.clone(),
        destination_preview,
        source_outcome: SourceOutcomeView {
            action: source_outcome.action(),
            current_state: selected_entry.state.clone(),
            resulting_state: source_outcome.resulting_source_state(&selected_entry.state),
            linkage_target: source_linkage_target,
            note: source_outcome_note(
                source_outcome,
                write,
                false,
                "project_truth",
                preserves_existing_provenance,
            ),
        },
        governance: governance.clone(),
        write_result,
        staged_write: None,
        message: None,
        warnings: Vec::new(),
    };

    let mut report = report;
    if write {
        let mutation_id = staged_request
            .map(|request| request.op_id.as_str())
            .unwrap_or("memory-promote-direct");
        governance = governance.with_rollback(
            mutation_id,
            write_options.actor_id.as_deref(),
            write_options
                .session_id
                .as_deref()
                .or(autonomous_session_id.as_deref()),
            vec![selected_entry.id.clone()],
            current_target_contents.as_deref().unwrap_or(""),
            Some(&repo_memory.source.content),
        );
        report.governance = governance.clone();
    }

    finalize_promote_report(
        repo_root,
        layout,
        report,
        write.then(|| queue::MemoryOpPlan {
            target: queue::MemoryFileMutation {
                scope: "project_truth".to_owned(),
                path: canonical_destination_path
                    .as_ref()
                    .expect("canonical target path when write is enabled")
                    .display()
                    .to_string(),
                next_contents: next_target_contents.expect("write checked"),
            },
            source: Some(queue::MemoryFileMutation {
                scope: "project_memory".to_owned(),
                path: repo_memory.source.path.display().to_string(),
                next_contents: next_repo_contents
                    .as_ref()
                    .map(|(contents, _)| contents.clone())
                    .expect("write checked"),
            }),
            refresh_locality_id: Some(locality_id.to_owned()),
            authored_entry_ids: vec![selected_entry.id.clone()],
            governance: Some(governance),
        }),
        staged_request,
        write_options,
    )
}

fn should_stage_via_queue(
    write: bool,
    _destination: &PromoteDestination,
    layout: &StateLayout,
) -> Result<bool> {
    if !write {
        return Ok(false);
    }
    queue::queue_required_for_autonomous(layout)
}

fn destination_queue_label(destination: &PromoteDestination) -> &'static str {
    match destination {
        PromoteDestination::BranchMemory => "work_stream_memory",
        PromoteDestination::RepoMemory => "project_memory",
        PromoteDestination::ProfileMemory => "profile_memory",
        PromoteDestination::ProjectTruth { .. } => "project_truth",
    }
}

pub(crate) fn replay_request(
    entry_id: &str,
    requested_source_outcome: Option<SourceOutcome>,
    destination: &PromoteDestination,
    op_id_hint: Option<&str>,
) -> Result<(String, String)> {
    let request_key = PromoteQueueRequestKey {
        command: "memory-promote",
        entry_id,
        destination: destination_queue_label(destination),
        source_outcome: requested_source_outcome.map(SourceOutcome::action),
    };
    let request_fingerprint = queue::fingerprint(&request_key)?;
    let op_id = queue::stable_op_id("promote", &request_fingerprint, op_id_hint);
    Ok((request_fingerprint, op_id))
}

fn finalize_promote_report(
    repo_root: &Path,
    layout: &StateLayout,
    mut report: MemoryPromoteReport,
    plan: Option<queue::MemoryOpPlan>,
    staged_request: Option<&QueuedWriteRequest>,
    write_options: &ExclusiveWriteOptions,
) -> Result<MemoryPromoteReport> {
    let Some(plan) = plan else {
        return Ok(report);
    };

    if let Some(staged_request) = staged_request {
        let staged_write = queue::stage_and_reconcile(
            repo_root,
            layout,
            "memory-promote",
            &staged_request.op_id,
            &staged_request.request_fingerprint,
            write_options,
            &plan,
        )?;
        let staged_ok = matches!(
            staged_write.outcome,
            crate::state::protected_write::AppendWriteOutcome::Applied
                | crate::state::protected_write::AppendWriteOutcome::IdempotentNoop
        );
        if !staged_ok {
            report.ok = false;
            report.write_result = None;
            report.message = staged_write.message.clone();
        }
        report.staged_write = Some(staged_write);
        if report.ok {
            queue::persist_report_snapshot(layout, &staged_request.op_id, &report)?;
        }
        return Ok(report);
    }

    let outcome = queue::apply_direct_plan(repo_root, layout, &plan)?;
    if outcome == crate::state::protected_write::AppendWriteOutcome::IdempotentNoop {
        if let Some(write_result) = report.write_result.as_mut() {
            write_result.destination_action = "already_applied";
            write_result.source_action = "already_applied";
        }
    }
    Ok(report)
}

struct PromoteGovernanceContext<'a> {
    layout: &'a StateLayout,
    source_scope: &'a str,
    target_scope: &'a str,
    selected_entry: &'a StructuredMemoryEntry,
    destination_entries: &'a [RuntimeMemoryEntry],
    autonomous: bool,
    session_id: Option<&'a str>,
    current_op_id: Option<&'a str>,
    action: governance::GovernanceAction,
    rationale: &'a str,
    evidence_summary: Vec<String>,
    pressure_signals: Vec<governance::GovernancePressureSignalView>,
}

fn build_promote_governance(
    mut context: PromoteGovernanceContext<'_>,
) -> Result<governance::GovernanceDecisionView> {
    if let Some(duplicate) =
        governance::find_runtime_duplicate(context.destination_entries, context.selected_entry)
    {
        return Ok(governance::GovernanceDecisionView::new(
            governance::GovernanceDecisionInit {
                action: governance::GovernanceAction::Discard,
                status: governance::GovernanceStatus::Discarded,
                target_scope: context.target_scope.to_owned(),
                source_scope: Some(context.source_scope.to_owned()),
                entry_type: context.selected_entry.entry_type.clone(),
                rationale: format!(
                    "a normalized duplicate already exists in {} as `{}`",
                    context.target_scope, duplicate.entry_id
                ),
                evidence_summary: vec![
                    "duplicate suppression runs before any broader-scope mutation".to_owned(),
                ],
                pressure_signals: vec![governance::signal(
                    "duplicate_match",
                    format!(
                        "{} already contains normalized duplicate `{}`",
                        context.target_scope, duplicate.entry_id
                    ),
                )],
                duplicate_digest: Some(duplicate.digest),
                rate_limit: None,
            },
        ));
    }

    let rate_limit = if context.autonomous {
        governance::session_rate_limit(
            context.layout,
            context.session_id,
            context.target_scope,
            context.current_op_id,
        )?
    } else {
        None
    };
    if let Some(rate_limit) = &rate_limit {
        if rate_limit.remaining == 0 {
            context.pressure_signals.push(governance::signal(
                "session_cap_reached",
                format!(
                    "{} already has {} queued admissions or promotions in this session",
                    rate_limit.scope, rate_limit.count
                ),
            ));
        } else if rate_limit.remaining == 1 {
            context.pressure_signals.push(governance::signal(
                "session_cap_near",
                format!(
                    "{} has one remaining governed mutation slot in this session",
                    rate_limit.scope
                ),
            ));
        }
    }

    Ok(governance::GovernanceDecisionView::new(
        governance::GovernanceDecisionInit {
            action: context.action,
            status: if rate_limit
                .as_ref()
                .is_some_and(|limit| limit.status == "reached")
            {
                governance::GovernanceStatus::RateLimited
            } else {
                governance::GovernanceStatus::Allowed
            },
            target_scope: context.target_scope.to_owned(),
            source_scope: Some(context.source_scope.to_owned()),
            entry_type: context.selected_entry.entry_type.clone(),
            rationale: context.rationale.to_owned(),
            evidence_summary: context.evidence_summary,
            pressure_signals: context.pressure_signals,
            duplicate_digest: Some(governance::normalized_duplicate_digest(
                context.selected_entry,
            )),
            rate_limit,
        },
    ))
}

fn promote_note(
    write: bool,
    autonomous: bool,
    governance: &governance::GovernanceDecisionView,
    write_note: &str,
    preview_note: &str,
) -> String {
    if write && governance.blocked_for_write(autonomous) {
        return format!(
            "Write was not applied. Governance outcome: {} ({})",
            governance.action.as_str(),
            governance.blocked_write_message()
        );
    }

    if !write && governance.status != governance::GovernanceStatus::Allowed {
        return format!(
            "Preview only. Governance outcome: {} ({})",
            governance.action.as_str(),
            governance.rationale
        );
    }

    if write {
        write_note.to_owned()
    } else {
        preview_note.to_owned()
    }
}

fn current_autonomous_session_id(layout: &StateLayout) -> Result<Option<String>> {
    if queue::queue_required_for_autonomous(layout)? {
        session::load_session_id(layout)
    } else {
        Ok(None)
    }
}

fn resolve_source_outcome(
    write: bool,
    requested_source_outcome: Option<SourceOutcome>,
) -> Result<SourceOutcome> {
    match (write, requested_source_outcome) {
        (true, None) => bail!(
            "`ccd memory promote --write` requires `--source-outcome <active|superseded|link-only>` so the source transition stays explicit"
        ),
        (_, Some(outcome)) => Ok(outcome),
        (false, None) => Ok(SourceOutcome::LinkOnly),
    }
}

fn promoted_destination_entry(
    selected_entry: &RuntimeMemoryEntry,
    provenance_link: &str,
) -> RuntimeMemoryEntry {
    let decay_class = selected_entry
        .structured_decay_class()
        .map(str::to_owned)
        .or(Some("stable".to_owned()));
    selected_entry
        .with_structured_state("active")
        .with_source_ref(Some(provenance_link.to_owned()))
        .with_decay_class(decay_class)
}

fn linked_source_entry(
    selected_entry: &RuntimeMemoryEntry,
    source_outcome: SourceOutcome,
    next_source_ref: String,
) -> Result<RuntimeMemoryEntry> {
    let current_state = selected_entry
        .structured_state()
        .expect("structured source entry");
    let resulting_state = source_outcome.resulting_source_state(current_state);
    let entry = selected_entry
        .with_structured_state(&resulting_state)
        .with_source_ref(Some(next_source_ref));
    if current_state != "superseded" && resulting_state == "superseded" {
        let superseded_at = Some(timestamps::current_utc_rfc3339()?);
        return Ok(entry.with_superseded_at(superseded_at));
    }
    Ok(entry)
}

fn preserves_existing_source_provenance(selected_entry: &StructuredMemoryEntry) -> bool {
    selected_entry.source_ref.is_some() && selected_entry.state == "promotion_candidate"
}

fn source_linkage_target(
    selected_entry: &StructuredMemoryEntry,
    linkage_target: &str,
    preserves_existing_provenance: bool,
) -> String {
    if preserves_existing_provenance {
        selected_entry
            .source_ref
            .clone()
            .expect("existing source_ref when preserving provenance")
    } else {
        linkage_target.to_owned()
    }
}

fn source_outcome_note(
    source_outcome: SourceOutcome,
    write: bool,
    explicitly_requested: bool,
    destination_scope: &'static str,
    preserves_existing_provenance: bool,
) -> &'static str {
    if preserves_existing_provenance {
        if write {
            return match source_outcome {
                SourceOutcome::Active => {
                    "source entry kept its existing provenance link and was marked active"
                }
                SourceOutcome::Superseded => {
                    "source entry kept its existing provenance link and was marked superseded"
                }
                SourceOutcome::LinkOnly => {
                    "source entry kept its existing provenance link without changing its state"
                }
            };
        }

        if explicitly_requested {
            return "previewing the requested source outcome; existing source provenance would be preserved on write";
        }

        return "preview defaults to `link_only`; existing source provenance would be preserved on write";
    }

    if write {
        return match source_outcome {
            SourceOutcome::Active => {
                match destination_scope {
                    "branch" => {
                        "source entry was linked to the promoted branch entry and marked active"
                    }
                    "repo" => {
                        "source entry was linked to the promoted repo entry and marked active"
                    }
                    "project_truth" => {
                        "source entry was linked to the project-truth draft and marked active"
                    }
                    _ => "source entry was linked to the promoted profile entry and marked active",
                }
            }
            SourceOutcome::Superseded => {
                match destination_scope {
                    "branch" => {
                        "source entry was linked to the promoted branch entry and marked superseded"
                    }
                    "repo" => {
                        "source entry was linked to the promoted repo entry and marked superseded"
                    }
                    "project_truth" => {
                        "source entry was linked to the project-truth draft and marked superseded"
                    }
                    _ => {
                        "source entry was linked to the promoted profile entry and marked superseded"
                    }
                }
            }
            SourceOutcome::LinkOnly => {
                match destination_scope {
                    "branch" => {
                        "source entry was linked to the promoted branch entry without changing its state"
                    }
                    "repo" => {
                        "source entry was linked to the promoted repo entry without changing its state"
                    }
                    "project_truth" => {
                        "source entry was linked to the project-truth draft without changing its state"
                    }
                    _ => {
                        "source entry was linked to the promoted profile entry without changing its state"
                    }
                }
            }
        };
    }

    if explicitly_requested {
        return "previewing the requested source outcome; rerun with `--write` to apply it";
    }

    if destination_scope == "project_truth" {
        return "preview defaults to `link_only` for project-truth destination";
    }

    "preview defaults to `link_only` until an explicit write is approved"
}

fn project_truth_draft_section(entry: &StructuredMemoryEntry, provenance_link: &str) -> String {
    format!(
        "<!-- ccd-memory-draft: {} | type: {} | source: {} -->\n{}",
        entry.id, entry.entry_type, provenance_link, entry.content
    )
}

fn append_plain_section(contents: &str, section: &str) -> String {
    if contents.trim().is_empty() {
        return format!("{section}\n");
    }
    format!("{}\n\n{section}\n", contents.trim_end_matches(['\n', '\r']))
}

fn ensure_profile_exists(layout: &StateLayout) -> Result<()> {
    let profile_root = layout.profile_root();
    if profile_root.is_dir() {
        return Ok(());
    }

    bail!(
        "profile `{}` does not exist at {}; bootstrap it with `ccd attach` before using `ccd memory promote`",
        layout.profile(),
        profile_root.display()
    )
}

fn ensure_repo_registry_exists(
    layout: &StateLayout,
    repo_root: &Path,
    locality_id: &str,
) -> Result<()> {
    let registry_path = layout.repo_metadata_path(locality_id)?;
    match repo_registry::load(&registry_path)? {
        Some(_) => Ok(()),
        None => bail!(
            "repo registry is missing for locality_id `{}` at {}; run `ccd link --path {}` or `ccd attach --path {}` again",
            locality_id,
            registry_path.display(),
            repo_root.display(),
            repo_root.display()
        ),
    }
}

fn ensure_repo_overlay_exists(layout: &StateLayout, locality_id: &str) -> Result<()> {
    let overlay_root = layout.repo_overlay_root(locality_id)?;
    if overlay_root.is_dir() {
        return Ok(());
    }

    bail!(
        "repo overlay is missing for locality_id `{}` at {}; run `ccd link --path .` or `ccd attach --path .` again",
        locality_id,
        overlay_root.display()
    )
}

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

    #[test]
    fn promotable_entry_types_include_the_lint_surface() {
        assert!(is_promotable_entry_type("rule"));
        assert!(is_promotable_entry_type("constraint"));
        assert!(is_promotable_entry_type("heuristic"));
        assert!(is_promotable_entry_type("attempt"));
        assert!(!is_promotable_entry_type("observation"));
    }
}
