//! Component enrichment: MPN → { price, stock, tier, popularity, wiki_url }.
//!
//! Sources (sibling Adom CLIs, shelled with a hard timeout so a slow/absent CLI
//! never hangs the UI):
//!   • popularity + wiki page — `adom-wiki discover search --query <mpn> --json`
//!     (vouch_count / star_count → popularity; owner/slug → wiki_url)
//!   • price + stock — `adom-parts-search search <mpn>` (max stock across
//!     vendors; first real unit price; JLCPCB tier from flags)
//!
//! Because the vendor lookup is slow (~30 s, live APIs), the server PRE-WARMS the
//! cache for every board MPN in a background thread at load time; hover then hits
//! the cache. `lookup()` is the (slow) cache-fill; `/enrich` reads the result.

use serde_json::{json, Value};
use std::process::{Command, Stdio};
use std::sync::mpsc;
use std::time::Duration;

/// Run a command, capping wall-clock at `secs`. Returns stdout on success.
fn run_capped(bin: &str, args: &[&str], secs: u64) -> Option<String> {
    let bin = bin.to_string();
    let args: Vec<String> = args.iter().map(|s| s.to_string()).collect();
    let (tx, rx) = mpsc::channel();
    std::thread::spawn(move || {
        let out = Command::new(&bin).args(&args).stdin(Stdio::null()).output();
        let _ = tx.send(out.ok().filter(|o| o.status.success()).map(|o| String::from_utf8_lossy(&o.stdout).into_owned()));
    });
    rx.recv_timeout(Duration::from_secs(secs)).ok().flatten()
}

/// Strip any leading non-JSON preamble (parts-search prints an "OK: …" summary
/// line before its JSON) and parse.
fn parse_json_tail(s: &str) -> Option<Value> {
    let start = s.find(['{', '['])?;
    serde_json::from_str(&s[start..]).ok()
}

const JLCPCB_DEFAULT: &str = "https://jlcpcb-wela51osctvp.adom.cloud";

/// Enrich a schematic component: JLCPCB stock/price/tier by LCSC part number
/// (the molecule parts carry `LCSC PN` C-numbers), plus a wiki page ONLY on a
/// close name match (so passives don't match random pages). `value` is the
/// symbol Value (e.g. "BME690"); `lcsc` is the LCSC C-number.
pub fn lookup(value: &str, lcsc: &str) -> String {
    let mut out = json!({});
    let o = out.as_object_mut().unwrap();
    let mut mpn = String::new(); // real MPN, learned from JLCPCB

    // ── JLCPCB by LCSC PN — stock / price / tier / popularity / mfr ──
    let is_lcsc = lcsc.starts_with('C') && lcsc.len() > 1 && lcsc[1..].chars().all(|c| c.is_ascii_digit());
    if is_lcsc {
        let api = std::env::var("JLCPCB_API").unwrap_or_else(|_| JLCPCB_DEFAULT.to_string());
        if let Some(txt) = run_capped("adom-jlcpcb", &["search", lcsc, "--limit", "1", "--api", &api], 20) {
            if let Some(v) = parse_json_tail(&txt) {
                if let Some(c) = v.get("components").and_then(|c| c.as_array()).and_then(|a| a.first()) {
                    if let Some(s) = c.get("stock").and_then(|x| x.as_i64()) { o.insert("stock".into(), json!(s)); }
                    if let Some(p) = c.get("unit_price_usd").and_then(|x| x.as_f64()) { o.insert("price".into(), json!(format!("${:.4}", p))); }
                    let tier = if c.get("is_basic").and_then(|x| x.as_bool()).unwrap_or(false) { Some("Basic") }
                        else if c.get("is_preferred").and_then(|x| x.as_bool()).unwrap_or(false) { Some("Preferred") } else { Some("Extended") };
                    if let Some(t) = tier { o.insert("tier".into(), json!(t)); }
                    if let Some(pop) = c.get("popularity").and_then(|x| x.as_i64()) { o.insert("popularity".into(), json!(pop)); }
                    if let Some(u) = c.get("jlcpcb_url").and_then(|x| x.as_str()) { o.insert("jlc_url".into(), json!(u)); }
                    if let Some(m) = c.get("mfr").and_then(|x| x.as_str()) { mpn = m.to_string(); }
                    if let Some(mf) = c.get("manufacturer").and_then(|x| x.as_str()) { o.insert("mfr".into(), json!(mf)); }
                }
            }
        }
    }

    // ── wiki page — ONLY on a close name match (avoids garbage for passives) ──
    let wq = if !mpn.is_empty() { mpn.clone() } else { value.to_string() };
    if wq.len() > 2 {
        if let Some(txt) = run_capped("adom-wiki", &["discover", "search", "--query", &wq, "--limit", "5", "--json"], 12) {
            if let Some(v) = parse_json_tail(&txt) {
                if let Some(results) = v.get("data").and_then(|d| d.get("results")).and_then(|r| r.as_array()) {
                    let ql = wq.to_lowercase();
                    let pick = results.iter().find(|r| {
                        let t = r.get("title").and_then(|t| t.as_str()).unwrap_or("").to_lowercase();
                        t.len() > 2 && (t == ql || ql.contains(&t) || t.contains(&ql))
                    });
                    if let Some(r) = pick {
                        let owner = r.get("owner").and_then(|x| x.as_str()).unwrap_or("");
                        let slug = r.get("slug").and_then(|x| x.as_str()).unwrap_or("");
                        if !slug.is_empty() { o.insert("wiki_url".into(), json!(format!("https://wiki.adom.inc/{owner}/{slug}"))); }
                        if !o.contains_key("popularity") {
                            let vouch = r.get("vouch_count").and_then(|x| x.as_i64()).unwrap_or(0);
                            o.insert("popularity".into(), json!(vouch));
                        }
                    }
                }
            }
        }
    }

    // the real manufacturer part number (learned from JLCPCB) — the abridged
    // hover card shows this instead of the schematic's `value`.
    if !mpn.is_empty() { o.insert("mpn".into(), json!(mpn)); }

    if o.is_empty() { return "{\"error\":\"no data\"}".into(); }
    out.to_string()
}