//! adom-layout-viewer — standalone Rust CLI + live app for whole KiCad PCBs.
//!
//! Sibling of adom-symbol / adom-footprint, but for a whole `.kicad_pcb`. The
//! headline feature is REAL net/trace highlighting + per-component hover info,
//! which is only possible because we self-render an identity-tagged SVG
//! (`data-net`/`data-ref`/`data-pad`) instead of consuming kicad-cli's flat,
//! anonymous export. Meant to be embedded as a live PCB "view" in any app.

mod pcb_render;
mod kicad;
mod eagle;
mod altium;
mod sexpr;
mod sch_render;
mod server;
mod enrich;

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

#[derive(Parser)]
#[command(name = "adom-layout-viewer", version, about = "Interactive PCB layout viewer (.kicad_pcb): net/trace highlighting, component outlines + hover, per-side layers")]
struct Cli {
    #[command(subcommand)]
    command: Cmd,
}

#[derive(Subcommand)]
enum Cmd {
    /// Render a `.kicad_pcb` to a themed, identity-tagged SVG (+ optional -meta.json).
    Render {
        #[arg(long)]
        file: PathBuf,
        #[arg(long)]
        out: PathBuf,
        /// Also write <out>.meta.json (nets, components, counts)
        #[arg(long)]
        meta: bool,
        /// EDA-exported SVG to use as the visual base (e.g. Altium's render/pcb.svg)
        #[arg(long)]
        base: Option<PathBuf>,
    },
    /// Export a self-contained INTERACTIVE HTML (pan/zoom + net/trace highlight +
    /// component hover) for embedding as a PCB "view" in any app via <iframe>.
    Embed {
        #[arg(long)]
        file: PathBuf,
        #[arg(long)]
        out: Option<PathBuf>,
        /// EDA-exported SVG to use as the visual base (e.g. Altium's render/pcb.svg)
        #[arg(long)]
        base: Option<PathBuf>,
    },
    /// [dev] Render a `.kicad_sch` to a themed identity-tagged SVG (adom-schematic engine).
    RenderSch {
        #[arg(long)]
        file: PathBuf,
        #[arg(long)]
        out: PathBuf,
        #[arg(long)]
        meta: bool,
    },
    /// Run the live app server (AI-drivable; the embeddable live PCB view).
    Serve {
        /// Optionally load a `.kicad_pcb` at startup.
        #[arg(long)]
        file: Option<PathBuf>,
        #[arg(long, default_value_t = 8784)]
        port: u16,
    },
    /// Drive the running server's front-end (the AI's live UI channel) as a user's
    /// click would: `ui toast "done"` · `ui fit` · `ui net GND` · `ui clear`.
    Ui { /// toast | fit | net | clear
         action: String,
         /// message (toast) or argument (net name)
         arg: Option<String>,
         #[arg(long = "type", default_value = "info")] ty: String,
         #[arg(long, default_value_t = 8784)] port: u16 },
    /// Check the live server is up on --port (prints OK:/ERROR: + a Hint:).
    Health { #[arg(long, default_value_t = 8784)] 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");

/// Build the self-contained embed HTML: inline viewer.js + the board payload in
/// place of the live `<script src="/viewer.js">` tag.
pub fn build_embed(svg: &str, meta_json: &str) -> String {
    let payload = format!(
        "<script>window.__PCB__={{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)
}

// 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-layout-viewer --help`");
        std::process::exit(1);
    }
}

fn run() -> Result<()> {
    let cli = Cli::parse();
    match cli.command {
        Cmd::Render { file, out, meta, base } => {
            let raw = fs::read(&file).with_context(|| format!("read {}", file.display()))?;
            let base_svg = base.as_ref().and_then(|p| fs::read_to_string(p).ok());
            let (svg, m) = pcb_render::render_board_bytes_with(&raw, base_svg.as_deref());
            fs::write(&out, &svg).with_context(|| format!("write {}", out.display()))?;
            println!(
                "OK: rendered {} -> {} ({} nets, {} comps, {} tracks, {} vias, {} pads, {:.1}x{:.1}mm)",
                file.display(), out.display(), m.net_names.len(), m.components.len(),
                m.track_count, m.via_count, m.pad_count, m.width_mm, m.height_mm
            );
            if meta {
                let mp = out.with_extension("meta.json");
                fs::write(&mp, serde_json::to_string_pretty(&m)?)?;
                println!("OK: meta -> {}", mp.display());
            }
        }
        Cmd::Embed { file, out, base } => {
            let raw = fs::read(&file).with_context(|| format!("read {}", file.display()))?;
            let base_svg = base.as_ref().and_then(|p| fs::read_to_string(p).ok());
            let (svg, m) = pcb_render::render_board_bytes_with(&raw, base_svg.as_deref());
            let html = build_embed(&svg, &serde_json::to_string(&m)?);
            let out = out.unwrap_or_else(|| file.with_extension("pcbview.html"));
            fs::write(&out, &html).with_context(|| format!("write {}", out.display()))?;
            println!("OK: embed -> {} ({} comps, {} nets)", out.display(), m.components.len(), m.net_names.len());
        }
        Cmd::RenderSch { file, out, meta } => {
            let content = fs::read_to_string(&file).with_context(|| format!("read {}", file.display()))?;
            let sheet = sch_render::parse(&content);
            let (svg, m) = sch_render::render(&sheet);
            fs::write(&out, &svg)?;
            eprintln!("[adom-schematic] {} → {} ({} comps, {} wires, {} nets, {:.0}×{:.0}mm)",
                file.display(), out.display(), m.components.len(), m.track_count, m.net_names.len(), m.width_mm, m.height_mm);
            if meta { let mp = out.with_extension("meta.json"); fs::write(&mp, serde_json::to_string_pretty(&m)?)?; }
            let emb = out.with_extension("embed.html");
            fs::write(&emb, build_embed(&svg, &serde_json::to_string(&m)?))?;
            eprintln!("[adom-schematic] embed → {}", emb.display());
        }
        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("board").to_string();
                server::load_pcb(&name, &content)?;
                println!("OK: loaded {}", f.display());
            }
            server::run(port)?;
        }
        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()),
                "net" => ("/ui/cmd", serde_json::json!({ "action": "net", "arg": arg.unwrap_or_default() }).to_string()),
                other => {
                    println!("ERROR: unknown ui action '{other}'");
                    println!("Hint: use one of: toast <msg> | fit | net <name> | 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-layout-viewer serve --port {port} &`");
                }
            }
        }
        Cmd::Health { port } => match ureq::get(&format!("http://127.0.0.1:{port}/version")).call() {
            Ok(r) => println!("OK: adom-layout-viewer healthy on port {port} ({})", r.into_string().unwrap_or_default().trim()),
            Err(_) => {
                println!("ERROR: no adom-layout-viewer server responding on port {port}");
                println!("Hint: start it with `adom-layout-viewer 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-layout-viewer");
            fs::copy(&exe, &dst)?;
            println!("OK: installed adom-layout-viewer to {dst}");
            println!("Hint: ensure ~/.local/bin is on your PATH");
        }
    }
    Ok(())
}