//! JST (J.S.T. Mfg. Co., Ltd.) — connector CAD source plugin.
//!
//! JST is unusual: its CAD is **email-gated**. Hitting a download link does
//! NOT return the file — it returns a License Agreement form, and on submit
//! (since April 2025) JST EMAILS the CAD as a `.zip` attachment. The full
//! flow (native-browser bridge, form fill, Gmail retrieval) lives in
//! `playbooks/jst.md`. This module is just the URL/part-number plumbing.
//!
//! Two sites:
//!   * jst.com (USA) — Cloudflare-walled against pup; spec tables only; it
//!     routes to jst-mfg.com for CAD. Drive it with the native-browser
//!     bridge, never pup.
//!   * jst-mfg.com (global) — where the CAD + catalogs actually live.
//!
//! No account login is required for CAD — only the agreement form + a
//! reachable email address.

/// The master index of every JST series ("Series Alphabet Search"). Each
/// series on this page is an `index.php?series=<id>` link. Start here when
/// you don't already know a part's series id.
pub fn series_index_url() -> &'static str {
    "https://www.jst-mfg.com/product/index.php?type=5"
}

/// A series listing page (all parts + their CAD download buttons).
pub fn series_url(series_id: u32) -> String {
    format!("https://www.jst-mfg.com/product/index.php?series={series_id}")
}

/// Global-site keyword search — use to resolve an MPN to its series when the
/// series id is unknown.
pub fn search_url(mpn: &str) -> String {
    let mut q = String::with_capacity(mpn.len());
    for b in mpn.bytes() {
        match b {
            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => q.push(b as char),
            _ => q.push_str(&format!("%{:02X}", b)),
        }
    }
    format!("https://www.jst-mfg.com/search/?q={q}")
}

/// JST CAD document kinds. The `doc=` query param selects the format, BUT the
/// numeric mapping is per-series — newer series expose STEP, older ones only
/// IGES. ALWAYS read the column header on the series page; treat these as the
/// common-case defaults (verified on the PH series, series=199), not gospel.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DocKind {
    Iges,  // doc=1 (common) — 3D, .IGS
    Step,  // doc=2 (common) — 3D, .STEP  ← preferred for chip-fetcher
    Pdf3d, // doc=3 (common) — 3D-PDF
    Pdf2d, // doc=4 (common) — 2D-PDF drawing
}

impl DocKind {
    /// The common-case `doc=` number for this kind. Verify against the page.
    pub fn default_doc(&self) -> u32 {
        match self {
            DocKind::Iges => 1,
            DocKind::Step => 2,
            DocKind::Pdf3d => 3,
            DocKind::Pdf2d => 4,
        }
    }
}

/// The email-gated download link for one file. GET-ing this in the
/// native browser:
///   1. sets the PHP session binding for (series, doc, filename), and
///   2. returns the License Agreement HTML form.
/// You then accept cookies, fill the form, and call the page's `download()`
/// JS — JST emails the `.zip`. `filename` is the part's zip name, e.g.
/// "PHR-2.zip".
pub fn download_url(series_id: u32, doc: u32, filename: &str) -> String {
    format!(
        "https://www.jst-mfg.com/product/index.php?type=10&series={series_id}&doc={doc}&filename={filename}"
    )
}

/// Series catalog/datasheet PDF — a DIRECT download (no gate). `series_slug`
/// is the short uppercase series tag, e.g. "PH" → ePH.pdf. JST has no
/// per-part datasheet; use the catalog + the 2D-PDF drawing.
pub fn catalog_pdf_url(series_slug: &str) -> String {
    format!(
        "https://www.jst-mfg.com/product/pdf/eng/e{}.pdf",
        series_slug.to_ascii_uppercase()
    )
}

/// The required License Agreement form fields, in page order. Field ids on
/// the page match these keys. `url` is optional; the rest are mandatory.
pub const FORM_FIELDS: &[&str] = &["company", "section", "name", "address", "tel", "email", "url"];

/// Normalize a JST ordering MPN to its catalog part number for searching:
/// strip the lead-free/plating suffix `(LF)(SN)` and any trailing
/// parenthetical option groups, plus a trailing `T` suction-cap option on
/// SMT headers (per JST + DigiKey TechForum guidance). E.g.
///   "B10B-PH-K-S(LF)(SN)"  -> "B10B-PH-K-S"
///   "BM03B-SRSS-TBT(LF)(SN)" -> "BM03B-SRSS-TB"
pub fn base_part(mpn: &str) -> String {
    // Drop everything from the first '(' onward (all option groups).
    let mut s = match mpn.find('(') {
        Some(i) => mpn[..i].to_string(),
        None => mpn.to_string(),
    };
    s = s.trim().to_string();
    // Trailing suction-cap "T" on SMT headers (…-TBT -> …-TB). Only strip a
    // lone trailing T that follows another uppercase letter, to avoid eating
    // a real terminal T that is part of the code.
    if s.ends_with('T') {
        let chars: Vec<char> = s.chars().collect();
        if chars.len() >= 2 && chars[chars.len() - 2].is_ascii_uppercase() {
            s.pop();
        }
    }
    s
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn strips_plating_and_option_suffixes() {
        assert_eq!(base_part("B10B-PH-K-S(LF)(SN)"), "B10B-PH-K-S");
        assert_eq!(base_part("BM03B-SRSS-TBT(LF)(SN)"), "BM03B-SRSS-TB");
        assert_eq!(base_part("PHR-2"), "PHR-2");
        assert_eq!(base_part("S2B-PH-K-S(LF)(SN)"), "S2B-PH-K-S");
    }

    #[test]
    fn doc_defaults() {
        assert_eq!(DocKind::Step.default_doc(), 2);
        assert_eq!(DocKind::Iges.default_doc(), 1);
    }

    #[test]
    fn urls() {
        assert_eq!(
            download_url(199, 2, "PHR-2.zip"),
            "https://www.jst-mfg.com/product/index.php?type=10&series=199&doc=2&filename=PHR-2.zip"
        );
        assert_eq!(catalog_pdf_url("ph"), "https://www.jst-mfg.com/product/pdf/eng/ePH.pdf");
    }
}