//! Env-gated property-call profiler (`ENTROPYK_PROF=1`), Story 0.7.
//!
//! Counts CoolProp property calls and cumulative self-time per
//! `(fluid, property, input-pair)` site so component query volume can be
//! reduced from data, not guesses. Zero setup cost when disabled: one atomic
//! `OnceLock` read per call, no allocation.

use crate::types::FluidState;
use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
use std::time::Duration;

static ENABLED: OnceLock<bool> = OnceLock::new();
#[allow(clippy::type_complexity)]
static SITES: OnceLock<Mutex<HashMap<(String, &'static str, &'static str), (u64, Duration)>>> =
    OnceLock::new();

/// True when `ENTROPYK_PROF=1` (resolved once per process).
#[inline]
pub fn enabled() -> bool {
    *ENABLED.get_or_init(|| {
        std::env::var("ENTROPYK_PROF")
            .map(|v| v == "1")
            .unwrap_or(false)
    })
}

/// Records one property call (no-op when profiling is disabled).
#[inline]
pub fn record(fluid: &str, property: &'static str, variant: &'static str, dt: Duration) {
    let Some(mutex) = SITES.get() else { return };
    if let Ok(mut map) = mutex.lock() {
        let entry = map
            .entry((fluid.to_string(), property, variant))
            .or_default();
        entry.0 += 1;
        entry.1 += dt;
    }
}

/// Lazily registers the map (called once from the instrumented site).
#[inline]
pub fn init_if_enabled() {
    if enabled() {
        SITES.get_or_init(|| Mutex::new(HashMap::new()));
    }
}

/// Label for the input-pair variant of a state (profiling key).
pub fn variant_label(state: &FluidState) -> &'static str {
    match state {
        FluidState::PressureTemperature(_, _) => "PT",
        FluidState::PressureEnthalpy(_, _) => "PH",
        FluidState::PressureEntropy(_, _) => "PS",
        FluidState::PressureQuality(_, _) => "PX",
        FluidState::PressureTemperatureMixture(_, _, _) => "PT-mix",
        FluidState::PressureEnthalpyMixture(_, _, _) => "PH-mix",
        FluidState::PressureQualityMixture(_, _, _) => "PX-mix",
    }
}

/// Sorted report (cumulative time desc), top 20 sites, with the grand total.
pub fn report() -> String {
    let Some(mutex) = SITES.get() else {
        return "profiling: no data (ENTROPYK_PROF=1 not set or no calls)".to_string();
    };
    let Ok(map) = mutex.lock() else {
        return "profiling: lock poisoned".to_string();
    };
    let mut rows: Vec<_> = map.iter().collect();
    rows.sort_by_key(|(_, (_, t))| std::cmp::Reverse(*t));
    let total_calls: u64 = rows.iter().map(|(_, (c, _))| c).sum();
    let total_time: Duration = rows.iter().map(|(_, (_, t))| *t).sum();
    let mut out = format!(
        "property-call profile: {total_calls} calls, {:.1} ms total\n",
        total_time.as_secs_f64() * 1e3
    );
    for ((fluid, prop, variant), (count, time)) in rows.into_iter().take(20) {
        out.push_str(&format!(
            "  {:>7} calls {:>9.2} ms  {} {} [{}]\n",
            count,
            time.as_secs_f64() * 1e3,
            fluid,
            prop,
            variant
        ));
    }
    out
}
