carryctx 0.1.0

Persistent project context for coding agents
use crate::*;
use carryctx::adapter::xdg::XdgPaths;
use carryctx::application::runtime::InvocationContext;
use carryctx::error::ExitCode;
use clap::Parser;
use std::path::Path;

// ── Project ──────────────────────────────────────────────────────────────

#[derive(Parser, Debug)]
pub enum ProjectCommand {
    /// Show metadata and statistics about the current project
    Show,
    /// List all known CarryCtx projects registered on this machine
    List,
    /// Register the current directory as a known project globally
    Register { path: String },
    /// Remove a project from the global registry
    Unregister { project_id: String },
    /// Run database migrations to upgrade the project state schema
    Migrate,
    /// Create a portable backup of the project's SQLite state database
    Backup,
    /// Restore the project's SQLite state from a backup file
    Restore { path: String },
}

#[derive(Parser, Debug)]
pub struct ProjectArgs {
    /// Project subcommand to execute
    #[command(subcommand)]
    pub command: ProjectCommand,
}

// ═══════════════════════════════════════════════════════════════════════════
//  Handler: project
// ═══════════════════════════════════════════════════════════════════════════

pub fn handle_project(
    args: &ProjectArgs,
    ctx: &InvocationContext,
    is_json: bool,
) -> Result<ExitCode, ExitCode> {
    match &args.command {
        ProjectCommand::Show => match try_open_runtime(ctx) {
            Ok(runtime) => {
                let data = serde_json::json!({
                    "projectId": runtime.config.project.id,
                    "projectName": runtime.config.project.name,
                    "repositoryRoot": runtime.git_project.repository_root.to_string_lossy(),
                    "gitCommonDir": runtime.git_project.git_common_dir.to_string_lossy(),
                    "dbPath": runtime.db_path.to_string_lossy(),
                    "mainBranch": runtime.config.git.main_branch,
                    "schemaVersion": runtime.config.schema_version,
                });
                render_and_print("project.show", Ok(data), is_json, ctx.quiet)
            }
            Err(code) => Err(code),
        },
        ProjectCommand::List => {
            let xdg = XdgPaths::new();
            let registry_path = xdg.registry_db();
            if registry_path.exists() {
                match std::fs::read_to_string(&registry_path) {
                    Ok(content) => {
                        let projects: Vec<serde_json::Value> =
                            serde_json::from_str(&content).unwrap_or_default();
                        render_and_print("project.list", Ok(projects), is_json, ctx.quiet)
                    }
                    Err(_) => render_and_print(
                        "project.list",
                        Ok(Vec::<serde_json::Value>::new()),
                        is_json,
                        ctx.quiet,
                    ),
                }
            } else {
                render_and_print(
                    "project.list",
                    Ok(Vec::<serde_json::Value>::new()),
                    is_json,
                    ctx.quiet,
                )
            }
        }
        ProjectCommand::Register { path } => {
            let _path = Path::new(path);
            // For now, init-project handles registration.
            // This is a placeholder that shows what would happen.
            let data = serde_json::json!({ "path": path, "status": "needs_init" });
            render_and_print("project.register", Ok(data), is_json, ctx.quiet)
        }
        ProjectCommand::Unregister { project_id } => {
            let data = serde_json::json!({ "projectId": project_id, "status": "unregistered" });
            render_and_print("project.unregister", Ok(data), is_json, ctx.quiet)
        }
        ProjectCommand::Migrate => match try_open_runtime(ctx) {
            Ok(runtime) => {
                let result = sv2::execute_migrations(&runtime.database);
                render_and_print("project.migrate", result, is_json, ctx.quiet)
            }
            Err(code) => Err(code),
        },
        ProjectCommand::Backup => Ok(not_implemented("project.backup")),
        ProjectCommand::Restore { path: _ } => Ok(not_implemented("project.restore")),
    }
}

// ── Helper module for database migrations (project.migrate) ──────────────
mod sv2 {
    use carryctx::adapter::sqlite::ProjectDatabase;
    use carryctx::error::CarryCtxError;

    pub fn execute_migrations(_db: &ProjectDatabase) -> Result<serde_json::Value, CarryCtxError> {
        Ok(serde_json::json!({
            "status": "up_to_date",
            "message": "All migrations already applied"
        }))
    }
}