//! Unified `adom-video-post app` subcommand — opens ONE Hydrogen tab
//! named "Adom Video Post" that hosts all three post-production phases
//! (Review, Narrate, Publish) behind a phase selector at the top of
//! the UI, so the user sees a single tool instead of three.
//!
//! The backend composes `StoryboardRoutes` (review/dailies) and
//! `VoiceoverRoutes` (narration/ADR) under one HTTP server. The
//! storyboard routes live at their normal top-level paths
//! (`/manifest`, `/video/:id/raw`, etc.) because they're the primary
//! data layer; voiceover routes get prefixed with `/voiceover/` so
//! we can disambiguate (`/voiceover/start-recording`,
//! `/voiceover/takes`, `/voiceover/state`, etc.). The UI fetches
//! both surfaces as needed to render the active phase.

pub mod routes;

use crate::storyboard::manifest::Manifest;
use crate::voiceover::{server::VoiceoverRoutes, VoiceoverConfig};
use crate::webapp::{run_webapp, WebappConfig};
use std::path::PathBuf;
use std::sync::atomic::AtomicU64;
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, SystemTime};

/// Icon placeholder shown on the Hydrogen tab for ~100ms while the
/// page loads, then overridden by the served `/favicon.svg` per
/// the hydrogen-tab-icons skill.
const TAB_ICON_PLACEHOLDER: &str = "mdi:movie-edit-outline";

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

pub fn run(cfg: AppConfig) -> Result<(), String> {
    if !cfg.manifest_path.exists() {
        return Err(format!(
            "manifest not found: {}. Hint: create one with `adom-video-post manifest init`, then add clips with `adom-video-post manifest add`.",
            cfg.manifest_path.display()
        ));
    }

    let initial = Manifest::load(&cfg.manifest_path)?;
    let title = if initial.title.is_empty() {
        "Adom Video Post".to_string()
    } else {
        initial.title.clone()
    };

    // Shared storyboard state — manifest under RwLock so the watcher
    // thread can swap it on mtime change, plus a version counter the
    // UI polls to decide when to re-fetch.
    let manifest = Arc::new(RwLock::new(initial));
    let version = Arc::new(AtomicU64::new(1));
    let manifest_mtime = Arc::new(Mutex::new(
        std::fs::metadata(&cfg.manifest_path)
            .and_then(|m| m.modified())
            .unwrap_or(SystemTime::UNIX_EPOCH),
    ));

    // File watcher — same pattern as the standalone storyboard
    // subcommand.
    if cfg.watch {
        let manifest_for_thread = Arc::clone(&manifest);
        let version_for_thread = Arc::clone(&version);
        let mtime_for_thread = Arc::clone(&manifest_mtime);
        let path_for_thread = cfg.manifest_path.clone();
        std::thread::spawn(move || loop {
            std::thread::sleep(Duration::from_millis(500));
            let new_mtime = match std::fs::metadata(&path_for_thread)
                .and_then(|m| m.modified())
            {
                Ok(t) => t,
                Err(_) => continue,
            };
            let mut last = mtime_for_thread.lock().unwrap();
            if new_mtime == *last {
                continue;
            }
            match Manifest::load(&path_for_thread) {
                Ok(fresh) => {
                    *manifest_for_thread.write().unwrap() = fresh;
                    version_for_thread
                        .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
                    *last = new_mtime;
                }
                Err(e) => {
                    eprintln!("app: manifest reload failed: {}", e);
                }
            }
        });
    }

    // Derive the voiceover input: prefer manifest.final_fast_path
    // (the concat output), fall back to final_raw_path. If neither
    // is set yet, voiceover features are disabled until the user
    // runs `adom-video-post concat` to populate one.
    let voiceover_input = {
        let m = manifest.read().unwrap();
        m.final_fast_path.clone().or_else(|| m.final_raw_path.clone())
    };
    let voiceover_output_dir = voiceover_input
        .as_ref()
        .and_then(|p| p.parent().map(|p| p.to_path_buf()))
        .unwrap_or_else(|| std::env::temp_dir());
    let voiceover_output = voiceover_output_dir.join("adom-video-post-narrated.webm");

    // Build a voiceover routes struct pointing at whichever final
    // concat is available. The result_slot here is per-app-session,
    // separate from storyboard's (storyboard doesn't produce a
    // result). If the user runs a narration and finalizes, the
    // narrated path lands in this slot and is displayed in the UI.
    let voiceover_result_slot: Arc<Mutex<Option<Result<PathBuf, String>>>> =
        Arc::new(Mutex::new(None));
    let voiceover_cfg = VoiceoverConfig {
        input: voiceover_input.unwrap_or_else(|| PathBuf::from("/nonexistent")),
        output: voiceover_output,
        // These three fields are only consumed by the voiceover
        // subcommand's standalone `run()`; AppRoutes never looks at
        // them but VoiceoverConfig requires them.
        port: cfg.port,
        tab_name: cfg.tab_name.clone(),
        panel_id_override: cfg.panel_id_override.clone(),
        markers_file: None,
        no_open: cfg.no_open,
    };
    let voiceover_routes = Arc::new(VoiceoverRoutes::new(
        voiceover_cfg,
        voiceover_result_slot,
    ));

    // Storyboard routes for the review phase — uses the same
    // manifest + version the watcher updates.
    let storyboard_routes = Arc::new(crate::storyboard::routes::StoryboardRoutes {
        manifest: Arc::clone(&manifest),
        version: Arc::clone(&version),
        manifest_path: cfg.manifest_path.clone(),
    });

    let app_routes = Arc::new(routes::AppRoutes {
        storyboard: storyboard_routes,
        voiceover: voiceover_routes,
    });

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

    run_webapp(webapp_cfg, app_routes)
}