ccd-cli 1.0.0-beta.1

Bootstrap and validate Continuous Context Development repositories
use std::path::Path;
use std::process::ExitCode;

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

use crate::handoff::{self, BranchMode};
use crate::output::CommandReport;
use crate::paths::state::StateLayout;
use crate::profile;
use crate::repo::marker as repo_marker;
use crate::state::session as session_state;

#[derive(Serialize)]
pub struct HandoffRefreshReport {
    command: &'static str,
    ok: bool,
    path: String,
    profile: String,
    mode: &'static str,
    source: &'static str,
    handoff: HandoffSurfaceView,
    refresh: RefreshView,
    #[serde(skip_serializing_if = "Option::is_none")]
    write_result: Option<WriteResultView>,
    warnings: Vec<String>,
}

#[derive(Serialize)]
struct HandoffSurfaceView {
    path: String,
    status: &'static str,
    section: &'static str,
    section_present: bool,
}

#[derive(Serialize)]
struct RefreshView {
    changed: bool,
    current_lines: Vec<String>,
    replacement_lines: Vec<String>,
    note: &'static str,
}

#[derive(Serialize)]
struct WriteResultView {
    action: &'static str,
    path: String,
    section: &'static str,
}

impl CommandReport for HandoffRefreshReport {
    fn exit_code(&self) -> ExitCode {
        ExitCode::SUCCESS
    }

    fn render_text(&self) {
        match &self.write_result {
            Some(write_result) => {
                println!(
                    "Current System State is derived live and was not written to the handoff."
                );
                println!("Write: {} ({})", write_result.action, write_result.path);
            }
            None => {
                println!("Prepared live Current System State preview.");
            }
        }

        println!("Mode: {}", self.mode);
        println!("Handoff: {} ({})", self.handoff.path, self.handoff.status);

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

        println!();
        println!("Replacement preview:");
        for line in &self.refresh.replacement_lines {
            println!("- {line}");
        }

        println!();
        println!("{}", self.refresh.note);
    }
}

pub fn run(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    write: bool,
) -> Result<HandoffRefreshReport> {
    run_internal(repo_root, explicit_profile, write)
}

fn run_internal(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    write: bool,
) -> Result<HandoffRefreshReport> {
    let profile = profile::resolve(explicit_profile)?;
    let layout = StateLayout::resolve(repo_root, profile.clone())?;
    ensure_profile_exists(&layout, repo_root)?;
    let locality_id = ensure_repo_linked(repo_root)?;
    let native_state_path = layout.state_db_path();
    let git = handoff::read_git_state(repo_root, BranchMode::AllowDetachedHead)?;
    let checkout_advisory = handoff::checkout_continuity_advisory(repo_root, &git);
    let runtime = crate::state::runtime::load_runtime_state(repo_root, &layout, &locality_id)?;
    let handoff_surface = runtime.sources.handoff.clone();
    if handoff_surface.is_missing() {
        bail!(
            "canonical native workspace-local handoff state is missing at {}; \
             create it with `ccd handoff write --path {}` or bootstrap with `ccd start --path {}`",
            native_state_path.display(),
            repo_root.display(),
            repo_root.display()
        );
    }
    let session_id = session_state::load_session_id(&layout)?;
    let replacement_lines = handoff::current_system_state_lines(Some(&git), session_id.as_deref());

    let mut warnings = Vec::new();
    if let Some(advisory) = checkout_advisory {
        warnings.push(advisory.handoff_refresh_warning(&git));
    }

    let write_result = if write {
        Some(WriteResultView {
            action: "derived_live",
            path: native_state_path.display().to_string(),
            section: "Current System State",
        })
    } else {
        None
    };

    let note = if write {
        "Write mode is retained for compatibility only. Current System State is derived live at read time and no canonical handoff mutation was performed."
    } else {
        "Preview only. Current System State is derived live at read time and is no longer stored in the handoff."
    };

    Ok(HandoffRefreshReport {
        command: "handoff-refresh",
        ok: true,
        path: repo_root.display().to_string(),
        profile: profile.to_string(),
        mode: if write { "write" } else { "preview" },
        source: "git_metadata",
        handoff: HandoffSurfaceView {
            path: native_state_path.display().to_string(),
            status: "loaded",
            section: "Current System State",
            section_present: false,
        },
        refresh: RefreshView {
            changed: false,
            current_lines: replacement_lines.clone(),
            replacement_lines,
            note,
        },
        write_result,
        warnings,
    })
}

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

    bail!(
        "profile `{}` does not exist at {}; bootstrap it with `ccd attach --path {}` before refreshing the handoff",
        layout.profile(),
        profile_root.display(),
        repo_root.display()
    )
}

fn ensure_repo_linked(repo_root: &Path) -> Result<String> {
    let Some(marker) = repo_marker::load(repo_root)? else {
        bail!(
            "repo is not linked: {} is missing; run `ccd attach --path {}` or `ccd link --path {}` first",
            repo_root.join(repo_marker::MARKER_FILE).display(),
            repo_root.display(),
            repo_root.display()
        )
    };

    Ok(marker.locality_id)
}