//! Storyboard subcommand — first phase of post-production review.
//!
//! Reads a manifest file describing a multi-clip video review,
//! opens a Hydrogen webview tab hosting the UI, and blocks until
//! /shutdown. With `--watch` (default), the manifest file is
//! poll-watched for mtime changes so the UI live-updates as the
//! user's demo script appends new clips during a run.
//!
//! The manifest schema and manifest-CRUD subcommands live in the
//! sibling modules; this file is the `run()` glue + the `impl Routes`
//! wiring.

pub mod manifest;
pub mod manifest_cli;
pub mod routes;

use crate::webapp::{run_webapp, WebappConfig};
use manifest::Manifest;

/// Pre-load placeholder icon — see the comment on
/// `voiceover::TAB_ICON_PLACEHOLDER`. Once the storyboard UI
/// finishes loading, Hydrogen overrides this with the favicon
/// served from `src/storyboard/icon.svg`.
const TAB_ICON_PLACEHOLDER: &str = "mdi:filmstrip-box-multiple";
use std::path::PathBuf;
use std::sync::atomic::AtomicU64;
use std::sync::{Arc, RwLock};
use std::time::{Duration, SystemTime};

pub struct StoryboardConfig {
    pub manifest_path: PathBuf,
    pub port: u16,
    pub tab_name: String,
    pub panel_id_override: Option<String>,
    /// Poll the manifest file for mtime changes and live-update the
    /// UI when it changes. Default true; disable with `--no-watch`.
    pub watch: bool,
    /// Skip the Hydrogen webview tab open; HTTP server only. Use
    /// when reviewing in pup or driving via browser_eval.
    pub no_open: bool,
}

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

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

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

    // Spawn a file-watcher thread if --watch is on. This is a
    // 500ms-poll-mtime loop rather than inotify because (a) we
    // only need to react to `manifest add` which writes atomically
    // via rename, (b) inotify on a renamed target is fiddly, and
    // (c) 500ms is well within the "feels live" window.
    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;
            }
            // File changed — reload and bump the version.
            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!("storyboard: manifest reload failed: {}", e);
                }
            }
        });
    }

    let routes = Arc::new(routes::StoryboardRoutes {
        manifest,
        version,
        manifest_path: cfg.manifest_path.clone(),
    });

    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: "storyboard".to_string(),
        title,
        no_open: cfg.no_open,
    };

    run_webapp(webapp_cfg, routes)
}