//! adom-schematic — interactive schematic viewer for a whole `.kicad_sch`.
//!
//! Sibling of adom-2dboard. The visual base is the EDA's OWN render
//! (service-kicad `sch/export/svg` — authentic symbols/wires/fonts); we overlay
//! a per-component hotspot layer so hovering a symbol shows what it is plus live
//! stock & price. Meant to be embedded as a live schematic "view" in any app.

mod sexpr;
mod sch_parse;
mod kicad_sch;
mod kicad_self;
mod eagle_sch;
mod altium_sch;
mod enrich;
mod server;

use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use std::fs;
use std::path::PathBuf;

#[derive(Parser)]
#[command(name = "adom-schematic", version, about = "Interactive schematic viewer (.kicad_sch): KiCad-native render + per-component hover with stock & price")]
struct Cli { #[command(subcommand)] command: Cmd }

#[derive(Subcommand)]
enum Cmd {
    /// Render a `.kicad_sch` to a composed interactive SVG (+ optional -meta.json).
    Render { #[arg(long)] file: PathBuf, #[arg(long)] out: PathBuf, #[arg(long)] meta: bool },
    /// Export a self-contained INTERACTIVE HTML for embedding as a schematic view.
    Embed { #[arg(long)] file: PathBuf, #[arg(long)] out: Option<PathBuf>,
            /// Directory holding this design's sibling `.kicad_sch` files. When
            /// given, every hierarchical child sheet is pre-rendered and bundled
            /// into the embed so navigating between sheets is instant (no reload).
            #[arg(long)] sheet_dir: Option<PathBuf> },
    /// Run the live app server (the embeddable live schematic view).
    Serve { #[arg(long)] file: Option<PathBuf>, #[arg(long, default_value_t = 8785)] port: u16 },
    /// Look up stock / price / MPN / wiki for one part, as JSON on stdout.
    /// Lets a HOST app (e.g. adom-project-manager) serve `/enrich` for an
    /// embedded view, which has no server of its own.
    Enrich { #[arg(long, default_value = "")] mpn: String, #[arg(long, default_value = "")] lcsc: String },
    /// Drive the running server's front-end (the AI's live UI channel) exactly as a
    /// user's click would: `ui toast "done"` · `ui fit` · `ui sheet Data` ·
    /// `ui highlight U1` · `ui clear`.
    Ui { /// toast | fit | sheet | highlight | clear
         action: String,
         /// message (toast) or argument (sheet file / component ref)
         arg: Option<String>,
         #[arg(long = "type", default_value = "info")] ty: String,
         #[arg(long, default_value_t = 8785)] port: u16 },
    /// Check the live server is up on --port (prints OK:/ERROR: + a Hint:).
    Health { #[arg(long, default_value_t = 8785)] port: u16 },
    /// Install this binary to ~/.local/bin so host apps can shell it by name.
    Install,
}

const APP_HTML: &str = include_str!("app.html");
const VIEWER_JS: &str = include_str!("viewer.js");

pub fn build_embed(svg: &str, meta_json: &str) -> String {
    let payload = format!(
        "<script>window.__SCH__={{svg:{},meta:{}}};</script>\n<script>{}</script>",
        serde_json::to_string(svg).unwrap_or_else(|_| "\"\"".into()), meta_json, VIEWER_JS);
    APP_HTML.replace(r#"<script src="viewer.js"></script>"#, &payload)
}

/// Like build_embed, but also inlines `window.__SHEETS__` = {filename: {svg,meta}}
/// (root under "__root__") so the frontend can switch sheets instantly.
pub fn build_embed_bundle(svg: &str, meta_json: &str, bundle_json: &str) -> String {
    let payload = format!(
        "<script>window.__SCH__={{svg:{},meta:{}}};window.__SHEETS__={};</script>\n<script>{}</script>",
        serde_json::to_string(svg).unwrap_or_else(|_| "\"\"".into()), meta_json, bundle_json, VIEWER_JS);
    APP_HTML.replace(r#"<script src="viewer.js"></script>"#, &payload)
}

// CLI contract: every response starts with `OK:` or `ERROR:`, and every error
// carries an actionable `Hint:` naming the next command.
fn main() {
    if let Err(e) = run() {
        println!("ERROR: {e}");
        println!("Hint: check the file path / arguments; run `adom-schematic --help`");
        std::process::exit(1);
    }
}

fn run() -> Result<()> {
    match Cli::parse().command {
        Cmd::Render { file, out, meta } => {
            let raw = fs::read(&file).with_context(|| format!("read {}", file.display()))?;
            let (svg, m) = kicad_sch::render_sheet_bytes(&raw)?;
            fs::write(&out, &svg)?;
            println!("OK: rendered {} -> {} ({} components)", file.display(), out.display(), m.comp_count);
            if meta { fs::write(out.with_extension("meta.json"), serde_json::to_string_pretty(&m)?)?; }
        }
        Cmd::Embed { file, out, sheet_dir } => {
            let raw = fs::read(&file).with_context(|| format!("read {}", file.display()))?;
            // an authentic pre-rendered base sits beside the file as `<name>.base.svg`
            let base_of = |p: &std::path::Path| -> Option<String> {
                fs::read_to_string(format!("{}.base.svg", p.display())).ok()
            };
            let (svg, m) = kicad_sch::render_sheet_bytes_base(&raw, base_of(&file).as_deref())?;
            // Bundle: the root plus every child sheet, keyed by filename, so the
            // frontend can swap sheets in-place with zero load time.
            let mut bundle = serde_json::Map::new();
            bundle.insert("__root__".into(), serde_json::json!({"svg": svg, "meta": m}));
            let mut n_sheets = 0usize;
            if let Some(dir) = &sheet_dir {
                for sh in &m.sub_sheets {
                    // render each child; a child may itself be hierarchical (its own
                    // sub_sheets carry into that sheet's meta for deeper nav).
                    let p = dir.join(&sh.file);
                    if let Ok(cbytes) = fs::read(&p) {
                        if let Ok((csvg, cm)) = kicad_sch::render_sheet_bytes_base(&cbytes, base_of(&p).as_deref()) {
                            bundle.insert(sh.file.clone(), serde_json::json!({"svg": csvg, "meta": cm}));
                            n_sheets += 1;
                        }
                    }
                }
            }
            let html = build_embed_bundle(&svg, &serde_json::to_string(&m)?, &serde_json::to_string(&bundle)?);
            let out = out.unwrap_or_else(|| file.with_extension("schview.html"));
            fs::write(&out, &html)?;
            println!("OK: embed -> {} ({} components, {} child sheets bundled)", out.display(), m.comp_count, n_sheets);
        }
        Cmd::Serve { file, port } => {
            if let Some(f) = file {
                let content = fs::read_to_string(&f).with_context(|| format!("read {}", f.display()))?;
                let name = f.file_stem().and_then(|s| s.to_str()).unwrap_or("sheet").to_string();
                server::load_sch(&name, &content)?;
                println!("OK: loaded {}", f.display());
            }
            server::run(port)?;
        }
        Cmd::Enrich { mpn, lcsc } => println!("{}", enrich::lookup(&mpn, &lcsc)),
        Cmd::Ui { action, arg, ty, port } => {
            let base = format!("http://127.0.0.1:{port}");
            let (path, payload) = match action.as_str() {
                "toast" => ("/ui/toast", serde_json::json!({ "message": arg.unwrap_or_default(), "type": ty }).to_string()),
                "fit" | "clear" => ("/ui/cmd", serde_json::json!({ "action": action }).to_string()),
                "sheet" | "highlight" => ("/ui/cmd", serde_json::json!({ "action": action, "arg": arg.unwrap_or_default() }).to_string()),
                other => {
                    println!("ERROR: unknown ui action '{other}'");
                    println!("Hint: use one of: toast <msg> | fit | sheet <file> | highlight <ref> | clear");
                    return Ok(());
                }
            };
            match ureq::post(&format!("{base}{path}")).set("Content-Type", "application/json").send_string(&payload) {
                Ok(_) => println!("OK: ui {action} sent to {base}"),
                Err(_) => {
                    println!("ERROR: could not reach the viewer server on port {port}");
                    println!("Hint: start it with `adom-schematic serve --port {port} &`");
                }
            }
        }
        Cmd::Health { port } => match ureq::get(&format!("http://127.0.0.1:{port}/version")).call() {
            Ok(r) => println!("OK: adom-schematic healthy on port {port} ({})", r.into_string().unwrap_or_default().trim()),
            Err(_) => {
                println!("ERROR: no adom-schematic server responding on port {port}");
                println!("Hint: start it with `adom-schematic serve --port {port} &`");
            }
        },
        Cmd::Install => {
            let exe = std::env::current_exe()?;
            let home = std::env::var("HOME").unwrap_or_default();
            let bin = format!("{home}/.local/bin");
            fs::create_dir_all(&bin)?;
            let dst = format!("{bin}/adom-schematic");
            fs::copy(&exe, &dst)?;
            println!("OK: installed adom-schematic to {dst}");
            println!("Hint: ensure ~/.local/bin is on your PATH");
        }
    }
    Ok(())
}