// State layout helpers feed multiple command surfaces and feature-gated
// entrypoints from one shared module.

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

use anyhow::{bail, Result};
use serde::Deserialize;

use crate::paths::substrate::ResolvedSubstrate;
use crate::profile::ProfileName;
use crate::repo::marker::validate_locality_id;

pub const CCD_DIR_NAME: &str = ".ccd";

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StateLayout {
    ccd_root: PathBuf,
    substrate: ResolvedSubstrate,
    profile: ProfileName,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default)]
pub struct ProfileConfigFile {
    pub focus: DispatchConfig,
    pub dispatch: DispatchConfig,
    pub memory: MemoryConfig,
    pub sync: SyncConfig,
    pub telemetry: TelemetryConfig,
    pub handoff: HandoffConfig,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default)]
pub struct DispatchConfig {
    pub coordination_scope: Option<String>,
    pub pod: Option<String>,
}

impl DispatchConfig {
    pub fn resolved_coordination_scope(&self) -> Option<&str> {
        self.coordination_scope.as_deref().or(self.pod.as_deref())
    }

    pub fn resolved_coordination_scope_source(&self) -> Option<CoordinationScopeKey> {
        if self.coordination_scope.is_some() {
            Some(CoordinationScopeKey::CoordinationScope)
        } else if self.pod.is_some() {
            Some(CoordinationScopeKey::PodAlias)
        } else {
            None
        }
    }
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default)]
pub struct SyncConfig {
    pub soft_line_limit: Option<usize>,
    pub hard_line_limit: Option<usize>,
    pub stub_soft_line_limit: Option<usize>,
    pub stub_hard_line_limit: Option<usize>,
    pub enforce_lf: Option<bool>,
    pub claude_header: Option<String>,
    pub gemini_header: Option<String>,
    pub stub_body: Option<String>,
}

// ── Repo overlay config types ───────────────────────────────────────────

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default)]
pub struct RepoOverlayConfigFile {
    pub sources: SourcesConfig,
    pub doctor: DoctorOverlayConfig,
    pub extensions: Vec<ExtensionBlock>,
    pub hosts: BTreeMap<String, HostIntegrationConfig>,
    pub dispatch: DispatchConfig,
    pub focus: DispatchConfig,
    pub memory: MemoryConfig,
    pub telemetry: TelemetryConfig,
    pub handoff: HandoffConfig,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default)]
pub struct HostIntegrationConfig {
    pub mode: Option<String>,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default)]
pub struct MemoryConfig {
    pub authoritative_provider: Option<String>,
    pub recall_provider: Option<String>,
    pub ingest_provider: Option<String>,
    pub start_recall_policy: Option<String>,
    pub providers: BTreeMap<String, MemoryProviderConfig>,
    pub ingest: BTreeMap<String, MemoryProviderConfig>,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default)]
pub struct MemoryProviderConfig {
    pub kind: Option<String>,
    pub mode: Option<String>,
    pub command: Option<Vec<String>>,
    pub timeout_s: Option<u64>,
    pub capabilities: Option<Vec<String>>,
    pub allowed_scopes: Option<Vec<String>>,
    pub inputs: BTreeMap<String, String>,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default)]
pub struct SourcesConfig {
    pub always: Vec<String>,
    pub relevant: Vec<String>,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default)]
pub struct TelemetryConfig {
    pub cost: TelemetryCostConfig,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default)]
pub struct TelemetryCostConfig {
    pub session_warn_usd: Option<f64>,
    pub item_warn_usd: Option<f64>,
    pub models: Vec<TelemetryCostModelConfig>,
}

impl TelemetryCostConfig {
    pub(crate) fn merge(profile: &Self, overlay: Option<&Self>) -> Self {
        let mut models = Vec::new();
        if let Some(overlay) = overlay {
            models.extend(overlay.models.clone());
        }
        models.extend(profile.models.clone());

        Self {
            session_warn_usd: overlay
                .and_then(|config| config.session_warn_usd)
                .or(profile.session_warn_usd),
            item_warn_usd: overlay
                .and_then(|config| config.item_warn_usd)
                .or(profile.item_warn_usd),
            models,
        }
    }

    pub(crate) fn is_configured(&self) -> bool {
        self.session_warn_usd.is_some() || self.item_warn_usd.is_some() || !self.models.is_empty()
    }
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default)]
pub struct TelemetryCostModelConfig {
    #[serde(rename = "match")]
    pub match_name: String,
    pub aliases: Vec<String>,
    pub input_usd_per_million: Option<f64>,
    pub output_usd_per_million: Option<f64>,
    pub cache_write_usd_per_million: Option<f64>,
    pub cache_read_usd_per_million: Option<f64>,
    pub blended_usd_per_million: Option<f64>,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct HandoffConfig {
    pub candidate_refresh: HandoffCandidateRefreshConfig,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct HandoffCandidateRefreshConfig {
    pub enabled: Option<bool>,
}

impl HandoffConfig {
    pub(crate) fn merge(profile: &Self, overlay: Option<&Self>) -> Self {
        Self {
            candidate_refresh: HandoffCandidateRefreshConfig {
                enabled: overlay
                    .and_then(|config| config.candidate_refresh.enabled)
                    .or(profile.candidate_refresh.enabled),
            },
        }
    }

    pub(crate) fn candidate_refresh_enabled(&self) -> bool {
        self.candidate_refresh.enabled.unwrap_or(false)
    }
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct DoctorOverlayConfig {
    pub handoff: HandoffDoctorOverlayConfig,
}

#[derive(Debug, Default, Clone, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct HandoffDoctorOverlayConfig {
    pub missing: Option<String>,
    pub sections: Option<String>,
    pub sections_empty: Option<String>,
    pub narration: Option<String>,
    pub freshness: Option<String>,
}

#[cfg_attr(not(test), allow(dead_code))]
#[derive(Debug, Clone, Deserialize)]
pub struct ExtensionBlock {
    #[serde(rename = "type")]
    pub extension_type: String,
    #[serde(default)]
    pub name: Option<String>,
    #[serde(default)]
    pub default: bool,
    #[serde(flatten)]
    pub extra: BTreeMap<String, toml::Value>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CoordinationScopeKey {
    CoordinationScope,
    PodAlias,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CoordinationScopeSource {
    RepoDispatchCoordinationScope,
    RepoDispatchPodAlias,
    RepoFocusPodAlias,
    ProfileDispatchCoordinationScope,
    ProfileDispatchPodAlias,
    ProfileFocusPodAlias,
}

impl CoordinationScopeSource {
    pub(crate) fn as_str(self) -> &'static str {
        match self {
            Self::RepoDispatchCoordinationScope => "repo_dispatch_coordination_scope",
            Self::RepoDispatchPodAlias => "repo_dispatch_pod_alias",
            Self::RepoFocusPodAlias => "repo_focus_pod_alias",
            Self::ProfileDispatchCoordinationScope => "profile_dispatch_coordination_scope",
            Self::ProfileDispatchPodAlias => "profile_dispatch_pod_alias",
            Self::ProfileFocusPodAlias => "profile_focus_pod_alias",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ResolvedCoordinationScope {
    pub name: String,
    pub source: CoordinationScopeSource,
    pub config_path: PathBuf,
    pub shared_root: PathBuf,
}

impl StateLayout {
    #[cfg_attr(not(test), allow(dead_code))]
    pub fn new(ccd_root: PathBuf, git_ccd_root: PathBuf, profile: ProfileName) -> Self {
        Self::new_with_substrate(ccd_root, ResolvedSubstrate::git(git_ccd_root), profile)
    }

    pub(crate) fn new_with_substrate(
        ccd_root: PathBuf,
        substrate: ResolvedSubstrate,
        profile: ProfileName,
    ) -> Self {
        Self {
            ccd_root,
            substrate,
            profile,
        }
    }

    pub fn resolve(repo_root: &Path, profile: ProfileName) -> Result<Self> {
        let ccd_root = default_ccd_root()?;
        Ok(Self::new_with_substrate(
            ccd_root.clone(),
            ResolvedSubstrate::resolve(repo_root, &ccd_root)?,
            profile,
        ))
    }

    pub(crate) fn resolve_for_attach(repo_root: &Path, profile: ProfileName) -> Result<Self> {
        let ccd_root = default_ccd_root()?;
        Ok(Self::new_with_substrate(
            ccd_root.clone(),
            ResolvedSubstrate::resolve_for_attach(repo_root, &ccd_root)?,
            profile,
        ))
    }

    pub fn ccd_root(&self) -> &Path {
        &self.ccd_root
    }

    pub fn workspace_state_root(&self) -> &Path {
        self.substrate.workspace_state_root()
    }

    pub(crate) fn resolved_substrate(&self) -> &ResolvedSubstrate {
        &self.substrate
    }

    pub fn profile(&self) -> &ProfileName {
        &self.profile
    }

    pub fn profile_root(&self) -> PathBuf {
        self.ccd_root.join("profiles").join(self.profile.as_str())
    }

    pub fn profile_config_path(&self) -> PathBuf {
        self.profile_root().join("config.toml")
    }

    pub fn profile_policy_path(&self) -> PathBuf {
        self.profile_root().join("policy.md")
    }

    pub fn profile_memory_path(&self) -> PathBuf {
        self.profile_root().join("memory.md")
    }

    pub fn profile_repos_root(&self) -> PathBuf {
        self.profile_root().join("repos")
    }

    pub fn repo_registry_root(&self) -> PathBuf {
        self.ccd_root.join("repos")
    }

    pub fn repo_registry_dir(&self, locality_id: &str) -> Result<PathBuf> {
        Ok(self
            .repo_registry_root()
            .join(validate_locality_id(locality_id)?))
    }

    pub fn repo_metadata_path(&self, locality_id: &str) -> Result<PathBuf> {
        Ok(self.repo_registry_dir(locality_id)?.join("repo.toml"))
    }

    pub fn repo_overlay_root(&self, locality_id: &str) -> Result<PathBuf> {
        Ok(self
            .profile_repos_root()
            .join(validate_locality_id(locality_id)?))
    }

    pub fn repo_manifest_path(&self, locality_id: &str) -> Result<PathBuf> {
        Ok(self.repo_overlay_root(locality_id)?.join("manifest.md"))
    }

    pub fn repo_policy_path(&self, locality_id: &str) -> Result<PathBuf> {
        Ok(self.repo_overlay_root(locality_id)?.join("policy.md"))
    }

    pub fn locality_policy_path(&self, locality_id: &str) -> Result<PathBuf> {
        self.repo_policy_path(locality_id)
    }

    pub fn repo_memory_path(&self, locality_id: &str) -> Result<PathBuf> {
        Ok(self.repo_overlay_root(locality_id)?.join("memory.md"))
    }

    pub fn locality_memory_path(&self, locality_id: &str) -> Result<PathBuf> {
        self.repo_memory_path(locality_id)
    }

    pub fn branch_memory_root(&self, locality_id: &str) -> Result<PathBuf> {
        Ok(self.repo_overlay_root(locality_id)?.join("branches"))
    }

    pub fn branch_memory_path(&self, locality_id: &str, branch: &str) -> Result<PathBuf> {
        if branch.is_empty() {
            bail!("branch name cannot be empty");
        }
        if branch.contains('\0') {
            bail!("branch name cannot contain NUL bytes");
        }
        Ok(self
            .branch_memory_root(locality_id)?
            .join(encode_branch_memory_segment(branch))
            .join("memory.md"))
    }

    pub fn profile_runtime_state_path(&self) -> PathBuf {
        self.profile_root().join("runtime_state.json")
    }

    pub fn repo_runtime_state_path(&self, locality_id: &str) -> Result<PathBuf> {
        Ok(self
            .repo_overlay_root(locality_id)?
            .join("runtime_state.json"))
    }

    pub fn locality_runtime_state_path(&self, locality_id: &str) -> Result<PathBuf> {
        self.repo_runtime_state_path(locality_id)
    }

    pub fn repo_validation_path(&self, locality_id: &str) -> Result<PathBuf> {
        Ok(self.repo_overlay_root(locality_id)?.join("validation.toml"))
    }

    pub fn repo_overlay_config_path(&self, locality_id: &str) -> Result<PathBuf> {
        Ok(self.repo_overlay_root(locality_id)?.join("config.toml"))
    }

    pub fn load_repo_overlay_config(
        &self,
        locality_id: &str,
    ) -> Result<Option<RepoOverlayConfigFile>> {
        let path = self.repo_overlay_config_path(locality_id)?;
        load_repo_overlay_config(&path)
    }

    pub fn clone_profile_root(&self) -> PathBuf {
        self.workspace_state_root()
            .join("profiles")
            .join(self.profile.as_str())
    }

    pub fn state_db_path(&self) -> PathBuf {
        self.clone_profile_root().join("state.db")
    }

    pub fn clone_memory_path(&self) -> PathBuf {
        self.clone_profile_root().join("memory.md")
    }

    /// Clone-local path of the legacy `handoff.md` export.
    ///
    /// Compatibility surface only. `ccd handoff export` no longer writes to
    /// this path; canonical handoff state lives in `state.db` (table
    /// `handoff`). Remaining callers are the start-path cleanup (which
    /// removes a stale copy if one is found) and the runtime-state export
    /// status field, which will always report `absent` once cleanup has
    /// run. Do not reintroduce this path as a live runtime projection.
    pub fn handoff_path(&self) -> PathBuf {
        self.clone_profile_root().join("handoff.md")
    }

    pub fn session_state_path(&self) -> PathBuf {
        self.clone_profile_root().join("session_state.json")
    }

    pub fn clone_working_buffer_path(&self) -> PathBuf {
        self.clone_profile_root().join("working_buffer.md")
    }

    pub fn clone_checkpoint_path(&self) -> PathBuf {
        self.clone_profile_root().join("checkpoint.json")
    }

    pub fn clone_runtime_state_root(&self) -> PathBuf {
        self.clone_profile_root().join("runtime_state")
    }

    pub fn clone_runtime_state_path(&self) -> PathBuf {
        self.clone_runtime_state_root().join("state.json")
    }

    pub fn clone_projection_metadata_path(&self) -> PathBuf {
        self.clone_runtime_state_root()
            .join("projection_metadata.json")
    }

    pub fn escalation_state_path(&self) -> PathBuf {
        self.clone_runtime_state_root().join("escalation.json")
    }

    pub fn compiled_state_path(&self) -> PathBuf {
        self.clone_profile_root().join("compiled_state.json")
    }

    pub fn load_profile_config(&self) -> Result<ProfileConfigFile> {
        load_profile_config(&self.profile_config_path())
    }

    pub(crate) fn pod_root(&self, pod: &str) -> Result<PathBuf> {
        validate_dispatch_pod_name(pod)?;
        Ok(self.ccd_root.join("pods").join(pod))
    }

    /// Pod-scoped legacy layout root. Used by the RFC 0006 M4(c) schema-bump
    /// gate to enforce the "run `ccd migrate from-pod-layout` before advancing
    /// to v16" invariant against the layout's own `ccd_root`, rather than a
    /// global HOME-derived path that would diverge for non-default layouts
    /// (tests, explicit overrides).
    pub(crate) fn pods_root(&self) -> PathBuf {
        self.ccd_root.join("pods")
    }

    /// Flat machine-registry root introduced by RFC 0006 M1. Coexists with
    /// the pod-scoped layout until the collapse completes.
    #[cfg_attr(not(test), allow(dead_code))]
    pub(crate) fn machines_root(&self) -> PathBuf {
        self.ccd_root.join("machines")
    }

    #[cfg_attr(not(test), allow(dead_code))]
    pub(crate) fn flat_machine_manifest_path(&self, machine_id: &str) -> Result<PathBuf> {
        validate_machine_id(machine_id)?;
        Ok(self.machines_root().join(machine_id).join("machine.toml"))
    }

    #[cfg_attr(not(test), allow(dead_code))]
    pub(crate) fn flat_machine_presence_root(&self) -> PathBuf {
        self.machines_root().join("presence")
    }

    #[cfg_attr(not(test), allow(dead_code))]
    pub(crate) fn flat_machine_presence_path(&self, machine_id: &str) -> Result<PathBuf> {
        validate_machine_id(machine_id)?;
        Ok(self
            .flat_machine_presence_root()
            .join(format!("{machine_id}.json")))
    }

    pub(crate) fn pod_machine_manifest_path(&self, pod: &str) -> Result<PathBuf> {
        Ok(self.pod_root(pod)?.join("machine.toml"))
    }

    pub(crate) fn pod_machine_presence_root(&self, pod: &str) -> Result<PathBuf> {
        Ok(self.pod_root(pod)?.join("presence"))
    }

    pub(crate) fn pod_machine_presence_path(&self, pod: &str, machine_id: &str) -> Result<PathBuf> {
        validate_machine_id(machine_id)?;
        Ok(self
            .pod_machine_presence_root(pod)?
            .join(format!("{machine_id}.json")))
    }

    pub(crate) fn pod_repo_overlay_root(&self, pod: &str, locality_id: &str) -> Result<PathBuf> {
        Ok(self
            .pod_root(pod)?
            .join("repos")
            .join(validate_locality_id(locality_id)?))
    }

    #[cfg_attr(not(test), allow(dead_code))]
    pub(crate) fn pod_extensions_root(&self, pod: &str, locality_id: &str) -> Result<PathBuf> {
        Ok(self
            .pod_repo_overlay_root(pod, locality_id)?
            .join("extensions"))
    }

    pub(crate) fn resolved_coordination_scope(
        &self,
        locality_id: &str,
    ) -> Result<Option<ResolvedCoordinationScope>> {
        let overlay_path = self.repo_overlay_root(locality_id)?.join("config.toml");
        if let Some(overlay_config) = load_repo_overlay_config(&overlay_path)? {
            if let Some(resolved) = resolved_coordination_scope_from_repo_config(
                &overlay_config,
                &overlay_path,
                locality_id,
                self,
            )? {
                return Ok(Some(resolved));
            }
        }

        let profile_path = self.profile_config_path();
        let profile_config = load_profile_config(&profile_path)?;
        resolved_coordination_scope_from_profile_config(
            &profile_config,
            &profile_path,
            locality_id,
            self,
        )
    }

    pub(crate) fn effective_telemetry_cost_config(
        &self,
        locality_id: &str,
    ) -> Result<TelemetryCostConfig> {
        let profile_config = load_profile_config(&self.profile_config_path())?;
        let overlay_path = self.repo_overlay_root(locality_id)?.join("config.toml");
        let overlay_config = load_repo_overlay_config(&overlay_path)?;

        Ok(TelemetryCostConfig::merge(
            &profile_config.telemetry.cost,
            overlay_config.as_ref().map(|config| &config.telemetry.cost),
        ))
    }

    pub(crate) fn effective_handoff_config(&self, locality_id: &str) -> Result<HandoffConfig> {
        let profile_config = load_profile_config(&self.profile_config_path())?;
        let overlay_path = self.repo_overlay_root(locality_id)?.join("config.toml");
        let overlay_config = load_repo_overlay_config(&overlay_path)?;

        Ok(HandoffConfig::merge(
            &profile_config.handoff,
            overlay_config.as_ref().map(|config| &config.handoff),
        ))
    }
}

pub fn default_ccd_root() -> Result<PathBuf> {
    let home = env::var_os("HOME").ok_or_else(|| anyhow::anyhow!("HOME is not set"))?;
    if home.is_empty() {
        bail!("HOME is empty");
    }

    Ok(PathBuf::from(home).join(CCD_DIR_NAME))
}

fn load_profile_config(path: &Path) -> Result<ProfileConfigFile> {
    let contents = match fs::read_to_string(path) {
        Ok(contents) => contents,
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
            return Ok(ProfileConfigFile::default());
        }
        Err(error) => return Err(error.into()),
    };

    if contents.trim().is_empty() {
        return Ok(ProfileConfigFile::default());
    }

    Ok(toml::from_str::<ProfileConfigFile>(&contents)?)
}

fn load_repo_overlay_config(path: &Path) -> Result<Option<RepoOverlayConfigFile>> {
    let contents = match fs::read_to_string(path) {
        Ok(contents) => contents,
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
        Err(error) => return Err(error.into()),
    };

    if contents.trim().is_empty() {
        return Ok(Some(RepoOverlayConfigFile::default()));
    }

    Ok(Some(toml::from_str::<RepoOverlayConfigFile>(&contents)?))
}

/// Validate a pod name for filesystem safety.
pub(crate) fn validate_pod_name(value: &str) -> Result<()> {
    validate_dispatch_pod_name(value)
}

pub(crate) fn validate_coordination_scope_name(value: &str) -> Result<()> {
    validate_dispatch_pod_name(value)
}

pub(crate) fn validate_machine_id(value: &str) -> Result<()> {
    validate_dispatch_pod_name(value)
}

fn resolved_coordination_scope_from_repo_config(
    config: &RepoOverlayConfigFile,
    config_path: &Path,
    locality_id: &str,
    layout: &StateLayout,
) -> Result<Option<ResolvedCoordinationScope>> {
    if let Some(name) = config.dispatch.resolved_coordination_scope() {
        validate_coordination_scope_name(name)?;
        return Ok(Some(ResolvedCoordinationScope {
            name: name.to_owned(),
            source: match config.dispatch.resolved_coordination_scope_source() {
                Some(CoordinationScopeKey::CoordinationScope) => {
                    CoordinationScopeSource::RepoDispatchCoordinationScope
                }
                Some(CoordinationScopeKey::PodAlias) => {
                    CoordinationScopeSource::RepoDispatchPodAlias
                }
                None => unreachable!("dispatch config must have a value"),
            },
            config_path: config_path.to_path_buf(),
            shared_root: layout.pod_repo_overlay_root(name, locality_id)?,
        }));
    }

    if let Some(name) = config.focus.resolved_coordination_scope() {
        validate_coordination_scope_name(name)?;
        return Ok(Some(ResolvedCoordinationScope {
            name: name.to_owned(),
            source: CoordinationScopeSource::RepoFocusPodAlias,
            config_path: config_path.to_path_buf(),
            shared_root: layout.pod_repo_overlay_root(name, locality_id)?,
        }));
    }

    Ok(None)
}

fn resolved_coordination_scope_from_profile_config(
    config: &ProfileConfigFile,
    config_path: &Path,
    locality_id: &str,
    layout: &StateLayout,
) -> Result<Option<ResolvedCoordinationScope>> {
    if let Some(name) = config.dispatch.resolved_coordination_scope() {
        validate_coordination_scope_name(name)?;
        return Ok(Some(ResolvedCoordinationScope {
            name: name.to_owned(),
            source: match config.dispatch.resolved_coordination_scope_source() {
                Some(CoordinationScopeKey::CoordinationScope) => {
                    CoordinationScopeSource::ProfileDispatchCoordinationScope
                }
                Some(CoordinationScopeKey::PodAlias) => {
                    CoordinationScopeSource::ProfileDispatchPodAlias
                }
                None => unreachable!("dispatch config must have a value"),
            },
            config_path: config_path.to_path_buf(),
            shared_root: layout.pod_repo_overlay_root(name, locality_id)?,
        }));
    }

    if let Some(name) = config.focus.resolved_coordination_scope() {
        validate_coordination_scope_name(name)?;
        return Ok(Some(ResolvedCoordinationScope {
            name: name.to_owned(),
            source: CoordinationScopeSource::ProfileFocusPodAlias,
            config_path: config_path.to_path_buf(),
            shared_root: layout.pod_repo_overlay_root(name, locality_id)?,
        }));
    }

    Ok(None)
}

fn validate_dispatch_pod_name(value: &str) -> Result<()> {
    if value.is_empty() {
        bail!("dispatch pod name cannot be empty");
    }

    if value == "." || value == ".." {
        bail!("dispatch pod name cannot be `.` or `..`");
    }

    if value.contains('/') || value.contains('\\') {
        bail!("dispatch pod name cannot contain path separators");
    }

    if value.as_bytes().contains(&0) {
        bail!("dispatch pod name cannot contain NUL bytes");
    }

    Ok(())
}

fn encode_branch_memory_segment(branch: &str) -> String {
    let mut encoded = String::new();
    for byte in branch.bytes() {
        match byte {
            b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' => {
                encoded.push(char::from(byte));
            }
            _ => {
                encoded.push('%');
                encoded.push(hex_nibble(byte >> 4));
                encoded.push(hex_nibble(byte & 0x0f));
            }
        }
    }
    encoded
}

fn hex_nibble(value: u8) -> char {
    match value {
        0..=9 => char::from(b'0' + value),
        10..=15 => char::from(b'A' + (value - 10)),
        _ => unreachable!("hex nibble must be within 0..=15"),
    }
}

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

    use tempfile::tempdir;

    use super::*;

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

        assert_eq!(
            layout.profile_root(),
            PathBuf::from("/tmp/home/.ccd/profiles/main")
        );
        assert_eq!(
            layout.profile_config_path(),
            PathBuf::from("/tmp/home/.ccd/profiles/main/config.toml")
        );
        assert_eq!(
            layout
                .repo_metadata_path("ccdrepo_123")
                .expect("repo metadata path"),
            PathBuf::from("/tmp/home/.ccd/repos/ccdrepo_123/repo.toml")
        );
        assert_eq!(
            layout
                .repo_manifest_path("ccdrepo_123")
                .expect("repo manifest path"),
            PathBuf::from("/tmp/home/.ccd/profiles/main/repos/ccdrepo_123/manifest.md")
        );
        assert_eq!(
            layout
                .branch_memory_path("ccdrepo_123", "session/feature-1")
                .expect("branch memory path"),
            PathBuf::from(
                "/tmp/home/.ccd/profiles/main/repos/ccdrepo_123/branches/session%2Ffeature-1/memory.md"
            )
        );
        assert_eq!(
            layout.profile_runtime_state_path(),
            PathBuf::from("/tmp/home/.ccd/profiles/main/runtime_state.json")
        );
        assert_eq!(
            layout
                .repo_runtime_state_path("ccdrepo_123")
                .expect("repo runtime state path"),
            PathBuf::from("/tmp/home/.ccd/profiles/main/repos/ccdrepo_123/runtime_state.json")
        );
        assert_eq!(
            layout
                .repo_validation_path("ccdrepo_123")
                .expect("repo validation path"),
            PathBuf::from("/tmp/home/.ccd/profiles/main/repos/ccdrepo_123/validation.toml")
        );
        assert_eq!(
            layout.session_state_path(),
            PathBuf::from("/tmp/repo/.git/ccd/profiles/main/session_state.json")
        );
        assert_eq!(
            layout.clone_working_buffer_path(),
            PathBuf::from("/tmp/repo/.git/ccd/profiles/main/working_buffer.md")
        );
        assert_eq!(
            layout.clone_checkpoint_path(),
            PathBuf::from("/tmp/repo/.git/ccd/profiles/main/checkpoint.json")
        );
        assert_eq!(
            layout.clone_runtime_state_path(),
            PathBuf::from("/tmp/repo/.git/ccd/profiles/main/runtime_state/state.json")
        );
        assert_eq!(
            layout.clone_projection_metadata_path(),
            PathBuf::from(
                "/tmp/repo/.git/ccd/profiles/main/runtime_state/projection_metadata.json"
            )
        );
        assert_eq!(
            layout.compiled_state_path(),
            PathBuf::from("/tmp/repo/.git/ccd/profiles/main/compiled_state.json")
        );
    }

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

        let error = layout
            .repo_overlay_root("../bad")
            .expect_err("project_id should fail");

        assert!(error.to_string().contains("project_id"));
    }

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

        let error = layout
            .branch_memory_path("ccdrepo_123", "")
            .expect_err("empty branch should fail");

        assert!(error.to_string().contains("branch name"));
    }

    #[test]
    fn profile_config_ignores_backlog_sections() {
        let temp = tempdir().expect("tempdir");
        let config_path = temp.path().join("config.toml");
        fs::write(
            &config_path,
            r#"
[focus]
pod = "operator"

[sync]
soft_line_limit = 120

[backlog]
default = "github"

[backlog.adapters.github]
kind = "builtin"
provider = "github-issues"
"#,
        )
        .expect("write config");

        let config = load_profile_config(&config_path).expect("parse config");
        assert_eq!(config.focus.pod.as_deref(), Some("operator"));
        assert_eq!(config.sync.soft_line_limit, Some(120));
    }

    #[test]
    fn rejects_invalid_dispatch_pod_names() {
        for invalid in ["", ".", "..", "ops/dev", "ops\\dev", "ops\0dev"] {
            let error = validate_dispatch_pod_name(invalid).expect_err("invalid pod name");
            assert!(!error.to_string().is_empty());
        }
    }

    #[test]
    fn default_ccd_root_uses_home_environment() {
        let temp = tempdir().expect("tempdir");
        let expected = temp.path().join(".ccd");

        let actual = default_ccd_root_with(Some(temp.path()));

        assert_eq!(actual.expect("ccd root"), expected);
    }

    #[test]
    fn default_ccd_root_requires_home() {
        let error = default_ccd_root_with(None).expect_err("home should be required");

        assert!(error.to_string().contains("HOME"));
    }

    fn default_ccd_root_with(home: Option<&Path>) -> Result<PathBuf> {
        match home {
            Some(path) => Ok(path.join(CCD_DIR_NAME)),
            None => bail!("HOME is not set"),
        }
    }

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

        assert_eq!(
            layout
                .repo_overlay_config_path("ccdrepo_123")
                .expect("config path"),
            PathBuf::from("/tmp/home/.ccd/profiles/main/repos/ccdrepo_123/config.toml")
        );
    }

    #[test]
    fn repo_overlay_config_parses_complete_file() {
        let temp = tempdir().expect("tempdir");
        let config_path = temp.path().join("config.toml");
        fs::write(
            &config_path,
            r#"
[sources]
always = ["AGENTS.md", "README.md"]
relevant = ["specs/"]

[doctor.handoff]
freshness = "error"
sections = "error"

[[extensions]]
type = "backlog"
name = "github"
repo = "dusk-network/ccd"
base_branch = "main"
"#,
        )
        .expect("write config");

        let config = load_repo_overlay_config(&config_path)
            .expect("parse")
            .expect("should be Some");

        assert_eq!(config.sources.always, vec!["AGENTS.md", "README.md"]);
        assert_eq!(config.sources.relevant, vec!["specs/"]);
        assert_eq!(config.doctor.handoff.freshness.as_deref(), Some("error"));
        assert_eq!(config.doctor.handoff.sections.as_deref(), Some("error"));
        assert_eq!(config.extensions.len(), 1);
        assert_eq!(config.extensions[0].extension_type, "backlog");
        assert_eq!(config.extensions[0].name.as_deref(), Some("github"));
        assert_eq!(
            config.extensions[0]
                .extra
                .get("repo")
                .and_then(|v| v.as_str()),
            Some("dusk-network/ccd")
        );
    }

    #[test]
    fn repo_overlay_config_parses_partial_file() {
        let temp = tempdir().expect("tempdir");
        let config_path = temp.path().join("config.toml");
        fs::write(
            &config_path,
            r#"
[sources]
always = ["AGENTS.md"]
"#,
        )
        .expect("write config");

        let config = load_repo_overlay_config(&config_path)
            .expect("parse")
            .expect("should be Some");

        assert_eq!(config.sources.always, vec!["AGENTS.md"]);
        assert!(config.sources.relevant.is_empty());
        assert!(config.extensions.is_empty());
    }

    #[test]
    fn repo_overlay_config_parses_host_expectations() {
        let temp = tempdir().expect("tempdir");
        let config_path = temp.path().join("config.toml");
        fs::write(
            &config_path,
            r#"
[hosts.claude]
mode = "native_hook"

[hosts.codex]
mode = "manual_skill"
"#,
        )
        .expect("write config");

        let config = load_repo_overlay_config(&config_path)
            .expect("parse")
            .expect("should be Some");

        assert_eq!(
            config
                .hosts
                .get("claude")
                .and_then(|host| host.mode.as_deref()),
            Some("native_hook")
        );
        assert_eq!(
            config
                .hosts
                .get("codex")
                .and_then(|host| host.mode.as_deref()),
            Some("manual_skill")
        );
    }

    #[test]
    fn repo_overlay_config_returns_none_for_missing_file() {
        let temp = tempdir().expect("tempdir");
        let config_path = temp.path().join("config.toml");

        let result = load_repo_overlay_config(&config_path).expect("should not error");
        assert!(result.is_none());
    }

    #[test]
    fn repo_overlay_config_parses_empty_file() {
        let temp = tempdir().expect("tempdir");
        let config_path = temp.path().join("config.toml");
        fs::write(&config_path, "").expect("write empty");

        let config = load_repo_overlay_config(&config_path)
            .expect("parse")
            .expect("should be Some");

        assert!(config.sources.always.is_empty());
        assert!(config.extensions.is_empty());
    }

    #[test]
    fn repo_overlay_config_parses_multiple_extensions() {
        let temp = tempdir().expect("tempdir");
        let config_path = temp.path().join("config.toml");
        fs::write(
            &config_path,
            r#"
[[extensions]]
type = "backlog"
name = "github"
default = true
repo = "dusk-network/ccd"

[[extensions]]
type = "backlog"
name = "local"
kind = "builtin"
provider = "local-markdown"
"#,
        )
        .expect("write config");

        let config = load_repo_overlay_config(&config_path)
            .expect("parse")
            .expect("should be Some");

        assert_eq!(config.extensions.len(), 2);
        assert_eq!(config.extensions[0].extension_type, "backlog");
        assert!(config.extensions[0].default);
        assert_eq!(config.extensions[1].name.as_deref(), Some("local"));
        assert!(!config.extensions[1].default);
        assert_eq!(
            config.extensions[1]
                .extra
                .get("provider")
                .and_then(|v| v.as_str()),
            Some("local-markdown")
        );
    }

    #[test]
    fn repo_overlay_config_parses_dispatch_pod() {
        let dir = tempdir().unwrap();
        let path = dir.path().join("config.toml");
        fs::write(&path, "[dispatch]\npod = \"operator\"\n").unwrap();
        let config: super::RepoOverlayConfigFile =
            toml::from_str(&fs::read_to_string(&path).unwrap()).unwrap();
        assert_eq!(config.dispatch.pod.as_deref(), Some("operator"));
        assert_eq!(
            config.dispatch.resolved_coordination_scope(),
            Some("operator")
        );
    }

    #[test]
    fn repo_overlay_config_parses_coordination_scope() {
        let dir = tempdir().unwrap();
        let path = dir.path().join("config.toml");
        fs::write(&path, "[dispatch]\ncoordination_scope = \"operator\"\n").unwrap();
        let config: super::RepoOverlayConfigFile =
            toml::from_str(&fs::read_to_string(&path).unwrap()).unwrap();
        assert_eq!(
            config.dispatch.coordination_scope.as_deref(),
            Some("operator")
        );
        assert_eq!(
            config.dispatch.resolved_coordination_scope(),
            Some("operator")
        );
    }

    #[test]
    fn repo_overlay_config_parses_legacy_focus_pod() {
        let dir = tempdir().unwrap();
        let path = dir.path().join("config.toml");
        fs::write(&path, "[focus]\npod = \"legacy-pod\"\n").unwrap();
        let config: super::RepoOverlayConfigFile =
            toml::from_str(&fs::read_to_string(&path).unwrap()).unwrap();
        assert_eq!(config.focus.pod.as_deref(), Some("legacy-pod"));
    }

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

        let path = layout
            .pod_extensions_root("mypod", "ccdrepo_abc123")
            .unwrap();
        assert_eq!(
            path,
            PathBuf::from("/tmp/home/.ccd/pods/mypod/repos/ccdrepo_abc123/extensions")
        );
    }

    #[test]
    fn pod_machine_manifest_path_returns_correct_path() {
        let layout = StateLayout::new(
            PathBuf::from("/tmp/home/.ccd"),
            PathBuf::from("/tmp/repo/.git/ccd"),
            ProfileName::new("main").expect("profile"),
        );
        assert_eq!(
            layout.pod_machine_manifest_path("mypod").unwrap(),
            PathBuf::from("/tmp/home/.ccd/pods/mypod/machine.toml")
        );
    }

    #[test]
    fn flat_machine_manifest_path_returns_correct_path() {
        let layout = StateLayout::new(
            PathBuf::from("/tmp/home/.ccd"),
            PathBuf::from("/tmp/repo/.git/ccd"),
            ProfileName::new("main").expect("profile"),
        );
        assert_eq!(
            layout.machines_root(),
            PathBuf::from("/tmp/home/.ccd/machines")
        );
        assert_eq!(
            layout
                .flat_machine_manifest_path("laptop-amsterdam")
                .unwrap(),
            PathBuf::from("/tmp/home/.ccd/machines/laptop-amsterdam/machine.toml")
        );
        assert_eq!(
            layout.flat_machine_presence_root(),
            PathBuf::from("/tmp/home/.ccd/machines/presence")
        );
        assert_eq!(
            layout
                .flat_machine_presence_path("laptop-amsterdam")
                .unwrap(),
            PathBuf::from("/tmp/home/.ccd/machines/presence/laptop-amsterdam.json")
        );
    }

    #[test]
    fn pod_machine_presence_path_returns_correct_path() {
        let layout = StateLayout::new(
            PathBuf::from("/tmp/home/.ccd"),
            PathBuf::from("/tmp/repo/.git/ccd"),
            ProfileName::new("main").expect("profile"),
        );
        assert_eq!(
            layout
                .pod_machine_presence_path("mypod", "laptop-amsterdam")
                .unwrap(),
            PathBuf::from("/tmp/home/.ccd/pods/mypod/presence/laptop-amsterdam.json")
        );
    }

    #[test]
    fn repo_overlay_config_rejects_unknown_doctor_keys() {
        let temp = tempdir().expect("tempdir");
        let config_path = temp.path().join("config.toml");
        fs::write(
            &config_path,
            r#"
[doctor.handoff]
freshnes = "error"
"#,
        )
        .expect("write config");

        let result = load_repo_overlay_config(&config_path);
        assert!(
            result.is_err(),
            "expected parse error for unknown doctor key, got: {:?}",
            result
        );
        let err = result.unwrap_err().to_string();
        assert!(
            err.contains("unknown field"),
            "error should mention unknown field, got: {err}"
        );
    }
}
