mod commands;
mod content_trust;
mod db;
mod display_safe;
mod extensions;
mod handoff;
mod host_adapter;
mod logging;
mod mcp;
mod memory;
mod output;
mod paths;
mod profile;
mod recovery;
mod repo;
mod runtime_api;
mod runtime_events;
mod session_boundary;
mod shipped_skills;
mod state;
mod symbolic_prompt;
mod telemetry;
mod timestamps;

use std::path::PathBuf;
use std::process::ExitCode;

use anyhow::Result;
use clap::{Args, CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum};
use output::OutputFormat;

#[derive(Parser)]
#[command(
    name = "ccd",
    version,
    about = "Runtime-neutral continuity and governance kernel for AI coding"
)]
struct Cli {
    #[arg(long, global = true, value_enum, default_value_t = OutputFormat::Text)]
    output: OutputFormat,
    #[command(subcommand)]
    command: Command,
}

#[derive(Subcommand)]
enum Command {
    Describe,
    Attach(AttachArgs),
    Init(InitArgs),
    Scaffold(ScaffoldArgs),
    Host {
        #[command(subcommand)]
        command: HostCommand,
    },
    Repo {
        #[command(subcommand)]
        command: RepoCommand,
    },
    Marker {
        #[command(subcommand)]
        command: MarkerCommand,
    },
    Link(LinkArgs),
    Check(CheckArgs),
    Preflight(PreflightArgs),
    Start(StartArgs),
    Status(StatusArgs),
    ContextCheck(ContextCheckArgs),
    PolicyCheck(PolicyCheckArgs),
    Gc(GcArgs),
    Unlink(UnlinkArgs),
    Sync(SyncArgs),
    Doctor(DoctorArgs),
    Consistency(ConsistencyArgs),
    Drift(DriftArgs),
    Handoff {
        #[command(subcommand)]
        command: HandoffCommand,
    },
    Remember(RememberArgs),
    Memory {
        #[command(subcommand)]
        command: MemoryCommand,
    },
    #[command(name = "host-hook")]
    HostHook(HostHookRunArgs),
    Telemetry {
        #[command(subcommand)]
        command: TelemetryCommand,
    },
    RuntimeState {
        #[command(subcommand)]
        command: RuntimeStateCommand,
    },
    Thread {
        #[command(subcommand)]
        command: ThreadCommand,
    },
    EscalationState {
        #[command(subcommand)]
        command: EscalationStateCommand,
    },
    Handover(HandoverArgs),
    Checkpoint(CheckpointArgs),
    Recovery {
        #[command(subcommand)]
        command: RecoveryCommand,
    },
    Session {
        #[command(subcommand)]
        command: SessionCommand,
    },
    SessionState {
        #[command(subcommand)]
        command: SessionStateCommand,
    },
    Hooks {
        #[command(subcommand)]
        command: HooksCommand,
    },
    Skills {
        #[command(subcommand)]
        command: SkillsCommand,
    },
    #[command(hide = true)]
    Pod {
        #[command(subcommand)]
        command: PodCommand,
    },
    Machine {
        #[command(subcommand)]
        command: MachineCommand,
    },
    Migrate {
        #[command(subcommand)]
        command: MigrateCommand,
    },
    #[command(hide = true)]
    McpServe,
    #[cfg(feature = "daemon")]
    Daemon(DaemonArgs),
}

impl Command {
    fn name(&self) -> &'static str {
        match self {
            Self::Describe => "Describe",
            Self::Attach(_) => "Attach",
            Self::Init(_) => "Init",
            Self::Scaffold(_) => "Scaffold",
            Self::Host { .. } => "Host",
            Self::Repo { .. } => "Repo",
            Self::Marker { .. } => "Marker",
            Self::Link(_) => "Link",
            Self::Check(_) => "Check",
            Self::Preflight(_) => "Preflight",
            Self::Start(_) => "Start",
            Self::Status(_) => "Status",
            Self::ContextCheck(_) => "ContextCheck",
            Self::PolicyCheck(_) => "PolicyCheck",
            Self::Gc(_) => "Gc",
            Self::Unlink(_) => "Unlink",
            Self::Sync(_) => "Sync",
            Self::Doctor(_) => "Doctor",
            Self::Consistency(_) => "Consistency",
            Self::Drift(_) => "Drift",
            Self::Handoff { .. } => "Handoff",
            Self::Remember(_) => "Remember",
            Self::Memory { .. } => "Memory",
            Self::HostHook(_) => "HostHook",
            Self::Telemetry { .. } => "Telemetry",
            Self::RuntimeState { .. } => "RuntimeState",
            Self::Thread { .. } => "Thread",
            Self::EscalationState { .. } => "EscalationState",
            Self::Handover(_) => "Handover",
            Self::Checkpoint(_) => "Checkpoint",
            Self::Recovery { .. } => "Recovery",
            Self::Session { .. } => "Session",
            Self::SessionState { .. } => "SessionState",
            Self::Hooks { .. } => "Hooks",
            Self::Skills { .. } => "Skills",
            Self::Pod { .. } => "Pod",
            Self::Machine { .. } => "Machine",
            Self::Migrate { .. } => "Migrate",
            Self::McpServe => "McpServe",
            #[cfg(feature = "daemon")]
            Self::Daemon(_) => "Daemon",
        }
    }
}

#[derive(Subcommand)]
enum HooksCommand {
    Install(HooksInstallArgs),
    Check(HooksCheckArgs),
}

#[derive(Subcommand)]
enum HostCommand {
    /// Apply a repo-local host integration previously scaffolded by `ccd init`.
    ///
    /// The target host is inferred from which `.ccd-hosts/` subdir is present;
    /// pass `--host` only when multiple hosts need mode-dependent application.
    Apply(HostApplyArgs),
}

#[derive(Subcommand)]
enum RepoCommand {
    Status(RepoStatusArgs),
    List(RepoListArgs),
    Relink(RepoRelinkArgs),
    Merge(RepoMergeArgs),
    Split(RepoSplitArgs),
}

#[derive(Subcommand)]
enum HandoffCommand {
    Refresh(HandoffRefreshArgs),
    Export(HandoffExportArgs),
    Write(HandoffWriteArgs),
}

#[derive(Subcommand)]
enum MemoryCommand {
    Search(MemorySearchArgs),
    Describe(MemoryDescribeArgs),
    Expand(MemoryExpandArgs),
    SyncStatus(MemorySyncStatusArgs),
    SourceMap(MemorySourceMapArgs),
    Evidence {
        #[command(subcommand)]
        command: MemoryEvidenceCommand,
    },
    Candidate {
        #[command(subcommand)]
        command: MemoryCandidateCommand,
    },
    Compact(MemoryCompactArgs),
    Promote(MemoryPromoteArgs),
}

#[derive(Subcommand)]
enum MemoryEvidenceCommand {
    Submit(MemoryEvidenceSubmitArgs),
}

#[derive(Subcommand)]
enum MemoryCandidateCommand {
    Admit(MemoryCandidateAdmitArgs),
    Extract(MemoryCandidateExtractArgs),
}

#[derive(Subcommand)]
enum SkillsCommand {
    Install(SkillsInstallArgs),
}

#[derive(Subcommand)]
enum PodCommand {
    Init(PodInitArgs),
    List,
    Status(PodStatusArgs),
    MigrateDefaults(PodMigrateDefaultsArgs),
}

#[derive(Subcommand)]
enum MachineCommand {
    /// List every machine registered under `~/.ccd/machines/` (flat
    /// layout) or `~/.ccd/pods/{pod}/machine.toml` (legacy layout).
    List,
}

#[derive(Subcommand)]
enum MigrateCommand {
    /// Relocate machine identity and presence records from the
    /// pod-scoped layout to `~/.ccd/machines/`, leaving every other
    /// file in place. Writes a `MIGRATED.md` marker per pod with a
    /// full inventory of preserved files.
    FromPodLayout(MigrateFromPodLayoutArgs),
}

#[derive(Args)]
struct MigrateFromPodLayoutArgs {
    /// Override the CCD root (`~/.ccd/` by default). Primarily for
    /// testing; operators should not normally set this.
    #[arg(long)]
    path: Option<PathBuf>,
    /// Emit the migration plan without relocating any files.
    #[arg(long)]
    dry_run: bool,
}

#[cfg(feature = "daemon")]
#[derive(Args)]
struct DaemonArgs {
    #[arg(long, default_value = "127.0.0.1")]
    host: String,
    #[arg(long, default_value_t = 8820)]
    port: u16,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long)]
    cors: bool,
}

#[derive(Args)]
struct PodInitArgs {
    /// Coordination scope name; writes canonical `[dispatch].coordination_scope`
    name: String,
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long)]
    force: bool,
}

#[derive(Args)]
struct PodStatusArgs {
    /// Coordination scope name (omit to resolve from --path context)
    name: Option<String>,
    #[arg(long)]
    path: Option<PathBuf>,
    #[arg(long)]
    profile: Option<String>,
}

#[derive(Args)]
struct PodMigrateDefaultsArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long = "memory-item")]
    memory_items: Vec<String>,
    #[arg(long = "policy-item")]
    policy_items: Vec<String>,
    #[arg(long)]
    adopt_suggested: bool,
    #[arg(long)]
    write: bool,
}

#[derive(Subcommand)]
enum RuntimeStateCommand {
    Export(RuntimeStateExportArgs),
    ChildBootstrap(RuntimeStateChildBootstrapArgs),
}

#[derive(Subcommand)]
enum ThreadCommand {
    /// Export an experimental portable thread transfer capsule.
    Export(ThreadExportArgs),
    /// Preview an experimental portable thread transfer capsule import.
    Import(ThreadImportArgs),
}

#[derive(Subcommand)]
enum TelemetryCommand {
    Report(TelemetryReportArgs),
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum PolicyActionFamilyArg {
    #[value(name = "read_only")]
    ReadOnly,
    #[value(name = "local_execution")]
    LocalExecution,
    #[value(name = "local_isolation")]
    LocalIsolation,
    #[value(name = "local_finalize")]
    LocalFinalize,
    #[value(name = "publish_external")]
    PublishExternal,
    #[value(name = "shared_queue_mutation")]
    SharedQueueMutation,
    #[value(name = "durable_memory_mutation")]
    DurableMemoryMutation,
    #[value(name = "repair_destructive")]
    RepairDestructive,
    #[value(name = "protected_surface_mutation")]
    ProtectedSurfaceMutation,
}

impl PolicyActionFamilyArg {
    fn into_policy_action_family(self) -> state::policy_projection::PolicyActionFamily {
        match self {
            Self::ReadOnly => state::policy_projection::PolicyActionFamily::ReadOnly,
            Self::LocalExecution => state::policy_projection::PolicyActionFamily::LocalExecution,
            Self::LocalIsolation => state::policy_projection::PolicyActionFamily::LocalIsolation,
            Self::LocalFinalize => state::policy_projection::PolicyActionFamily::LocalFinalize,
            Self::PublishExternal => state::policy_projection::PolicyActionFamily::PublishExternal,
            Self::SharedQueueMutation => {
                state::policy_projection::PolicyActionFamily::SharedQueueMutation
            }
            Self::DurableMemoryMutation => {
                state::policy_projection::PolicyActionFamily::DurableMemoryMutation
            }
            Self::RepairDestructive => {
                state::policy_projection::PolicyActionFamily::RepairDestructive
            }
            Self::ProtectedSurfaceMutation => {
                state::policy_projection::PolicyActionFamily::ProtectedSurfaceMutation
            }
        }
    }
}

#[derive(Subcommand)]
enum RecoveryCommand {
    Write(RecoveryWriteArgs),
}

#[derive(Subcommand)]
enum EscalationStateCommand {
    Set(EscalationStateSetArgs),
    Clear(EscalationStateClearArgs),
    List(EscalationStateListArgs),
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum ProjectionTargetArg {
    Default,
    Planning,
    Session,
}

impl ProjectionTargetArg {
    fn into_projection_target(self) -> state::compiled::ProjectionTarget {
        match self {
            Self::Default => state::compiled::ProjectionTarget::Default,
            Self::Planning => state::compiled::ProjectionTarget::Planning,
            Self::Session => state::compiled::ProjectionTarget::Session,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum ProjectionFormatArg {
    Narrative,
    Symbolic,
    Bundle,
}

impl ProjectionFormatArg {
    fn into_projection_format(self) -> state::compiled::ProjectionFormat {
        match self {
            Self::Narrative => state::compiled::ProjectionFormat::Narrative,
            Self::Symbolic => state::compiled::ProjectionFormat::Symbolic,
            Self::Bundle => state::compiled::ProjectionFormat::Bundle,
        }
    }
}

#[derive(Subcommand)]
enum SessionStateCommand {
    Start(SessionStateStartArgs),
    Heartbeat(SessionStateHeartbeatArgs),
    Clear(SessionStateClearArgs),
    Takeover(SessionStateTakeoverArgs),
    Gates {
        #[command(subcommand)]
        command: SessionGateCommand,
    },
}

#[derive(Subcommand)]
enum SessionGateCommand {
    List(SessionStateArgs),
    Replace(SessionGateReplaceArgs),
    Seed(SessionGateSeedArgs),
    SetStatus(SessionGateSetStatusArgs),
    Advance(SessionGateMutateArgs),
    Clear(SessionGateMutateArgs),
}

#[derive(Subcommand)]
enum SessionCommand {
    Open(SessionOpenArgs),
}

#[derive(Args)]
struct SessionStateArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
}

#[derive(Args, Clone, Default)]
struct ProtectedWriteArgs {
    #[arg(long = "actor-id")]
    actor_id: Option<String>,
    #[arg(long = "session-id")]
    session_id: Option<String>,
    #[arg(long = "expected-revision")]
    expected_revision: Option<u64>,
}

impl ProtectedWriteArgs {
    fn into_state_options(self) -> state::protected_write::ExclusiveWriteOptions {
        state::protected_write::ExclusiveWriteOptions {
            actor_id: self.actor_id,
            session_id: self.session_id,
            expected_revision: self.expected_revision,
        }
    }
}

#[derive(Args)]
struct SessionStateStartArgs {
    #[command(flatten)]
    base: SessionStateArgs,
    #[arg(long, value_enum)]
    mode: Option<SessionModeArg>,
    #[arg(long, value_enum, default_value_t = SessionLifecycleArg::Interactive)]
    lifecycle: SessionLifecycleArg,
    #[arg(long = "owner-kind", value_enum)]
    owner_kind: Option<AutonomousOwnerKindArg>,
    #[arg(long = "actor-id")]
    actor_id: Option<String>,
    #[arg(long = "supervisor-id")]
    supervisor_id: Option<String>,
    #[arg(long = "lease-seconds")]
    lease_seconds: Option<u64>,
    /// Explicitly recover this actor's own stale autonomous lease, preserving prior owner/supervisor/TTL unless overridden.
    #[arg(long = "recover-stale")]
    recover_stale: bool,
}

#[derive(Args)]
struct SessionStateHeartbeatArgs {
    #[command(flatten)]
    base: SessionStateArgs,
    #[arg(long = "actor-id")]
    actor_id: String,
    #[arg(long)]
    activity: Option<String>,
}

#[derive(Args)]
struct SessionStateClearArgs {
    #[command(flatten)]
    base: SessionStateArgs,
    #[arg(long = "actor-id")]
    actor_id: Option<String>,
    #[arg(long)]
    reason: Option<String>,
}

#[derive(Args)]
struct SessionStateTakeoverArgs {
    #[command(flatten)]
    base: SessionStateArgs,
    #[arg(long = "actor-id")]
    actor_id: String,
    #[arg(long = "supervisor-id")]
    supervisor_id: Option<String>,
    #[arg(long)]
    reason: String,
}

#[derive(Args)]
struct SessionGateReplaceArgs {
    #[command(flatten)]
    base: SessionStateArgs,
    #[command(flatten)]
    protected_write: ProtectedWriteArgs,
    #[arg(long = "gate", required = true)]
    gates: Vec<String>,
}

#[derive(Args)]
struct SessionGateSeedArgs {
    #[command(flatten)]
    base: SessionStateArgs,
    #[command(flatten)]
    protected_write: ProtectedWriteArgs,
    #[arg(long, value_enum)]
    from: GateSeedSourceArg,
}

#[derive(Args)]
struct SessionGateSetStatusArgs {
    #[command(flatten)]
    base: SessionStateArgs,
    #[command(flatten)]
    protected_write: ProtectedWriteArgs,
    #[arg(long)]
    index: usize,
    #[arg(long, value_enum)]
    status: GateStatusArg,
}

#[derive(Args)]
struct SessionGateMutateArgs {
    #[command(flatten)]
    base: SessionStateArgs,
    #[command(flatten)]
    protected_write: ProtectedWriteArgs,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum GateSeedSourceArg {
    ImmediateActions,
    DefinitionOfDone,
}

impl GateSeedSourceArg {
    fn into_state_source(self) -> state::session_gates::GateSeedSource {
        match self {
            Self::ImmediateActions => state::session_gates::GateSeedSource::ImmediateActions,
            Self::DefinitionOfDone => state::session_gates::GateSeedSource::DefinitionOfDone,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum GateStatusArg {
    Open,
    Done,
    Blocked,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum SessionModeArg {
    General,
    Research,
    Implement,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum SessionLifecycleArg {
    Interactive,
    Autonomous,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum AttemptOutcomeArg {
    Progress,
    NoProgress,
    Neutral,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum AutonomousOwnerKindArg {
    RuntimeWorker,
    RuntimeSupervisor,
}

impl SessionModeArg {
    fn into_state_mode(self) -> state::session::SessionMode {
        match self {
            Self::General => state::session::SessionMode::General,
            Self::Research => state::session::SessionMode::Research,
            Self::Implement => state::session::SessionMode::Implement,
        }
    }
}

impl SessionLifecycleArg {
    fn into_state_lifecycle(self) -> state::session::SessionLifecycle {
        match self {
            Self::Interactive => state::session::SessionLifecycle::Interactive,
            Self::Autonomous => state::session::SessionLifecycle::Autonomous,
        }
    }
}

impl AttemptOutcomeArg {
    fn into_state_outcome(self) -> state::work_stream_decay::AttemptOutcome {
        match self {
            Self::Progress => state::work_stream_decay::AttemptOutcome::Progress,
            Self::NoProgress => state::work_stream_decay::AttemptOutcome::NoProgress,
            Self::Neutral => state::work_stream_decay::AttemptOutcome::Neutral,
        }
    }
}

impl AutonomousOwnerKindArg {
    fn into_state_owner_kind(self) -> state::session::SessionOwnerKind {
        match self {
            Self::RuntimeWorker => state::session::SessionOwnerKind::RuntimeWorker,
            Self::RuntimeSupervisor => state::session::SessionOwnerKind::RuntimeSupervisor,
        }
    }
}

impl GateStatusArg {
    fn into_status(self) -> state::session_gates::ExecutionGateStatus {
        match self {
            Self::Open => state::session_gates::ExecutionGateStatus::Open,
            Self::Done => state::session_gates::ExecutionGateStatus::Done,
            Self::Blocked => state::session_gates::ExecutionGateStatus::Blocked,
        }
    }
}

#[derive(Args)]
struct SessionOpenArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long)]
    worktree: Option<PathBuf>,
    #[arg(long)]
    branch: Option<String>,
    #[arg(long = "from")]
    from_ref: Option<String>,
    /// Retired under RFC 0006 M4(d); accepted so callers receive a
    /// fail-closed remediation error instead of an `unexpected argument`
    /// parse failure.
    #[arg(long, hide = true)]
    pod: Option<String>,
}

#[derive(Args)]
struct SkillsInstallArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long, default_value_t = true)]
    global: bool,
    #[arg(long)]
    local: bool,
}

#[derive(Args)]
struct RuntimeStateExportArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long, value_enum)]
    projection_target: Option<ProjectionTargetArg>,
    #[arg(long, value_enum)]
    projection_format: Option<ProjectionFormatArg>,
}

#[derive(Args)]
struct RuntimeStateChildBootstrapArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
}

#[derive(Args)]
struct ThreadExportArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
}

#[derive(Args)]
struct ThreadImportArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long, value_name = "FILE")]
    from: PathBuf,
    #[arg(long)]
    preview: bool,
    #[arg(long, hide = true)]
    write: bool,
}

#[derive(Args)]
struct TelemetryReportArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long, default_value_t = 20)]
    limit: usize,
}

#[derive(Args, Clone)]
struct EscalationStateArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
}

#[derive(Args)]
struct EscalationStateSetArgs {
    #[command(flatten)]
    base: EscalationStateArgs,
    #[command(flatten)]
    protected_write: ProtectedWriteArgs,
    #[arg(long, value_enum, default_value_t = EscalationKindArg::Blocking)]
    kind: EscalationKindArg,
    #[arg(long)]
    id: Option<String>,
    #[arg(long)]
    reason: String,
}

#[derive(Args)]
struct EscalationStateClearArgs {
    #[command(flatten)]
    base: EscalationStateArgs,
    #[command(flatten)]
    protected_write: ProtectedWriteArgs,
    #[arg(long)]
    id: Option<String>,
}

#[derive(Args)]
struct EscalationStateListArgs {
    #[command(flatten)]
    base: EscalationStateArgs,
}

#[derive(Args)]
struct HooksInstallArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    force: bool,
}

#[derive(Args)]
struct HooksCheckArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
}

#[derive(Args)]
struct HandoffRefreshArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long)]
    write: bool,
}

#[derive(Args)]
struct HandoffExportArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
}

#[derive(Args)]
struct HandoffWriteArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[command(flatten)]
    protected_write: ProtectedWriteArgs,
    /// Skip the write-time path-reference audit. Use this only when the
    /// handoff deliberately names a path that does not exist yet.
    #[arg(long = "allow-missing-refs")]
    allow_missing_refs: bool,
}

#[derive(Args)]
struct RecoveryWriteArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[command(flatten)]
    protected_write: ProtectedWriteArgs,
}

#[derive(Args)]
struct RememberArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long, value_enum)]
    scope: Option<RememberScopeArg>,
    #[arg(long = "type", value_enum, default_value_t = RememberTypeArg::Observation)]
    entry_type: RememberTypeArg,
    #[arg(long, value_enum, default_value_t = RememberOriginArg::Manual)]
    origin: RememberOriginArg,
    #[arg(long)]
    source_ref: Option<String>,
    #[arg(long)]
    dry_run: bool,
    content: String,
}

#[derive(Args)]
struct AttachArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long = "project-id")]
    project_id: Option<String>,
    #[arg(long)]
    display_name: Option<String>,
}

#[derive(Args)]
struct InitArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long = "project-id")]
    project_id: Option<String>,
    #[arg(long)]
    display_name: Option<String>,
    #[arg(long)]
    force: bool,
}

#[derive(Args)]
struct ScaffoldArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    force: bool,
}

#[derive(Subcommand)]
enum MarkerCommand {
    Repair(MarkerRepairArgs),
}

#[derive(Args)]
struct MarkerRepairArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    /// Write the repaired marker. Without --yes this command prints a safe preview only.
    #[arg(long)]
    yes: bool,
}

#[derive(Args)]
struct LinkArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long = "project-id")]
    project_id: Option<String>,
    #[arg(long)]
    display_name: Option<String>,
}

#[derive(Args)]
struct HostApplyArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long)]
    force: bool,
    #[arg(long = "with-launcher")]
    with_launcher: bool,
    /// Optional host selector when multiple hosts are scaffolded and
    /// mode-dependent application (e.g. Codex launcher mode) must be
    /// targeted at one specific host.
    #[arg(long = "host", value_enum)]
    host: Option<RepoHostArg>,
}

#[derive(Args)]
struct RepoStatusArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
}

#[derive(Args)]
struct RepoListArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
}

#[derive(Args)]
struct RepoRelinkArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    locality_id: String,
}

#[derive(Args)]
struct RepoMergeArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    target_locality_id: String,
    source_locality_id: String,
    #[arg(long)]
    force: bool,
}

#[derive(Args)]
struct RepoSplitArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    source_locality_id: String,
}

#[derive(Args)]
struct CheckArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
}

#[derive(Args)]
struct PreflightArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
}

#[derive(Args)]
struct StartArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long)]
    refresh: bool,
    #[arg(long)]
    check: bool,
    #[arg(long, value_delimiter = ',')]
    fields: Option<Vec<String>>,
    #[arg(long, value_enum)]
    memory_depth: Option<output::MemoryDepth>,
    #[arg(long)]
    activate: bool,
    #[arg(long, value_enum)]
    mode: Option<SessionModeArg>,
}

#[derive(Args)]
struct StatusArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long, value_delimiter = ',')]
    fields: Option<Vec<String>>,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum ContextCheckTriggerArg {
    Interval,
    Manual,
    #[value(name = "pre-compaction")]
    PreCompaction,
    #[value(name = "idle-reset")]
    IdleReset,
    Resume,
    #[value(name = "supervisor-poll")]
    SupervisorPoll,
}

impl ContextCheckTriggerArg {
    fn into_trigger(self) -> state::radar::ContextCheckTrigger {
        match self {
            Self::Interval => state::radar::ContextCheckTrigger::Interval,
            Self::Manual => state::radar::ContextCheckTrigger::Manual,
            Self::PreCompaction => state::radar::ContextCheckTrigger::PreCompaction,
            Self::IdleReset => state::radar::ContextCheckTrigger::IdleReset,
            Self::Resume => state::radar::ContextCheckTrigger::Resume,
            Self::SupervisorPoll => state::radar::ContextCheckTrigger::SupervisorPoll,
        }
    }
}

#[derive(Args)]
struct ContextCheckArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long, value_enum, default_value = "interval")]
    trigger: ContextCheckTriggerArg,
    #[arg(long, value_delimiter = ',')]
    fields: Option<Vec<String>>,
    #[arg(long, value_enum)]
    memory_depth: Option<output::MemoryDepth>,
}

#[derive(Args)]
struct PolicyCheckArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long, value_enum)]
    action_family: PolicyActionFamilyArg,
}

#[derive(Args)]
struct GcArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
}

#[derive(Args)]
struct UnlinkArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
}

#[derive(Args)]
struct SyncArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long)]
    check: bool,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum DoctorSeverityFilter {
    Pass,
    Warn,
    Fail,
}

#[derive(Args)]
struct DoctorArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long, hide = true)]
    skip_repo_native_checks: bool,
    #[arg(long, value_enum)]
    severity: Option<DoctorSeverityFilter>,
    #[arg(long, value_delimiter = ',')]
    fields: Option<Vec<String>>,
}

#[derive(Args)]
struct ConsistencyArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
}

#[derive(Args)]
struct DriftArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
}

#[derive(Args)]
struct MemoryPromoteArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[command(flatten)]
    protected_write: ProtectedWriteArgs,
    #[arg(long)]
    entry: String,
    #[arg(long, value_enum)]
    source_outcome: Option<MemoryPromoteSourceOutcomeArg>,
    /// Destination for the promotion. Defaults to `profile-memory`.
    /// Use `work-stream-memory` (or compatibility `branch-memory`) for
    /// workspace -> work-stream promotion, `project-memory` (or
    /// compatibility `repo-memory`) for work-stream/workspace -> project
    /// promotion, or `project-truth` to draft the entry toward a
    /// project-truth file.
    #[arg(long, value_enum, default_value = "profile-memory")]
    destination: MemoryPromoteDestinationArg,
    /// Path to the project-truth target file. Required when `--destination project-truth`.
    #[arg(long)]
    target_file: Option<PathBuf>,
    #[arg(long)]
    write: bool,
}

#[derive(Args)]
struct MemoryCandidateAdmitArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[command(flatten)]
    protected_write: ProtectedWriteArgs,
    #[arg(long)]
    entry: String,
    #[arg(long, value_enum)]
    source_scope: MemoryCandidateSourceScopeArg,
    #[arg(long, value_enum)]
    destination: MemoryCandidateDestinationArg,
    #[arg(long)]
    write: bool,
}

#[derive(Args)]
struct MemoryEvidenceSubmitArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[command(flatten)]
    protected_write: ProtectedWriteArgs,
    #[arg(long, value_enum)]
    scope: MemoryEvidenceScopeArg,
    #[arg(long = "type", value_enum)]
    entry_type: RememberTypeArg,
    #[arg(long = "source-kind", value_enum, default_value_t = MemoryEvidenceSourceKindArg::HookOutput)]
    source_kind: MemoryEvidenceSourceKindArg,
    #[arg(long)]
    source_ref: Option<String>,
    #[arg(long = "host-hook", value_enum)]
    host_hook: Option<HostHookArg>,
    #[arg(long)]
    host: Option<String>,
    #[arg(long)]
    provider: Option<String>,
    #[arg(long = "provider-ref")]
    provider_ref: Option<String>,
    #[arg(long)]
    summary: String,
}

#[derive(Args)]
struct HostHookRunArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long)]
    host: Option<String>,
    #[arg(long, value_enum)]
    mode: Option<SessionModeArg>,
    #[arg(long, value_enum, default_value_t = SessionLifecycleArg::Interactive)]
    lifecycle: SessionLifecycleArg,
    #[arg(long = "owner-kind", value_enum)]
    owner_kind: Option<AutonomousOwnerKindArg>,
    #[arg(long = "supervisor-id")]
    supervisor_id: Option<String>,
    #[arg(long = "lease-seconds")]
    lease_seconds: Option<u64>,
    #[arg(long = "host-session-id")]
    host_session_id: Option<String>,
    #[arg(long = "host-run-id")]
    host_run_id: Option<String>,
    #[arg(long = "host-task-id")]
    host_task_id: Option<String>,
    #[arg(long)]
    diagnostics: bool,
    #[arg(long = "host-total-context-chars")]
    host_total_context_chars: Option<u64>,
    #[arg(long, value_enum)]
    hook: HostHookArg,
    #[command(flatten)]
    protected_write: ProtectedWriteArgs,
    #[arg(long = "capture-scope", value_enum)]
    capture_scope: Option<MemoryEvidenceScopeArg>,
    #[arg(long = "capture-type", value_enum)]
    capture_type: Option<RememberTypeArg>,
    #[arg(long = "capture-source-kind", value_enum)]
    capture_source_kind: Option<MemoryEvidenceSourceKindArg>,
    #[arg(long = "capture-summary")]
    capture_summary: Option<String>,
    #[arg(long = "capture-source-ref")]
    capture_source_ref: Option<String>,
    #[arg(long = "capture-provider")]
    capture_provider: Option<String>,
    #[arg(long = "capture-provider-ref", requires = "capture_provider")]
    capture_provider_ref: Option<String>,
    #[arg(long = "attempt-outcome", value_enum)]
    attempt_outcome: Option<AttemptOutcomeArg>,
}

#[derive(Args)]
struct MemoryCandidateExtractArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[command(flatten)]
    protected_write: ProtectedWriteArgs,
    #[arg(long = "evidence-id")]
    evidence_id: Option<String>,
    #[arg(long, default_value_t = 8, value_parser = parse_positive_usize)]
    limit: usize,
    #[arg(long)]
    write: bool,
}

#[derive(Args)]
struct MemoryCompactArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[command(flatten)]
    protected_write: ProtectedWriteArgs,
    #[arg(long, value_enum)]
    scope: MemoryCompactScopeArg,
    #[arg(long)]
    entry: Option<String>,
    #[arg(long)]
    keep: Option<String>,
    #[arg(long, value_enum)]
    decay_class: Option<MemoryDecayClassArg>,
    #[arg(long, value_enum)]
    review: Option<MemoryCompactReviewArg>,
    #[arg(long)]
    remove: bool,
    #[arg(long)]
    write: bool,
}

#[derive(Args)]
struct MemorySearchArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long)]
    query: String,
    #[arg(long, default_value_t = 5, value_parser = parse_positive_usize)]
    limit: usize,
}

#[derive(Args)]
struct MemoryDescribeArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long = "native-id")]
    native_id: String,
}

#[derive(Args)]
struct MemoryExpandArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long = "native-id")]
    native_id: String,
    #[arg(long, default_value_t = 3, value_parser = parse_positive_usize)]
    limit: usize,
}

#[derive(Args)]
struct MemorySyncStatusArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
}

#[derive(Args)]
struct MemorySourceMapArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
}

fn parse_positive_usize(raw: &str) -> Result<usize, String> {
    let value = raw
        .parse::<usize>()
        .map_err(|_| format!("invalid value `{raw}`: expected a positive integer"))?;
    if value == 0 {
        return Err("expected a positive integer greater than zero".to_owned());
    }
    Ok(value)
}

#[derive(Args)]
struct HandoverArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long, value_delimiter = ',')]
    fields: Option<Vec<String>>,
    #[arg(long, value_enum)]
    memory_depth: Option<output::MemoryDepth>,
    #[arg(long)]
    commit: bool,
    /// Apply deterministic clean close-out when handover finds no human-choice surfaces.
    #[arg(long = "close-clean")]
    close_clean: bool,
    #[arg(long)]
    since_session: Option<String>,
}

#[derive(Args)]
struct CheckpointArgs {
    #[arg(long, default_value = ".")]
    path: PathBuf,
    #[arg(long)]
    profile: Option<String>,
    #[arg(long, value_delimiter = ',')]
    fields: Option<Vec<String>>,
    #[arg(long, value_enum)]
    memory_depth: Option<output::MemoryDepth>,
    #[arg(long)]
    commit: bool,
    #[arg(long)]
    since_session: Option<String>,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
enum MemoryPromoteSourceOutcomeArg {
    Active,
    Superseded,
    LinkOnly,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
enum MemoryPromoteDestinationArg {
    #[value(
        name = "work-stream-memory",
        alias = "branch-memory",
        alias = "work_stream-memory",
        alias = "work_stream_memory"
    )]
    BranchMemory,
    #[value(name = "project-memory", alias = "repo-memory")]
    RepoMemory,
    #[value(name = "profile-memory")]
    ProfileMemory,
    #[value(name = "project-truth")]
    ProjectTruth,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
enum MemoryCandidateSourceScopeArg {
    #[value(name = "workspace-memory", alias = "clone-memory")]
    Clone,
    #[value(
        name = "work-stream-memory",
        alias = "branch-memory",
        alias = "work-stream-memory",
        alias = "work_stream-memory",
        alias = "work_stream_memory"
    )]
    Branch,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
enum MemoryCandidateDestinationArg {
    #[value(
        name = "work-stream-memory",
        alias = "branch-memory",
        alias = "work-stream-memory",
        alias = "work_stream-memory",
        alias = "work_stream_memory"
    )]
    Branch,
    #[value(name = "project-memory", alias = "repo-memory")]
    Repo,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
enum MemoryEvidenceScopeArg {
    #[value(name = "workspace", alias = "clone")]
    Workspace,
    #[value(name = "work-stream", alias = "work_stream", alias = "branch")]
    WorkStream,
    #[value(name = "project", alias = "repo")]
    Project,
    Profile,
    #[value(name = "project-truth", alias = "project_truth")]
    ProjectTruth,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
enum MemoryEvidenceSourceKindArg {
    Transcript,
    Session,
    #[value(name = "event-stream", alias = "event_stream")]
    EventStream,
    #[value(name = "hook-output", alias = "hook_output")]
    HookOutput,
    Log,
    Document,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
enum HostHookArg {
    #[value(name = "on-session-start")]
    OnSessionStart,
    #[value(name = "before-prompt-build")]
    BeforePromptBuild,
    #[value(name = "on-compaction-notice")]
    OnCompactionNotice,
    #[value(name = "on-agent-end")]
    OnAgentEnd,
    #[value(name = "on-session-end")]
    OnSessionEnd,
    #[value(name = "supervisor-tick")]
    SupervisorTick,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
enum RepoHostArg {
    #[value(name = "claude", alias = "claude-code")]
    Claude,
    Codex,
    Hermes,
    #[value(name = "openclaw")]
    OpenClaw,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
enum MemoryCompactScopeArg {
    Profile,
    #[value(name = "project", alias = "repo")]
    Repo,
    #[value(name = "work-stream", alias = "branch", alias = "work_stream")]
    Branch,
    #[value(name = "workspace", alias = "clone")]
    Clone,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
enum MemoryCompactReviewArg {
    Expired,
    Superseded,
    PromotionCandidate,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
enum MemoryDecayClassArg {
    Permanent,
    Stable,
    Active,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum EscalationKindArg {
    Blocking,
    #[value(alias = "non_blocking")]
    NonBlocking,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
enum RememberScopeArg {
    Profile,
    #[value(name = "project", alias = "repo")]
    Repo,
    #[value(name = "work-stream", alias = "branch", alias = "work_stream")]
    Branch,
    #[value(name = "workspace", alias = "clone")]
    Clone,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
enum RememberTypeArg {
    Rule,
    Constraint,
    Heuristic,
    Observation,
    Attempt,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
enum RememberOriginArg {
    Manual,
    Agent,
    Radar,
}

impl MemoryCompactScopeArg {
    fn into_scope(self) -> memory::compact::CompactScope {
        match self {
            Self::Profile => memory::compact::CompactScope::Profile,
            Self::Repo => memory::compact::CompactScope::Repo,
            Self::Branch => memory::compact::CompactScope::Branch,
            Self::Clone => memory::compact::CompactScope::Clone,
        }
    }
}

impl MemoryCompactReviewArg {
    fn into_review_kind(self) -> memory::compact::CompactReviewKind {
        match self {
            Self::Expired => memory::compact::CompactReviewKind::Expired,
            Self::Superseded => memory::compact::CompactReviewKind::Superseded,
            Self::PromotionCandidate => memory::compact::CompactReviewKind::PromotionCandidate,
        }
    }
}

impl MemoryDecayClassArg {
    fn into_decay_class(self) -> memory::compact::DecayClass {
        match self {
            Self::Permanent => memory::compact::DecayClass::Permanent,
            Self::Stable => memory::compact::DecayClass::Stable,
            Self::Active => memory::compact::DecayClass::Active,
        }
    }
}

impl EscalationKindArg {
    fn into_kind(self) -> state::escalation::EscalationKind {
        match self {
            Self::Blocking => state::escalation::EscalationKind::Blocking,
            Self::NonBlocking => state::escalation::EscalationKind::NonBlocking,
        }
    }
}

impl RememberScopeArg {
    fn into_scope(self) -> memory::remember::RememberScope {
        match self {
            Self::Profile => memory::remember::RememberScope::Profile,
            Self::Repo => memory::remember::RememberScope::Repo,
            Self::Branch => memory::remember::RememberScope::Branch,
            Self::Clone => memory::remember::RememberScope::Clone,
        }
    }
}

impl RememberTypeArg {
    fn as_str(self) -> &'static str {
        match self {
            Self::Rule => "rule",
            Self::Constraint => "constraint",
            Self::Heuristic => "heuristic",
            Self::Observation => "observation",
            Self::Attempt => "attempt",
        }
    }
}

impl RememberOriginArg {
    fn as_str(self) -> &'static str {
        match self {
            Self::Manual => "manual",
            Self::Agent => "agent",
            Self::Radar => "radar",
        }
    }
}

impl MemoryEvidenceScopeArg {
    fn as_str(self) -> &'static str {
        match self {
            Self::Workspace => "workspace",
            Self::WorkStream => "work_stream",
            Self::Project => "project",
            Self::Profile => "profile",
            Self::ProjectTruth => "project_truth",
        }
    }
}

impl MemoryEvidenceSourceKindArg {
    fn as_str(self) -> &'static str {
        match self {
            Self::Transcript => "transcript",
            Self::Session => "session",
            Self::EventStream => "event_stream",
            Self::HookOutput => "hook_output",
            Self::Log => "log",
            Self::Document => "document",
        }
    }
}

impl HostHookArg {
    fn as_str(self) -> &'static str {
        match self {
            Self::OnSessionStart => "on_session_start",
            Self::BeforePromptBuild => "before_prompt_build",
            Self::OnCompactionNotice => "on_compaction_notice",
            Self::OnAgentEnd => "on_agent_end",
            Self::OnSessionEnd => "on_session_end",
            Self::SupervisorTick => "supervisor_tick",
        }
    }
}

impl RepoHostArg {
    fn into_host(self) -> commands::host::ManagedHost {
        match self {
            Self::Claude => commands::host::ManagedHost::Claude,
            Self::Codex => commands::host::ManagedHost::Codex,
            Self::Hermes => commands::host::ManagedHost::Hermes,
            Self::OpenClaw => commands::host::ManagedHost::OpenClaw,
        }
    }
}

fn resolve_host_adapter_name(explicit: Option<String>) -> Result<String> {
    explicit
        .and_then(|value| {
            let trimmed = value.trim();
            if trimmed.is_empty() {
                None
            } else {
                Some(trimmed.to_owned())
            }
        })
        .or_else(|| telemetry::host::env_string(&["CCD_HOST_ADAPTER"]))
        .ok_or_else(|| anyhow::anyhow!("missing host adapter: pass --host or set CCD_HOST_ADAPTER"))
}

fn build_host_hook_capture(
    args: &HostHookRunArgs,
    host: &str,
    runtime_hints: &memory::evidence::SubmitRuntimeHints,
) -> Result<Option<memory::evidence::MemoryEvidenceEnvelope>> {
    let hook = args.hook.as_str();
    let capture_requested = args.capture_scope.is_some()
        || args.capture_type.is_some()
        || args.capture_source_kind.is_some()
        || args.capture_summary.is_some()
        || args.capture_source_ref.is_some()
        || args.capture_provider.is_some()
        || args.capture_provider_ref.is_some();

    if !capture_requested {
        return Ok(None);
    }

    if hook != "on_compaction_notice" {
        anyhow::bail!("capture arguments are only supported for --hook on-compaction-notice");
    }

    let scope = args
        .capture_scope
        .ok_or_else(|| anyhow::anyhow!("missing required argument: --capture-scope"))?
        .as_str()
        .to_owned();
    let entry_type = args
        .capture_type
        .ok_or_else(|| anyhow::anyhow!("missing required argument: --capture-type"))?
        .as_str()
        .to_owned();
    let source_kind = args
        .capture_source_kind
        .unwrap_or(MemoryEvidenceSourceKindArg::HookOutput)
        .as_str()
        .to_owned();
    let summary = args
        .capture_summary
        .clone()
        .ok_or_else(|| anyhow::anyhow!("missing required argument: --capture-summary"))?;

    Ok(Some(memory::evidence::MemoryEvidenceEnvelope {
        scope,
        entry_type,
        source_kind,
        summary,
        source_ref: args.capture_source_ref.clone(),
        host_reference: Some(memory::evidence::MemoryEvidenceHostReference {
            host: host.to_owned(),
            hook: Some(hook.to_owned()),
            session_id: runtime_hints.host_session_id.clone(),
            run_id: runtime_hints.host_run_id.clone(),
            task_id: runtime_hints.host_task_id.clone(),
        }),
        provider_reference: args.capture_provider.clone().map(|provider| {
            memory::evidence::MemoryEvidenceProviderReference {
                provider,
                native_id: args.capture_provider_ref.clone(),
            }
        }),
    }))
}

fn host_hook_runtime_hints(args: &HostHookRunArgs) -> memory::evidence::SubmitRuntimeHints {
    memory::evidence::SubmitRuntimeHints {
        host_session_id: args.host_session_id.clone(),
        host_run_id: args.host_run_id.clone(),
        host_task_id: args.host_task_id.clone(),
    }
}

fn host_hook_session_start_options(
    args: &HostHookRunArgs,
) -> host_adapter::HostSessionStartOptions {
    host_adapter::HostSessionStartOptions {
        mode: args.mode.map(SessionModeArg::into_state_mode),
        lifecycle: args.lifecycle.into_state_lifecycle(),
        owner_kind: args
            .owner_kind
            .map(AutonomousOwnerKindArg::into_state_owner_kind),
        actor_id: args.protected_write.actor_id.clone(),
        supervisor_id: args.supervisor_id.clone(),
        lease_ttl_secs: args.lease_seconds,
        host_session_id: args.host_session_id.clone(),
        host_run_id: args.host_run_id.clone(),
        host_task_id: args.host_task_id.clone(),
    }
}

impl MemoryPromoteSourceOutcomeArg {
    fn into_source_outcome(self) -> memory::promote::SourceOutcome {
        match self {
            Self::Active => memory::promote::SourceOutcome::Active,
            Self::Superseded => memory::promote::SourceOutcome::Superseded,
            Self::LinkOnly => memory::promote::SourceOutcome::LinkOnly,
        }
    }
}

impl MemoryCandidateSourceScopeArg {
    fn into_source_scope(self) -> memory::candidate::CandidateSourceScope {
        match self {
            Self::Clone => memory::candidate::CandidateSourceScope::Clone,
            Self::Branch => memory::candidate::CandidateSourceScope::Branch,
        }
    }
}

impl MemoryCandidateDestinationArg {
    fn into_destination(self) -> memory::candidate::CandidateDestination {
        match self {
            Self::Branch => memory::candidate::CandidateDestination::Branch,
            Self::Repo => memory::candidate::CandidateDestination::Repo,
        }
    }
}

/// Apply the `--since-session` delta filter to a serialized radar/checkpoint
/// JSON value. Collapses unchanged source-data surfaces to stubs using
/// digests from the session baseline. Shared by both command handlers.
fn apply_since_session_delta(
    mut value: serde_json::Value,
    repo_path: &std::path::Path,
    explicit_profile: Option<&str>,
    since: &str,
    current_digests: &state::compiled::ProjectionDigests,
) -> anyhow::Result<serde_json::Value> {
    let session_layout =
        paths::state::StateLayout::resolve(repo_path, profile::resolve(explicit_profile)?)?;

    let mut delta_status = "filtered";
    let mut delta_warning = None;
    let mut skip_stats = state::compiled::ProjectionDeltaStats::default();
    let target_session_id = if since == "current" {
        match state::session::load_session_id(&session_layout)? {
            Some(session_id) => Some(session_id),
            None => {
                delta_status = "no_active_session";
                delta_warning = Some(
                    "--since-session current: no active session found; emitting full output"
                        .to_owned(),
                );
                None
            }
        }
    } else {
        Some(since.to_owned())
    };

    if let Some(target_session_id) = target_session_id.as_deref() {
        match state::projection_metadata::load_baseline_for_session(
            &session_layout,
            target_session_id,
        )? {
            Some(obs) if obs.projection_digests.is_some() => {
                let baseline = obs.projection_digests.unwrap();
                skip_stats =
                    state::compiled::compute_projection_delta_stats(&baseline, current_digests);
                value = state::radar::apply_delta_filter(value, &baseline, current_digests);
            }
            _ => {
                delta_status = "baseline_missing";
                delta_warning =
                    Some("no baseline found for session; emitting full output".to_owned());
            }
        }
    }

    if let Some(obj) = value.as_object_mut() {
        if let Some(warning) = delta_warning.as_deref() {
            obj.insert("delta_warning".to_owned(), serde_json::json!(warning));
        }
        obj.insert(
            "session_delta".to_owned(),
            serde_json::json!({
                "requested": since,
                "status": delta_status,
                "resolved_session_id": target_session_id,
                "skip_stats": skip_stats,
                "warning": delta_warning,
            }),
        );
    }

    Ok(value)
}

fn main() -> ExitCode {
    logging::init();
    let matches = cli_command().get_matches();
    let output = matches
        .get_one::<OutputFormat>("output")
        .copied()
        .unwrap_or(OutputFormat::Text);

    match run_matches(&matches) {
        Ok(code) => code,
        Err(error) => {
            output::render_error(output, &error);
            ExitCode::from(1)
        }
    }
}

pub(crate) fn cli_command() -> clap::Command {
    extensions::augment_clap(Cli::command())
}

fn run_matches(matches: &clap::ArgMatches) -> Result<ExitCode> {
    let output = matches
        .get_one::<OutputFormat>("output")
        .copied()
        .unwrap_or(OutputFormat::Text);

    if output == OutputFormat::HookProtocol
        && !matches!(matches.subcommand_name(), Some("host-hook"))
    {
        anyhow::bail!(
            "--output hook-protocol is only supported for `ccd host-hook`; other commands must use --output text or --output json"
        );
    }

    if let Some((subcommand_name, submatches)) = matches.subcommand() {
        if let Some(result) = extensions::dispatch_cli(subcommand_name, submatches, output) {
            return result;
        }
    }

    let cli = Cli::from_arg_matches(matches)?;
    run(cli)
}

fn run(cli: Cli) -> Result<ExitCode> {
    let command_name = cli.command.name();
    if cli.output == OutputFormat::HookProtocol && !matches!(&cli.command, Command::HostHook(_)) {
        anyhow::bail!(
            "--output hook-protocol is only supported for `ccd host-hook`; other commands must use --output text or --output json"
        );
    }
    let _span = tracing::info_span!("command", name = command_name).entered();
    tracing::info!(command = command_name, "dispatching command");
    match cli.command {
        Command::Describe => commands::dispatch::maintenance::describe(cli.output),
        Command::Attach(args) => commands::dispatch::bootstrap::attach(args, cli.output),
        Command::Init(args) => commands::dispatch::bootstrap::init(args, cli.output),
        Command::Scaffold(args) => commands::dispatch::bootstrap::scaffold(args, cli.output),
        Command::Host { command } => commands::dispatch::host::host(command, cli.output),
        Command::Repo { command } => commands::dispatch::repo::dispatch(command, cli.output),
        Command::Marker { command } => commands::dispatch::maintenance::marker(command, cli.output),
        Command::Link(args) => commands::dispatch::bootstrap::link(args, cli.output),
        Command::Check(args) => commands::dispatch::maintenance::check(args, cli.output),
        Command::Preflight(args) => commands::dispatch::session::preflight(args, cli.output),
        Command::Start(args) => commands::dispatch::session::start(args, cli.output),
        Command::Status(args) => commands::dispatch::session::status(args, cli.output),
        Command::ContextCheck(args) => commands::dispatch::state::context_check(args, cli.output),
        Command::PolicyCheck(args) => commands::dispatch::state::policy_check(args, cli.output),
        Command::Gc(args) => commands::dispatch::maintenance::gc(args, cli.output),
        Command::Unlink(args) => commands::dispatch::bootstrap::unlink(args, cli.output),
        Command::Sync(args) => commands::dispatch::maintenance::sync(args, cli.output),
        Command::Doctor(args) => commands::dispatch::maintenance::doctor(args, cli.output),
        Command::Consistency(args) => {
            commands::dispatch::maintenance::consistency(args, cli.output)
        }
        Command::Drift(args) => commands::dispatch::maintenance::drift(args, cli.output),
        Command::Handoff { command } => {
            commands::dispatch::continuity::handoff(command, cli.output)
        }
        Command::Remember(args) => commands::dispatch::memory::remember(args, cli.output),
        Command::Memory { command } => commands::dispatch::memory::dispatch(command, cli.output),
        Command::HostHook(args) => commands::dispatch::host::host_hook(args, cli.output),
        Command::Telemetry { command } => commands::dispatch::host::telemetry(command, cli.output),
        Command::RuntimeState { command } => {
            commands::dispatch::state::runtime_state(command, cli.output)
        }
        Command::Thread { command } => commands::dispatch::state::thread(command, cli.output),
        Command::EscalationState { command } => {
            commands::dispatch::state::escalation_state(command, cli.output)
        }
        Command::Handover(args) => commands::dispatch::state::handover(args, cli.output),
        Command::Checkpoint(args) => commands::dispatch::state::checkpoint(args, cli.output),
        Command::Recovery { command } => {
            commands::dispatch::continuity::recovery(command, cli.output)
        }
        Command::Session { command } => commands::dispatch::session::session(command, cli.output),
        Command::SessionState { command } => {
            commands::dispatch::session::session_state(command, cli.output)
        }
        Command::Hooks { command } => commands::dispatch::maintenance::hooks(command, cli.output),
        Command::Skills { command } => commands::dispatch::maintenance::skills(command, cli.output),
        Command::Pod { command } => commands::dispatch::pod::dispatch(command, cli.output),
        Command::Machine { command } => commands::dispatch::machine::dispatch(command, cli.output),
        Command::Migrate { command } => commands::dispatch::migrate::dispatch(command, cli.output),
        Command::McpServe => commands::dispatch::host::mcp_serve(),
        #[cfg(feature = "daemon")]
        Command::Daemon(args) => commands::dispatch::host::daemon(args),
    }
}
