//! Native Altium `.SchLib` WRITER — pure Rust, no Altium. The inverse of the
//! reader: emits an OLE2/CFB container whose streams and records are
//! byte-structurally identical to what real Altium produces (verified by
//! re-reading with this crate's parser and by byte-diffing pin records against
//! manufacturer `.SchLib` files).
//!
//! Container: `/FileHeader` (component list) · `/Storage` (icon placeholder) ·
//! `/\x01Sh33tJ5` (the constant "7262" marker) · `/<part>` storage · `/<part>/Data`
//! (the record stream). Every stream is framed `<u16 len><u16 flags><payload>`.

use anyhow::Result;
use std::io::{Cursor, Write};

/// A schematic pin to emit, in Altium SCH internal units (1 unit = 2.54/2560 mm).
/// `x`/`y` are the pin's connection-end location; Altium Y is up. `rotation` is
/// 0/90/180/270 degrees.
pub struct WritePin {
    pub name: String,
    pub designator: String,
    pub x: i32,
    pub y: i32,
    pub length: i32,
    pub rotation: i32,
}

/// The fixed 26-byte prefix of an Altium pin record (record type `0x02`),
/// captured byte-for-byte from a real manufacturer `.SchLib`. Bytes [14] (pin
/// conglomerate / orientation), [15..17] (length), [17..19] (X) and [19..21] (Y)
/// are overwritten per pin; the rest (owner-part id, electrical defaults, the
/// trailing sentinels) carry through unchanged — which is why generated pins are
/// byte-identical to Altium's own.
const PIN_PREFIX: [u8; 26] = [
    0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x01, 0x04, 0x3a, 0x14, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];

fn put_rec(out: &mut Vec<u8>, flags: u16, payload: &[u8]) {
    out.extend_from_slice(&(payload.len() as u16).to_le_bytes());
    out.extend_from_slice(&flags.to_le_bytes());
    out.extend_from_slice(payload);
}

/// Altium length-prefixed (1-byte) string.
fn put_pstr(out: &mut Vec<u8>, s: &str) {
    let b = s.as_bytes();
    let n = b.len().min(255);
    out.push(n as u8);
    out.extend_from_slice(&b[..n]);
}

/// A text record (`|KEY=VAL|…`) — payload is the text plus a trailing NUL, the
/// way Altium frames its FileHeader and component header records.
fn put_text(out: &mut Vec<u8>, flags: u16, body: &str) {
    let mut p = body.as_bytes().to_vec();
    p.push(0);
    put_rec(out, flags, &p);
}

/// Deterministic 8-uppercase-letter UniqueID from an index (Altium wants a
/// stable A–Z token per primitive; exact value is cosmetic).
fn uid(i: usize) -> String {
    let mut s = String::new();
    let mut n = i + 1;
    for _ in 0..8 {
        s.push((b'A' + (n % 26) as u8) as char);
        n = n / 26 + 1;
    }
    s
}

/// Emit a native `.SchLib` for one component. `body` is the symbol body
/// rectangle (x1,y1,x2,y2) in Altium SCH units. `params` become hidden
/// `RECORD=41` parameters (Manufacturer / MPN / "LCSC Part #" / …) — exactly the
/// fields Altium surfaces in the BOM.
pub fn to_schlib_bytes(
    part: &str,
    description: &str,
    body: (i32, i32, i32, i32),
    pins: &[WritePin],
    params: &[(String, String)],
) -> Result<Vec<u8>> {
    // ----- component Data stream -----
    let mut d = Vec::new();
    put_text(&mut d, 0, &format!(
        "|RECORD=1|LibReference={part}|ComponentDescription={description}|PartCount=1|DisplayModeCount=1|IndexInSheet=-1|OwnerPartId=-1|CurrentPartId=1|LibraryPath=*|SourceLibraryName=*|SheetPartFileName=*|TargetFileName=*|UniqueID={}|AreaColor=11599871|Color=128|PartIDLocked=T|AllPinCount={}",
        uid(0), pins.len()));
    for (k, (name, val)) in params.iter().enumerate() {
        put_text(&mut d, 0, &format!(
            "|RECORD=41|IndexInSheet={k}|OwnerPartId=-1|Color=8388608|FontID=1|IsHidden=T|Text={val}|Name={name}|UniqueID={}",
            uid(100 + k)));
    }
    let (lx, ly, cx, cy) = body;
    put_text(&mut d, 0, &format!(
        "|RECORD=14|IsNotAccesible=T|OwnerPartId=1|Location.X={lx}|Location.Y={ly}|Corner.X={cx}|Corner.Y={cy}|LineWidth=1|Color=128|AreaColor=11599871|IsSolid=T|UniqueID={}",
        uid(50)));
    for p in pins {
        let mut h = PIN_PREFIX.to_vec();
        h[14] = (h[14] & !0x03) | (((p.rotation.rem_euclid(360) / 90) & 0x03) as u8);
        h[15..17].copy_from_slice(&(p.length as i16).to_le_bytes());
        h[17..19].copy_from_slice(&(p.x as i16).to_le_bytes());
        h[19..21].copy_from_slice(&(p.y as i16).to_le_bytes());
        let mut pl = h;
        put_pstr(&mut pl, &p.name);
        put_pstr(&mut pl, &p.designator);
        put_pstr(&mut pl, "0");
        put_pstr(&mut pl, "0");
        put_pstr(&mut pl, "");
        put_rec(&mut d, 0x0100, &pl);
    }

    // ----- FileHeader (the component list) -----
    let mut fh = Vec::new();
    put_text(&mut fh, 0, &format!(
        "|HEADER=Protel for Windows - Schematic Library Editor Binary File Version 5.0|Weight=80|MinorVersion=3|UniqueID=ADOMLBRX|FontIdCount=2|Size1=10|FontName1=Times New Roman|Size2=10|Underline2=T|FontName2=Times New Roman|UseMBCS=T|IsBOC=T|SheetStyle=9|BorderOn=T|SheetNumberSpaceSize=12|AreaColor=16317695|SnapGridOn=T|SnapGridSize=10|VisibleGridOn=T|VisibleGridSize=10|CustomX=18000|CustomY=18000|UseCustomSheet=T|ReferenceZonesOn=T|Display_Unit=0|CompCount=1|LibRef0={part}|CompDescr0={description}|PartCount0=1|AlwaysShowCD=T"));

    let mut storage = Vec::new();
    put_text(&mut storage, 0, "|HEADER=Icon storage");

    // ----- assemble the OLE2/CFB container -----
    let mut comp = cfb::CompoundFile::create(Cursor::new(Vec::new()))?;
    comp.create_stream("/FileHeader")?.write_all(&fh)?;
    comp.create_stream("/Storage")?.write_all(&storage)?;
    comp.create_stream("/\u{1}Sh33tJ5")?.write_all(b"7262")?;
    comp.create_storage(&format!("/{part}"))?;
    comp.create_stream(&format!("/{part}/Data"))?.write_all(&d)?;
    comp.flush()?;
    Ok(comp.into_inner().into_inner())
}