//! ffmpeg / ffprobe discovery, probing, and small shared helpers.

use std::path::{Path, PathBuf};
use std::process::Command;

/// On Windows, prevent child processes from flashing a console window.
#[cfg(windows)]
pub const CREATE_NO_WINDOW: u32 = 0x0800_0000;

/// Apply the no-window flag to a Command (no-op on non-Windows).
pub fn hide_window(cmd: &mut Command) {
    #[cfg(windows)]
    {
        use std::os::windows::process::CommandExt;
        cmd.creation_flags(CREATE_NO_WINDOW);
    }
    #[cfg(not(windows))]
    {
        let _ = cmd;
    }
}

/// Forcibly kill a process *and all of its descendants*.
///
/// `Child::kill()` only signals the direct child. ffmpeg is a single process,
/// so that is usually enough, but `taskkill /T` walks the whole tree to be
/// safe if ffmpeg ever spawns helpers of its own.
pub fn kill_process_tree(pid: u32) {
    #[cfg(windows)]
    {
        let mut cmd = Command::new("taskkill");
        cmd.args(["/F", "/T", "/PID", &pid.to_string()]);
        hide_window(&mut cmd);
        let _ = cmd.output();
    }
    #[cfg(not(windows))]
    {
        // Best-effort on Unix: kill the process group.
        let _ = Command::new("pkill").args(["-TERM", "-P", &pid.to_string()]).output();
    }
}

/// Directory the executable lives in (resources dir when bundled).
fn app_dir() -> Option<PathBuf> {
    std::env::current_exe().ok()?.parent().map(|p| p.to_path_buf())
}

/// Locate ffmpeg / ffprobe: next to the exe, then on PATH.
pub fn find_tool(name: &str) -> Option<String> {
    let exe = format!("{name}.exe");
    if let Some(base) = app_dir() {
        for sub in ["", "bin", "resources", "_internal", ".."] {
            let cand = base.join(sub).join(&exe);
            if cand.exists() {
                return Some(cand.to_string_lossy().into_owned());
            }
        }
    }
    // Fall back to PATH discovery via `where` semantics.
    if which(&exe).is_some() {
        return Some(exe);
    }
    if which(name).is_some() {
        return Some(name.to_string());
    }
    None
}

/// Minimal PATH lookup so we don't pull in an extra crate.
fn which(name: &str) -> Option<PathBuf> {
    let path = std::env::var_os("PATH")?;
    for dir in std::env::split_paths(&path) {
        let full = dir.join(name);
        if full.is_file() {
            return Some(full);
        }
    }
    None
}

/// Probe media duration (seconds) via ffprobe. Returns 0.0 on any failure.
pub fn probe_duration(ffprobe: &str, path: &str) -> f64 {
    let mut cmd = Command::new(ffprobe);
    cmd.args([
        "-v", "error",
        "-show_entries", "format=duration",
        "-of", "default=noprint_wrappers=1:nokey=1",
        path,
    ]);
    hide_window(&mut cmd);
    match cmd.output() {
        Ok(out) if out.status.success() => String::from_utf8_lossy(&out.stdout)
            .trim()
            .parse::<f64>()
            .unwrap_or(0.0)
            .max(0.0),
        _ => 0.0,
    }
}

/// Format seconds as H:MM:SS or M:SS.
pub fn fmt_dur(seconds: f64) -> String {
    if seconds < 0.0 {
        return "…".into();
    }
    let s = seconds.round() as i64;
    let (h, rem) = (s / 3600, s % 3600);
    let (m, sec) = (rem / 60, rem % 60);
    if h > 0 {
        format!("{h}:{m:02}:{sec:02}")
    } else {
        format!("{m}:{sec:02}")
    }
}

/// True if a path has a recognised video extension.
pub fn is_video(path: &str) -> bool {
    const EXTS: [&str; 11] = [
        "mp4", "mov", "mkv", "avi", "flv", "ts", "wmv", "m4v", "webm", "mts", "m2ts",
    ];
    Path::new(path)
        .extension()
        .and_then(|e| e.to_str())
        .map(|e| EXTS.contains(&e.to_lowercase().as_str()))
        .unwrap_or(false)
}
