//! NUVMAP Projection Engine — Rust Port
//!
//! Mirrors `python/nuvmap/projection_engine.py` (SilverSight Q16_16 version).

use crate::q16::*;
use sha2::{Sha256, Digest};
use std::collections::HashMap;

#[derive(Debug, Clone, PartialEq)]
pub struct Cell {
    pub u_i: i32,
    pub v_i: i32,
    pub k_i: i32,
    pub e_i: Q16,
    pub r_i: Q16,
    pub chi_i: Q16,
    pub s_i: Q16,
    pub l_i: Q16,
    pub q_i: i32,
    pub admissible: bool,
    pub equation_id: i32,
    pub fingerprint: String,
}

#[derive(Debug, Clone)]
pub struct Surface {
    pub cells: Vec<Cell>,
    pub total_qubits: i32,
    pub bekenstein_bound: Q16,
    pub area_utilization: Q16,
    pub root_fingerprint: String,
    pub timestamp: String,
}

/// Build input data: maps chiral_state string to int index.
pub fn make_input(eq_id: i32, amvr_q: i32, avmr_q: i32, cr_q: i32, chiral_state: &str) -> HashMap<String, i32> {
    let mut m = HashMap::new();
    m.insert("equation_id".into(), eq_id);
    m.insert("amvr_q16".into(), amvr_q);
    m.insert("avmr_q16".into(), avmr_q);
    m.insert("chiral_residual_q16".into(), cr_q);
    let idx = match chiral_state {
        "achiral_stable" => 0,
        "left_handed_mass_bias" => 1,
        "chiral_scarred" => 2,
        "right_handed_vector_bias" => 3,
        _ => 0,
    };
    m.insert("chiral_state_idx".into(), idx);
    m
}

#[derive(Debug, Clone)]
pub struct Engine {
    pub total_qubit_budget: i32,
    pub chi_max_q16: Q16,
    pub r_max_q16: Q16,
    pub landauer_threshold_q16: Q16,
}

impl Default for Engine {
    fn default() -> Self {
        Self {
            total_qubit_budget: 0,
            chi_max_q16: HALF,
            r_max_q16: HALF,
            landauer_threshold_q16: ONE.saturating_div(10),
        }
    }
}

impl Engine {
    pub fn project(&self, data: &[HashMap<String, i32>]) -> Surface {
        if data.is_empty() {
            return Surface {
                cells: vec![],
                total_qubits: 0,
                bekenstein_bound: ZERO,
                area_utilization: ZERO,
                root_fingerprint: String::new(),
                timestamp: String::new(),
            };
        }

        let mut cells = Vec::with_capacity(data.len());

        // max eigenmass
        let mut max_eigenmass = ZERO;
        for d in data {
            let raw_q = div_op(
                add(d["amvr_q16"], d["avmr_q16"]),
                of_raw(131072),
            );
            if raw_q > max_eigenmass {
                max_eigenmass = raw_q;
            }
        }
        let max_eigenmass = if max_eigenmass == ZERO { ONE } else { max_eigenmass };

        for (i, d) in data.iter().enumerate() {
            let amvr_q = d["amvr_q16"];
            let avmr_q = d["avmr_q16"];
            let cr_q = d["chiral_residual_q16"];
            let eq_id = d["equation_id"];
            let cs_idx = d["chiral_state_idx"];

            let raw_eigenmass = div_op(add(amvr_q, avmr_q), of_raw(131072));
            let e_norm = div_op(raw_eigenmass, max_eigenmass);

            let one_minus = sub(ONE, e_norm);
            let r_i = if one_minus > PCT1 { one_minus } else { PCT1 };

            let r_i = if cs_idx == 2 { mul(r_i, ONE50) } else { r_i };

            let s_i = match cs_idx {
                0 => ONE,
                1 | 3 => PCT70,
                _ => PCT30,
            };

            let l_i = if e_norm > self.landauer_threshold_q16 {
                ONE
            } else {
                div_op(e_norm, self.landauer_threshold_q16)
            };

            let e_i = div_op(
                mul(mul(mul(ONE, e_norm), s_i), l_i),
                add(r_i, EPSILON),
            );

            let chi_i = cr_q;
            let admissible = r_i <= self.r_max_q16 && chi_i <= self.chi_max_q16;

            let fp_payload = format!("{eq_id}\x00{amvr_q}\x00{avmr_q}\x00{cr_q}\x00{cs_idx}");
            let mut hasher = Sha256::new();
            hasher.update(fp_payload.as_bytes());
            let fp = format!("{:x}", hasher.finalize());

            cells.push(Cell {
                u_i: i as i32, v_i: i as i32, k_i: i as i32,
                e_i, r_i, chi_i, s_i, l_i,
                q_i: 0, admissible,
                equation_id: eq_id,
                fingerprint: fp,
            });
        }

        // Qubit allocation
        let mut total_weight = ZERO;
        for c in &cells {
            total_weight = add(total_weight, div_op(c.e_i, add(c.r_i, EPSILON)));
        }
        if total_weight == ZERO { let _ = std::mem::replace(&mut total_weight, ONE); }

        let mut budget: i64 = if self.total_qubit_budget > 0 {
            self.total_qubit_budget as i64
        } else {
            cells.iter()
                .filter(|c| c.admissible)
                .map(|c| (c.e_i as i64 * 100) / Q16_SCALE as i64)
                .sum()
        };
        let n_admissible = cells.iter().filter(|c| c.admissible).count() as i64;
        budget = budget.max(n_admissible);

        for cell in cells.iter_mut() {
            if cell.admissible {
                let weight = div_op(cell.e_i, add(cell.r_i, EPSILON));
                let raw_q = (budget * weight as i64) / total_weight as i64;
                cell.q_i = (1i32).max(raw_q as i32);
            } else {
                cell.q_i = 0;
            }
        }

        let total_qubits: i32 = cells.iter().map(|c| c.q_i).sum();

        let mut sum_e = ZERO;
        for c in &cells { sum_e = add(sum_e, c.e_i); }
        let bekenstein = if !cells.is_empty() {
            div_op(sum_e, of_raw((cells.len() * Q16_SCALE as usize) as i32))
        } else { ZERO };

        let area_util = if bekenstein > ZERO {
            div_op(
                of_raw((total_qubits as i64 * Q16_SCALE as i64) as i32),
                add(bekenstein, EPSILON),
            )
        } else { ZERO };

        let mut sorted = cells.clone();
        sorted.sort_by_key(|c| c.u_i);
        let payload: Vec<String> = sorted.iter().map(|c| {
            format!("{}:{}:{}:{}", c.u_i, c.e_i, c.chi_i, c.q_i)
        }).collect();
        let payload_str = payload.join("|");
        let mut hasher = Sha256::new();
        hasher.update(payload_str.as_bytes());
        let root_fp = format!("{:x}", hasher.finalize());

        Surface {
            cells,
            total_qubits,
            bekenstein_bound: bekenstein,
            area_utilization: area_util,
            root_fingerprint: root_fp,
            timestamp: String::new(),
        }
    }
}
