//! Compile-verify the Silicon Labs SLSTK3701A BSP generator output against
//! the real `efm32gg11b-pac` crate on `thumbv7em-none-eabihf`.
//!
//! The snapshot test (`bsp_silabs_slstk3701a_render.rs`) only checks the
//! rendered *text* of the SilabsIr → PAC pipeline. This test closes the
//! type-checking gap by:
//!
//! 1. Rendering the `EFM32GG11` + `slstk3701a` BSP into a tempdir.
//! 2. Materializing a minimal stand-alone cargo project around the
//!    generated files (`[workspace]` so cargo doesn't walk up to rlvgl's
//!    workspace).
//! 3. Shelling out to `cargo check --target thumbv7em-none-eabihf` inside
//!    that project. Fail the test if it doesn't compile.
//!
//! Series 2 chips (EFR32MG24, `thumbv8m.main-none-eabihf`) defer to a
//! future sub-letter — see `CHIPS-SILABS-00 §12(e)`.
//!
//! The test is gated behind `feature = "compile-verify"` because it needs
//! (a) the `thumbv7em-none-eabihf` rustup target, and (b) network access
//! to fetch `efm32gg11b-pac`. Both are reasonable for CI but too heavy for
//! the default `cargo test` run.
#![cfg(all(
    feature = "compile-verify",
    feature = "creator",
    feature = "regression"
))]
#![allow(dead_code, unused_imports)]

#[path = "../src/bin/creator/bsp/silabs/mod.rs"]
mod silabs;

use silabs::{load_board_db, load_chip_db, merge, render_silabs_pac};
use std::process::Command;
use std::{env, fs};

/// Materialize a stand-alone cargo project around the rendered BSP and
/// return the path to its root.
///
/// The generator's `mod.rs` IS the crate root (it declares `pub mod board`,
/// `pub mod clocks`, etc.), so we drop it at `src/lib.rs` and put the other
/// five files alongside it at `src/{pac,clocks,io_mux,peripherals,board}.rs`.
/// That way the `use crate::board::*;` style references inside
/// `peripherals.rs` resolve cleanly to the top-level modules.
fn materialize_fixture_crate(bsp_src_dir: &std::path::Path, dst: &std::path::Path) {
    let src = dst.join("src");
    fs::create_dir_all(&src).expect("create fixture src");

    // mod.rs → src/lib.rs (crate root)
    fs::copy(bsp_src_dir.join("mod.rs"), src.join("lib.rs")).expect("copy mod.rs -> lib.rs");

    // The other five files go at src/<name>.rs.
    for name in [
        "pac.rs",
        "clocks.rs",
        "io_mux.rs",
        "peripherals.rs",
        "board.rs",
    ] {
        let from = bsp_src_dir.join(name);
        let to = src.join(name);
        fs::copy(&from, &to)
            .unwrap_or_else(|e| panic!("copy {} -> {}: {e}", from.display(), to.display()));
    }

    // Stand-alone Cargo.toml. `[workspace]` at the top prevents cargo
    // from walking up and finding rlvgl's workspace.
    //
    // The `efm32gg11b820` SKU feature is required because the PAC crate
    // gates its register-block visibility on that feature (per
    // CHIPS-SILABS-01a's PAC pin: `efm32gg11b-pac` 0.1.4, generated by
    // svd2rust 0.28.0 from the SiSDK SVD for EFM32GG11B820F2048GL192).
    let cargo_toml = r#"[workspace]

[package]
name = "bsp-slstk3701a-compile-verify"
version = "0.0.0"
edition = "2021"
publish = false

[lib]
path = "src/lib.rs"

[dependencies]
efm32gg11b-pac = { version = "0.1.4", features = ["efm32gg11b820"] }
"#;
    fs::write(dst.join("Cargo.toml"), cargo_toml).expect("write Cargo.toml");
}

fn compile_verify_board(chip_key: &str, board_slug: &str, rendered_subdir: &str, tag: &str) {
    // Skip gracefully if the thumbv7em target isn't installed.
    let target = "thumbv7em-none-eabihf";
    let rustup_list = Command::new("rustup")
        .args(["target", "list", "--installed"])
        .output();
    match rustup_list {
        Ok(out) if out.status.success() => {
            let list = String::from_utf8_lossy(&out.stdout);
            if !list.lines().any(|l| l.trim() == target) {
                eprintln!(
                    "skipping compile-verify for {board_slug}: target {target} not installed (run `rustup target add {target}`)"
                );
                return;
            }
        }
        _ => {
            eprintln!("skipping compile-verify for {board_slug}: rustup not available");
            return;
        }
    }

    // Render.
    let chip = load_chip_db(chip_key).expect("chip yaml");
    let board = load_board_db(board_slug).expect("board yaml");
    let ir = merge(chip, board).expect("merge ok");
    let render_tmp = tempfile::tempdir().expect("render tempdir");
    let written = render_silabs_pac(&ir, render_tmp.path()).expect("render ok");
    // 6 .rs files + 2 linker scripts (memory.x, efm32_gg11.x) since
    // CHIPS-SILABS-05. The compile-verify materialisation step below
    // only copies the 5 non-mod.rs sources into the throwaway cargo
    // project — `cargo check` doesn't invoke the linker so the linker
    // emission is exercised by the render snapshot gate only.
    assert_eq!(written.len(), 8);
    let bsp_src_dir = render_tmp.path().join(rendered_subdir);
    assert!(bsp_src_dir.is_dir(), "expected {}", bsp_src_dir.display());

    // Materialize a throwaway cargo project around the rendered BSP.
    let fixture_tmp = tempfile::tempdir().expect("fixture tempdir");
    let fixture_root = fixture_tmp.path().join(format!("bsp-{tag}-compile-verify"));
    materialize_fixture_crate(&bsp_src_dir, &fixture_root);

    // Reuse a stable target dir so repeated runs are fast.
    // Use a per-board tag so parallel board tests don't lock each other out.
    let mut stable_target_dir = env::temp_dir();
    stable_target_dir.push(format!("rlvgl-bsp-{tag}-compile-verify-target"));
    fs::create_dir_all(&stable_target_dir).expect("create stable target dir");

    let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
    let output = Command::new(&cargo)
        .args(["check", "--target", target])
        .current_dir(&fixture_root)
        .env("CARGO_TARGET_DIR", &stable_target_dir)
        // Drop inherited RUSTFLAGS so a parent `RUSTFLAGS="-C target-cpu=..."`
        // from a `make` target doesn't leak in and break the thumbv7em build.
        .env_remove("RUSTFLAGS")
        .output()
        .expect("spawn cargo check");

    if !output.status.success() {
        eprintln!(
            "\n=== cargo check stdout ({board_slug}) ===\n{}",
            String::from_utf8_lossy(&output.stdout)
        );
        eprintln!(
            "\n=== cargo check stderr ({board_slug}) ===\n{}",
            String::from_utf8_lossy(&output.stderr)
        );
        eprintln!("\n=== rendered BSP files at {} ===", bsp_src_dir.display());
        for name in [
            "mod.rs",
            "pac.rs",
            "clocks.rs",
            "io_mux.rs",
            "peripherals.rs",
            "board.rs",
        ] {
            if let Ok(contents) = fs::read_to_string(bsp_src_dir.join(name)) {
                eprintln!("\n--- {name} ---\n{contents}");
            }
        }
        panic!(
            "generator output for {board_slug} failed to compile against efm32gg11b-pac 0.1.4 on {target}"
        );
    }
}

#[test]
fn slstk3701a_output_compiles_against_real_pac() {
    compile_verify_board("EFM32GG11", "slstk3701a", "slstk3701_a", "slstk3701a");
}
