use crate::library;
use crate::queue;
use crate::ui::{err, hint, ok, warn, CYAN, DIM, RESET};
use crate::{install, manifest, note, render_3d};
use clap::{Args, CommandFactory, Parser, Subcommand, ValueEnum};
use std::process::Command as ProcCommand;

#[derive(Parser, Debug)]
#[command(
    name = "adom-chip-thumbnailer",
    version,
    about = "Render symbol/footprint/3D thumbnails for chip-fetcher library entries.",
    long_about = "\
chip-thumbnailer renders three thumbnails per chip in the chip-fetcher\n\
library and drops them next to the source files:\n  \n\
  <MPN>-symbol.svg     Adom-themed KiCad symbol (kicad-cli + svg-theme)\n\
  <MPN>-footprint.svg  FpView-styled footprint (kicad-cli + dark grid)\n\
  <MPN>-3d-iso.png     OCCT-direct render via service-step2glb /thumbnail\n\
"
)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Command,
}

#[derive(Subcommand, Debug)]
pub enum Command {
    /// Render all three thumbnails for one chip (writes alongside the source files).
    Once(OnceArgs),
    /// Render every chip in the library that has a `.thumb-pending` marker.
    Scan,
    /// Show which thumbnails exist + which sources are present for one chip.
    Status(StatusArgs),
    /// Re-render only one kind for one chip (sym, fp, 3d, or all).
    Rerender(RerenderArgs),
    /// Probe local kicad-cli + step2glb + service-step2glb reachability.
    Health,
    /// Print resolved library root + step2glb binary location.
    Config,
    /// Manually mark a chip's thumbnails as out-of-date (touches `.thumb-pending`).
    TouchPending(TouchPendingArgs),
    /// Install SKILL.md + bash completions (fetches fresh SKILL.md from the wiki).
    Install,
    /// Print shell completions to stdout: `chip-thumbnailer completions bash`
    Completions(CompletionsArgs),
    /// Run the web app: a live dashboard that renders every icon for a STEP
    /// file in real-time, with per-icon size / format / transparency.
    Serve(ServeArgs),
    /// Open the web app in a Hydrogen webview tab (starts `serve` if needed).
    App(ServeArgs),
}

#[derive(Args, Debug)]
pub struct ServeArgs {
    /// Port to bind the web app on.
    #[arg(long, env = "CHIP_THUMBNAILER_PORT", default_value = "8794")]
    pub port: u16,
}

#[derive(Args, Debug)]
pub struct OnceArgs {
    pub mpn: String,
}

#[derive(Args, Debug)]
pub struct StatusArgs {
    pub mpn: String,
}

#[derive(Args, Debug)]
pub struct RerenderArgs {
    pub mpn: String,
    /// Which thumbnail to re-render. Default = all three.
    #[arg(long, default_value = "all")]
    pub kind: String,
}

#[derive(Args, Debug)]
pub struct TouchPendingArgs {
    pub mpn: String,
}

#[derive(Args, Debug)]
pub struct CompletionsArgs {
    #[arg(value_enum)]
    pub shell: ShellArg,
}

#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum ShellArg {
    Bash,
    Zsh,
    Fish,
}

impl From<ShellArg> for clap_complete::Shell {
    fn from(s: ShellArg) -> Self {
        match s {
            ShellArg::Bash => clap_complete::Shell::Bash,
            ShellArg::Zsh => clap_complete::Shell::Zsh,
            ShellArg::Fish => clap_complete::Shell::Fish,
        }
    }
}

// ────────────────────────────────────────────────────────────────────────
// command implementations
// ────────────────────────────────────────────────────────────────────────

pub fn once_cmd(args: OnceArgs) {
    match queue::render_one(&args.mpn) {
        Ok(report) => {
            if report.all_ok() {
                ok(format!(
                    "{CYAN}{}{RESET} — 3D orientations + transparent icons rendered, .thumb-pending cleared",
                    args.mpn
                ));
            } else {
                warn(format!(
                    "{CYAN}{}{RESET} — 3D render FAILED. See .thumb-errors.json",
                    args.mpn,
                ));
            }

            // Build the AI-hint payload by reading back the freshly-written
            // manifest so the JSON output of `once` is self-describing —
            // any AI agent driving chip-thumbnailer learns from one
            // command both what was rendered AND how to use it.
            let chip = library::ChipPaths::for_mpn(&args.mpn).ok();
            let m = chip.as_ref().map(manifest::build);

            let mut next_steps: Vec<String> = Vec::new();
            if report.all_ok() {
                if let Some(ref c) = chip {
                    next_steps.push(format!(
                        "Manifest written: cat library/{}/{}-thumbs.json",
                        c.mpn, c.mpn
                    ));
                    next_steps.push(format!(
                        "Open the 3D in interactive mode: adom-step view library/{}/{}.step",
                        c.mpn, c.mpn
                    ));
                    next_steps.push(format!(
                        "Open the chip icons as VS Code tabs: adom-vscode open library/{}/{}-3d-iso.png && adom-vscode open library/{}/{}-3d-iso-icon.png",
                        c.mpn, c.mpn, c.mpn, c.mpn
                    ));
                }
            }
            if !report.all_ok() {
                next_steps.push(format!(
                    "Inspect failures: cat library/{}/.thumb-errors.json",
                    args.mpn
                ));
                next_steps.push(format!(
                    "Retry the render: chip-thumbnailer rerender {} --kind 3d",
                    args.mpn
                ));
            }

            println!(
                "{}",
                serde_json::to_string(&serde_json::json!({
                    "ok": report.all_ok(),
                    "mpn": report.mpn,
                    "3d": report.three_d_ok,
                    "errors": report.errors,
                    "manifest": m,
                    "_hints": {
                        "purpose": "These thumbnails are sized for card / index / tooltip surfaces. For full-fidelity inspection, see manifest.thumbnails.<kind>.full_fidelity.tool / .command.",
                        "palette": "All thumbnails use the canonical Adom palette in manifest.palette — match your surrounding UI to canvas_bg #0d1117 and accent_teal #00b8b0 to avoid clashing.",
                        "sizes": "3D PNGs come in sm (160×120), md (320×240, no suffix — back-compat), lg (800×600). SVGs scale infinitely; one file each.",
                        "atmosphere": "Themed SVGs bake in the dark canvas + 2.54 mm PCB grid + faint teal radial glow. If your container already provides those (e.g. SymView), don't double them.",
                        "next_steps": next_steps,
                    }
                })).unwrap()
            );
            if !report.all_ok() {
                std::process::exit(3);
            }
        }
        Err(e) => {
            err(format!("once failed for {}: {e}", args.mpn));
            hint("Run `chip-thumbnailer status <MPN>` to inspect the chip dir.");
            std::process::exit(1);
        }
    }
}

pub fn scan_cmd() {
    match queue::render_pending() {
        Ok(0) => {
            ok("no chips with .thumb-pending markers — nothing to do");
        }
        Ok(n) => {
            ok(format!("processed {n} pending chip(s)"));
        }
        Err(e) => {
            err(format!("scan failed: {e}"));
            std::process::exit(1);
        }
    }
}

pub fn status_cmd(args: StatusArgs) {
    let chip = match library::ChipPaths::for_mpn(&args.mpn) {
        Ok(c) => c,
        Err(e) => {
            err(format!("{e}"));
            std::process::exit(1);
        }
    };
    let yes = "✓";
    let no = "·";
    note!("{CYAN}{}{RESET}", chip.mpn);
    note!("  {DIM}dir{RESET}        {}", chip.dir.display());
    note!(
        "  {DIM}sources{RESET}    sym={}  mod={}  step={}",
        if chip.kicad_sym.is_some() { yes } else { no },
        if chip.kicad_mod.is_some() { yes } else { no },
        if chip.step.is_some() { yes } else { no },
    );
    note!(
        "  {DIM}thumbs{RESET}     sym={}  fp={}  3d={}",
        if chip.symbol_thumb().is_file() { yes } else { no },
        if chip.footprint_thumb().is_file() { yes } else { no },
        if chip.three_d_thumb().is_file() { yes } else { no },
    );
    note!(
        "  {DIM}pending{RESET}    {}",
        if chip.thumb_pending { "yes (.thumb-pending)" } else { "no" }
    );
}

pub fn rerender_cmd(args: RerenderArgs) {
    let chip = match library::ChipPaths::for_mpn(&args.mpn) {
        Ok(c) => c,
        Err(e) => {
            err(format!("{e}"));
            std::process::exit(1);
        }
    };
    // chip-thumbnailer renders chip icons only; `3d` (the 3D orientations +
    // transparent icons) is the sole kind. `all` is kept as an alias.
    let mut had_failure = false;
    if !["all", "3d"].contains(&args.kind.as_str()) {
        err(format!("unknown --kind `{}`; chip-thumbnailer renders chip icons, so the only kind is `3d` (or `all`)", args.kind));
        std::process::exit(1);
    }
    match render_3d::render(&chip) {
        Ok(_) => ok(format!("3d   → {}", chip.three_d_thumb().display())),
        Err(e) => { err(format!("3d failed: {e}")); had_failure = true; }
    }
    // Refresh the manifest so file lists, palette, version stay in sync
    // with what's on disk — same contract as `once`.
    match manifest::write(&chip) {
        Ok(path) => ok(format!("manifest → {}", path.display())),
        Err(e) => warn(format!("manifest write failed: {e}")),
    }
    if had_failure {
        std::process::exit(3);
    }
}

pub fn health_cmd() {
    let kicad_ok = ProcCommand::new("kicad-cli").arg("version").output().is_ok();
    let step2glb_ok = ProcCommand::new("adom-step2glb").arg("--version").output().is_ok();
    let svc_ok = ureq::get("https://step2glb-gmdoncpxdwx0.adom.cloud/health")
        .timeout(std::time::Duration::from_secs(5))
        .call()
        .is_ok();
    let lib_ok = library::root().is_dir();

    note!("{DIM}kicad-cli{RESET}         {}", if kicad_ok { "ok" } else { "MISSING" });
    note!("{DIM}step2glb{RESET}          {}", if step2glb_ok { "ok" } else { "MISSING" });
    note!("{DIM}service-step2glb{RESET}  {}", if svc_ok { "reachable" } else { "UNREACHABLE" });
    note!("{DIM}library{RESET}           {} ({})",
        library::root().display(),
        if lib_ok { "exists" } else { "MISSING" }
    );
    if !(kicad_ok && step2glb_ok && svc_ok && lib_ok) {
        std::process::exit(2);
    }
}

pub fn config_cmd() {
    note!("{DIM}library root:{RESET}     {}", library::root().display());
    note!("{DIM}kicad-cli:{RESET}        {}", which_or("kicad-cli"));
    note!("{DIM}step2glb:{RESET}         {}", which_or("step2glb"));
    note!("{DIM}service-step2glb:{RESET} https://step2glb-gmdoncpxdwx0.adom.cloud");
}

pub fn touch_pending_cmd(args: TouchPendingArgs) {
    match library::ChipPaths::for_mpn(&args.mpn) {
        Ok(c) => match library::touch_pending(&args.mpn) {
            Ok(_) => ok(format!(
                "marked {} as pending — next `scan` will re-render",
                c.mpn
            )),
            Err(e) => {
                err(format!("{e}"));
                std::process::exit(1);
            }
        },
        Err(e) => {
            err(format!("{e}"));
            std::process::exit(1);
        }
    }
}

pub fn install_cmd() {
    install::run();
}

pub fn completions_cmd(args: CompletionsArgs) {
    let shell: clap_complete::Shell = args.shell.into();
    let mut cmd = Cli::command();
    clap_complete::generate(shell, &mut cmd, "chip-thumbnailer", &mut std::io::stdout());
}

pub fn serve_cmd(args: ServeArgs) {
    if let Err(e) = crate::serve::run(args.port) {
        err(format!("serve failed: {e}"));
        std::process::exit(1);
    }
}

pub fn app_cmd(args: ServeArgs) {
    match crate::serve::open_in_hydrogen(args.port) {
        Ok(_) => {}
        Err(e) => {
            err(format!("could not open app: {e}"));
            hint("Is the server running? Try `chip-thumbnailer serve` in another shell.");
            std::process::exit(1);
        }
    }
}

fn which_or(name: &str) -> String {
    ProcCommand::new("which")
        .arg(name)
        .output()
        .ok()
        .filter(|o| o.status.success())
        .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
        .filter(|s| !s.is_empty())
        .unwrap_or_else(|| format!("(not on PATH)"))
}