//! WiFi-Mat WASM bindings for browser-based disaster response dashboard.
//!
//! This module provides JavaScript-callable functions for:
//! - Creating and managing disaster events
//! - Adding/removing scan zones with canvas coordinates
//! - Getting survivor list with positions
//! - Subscribing to real-time updates via callbacks
//!
//! # Example Usage (JavaScript)
//!
//! ```javascript
//! import init, { MatDashboard } from './wifi_densepose_wasm.js';
//!
//! async function main() {
//!     await init();
//!
//!     const dashboard = MatDashboard.new();
//!
//!     // Create a disaster event
//!     const eventId = dashboard.createEvent('earthquake', 37.7749, -122.4194, 'Bay Area Earthquake');
//!
//!     // Add scan zones
//!     dashboard.addRectangleZone('Zone A', 0, 0, 100, 80);
//!     dashboard.addCircleZone('Zone B', 200, 150, 50);
//!
//!     // Subscribe to updates
//!     dashboard.onSurvivorDetected((survivor) => {
//!         console.log('Survivor detected:', survivor);
//!     });
//!
//!     dashboard.onAlertGenerated((alert) => {
//!         console.log('Alert:', alert);
//!     });
//! }
//! ```

use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use uuid::Uuid;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;

// ============================================================================
// TypeScript Type Definitions (exported via JSDoc-style comments)
// ============================================================================

/// JavaScript-friendly disaster type enumeration
#[wasm_bindgen]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum JsDisasterType {
    BuildingCollapse = 0,
    Earthquake = 1,
    Landslide = 2,
    Avalanche = 3,
    Flood = 4,
    MineCollapse = 5,
    Industrial = 6,
    TunnelCollapse = 7,
    Unknown = 8,
}

impl Default for JsDisasterType {
    fn default() -> Self {
        Self::Unknown
    }
}

/// JavaScript-friendly triage status
#[wasm_bindgen]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum JsTriageStatus {
    /// Immediate (Red) - Life-threatening
    Immediate = 0,
    /// Delayed (Yellow) - Serious but stable
    Delayed = 1,
    /// Minor (Green) - Walking wounded
    Minor = 2,
    /// Deceased (Black)
    Deceased = 3,
    /// Unknown
    Unknown = 4,
}

impl JsTriageStatus {
    /// Get the CSS color for this triage status
    pub fn color(&self) -> &'static str {
        match self {
            JsTriageStatus::Immediate => "#ff0000",
            JsTriageStatus::Delayed => "#ffcc00",
            JsTriageStatus::Minor => "#00cc00",
            JsTriageStatus::Deceased => "#333333",
            JsTriageStatus::Unknown => "#999999",
        }
    }

    /// Get priority (1 = highest)
    pub fn priority(&self) -> u8 {
        match self {
            JsTriageStatus::Immediate => 1,
            JsTriageStatus::Delayed => 2,
            JsTriageStatus::Minor => 3,
            JsTriageStatus::Deceased => 4,
            JsTriageStatus::Unknown => 5,
        }
    }
}

/// JavaScript-friendly zone status
#[wasm_bindgen]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum JsZoneStatus {
    Active = 0,
    Paused = 1,
    Complete = 2,
    Inaccessible = 3,
}

/// JavaScript-friendly alert priority
#[wasm_bindgen]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum JsAlertPriority {
    Critical = 0,
    High = 1,
    Medium = 2,
    Low = 3,
}

// ============================================================================
// JavaScript-Compatible Data Structures
// ============================================================================

/// Survivor data for JavaScript consumption
#[derive(Debug, Clone, Serialize, Deserialize)]
#[wasm_bindgen(getter_with_clone)]
pub struct JsSurvivor {
    /// Unique identifier
    pub id: String,
    /// Zone ID where detected
    pub zone_id: String,
    /// X position on canvas (pixels)
    pub x: f64,
    /// Y position on canvas (pixels)
    pub y: f64,
    /// Estimated depth in meters (negative = buried)
    pub depth: f64,
    /// Triage status (0=Immediate, 1=Delayed, 2=Minor, 3=Deceased, 4=Unknown)
    pub triage_status: u8,
    /// Triage color for rendering
    pub triage_color: String,
    /// Detection confidence (0.0-1.0)
    pub confidence: f64,
    /// Breathing rate (breaths per minute), -1 if not detected
    pub breathing_rate: f64,
    /// Heart rate (beats per minute), -1 if not detected
    pub heart_rate: f64,
    /// First detection timestamp (ISO 8601)
    pub first_detected: String,
    /// Last update timestamp (ISO 8601)
    pub last_updated: String,
    /// Whether survivor is deteriorating
    pub is_deteriorating: bool,
}

#[wasm_bindgen]
impl JsSurvivor {
    /// Get triage status as enum
    #[wasm_bindgen(getter)]
    pub fn triage(&self) -> JsTriageStatus {
        match self.triage_status {
            0 => JsTriageStatus::Immediate,
            1 => JsTriageStatus::Delayed,
            2 => JsTriageStatus::Minor,
            3 => JsTriageStatus::Deceased,
            _ => JsTriageStatus::Unknown,
        }
    }

    /// Check if survivor needs urgent attention
    #[wasm_bindgen]
    pub fn is_urgent(&self) -> bool {
        self.triage_status <= 1
    }
}

/// Scan zone data for JavaScript consumption
#[derive(Debug, Clone, Serialize, Deserialize)]
#[wasm_bindgen(getter_with_clone)]
pub struct JsScanZone {
    /// Unique identifier
    pub id: String,
    /// Human-readable name
    pub name: String,
    /// Zone type: "rectangle", "circle", "polygon"
    pub zone_type: String,
    /// Status (0=Active, 1=Paused, 2=Complete, 3=Inaccessible)
    pub status: u8,
    /// Number of scans completed
    pub scan_count: u32,
    /// Number of detections in this zone
    pub detection_count: u32,
    /// Zone bounds as JSON string
    pub bounds_json: String,
}

/// Alert data for JavaScript consumption
#[derive(Debug, Clone, Serialize, Deserialize)]
#[wasm_bindgen(getter_with_clone)]
pub struct JsAlert {
    /// Unique identifier
    pub id: String,
    /// Related survivor ID
    pub survivor_id: String,
    /// Priority (0=Critical, 1=High, 2=Medium, 3=Low)
    pub priority: u8,
    /// Alert title
    pub title: String,
    /// Alert message
    pub message: String,
    /// Recommended action
    pub recommended_action: String,
    /// Triage status of survivor
    pub triage_status: u8,
    /// Location X (canvas pixels)
    pub location_x: f64,
    /// Location Y (canvas pixels)
    pub location_y: f64,
    /// Creation timestamp (ISO 8601)
    pub created_at: String,
    /// Priority color for rendering
    pub priority_color: String,
}

/// Dashboard statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
#[wasm_bindgen(getter_with_clone)]
pub struct JsDashboardStats {
    /// Total survivors detected
    pub total_survivors: u32,
    /// Immediate (red) count
    pub immediate_count: u32,
    /// Delayed (yellow) count
    pub delayed_count: u32,
    /// Minor (green) count
    pub minor_count: u32,
    /// Deceased (black) count
    pub deceased_count: u32,
    /// Unknown count
    pub unknown_count: u32,
    /// Total active zones
    pub active_zones: u32,
    /// Total scans performed
    pub total_scans: u32,
    /// Active alerts count
    pub active_alerts: u32,
    /// Event elapsed time in seconds
    pub elapsed_seconds: f64,
}

// ============================================================================
// Internal State Management
// ============================================================================

/// Internal survivor state
#[derive(Debug, Clone)]
struct InternalSurvivor {
    id: Uuid,
    zone_id: Uuid,
    x: f64,
    y: f64,
    depth: f64,
    triage_status: JsTriageStatus,
    confidence: f64,
    breathing_rate: Option<f64>,
    heart_rate: Option<f64>,
    first_detected: chrono::DateTime<chrono::Utc>,
    last_updated: chrono::DateTime<chrono::Utc>,
    is_deteriorating: bool,
    alert_sent: bool,
}

impl InternalSurvivor {
    fn to_js(&self) -> JsSurvivor {
        JsSurvivor {
            id: self.id.to_string(),
            zone_id: self.zone_id.to_string(),
            x: self.x,
            y: self.y,
            depth: self.depth,
            triage_status: self.triage_status as u8,
            triage_color: self.triage_status.color().to_string(),
            confidence: self.confidence,
            breathing_rate: self.breathing_rate.unwrap_or(-1.0),
            heart_rate: self.heart_rate.unwrap_or(-1.0),
            first_detected: self.first_detected.to_rfc3339(),
            last_updated: self.last_updated.to_rfc3339(),
            is_deteriorating: self.is_deteriorating,
        }
    }
}

/// Zone bounds variants
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
enum ZoneBounds {
    Rectangle {
        x: f64,
        y: f64,
        width: f64,
        height: f64,
    },
    Circle {
        center_x: f64,
        center_y: f64,
        radius: f64,
    },
    Polygon {
        vertices: Vec<(f64, f64)>,
    },
}

/// Internal zone state
#[derive(Debug, Clone)]
struct InternalZone {
    id: Uuid,
    name: String,
    bounds: ZoneBounds,
    status: JsZoneStatus,
    scan_count: u32,
    detection_count: u32,
}

impl InternalZone {
    fn to_js(&self) -> JsScanZone {
        let zone_type = match &self.bounds {
            ZoneBounds::Rectangle { .. } => "rectangle",
            ZoneBounds::Circle { .. } => "circle",
            ZoneBounds::Polygon { .. } => "polygon",
        };

        JsScanZone {
            id: self.id.to_string(),
            name: self.name.clone(),
            zone_type: zone_type.to_string(),
            status: self.status as u8,
            scan_count: self.scan_count,
            detection_count: self.detection_count,
            bounds_json: serde_json::to_string(&self.bounds).unwrap_or_default(),
        }
    }

    fn contains_point(&self, x: f64, y: f64) -> bool {
        match &self.bounds {
            ZoneBounds::Rectangle {
                x: rx,
                y: ry,
                width,
                height,
            } => x >= *rx && x <= rx + width && y >= *ry && y <= ry + height,
            ZoneBounds::Circle {
                center_x,
                center_y,
                radius,
            } => {
                let dx = x - center_x;
                let dy = y - center_y;
                (dx * dx + dy * dy).sqrt() <= *radius
            }
            ZoneBounds::Polygon { vertices } => {
                if vertices.len() < 3 {
                    return false;
                }
                // Ray casting algorithm
                let mut inside = false;
                let n = vertices.len();
                let mut j = n - 1;
                for i in 0..n {
                    let (xi, yi) = vertices[i];
                    let (xj, yj) = vertices[j];
                    if ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
                        inside = !inside;
                    }
                    j = i;
                }
                inside
            }
        }
    }
}

/// Internal alert state
#[derive(Debug, Clone)]
struct InternalAlert {
    id: Uuid,
    survivor_id: Uuid,
    priority: JsAlertPriority,
    title: String,
    message: String,
    recommended_action: String,
    triage_status: JsTriageStatus,
    location_x: f64,
    location_y: f64,
    created_at: chrono::DateTime<chrono::Utc>,
    acknowledged: bool,
}

impl InternalAlert {
    fn to_js(&self) -> JsAlert {
        let priority_color = match self.priority {
            JsAlertPriority::Critical => "#ff0000",
            JsAlertPriority::High => "#ff6600",
            JsAlertPriority::Medium => "#ffcc00",
            JsAlertPriority::Low => "#0066ff",
        };

        JsAlert {
            id: self.id.to_string(),
            survivor_id: self.survivor_id.to_string(),
            priority: self.priority as u8,
            title: self.title.clone(),
            message: self.message.clone(),
            recommended_action: self.recommended_action.clone(),
            triage_status: self.triage_status as u8,
            location_x: self.location_x,
            location_y: self.location_y,
            created_at: self.created_at.to_rfc3339(),
            priority_color: priority_color.to_string(),
        }
    }
}

/// Dashboard internal state
struct DashboardState {
    event_id: Option<Uuid>,
    disaster_type: JsDisasterType,
    event_start: Option<chrono::DateTime<chrono::Utc>>,
    location: (f64, f64),
    description: String,
    zones: HashMap<Uuid, InternalZone>,
    survivors: HashMap<Uuid, InternalSurvivor>,
    alerts: HashMap<Uuid, InternalAlert>,
    // Callbacks
    on_survivor_detected: Option<js_sys::Function>,
    on_survivor_updated: Option<js_sys::Function>,
    on_alert_generated: Option<js_sys::Function>,
    on_zone_updated: Option<js_sys::Function>,
}

impl Default for DashboardState {
    fn default() -> Self {
        Self {
            event_id: None,
            disaster_type: JsDisasterType::Unknown,
            event_start: None,
            location: (0.0, 0.0),
            description: String::new(),
            zones: HashMap::new(),
            survivors: HashMap::new(),
            alerts: HashMap::new(),
            on_survivor_detected: None,
            on_survivor_updated: None,
            on_alert_generated: None,
            on_zone_updated: None,
        }
    }
}

// ============================================================================
// Main Dashboard Class
// ============================================================================

/// WiFi-Mat Disaster Response Dashboard for browser integration.
///
/// This class provides a complete interface for managing disaster response
/// operations from a web browser, including zone management, survivor tracking,
/// and real-time alert notifications.
#[wasm_bindgen]
pub struct MatDashboard {
    state: Rc<RefCell<DashboardState>>,
}

#[wasm_bindgen]
impl MatDashboard {
    /// Create a new MatDashboard instance.
    ///
    /// @returns {MatDashboard} A new dashboard instance
    #[wasm_bindgen(constructor)]
    pub fn new() -> MatDashboard {
        // Initialize panic hook for better error messages
        #[cfg(feature = "console_error_panic_hook")]
        console_error_panic_hook::set_once();

        MatDashboard {
            state: Rc::new(RefCell::new(DashboardState::default())),
        }
    }

    // ========================================================================
    // Event Management
    // ========================================================================

    /// Create a new disaster event.
    ///
    /// @param {string} disaster_type - Type: "earthquake", "building_collapse", etc.
    /// @param {number} latitude - Event latitude
    /// @param {number} longitude - Event longitude
    /// @param {string} description - Event description
    /// @returns {string} The event ID
    #[wasm_bindgen(js_name = createEvent)]
    pub fn create_event(
        &self,
        disaster_type: &str,
        latitude: f64,
        longitude: f64,
        description: &str,
    ) -> String {
        let mut state = self.state.borrow_mut();

        let dtype = match disaster_type.to_lowercase().as_str() {
            "earthquake" => JsDisasterType::Earthquake,
            "building_collapse" | "buildingcollapse" => JsDisasterType::BuildingCollapse,
            "landslide" => JsDisasterType::Landslide,
            "avalanche" => JsDisasterType::Avalanche,
            "flood" => JsDisasterType::Flood,
            "mine_collapse" | "minecollapse" => JsDisasterType::MineCollapse,
            "industrial" => JsDisasterType::Industrial,
            "tunnel_collapse" | "tunnelcollapse" => JsDisasterType::TunnelCollapse,
            _ => JsDisasterType::Unknown,
        };

        let event_id = Uuid::new_v4();
        state.event_id = Some(event_id);
        state.disaster_type = dtype;
        state.event_start = Some(chrono::Utc::now());
        state.location = (latitude, longitude);
        state.description = description.to_string();

        // Clear previous data
        state.zones.clear();
        state.survivors.clear();
        state.alerts.clear();

        log::info!("Created disaster event: {} - {}", event_id, description);

        event_id.to_string()
    }

    /// Get the current event ID.
    ///
    /// @returns {string | undefined} The event ID or undefined
    #[wasm_bindgen(js_name = getEventId)]
    pub fn get_event_id(&self) -> Option<String> {
        self.state.borrow().event_id.map(|id| id.to_string())
    }

    /// Get the disaster type.
    ///
    /// @returns {number} The disaster type enum value
    #[wasm_bindgen(js_name = getDisasterType)]
    pub fn get_disaster_type(&self) -> JsDisasterType {
        self.state.borrow().disaster_type
    }

    /// Close the current event.
    #[wasm_bindgen(js_name = closeEvent)]
    pub fn close_event(&self) {
        let mut state = self.state.borrow_mut();
        state.event_id = None;
        state.event_start = None;
        log::info!("Disaster event closed");
    }

    // ========================================================================
    // Zone Management
    // ========================================================================

    /// Add a rectangular scan zone.
    ///
    /// @param {string} name - Zone name
    /// @param {number} x - Top-left X coordinate (canvas pixels)
    /// @param {number} y - Top-left Y coordinate (canvas pixels)
    /// @param {number} width - Zone width (pixels)
    /// @param {number} height - Zone height (pixels)
    /// @returns {string} The zone ID
    #[wasm_bindgen(js_name = addRectangleZone)]
    pub fn add_rectangle_zone(
        &self,
        name: &str,
        x: f64,
        y: f64,
        width: f64,
        height: f64,
    ) -> String {
        let mut state = self.state.borrow_mut();

        let zone = InternalZone {
            id: Uuid::new_v4(),
            name: name.to_string(),
            bounds: ZoneBounds::Rectangle {
                x,
                y,
                width,
                height,
            },
            status: JsZoneStatus::Active,
            scan_count: 0,
            detection_count: 0,
        };

        let zone_id = zone.id;
        let js_zone = zone.to_js();
        state.zones.insert(zone_id, zone);

        // Fire callback
        if let Some(callback) = &state.on_zone_updated {
            let this = JsValue::NULL;
            let zone_value = serde_wasm_bindgen::to_value(&js_zone).unwrap_or(JsValue::NULL);
            let _ = callback.call1(&this, &zone_value);
        }

        log::info!("Added rectangle zone: {} ({})", name, zone_id);
        zone_id.to_string()
    }

    /// Add a circular scan zone.
    ///
    /// @param {string} name - Zone name
    /// @param {number} centerX - Center X coordinate (canvas pixels)
    /// @param {number} centerY - Center Y coordinate (canvas pixels)
    /// @param {number} radius - Zone radius (pixels)
    /// @returns {string} The zone ID
    #[wasm_bindgen(js_name = addCircleZone)]
    pub fn add_circle_zone(&self, name: &str, center_x: f64, center_y: f64, radius: f64) -> String {
        let mut state = self.state.borrow_mut();

        let zone = InternalZone {
            id: Uuid::new_v4(),
            name: name.to_string(),
            bounds: ZoneBounds::Circle {
                center_x,
                center_y,
                radius,
            },
            status: JsZoneStatus::Active,
            scan_count: 0,
            detection_count: 0,
        };

        let zone_id = zone.id;
        let js_zone = zone.to_js();
        state.zones.insert(zone_id, zone);

        // Fire callback
        if let Some(callback) = &state.on_zone_updated {
            let this = JsValue::NULL;
            let zone_value = serde_wasm_bindgen::to_value(&js_zone).unwrap_or(JsValue::NULL);
            let _ = callback.call1(&this, &zone_value);
        }

        log::info!("Added circle zone: {} ({})", name, zone_id);
        zone_id.to_string()
    }

    /// Add a polygon scan zone.
    ///
    /// @param {string} name - Zone name
    /// @param {Float64Array} vertices - Flat array of [x1, y1, x2, y2, ...] coordinates
    /// @returns {string} The zone ID
    #[wasm_bindgen(js_name = addPolygonZone)]
    pub fn add_polygon_zone(&self, name: &str, vertices: &[f64]) -> String {
        let mut state = self.state.borrow_mut();

        // Convert flat array to vertex pairs
        let vertex_pairs: Vec<(f64, f64)> = vertices
            .chunks(2)
            .filter(|chunk| chunk.len() == 2)
            .map(|chunk| (chunk[0], chunk[1]))
            .collect();

        let zone = InternalZone {
            id: Uuid::new_v4(),
            name: name.to_string(),
            bounds: ZoneBounds::Polygon {
                vertices: vertex_pairs,
            },
            status: JsZoneStatus::Active,
            scan_count: 0,
            detection_count: 0,
        };

        let zone_id = zone.id;
        let js_zone = zone.to_js();
        state.zones.insert(zone_id, zone);

        // Fire callback
        if let Some(callback) = &state.on_zone_updated {
            let this = JsValue::NULL;
            let zone_value = serde_wasm_bindgen::to_value(&js_zone).unwrap_or(JsValue::NULL);
            let _ = callback.call1(&this, &zone_value);
        }

        log::info!("Added polygon zone: {} ({})", name, zone_id);
        zone_id.to_string()
    }

    /// Remove a scan zone.
    ///
    /// @param {string} zone_id - Zone ID to remove
    /// @returns {boolean} True if removed
    #[wasm_bindgen(js_name = removeZone)]
    pub fn remove_zone(&self, zone_id: &str) -> bool {
        let mut state = self.state.borrow_mut();

        if let Ok(uuid) = Uuid::parse_str(zone_id) {
            if state.zones.remove(&uuid).is_some() {
                log::info!("Removed zone: {}", zone_id);
                return true;
            }
        }
        false
    }

    /// Update zone status.
    ///
    /// @param {string} zone_id - Zone ID
    /// @param {number} status - New status (0=Active, 1=Paused, 2=Complete, 3=Inaccessible)
    /// @returns {boolean} True if updated
    #[wasm_bindgen(js_name = setZoneStatus)]
    pub fn set_zone_status(&self, zone_id: &str, status: u8) -> bool {
        let mut state = self.state.borrow_mut();

        if let Ok(uuid) = Uuid::parse_str(zone_id) {
            if let Some(zone) = state.zones.get_mut(&uuid) {
                zone.status = match status {
                    0 => JsZoneStatus::Active,
                    1 => JsZoneStatus::Paused,
                    2 => JsZoneStatus::Complete,
                    3 => JsZoneStatus::Inaccessible,
                    _ => return false,
                };

                // Get JS zone before callback
                let js_zone = zone.to_js();

                // Fire callback
                if let Some(callback) = &state.on_zone_updated {
                    let this = JsValue::NULL;
                    let zone_value =
                        serde_wasm_bindgen::to_value(&js_zone).unwrap_or(JsValue::NULL);
                    let _ = callback.call1(&this, &zone_value);
                }

                return true;
            }
        }
        false
    }

    /// Get all zones.
    ///
    /// @returns {Array<JsScanZone>} Array of zones
    #[wasm_bindgen(js_name = getZones)]
    pub fn get_zones(&self) -> JsValue {
        let state = self.state.borrow();
        let zones: Vec<JsScanZone> = state.zones.values().map(|z| z.to_js()).collect();
        serde_wasm_bindgen::to_value(&zones).unwrap_or(JsValue::NULL)
    }

    /// Get a specific zone.
    ///
    /// @param {string} zone_id - Zone ID
    /// @returns {JsScanZone | undefined} The zone or undefined
    #[wasm_bindgen(js_name = getZone)]
    pub fn get_zone(&self, zone_id: &str) -> JsValue {
        let state = self.state.borrow();

        if let Ok(uuid) = Uuid::parse_str(zone_id) {
            if let Some(zone) = state.zones.get(&uuid) {
                return serde_wasm_bindgen::to_value(&zone.to_js()).unwrap_or(JsValue::NULL);
            }
        }
        JsValue::UNDEFINED
    }

    // ========================================================================
    // Survivor Management
    // ========================================================================

    /// Simulate a survivor detection (for testing/demo).
    ///
    /// @param {number} x - X position (canvas pixels)
    /// @param {number} y - Y position (canvas pixels)
    /// @param {number} depth - Depth in meters (negative = buried)
    /// @param {number} triage - Triage status (0-4)
    /// @param {number} confidence - Detection confidence (0.0-1.0)
    /// @returns {string} The survivor ID
    #[wasm_bindgen(js_name = simulateSurvivorDetection)]
    pub fn simulate_survivor_detection(
        &self,
        x: f64,
        y: f64,
        depth: f64,
        triage: u8,
        confidence: f64,
    ) -> String {
        let mut state = self.state.borrow_mut();

        // Find which zone contains this point
        let zone_id = state
            .zones
            .iter()
            .find(|(_, z)| z.contains_point(x, y))
            .map(|(id, _)| *id)
            .unwrap_or_else(Uuid::new_v4);

        // Update zone detection count
        if let Some(zone) = state.zones.get_mut(&zone_id) {
            zone.detection_count += 1;
        }

        let triage_status = match triage {
            0 => JsTriageStatus::Immediate,
            1 => JsTriageStatus::Delayed,
            2 => JsTriageStatus::Minor,
            3 => JsTriageStatus::Deceased,
            _ => JsTriageStatus::Unknown,
        };

        let now = chrono::Utc::now();
        let survivor = InternalSurvivor {
            id: Uuid::new_v4(),
            zone_id,
            x,
            y,
            depth,
            triage_status,
            confidence: confidence.clamp(0.0, 1.0),
            breathing_rate: Some(12.0 + (confidence * 8.0)),
            heart_rate: Some(60.0 + (confidence * 40.0)),
            first_detected: now,
            last_updated: now,
            is_deteriorating: false,
            alert_sent: false,
        };

        let survivor_id = survivor.id;
        let js_survivor = survivor.to_js();
        state.survivors.insert(survivor_id, survivor);

        // Fire callback
        if let Some(callback) = &state.on_survivor_detected {
            let this = JsValue::NULL;
            let survivor_value =
                serde_wasm_bindgen::to_value(&js_survivor).unwrap_or(JsValue::NULL);
            let _ = callback.call1(&this, &survivor_value);
        }

        // Generate alert for urgent survivors
        if triage_status == JsTriageStatus::Immediate || triage_status == JsTriageStatus::Delayed {
            self.generate_alert_internal(&mut state, survivor_id, triage_status, x, y);
        }

        log::info!(
            "Survivor detected: {} at ({}, {}) - {:?}",
            survivor_id,
            x,
            y,
            triage_status
        );

        survivor_id.to_string()
    }

    /// Get all survivors.
    ///
    /// @returns {Array<JsSurvivor>} Array of survivors
    #[wasm_bindgen(js_name = getSurvivors)]
    pub fn get_survivors(&self) -> JsValue {
        let state = self.state.borrow();
        let survivors: Vec<JsSurvivor> = state.survivors.values().map(|s| s.to_js()).collect();
        serde_wasm_bindgen::to_value(&survivors).unwrap_or(JsValue::NULL)
    }

    /// Get survivors filtered by triage status.
    ///
    /// @param {number} triage - Triage status to filter (0-4)
    /// @returns {Array<JsSurvivor>} Filtered survivors
    #[wasm_bindgen(js_name = getSurvivorsByTriage)]
    pub fn get_survivors_by_triage(&self, triage: u8) -> JsValue {
        let state = self.state.borrow();
        let target_status = match triage {
            0 => JsTriageStatus::Immediate,
            1 => JsTriageStatus::Delayed,
            2 => JsTriageStatus::Minor,
            3 => JsTriageStatus::Deceased,
            _ => JsTriageStatus::Unknown,
        };

        let survivors: Vec<JsSurvivor> = state
            .survivors
            .values()
            .filter(|s| s.triage_status == target_status)
            .map(|s| s.to_js())
            .collect();

        serde_wasm_bindgen::to_value(&survivors).unwrap_or(JsValue::NULL)
    }

    /// Get a specific survivor.
    ///
    /// @param {string} survivor_id - Survivor ID
    /// @returns {JsSurvivor | undefined} The survivor or undefined
    #[wasm_bindgen(js_name = getSurvivor)]
    pub fn get_survivor(&self, survivor_id: &str) -> JsValue {
        let state = self.state.borrow();

        if let Ok(uuid) = Uuid::parse_str(survivor_id) {
            if let Some(survivor) = state.survivors.get(&uuid) {
                return serde_wasm_bindgen::to_value(&survivor.to_js()).unwrap_or(JsValue::NULL);
            }
        }
        JsValue::UNDEFINED
    }

    /// Mark a survivor as rescued.
    ///
    /// @param {string} survivor_id - Survivor ID
    /// @returns {boolean} True if updated
    #[wasm_bindgen(js_name = markSurvivorRescued)]
    pub fn mark_survivor_rescued(&self, survivor_id: &str) -> bool {
        let mut state = self.state.borrow_mut();

        if let Ok(uuid) = Uuid::parse_str(survivor_id) {
            if let Some(_survivor) = state.survivors.remove(&uuid) {
                log::info!("Survivor {} marked as rescued", survivor_id);
                return true;
            }
        }
        false
    }

    /// Update survivor deterioration status.
    ///
    /// @param {string} survivor_id - Survivor ID
    /// @param {boolean} is_deteriorating - Whether survivor is deteriorating
    /// @returns {boolean} True if updated
    #[wasm_bindgen(js_name = setSurvivorDeteriorating)]
    pub fn set_survivor_deteriorating(&self, survivor_id: &str, is_deteriorating: bool) -> bool {
        let mut state = self.state.borrow_mut();

        if let Ok(uuid) = Uuid::parse_str(survivor_id) {
            if let Some(survivor) = state.survivors.get_mut(&uuid) {
                survivor.is_deteriorating = is_deteriorating;
                survivor.last_updated = chrono::Utc::now();

                // Get JS survivor before callback
                let js_survivor = survivor.to_js();

                // Fire callback
                if let Some(callback) = &state.on_survivor_updated {
                    let this = JsValue::NULL;
                    let survivor_value =
                        serde_wasm_bindgen::to_value(&js_survivor).unwrap_or(JsValue::NULL);
                    let _ = callback.call1(&this, &survivor_value);
                }

                return true;
            }
        }
        false
    }

    // ========================================================================
    // Alert Management
    // ========================================================================

    fn generate_alert_internal(
        &self,
        state: &mut DashboardState,
        survivor_id: Uuid,
        triage_status: JsTriageStatus,
        x: f64,
        y: f64,
    ) {
        let priority = match triage_status {
            JsTriageStatus::Immediate => JsAlertPriority::Critical,
            JsTriageStatus::Delayed => JsAlertPriority::High,
            JsTriageStatus::Minor => JsAlertPriority::Medium,
            _ => JsAlertPriority::Low,
        };

        let title = match triage_status {
            JsTriageStatus::Immediate => "CRITICAL: Survivor needs immediate attention",
            JsTriageStatus::Delayed => "URGENT: Survivor detected - delayed priority",
            _ => "Survivor detected",
        };

        let alert = InternalAlert {
            id: Uuid::new_v4(),
            survivor_id,
            priority,
            title: title.to_string(),
            message: format!(
                "Survivor detected at position ({:.0}, {:.0}). Triage: {:?}",
                x, y, triage_status
            ),
            recommended_action: match triage_status {
                JsTriageStatus::Immediate => "Dispatch rescue team immediately".to_string(),
                JsTriageStatus::Delayed => "Schedule rescue team dispatch".to_string(),
                _ => "Monitor and assess".to_string(),
            },
            triage_status,
            location_x: x,
            location_y: y,
            created_at: chrono::Utc::now(),
            acknowledged: false,
        };

        let alert_id = alert.id;
        let js_alert = alert.to_js();
        state.alerts.insert(alert_id, alert);

        // Mark survivor alert sent
        if let Some(survivor) = state.survivors.get_mut(&survivor_id) {
            survivor.alert_sent = true;
        }

        // Fire callback
        if let Some(callback) = &state.on_alert_generated {
            let this = JsValue::NULL;
            let alert_value = serde_wasm_bindgen::to_value(&js_alert).unwrap_or(JsValue::NULL);
            let _ = callback.call1(&this, &alert_value);
        }
    }

    /// Get all active alerts.
    ///
    /// @returns {Array<JsAlert>} Array of alerts
    #[wasm_bindgen(js_name = getAlerts)]
    pub fn get_alerts(&self) -> JsValue {
        let state = self.state.borrow();
        let alerts: Vec<JsAlert> = state
            .alerts
            .values()
            .filter(|a| !a.acknowledged)
            .map(|a| a.to_js())
            .collect();
        serde_wasm_bindgen::to_value(&alerts).unwrap_or(JsValue::NULL)
    }

    /// Acknowledge an alert.
    ///
    /// @param {string} alert_id - Alert ID
    /// @returns {boolean} True if acknowledged
    #[wasm_bindgen(js_name = acknowledgeAlert)]
    pub fn acknowledge_alert(&self, alert_id: &str) -> bool {
        let mut state = self.state.borrow_mut();

        if let Ok(uuid) = Uuid::parse_str(alert_id) {
            if let Some(alert) = state.alerts.get_mut(&uuid) {
                alert.acknowledged = true;
                log::info!("Alert {} acknowledged", alert_id);
                return true;
            }
        }
        false
    }

    // ========================================================================
    // Statistics
    // ========================================================================

    /// Get dashboard statistics.
    ///
    /// @returns {JsDashboardStats} Current statistics
    #[wasm_bindgen(js_name = getStats)]
    pub fn get_stats(&self) -> JsDashboardStats {
        let state = self.state.borrow();

        let mut immediate_count = 0u32;
        let mut delayed_count = 0u32;
        let mut minor_count = 0u32;
        let mut deceased_count = 0u32;
        let mut unknown_count = 0u32;

        for survivor in state.survivors.values() {
            match survivor.triage_status {
                JsTriageStatus::Immediate => immediate_count += 1,
                JsTriageStatus::Delayed => delayed_count += 1,
                JsTriageStatus::Minor => minor_count += 1,
                JsTriageStatus::Deceased => deceased_count += 1,
                JsTriageStatus::Unknown => unknown_count += 1,
            }
        }

        let active_zones = state
            .zones
            .values()
            .filter(|z| z.status == JsZoneStatus::Active)
            .count() as u32;

        let total_scans: u32 = state.zones.values().map(|z| z.scan_count).sum();

        let active_alerts = state.alerts.values().filter(|a| !a.acknowledged).count() as u32;

        let elapsed_seconds = state
            .event_start
            .map(|start| (chrono::Utc::now() - start).num_milliseconds() as f64 / 1000.0)
            .unwrap_or(0.0);

        JsDashboardStats {
            total_survivors: state.survivors.len() as u32,
            immediate_count,
            delayed_count,
            minor_count,
            deceased_count,
            unknown_count,
            active_zones,
            total_scans,
            active_alerts,
            elapsed_seconds,
        }
    }

    // ========================================================================
    // Callback Registration
    // ========================================================================

    /// Register callback for survivor detection events.
    ///
    /// @param {Function} callback - Function to call with JsSurvivor when detected
    #[wasm_bindgen(js_name = onSurvivorDetected)]
    pub fn on_survivor_detected(&self, callback: js_sys::Function) {
        self.state.borrow_mut().on_survivor_detected = Some(callback);
    }

    /// Register callback for survivor update events.
    ///
    /// @param {Function} callback - Function to call with JsSurvivor when updated
    #[wasm_bindgen(js_name = onSurvivorUpdated)]
    pub fn on_survivor_updated(&self, callback: js_sys::Function) {
        self.state.borrow_mut().on_survivor_updated = Some(callback);
    }

    /// Register callback for alert generation events.
    ///
    /// @param {Function} callback - Function to call with JsAlert when generated
    #[wasm_bindgen(js_name = onAlertGenerated)]
    pub fn on_alert_generated(&self, callback: js_sys::Function) {
        self.state.borrow_mut().on_alert_generated = Some(callback);
    }

    /// Register callback for zone update events.
    ///
    /// @param {Function} callback - Function to call with JsScanZone when updated
    #[wasm_bindgen(js_name = onZoneUpdated)]
    pub fn on_zone_updated(&self, callback: js_sys::Function) {
        self.state.borrow_mut().on_zone_updated = Some(callback);
    }

    // ========================================================================
    // Canvas Rendering Helpers
    // ========================================================================

    /// Render all zones on a canvas context.
    ///
    /// @param {CanvasRenderingContext2D} ctx - Canvas 2D context
    #[wasm_bindgen(js_name = renderZones)]
    pub fn render_zones(&self, ctx: &web_sys::CanvasRenderingContext2d) {
        let state = self.state.borrow();

        for zone in state.zones.values() {
            let color = match zone.status {
                JsZoneStatus::Active => "rgba(0, 150, 255, 0.3)",
                JsZoneStatus::Paused => "rgba(255, 200, 0, 0.3)",
                JsZoneStatus::Complete => "rgba(0, 200, 0, 0.3)",
                JsZoneStatus::Inaccessible => "rgba(150, 150, 150, 0.3)",
            };

            let border_color = match zone.status {
                JsZoneStatus::Active => "#0096ff",
                JsZoneStatus::Paused => "#ffc800",
                JsZoneStatus::Complete => "#00c800",
                JsZoneStatus::Inaccessible => "#969696",
            };

            ctx.set_fill_style_str(color);
            ctx.set_stroke_style_str(border_color);
            ctx.set_line_width(2.0);

            match &zone.bounds {
                ZoneBounds::Rectangle {
                    x,
                    y,
                    width,
                    height,
                } => {
                    ctx.fill_rect(*x, *y, *width, *height);
                    ctx.stroke_rect(*x, *y, *width, *height);

                    // Draw zone name
                    ctx.set_fill_style_str("#ffffff");
                    ctx.set_font("12px sans-serif");
                    let _ = ctx.fill_text(&zone.name, *x + 5.0, *y + 15.0);
                }
                ZoneBounds::Circle {
                    center_x,
                    center_y,
                    radius,
                } => {
                    ctx.begin_path();
                    let _ = ctx.arc(*center_x, *center_y, *radius, 0.0, std::f64::consts::TAU);
                    ctx.fill();
                    ctx.stroke();

                    // Draw zone name
                    ctx.set_fill_style_str("#ffffff");
                    ctx.set_font("12px sans-serif");
                    let _ = ctx.fill_text(&zone.name, *center_x - 20.0, *center_y);
                }
                ZoneBounds::Polygon { vertices } => {
                    if !vertices.is_empty() {
                        ctx.begin_path();
                        ctx.move_to(vertices[0].0, vertices[0].1);
                        for (x, y) in vertices.iter().skip(1) {
                            ctx.line_to(*x, *y);
                        }
                        ctx.close_path();
                        ctx.fill();
                        ctx.stroke();

                        // Draw zone name at centroid
                        if !vertices.is_empty() {
                            let cx: f64 = vertices.iter().map(|(x, _)| x).sum::<f64>()
                                / vertices.len() as f64;
                            let cy: f64 = vertices.iter().map(|(_, y)| y).sum::<f64>()
                                / vertices.len() as f64;
                            ctx.set_fill_style_str("#ffffff");
                            ctx.set_font("12px sans-serif");
                            let _ = ctx.fill_text(&zone.name, cx - 20.0, cy);
                        }
                    }
                }
            }
        }
    }

    /// Render all survivors on a canvas context.
    ///
    /// @param {CanvasRenderingContext2D} ctx - Canvas 2D context
    #[wasm_bindgen(js_name = renderSurvivors)]
    pub fn render_survivors(&self, ctx: &web_sys::CanvasRenderingContext2d) {
        let state = self.state.borrow();

        for survivor in state.survivors.values() {
            let color = survivor.triage_status.color();
            let radius = if survivor.is_deteriorating {
                12.0
            } else {
                10.0
            };

            // Draw outer glow for urgent survivors
            if survivor.triage_status == JsTriageStatus::Immediate {
                ctx.set_fill_style_str("rgba(255, 0, 0, 0.3)");
                ctx.begin_path();
                let _ = ctx.arc(
                    survivor.x,
                    survivor.y,
                    radius + 8.0,
                    0.0,
                    std::f64::consts::TAU,
                );
                ctx.fill();
            }

            // Draw marker
            ctx.set_fill_style_str(color);
            ctx.begin_path();
            let _ = ctx.arc(survivor.x, survivor.y, radius, 0.0, std::f64::consts::TAU);
            ctx.fill();

            // Draw border
            ctx.set_stroke_style_str("#ffffff");
            ctx.set_line_width(2.0);
            ctx.stroke();

            // Draw deterioration indicator
            if survivor.is_deteriorating {
                ctx.set_stroke_style_str("#ff0000");
                ctx.set_line_width(3.0);
                ctx.begin_path();
                let _ = ctx.arc(
                    survivor.x,
                    survivor.y,
                    radius + 4.0,
                    0.0,
                    std::f64::consts::TAU,
                );
                ctx.stroke();
            }

            // Draw depth indicator if buried
            if survivor.depth < 0.0 {
                ctx.set_fill_style_str("#ffffff");
                ctx.set_font("10px sans-serif");
                let depth_text = format!("{:.1}m", -survivor.depth);
                let _ = ctx.fill_text(&depth_text, survivor.x + radius + 2.0, survivor.y + 4.0);
            }
        }
    }

    // ========================================================================
    // CSI Data Ingestion (ADR-009: Signal Pipeline Exposure)
    // ========================================================================

    /// Push raw CSI amplitude/phase data into the dashboard for signal analysis.
    ///
    /// This is the primary data ingestion path for browser-based applications
    /// receiving CSI data from a WebSocket or fetch endpoint. The data is
    /// processed through a lightweight signal analysis to extract breathing
    /// rate and confidence estimates.
    ///
    /// @param {Float64Array} amplitudes - CSI amplitude samples
    /// @param {Float64Array} phases - CSI phase samples (same length as amplitudes)
    /// @returns {string} JSON string with analysis results, or error string
    #[wasm_bindgen(js_name = pushCsiData)]
    pub fn push_csi_data(&self, amplitudes: &[f64], phases: &[f64]) -> String {
        if amplitudes.len() != phases.len() {
            return serde_json::json!({
                "error": "Amplitudes and phases must have equal length"
            })
            .to_string();
        }

        if amplitudes.is_empty() {
            return serde_json::json!({
                "error": "CSI data cannot be empty"
            })
            .to_string();
        }

        // Lightweight breathing rate extraction using zero-crossing analysis
        // on amplitude envelope. This runs entirely in WASM without Rust signal crate.
        let n = amplitudes.len();

        // Compute amplitude mean and variance
        let mean: f64 = amplitudes.iter().sum::<f64>() / n as f64;
        let variance: f64 = amplitudes.iter().map(|a| (a - mean).powi(2)).sum::<f64>() / n as f64;

        // Count zero crossings (crossings of mean value) for frequency estimation
        let mut zero_crossings = 0usize;
        for i in 1..n {
            let prev = amplitudes[i - 1] - mean;
            let curr = amplitudes[i] - mean;
            if prev.signum() != curr.signum() {
                zero_crossings += 1;
            }
        }

        // Estimate frequency from zero crossings (each full cycle = 2 crossings)
        // Assuming ~100 Hz sample rate for typical WiFi CSI
        let assumed_sample_rate = 100.0_f64;
        let duration_secs = n as f64 / assumed_sample_rate;
        let estimated_freq = if duration_secs > 0.0 {
            zero_crossings as f64 / (2.0 * duration_secs)
        } else {
            0.0
        };

        // Convert to breaths per minute
        let breathing_rate_bpm = estimated_freq * 60.0;

        // Confidence based on signal variance and consistency
        let confidence =
            if variance > 0.001 && breathing_rate_bpm > 4.0 && breathing_rate_bpm < 40.0 {
                let regularity = 1.0 - (variance.sqrt() / mean.abs().max(0.01)).min(1.0);
                (regularity * 0.8 + 0.2).min(1.0)
            } else {
                0.0
            };

        // Phase coherence (how correlated phase is with amplitude)
        let phase_mean: f64 = phases.iter().sum::<f64>() / n as f64;
        let _phase_coherence: f64 = if n > 1 {
            let cov: f64 = amplitudes
                .iter()
                .zip(phases.iter())
                .map(|(a, p)| (a - mean) * (p - phase_mean))
                .sum::<f64>()
                / n as f64;
            let std_a = variance.sqrt();
            let std_p =
                (phases.iter().map(|p| (p - phase_mean).powi(2)).sum::<f64>() / n as f64).sqrt();
            if std_a > 0.0 && std_p > 0.0 {
                (cov / (std_a * std_p)).abs()
            } else {
                0.0
            }
        } else {
            0.0
        };

        log::debug!(
            "CSI analysis: {} samples, rate={:.1} BPM, confidence={:.2}",
            n,
            breathing_rate_bpm,
            confidence
        );

        let result = serde_json::json!({
            "accepted": true,
            "samples": n,
            "analysis": {
                "estimated_breathing_rate_bpm": breathing_rate_bpm,
                "confidence": confidence,
                "signal_variance": variance,
                "duration_secs": duration_secs,
                "zero_crossings": zero_crossings,
            }
        });

        result.to_string()
    }

    /// Get the current pipeline analysis configuration.
    ///
    /// @returns {string} JSON configuration
    #[wasm_bindgen(js_name = getPipelineConfig)]
    pub fn get_pipeline_config(&self) -> String {
        serde_json::json!({
            "sample_rate": 100.0,
            "breathing_freq_range": [0.1, 0.67],
            "heartbeat_freq_range": [0.8, 3.0],
            "min_confidence": 0.3,
            "buffer_duration_secs": 10.0,
        })
        .to_string()
    }

    // ========================================================================
    // WebSocket Integration
    // ========================================================================

    /// Connect to a WebSocket for real-time updates.
    ///
    /// @param {string} url - WebSocket URL
    /// @returns {Promise<void>} Promise that resolves when connected
    #[wasm_bindgen(js_name = connectWebSocket)]
    pub fn connect_websocket(&self, url: &str) -> js_sys::Promise {
        let state = Rc::clone(&self.state);
        let url = url.to_string();

        wasm_bindgen_futures::future_to_promise(async move {
            let ws = web_sys::WebSocket::new(&url)
                .map_err(|e| JsValue::from_str(&format!("Failed to create WebSocket: {:?}", e)))?;

            ws.set_binary_type(web_sys::BinaryType::Arraybuffer);

            // Set up message handler
            let _state_clone = Rc::clone(&state);
            let onmessage_callback = Closure::wrap(Box::new(move |e: web_sys::MessageEvent| {
                if let Ok(txt) = e.data().dyn_into::<js_sys::JsString>() {
                    let msg: String = txt.into();
                    // Parse and handle incoming survivor data
                    if let Ok(data) = serde_json::from_str::<serde_json::Value>(&msg) {
                        if let Some(msg_type) = data.get("type").and_then(|t| t.as_str()) {
                            match msg_type {
                                "survivor_detection" => {
                                    log::info!("Received survivor detection via WebSocket");
                                    // Process survivor data...
                                }
                                "zone_update" => {
                                    log::info!("Received zone update via WebSocket");
                                }
                                _ => {}
                            }
                        }
                    }
                }
            }) as Box<dyn FnMut(_)>);

            ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
            onmessage_callback.forget();

            // Set up error handler
            let onerror_callback = Closure::wrap(Box::new(move |e: web_sys::ErrorEvent| {
                log::error!("WebSocket error: {:?}", e.message());
            }) as Box<dyn FnMut(_)>);

            ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
            onerror_callback.forget();

            log::info!("WebSocket connected to {}", url);

            Ok(JsValue::UNDEFINED)
        })
    }
}

impl Default for MatDashboard {
    fn default() -> Self {
        Self::new()
    }
}

// ============================================================================
// TypeScript Type Definitions
// ============================================================================

/// Generate TypeScript definitions.
/// This is exported as a constant string for tooling.
#[wasm_bindgen]
pub fn get_typescript_definitions() -> String {
    r#"
// WiFi-Mat TypeScript Definitions

export enum DisasterType {
    BuildingCollapse = 0,
    Earthquake = 1,
    Landslide = 2,
    Avalanche = 3,
    Flood = 4,
    MineCollapse = 5,
    Industrial = 6,
    TunnelCollapse = 7,
    Unknown = 8,
}

export enum TriageStatus {
    Immediate = 0,  // Red
    Delayed = 1,    // Yellow
    Minor = 2,      // Green
    Deceased = 3,   // Black
    Unknown = 4,    // Gray
}

export enum ZoneStatus {
    Active = 0,
    Paused = 1,
    Complete = 2,
    Inaccessible = 3,
}

export enum AlertPriority {
    Critical = 0,
    High = 1,
    Medium = 2,
    Low = 3,
}

export interface Survivor {
    id: string;
    zone_id: string;
    x: number;
    y: number;
    depth: number;
    triage_status: TriageStatus;
    triage_color: string;
    confidence: number;
    breathing_rate: number;
    heart_rate: number;
    first_detected: string;
    last_updated: string;
    is_deteriorating: boolean;
}

export interface ScanZone {
    id: string;
    name: string;
    zone_type: 'rectangle' | 'circle' | 'polygon';
    status: ZoneStatus;
    scan_count: number;
    detection_count: number;
    bounds_json: string;
}

export interface Alert {
    id: string;
    survivor_id: string;
    priority: AlertPriority;
    title: string;
    message: string;
    recommended_action: string;
    triage_status: TriageStatus;
    location_x: number;
    location_y: number;
    created_at: string;
    priority_color: string;
}

export interface DashboardStats {
    total_survivors: number;
    immediate_count: number;
    delayed_count: number;
    minor_count: number;
    deceased_count: number;
    unknown_count: number;
    active_zones: number;
    total_scans: number;
    active_alerts: number;
    elapsed_seconds: number;
}

export class MatDashboard {
    constructor();

    // Event Management
    createEvent(disasterType: string, latitude: number, longitude: number, description: string): string;
    getEventId(): string | undefined;
    getDisasterType(): DisasterType;
    closeEvent(): void;

    // Zone Management
    addRectangleZone(name: string, x: number, y: number, width: number, height: number): string;
    addCircleZone(name: string, centerX: number, centerY: number, radius: number): string;
    addPolygonZone(name: string, vertices: Float64Array): string;
    removeZone(zoneId: string): boolean;
    setZoneStatus(zoneId: string, status: ZoneStatus): boolean;
    getZones(): ScanZone[];
    getZone(zoneId: string): ScanZone | undefined;

    // Survivor Management
    simulateSurvivorDetection(x: number, y: number, depth: number, triage: TriageStatus, confidence: number): string;
    getSurvivors(): Survivor[];
    getSurvivorsByTriage(triage: TriageStatus): Survivor[];
    getSurvivor(survivorId: string): Survivor | undefined;
    markSurvivorRescued(survivorId: string): boolean;
    setSurvivorDeteriorating(survivorId: string, isDeteriorating: boolean): boolean;

    // Alert Management
    getAlerts(): Alert[];
    acknowledgeAlert(alertId: string): boolean;

    // Statistics
    getStats(): DashboardStats;

    // Callbacks
    onSurvivorDetected(callback: (survivor: Survivor) => void): void;
    onSurvivorUpdated(callback: (survivor: Survivor) => void): void;
    onAlertGenerated(callback: (alert: Alert) => void): void;
    onZoneUpdated(callback: (zone: ScanZone) => void): void;

    // Rendering
    renderZones(ctx: CanvasRenderingContext2D): void;
    renderSurvivors(ctx: CanvasRenderingContext2D): void;

    // CSI Signal Processing
    pushCsiData(amplitudes: Float64Array, phases: Float64Array): string;
    getPipelineConfig(): string;

    // WebSocket
    connectWebSocket(url: string): Promise<void>;
}
"#.to_string()
}

#[cfg(test)]
mod tests {
    use super::*;
    use wasm_bindgen_test::*;

    #[wasm_bindgen_test]
    fn test_create_dashboard() {
        let dashboard = MatDashboard::new();
        assert!(dashboard.get_event_id().is_none());
    }

    #[wasm_bindgen_test]
    fn test_create_event() {
        let dashboard = MatDashboard::new();
        let event_id = dashboard.create_event("earthquake", 37.7749, -122.4194, "Test Event");
        assert!(!event_id.is_empty());
        assert!(dashboard.get_event_id().is_some());
    }

    #[wasm_bindgen_test]
    fn test_add_zone() {
        let dashboard = MatDashboard::new();
        dashboard.create_event("earthquake", 0.0, 0.0, "Test");

        let zone_id = dashboard.add_rectangle_zone("Zone A", 0.0, 0.0, 100.0, 80.0);
        assert!(!zone_id.is_empty());
    }

    #[wasm_bindgen_test]
    fn test_simulate_survivor() {
        let dashboard = MatDashboard::new();
        dashboard.create_event("earthquake", 0.0, 0.0, "Test");
        dashboard.add_rectangle_zone("Zone A", 0.0, 0.0, 100.0, 80.0);

        let survivor_id = dashboard.simulate_survivor_detection(50.0, 40.0, -2.0, 0, 0.85);
        assert!(!survivor_id.is_empty());
    }
}
