// Equation Ingestion Pipeline — Rust CLI
// Markdown → Parse → Classify → Compute → Emit
// Usage: cargo run --bin ingest -- equations.md
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use regex::Regex;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct ParsedEquation {
    text: String,
    line: usize,
    is_block: bool,
    classification: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    spectral_fingerprint: Option<SpectralFingerprint>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct SpectralFingerprint {
    sigma: f64,
    tau: f64,
    delta: f64,
    lambda_min: i32,
    lambda_max: i32,
    denominator: i32,
    regime: String,
    gap_numerator: i32,
}

#[derive(Serialize, Deserialize)]
struct DensityReport {
    total_equations: usize,
    classified: usize,
    unknown: usize,
    max_allowed: usize,
    should_pause: bool,
    reason: String,
    shannon_entropy: f64,
    landauer_bits: f64,
    landauer_energy_ev: f64,
    rgflow_bound: f64,
    density_level: String,
}

#[derive(Serialize, Deserialize)]
struct IngestionReceipt {
    schema: String,
    source_file: String,
    total_parsed: usize,
    total_processed: usize,
    density: DensityReport,
    results: Vec<ParsedEquation>,
}

// ── Chiral weight table (integer rational pairs, zero floats) ─────
const CHIRAL_WEIGHTS: [(i32, i32); 4] = [(1,1), (1,2), (3,2), (3,2)]; // A,S,L,R
const CHIRAL_NAMES: [&str; 4] = ["A", "S", "L", "R"];

fn parse_markdown(text: &str) -> Vec<ParsedEquation> {
    let mut equations = Vec::new();
    let block_re = Regex::new(r"\$\$(.+?)\$\$").unwrap();
    let inline_re = Regex::new(r"\$(.+?)\$").unwrap();

    for (i, line) in text.lines().enumerate() {
        for cap in block_re.captures_iter(line) {
            let eq = cap[1].trim().to_string();
            if !eq.is_empty() {
                equations.push(ParsedEquation {
                    text: eq, line: i + 1, is_block: true,
                    classification: String::new(), spectral_fingerprint: None,
                });
            }
        }
        for cap in inline_re.captures_iter(line) {
            let eq = cap[1].trim().to_string();
            if !eq.is_empty() && !line.contains(&format!("$${}$$", eq)) {
                equations.push(ParsedEquation {
                    text: eq, line: i + 1, is_block: false,
                    classification: String::new(), spectral_fingerprint: None,
                });
            }
        }
    }
    equations
}

fn classify_equation(eq: &mut ParsedEquation) {
    let text = eq.text.to_lowercase();

    let spectral_kw = ["sigma", "tau", "delta", "lambda", "spectral", "eigenvalue",
        "gap", "radius", "threshold", "1792", "256", "273", "28",
        "cartan", "sidon", "chiral", "rossby", "kelvin", "braid"];
    let braid_kw = ["braid", "strand", "cross", "sidon", "eigensolid", "yang-baxter"];
    let cartan_kw = ["cartan", "weight", "matrix", "diagonal", "adjacent", "block"];
    
    let mut scores = HashMap::new();
    scores.insert("spectral", 0usize);
    scores.insert("braid", 0usize);
    scores.insert("cartan", 0usize);

    for kw in &spectral_kw {
        if text.contains(kw) { *scores.get_mut("spectral").unwrap() += 1; }
    }
    for kw in &braid_kw {
        if text.contains(kw) { *scores.get_mut("braid").unwrap() += 1; }
    }
    for kw in &cartan_kw {
        if text.contains(kw) { *scores.get_mut("cartan").unwrap() += 1; }
    }

    let best = scores.iter().max_by_key(|&(_, v)| v).unwrap();
    eq.classification = if *best.1 > 0 { best.0.to_string() } else { "unknown".to_string() };
}

// ── Spectral computation (integer only) ──────────────────────────

fn compute_spectral(chiral: &str) -> SpectralFingerprint {
    // chiral is 8 chars of A/S/L/R
    let names: Vec<char> = chiral.chars().collect();
    let pairs = [(0,1), (2,3), (4,5), (6,7)];
    let mut all_lo = Vec::new();
    let mut all_hi = Vec::new();

    for (a, b) in pairs {
        let ai = CHIRAL_NAMES.iter().position(|&c| c == names[a].to_string()).unwrap_or(0);
        let bi = CHIRAL_NAMES.iter().position(|&c| c == names[b].to_string()).unwrap_or(0);
        let (an, ad) = CHIRAL_WEIGHTS[ai];
        let (bn, bd) = CHIRAL_WEIGHTS[bi];
        // w = 128 × (num_a*den_b + num_b*den_a) / (den_a * den_b)
        let num = 128 * (an * bd + bn * ad);
        let den = ad * bd;
        let w = num / den;

        all_lo.push(273 + w);
        all_hi.push(273 - w);
    }

    let lam_max = *all_lo.iter().max().unwrap();
    let lam_min = *all_hi.iter().min().unwrap();
    let d = 1792;

    let sigma = 273.0 / d as f64;
    let w_avg: i32 = all_lo.iter().sum::<i32>() / all_lo.len() as i32 - 273;
    let tau = w_avg as f64 / d as f64;
    let delta = sigma - tau;

    let regime = if lam_min < 0 { "ROSSBY" } else if lam_min == 17 { "CANONICAL" } else { "SCARRED" };

    SpectralFingerprint {
        sigma, tau, delta, lambda_min: lam_min, lambda_max: lam_max,
        denominator: d, regime: regime.to_string(), gap_numerator: lam_min,
    }
}

fn check_density(eqs: &[ParsedEquation], max_eqns: usize, max_unknown: usize) -> DensityReport {
    let total = eqs.len();
    let unknown = eqs.iter().filter(|e| e.classification == "unknown").count();

    // ── Shannon Entropy: H = -Σ p(x) log₂ p(x) ──────────────────
    // Entropy over the equation text symbol distribution.
    // Higher H = more informational diversity = denser content.
    let full_text: String = eqs.iter().map(|e| &e.text).fold(String::new(), |a, b| a + b);
    let mut freq = [0usize; 256];
    for b in full_text.bytes() { freq[b as usize] += 1; }
    let total_bytes = full_text.len().max(1) as f64;
    let shannon: f64 = freq.iter()
        .filter(|&&c| c > 0)
        .map(|&c| { let p = c as f64 / total_bytes; -p * p.log2() })
        .sum();
    
    // ── Landauer Cost: E = kT·ln(2) × unique_terms ──────────────
    // Landauer (1961): erasing N bits costs N·kT·ln(2) energy.
    // Each distinct mathematical symbol/operator is a "bit" of
    // computational state that must be erased.
    // kT ≈ 0.025 eV at room temp → Landauer energy per bit.
    let kT_eV = 0.025;  // eV at 290K
    let ln2 = 0.693147;
    let landauer_per_bit = kT_eV * ln2;  // ≈ 0.017 eV/bit

    // Count distinct mathematical symbols/tokens
    let math_tokens = Regex::new(r"[\\a-z]+|\d+|[+\-*/=<>\[\]{}()∫∑∏∂∇√∞π]").unwrap();
    let mut terms = std::collections::HashSet::new();
    for eq in eqs {
        for m in math_tokens.find_iter(&eq.text) {
            terms.insert(m.as_str().to_string());
        }
    }
    let landauer_bits = terms.len() as f64;
    let landauer_energy = landauer_bits * landauer_per_bit;

    // ── RGFlow Entropy Bound ──────────────────────────────────────
    // Renormalization group flow: entropy decreases monotonically
    // under coarse-graining. The RG fixed-point occurs at H = ln(2⁸) ≈ 5.545
    // for the 8-strand system (8 bits of address space).
    // Equation sets exceeding this entropy need RG coarse-graining
    // (information compression) before they can be reliably classified.
    let rgflow_bound = (8.0_f64).ln();  // ln(2⁸) ≈ 5.545

    // ── Decision ──────────────────────────────────────────────────
    let exceeds_shannon = shannon > rgflow_bound;
    let exceeds_landauer = landauer_bits > (max_eqns as f64 * 3.0);
    let exceeds_count = total > max_eqns || unknown > max_unknown;
    let should_pause = exceeds_shannon || exceeds_landauer || exceeds_count;

    let density_level = if exceeds_shannon && exceeds_landauer {
        "THERMODYNAMIC_SATURATION"
    } else if exceeds_shannon {
        "SHANNON_SATURATED"
    } else if exceeds_landauer {
        "LANDAUER_EXPENSIVE"
    } else if exceeds_count {
        "COUNT_EXCEEDED"
    } else {
        "PROCESSABLE"
    };

    DensityReport {
        total_equations: total,
        classified: total - unknown,
        unknown,
        max_allowed: max_eqns,
        should_pause,
        reason: if exceeds_shannon {
            format!("Shannon entropy {:.2} > rgflow bound {:.2} — information-saturated", shannon, rgflow_bound)
        } else if exceeds_landauer {
            format!("Landauer cost {:.0} bits > threshold — too expensive to auto-process", landauer_bits)
        } else if exceeds_count {
            if total > max_eqns { format!("too many equations ({} > {})", total, max_eqns) }
            else { format!("too many unknowns ({} > {})", unknown, max_unknown) }
        } else {
            "proceed".to_string()
        },
        shannon_entropy: (shannon * 100.0).round() / 100.0,
        landauer_bits,
        landauer_energy_ev: (landauer_energy * 1000.0).round() / 1000.0,
        rgflow_bound: (rgflow_bound * 100.0).round() / 100.0,
        density_level: density_level.to_string(),
    }
}

fn write_decision_doc(input: &PathBuf, eqs: &[ParsedEquation], density: &DensityReport) -> PathBuf {
    let out = input.with_extension("decision.md");
    let mut lines = vec![
        format!("# Equation Pipeline — Decision Required\n"),
        format!("**File:** `{}`", input.display()),
        format!("**Date:** auto-generated\n"),
        format!("## Density Report\n"),
        format!("| Metric | Value |"),
        format!("|--------|-------|"),
        format!("| Total equations | {} |", density.total_equations),
        format!("| Classified | {} |", density.classified),
        format!("| Unknown | {} |", density.unknown),
        format!("| Max auto-process | {} |\n", density.max_allowed),
        format!("**Reason for pause:** {}\n", density.reason),
        format!("## Detected Equations\n"),
        format!("| Line | Type | Classification | Equation |"),
        format!("|------|------|---------------|----------|"),
    ];

    for eq in eqs {
        let preview: String = eq.text.chars().take(60).collect();
        let more = if eq.text.len() > 60 { "..." } else { "" };
        lines.push(format!("| {} | {} | {} | `{}{}` |",
            eq.line,
            if eq.is_block { "block" } else { "inline" },
            eq.classification,
            preview, more));
    }

    lines.push(format!("\nRun with `--force` to process anyway."));
    fs::write(&out, lines.join("\n")).unwrap();
    out
}

// ── Main ──────────────────────────────────────────────────────────

fn main() {
    let args: Vec<String> = std::env::args().collect();
    if args.len() < 2 {
        eprintln!("Usage: {} <equations.md> [--force] [--max-eqns N]", args[0]);
        std::process::exit(1);
    }

    let input = PathBuf::from(&args[1]);
    let force = args.contains(&"--force".to_string());
    let classified_only = args.contains(&"--classified-only".to_string());
    let max_eqns = args.iter().position(|a| a == "--max-eqns")
        .and_then(|i| args.get(i+1))
        .and_then(|s| s.parse().ok())
        .unwrap_or(20);

    let text = fs::read_to_string(&input).unwrap_or_else(|e| {
        eprintln!("Error reading {}: {}", input.display(), e);
        std::process::exit(1);
    });

    let mut equations = parse_markdown(&text);
    println!("Parsed {} equations from {}", equations.len(), input.display());

    for eq in &mut equations { classify_equation(eq); }

    let spectral = equations.iter().filter(|e| e.classification == "spectral").count();
    let braid = equations.iter().filter(|e| e.classification == "braid").count();
    let cartan = equations.iter().filter(|e| e.classification == "cartan").count();
    let unknown = equations.iter().filter(|e| e.classification == "unknown").count();
    println!("  Spectral: {}, Braid: {}, Cartan: {}, Unknown: {}", spectral, braid, cartan, unknown);

    let density = check_density(&equations, max_eqns, 5);
    if density.should_pause && !force {
        let doc = write_decision_doc(&input, &equations, &density);
        println!("\n⚠️  Too dense — wrote decision document:");
        println!("   {}", doc.display());
        println!("   Review and re-run with --force to proceed.");
        return;
    }

    if classified_only {
        equations.retain(|e| e.classification != "unknown");
        println!("Processing {} classified equations", equations.len());
    }

    // Compute spectral fingerprints
    for eq in &mut equations {
        if eq.classification == "unknown" { continue; }

        let chiral = if eq.text.to_lowercase().contains("rossby") || 
                         eq.text.to_lowercase().contains("chiral") {
            "LLLLLLLL"
        } else if eq.text.to_lowercase().contains("scarred") {
            "SSSSSSSS"
        } else {
            "AAAAAAAA"
        };

        eq.spectral_fingerprint = Some(compute_spectral(chiral));
    }

    let processed = equations.iter().filter(|e| e.spectral_fingerprint.is_some()).count();
    let total = equations.len();
    let receipt = IngestionReceipt {
        schema: "equation_ingestion_v1".to_string(),
        source_file: input.display().to_string(),
        total_parsed: total,
        total_processed: processed,
        density,
        results: equations,
    };

    let out = input.with_extension("receipt.json");
    fs::write(&out, serde_json::to_string_pretty(&receipt).unwrap()).unwrap();
    println!("\n✅ Pipeline complete");
    println!("   Processed: {}/{} equations", processed, total);
    println!("   Receipt: {}", out.display());

    let computed: Vec<_> = receipt.results.iter().filter(|e| e.spectral_fingerprint.is_some()).collect();
    if !computed.is_empty() {
        for eq in computed {
            let fp = eq.spectral_fingerprint.as_ref().unwrap();
            println!("   [{}] λ=[{}, {}] σ={:.6} τ={:.6} ∆={:.6} {}",
                eq.classification, fp.lambda_min, fp.lambda_max, 
                fp.sigma, fp.tau, fp.delta, fp.regime);
        }
    }
}
