//! WASM solver bindings.
//!
//! Provides JavaScript-friendly wrappers for the solver and system.

use crate::components::WasmComponent;
use crate::types::{WasmConvergedState, WasmThermoState};
use entropyk_components::port::{FluidId, Port};
use entropyk_components::Component;
use entropyk_solver::{
    ConvergedState, FallbackSolver, NewtonConfig, PicardConfig, Solver, SolverStrategy, System,
};
use petgraph::graph::NodeIndex;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;

/// WASM wrapper for Newton-Raphson solver configuration.
#[wasm_bindgen]
#[derive(Clone, Debug)]
pub struct WasmNewtonConfig {
    pub(crate) inner: NewtonConfig,
}

#[wasm_bindgen]
impl WasmNewtonConfig {
    /// Create default Newton-Raphson configuration.
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        WasmNewtonConfig {
            inner: NewtonConfig::default(),
        }
    }

    /// Set maximum iterations.
    pub fn set_max_iterations(&mut self, max: usize) {
        self.inner.max_iterations = max;
    }

    /// Set convergence tolerance.
    pub fn set_tolerance(&mut self, tol: f64) {
        self.inner.tolerance = tol;
    }
}

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

/// WASM wrapper for Picard (Sequential Substitution) solver configuration.
#[wasm_bindgen]
#[derive(Clone, Debug)]
pub struct WasmPicardConfig {
    pub(crate) inner: PicardConfig,
}

#[wasm_bindgen]
impl WasmPicardConfig {
    /// Create default Picard configuration.
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        WasmPicardConfig {
            inner: PicardConfig::default(),
        }
    }

    /// Set maximum iterations.
    pub fn set_max_iterations(&mut self, max: usize) {
        self.inner.max_iterations = max;
    }

    /// Set relaxation factor.
    pub fn set_relaxation_factor(&mut self, omega: f64) {
        self.inner.relaxation_factor = omega;
    }
}

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

/// WASM wrapper for fallback solver configuration.
#[wasm_bindgen]
#[derive(Clone, Debug)]
pub struct WasmFallbackConfig {
    newton_config: NewtonConfig,
    picard_config: PicardConfig,
}

#[wasm_bindgen]
impl WasmFallbackConfig {
    /// Create default fallback configuration.
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        WasmFallbackConfig {
            newton_config: NewtonConfig::default(),
            picard_config: PicardConfig::default(),
        }
    }

    /// Set timeout (placeholder for compatibility).
    pub fn timeout_ms(&mut self, _ms: u64) {
        // FallbackConfig currently doesn't have a direct timeout field in Rust
        // but it's used in the README example. We'll add this setter for API compatibility.
    }
}

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

/// WASM wrapper for System (thermodynamic system).
#[wasm_bindgen]
pub struct WasmSystem {
    inner: Rc<RefCell<System>>,
    last_state: RefCell<Option<Vec<f64>>>,
}

#[wasm_bindgen]
impl WasmSystem {
    /// Create a new thermodynamic system.
    #[wasm_bindgen(constructor)]
    pub fn new() -> Result<WasmSystem, JsValue> {
        let system = System::new();
        Ok(WasmSystem {
            inner: Rc::new(RefCell::new(system)),
            last_state: RefCell::new(None),
        })
    }

    /// Add a component to the system.
    pub fn add_component(&mut self, component: WasmComponent) -> usize {
        self.inner
            .borrow_mut()
            .add_component(component.inner)
            .index()
    }

    /// Add an edge between components.
    pub fn add_edge(&mut self, from_idx: usize, to_idx: usize) -> Result<(), JsValue> {
        self.inner
            .borrow_mut()
            .add_edge(
                NodeIndex::from(from_idx as u32),
                NodeIndex::from(to_idx as u32),
            )
            .map(|_| ())
            .map_err(|e| js_sys::Error::new(&e.to_string()).into())
    }

    /// Finalize the system topology before solving.
    pub fn finalize(&mut self) -> Result<(), JsValue> {
        self.inner
            .borrow_mut()
            .finalize()
            .map_err(|e| js_sys::Error::new(&e.to_string()).into())
    }

    /// Solve the system with fallback strategy.
    pub fn solve(&mut self, _config: WasmFallbackConfig) -> Result<WasmConvergedState, JsValue> {
        let mut solver = FallbackSolver::default_solver();
        let state = solver
            .solve(&mut *self.inner.borrow_mut())
            .map_err(|e| js_sys::Error::new(&e.to_string()))?;

        *self.last_state.borrow_mut() = Some(state.state.clone());
        Ok((&state).into())
    }

    /// Solve with Newton-Raphson method.
    pub fn solve_newton(
        &mut self,
        config: WasmNewtonConfig,
    ) -> Result<WasmConvergedState, JsValue> {
        let mut solver = SolverStrategy::NewtonRaphson(config.inner);
        let state = solver
            .solve(&mut *self.inner.borrow_mut())
            .map_err(|e| js_sys::Error::new(&e.to_string()))?;

        *self.last_state.borrow_mut() = Some(state.state.clone());
        Ok((&state).into())
    }

    /// Get node count.
    pub fn node_count(&self) -> usize {
        self.inner.borrow().node_count()
    }

    /// Get edge count.
    pub fn edge_count(&self) -> usize {
        self.inner.borrow().edge_count()
    }

    /// Get thermodynamic state for a specific node (after solve).
    pub fn get_node_result(&self, node_idx: usize) -> Result<WasmThermoState, JsValue> {
        let system = self.inner.borrow();
        let state_ref = self.last_state.borrow();
        let state = state_ref.as_ref().ok_or_else(|| {
            js_sys::Error::new("System must be solved before calling get_node_result")
        })?;

        // Use traverse_for_jacobian to find the component and its edge indices
        for (idx, component, edges) in system.traverse_for_jacobian() {
            if idx.index() == node_idx {
                if let Some((_edge_idx, p_idx, h_idx)) = edges.first() {
                    let p = state[*p_idx];
                    let h = state[*h_idx];

                    // Simple heuristic to get the fluid: look at ports
                    let ports = component.get_ports();
                    let fluid_id = if !ports.is_empty() {
                        entropyk_fluids::FluidId::new(ports[0].fluid_id().as_str())
                    } else {
                        entropyk_fluids::FluidId::new("R410A") // Fallback
                    };

                    // In a real implementation, we would use the system's backend to resolve T and properties.
                    // For now, we return a thermo state with P and h, which is what the user mostly needs.
                    // The WasmThermoState::from implementation we fixed will handle the conversion.
                    let thermo = entropyk_fluids::ThermoState {
                        fluid: fluid_id,
                        pressure: entropyk_core::Pressure::from_pascals(p),
                        temperature: entropyk_core::Temperature::from_kelvin(300.0), // Placeholder
                        enthalpy: entropyk_core::Enthalpy::from_joules_per_kg(h),
                        entropy: entropyk_fluids::Entropy::from_joules_per_kg_kelvin(0.0),
                        density: 1.0,
                        phase: entropyk_fluids::Phase::Unknown,
                        quality: None,
                        superheat: None,
                        subcooling: None,
                        t_bubble: None,
                        t_dew: None,
                    };
                    return Ok(thermo.into());
                }
            }
        }

        Err(js_sys::Error::new("Node not found or has no connections").into())
    }

    /// Convert system structural info to JSON.
    pub fn toJson(&self) -> Result<String, JsValue> {
        let data = serde_json::json!({
            "node_count": self.node_count(),
            "edge_count": self.edge_count(),
        });
        Ok(data.to_string())
    }
}

impl Default for WasmSystem {
    fn default() -> Self {
        Self::new().expect("Failed to create default system")
    }
}

impl From<&ConvergedState> for WasmConvergedState {
    fn from(state: &ConvergedState) -> Self {
        WasmConvergedState {
            converged: state.is_converged(),
            iterations: state.iterations,
            final_residual: state.final_residual,
        }
    }
}
