//! Build script: parses `legacy_contracts.toml` and emits a Rust const
//! array of `(BLAKE3-32, &'static str)` entries that the binary uses
//! for permissionless migration on `git fetch`.
//!
//! See `legacy_contracts.toml` for the format and rationale.

use std::collections::BTreeMap;
use std::env;
use std::fs;
use std::path::PathBuf;

fn main() {
    let manifest_dir =
        PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR set by cargo"));
    let toml_path = manifest_dir.join("legacy_contracts.toml");
    println!("cargo:rerun-if-changed={}", toml_path.display());

    let contents = fs::read_to_string(&toml_path).unwrap_or_default();
    let entries = parse_legacy_entries(&contents);

    let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR set by cargo"));
    let out_path = out_dir.join("legacy_contracts.rs");
    let generated = generate_code(&entries);
    fs::write(&out_path, generated).expect("write generated legacy_contracts.rs");
}

#[derive(Debug, Default)]
struct Entry {
    description: String,
    wasm_hash: [u8; 32],
}

/// Hand-rolled tiny TOML parser for our specific schema. We only need
/// to extract `[[entry]]` blocks with `description` and `wasm_hash`
/// fields. Avoids pulling in a full TOML crate as a build-script dep.
fn parse_legacy_entries(toml: &str) -> Vec<Entry> {
    let mut entries: Vec<Entry> = Vec::new();
    let mut current: Option<BTreeMap<String, String>> = None;

    let flush = |current: &mut Option<BTreeMap<String, String>>, entries: &mut Vec<Entry>| {
        if let Some(fields) = current.take() {
            let description = fields
                .get("description")
                .cloned()
                .unwrap_or_else(|| "(unnamed)".to_string());
            let wasm_hash_hex = fields.get("wasm_hash").cloned().unwrap_or_default();
            if wasm_hash_hex.is_empty() {
                panic!("legacy_contracts.toml: entry '{description}' missing wasm_hash field");
            }
            let mut wasm_hash = [0u8; 32];
            decode_hex_into(&wasm_hash_hex, &mut wasm_hash).unwrap_or_else(|e| {
                panic!("legacy_contracts.toml: bad wasm_hash for '{description}': {e}")
            });
            entries.push(Entry {
                description,
                wasm_hash,
            });
        }
    };

    for raw in toml.lines() {
        let line = raw.trim();
        if line.is_empty() || line.starts_with('#') {
            continue;
        }
        if line == "[[entry]]" {
            flush(&mut current, &mut entries);
            current = Some(BTreeMap::new());
            continue;
        }
        if let Some((key, value)) = line.split_once('=') {
            let key = key.trim();
            let value = value.trim().trim_matches('"').to_string();
            if let Some(map) = current.as_mut() {
                map.insert(key.to_string(), value);
            } else {
                // Top-level key/value pairs aren't part of our schema;
                // skip silently to allow future expansion.
            }
        }
    }
    flush(&mut current, &mut entries);
    entries
}

fn decode_hex_into(hex: &str, out: &mut [u8; 32]) -> Result<(), String> {
    if hex.len() != 64 {
        return Err(format!("expected 64 hex chars, got {}", hex.len()));
    }
    for (i, chunk) in hex.as_bytes().chunks(2).enumerate() {
        let s = std::str::from_utf8(chunk).map_err(|e| e.to_string())?;
        out[i] = u8::from_str_radix(s, 16).map_err(|e| e.to_string())?;
    }
    Ok(())
}

fn generate_code(entries: &[Entry]) -> String {
    let mut s = String::new();
    s.push_str("// Generated by build.rs from legacy_contracts.toml.\n");
    s.push_str("// Do not edit by hand.\n\n");
    s.push_str("/// Prior repo-contract WASM hashes used for permissionless\n");
    s.push_str("/// migration. Each entry is `(BLAKE3-32 of WASM, description)`.\n");
    s.push_str("/// See `legacy_contracts.toml` for context.\n");
    s.push_str("pub const LEGACY_REPO_CONTRACT_WASM_HASHES: &[(&[u8; 32], &str)] = &[\n");
    for entry in entries {
        s.push_str("    (&[");
        for (i, b) in entry.wasm_hash.iter().enumerate() {
            if i > 0 {
                s.push_str(", ");
            }
            s.push_str(&format!("0x{b:02x}"));
        }
        s.push_str("], ");
        s.push('"');
        // No quotes or backslashes in our descriptions, but escape
        // defensively just in case.
        for c in entry.description.chars() {
            match c {
                '"' => s.push_str("\\\""),
                '\\' => s.push_str("\\\\"),
                _ => s.push(c),
            }
        }
        s.push_str("\"),\n");
    }
    s.push_str("];\n");
    s
}
