//! Voiceover subcommand — phase 2 of adom-video-post.
//!
//! Plays the input (sped-up) video back in a Hydrogen webview tab,
//! captures mic audio via Hydrogen's first-class audio API while
//! the user narrates, then muxes the audio with the video to produce
//! a narrated output. The HTTP plumbing is now in `crate::webapp`;
//! this file is the thin wrapper that owns the voiceover-specific
//! config + result slot and hands everything over to `run_webapp`.

pub mod server;

use crate::webapp::{run_webapp, WebappConfig};
use std::path::PathBuf;
use std::process::Command;
use std::sync::{Arc, Mutex};

/// Pre-load placeholder icon. Shown on the Hydrogen tab for ~100ms
/// while the UI HTML loads, then swapped for `src/voiceover/icon.svg`
/// via the `<link rel="icon">` the page ships (see the
/// hydrogen-tab-icons skill). MDI gets the right theme color
/// automatically so the placeholder and the served favicon look
/// nearly identical and the swap is invisible.
const TAB_ICON_PLACEHOLDER: &str = "mdi:microphone-variant";

pub struct VoiceoverConfig {
    pub input: PathBuf,
    pub output: PathBuf,
    pub port: u16,
    pub tab_name: String,
    pub panel_id_override: Option<String>,
    pub markers_file: Option<PathBuf>,
    /// Skip the Hydrogen webview tab open; HTTP server only.
    pub no_open: bool,
}

/// Run the voiceover flow end-to-end. Blocks until the user finalizes
/// a take and hits /shutdown (or an error occurs), then returns the
/// path to the final muxed video.
pub fn run(cfg: VoiceoverConfig) -> Result<PathBuf, String> {
    if !cfg.input.exists() {
        return Err(format!(
            "input video not found: {}. Hint: run `adom-video-post process` first to produce a sped-up video.",
            cfg.input.display()
        ));
    }

    let title = cfg
        .input
        .file_name()
        .map(|n| n.to_string_lossy().to_string())
        .unwrap_or_else(|| "voiceover".to_string());

    let webapp_cfg = WebappConfig {
        port: cfg.port,
        tab_name: cfg.tab_name.clone(),
        panel_id_override: cfg.panel_id_override.clone(),
        display_icon: TAB_ICON_PLACEHOLDER.to_string(),
        subcommand: "voiceover".to_string(),
        title,
        no_open: cfg.no_open,
    };

    let result_slot: Arc<Mutex<Option<Result<PathBuf, String>>>> = Arc::new(Mutex::new(None));
    let routes = Arc::new(server::VoiceoverRoutes::new(cfg, result_slot.clone()));

    run_webapp(webapp_cfg, routes)?;

    let final_result = result_slot.lock().unwrap().take();
    match final_result {
        Some(Ok(path)) => Ok(path),
        Some(Err(e)) => Err(e),
        None => Err("voiceover session ended without producing a final video".to_string()),
    }
}

/// Mux the recorded voiceover audio with the input video.
/// Output is video-only with the new audio track.
pub fn mux(
    input_video: &std::path::Path,
    voiceover_audio: &std::path::Path,
    output: &std::path::Path,
) -> Result<(), String> {
    let result = Command::new("ffmpeg")
        .args(["-y", "-i"])
        .arg(input_video)
        .args(["-i"])
        .arg(voiceover_audio)
        .args(["-c:v", "copy", "-c:a", "libopus", "-shortest"])
        .arg(output)
        .output()
        .map_err(|e| format!("ffmpeg spawn failed: {}", e))?;
    if !result.status.success() {
        let stderr = String::from_utf8_lossy(&result.stderr);
        let tail: Vec<&str> = stderr.lines().rev().take(20).collect();
        let tail: Vec<&str> = tail.into_iter().rev().collect();
        return Err(format!("ffmpeg mux failed:\n{}", tail.join("\n")));
    }
    Ok(())
}