//! altium-codec CLI — encode neutral-model JSON to Altium libraries, or decode.
//!
//! Usage:
//!   altium-codec encode-sym <symbols.json> <out.SchLib>
//!   altium-codec encode-fp  <footprint.json> <out.PcbLib>
//!   altium-codec decode     <in.SchLib|in.PcbLib>
//!
//! symbols.json: a JSON array of Symbol, or a single Symbol object.
//! footprint.json: a single Footprint object.

use altium_codec::{
    decode_pcblib, decode_schlib_first, encode_pcblib, encode_schlib, import_kicad, Footprint,
    Symbol,
};
use anyhow::{bail, Context, Result};
use std::fs;
use std::path::Path;

/// Seed a symbol's Altium property fields from the part identity, unless the
/// adom-lbr json already carries a same-named parameter. `Value` is drawn on the
/// symbol; identity metadata (Manufacturer, MPN, Package) is added hidden.
fn add_part_parameters(symbol: &mut altium_codec::Symbol, part: &altium_codec::AdomLbrPart) {
    use altium_codec::Parameter;
    let mut push = |name: &str, value: &str, visible: bool| {
        if value.is_empty() {
            return;
        }
        if symbol.parameters.iter().any(|p| p.name.eq_ignore_ascii_case(name)) {
            return;
        }
        symbol.parameters.push(Parameter { name: name.into(), value: value.into(), visible });
    };
    push("Value", &part.value, true);
    push("Manufacturer", &part.manufacturer, false);
    push("Manufacturer Part Number", &part.mpn, false);
    push("Package", &part.package, false);
}

fn main() -> Result<()> {
    let args: Vec<String> = std::env::args().collect();
    if args.len() < 3 {
        eprintln!("usage: altium-codec <encode-sym|encode-fp|decode> <in> [out]");
        std::process::exit(2);
    }
    match args[1].as_str() {
        "encode-sym" => {
            let json = fs::read_to_string(&args[2]).context("read symbols json")?;
            let syms: Vec<Symbol> = match serde_json::from_str::<Vec<Symbol>>(&json) {
                Ok(v) => v,
                Err(_) => vec![serde_json::from_str::<Symbol>(&json).context("parse Symbol")?],
            };
            let bytes = encode_schlib(&syms)?;
            let out = args.get(3).map(|s| s.as_str()).unwrap_or("out.SchLib");
            fs::write(out, &bytes)?;
            println!("wrote {} ({} bytes, {} symbols)", out, bytes.len(), syms.len());
        }
        "encode-fp" => {
            let json = fs::read_to_string(&args[2]).context("read footprint json")?;
            let fp: Footprint = serde_json::from_str(&json).context("parse Footprint")?;
            let bytes = encode_pcblib(&fp)?;
            let out = args.get(3).map(|s| s.as_str()).unwrap_or("out.PcbLib");
            fs::write(out, &bytes)?;
            println!("wrote {} ({} bytes, {} pads)", out, bytes.len(), fp.pads.len());
        }
        "decode" => {
            let p = Path::new(&args[2]);
            let bytes = fs::read(p).context("read input")?;
            let ext = p.extension().and_then(|e| e.to_str()).unwrap_or("").to_lowercase();
            let v = if ext.contains("pcblib") {
                decode_pcblib(&bytes)?
            } else {
                decode_schlib_first(&bytes)?
            };
            println!("{}", serde_json::to_string_pretty(&v)?);
        }
        // kicad sym+mod  →  universal part.json  (the import / hub step)
        "import-kicad" => {
            let md_path = args
                .get(3)
                .ok_or_else(|| anyhow::anyhow!("usage: altium-codec import-kicad <in.kicad_sym> <in.kicad_mod> [mpn]"))?;
            let sym = fs::read_to_string(&args[2]).context("read kicad_sym")?;
            let md = fs::read_to_string(md_path).context("read kicad_mod")?;
            let mpn = args.get(4).cloned().unwrap_or_else(|| "PART".into());
            let part = import_kicad(&sym, &md, &mpn)?;
            let json = serde_json::to_string_pretty(&part)?;
            println!("{json}");
        }
        // universal part.json  →  Altium .SchLib + .PcbLib  (the export step)
        "export-altium" => {
            let json = fs::read_to_string(&args[2]).context("read part.json")?;
            let part: altium_codec::AdomLbrPart =
                serde_json::from_str(&json).context("parse AdomLbrPart")?;
            let outdir = args.get(3).map(|s| s.as_str()).unwrap_or(".");
            let mut symbol = part.symbol.clone();
            add_part_parameters(&mut symbol, &part);
            let sym_bytes = encode_schlib(&[symbol])?;
            let fp_bytes = encode_pcblib(&part.footprint)?;
            let sp = format!("{outdir}/{}.SchLib", part.mpn);
            let pp = format!("{outdir}/{}.PcbLib", part.mpn);
            fs::write(&sp, &sym_bytes)?;
            fs::write(&pp, &fp_bytes)?;
            println!(
                "wrote {sp} ({} pins) + {pp} ({} pads)",
                part.symbol.pins.len(),
                part.footprint.pads.len()
            );
        }
        // universal part.json  →  one self-contained Altium .IntLib
        "export-intlib" => {
            let json = fs::read_to_string(&args[2]).context("read part.json")?;
            let part: altium_codec::AdomLbrPart =
                serde_json::from_str(&json).context("parse AdomLbrPart")?;
            let descr = if part.value.is_empty() { part.mpn.clone() } else { format!("{} {}", part.value, part.package) };
            let mut symbol = part.symbol.clone();
            add_part_parameters(&mut symbol, &part);
            let bytes = altium_codec::encode_intlib(&symbol, &part.footprint, &descr)?;
            let out = args.get(3).cloned().unwrap_or_else(|| format!("{}.IntLib", part.mpn));
            fs::write(&out, &bytes)?;
            println!("wrote {out} ({} bytes) — symbol {} + footprint {} bundled",
                bytes.len(), part.symbol.name, part.footprint.name);
        }
        // Add a part's symbol into an EXISTING .SchLib (non-destructive merge):
        //   altium-codec add-to-schlib <existing.SchLib> <part.json> <out.SchLib>
        "add-to-schlib" => {
            let json_path = args
                .get(3)
                .ok_or_else(|| anyhow::anyhow!("usage: altium-codec add-to-schlib <existing.SchLib> <part.json> [out.SchLib]"))?;
            let existing = fs::read(&args[2]).context("read existing SchLib")?;
            let json = fs::read_to_string(json_path).context("read part.json")?;
            // Accept an AdomLbrPart, a bare Symbol, or an array of Symbols.
            let syms: Vec<Symbol> = if let Ok(p) =
                serde_json::from_str::<altium_codec::AdomLbrPart>(&json)
            {
                vec![p.symbol]
            } else if let Ok(v) = serde_json::from_str::<Vec<Symbol>>(&json) {
                v
            } else {
                vec![serde_json::from_str::<Symbol>(&json).context("parse Symbol/AdomLbrPart")?]
            };
            let merged = altium_codec::add_symbols_to_schlib(&existing, &syms)?;
            let out = args.get(4).map(|s| s.as_str()).unwrap_or("merged.SchLib");
            fs::write(out, &merged)?;
            println!(
                "wrote {out} ({} bytes) — added {} symbol(s) into existing library",
                merged.len(),
                syms.len()
            );
        }
        other => bail!("unknown command: {other}"),
    }
    Ok(())
}