//! Build script for soliton.
//!
//! Embeds the EFF large wordlist for verification phrase generation.
//! The wordlist is checked into the repo at `src/eff_large_wordlist.txt`
//! and SHA-256 verified at build time.
//!
//! All cryptographic dependencies are pure Rust — no C compilation or linking needed.

use sha2::{Digest, Sha256};
use std::env;
use std::fs;
use std::io::Write;
use std::path::PathBuf;

/// SHA-256 hash of the canonical EFF large wordlist (7776 lines).
/// <https://www.eff.org/files/2016/07/18/eff_large_wordlist.txt>
const EFF_WORDLIST_SHA256: &str =
    "addd35536511597a02fa0a9ff1e5284677b8883b83e986e43f15a3db996b903e";

fn main() {
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=src/eff_large_wordlist.txt");

    generate_wordlist();
}

/// Read the EFF large wordlist from the in-repo cached copy and generate
/// a Rust source file with the words as a static array. The file is
/// written to OUT_DIR and included via `include!` in the verification
/// module.
///
/// The EFF large wordlist contains 7776 words (6^5), one per line, prefixed
/// with a dice roll number and a tab. We strip the prefix and keep only the
/// words.
fn generate_wordlist() {
    // Cargo guarantees OUT_DIR and CARGO_MANIFEST_DIR for build scripts.
    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
    let wordlist_rs = out_dir.join("eff_wordlist.rs");
    let cache_path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
        .join("src")
        .join("eff_large_wordlist.txt");

    // Skip regeneration if the output already exists AND the cached source
    // passes SHA-256 verification. Safe because the output is deterministically
    // generated from the source — same input always produces identical output.
    // The SHA-256 check protects against tampered build caches.
    if wordlist_rs.exists() {
        if let Ok(cached) = fs::read_to_string(&cache_path) {
            verify_wordlist_hash(&cached);
            return;
        }
    }

    let raw = fs::read_to_string(&cache_path)
        .expect("EFF wordlist not available: src/eff_large_wordlist.txt missing from repo");

    verify_wordlist_hash(&raw);

    // Parse: each line is "DDDDD\tword\n" (5-digit dice roll, tab, word).
    let words: Vec<&str> = raw
        .lines()
        .filter_map(|line| {
            let line = line.trim();
            if line.is_empty() {
                return None;
            }
            line.split('\t').nth(1)
        })
        .collect();

    // Panic: build scripts communicate errors by panicking. A count mismatch
    // indicates a corrupted or non-canonical wordlist that passed SHA-256
    // (hash collision or wrong file).
    assert_eq!(
        words.len(),
        7776,
        "EFF wordlist should have 7776 entries, got {}",
        words.len()
    );

    // Panic on I/O failure: build scripts have no recovery path — Cargo
    // treats a panic as a build error and reports the message to the user.
    let mut out = fs::File::create(&wordlist_rs).unwrap();
    writeln!(out, "/// EFF large wordlist (7776 words).").unwrap();
    writeln!(
        out,
        "/// Source: <https://www.eff.org/dice>, checked into repo at src/eff_large_wordlist.txt."
    )
    .unwrap();
    writeln!(out, "pub(crate) static EFF_WORDLIST: [&str; 7776] = [").unwrap();
    for word in &words {
        writeln!(out, "    \"{word}\",").unwrap();
    }
    writeln!(out, "];").unwrap();
}

/// Verify the SHA-256 hash of the EFF wordlist content.
///
/// # Security
///
/// Panics with a clear message if the hash doesn't match, preventing
/// use of a tampered or corrupted wordlist in verification phrases.
/// This is the sole integrity gate for the in-repo wordlist.
fn verify_wordlist_hash(content: &str) {
    let computed = format!("{:x}", Sha256::digest(content.as_bytes()));

    assert_eq!(
        computed, EFF_WORDLIST_SHA256,
        "EFF wordlist SHA-256 mismatch!\n  expected: {}\n  computed: {}\n\
         The wordlist may be corrupted or tampered with.",
        EFF_WORDLIST_SHA256, computed
    );
}
