//! adom-jlcpcb: JLCPCB component search.
//!
//! Thin Rust CLI over the existing Node.js JLCPCB backend. Five verbs
//! matching the legacy MCP tools:
//!   search — keyword/part-number search (FTS5)
//!   category-search — keyword search scoped to a category
//!   list — list components in a category, sorted by stock
//!   categories — list all categories + subcategories
//!   health — backend reachability + DB stats

mod app;
mod cli;
mod install;
mod jlcpcb;

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(msg: impl std::fmt::Display) {
    if stdout_is_tty() { println!("{GREEN}{BOLD}OK:{RESET} {msg}"); }
    else { println!("OK: {}", strip_ansi(&msg.to_string())); }
}
pub fn err(msg: impl std::fmt::Display) {
    if stderr_is_tty() { eprintln!("{RED}{BOLD}ERROR:{RESET} {msg}"); }
    else { eprintln!("ERROR: {}", strip_ansi(&msg.to_string())); }
}
pub fn hint(msg: impl std::fmt::Display) {
    if stderr_is_tty() { eprintln!("{DIM}Hint:{RESET} {msg}"); }
    else { eprintln!("Hint: {}", strip_ansi(&msg.to_string())); }
}
pub fn warn(msg: impl std::fmt::Display) {
    if stderr_is_tty() { eprintln!("{YELLOW}{BOLD}WARN:{RESET} {msg}"); }
    else { eprintln!("WARN: {}", strip_ansi(&msg.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-jlcpcb", version, about = "JLCPCB component search — CLI + Hydrogen webview app")]
pub struct Cli {
    #[command(subcommand)]
    cmd: Cmd,
}

#[derive(Subcommand)]
enum Cmd {
    /// Keyword / part-number search via FTS5. Backend is Node + SQLite.
    Search(SearchArgs),

    /// Keyword search scoped to a specific category.
    #[command(name = "category-search")]
    CategorySearch(CategorySearchArgs),

    /// List all components in a category, sorted by stock.
    List(ListArgs),

    /// List all categories and subcategories.
    Categories(CategoriesArgs),

    /// Backend reachability + DB stats.
    Health(HealthArgs),

    /// Open the JLCPCB search webview as a Hydrogen tab.
    App(AppArgs),

    /// Install SKILL.md + BUILD-SKILL.md + bash completions.
    Install,

    /// Print shell completion script.
    Completions { shell: clap_complete::Shell },
}

#[derive(clap::Args)]
pub struct SearchArgs {
    pub keyword: String,
    #[arg(long, default_value_t = 10)] pub limit: u32,
    #[arg(long)] pub in_stock_only: bool,
    #[arg(long, env = "JLCPCB_API")] pub api: Option<String>,
}
#[derive(clap::Args)]
pub struct CategorySearchArgs {
    pub keyword: String,
    #[arg(long)] pub category: String,
    #[arg(long, default_value_t = 10)] pub limit: u32,
    #[arg(long)] pub in_stock_only: bool,
    #[arg(long, env = "JLCPCB_API")] pub api: Option<String>,
}
#[derive(clap::Args)]
pub struct ListArgs {
    #[arg(long)] pub category: String,
    #[arg(long)] pub subcategory: Option<String>,
    #[arg(long, default_value_t = 10)] pub limit: u32,
    #[arg(long)] pub in_stock_only: bool,
    #[arg(long, env = "JLCPCB_API")] pub api: Option<String>,
}
#[derive(clap::Args)]
pub struct CategoriesArgs {
    #[arg(long, env = "JLCPCB_API")] pub api: Option<String>,
}
#[derive(clap::Args)]
pub struct HealthArgs {
    #[arg(long, env = "JLCPCB_API")] pub api: Option<String>,
}
#[derive(clap::Args)]
pub struct AppArgs {
    #[arg(long, default_value_t = 8892)] pub port: u16,
    #[arg(long, env = "JLCPCB_API")] pub api: Option<String>,
    #[arg(long, default_value = "JLCPCB Search")] pub tab_name: String,
    #[arg(long)] pub no_tab: bool,
    #[arg(long, env = "ADOM_JLCPCB_DEV")] pub dev: bool,
}

fn main() -> ExitCode {
    let args = Cli::parse();
    let result = match args.cmd {
        Cmd::Search(a) => cli::run_search(a),
        Cmd::CategorySearch(a) => cli::run_category_search(a),
        Cmd::List(a) => cli::run_list(a),
        Cmd::Categories(a) => cli::run_categories(a),
        Cmd::Health(a) => cli::run_health(a),
        Cmd::App(a) => app::run(a),
        Cmd::Install => install::install(),
        Cmd::Completions { shell } => {
            let mut cmd = Cli::command();
            clap_complete::generate(shell, &mut cmd, "adom-jlcpcb", &mut std::io::stdout());
            Ok(())
        }
    };
    match result {
        Ok(()) => ExitCode::SUCCESS,
        Err(e) => { err(&e); ExitCode::FAILURE }
    }
}