//! adom-usb: the USB device mesh dashboard.
//!
//! See every USB device across the Adom ecosystem — your laptop, its WSL2 /
//! Hydrogen Desktop, this cloud container, the Azure VM and its WSL2, and
//! workcells — in one table, and mount / unmount / reset / test / force-class /
//! sniff any of them. The dashboard is a Hydrogen webview backed by a tiny HTTP
//! server; every button is also a plain HTTP endpoint, so the AI drives it the
//! same way the human does.

mod app;
mod cli;
mod devices;
mod install;
mod plugins;

use clap::{CommandFactory, Parser, Subcommand};
use std::io::IsTerminal;
use std::process::ExitCode;
use std::sync::OnceLock;

pub const GREEN: &str = "\x1b[32m";
pub const RED: &str = "\x1b[31m";
pub const YELLOW: &str = "\x1b[33m";
pub const CYAN: &str = "\x1b[36m";
pub const BOLD: &str = "\x1b[1m";
pub const DIM: &str = "\x1b[2m";
pub const RESET: &str = "\x1b[0m";

static STDOUT_IS_TTY: OnceLock<bool> = OnceLock::new();
static STDERR_IS_TTY: OnceLock<bool> = OnceLock::new();
pub fn stdout_is_tty() -> bool { *STDOUT_IS_TTY.get_or_init(|| std::io::stdout().is_terminal()) }
pub fn stderr_is_tty() -> bool { *STDERR_IS_TTY.get_or_init(|| std::io::stderr().is_terminal()) }
pub fn strip_ansi(s: &str) -> String {
    let mut out = String::with_capacity(s.len());
    let mut chars = s.chars().peekable();
    while let Some(c) = chars.next() {
        if c == '\x1b' && chars.peek() == Some(&'[') {
            chars.next();
            for c2 in chars.by_ref() { if c2.is_ascii_alphabetic() { break; } }
            continue;
        }
        out.push(c);
    }
    out
}
pub fn ok(m: impl std::fmt::Display) {
    if stdout_is_tty() { println!("{GREEN}{BOLD}OK:{RESET} {m}"); } else { println!("OK: {}", strip_ansi(&m.to_string())); }
}
pub fn err(m: impl std::fmt::Display) {
    if stderr_is_tty() { eprintln!("{RED}{BOLD}ERROR:{RESET} {m}"); } else { eprintln!("ERROR: {}", strip_ansi(&m.to_string())); }
}
pub fn warn(m: impl std::fmt::Display) {
    if stderr_is_tty() { eprintln!("{YELLOW}{BOLD}WARN:{RESET} {m}"); } else { eprintln!("WARN: {}", strip_ansi(&m.to_string())); }
}
pub fn hint(m: impl std::fmt::Display) {
    if stderr_is_tty() { eprintln!("{DIM}Hint:{RESET} {m}"); } else { eprintln!("Hint: {}", strip_ansi(&m.to_string())); }
}
#[macro_export]
macro_rules! note {
    ($($arg:tt)*) => {{
        let __s = format!($($arg)*);
        if $crate::stdout_is_tty() { println!("{}", __s); } else { println!("{}", $crate::strip_ansi(&__s)); }
    }};
}

#[derive(Parser)]
#[command(name = "adom-usb", version, about = "USB device mesh dashboard — see and route every USB device across the Adom ecosystem")]
pub struct Cli {
    #[command(subcommand)]
    cmd: Cmd,
}

#[derive(Subcommand)]
enum Cmd {
    /// Open the Hydrogen webview dashboard (starts the HTTP server).
    App(AppArgs),
    /// Print the unified device inventory as JSON (no server needed).
    List {
        /// Seed the representative cross-ecosystem demo set on top of real lsusb.
        #[arg(long)]
        demo: bool,
    },
    /// Mount a device onto a target host (POSTs to a running `app` server).
    Mount(MountArgs),
    /// Unmount a device from wherever it is attached.
    Unmount(UnmountArgs),
    /// Flash a stub-firmware persona onto a device (e.g. RP2040 -> HID keyboard)
    /// via a matching firmware device-test pack.
    Flash(FlashArgs),
    /// Check whether a running dashboard server is reachable.
    Health {
        #[arg(long, default_value_t = 8896)]
        port: u16,
    },
    /// List installed sniffer/decoder plugins, or search the wiki for more.
    Plugins {
        #[command(subcommand)]
        sub: PluginsCmd,
    },
    /// Install SKILL.md + bash completions.
    Install,
    /// Print shell completion script.
    Completions { shell: clap_complete::Shell },
}

#[derive(clap::Args)]
pub struct AppArgs {
    #[arg(long, default_value_t = 8896)] pub port: u16,
    #[arg(long, default_value = "USB Devices")] pub tab_name: String,
    #[arg(long)] pub no_tab: bool,
    /// Seed a representative cross-ecosystem device set so the dashboard is
    /// demonstrable before the live agents are wired.
    #[arg(long, env = "ADOM_USB_DEMO")] pub demo: bool,
    #[arg(long, env = "ADOM_USB_DEV")] pub dev: bool,
}

#[derive(Subcommand)]
pub enum PluginsCmd {
    /// List plugins installed on this host.
    List,
    /// Search the Adom Wiki for publishable adom-usb plugins.
    Search,
}

#[derive(clap::Args)]
pub struct MountArgs {
    pub device_id: String,
    pub target_id: String,
    #[arg(long, default_value_t = 8896)] pub port: u16,
}

#[derive(clap::Args)]
pub struct UnmountArgs {
    pub device_id: String,
    #[arg(long, default_value_t = 8896)] pub port: u16,
}

#[derive(clap::Args)]
pub struct FlashArgs {
    pub device_id: String,
    /// Persona id from the device's firmware_personas (e.g. hid-keyboard, msc-disk).
    pub persona: String,
    #[arg(long, default_value_t = 8896)] pub port: u16,
}

fn main() -> ExitCode {
    let args = Cli::parse();
    let result = match args.cmd {
        Cmd::App(a) => app::run(a),
        Cmd::List { demo } => cli::run_list(demo),
        Cmd::Mount(a) => cli::run_mount(a),
        Cmd::Unmount(a) => cli::run_unmount(a),
        Cmd::Flash(a) => cli::run_flash(a),
        Cmd::Health { port } => cli::run_health(port),
        Cmd::Plugins { sub } => cli::run_plugins(sub),
        Cmd::Install => install::install(),
        Cmd::Completions { shell } => {
            let mut cmd = Cli::command();
            clap_complete::generate(shell, &mut cmd, "adom-usb", &mut std::io::stdout());
            Ok(())
        }
    };
    match result { Ok(()) => ExitCode::SUCCESS, Err(e) => { err(&e); ExitCode::FAILURE } }
}