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

use anyhow::{anyhow, bail, Context, Result};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};

use crate::db;
use crate::handoff;
use crate::memory::entries as memory_entries;
use crate::memory::file as memory_file;
use crate::paths::state::StateLayout;
use crate::paths::write as path_write;
use crate::state::machine_registry as pod_identity;
use crate::state::session_gates;
use crate::state::text_surface::{
    extract_generic_markdown_lines, starts_with_any_prefix_case_insensitive, strip_fenced_blocks,
    MarkdownLineExtraction,
};

const CCD_MEMORY_OPENING_FENCE: &str = "```ccd-memory";
const GENERIC_CLOSING_FENCE: &str = "```";
const NATIVE_PROFILE_RUNTIME_SCHEMA_VERSION: u32 = 1;
const NATIVE_REPO_RUNTIME_SCHEMA_VERSION: u32 = 1;
const CHECKPOINT_SCHEMA_VERSION: u32 = 1;
const MAX_CHECKPOINT_SUMMARY_CHARS: usize = 280;
const MAX_RECOVERY_IMMEDIATE_ACTIONS: usize = 8;
const MAX_RECOVERY_KEY_FILES: usize = 8;
const MAX_WORKING_BUFFER_SUMMARY_LINES: usize = 12;
const MAX_WORKING_BUFFER_BYTES: usize = 4096;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RuntimeLifecycle {
    #[serde(rename = "active", alias = "Active")]
    Active,
    #[serde(rename = "inactive", alias = "Inactive")]
    Inactive,
}

impl RuntimeLifecycle {
    pub fn is_active(self) -> bool {
        matches!(self, Self::Active)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum RuntimeMemoryOrigin {
    Narrative,
    Structured { entry_type: String, origin: String },
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeMemoryEntry {
    pub id: Option<String>,
    pub text: String,
    pub lifecycle: RuntimeLifecycle,
    pub origin: RuntimeMemoryOrigin,
    pub state: Option<String>,
    pub created_at: Option<String>,
    pub last_touched_session: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub superseded_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub decay_class: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub expires_at: Option<String>,
    pub tags: Vec<String>,
    pub source_ref: Option<String>,
    pub supersedes: Vec<String>,
}

impl RuntimeMemoryEntry {
    pub fn narrative(text: impl Into<String>) -> Self {
        Self {
            id: None,
            text: text.into(),
            lifecycle: RuntimeLifecycle::Active,
            origin: RuntimeMemoryOrigin::Narrative,
            state: None,
            created_at: None,
            last_touched_session: None,
            superseded_at: None,
            decay_class: None,
            expires_at: None,
            tags: Vec::new(),
            source_ref: None,
            supersedes: Vec::new(),
        }
    }

    pub fn from_structured_entry(entry: memory_entries::StructuredMemoryEntry) -> Self {
        let lifecycle = runtime_memory_lifecycle(&entry.state);
        Self {
            id: Some(entry.id),
            text: entry.content,
            lifecycle,
            origin: RuntimeMemoryOrigin::Structured {
                entry_type: entry.entry_type,
                origin: entry.origin,
            },
            state: Some(entry.state),
            created_at: Some(entry.created_at),
            last_touched_session: Some(entry.last_touched_session),
            superseded_at: entry.superseded_at,
            decay_class: entry.decay_class,
            expires_at: entry.expires_at,
            tags: entry.tags,
            source_ref: entry.source_ref,
            supersedes: entry.supersedes,
        }
    }

    pub fn projection_text(&self) -> String {
        match &self.origin {
            RuntimeMemoryOrigin::Narrative => self.text.clone(),
            RuntimeMemoryOrigin::Structured { entry_type, .. } => {
                format!("{entry_type}: {}", self.text)
            }
        }
    }

    pub fn structured_id(&self) -> Option<&str> {
        self.id.as_deref()
    }

    pub fn structured_state(&self) -> Option<&str> {
        self.state.as_deref()
    }

    pub fn as_structured_entry(&self) -> Option<memory_entries::StructuredMemoryEntry> {
        let RuntimeMemoryOrigin::Structured { entry_type, origin } = &self.origin else {
            return None;
        };

        Some(memory_entries::StructuredMemoryEntry {
            id: self.id.clone()?,
            entry_type: entry_type.clone(),
            state: self.state.clone()?,
            created_at: self.created_at.clone()?,
            last_touched_session: self.last_touched_session?,
            origin: origin.clone(),
            superseded_at: self.superseded_at.clone(),
            decay_class: self.decay_class.clone(),
            expires_at: self.expires_at.clone(),
            tags: self.tags.clone(),
            source_ref: self.source_ref.clone(),
            supersedes: self.supersedes.clone(),
            content: self.text.clone(),
        })
    }

    pub fn with_structured_state(&self, state: &str) -> Self {
        let mut entry = self.clone();
        entry.state = Some(state.to_owned());
        entry.lifecycle = runtime_memory_lifecycle(state);
        if state != "superseded" {
            entry.superseded_at = None;
        }
        entry
    }

    pub fn with_source_ref(&self, source_ref: Option<String>) -> Self {
        let mut entry = self.clone();
        entry.source_ref = source_ref;
        entry
    }

    pub fn with_superseded_at(&self, superseded_at: Option<String>) -> Self {
        let mut entry = self.clone();
        entry.superseded_at = superseded_at;
        entry
    }

    pub fn structured_decay_class(&self) -> Option<&str> {
        self.decay_class.as_deref()
    }

    pub fn with_decay_class(&self, decay_class: Option<String>) -> Self {
        let mut entry = self.clone();
        entry.decay_class = decay_class;
        entry
    }

    pub fn with_supersedes(&self, supersedes: Vec<String>) -> Self {
        let mut entry = self.clone();
        entry.supersedes = supersedes;
        entry
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeHandoffItem {
    pub text: String,
    pub lifecycle: RuntimeLifecycle,
}

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct RuntimeMemoryState {
    pub profile: Vec<RuntimeMemoryEntry>,
    #[serde(alias = "repo")]
    pub locality: Vec<RuntimeMemoryEntry>,
    #[serde(default)]
    pub branch: Vec<RuntimeMemoryEntry>,
    #[serde(default)]
    pub clone: Vec<RuntimeMemoryEntry>,
}

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct RuntimeHandoffState {
    pub title: String,
    pub immediate_actions: Vec<RuntimeHandoffItem>,
    pub completed_state: Vec<RuntimeHandoffItem>,
    pub operational_guardrails: Vec<RuntimeHandoffItem>,
    pub key_files: Vec<RuntimeHandoffItem>,
    pub definition_of_done: Vec<RuntimeHandoffItem>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RecoveryOrigin {
    Compaction,
    RiskyPause,
    Manual,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeCheckpointState {
    pub origin: RecoveryOrigin,
    pub captured_at_epoch_s: u64,
    pub session_started_at_epoch_s: u64,
    pub summary: String,
    pub immediate_actions: Vec<String>,
    pub key_files: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeWorkingBufferState {
    pub origin: RecoveryOrigin,
    pub captured_at_epoch_s: u64,
    pub session_started_at_epoch_s: u64,
    pub summary_lines: Vec<String>,
}

pub type RuntimeMemoryAutomationLedger = BTreeMap<String, RuntimeMemoryAutomationRecord>;

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct RuntimeMemoryAutomationRecord {
    pub entry_type: String,
    #[serde(default)]
    pub observation_count: u64,
    #[serde(default)]
    pub session_ids: Vec<String>,
    #[serde(default)]
    pub locality_ids: Vec<String>,
    pub first_observed_at_epoch_s: u64,
    pub last_observed_at_epoch_s: u64,
    #[serde(default)]
    pub contradiction_count: u64,
    #[serde(default)]
    pub has_structural_signal: bool,
}

impl RuntimeMemoryAutomationRecord {
    pub fn observed_session_count(&self) -> usize {
        self.session_ids.len()
    }

    pub fn observed_locality_count(&self) -> usize {
        self.locality_ids.len()
    }

    pub fn observe(
        &self,
        entry_type: &str,
        observed_at_epoch_s: u64,
        session_id: Option<&str>,
        locality_id: Option<&str>,
        structural_signal: bool,
    ) -> Self {
        let mut next = if self.observation_count == 0 {
            Self {
                entry_type: entry_type.to_owned(),
                observation_count: 0,
                session_ids: Vec::new(),
                locality_ids: Vec::new(),
                first_observed_at_epoch_s: observed_at_epoch_s,
                last_observed_at_epoch_s: observed_at_epoch_s,
                contradiction_count: 0,
                has_structural_signal: false,
            }
        } else {
            self.clone()
        };

        if !next.entry_type.is_empty() && next.entry_type != entry_type {
            next.contradiction_count = next.contradiction_count.saturating_add(1);
        }
        next.entry_type = entry_type.to_owned();
        next.observation_count = next.observation_count.saturating_add(1);
        next.last_observed_at_epoch_s = observed_at_epoch_s;
        if next.first_observed_at_epoch_s == 0 {
            next.first_observed_at_epoch_s = observed_at_epoch_s;
        }
        if let Some(session_id) = session_id {
            if !next
                .session_ids
                .iter()
                .any(|existing| existing == session_id)
            {
                next.session_ids.push(session_id.to_owned());
                next.session_ids.sort_unstable();
            }
        }
        if let Some(locality_id) = locality_id {
            if !next
                .locality_ids
                .iter()
                .any(|existing| existing == locality_id)
            {
                next.locality_ids.push(locality_id.to_owned());
                next.locality_ids.sort_unstable();
            }
        }
        next.has_structural_signal |= structural_signal;
        next
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct RuntimeRecoveryState {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub checkpoint: Option<RuntimeCheckpointState>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub working_buffer: Option<RuntimeWorkingBufferState>,
}

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct RuntimeState {
    pub memory: RuntimeMemoryState,
    pub handoff: RuntimeHandoffState,
}

#[derive(Debug, Clone)]
pub struct RuntimeTextSurface {
    pub kind: &'static str,
    pub path: PathBuf,
    pub status: RuntimeTextSurfaceStatus,
    pub content: String,
    pub migrated_from: Option<String>,
}

impl RuntimeTextSurface {
    pub fn missing(kind: &'static str, path: &Path) -> Self {
        Self {
            kind,
            path: path.to_path_buf(),
            status: RuntimeTextSurfaceStatus::Missing,
            content: String::new(),
            migrated_from: None,
        }
    }

    pub fn is_missing(&self) -> bool {
        self.status.is_missing()
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RuntimeTextSurfaceStatus {
    Missing,
    Empty,
    Loaded,
    LoadedNative,
}

impl RuntimeTextSurfaceStatus {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Missing => "missing",
            Self::Empty => "empty",
            Self::Loaded => "loaded",
            Self::LoadedNative => "loaded_native",
        }
    }

    pub fn is_missing(self) -> bool {
        matches!(self, Self::Missing)
    }

    pub fn is_loaded_native(self) -> bool {
        matches!(self, Self::LoadedNative)
    }
}

#[derive(Debug, Clone)]
pub struct RuntimeSourceSurfaces {
    pub profile_memory: RuntimeTextSurface,
    pub locality_memory: RuntimeTextSurface,
    pub branch_memory: RuntimeTextSurface,
    pub clone_memory: RuntimeTextSurface,
    pub execution_gates: RuntimeTextSurface,
    pub handoff: RuntimeTextSurface,
}

#[derive(Debug, Clone)]
pub struct RuntimeRecoverySurface {
    pub kind: &'static str,
    pub path: PathBuf,
    pub status: &'static str,
}

impl RuntimeRecoverySurface {
    fn missing(kind: &'static str, path: &Path) -> Self {
        Self {
            kind,
            path: path.to_path_buf(),
            status: "missing",
        }
    }
}

#[derive(Debug, Clone)]
pub struct RuntimeRecoverySurfaces {
    pub checkpoint: RuntimeRecoverySurface,
    pub working_buffer: RuntimeRecoverySurface,
}

#[derive(Debug, Clone)]
pub struct LoadedRuntimeState {
    pub state: RuntimeState,
    pub sources: RuntimeSourceSurfaces,
    pub execution_gates: LoadedRuntimeExecutionGates,
    pub recovery: LoadedRuntimeRecoveryState,
    pub pod_identity_active: bool,
}

#[derive(Debug, Clone)]
pub struct LoadedRuntimeMemorySurface {
    pub source: RuntimeTextSurface,
    pub entries: Vec<RuntimeMemoryEntry>,
}

#[derive(Debug, Clone)]
pub struct LoadedRuntimeExecutionGates {
    pub source: RuntimeTextSurface,
    pub view: session_gates::ExecutionGatesView,
}

#[derive(Debug, Clone)]
pub struct LoadedRuntimeRecoveryState {
    pub state: RuntimeRecoveryState,
    pub sources: RuntimeRecoverySurfaces,
}

#[derive(Debug, Clone, Serialize)]
pub struct RuntimeRecoveryView {
    pub status: &'static str,
    pub checkpoint: RuntimeCheckpointView,
    pub working_buffer: RuntimeWorkingBufferView,
}

#[derive(Debug, Clone, Serialize)]
pub struct RuntimeCheckpointView {
    pub path: String,
    pub status: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub origin: Option<RecoveryOrigin>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub captured_at_epoch_s: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub session_started_at_epoch_s: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub summary: Option<String>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub immediate_actions: Vec<String>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub key_files: Vec<String>,
}

#[derive(Debug, Clone, Serialize)]
pub struct RuntimeWorkingBufferView {
    pub path: String,
    pub status: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub origin: Option<RecoveryOrigin>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub captured_at_epoch_s: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub session_started_at_epoch_s: Option<u64>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub summary_lines: Vec<String>,
}

#[derive(Debug, Clone, Deserialize)]
struct NativeCloneRuntimeState {
    #[serde(rename = "schema_version")]
    _schema_version: u32,
    handoff: RuntimeHandoffState,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct NativeProfileRuntimeState {
    schema_version: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    memory_adapter_fingerprint: Option<String>,
    #[serde(default)]
    memory: Vec<RuntimeMemoryEntry>,
    #[serde(default)]
    memory_automation: RuntimeMemoryAutomationLedger,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct NativeRepoRuntimeState {
    schema_version: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    repo_memory_adapter_fingerprint: Option<String>,
    #[serde(default)]
    repo_memory: Vec<RuntimeMemoryEntry>,
    #[serde(default)]
    memory_automation: RuntimeMemoryAutomationLedger,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
struct CheckpointFile {
    schema_version: u32,
    origin: RecoveryOrigin,
    captured_at_epoch_s: u64,
    session_started_at_epoch_s: u64,
    summary: String,
    #[serde(default)]
    immediate_actions: Vec<String>,
    #[serde(default)]
    key_files: Vec<String>,
}

pub fn load_runtime_state(
    repo_root: &Path,
    layout: &StateLayout,
    locality_id: &str,
) -> Result<LoadedRuntimeState> {
    load_runtime_state_internal(repo_root, layout, locality_id)
}

/// Raw file contents loaded but not yet parsed/normalized.
/// Used by the compiled-state fast path to fingerprint sources
/// before deciding whether full normalization is needed.
pub struct RawRuntimeSources {
    pub profile_memory: RuntimeTextSurface,
    pub locality_memory: RuntimeTextSurface,
    pub branch_memory: RuntimeTextSurface,
    pub clone_memory: RuntimeTextSurface,
    pub execution_gates: LoadedRuntimeExecutionGates,
    pub handoff: RuntimeTextSurface,
    pub(crate) handoff_state: Option<RuntimeHandoffState>,
    pub recovery: LoadedRuntimeRecoveryState,
    pub pod_identity_active: bool,
}

pub fn load_raw_start_runtime_sources(
    repo_root: &Path,
    layout: &StateLayout,
    locality_id: &str,
    pod_identity_active: bool,
) -> Result<RawRuntimeSources> {
    let profile_memory = load_profile_memory_surface_internal(layout)?.source;
    let locality_memory = load_repo_runtime_surfaces_internal(layout, locality_id)?;
    let branch_memory = load_branch_memory_surface_internal(repo_root, layout, locality_id)?.source;
    let clone_memory = load_clone_memory_surface_internal(layout)?.source;
    let execution_gates = load_execution_gates(layout)?;
    let (handoff_state, handoff) = load_runtime_handoff_surface(layout)?;
    let recovery = load_runtime_recovery_state(layout)?;

    Ok(RawRuntimeSources {
        profile_memory,
        locality_memory: locality_memory.source,
        branch_memory,
        clone_memory,
        execution_gates,
        handoff,
        handoff_state: Some(handoff_state),
        recovery,
        pod_identity_active,
    })
}

pub fn normalize_raw_into_loaded(raw: RawRuntimeSources) -> Result<LoadedRuntimeState> {
    build_loaded_runtime_state(raw)
}

pub fn load_profile_memory_surface(layout: &StateLayout) -> Result<LoadedRuntimeMemorySurface> {
    load_profile_memory_surface_internal(layout)
}

pub fn load_locality_memory_surface(
    layout: &StateLayout,
    locality_id: &str,
) -> Result<LoadedRuntimeMemorySurface> {
    load_locality_memory_surface_internal(layout, locality_id)
}

pub fn load_branch_memory_surface(
    repo_root: &Path,
    layout: &StateLayout,
    locality_id: &str,
) -> Result<LoadedRuntimeMemorySurface> {
    load_branch_memory_surface_internal(repo_root, layout, locality_id)
}

pub fn load_clone_memory_surface(layout: &StateLayout) -> Result<LoadedRuntimeMemorySurface> {
    load_clone_memory_surface_internal(layout)
}

pub fn resolve_active_branch_memory_path(
    repo_root: &Path,
    layout: &StateLayout,
    locality_id: &str,
) -> Result<Option<PathBuf>> {
    let (path, active) = resolve_branch_memory_path(repo_root, layout, locality_id)?;
    Ok(active.then_some(path))
}

pub(crate) fn load_repo_memory_automation_record(
    layout: &StateLayout,
    locality_id: &str,
    dedupe_key: &str,
) -> Result<Option<RuntimeMemoryAutomationRecord>> {
    match read_native_repo_runtime_state(layout, locality_id)? {
        NativeRepoRuntimeLoad::Missing => Ok(None),
        NativeRepoRuntimeLoad::Loaded(state) => {
            Ok(state.memory_automation.get(dedupe_key).cloned())
        }
        NativeRepoRuntimeLoad::Invalid(_) => Ok(None),
    }
}

pub(crate) fn load_repo_memory_automation_ledger(
    layout: &StateLayout,
    locality_id: &str,
) -> Result<RuntimeMemoryAutomationLedger> {
    match read_native_repo_runtime_state(layout, locality_id)? {
        NativeRepoRuntimeLoad::Missing => Ok(RuntimeMemoryAutomationLedger::new()),
        NativeRepoRuntimeLoad::Loaded(state) => Ok(state.memory_automation),
        NativeRepoRuntimeLoad::Invalid(error) => Err(error),
    }
}

pub(crate) fn load_profile_memory_automation_record(
    layout: &StateLayout,
    dedupe_key: &str,
) -> Result<Option<RuntimeMemoryAutomationRecord>> {
    match read_native_profile_runtime_state(layout)? {
        NativeProfileRuntimeLoad::Missing => Ok(None),
        NativeProfileRuntimeLoad::Loaded(state) => {
            Ok(state.memory_automation.get(dedupe_key).cloned())
        }
        NativeProfileRuntimeLoad::Invalid(_) => Ok(None),
    }
}

pub(crate) fn load_profile_memory_automation_ledger(
    layout: &StateLayout,
) -> Result<RuntimeMemoryAutomationLedger> {
    match read_native_profile_runtime_state(layout)? {
        NativeProfileRuntimeLoad::Missing => Ok(RuntimeMemoryAutomationLedger::new()),
        NativeProfileRuntimeLoad::Loaded(state) => Ok(state.memory_automation),
        NativeProfileRuntimeLoad::Invalid(error) => Err(error),
    }
}

pub(crate) fn persist_repo_memory_automation_record(
    layout: &StateLayout,
    locality_id: &str,
    dedupe_key: &str,
    record: RuntimeMemoryAutomationRecord,
) -> Result<()> {
    persist_native_repo_runtime_state(layout, locality_id, |store| {
        store
            .memory_automation
            .insert(dedupe_key.to_owned(), record);
    })
}

pub(crate) fn persist_profile_memory_automation_record(
    layout: &StateLayout,
    dedupe_key: &str,
    record: RuntimeMemoryAutomationRecord,
) -> Result<()> {
    persist_native_profile_runtime_state(layout, |store| {
        store
            .memory_automation
            .insert(dedupe_key.to_owned(), record);
    })
}

pub fn load_execution_gates(layout: &StateLayout) -> Result<LoadedRuntimeExecutionGates> {
    let path = layout.state_db_path();
    let Some(db) = try_open_handoff_db(layout)? else {
        return Ok(LoadedRuntimeExecutionGates {
            source: RuntimeTextSurface::missing("execution_gates", &path),
            view: session_gates::build_view(layout, None),
        });
    };

    let raw = db::session_gates::read(db.conn())?;
    Ok(LoadedRuntimeExecutionGates {
        source: execution_gates_surface(layout, raw.as_ref())?,
        view: session_gates::build_view(layout, raw.clone()),
    })
}

pub fn load_runtime_recovery_state(layout: &StateLayout) -> Result<LoadedRuntimeRecoveryState> {
    let db_path = layout.state_db_path();
    let Some(db) = try_open_handoff_db(layout)? else {
        return Ok(LoadedRuntimeRecoveryState {
            state: RuntimeRecoveryState {
                checkpoint: None,
                working_buffer: None,
            },
            sources: RuntimeRecoverySurfaces {
                checkpoint: RuntimeRecoverySurface::missing("checkpoint", &db_path),
                working_buffer: RuntimeRecoverySurface::missing("working_buffer", &db_path),
            },
        });
    };

    let checkpoint = db::recovery::read_checkpoint(db.conn())?;
    let working_buffer = db::recovery::read_working_buffer(db.conn())?;

    let checkpoint_source = RuntimeRecoverySurface {
        kind: "checkpoint",
        path: db_path.clone(),
        status: if checkpoint.is_some() {
            "loaded"
        } else {
            "missing"
        },
    };
    let working_buffer_source = RuntimeRecoverySurface {
        kind: "working_buffer",
        path: db_path,
        status: if working_buffer.is_some() {
            "loaded"
        } else {
            "missing"
        },
    };

    Ok(LoadedRuntimeRecoveryState {
        state: RuntimeRecoveryState {
            checkpoint,
            working_buffer,
        },
        sources: RuntimeRecoverySurfaces {
            checkpoint: checkpoint_source,
            working_buffer: working_buffer_source,
        },
    })
}

pub fn recovery_view(recovery: &LoadedRuntimeRecoveryState) -> RuntimeRecoveryView {
    let checkpoint = checkpoint_view(
        &recovery.sources.checkpoint,
        recovery.state.checkpoint.as_ref(),
    );
    let working_buffer = working_buffer_view(
        &recovery.sources.working_buffer,
        recovery.state.working_buffer.as_ref(),
    );

    RuntimeRecoveryView {
        status: if recovery.state.checkpoint.is_some() || recovery.state.working_buffer.is_some() {
            "loaded"
        } else {
            "missing"
        },
        checkpoint,
        working_buffer,
    }
}

fn checkpoint_view(
    source: &RuntimeRecoverySurface,
    checkpoint: Option<&RuntimeCheckpointState>,
) -> RuntimeCheckpointView {
    if let Some(checkpoint) = checkpoint {
        return RuntimeCheckpointView {
            path: source.path.display().to_string(),
            status: source.status,
            origin: Some(checkpoint.origin),
            captured_at_epoch_s: Some(checkpoint.captured_at_epoch_s),
            session_started_at_epoch_s: Some(checkpoint.session_started_at_epoch_s),
            summary: Some(checkpoint.summary.clone()),
            immediate_actions: checkpoint.immediate_actions.clone(),
            key_files: checkpoint.key_files.clone(),
        };
    }

    RuntimeCheckpointView {
        path: source.path.display().to_string(),
        status: source.status,
        origin: None,
        captured_at_epoch_s: None,
        session_started_at_epoch_s: None,
        summary: None,
        immediate_actions: Vec::new(),
        key_files: Vec::new(),
    }
}

fn working_buffer_view(
    source: &RuntimeRecoverySurface,
    working_buffer: Option<&RuntimeWorkingBufferState>,
) -> RuntimeWorkingBufferView {
    if let Some(working_buffer) = working_buffer {
        return RuntimeWorkingBufferView {
            path: source.path.display().to_string(),
            status: source.status,
            origin: Some(working_buffer.origin),
            captured_at_epoch_s: Some(working_buffer.captured_at_epoch_s),
            session_started_at_epoch_s: Some(working_buffer.session_started_at_epoch_s),
            summary_lines: working_buffer.summary_lines.clone(),
        };
    }

    RuntimeWorkingBufferView {
        path: source.path.display().to_string(),
        status: source.status,
        origin: None,
        captured_at_epoch_s: None,
        session_started_at_epoch_s: None,
        summary_lines: Vec::new(),
    }
}

pub fn native_handoff_exists(layout: &StateLayout) -> Result<bool> {
    let Some(db) = try_open_handoff_db(layout)? else {
        return Ok(false);
    };
    db::handoff::exists(db.conn())
}

fn load_runtime_state_internal(
    repo_root: &Path,
    layout: &StateLayout,
    locality_id: &str,
) -> Result<LoadedRuntimeState> {
    let pod_identity_active = pod_identity::resolve_active_identity(layout)?.is_some();
    normalize_raw_into_loaded(load_raw_start_runtime_sources(
        repo_root,
        layout,
        locality_id,
        pod_identity_active,
    )?)
}

fn load_profile_memory_surface_internal(
    layout: &StateLayout,
) -> Result<LoadedRuntimeMemorySurface> {
    let source = read_optional_text("profile_memory", &layout.profile_memory_path())?;
    let native = read_native_profile_runtime_state(layout)?;
    load_runtime_memory_surface_from_native(
        source,
        native_profile_memory_surface(layout, native),
        |entries, adapter_contents| {
            persist_native_profile_memory_state(layout, entries, adapter_contents)
        },
    )
}

fn load_locality_memory_surface_internal(
    layout: &StateLayout,
    locality_id: &str,
) -> Result<LoadedRuntimeMemorySurface> {
    let source = read_optional_text(
        "locality_memory",
        &layout.locality_memory_path(locality_id)?,
    )?;
    let native = read_native_repo_runtime_state(layout, locality_id)?;
    load_runtime_memory_surface_from_native(
        source,
        native_repo_memory_surface(layout, locality_id, native)?,
        |entries, adapter_contents| {
            persist_native_repo_memory_state(layout, locality_id, entries, adapter_contents)
        },
    )
}

fn load_branch_memory_surface_internal(
    repo_root: &Path,
    layout: &StateLayout,
    locality_id: &str,
) -> Result<LoadedRuntimeMemorySurface> {
    let (path, active) = resolve_branch_memory_path(repo_root, layout, locality_id)?;
    let source = if active {
        read_optional_text("branch_memory", &path)?
    } else {
        RuntimeTextSurface::missing("branch_memory", &path)
    };
    validate_memory_surface(&source)?;
    Ok(LoadedRuntimeMemorySurface {
        entries: normalize_memory_scope(&source.content),
        source,
    })
}

fn load_clone_memory_surface_internal(layout: &StateLayout) -> Result<LoadedRuntimeMemorySurface> {
    let source = read_optional_text("clone_memory", &layout.clone_memory_path())?;
    validate_memory_surface(&source)?;
    Ok(LoadedRuntimeMemorySurface {
        entries: normalize_memory_scope(&source.content),
        source,
    })
}

fn resolve_branch_memory_path(
    repo_root: &Path,
    layout: &StateLayout,
    locality_id: &str,
) -> Result<(PathBuf, bool)> {
    let git = match handoff::read_git_state(repo_root, handoff::BranchMode::AllowDetachedHead) {
        Ok(git) => git,
        Err(_) => return Ok((layout.branch_memory_path(locality_id, "HEAD")?, false)),
    };
    let path = layout.branch_memory_path(locality_id, &git.branch)?;
    let active = git.branch != "HEAD"
        && !handoff::is_trunk_branch(&git.branch)
        && handoff::branch_merged_into_trunk(repo_root, &git.branch).is_none();
    Ok((path, active))
}

fn load_repo_runtime_surfaces_internal(
    layout: &StateLayout,
    locality_id: &str,
) -> Result<LoadedRuntimeMemorySurface> {
    let locality_memory_source = read_optional_text(
        "locality_memory",
        &layout.locality_memory_path(locality_id)?,
    )?;

    let native = read_native_repo_runtime_state(layout, locality_id)?;
    let native_memory = match native {
        NativeRepoRuntimeLoad::Missing => NativeMemorySurfaceLoad::Missing,
        NativeRepoRuntimeLoad::Loaded(state) => {
            native_repo_memory_from_state(layout.locality_runtime_state_path(locality_id)?, state)
        }
        NativeRepoRuntimeLoad::Invalid(error) => {
            if locality_memory_source.is_missing() {
                return Err(error);
            }
            NativeMemorySurfaceLoad::Missing
        }
    };

    let locality_memory = load_runtime_memory_surface_from_native(
        locality_memory_source,
        native_memory,
        |entries, adapter_contents| {
            persist_native_repo_memory_state(layout, locality_id, entries, adapter_contents)
        },
    )?;
    Ok(locality_memory)
}

fn load_runtime_memory_surface_from_native(
    source: RuntimeTextSurface,
    native: NativeMemorySurfaceLoad,
    persist_native: impl Fn(&[RuntimeMemoryEntry], Option<&str>) -> Result<()>,
) -> Result<LoadedRuntimeMemorySurface> {
    if !source.is_missing() {
        validate_memory_surface(&source)?;
        let entries = normalize_memory_scope(&source.content);
        let adapter_fingerprint = content_fingerprint(&source.content);
        let native_current = matches!(
            &native,
            NativeMemorySurfaceLoad::Loaded(state)
                if state.adapter_fingerprint.as_deref() == Some(adapter_fingerprint.as_str())
        );
        if !native_current {
            persist_native(&entries, Some(&source.content))?;
        }
        return Ok(LoadedRuntimeMemorySurface { source, entries });
    }

    match native {
        NativeMemorySurfaceLoad::Loaded(state) => Ok(LoadedRuntimeMemorySurface {
            source: state.source,
            entries: state.entries,
        }),
        NativeMemorySurfaceLoad::Missing => Ok(LoadedRuntimeMemorySurface {
            source,
            entries: Vec::new(),
        }),
        NativeMemorySurfaceLoad::Invalid(error) => Err(error),
    }
}

pub fn structured_memory_view(
    sources: &RuntimeSourceSurfaces,
) -> memory_entries::StructuredMemoryView {
    memory_entries::inspect_sources_with_branch_and_clone(
        some_if_present(&sources.profile_memory),
        some_if_present(&sources.locality_memory),
        some_if_present(&sources.branch_memory),
        some_if_present(&sources.clone_memory),
    )
}

pub(crate) fn memory_state_from_sources(sources: &RuntimeSourceSurfaces) -> RuntimeMemoryState {
    RuntimeMemoryState {
        profile: normalize_memory_scope(&sources.profile_memory.content),
        locality: normalize_memory_scope(&sources.locality_memory.content),
        branch: normalize_memory_scope(&sources.branch_memory.content),
        clone: normalize_memory_scope(&sources.clone_memory.content),
    }
}

// --- DB helpers ---

/// Open the state DB and migrate any legacy handoff/recovery JSON files.
///
/// Uses `StateDb::open` (not `open_for_layout`) so this function only
/// migrates its own surfaces (handoff, checkpoint, working_buffer).
///
/// Creates the DB if it does not exist. Use `try_open_handoff_db` for read
/// paths that should not create the DB when there is nothing to read.
fn open_handoff_db(layout: &StateLayout) -> Result<db::StateDb> {
    let pods_root = layout.pods_root();
    let db = db::StateDb::open(&layout.state_db_path(), Some(&pods_root))?;
    migrate_handoff_json(&db, layout)?;
    Ok(db)
}

/// Open the state DB only if it already exists or legacy JSON needs migration.
/// Returns `None` when neither is present, avoiding DB creation on read-only
/// clone-local trees that have no handoff or recovery data.
fn try_open_handoff_db(layout: &StateLayout) -> Result<Option<db::StateDb>> {
    db::StateDb::try_open_with_migration(
        layout,
        || {
            layout.clone_runtime_state_path().exists()
                || layout.clone_checkpoint_path().exists()
                || layout.clone_working_buffer_path().exists()
        },
        |db| migrate_handoff_json(db, layout),
    )
}

/// Import legacy handoff (`runtime_state/state.json`), checkpoint
/// (`checkpoint.json`), and working-buffer (`working_buffer.md`) files into
/// the DB if they still exist.
///
/// Idempotent: writes are INSERT OR REPLACE, and source files are renamed to
/// `.migrated` only after all writes succeed. Session-state JSON is NOT
/// touched by this function.
fn migrate_handoff_json(db: &db::StateDb, layout: &StateLayout) -> Result<()> {
    migrate_handoff_state_json(db, layout)?;
    migrate_checkpoint_json(db, layout)?;
    migrate_working_buffer_md(db, layout)?;
    Ok(())
}

fn migrate_handoff_state_json(db: &db::StateDb, layout: &StateLayout) -> Result<()> {
    let path = layout.clone_runtime_state_path();
    let contents = match fs::read_to_string(&path) {
        Ok(c) => c,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
        Err(e) => return Err(e).with_context(|| format!("failed to read {}", path.display())),
    };

    // Only import if the DB has no handoff yet — a surviving legacy file
    // after a partial rename must not overwrite newer DB-backed state.
    if db::handoff::read(db.conn())?.is_none() {
        let native: NativeCloneRuntimeState = serde_json::from_str(&contents)
            .with_context(|| format!("failed to parse {}", path.display()))?;
        db::handoff::write(db.conn(), &native.handoff)?;
    }

    let mut migrated = path.as_os_str().to_owned();
    migrated.push(".migrated");
    fs::rename(&path, Path::new(&migrated))
        .with_context(|| format!("failed to rename {} to .migrated", path.display()))?;
    Ok(())
}

fn migrate_checkpoint_json(db: &db::StateDb, layout: &StateLayout) -> Result<()> {
    let path = layout.clone_checkpoint_path();
    let contents = match fs::read_to_string(&path) {
        Ok(c) => c,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
        Err(e) => return Err(e).with_context(|| format!("failed to read {}", path.display())),
    };

    // Only import if the DB has no checkpoint yet — a surviving legacy file
    // after a partial rename must not overwrite newer DB-backed state.
    if !contents.trim().is_empty() && db::recovery::read_checkpoint(db.conn())?.is_none() {
        let checkpoint: CheckpointFile = serde_json::from_str(&contents)
            .with_context(|| format!("failed to parse {}", path.display()))?;
        if checkpoint.schema_version != CHECKPOINT_SCHEMA_VERSION {
            bail!(
                "{} has unsupported checkpoint schema version {}; expected {}",
                path.display(),
                checkpoint.schema_version,
                CHECKPOINT_SCHEMA_VERSION
            );
        }
        validate_checkpoint(&checkpoint, &path)?;

        db::recovery::write_checkpoint(
            db.conn(),
            &RuntimeCheckpointState {
                origin: checkpoint.origin,
                captured_at_epoch_s: checkpoint.captured_at_epoch_s,
                session_started_at_epoch_s: checkpoint.session_started_at_epoch_s,
                summary: checkpoint.summary,
                immediate_actions: checkpoint.immediate_actions,
                key_files: checkpoint.key_files,
            },
        )?;
    }

    let mut migrated = path.as_os_str().to_owned();
    migrated.push(".migrated");
    fs::rename(&path, Path::new(&migrated))
        .with_context(|| format!("failed to rename {} to .migrated", path.display()))?;
    Ok(())
}

fn migrate_working_buffer_md(db: &db::StateDb, layout: &StateLayout) -> Result<()> {
    let path = layout.clone_working_buffer_path();
    let contents = match fs::read_to_string(&path) {
        Ok(c) => c,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
        Err(e) => return Err(e).with_context(|| format!("failed to read {}", path.display())),
    };

    // Only import if the DB has no working buffer yet — a surviving legacy file
    // after a partial rename must not overwrite newer DB-backed state.
    if !contents.trim().is_empty() && db::recovery::read_working_buffer(db.conn())?.is_none() {
        if contents.len() > MAX_WORKING_BUFFER_BYTES {
            bail!(
                "{} exceeds the working-buffer size limit of {} bytes",
                path.display(),
                MAX_WORKING_BUFFER_BYTES
            );
        }

        let provenance = parse_recovery_provenance(
            &handoff::extract_bulleted_section(&contents, "Provenance"),
            &path,
        )?;
        let summary_lines = handoff::extract_bulleted_section(&contents, "Recent Exchange Summary");
        if summary_lines.is_empty() {
            bail!(
                "{} is missing `## Recent Exchange Summary` bullet items",
                path.display()
            );
        }
        if summary_lines.len() > MAX_WORKING_BUFFER_SUMMARY_LINES {
            bail!(
                "{} has {} working-buffer summary lines; the limit is {}",
                path.display(),
                summary_lines.len(),
                MAX_WORKING_BUFFER_SUMMARY_LINES
            );
        }

        db::recovery::write_working_buffer(
            db.conn(),
            &RuntimeWorkingBufferState {
                origin: provenance.origin,
                captured_at_epoch_s: provenance.captured_at_epoch_s,
                session_started_at_epoch_s: provenance.session_started_at_epoch_s,
                summary_lines,
            },
        )?;
    }

    let mut migrated = path.as_os_str().to_owned();
    migrated.push(".migrated");
    fs::rename(&path, Path::new(&migrated))
        .with_context(|| format!("failed to rename {} to .migrated", path.display()))?;
    Ok(())
}

pub fn persist_canonical_handoff_state(
    layout: &StateLayout,
    handoff: &RuntimeHandoffState,
) -> Result<u64> {
    let db = open_handoff_db(layout)?;
    db::handoff::write(db.conn(), handoff)
}

pub fn load_canonical_handoff_surface(layout: &StateLayout) -> Result<RuntimeTextSurface> {
    let Some(db) = try_open_handoff_db(layout)? else {
        return Ok(RuntimeTextSurface::missing(
            "handoff",
            &layout.state_db_path(),
        ));
    };
    match db::handoff::read(db.conn())? {
        Some(handoff) => Ok(native_handoff_surface(layout, &handoff)),
        None => Ok(RuntimeTextSurface::missing(
            "handoff",
            &layout.state_db_path(),
        )),
    }
}

fn build_loaded_runtime_state(raw: RawRuntimeSources) -> Result<LoadedRuntimeState> {
    let RawRuntimeSources {
        profile_memory,
        locality_memory,
        branch_memory,
        clone_memory,
        execution_gates,
        handoff,
        handoff_state,
        recovery,
        pod_identity_active,
    } = raw;

    validate_memory_surface(&profile_memory)?;
    validate_memory_surface(&locality_memory)?;
    validate_memory_surface(&branch_memory)?;
    validate_memory_surface(&clone_memory)?;

    let state = RuntimeState {
        memory: RuntimeMemoryState {
            profile: normalize_memory_scope(&profile_memory.content),
            locality: normalize_memory_scope(&locality_memory.content),
            branch: normalize_memory_scope(&branch_memory.content),
            clone: normalize_memory_scope(&clone_memory.content),
        },
        handoff: handoff_state.unwrap_or_else(|| normalize_handoff(&handoff.content)),
    };

    Ok(LoadedRuntimeState {
        state,
        sources: RuntimeSourceSurfaces {
            profile_memory,
            locality_memory,
            branch_memory,
            clone_memory,
            execution_gates: execution_gates.source.clone(),
            handoff,
        },
        execution_gates,
        recovery,
        pod_identity_active,
    })
}

fn read_optional_text(kind: &'static str, path: &Path) -> Result<RuntimeTextSurface> {
    match fs::read_to_string(path) {
        Ok(content) => Ok(RuntimeTextSurface {
            kind,
            path: path.to_path_buf(),
            status: if content.is_empty() {
                RuntimeTextSurfaceStatus::Empty
            } else {
                RuntimeTextSurfaceStatus::Loaded
            },
            content,
            migrated_from: None,
        }),
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
            Ok(RuntimeTextSurface::missing(kind, path))
        }
        Err(error) => Err(error).with_context(|| format!("failed to read {}", path.display())),
    }
}

fn load_runtime_handoff_surface(
    layout: &StateLayout,
) -> Result<(RuntimeHandoffState, RuntimeTextSurface)> {
    let Some(db) = try_open_handoff_db(layout)? else {
        return Ok((
            RuntimeHandoffState::default(),
            RuntimeTextSurface::missing("handoff", &layout.state_db_path()),
        ));
    };
    match db::handoff::read(db.conn())? {
        Some(handoff) => {
            let surface = native_handoff_surface(layout, &handoff);
            Ok((handoff, surface))
        }
        None => Ok((
            RuntimeHandoffState::default(),
            RuntimeTextSurface::missing("handoff", &layout.state_db_path()),
        )),
    }
}

fn execution_gates_surface(
    layout: &StateLayout,
    raw: Option<&session_gates::ExecutionGateStateFile>,
) -> Result<RuntimeTextSurface> {
    let Some(raw) = raw else {
        return Ok(RuntimeTextSurface::missing(
            "execution_gates",
            &layout.state_db_path(),
        ));
    };

    Ok(RuntimeTextSurface {
        kind: "execution_gates",
        path: layout.state_db_path(),
        status: RuntimeTextSurfaceStatus::LoadedNative,
        content: serde_json::to_string_pretty(raw)?,
        migrated_from: None,
    })
}

fn native_handoff_surface(
    layout: &StateLayout,
    handoff: &RuntimeHandoffState,
) -> RuntimeTextSurface {
    RuntimeTextSurface {
        kind: "handoff",
        path: layout.state_db_path(),
        status: RuntimeTextSurfaceStatus::LoadedNative,
        content: render_handoff_markdown(handoff),
        migrated_from: None,
    }
}

fn native_profile_memory_surface(
    layout: &StateLayout,
    native: NativeProfileRuntimeLoad,
) -> NativeMemorySurfaceLoad {
    match native {
        NativeProfileRuntimeLoad::Missing => NativeMemorySurfaceLoad::Missing,
        NativeProfileRuntimeLoad::Invalid(error) => NativeMemorySurfaceLoad::Invalid(error),
        NativeProfileRuntimeLoad::Loaded(state) => {
            NativeMemorySurfaceLoad::Loaded(NativeMemorySurfaceState {
                source: RuntimeTextSurface {
                    kind: "profile_memory",
                    path: layout.profile_runtime_state_path(),
                    status: RuntimeTextSurfaceStatus::LoadedNative,
                    content: render_memory_markdown("Profile Memory", &state.memory),
                    migrated_from: None,
                },
                adapter_fingerprint: state.memory_adapter_fingerprint,
                entries: state.memory,
            })
        }
    }
}

fn native_repo_memory_surface(
    layout: &StateLayout,
    locality_id: &str,
    native: NativeRepoRuntimeLoad,
) -> Result<NativeMemorySurfaceLoad> {
    match native {
        NativeRepoRuntimeLoad::Missing => Ok(NativeMemorySurfaceLoad::Missing),
        NativeRepoRuntimeLoad::Invalid(error) => Ok(NativeMemorySurfaceLoad::Invalid(error)),
        NativeRepoRuntimeLoad::Loaded(state) => Ok(native_repo_memory_from_state(
            layout.locality_runtime_state_path(locality_id)?,
            state,
        )),
    }
}

fn native_repo_memory_from_state(
    path: PathBuf,
    state: NativeRepoRuntimeState,
) -> NativeMemorySurfaceLoad {
    NativeMemorySurfaceLoad::Loaded(NativeMemorySurfaceState {
        source: RuntimeTextSurface {
            kind: "locality_memory",
            path,
            status: RuntimeTextSurfaceStatus::LoadedNative,
            content: render_memory_markdown("Project Memory", &state.repo_memory),
            migrated_from: None,
        },
        adapter_fingerprint: state.repo_memory_adapter_fingerprint,
        entries: state.repo_memory,
    })
}

fn read_native_profile_runtime_state(layout: &StateLayout) -> Result<NativeProfileRuntimeLoad> {
    let path = layout.profile_runtime_state_path();
    let contents = match fs::read_to_string(&path) {
        Ok(contents) => contents,
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
            return Ok(NativeProfileRuntimeLoad::Missing)
        }
        Err(error) => {
            return Err(error).with_context(|| format!("failed to read {}", path.display()))
        }
    };

    let parsed = serde_json::from_str::<NativeProfileRuntimeState>(&contents)
        .with_context(|| format!("failed to parse {}", path.display()));
    match parsed {
        Ok(state) if state.schema_version == NATIVE_PROFILE_RUNTIME_SCHEMA_VERSION => {
            Ok(NativeProfileRuntimeLoad::Loaded(state))
        }
        Ok(_) => Ok(NativeProfileRuntimeLoad::Invalid(anyhow!(
            "{} has unsupported schema version",
            path.display()
        ))),
        Err(error) => Ok(NativeProfileRuntimeLoad::Invalid(error)),
    }
}

fn read_native_repo_runtime_state(
    layout: &StateLayout,
    locality_id: &str,
) -> Result<NativeRepoRuntimeLoad> {
    let path = layout.repo_runtime_state_path(locality_id)?;
    let contents = match fs::read_to_string(&path) {
        Ok(contents) => contents,
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
            return Ok(NativeRepoRuntimeLoad::Missing)
        }
        Err(error) => {
            return Err(error).with_context(|| format!("failed to read {}", path.display()))
        }
    };
    if contents.trim().is_empty() {
        return Ok(NativeRepoRuntimeLoad::Missing);
    }

    let parsed = serde_json::from_str::<NativeRepoRuntimeState>(&contents)
        .with_context(|| format!("failed to parse {}", path.display()));
    match parsed {
        Ok(state) if state.schema_version == NATIVE_REPO_RUNTIME_SCHEMA_VERSION => {
            Ok(NativeRepoRuntimeLoad::Loaded(state))
        }
        Ok(_) => Ok(NativeRepoRuntimeLoad::Invalid(anyhow!(
            "{} has unsupported schema version",
            path.display()
        ))),
        Err(error) => Ok(NativeRepoRuntimeLoad::Invalid(error)),
    }
}

fn persist_native_profile_memory_state(
    layout: &StateLayout,
    memory: &[RuntimeMemoryEntry],
    adapter_contents: Option<&str>,
) -> Result<()> {
    persist_native_profile_runtime_state(layout, |store| {
        store.memory_adapter_fingerprint = adapter_contents.map(content_fingerprint);
        store.memory = memory.to_vec();
    })
}

fn persist_native_repo_memory_state(
    layout: &StateLayout,
    locality_id: &str,
    memory: &[RuntimeMemoryEntry],
    adapter_contents: Option<&str>,
) -> Result<()> {
    persist_native_repo_runtime_state(layout, locality_id, |store| {
        store.repo_memory_adapter_fingerprint = adapter_contents.map(content_fingerprint);
        store.repo_memory = memory.to_vec();
    })
}

fn empty_native_repo_runtime_state() -> NativeRepoRuntimeState {
    NativeRepoRuntimeState {
        schema_version: NATIVE_REPO_RUNTIME_SCHEMA_VERSION,
        repo_memory_adapter_fingerprint: None,
        repo_memory: Vec::new(),
        memory_automation: BTreeMap::new(),
    }
}

fn empty_native_profile_runtime_state() -> NativeProfileRuntimeState {
    NativeProfileRuntimeState {
        schema_version: NATIVE_PROFILE_RUNTIME_SCHEMA_VERSION,
        memory_adapter_fingerprint: None,
        memory: Vec::new(),
        memory_automation: BTreeMap::new(),
    }
}

fn persist_native_profile_runtime_state(
    layout: &StateLayout,
    mutate: impl FnOnce(&mut NativeProfileRuntimeState),
) -> Result<()> {
    let mut store = match read_native_profile_runtime_state(layout)? {
        NativeProfileRuntimeLoad::Loaded(store) => store,
        NativeProfileRuntimeLoad::Missing | NativeProfileRuntimeLoad::Invalid(_) => {
            empty_native_profile_runtime_state()
        }
    };
    mutate(&mut store);
    store.schema_version = NATIVE_PROFILE_RUNTIME_SCHEMA_VERSION;

    let path = layout.profile_runtime_state_path();
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)
            .with_context(|| format!("failed to create directory {}", parent.display()))?;
    }
    path_write::replace_text(&path, &serde_json::to_string_pretty(&store)?, None)
        .with_context(|| format!("failed to write {}", path.display()))
}

/// Phase 1 keeps repo runtime mirrors on a simple read-modify-write path.
/// That is sufficient for the current single-process CLI flow, but later
/// write-path cutovers should add explicit cross-process locking or another
/// atomic merge strategy before treating this as a shared mutation surface.
fn persist_native_repo_runtime_state(
    layout: &StateLayout,
    locality_id: &str,
    mutate: impl FnOnce(&mut NativeRepoRuntimeState),
) -> Result<()> {
    let mut store = match read_native_repo_runtime_state(layout, locality_id)? {
        NativeRepoRuntimeLoad::Loaded(store) => store,
        NativeRepoRuntimeLoad::Missing | NativeRepoRuntimeLoad::Invalid(_) => {
            empty_native_repo_runtime_state()
        }
    };
    store.schema_version = NATIVE_REPO_RUNTIME_SCHEMA_VERSION;
    mutate(&mut store);

    let path = layout.repo_runtime_state_path(locality_id)?;
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)
            .with_context(|| format!("failed to create directory {}", parent.display()))?;
    }
    path_write::replace_text(&path, &serde_json::to_string_pretty(&store)?, None)
        .with_context(|| format!("failed to write {}", path.display()))
}

fn content_fingerprint(contents: &str) -> String {
    let mut hasher = Sha256::new();
    hasher.update(contents.as_bytes());
    format!("{:x}", hasher.finalize())
}

struct RecoveryProvenance {
    origin: RecoveryOrigin,
    captured_at_epoch_s: u64,
    session_started_at_epoch_s: u64,
}

fn parse_recovery_provenance(items: &[String], path: &Path) -> Result<RecoveryProvenance> {
    let mut values = BTreeMap::new();
    for item in items {
        let (key, value) = item.split_once(':').ok_or_else(|| {
            anyhow!(
                "{} has an invalid recovery provenance item `{item}`; expected `Key: value`",
                path.display()
            )
        })?;
        values.insert(key.trim().to_ascii_lowercase(), value.trim().to_owned());
    }

    let origin = parse_recovery_origin(
        values
            .remove("origin")
            .ok_or_else(|| anyhow!("{} is missing `Origin` in `## Provenance`", path.display()))?,
        path,
    )?;
    let captured_at_epoch_s = parse_required_epoch(
        values.remove("captured at epoch").ok_or_else(|| {
            anyhow!(
                "{} is missing `Captured At Epoch` in `## Provenance`",
                path.display()
            )
        })?,
        path,
        "Captured At Epoch",
    )?;
    let session_started_at_epoch_s = parse_required_epoch(
        values.remove("session started at epoch").ok_or_else(|| {
            anyhow!(
                "{} is missing `Session Started At Epoch` in `## Provenance`",
                path.display()
            )
        })?,
        path,
        "Session Started At Epoch",
    )?;
    if !values.is_empty() {
        let unexpected = values.keys().cloned().collect::<Vec<_>>().join(", ");
        bail!(
            "{} has unsupported recovery provenance keys: {}",
            path.display(),
            unexpected
        );
    }

    Ok(RecoveryProvenance {
        origin,
        captured_at_epoch_s,
        session_started_at_epoch_s,
    })
}

fn parse_recovery_origin(value: String, path: &Path) -> Result<RecoveryOrigin> {
    match value.as_str() {
        "compaction" => Ok(RecoveryOrigin::Compaction),
        "risky_pause" => Ok(RecoveryOrigin::RiskyPause),
        "manual" => Ok(RecoveryOrigin::Manual),
        _ => bail!(
            "{} has unsupported recovery origin `{value}`; expected `compaction`, `risky_pause`, or `manual`",
            path.display()
        ),
    }
}

fn parse_required_epoch(value: String, path: &Path, label: &str) -> Result<u64> {
    let parsed = value.parse::<u64>().with_context(|| {
        format!(
            "{} has an invalid `{label}` value `{value}`; expected an unsigned epoch second",
            path.display()
        )
    })?;
    if parsed == 0 {
        bail!(
            "{} has invalid `{label}` value `0`; expected a positive epoch second",
            path.display()
        );
    }
    Ok(parsed)
}

fn validate_checkpoint(checkpoint: &CheckpointFile, path: &Path) -> Result<()> {
    if checkpoint.captured_at_epoch_s == 0 {
        bail!(
            "{} has invalid `captured_at_epoch_s` value `0`; expected a positive epoch second",
            path.display()
        );
    }
    if checkpoint.session_started_at_epoch_s == 0 {
        bail!(
            "{} has invalid `session_started_at_epoch_s` value `0`; expected a positive epoch second",
            path.display()
        );
    }

    let summary = checkpoint.summary.trim();
    if summary.is_empty() {
        bail!("{} has an empty checkpoint summary", path.display());
    }
    if summary.len() > MAX_CHECKPOINT_SUMMARY_CHARS {
        bail!(
            "{} has a checkpoint summary longer than {} characters",
            path.display(),
            MAX_CHECKPOINT_SUMMARY_CHARS
        );
    }

    validate_recovery_items(
        "immediate_actions",
        &checkpoint.immediate_actions,
        MAX_RECOVERY_IMMEDIATE_ACTIONS,
        path,
    )?;
    validate_recovery_items(
        "key_files",
        &checkpoint.key_files,
        MAX_RECOVERY_KEY_FILES,
        path,
    )?;
    Ok(())
}

fn validate_recovery_items(
    label: &str,
    items: &[String],
    max_items: usize,
    path: &Path,
) -> Result<()> {
    if items.len() > max_items {
        bail!(
            "{} has {} `{label}` entries; the limit is {}",
            path.display(),
            items.len(),
            max_items
        );
    }
    if items.iter().any(|item| item.trim().is_empty()) {
        bail!(
            "{} has an empty `{label}` entry; recovery artifacts must use explicit non-empty items",
            path.display()
        );
    }
    Ok(())
}

fn validate_memory_surface(surface: &RuntimeTextSurface) -> Result<()> {
    let report = memory_entries::parse_document(&surface.content);
    if report.diagnostics.is_empty() {
        return Ok(());
    }

    Err(anyhow!(
        "{} has invalid structured CCD memory entries: {}",
        surface.path.display(),
        report.diagnostics.join("; ")
    ))
}

fn some_if_present(surface: &RuntimeTextSurface) -> Option<&str> {
    if surface.is_missing() {
        None
    } else {
        Some(&surface.content)
    }
}

pub(crate) fn render_handoff_markdown(state: &RuntimeHandoffState) -> String {
    // The markdown handoff is an active-session export view. Inactive items stay
    // only in native runtime state so the authored/export surface remains a
    // concise next-session handoff instead of a full lifecycle ledger.
    let mut body = String::new();
    if !state.title.is_empty() {
        body.push_str("# ");
        body.push_str(&state.title);
        body.push_str("\n\n");
    }
    append_numbered_section(&mut body, "Immediate Actions", &state.immediate_actions);
    append_bulleted_section(&mut body, "Completed State", &state.completed_state);
    append_bulleted_section(
        &mut body,
        "Operational Guardrails",
        &state.operational_guardrails,
    );
    append_bulleted_section(
        &mut body,
        handoff::KEY_FILES_SECTION_TITLE,
        &state.key_files,
    );
    append_numbered_section(&mut body, "Definition of Done", &state.definition_of_done);
    body
}

fn append_bulleted_section(body: &mut String, title: &str, items: &[RuntimeHandoffItem]) {
    let active_items = items
        .iter()
        .filter(|item| item.lifecycle.is_active())
        .collect::<Vec<_>>();
    if active_items.is_empty() {
        return;
    }
    if !body.is_empty() {
        body.push('\n');
    }
    body.push_str("## ");
    body.push_str(title);
    body.push_str("\n\n");
    for item in active_items {
        body.push_str("- ");
        body.push_str(&item.text);
        body.push('\n');
    }
}

fn append_numbered_section(body: &mut String, title: &str, items: &[RuntimeHandoffItem]) {
    let active_items = items
        .iter()
        .filter(|item| item.lifecycle.is_active())
        .collect::<Vec<_>>();
    if active_items.is_empty() {
        return;
    }
    if !body.is_empty() {
        body.push('\n');
    }
    body.push_str("## ");
    body.push_str(title);
    body.push_str("\n\n");
    for (i, item) in active_items.iter().enumerate() {
        body.push_str(&format!("{}. {}\n", i + 1, item.text));
    }
}

enum NativeProfileRuntimeLoad {
    Missing,
    Loaded(NativeProfileRuntimeState),
    Invalid(anyhow::Error),
}

enum NativeRepoRuntimeLoad {
    Missing,
    Loaded(NativeRepoRuntimeState),
    Invalid(anyhow::Error),
}

struct NativeMemorySurfaceState {
    source: RuntimeTextSurface,
    adapter_fingerprint: Option<String>,
    entries: Vec<RuntimeMemoryEntry>,
}

enum NativeMemorySurfaceLoad {
    Missing,
    Loaded(NativeMemorySurfaceState),
    Invalid(anyhow::Error),
}

fn render_memory_markdown(title: &str, entries: &[RuntimeMemoryEntry]) -> String {
    if entries.is_empty() {
        return String::new();
    }

    let mut body = format!("# {title}\n");
    let narrative = entries
        .iter()
        .filter(|entry| matches!(&entry.origin, RuntimeMemoryOrigin::Narrative))
        .map(RuntimeMemoryEntry::projection_text)
        .collect::<Vec<_>>();
    if !narrative.is_empty() {
        body.push_str("\n## Active Notes\n\n");
        for item in &narrative {
            body.push_str("- ");
            body.push_str(item);
            body.push('\n');
        }
    }

    let structured = entries
        .iter()
        .filter_map(RuntimeMemoryEntry::as_structured_entry)
        .collect::<Vec<_>>();
    if !structured.is_empty() {
        if !body.ends_with("\n\n") {
            body.push('\n');
        }
        for (index, entry) in structured.iter().enumerate() {
            if index > 0 {
                body.push('\n');
            }
            body.push_str(&memory_file::render_entry_block(entry));
            body.push('\n');
        }
    }

    body
}

fn normalize_memory_scope(contents: &str) -> Vec<RuntimeMemoryEntry> {
    let report = memory_entries::parse_document(contents);
    let mut items = extract_narrative_memory_lines(&strip_ccd_memory_blocks(contents))
        .into_iter()
        .map(RuntimeMemoryEntry::narrative)
        .collect::<Vec<_>>();

    if report.diagnostics.is_empty() {
        items.extend(
            report
                .entries
                .into_iter()
                .map(RuntimeMemoryEntry::from_structured_entry),
        );
    }

    items
}

fn runtime_memory_lifecycle(state: &str) -> RuntimeLifecycle {
    if state == "active" {
        RuntimeLifecycle::Active
    } else {
        RuntimeLifecycle::Inactive
    }
}

pub(crate) fn normalize_handoff(contents: &str) -> RuntimeHandoffState {
    if contents.is_empty() {
        return RuntimeHandoffState::default();
    }

    let title = contents
        .lines()
        .find_map(|line| line.strip_prefix("# ").map(str::trim))
        .unwrap_or_default()
        .to_owned();

    RuntimeHandoffState {
        title,
        immediate_actions: handoff::extract_numbered_section(contents, "Immediate Actions")
            .into_iter()
            .map(active_handoff_item)
            .collect(),
        completed_state: handoff::extract_bulleted_section(contents, "Completed State")
            .into_iter()
            .filter(|line| !starts_with_inactive_marker(line))
            .map(active_handoff_item)
            .collect(),
        operational_guardrails: handoff::extract_bulleted_section(
            contents,
            "Operational Guardrails",
        )
        .into_iter()
        .map(active_handoff_item)
        .collect(),
        key_files: handoff::extract_bulleted_section(contents, handoff::KEY_FILES_SECTION_TITLE)
            .into_iter()
            .map(active_handoff_item)
            .collect(),
        definition_of_done: handoff::extract_numbered_section(contents, "Definition of Done")
            .into_iter()
            .map(active_handoff_item)
            .collect(),
    }
}

fn active_handoff_item(text: String) -> RuntimeHandoffItem {
    RuntimeHandoffItem {
        text,
        lifecycle: RuntimeLifecycle::Active,
    }
}

fn extract_narrative_memory_lines(contents: &str) -> Vec<String> {
    extract_generic_markdown_lines(
        contents,
        MarkdownLineExtraction {
            inactive_headings: &[
                "archived",
                "archive",
                "historical",
                "history",
                "superseded",
                "stale",
            ],
            inactive_item_prefixes: &["archived:", "stale:", "superseded:"],
            inline_skip_keywords: &["archived:", "stale:"],
        },
    )
}

fn strip_ccd_memory_blocks(contents: &str) -> String {
    strip_fenced_blocks(contents, CCD_MEMORY_OPENING_FENCE, GENERIC_CLOSING_FENCE)
}

fn starts_with_inactive_marker(line: &str) -> bool {
    starts_with_any_prefix_case_insensitive(line, &["archived:", "stale:", "superseded:"])
}

#[cfg(test)]
mod tests {
    use std::fs;
    use std::path::PathBuf;

    use serde_json::Value as JsonValue;
    use tempfile::tempdir;

    use super::*;
    use crate::profile::ProfileName;

    #[test]
    fn loads_runtime_state_from_current_authored_memory_and_focus_surfaces() {
        let temp = tempdir().expect("tempdir");
        let layout = StateLayout::new(
            temp.path().join(".ccd"),
            temp.path().join("repo/.git/ccd"),
            ProfileName::new("main").expect("profile"),
        );
        let locality_id = "ccdrepo_runtime";

        fs::create_dir_all(layout.profile_root()).expect("profile root");
        fs::create_dir_all(layout.repo_overlay_root(locality_id).expect("repo overlay"))
            .expect("repo overlay dir");
        fs::create_dir_all(layout.clone_profile_root()).expect("clone profile root");

        fs::write(
            layout.profile_memory_path(),
            concat!(
                "# Profile Memory\n\n",
                "- Prefer bounded projections.\n\n",
                "```ccd-memory\n",
                "id = \"mem_1\"\n",
                "type = \"rule\"\n",
                "state = \"active\"\n",
                "created_at = \"2026-03-10T10:00:00Z\"\n",
                "last_touched_session = 1\n",
                "origin = \"manual\"\n",
                "content = \"Keep cache artifacts disposable.\"\n",
                "```\n"
            ),
        )
        .expect("profile memory");
        fs::write(
            layout
                .locality_memory_path(locality_id)
                .expect("locality memory path"),
            "# Repo Memory\n\n- Keep start deterministic.\n",
        )
        .expect("repo memory");
        fs::write(
            layout.handoff_path(),
            concat!(
                "# Next Session: Runtime state\n\n",
                "## Immediate Actions\n\n",
                "1. Add runtime-state scaffolding.\n\n",
                "## Current System State\n\n",
                "- Working tree is dirty.\n"
            ),
        )
        .expect("handoff");

        let loaded =
            load_runtime_state(temp.path(), &layout, locality_id).expect("load runtime state");

        assert_eq!(
            loaded.sources.profile_memory.status,
            RuntimeTextSurfaceStatus::Loaded
        );
        assert_eq!(
            loaded
                .state
                .memory
                .profile
                .iter()
                .map(RuntimeMemoryEntry::projection_text)
                .collect::<Vec<_>>(),
            vec![
                "Prefer bounded projections.".to_owned(),
                "rule: Keep cache artifacts disposable.".to_owned(),
            ]
        );
        assert_eq!(
            loaded.sources.handoff.status,
            RuntimeTextSurfaceStatus::Missing
        );
        assert!(loaded.state.handoff.title.is_empty());
        assert!(loaded.state.handoff.immediate_actions.is_empty());
    }

    #[test]
    fn runtime_memory_surface_preserves_structured_entry_metadata() {
        let temp = tempdir().expect("tempdir");
        let layout = StateLayout::new(
            temp.path().join(".ccd"),
            temp.path().join("repo/.git/ccd"),
            ProfileName::new("main").expect("profile"),
        );

        fs::create_dir_all(layout.profile_root()).expect("profile root");
        fs::write(
            layout.profile_memory_path(),
            concat!(
                "# Profile Memory\n\n",
                "```ccd-memory\n",
                "id = \"mem_profile_rule\"\n",
                "type = \"rule\"\n",
                "state = \"promotion_candidate\"\n",
                "created_at = \"2026-03-10T10:00:00Z\"\n",
                "last_touched_session = 7\n",
                "origin = \"manual\"\n",
                "decay_class = \"stable\"\n",
                "expires_at = \"2026-06-01T00:00:00Z\"\n",
                "tags = [\"rust\", \"lint\"]\n",
                "source_ref = \"docs/spec.md#L1\"\n",
                "supersedes = [\"mem_old\"]\n",
                "content = \"Keep runtime mutations deterministic.\"\n",
                "```\n",
            ),
        )
        .expect("profile memory");

        let loaded = load_profile_memory_surface(&layout).expect("load profile memory");
        let entry = loaded
            .entries
            .iter()
            .find(|entry| entry.structured_id() == Some("mem_profile_rule"))
            .expect("structured memory entry");
        let round_trip = entry
            .as_structured_entry()
            .expect("round-trip structured entry");

        assert_eq!(loaded.source.status, RuntimeTextSurfaceStatus::Loaded);
        assert_eq!(entry.structured_state(), Some("promotion_candidate"));
        assert_eq!(round_trip.id, "mem_profile_rule");
        assert_eq!(round_trip.state, "promotion_candidate");
        assert_eq!(round_trip.last_touched_session, 7);
        assert_eq!(round_trip.decay_class.as_deref(), Some("stable"));
        assert_eq!(
            round_trip.expires_at.as_deref(),
            Some("2026-06-01T00:00:00Z")
        );
        assert_eq!(round_trip.tags, vec!["rust".to_owned(), "lint".to_owned()]);
        assert_eq!(round_trip.source_ref.as_deref(), Some("docs/spec.md#L1"));
        assert_eq!(round_trip.supersedes, vec!["mem_old".to_owned()]);
    }

    #[test]
    fn runtime_memory_surface_preserves_superseded_at() {
        let temp = tempdir().expect("tempdir");
        let layout = StateLayout::new(
            temp.path().join(".ccd"),
            temp.path().join("repo/.git/ccd"),
            ProfileName::new("main").expect("profile"),
        );

        fs::create_dir_all(layout.profile_root()).expect("profile root");
        fs::write(
            layout.profile_memory_path(),
            concat!(
                "# Profile Memory\n\n",
                "```ccd-memory\n",
                "id = \"mem_profile_rule\"\n",
                "type = \"rule\"\n",
                "state = \"superseded\"\n",
                "created_at = \"2026-03-10T10:00:00Z\"\n",
                "last_touched_session = 7\n",
                "origin = \"manual\"\n",
                "superseded_at = \"2026-03-12T09:30:00Z\"\n",
                "content = \"Keep runtime mutations deterministic.\"\n",
                "```\n",
            ),
        )
        .expect("profile memory");

        let loaded = load_profile_memory_surface(&layout).expect("load profile memory");
        let entry = loaded
            .entries
            .iter()
            .find(|entry| entry.structured_id() == Some("mem_profile_rule"))
            .expect("structured memory entry");
        let round_trip = entry
            .as_structured_entry()
            .expect("round-trip structured entry");

        assert_eq!(entry.structured_state(), Some("superseded"));
        assert_eq!(entry.superseded_at.as_deref(), Some("2026-03-12T09:30:00Z"));
        assert_eq!(
            round_trip.superseded_at.as_deref(),
            Some("2026-03-12T09:30:00Z")
        );
    }

    #[test]
    fn profile_memory_surface_uses_native_state_when_markdown_is_missing() {
        let temp = tempdir().expect("tempdir");
        let layout = StateLayout::new(
            temp.path().join(".ccd"),
            temp.path().join("repo/.git/ccd"),
            ProfileName::new("main").expect("profile"),
        );

        fs::create_dir_all(layout.profile_root()).expect("profile root");
        fs::write(
            layout.profile_memory_path(),
            concat!(
                "# Profile Memory\n\n",
                "- Prefer native runtime state.\n\n",
                "```ccd-memory\n",
                "id = \"mem_profile_rule\"\n",
                "type = \"rule\"\n",
                "state = \"active\"\n",
                "created_at = \"2026-03-10T10:00:00Z\"\n",
                "last_touched_session = 7\n",
                "origin = \"manual\"\n",
                "content = \"Keep generated state JSON-native.\"\n",
                "```\n",
            ),
        )
        .expect("profile memory");

        load_profile_memory_surface(&layout).expect("bootstrap native profile memory");
        fs::remove_file(layout.profile_memory_path()).expect("remove profile memory markdown");

        let loaded = load_profile_memory_surface(&layout).expect("load native profile memory");
        let native: JsonValue = serde_json::from_str(
            &fs::read_to_string(layout.profile_runtime_state_path()).expect("native profile store"),
        )
        .expect("parse native profile runtime");

        assert_eq!(loaded.source.status, RuntimeTextSurfaceStatus::LoadedNative);
        assert_eq!(loaded.source.path, layout.profile_runtime_state_path());
        assert_eq!(
            loaded
                .entries
                .iter()
                .map(RuntimeMemoryEntry::projection_text)
                .collect::<Vec<_>>(),
            vec![
                "Prefer native runtime state.".to_owned(),
                "rule: Keep generated state JSON-native.".to_owned(),
            ]
        );
        assert_eq!(
            native["memory"][1]["text"],
            "Keep generated state JSON-native."
        );
    }

    #[test]
    fn runtime_load_uses_native_repo_memory_when_markdown_is_missing() {
        let temp = tempdir().expect("tempdir");
        let layout = StateLayout::new(
            temp.path().join(".ccd"),
            temp.path().join("repo/.git/ccd"),
            ProfileName::new("main").expect("profile"),
        );
        let locality_id = "ccdrepo_runtime";

        fs::create_dir_all(layout.profile_root()).expect("profile root");
        fs::create_dir_all(layout.repo_overlay_root(locality_id).expect("repo overlay"))
            .expect("repo overlay dir");

        fs::write(
            layout
                .locality_memory_path(locality_id)
                .expect("locality memory path"),
            "# Repo Memory\n\n- Keep repo state JSON-backed.\n",
        )
        .expect("repo memory");

        load_runtime_state(temp.path(), &layout, locality_id)
            .expect("bootstrap native repo runtime");
        fs::remove_file(
            layout
                .locality_memory_path(locality_id)
                .expect("locality memory path"),
        )
        .expect("remove repo memory markdown");

        let loaded = load_runtime_state(temp.path(), &layout, locality_id)
            .expect("load runtime from native repo");

        assert_eq!(
            loaded.sources.locality_memory.status,
            RuntimeTextSurfaceStatus::LoadedNative
        );
        assert_eq!(
            loaded
                .state
                .memory
                .locality
                .iter()
                .map(RuntimeMemoryEntry::projection_text)
                .collect::<Vec<_>>(),
            vec!["Keep repo state JSON-backed.".to_owned()]
        );
    }

    #[test]
    fn runtime_load_accepts_legacy_native_repo_state_without_memory_automation() {
        let temp = tempdir().expect("tempdir");
        let layout = StateLayout::new(
            temp.path().join(".ccd"),
            temp.path().join("repo/.git/ccd"),
            ProfileName::new("main").expect("profile"),
        );
        let locality_id = "ccdrepo_legacy_runtime";

        fs::create_dir_all(layout.profile_root()).expect("profile root");
        fs::create_dir_all(layout.repo_overlay_root(locality_id).expect("repo overlay"))
            .expect("repo overlay dir");

        fs::write(
            layout
                .repo_runtime_state_path(locality_id)
                .expect("repo runtime path"),
            serde_json::to_vec_pretty(&serde_json::json!({
                "schema_version": 1,
                "repo_memory_adapter_fingerprint": null,
                "repo_memory": [
                    {
                        "id": "mem_repo_rule",
                        "text": "Legacy runtime JSON still loads.",
                        "lifecycle": "active",
                        "origin": {
                            "Structured": {
                                "entry_type": "rule",
                                "origin": "agent"
                            }
                        },
                        "state": "active",
                        "created_at": "2026-04-08T10:00:00Z",
                        "last_touched_session": 4,
                        "tags": [],
                        "source_ref": null,
                        "supersedes": []
                    }
                ]
            }))
            .expect("legacy repo runtime json"),
        )
        .expect("write legacy repo runtime");

        let loaded = load_runtime_state(temp.path(), &layout, locality_id)
            .expect("load runtime from legacy native repo state");

        assert_eq!(
            loaded.sources.locality_memory.status,
            RuntimeTextSurfaceStatus::LoadedNative
        );
        assert_eq!(
            loaded
                .state
                .memory
                .locality
                .iter()
                .map(RuntimeMemoryEntry::projection_text)
                .collect::<Vec<_>>(),
            vec!["rule: Legacy runtime JSON still loads.".to_owned()]
        );
        assert!(
            load_repo_memory_automation_record(&layout, locality_id, "missing-key")
                .expect("load missing automation key")
                .is_none()
        );
    }

    #[test]
    fn missing_authored_surfaces_become_missing_runtime_sources() {
        let temp = tempdir().expect("tempdir");
        let layout = StateLayout::new(
            temp.path().join(".ccd"),
            PathBuf::from("/tmp/repo/.git/ccd"),
            ProfileName::new("main").expect("profile"),
        );

        let loaded = load_runtime_state(temp.path(), &layout, "ccdrepo_missing")
            .expect("missing runtime state should load");

        assert_eq!(
            loaded.sources.profile_memory.status,
            RuntimeTextSurfaceStatus::Missing
        );
        assert!(loaded.state.memory.profile.is_empty());
        assert!(loaded.state.handoff.title.is_empty());
    }

    #[test]
    fn runtime_load_uses_native_handoff_when_markdown_is_missing() {
        let temp = tempdir().expect("tempdir");
        let repo_root = temp.path().join("repo");
        let layout = StateLayout::new(
            temp.path().join(".ccd"),
            repo_root.join(".git/ccd"),
            ProfileName::new("main").expect("profile"),
        );
        let locality_id = "ccdrepo_runtime";

        fs::create_dir_all(layout.profile_root()).expect("profile root");
        fs::create_dir_all(layout.repo_overlay_root(locality_id).expect("repo overlay"))
            .expect("repo overlay dir");
        fs::create_dir_all(layout.clone_profile_root()).expect("clone profile root");
        fs::create_dir_all(&repo_root).expect("repo root");

        // Write native state directly (no export bootstrap).
        let handoff = RuntimeHandoffState {
            title: "Next Session: Native fallback".to_owned(),
            immediate_actions: vec![RuntimeHandoffItem {
                text: "Use native handoff state.".to_owned(),
                lifecycle: RuntimeLifecycle::Active,
            }],
            ..RuntimeHandoffState::default()
        };
        persist_canonical_handoff_state(&layout, &handoff).expect("persist native handoff");

        let loaded =
            load_runtime_state(&repo_root, &layout, locality_id).expect("load runtime state");

        assert_eq!(
            loaded.sources.handoff.status,
            RuntimeTextSurfaceStatus::LoadedNative
        );
        assert_eq!(loaded.state.handoff.title, "Next Session: Native fallback");
        assert_eq!(
            loaded
                .state
                .handoff
                .immediate_actions
                .iter()
                .map(|item| item.text.clone())
                .collect::<Vec<_>>(),
            vec!["Use native handoff state.".to_owned()]
        );
    }

    #[test]
    fn runtime_load_accepts_lowercase_native_handoff_lifecycle_values() {
        let temp = tempdir().expect("tempdir");
        let repo_root = temp.path().join("repo");
        let layout = StateLayout::new(
            temp.path().join(".ccd"),
            repo_root.join(".git/ccd"),
            ProfileName::new("main").expect("profile"),
        );
        let locality_id = "ccdrepo_runtime";

        fs::create_dir_all(layout.profile_root()).expect("profile root");
        fs::create_dir_all(layout.repo_overlay_root(locality_id).expect("repo overlay"))
            .expect("repo overlay dir");
        fs::create_dir_all(layout.clone_profile_root()).expect("clone profile root");
        fs::create_dir_all(&repo_root).expect("repo root");
        fs::create_dir_all(
            layout
                .clone_runtime_state_path()
                .parent()
                .expect("clone runtime parent"),
        )
        .expect("clone runtime dir");

        fs::write(
            layout.clone_runtime_state_path(),
            serde_json::to_string_pretty(&serde_json::json!({
                "schema_version": 1,
                "handoff_adapter_fingerprint": serde_json::Value::Null,
                "handoff": {
                    "title": "Next Session: Lowercase native lifecycle",
                    "immediate_actions": [
                        { "text": "Accept lowercase lifecycle values.", "lifecycle": "active" }
                    ],
                    "completed_state": [],
                    "operational_guardrails": [],
                    "key_files": [],
                    "definition_of_done": []
                }
            }))
            .expect("serialize native runtime"),
        )
        .expect("native runtime");

        let loaded =
            load_runtime_state(temp.path(), &layout, locality_id).expect("runtime-first load");

        assert_eq!(
            loaded.sources.handoff.status,
            RuntimeTextSurfaceStatus::LoadedNative
        );
        assert_eq!(
            loaded.state.handoff.immediate_actions,
            vec![RuntimeHandoffItem {
                text: "Accept lowercase lifecycle values.".to_owned(),
                lifecycle: RuntimeLifecycle::Active,
            }]
        );
    }

    #[test]
    fn runtime_load_accepts_legacy_native_profile_state_without_memory_automation() {
        let temp = tempdir().expect("tempdir");
        let layout = StateLayout::new(
            temp.path().join(".ccd"),
            temp.path().join("repo/.git/ccd"),
            ProfileName::new("main").expect("profile"),
        );

        fs::create_dir_all(layout.profile_root()).expect("profile root");
        fs::write(
            layout.profile_runtime_state_path(),
            serde_json::to_vec_pretty(&serde_json::json!({
                "schema_version": 1,
                "memory_adapter_fingerprint": null,
                "memory": [
                    {
                        "id": "mem_profile_rule",
                        "text": "Legacy profile runtime JSON still loads.",
                        "lifecycle": "active",
                        "origin": {
                            "Structured": {
                                "entry_type": "rule",
                                "origin": "agent"
                            }
                        },
                        "state": "active",
                        "created_at": "2026-04-08T10:00:00Z",
                        "last_touched_session": 4,
                        "tags": [],
                        "source_ref": null,
                        "supersedes": []
                    }
                ]
            }))
            .expect("legacy profile runtime json"),
        )
        .expect("write legacy profile runtime");

        let loaded = load_profile_memory_surface(&layout)
            .expect("load memory from legacy native profile state");

        assert_eq!(loaded.source.status, RuntimeTextSurfaceStatus::LoadedNative);
        assert_eq!(
            loaded
                .entries
                .iter()
                .map(RuntimeMemoryEntry::projection_text)
                .collect::<Vec<_>>(),
            vec!["rule: Legacy profile runtime JSON still loads.".to_owned()]
        );
        assert!(
            load_profile_memory_automation_record(&layout, "missing-key")
                .expect("load missing profile automation key")
                .is_none()
        );
    }

    #[test]
    fn render_handoff_markdown_omits_inactive_items_from_export() {
        let rendered = render_handoff_markdown(&RuntimeHandoffState {
            title: "Next Session: Active export only".to_owned(),
            immediate_actions: vec![
                RuntimeHandoffItem {
                    text: "Do the active thing.".to_owned(),
                    lifecycle: RuntimeLifecycle::Active,
                },
                RuntimeHandoffItem {
                    text: "Do not render this archived action.".to_owned(),
                    lifecycle: RuntimeLifecycle::Inactive,
                },
            ],
            completed_state: vec![RuntimeHandoffItem {
                text: "Keep this visible.".to_owned(),
                lifecycle: RuntimeLifecycle::Active,
            }],
            operational_guardrails: vec![],
            key_files: vec![RuntimeHandoffItem {
                text: "`src/archived.rs`".to_owned(),
                lifecycle: RuntimeLifecycle::Inactive,
            }],
            definition_of_done: vec![],
        });

        assert!(rendered.contains("Do the active thing."));
        assert!(rendered.contains("Keep this visible."));
        assert!(!rendered.contains("Do not render this archived action."));
        assert!(!rendered.contains("`src/archived.rs`"));
        assert!(!rendered.contains("## Key Files for This Session"));
    }
}
