//! CLI handler for `cqs convert`.

use std::path::PathBuf;

use anyhow::Result;

pub fn cmd_convert(
    path: &str,
    output: Option<&str>,
    overwrite: bool,
    dry_run: bool,
    clean_tags: Option<&str>,
    json: bool,
) -> Result<()> {
    let _span = tracing::info_span!("cmd_convert").entered();
    // P2.12: track wall-clock for the JSON envelope; doubles as a sanity
    // metric on long PDF/CHM batches.
    let start = std::time::Instant::now();

    let source = PathBuf::from(path);
    if !source.exists() {
        anyhow::bail!("Path not found: {}", path);
    }

    // Default output dir: same directory as input (or input dir itself)
    // SEC-4: Canonicalize to normalize symlinks and warn if outside source tree
    let output_dir = match output {
        Some(dir) => {
            let raw = PathBuf::from(dir);
            let canonical = dunce::canonicalize(&raw).unwrap_or(raw);
            if let Ok(source_parent) = dunce::canonicalize(source.parent().unwrap_or(&source)) {
                if !canonical.starts_with(&source_parent) {
                    tracing::warn!(
                        output = %canonical.display(),
                        source = %source_parent.display(),
                        "Output directory is outside source tree"
                    );
                }
            }
            canonical
        }
        None => {
            if source.is_dir() {
                source.clone()
            } else {
                source
                    .parent()
                    .map(|p| p.to_path_buf())
                    .unwrap_or_else(|| PathBuf::from("."))
            }
        }
    };

    let tags: Vec<String> = clean_tags
        .map(|s| s.split(',').map(|t| t.trim().to_string()).collect())
        .unwrap_or_default();

    let opts = cqs::convert::ConvertOptions {
        output_dir,
        overwrite,
        dry_run,
        clean_tags: tags,
    };

    let results = cqs::convert::convert_path(&source, &opts)?;

    if json {
        // P2.12: structured summary for JSON-driven agents. We don't have a
        // distinct `skipped` list out of `convert_path` today (the converter
        // surfaces skips as warnings), so the field stays as an empty array
        // for forward compatibility — schema reservation, not a lie.
        let converted: Vec<serde_json::Value> = results
            .iter()
            .map(|r| {
                serde_json::json!({
                    "source": r.source.display().to_string(),
                    "output": r.output.display().to_string(),
                    "format": r.format.to_string(),
                    "title": r.title,
                    "sections": r.sections,
                })
            })
            .collect();
        let obj = serde_json::json!({
            "converted": converted,
            "skipped": Vec::<serde_json::Value>::new(),
            "took_ms": start.elapsed().as_millis() as u64,
            "dry_run": dry_run,
        });
        crate::cli::json_envelope::emit_json(&obj)?;
        return Ok(());
    }

    if results.is_empty() {
        println!("No supported documents found.");
        return Ok(());
    }

    if dry_run {
        println!(
            "Dry run — {} document(s) would be converted:\n",
            results.len()
        );
    } else {
        println!("Converted {} document(s):\n", results.len());
    }

    for r in &results {
        println!(
            "  {} → {}",
            r.source.display(),
            r.output.file_name().unwrap_or_default().to_string_lossy()
        );
        println!(
            "    Format: {} | Title: {} | Sections: {}",
            r.format, r.title, r.sections
        );
    }

    Ok(())
}
