//! `ckg remove <path|repo_id>` — drop a repo from the manifest and its DB.
use std::path::PathBuf;

use anyhow::{Context, Result};
use ckg_storage::{Manifest, manifest_with_lock, with_repo_lock};
use clap::Args as ClapArgs;

#[derive(ClapArgs, Debug)]
pub struct Args {
    pub target: String,
}

pub fn run(home: Option<PathBuf>, args: Args) -> Result<()> {
    let base = crate::ckg_home(home)?;
    let manifest_path = base.join("manifest.json");
    // Accept relative paths (`.`, `./foo`, `..`) by canonicalizing the
    // target. Manifest keys are absolute canonical paths, so a direct
    // `target == path` compare misses the common `ckg remove .` case.
    let target_canonical = std::path::Path::new(&args.target)
        .canonicalize()
        .ok()
        .map(|p| p.to_string_lossy().into_owned());
    // Hold the cross-process advisory lock for the entire read-modify-
    // write window so a concurrent `ckg index` (or another `ckg remove`)
    // doesn't drop our update. R2 in re-review.
    let result = manifest_with_lock(&manifest_path, || {
        let mut manifest = Manifest::read(&manifest_path)?;
        let key_to_remove = manifest
            .repos
            .iter()
            .find(|(path, e)| {
                path.as_str() == args.target
                    || e.repo_id == args.target
                    || target_canonical.as_deref() == Some(path.as_str())
            })
            .map(|(k, _)| k.clone());
        let Some(key) = key_to_remove else {
            return Ok(None);
        };
        let entry = manifest.repos.remove(&key).expect("just-found key");
        let db_dir = base.join("workspace_folders").join(&entry.repo_id);
        if db_dir.exists() {
            // #1 fix: take the per-repo flock before deleting so a
            // concurrent `ckg index <same-repo>` (which holds the
            // same flock for the duration of `index_one`) blocks
            // here until indexing finishes. Without this, the index
            // process keeps writing to a directory we're about to
            // delete — orphaned RocksDB inodes leak.
            with_repo_lock(&entry.repo_id, &base, || {
                std::fs::remove_dir_all(&db_dir).map_err(|e| {
                    ckg_core::Error::wrap(format!("removing {}", db_dir.display()), e)
                })
            })?;
        }
        manifest.write_atomic(&manifest_path)?;
        Ok(Some((entry.repo_id, key)))
    })
    .with_context(|| format!("manifest update at {}", manifest_path.display()))?;
    match result {
        Some((repo_id, key)) => println!("removed {repo_id} ({key})"),
        None => println!("not found: {}", args.target),
    }
    Ok(())
}
