//! Leak Tracker - Exit-time diagnostics for strong references still held
//!
//! Phase 285: Extended to report all global roots (modules, host_handles, plugin boxes).
//! Phase 29y.1: Added root category summary (SSOT: 30-OBSERVABILITY-SSOT.md)
//!
//! ## Environment Variable
//!
//! - `NYASH_LEAK_LOG=1` - Summary counts only (with category breakdown)
//! - `NYASH_LEAK_LOG=2` - Verbose (include names/entries, truncated to first 10)
//!
//! ## Output Format
//!
//! ```text
//! [lifecycle/leak] Roots still held at exit:
//! [lifecycle/leak]   modules: 3
//! [lifecycle/leak]   host_handles: 5
//! [lifecycle/leak]   plugin_boxes: 2
//! [lifecycle/leak] Root categories:
//! [lifecycle/leak]   handles: 8
//! [lifecycle/leak]   (Phase 1 limitation: locals/temps/heap_fields/singletons=0)
//! ```

use crate::runtime::get_global_ring0;
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::Mutex;

/// Leak log level: 0 = off, 1 = summary, 2 = verbose
static LEVEL: Lazy<u8> = Lazy::new(crate::config::env::leak_log_level);

/// Backward compatibility: enabled if level >= 1
static ENABLED: Lazy<bool> = Lazy::new(|| *LEVEL >= 1);

static LEAKS: Lazy<Mutex<HashMap<(String, u32), &'static str>>> =
    Lazy::new(|| Mutex::new(HashMap::new()));

/// Phase 29y.1: Root category summary for observability
///
/// Categories are defined in docs/development/current/main/phases/phase-29y/30-OBSERVABILITY-SSOT.md
#[derive(Debug, Default, Clone)]
pub struct RootSummary {
    /// host-visible registry/handle table
    pub handles: usize,
    /// runtime singleton/globals (Phase 1: always 0)
    pub singletons: usize,
    /// object strong-owned fields (Phase 1: always 0 - VM teardown)
    pub heap_fields: usize,
    /// temporary values/VM registers (Phase 1: always 0 - VM teardown)
    pub temps: usize,
    /// local variable bindings (Phase 1: always 0 - VM teardown)
    pub locals: usize,
}

/// Phase 29y.1: Collect root summary from available sources
///
/// Phase 1 limitation: Only handles category is observable at exit time.
/// locals/temps/heap_fields/singletons require VM state which is unavailable after teardown.
fn collect_root_summary() -> RootSummary {
    let host_handles = crate::runtime::host_handles::snapshot();
    let modules = crate::runtime::modules_registry::snapshot_names_and_strings();

    RootSummary {
        handles: host_handles.len() + modules.len(),
        singletons: 0,  // Phase 1 limitation: VM state not available
        heap_fields: 0, // Phase 1 limitation: VM state not available
        temps: 0,       // Phase 1 limitation: VM state not available
        locals: 0,      // Phase 1 limitation: VM state not available
    }
}

pub fn init() {
    let _ = &*REPORTER;
}

pub fn register_plugin(box_type: &str, instance_id: u32) {
    if !*ENABLED {
        return;
    }
    LEAKS
        .lock()
        .unwrap()
        .insert((box_type.to_string(), instance_id), "plugin");
}

pub fn finalize_plugin(box_type: &str, instance_id: u32) {
    if !*ENABLED {
        return;
    }
    LEAKS
        .lock()
        .unwrap()
        .remove(&(box_type.to_string(), instance_id));
}

struct Reporter;
impl Drop for Reporter {
    fn drop(&mut self) {
        if !*ENABLED {
            return;
        }
        emit_leak_report();
    }
}

static REPORTER: Lazy<Reporter> = Lazy::new(|| Reporter);

/// Emit exit-time leak report (Phase 285)
///
/// Called automatically on program exit via Reporter::drop.
/// Can also be called manually for testing.
pub fn emit_leak_report() {
    let level = *LEVEL;
    if level == 0 {
        return;
    }

    let ring0 = get_global_ring0();

    // Collect root counts
    let modules = crate::runtime::modules_registry::snapshot_names_and_strings();
    let host_handles = crate::runtime::host_handles::snapshot();
    let plugin_boxes = LEAKS.lock().map(|m| m.len()).unwrap_or(0);

    let modules_count = modules.len();
    let host_handles_count = host_handles.len();

    // Only print if there's something to report
    if modules_count == 0 && host_handles_count == 0 && plugin_boxes == 0 {
        return;
    }

    // Summary header
    ring0
        .log
        .warn("[lifecycle/leak] Roots still held at exit:");

    // Summary counts
    if modules_count > 0 {
        ring0
            .log
            .warn(&format!("[lifecycle/leak]   modules: {}", modules_count));
    }
    if host_handles_count > 0 {
        ring0.log.warn(&format!(
            "[lifecycle/leak]   host_handles: {}",
            host_handles_count
        ));
    }
    if plugin_boxes > 0 {
        ring0
            .log
            .warn(&format!("[lifecycle/leak]   plugin_boxes: {}", plugin_boxes));
    }

    // Phase 29y.1: Root category summary
    let summary = collect_root_summary();
    ring0.log.warn("[lifecycle/leak] Root categories:");
    ring0.log.warn(&format!("[lifecycle/leak]   handles: {}", summary.handles));
    ring0.log.warn("[lifecycle/leak]   (Phase 1 limitation: locals/temps/heap_fields/singletons=0)");

    // Verbose details (level 2)
    if level >= 2 {
        const MAX_ENTRIES: usize = 10;

        // Module names
        if !modules.is_empty() {
            ring0.log.warn("[lifecycle/leak]   module names:");
            for (i, (name, _value)) in modules.iter().take(MAX_ENTRIES).enumerate() {
                ring0
                    .log
                    .warn(&format!("[lifecycle/leak]     [{}] {}", i, name));
            }
            if modules.len() > MAX_ENTRIES {
                ring0.log.warn(&format!(
                    "[lifecycle/leak]     ... and {} more",
                    modules.len() - MAX_ENTRIES
                ));
            }
        }

        // Plugin box details
        if plugin_boxes > 0 {
            ring0.log.warn("[lifecycle/leak]   plugin box details:");
            if let Ok(m) = LEAKS.lock() {
                for (i, ((ty, id), _)) in m.iter().take(MAX_ENTRIES).enumerate() {
                    ring0.log.warn(&format!(
                        "[lifecycle/leak]     [{}] {}(id={}) not finalized",
                        i, ty, id
                    ));
                }
                if m.len() > MAX_ENTRIES {
                    ring0.log.warn(&format!(
                        "[lifecycle/leak]     ... and {} more",
                        m.len() - MAX_ENTRIES
                    ));
                }
            }
        }
    }
}
