//! `adom-video-post manifest {init,add,inspect,remove}` handlers.
//!
//! The demo script calls `add` once per completed step, which is
//! the whole contract between the demo-side bash and adom-video-post's
//! post-processing side. Everything else (`storyboard`, `process`,
//! `voiceover`, `publish`, `concat`) reads the resulting file.

use super::manifest::{parse_speedup_arg, Clip, Manifest, SpeedupEvent};
use std::path::{Path, PathBuf};

/// `manifest init <file> [--title "..."]`
pub fn init(file: &Path, title: &str) -> Result<(), String> {
    if file.exists() {
        return Err(format!(
            "manifest already exists at {}. Hint: delete it first, or use `manifest add` to append.",
            file.display()
        ));
    }
    let m = Manifest::init(title);
    m.save(file)?;
    Ok(())
}

/// `manifest add <file> --id step-03 --title "Open BQ25792" ...`
/// All clip fields are optional except `id` and `title`.
#[allow(clippy::too_many_arguments)]
pub fn add(
    file: &Path,
    id: &str,
    title: &str,
    description: Option<String>,
    raw_path: Option<PathBuf>,
    fast_path: Option<PathBuf>,
    image_path: Option<PathBuf>,
    console_png_path: Option<PathBuf>,
    speedups: Vec<String>,
    actions: Vec<String>,
    warnings: Vec<String>,
    narration: Option<String>,
) -> Result<(), String> {
    let mut manifest = if file.exists() {
        Manifest::load(file)?
    } else {
        // Auto-init so the user doesn't have to call `init` first.
        // This matches how demo scripts naturally work: first `add`
        // implicitly creates a fresh manifest.
        Manifest::init("Untitled")
    };

    // Upsert semantics: an existing clip with the same id is replaced
    // in-place, preserving its position in the ordered list. Demo
    // scripts often call `manifest add` twice per step (once early
    // with bare info, once at the end with description/image/speedup
    // populated); the second call updates the first. To force-fail
    // on duplicates, the caller should use `manifest remove` first.
    let existing_index = manifest.clips.iter().position(|c| c.id == id);

    let speedup_events: Result<Vec<SpeedupEvent>, String> =
        speedups.iter().map(|s| parse_speedup_arg(s)).collect();

    // Validate any paths the caller supplied exist — but only warn,
    // don't fail. Partial manifests are fine; the UI surfaces
    // missing files as warnings on the affected card.
    let mut auto_warnings: Vec<String> = Vec::new();
    for (name, p) in [
        ("raw", &raw_path),
        ("fast", &fast_path),
        ("image", &image_path),
        ("console-png", &console_png_path),
    ] {
        if let Some(path) = p {
            if !path.exists() {
                auto_warnings.push(format!("{} path not found on disk: {}", name, path.display()));
            }
        }
    }

    let clip = Clip {
        id: id.to_string(),
        title: title.to_string(),
        description,
        raw_path,
        fast_path,
        poster_path: None,
        image_path,
        console_png_path,
        speedup_events: speedup_events?,
        actions,
        warnings: warnings.into_iter().chain(auto_warnings).collect(),
        narration,
    };

    if let Some(idx) = existing_index {
        manifest.clips[idx] = clip;
    } else {
        manifest.clips.push(clip);
    }
    manifest.save(file)?;
    Ok(())
}

/// `manifest inspect <file>` — print a human-readable summary.
pub fn inspect(file: &Path) -> Result<(), String> {
    let manifest = Manifest::load(file)?;
    println!("title: {}", manifest.title);
    println!("clips: {}", manifest.clips.len());
    for (i, clip) in manifest.clips.iter().enumerate() {
        let n = i + 1;
        println!("  [{}] {} — {}", n, clip.id, clip.title);
        if let Some(p) = &clip.raw_path {
            let present = if p.exists() { "✓" } else { "✗ missing" };
            println!("        raw:  {} ({})", p.display(), present);
        }
        if let Some(p) = &clip.fast_path {
            let present = if p.exists() { "✓" } else { "✗ missing" };
            println!("        fast: {} ({})", p.display(), present);
        }
        if !clip.speedup_events.is_empty() {
            let labels: Vec<String> = clip
                .speedup_events
                .iter()
                .map(|e| format!("{}x:{}", e.speed, e.label))
                .collect();
            println!("        speedup: {}", labels.join(", "));
        }
        if !clip.warnings.is_empty() {
            for w in &clip.warnings {
                println!("        ⚠ {}", w);
            }
        }
    }
    if let Some(p) = &manifest.final_raw_path {
        println!("final raw:  {}", p.display());
    }
    if let Some(p) = &manifest.final_fast_path {
        println!("final fast: {}", p.display());
    }
    if let Some(pub_target) = &manifest.publish {
        println!("publish → {}", pub_target.page);
    }
    Ok(())
}

/// `manifest remove <file> --id step-03`
pub fn remove(file: &Path, id: &str) -> Result<(), String> {
    let mut manifest = Manifest::load(file)?;
    let before = manifest.clips.len();
    manifest.clips.retain(|c| c.id != id);
    if manifest.clips.len() == before {
        return Err(format!(
            "no clip with id {:?} in manifest {}",
            id,
            file.display()
        ));
    }
    manifest.save(file)?;
    Ok(())
}