//! Solver strategy implementations for thermodynamic system solving.
//!
//! This module provides the concrete solver implementations that can be used
//! via the [`Solver`] trait or the [`SolverStrategy`] enum for zero-cost
//! static dispatch.
//!
//! # Available Strategies
//!
//! - [`NewtonRaphson`] — Newton-Raphson solver with quadratic convergence
//! - [`SequentialSubstitution`] — Picard iteration solver, more robust for non-linear systems
//! - [`FallbackSolver`] — Intelligent fallback between Newton and Picard
//!
//! # Example
//!
//! ```rust
//! use entropyk_solver::solver::{Solver, SolverStrategy};
//! use std::time::Duration;
//!
//! let solver = SolverStrategy::default()
//!     .with_timeout(Duration::from_millis(500));
//! ```

mod fallback;
mod homotopy;
mod newton_raphson;
mod pseudo_transient;
mod sequential_substitution;
mod trust_region;

pub use fallback::{FallbackConfig, FallbackSolver};
pub use homotopy::HomotopyConfig;
pub use newton_raphson::NewtonConfig;
pub use pseudo_transient::PtcConfig;
pub use sequential_substitution::PicardConfig;
pub use trust_region::TrustRegionConfig;

use crate::solver::{ConvergedState, Solver, SolverError};
use crate::system::System;
use std::time::Duration;

/// Enum-based solver strategy dispatcher.
///
/// Provides zero-cost static dispatch to the selected solver strategy via
/// `match` (monomorphization), avoiding vtable overhead while still allowing
/// runtime strategy selection.
///
/// # Default
///
/// `SolverStrategy::default()` returns `NewtonRaphson(NewtonConfig::default())`.
///
/// # Example
///
/// ```rust
/// use entropyk_solver::solver::{Solver, SolverStrategy, PicardConfig};
/// use std::time::Duration;
///
/// let strategy = SolverStrategy::SequentialSubstitution(
///     PicardConfig { relaxation_factor: 0.3, ..Default::default() }
/// ).with_timeout(Duration::from_secs(1));
/// ```
#[derive(Debug, Clone, PartialEq)]
pub enum SolverStrategy {
    /// Newton-Raphson solver (quadratic convergence, requires Jacobian).
    NewtonRaphson(NewtonConfig),
    /// Sequential Substitution / Picard iteration (robust, no Jacobian needed).
    SequentialSubstitution(PicardConfig),
}

impl Default for SolverStrategy {
    /// Returns `SolverStrategy::NewtonRaphson(NewtonConfig::default())`.
    fn default() -> Self {
        SolverStrategy::NewtonRaphson(NewtonConfig::default())
    }
}

impl Solver for SolverStrategy {
    fn solve(&mut self, system: &mut System) -> Result<ConvergedState, SolverError> {
        tracing::info!(
            strategy = match self {
                SolverStrategy::NewtonRaphson(_) => "NewtonRaphson",
                SolverStrategy::SequentialSubstitution(_) => "SequentialSubstitution",
            },
            "SolverStrategy::solve dispatching"
        );
        let result = match self {
            SolverStrategy::NewtonRaphson(cfg) => cfg.solve(system),
            SolverStrategy::SequentialSubstitution(cfg) => cfg.solve(system),
        };

        if let Ok(state) = &result {
            if state.is_converged() {
                // Post-solve validation checks. Components index the state slice by
                // global index, so pass the raw (ṁ, P, h)-strided vector directly
                // rather than through the stride-2 SystemState conversion (CM1.2).
                let state_slice: &[f64] = &state.state;
                system.check_mass_balance(state_slice)?;
                system.check_energy_balance(state_slice)?;
            }
        }

        result
    }

    fn with_timeout(self, timeout: Duration) -> Self {
        match self {
            SolverStrategy::NewtonRaphson(cfg) => {
                SolverStrategy::NewtonRaphson(cfg.with_timeout(timeout))
            }
            SolverStrategy::SequentialSubstitution(cfg) => {
                SolverStrategy::SequentialSubstitution(cfg.with_timeout(timeout))
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::system::System;
    use std::time::Duration;

    /// Verify that `SolverStrategy::default()` returns Newton-Raphson.
    #[test]
    fn test_solver_strategy_default_is_newton_raphson() {
        let strategy = SolverStrategy::default();
        assert!(
            matches!(strategy, SolverStrategy::NewtonRaphson(_)),
            "Default strategy must be NewtonRaphson, got {:?}",
            strategy
        );
    }

    /// Verify that the Newton-Raphson variant wraps a `NewtonConfig`.
    #[test]
    fn test_solver_strategy_newton_raphson_variant() {
        let strategy = SolverStrategy::NewtonRaphson(NewtonConfig::default());
        match strategy {
            SolverStrategy::NewtonRaphson(cfg) => {
                assert_eq!(cfg.max_iterations, 100);
                assert!((cfg.tolerance - 1e-6).abs() < 1e-15);
                assert!(!cfg.line_search);
                assert!(cfg.timeout.is_none());
            }
            other => panic!("Expected NewtonRaphson, got {:?}", other),
        }
    }

    /// Verify that the Sequential Substitution variant wraps a `PicardConfig`.
    #[test]
    fn test_solver_strategy_sequential_substitution_variant() {
        let strategy = SolverStrategy::SequentialSubstitution(PicardConfig::default());
        match strategy {
            SolverStrategy::SequentialSubstitution(cfg) => {
                assert_eq!(cfg.max_iterations, 100);
                assert!((cfg.tolerance - 1e-6).abs() < 1e-15);
                assert!((cfg.relaxation_factor - 0.5).abs() < 1e-15);
                assert!(cfg.timeout.is_none());
            }
            other => panic!("Expected SequentialSubstitution, got {:?}", other),
        }
    }

    /// Verify that `with_timeout` on `SolverStrategy::NewtonRaphson` propagates to inner config.
    #[test]
    fn test_solver_strategy_newton_with_timeout() {
        let timeout = Duration::from_millis(500);
        let strategy = SolverStrategy::default().with_timeout(timeout);
        match strategy {
            SolverStrategy::NewtonRaphson(cfg) => {
                assert_eq!(cfg.timeout, Some(timeout));
            }
            other => panic!("Expected NewtonRaphson after with_timeout, got {:?}", other),
        }
    }

    /// Verify that `with_timeout` on `SolverStrategy::SequentialSubstitution` propagates.
    #[test]
    fn test_solver_strategy_picard_with_timeout() {
        let timeout = Duration::from_secs(1);
        let strategy =
            SolverStrategy::SequentialSubstitution(PicardConfig::default()).with_timeout(timeout);
        match strategy {
            SolverStrategy::SequentialSubstitution(cfg) => {
                assert_eq!(cfg.timeout, Some(timeout));
            }
            other => panic!(
                "Expected SequentialSubstitution after with_timeout, got {:?}",
                other
            ),
        }
    }

    /// Verify that `SolverStrategy::NewtonRaphson` dispatches to the Newton implementation.
    #[test]
    fn test_solver_strategy_newton_dispatch_reaches_stub() {
        let mut strategy = SolverStrategy::default(); // NewtonRaphson
        let mut system = System::new();
        system.finalize().unwrap();
        let result = strategy.solve(&mut system);
        // Empty system should return InvalidSystem
        assert!(
            result.is_err(),
            "Newton solver must return Err for empty system"
        );
        match result {
            Err(SolverError::InvalidSystem { ref message }) => {
                assert!(
                    message.contains("Empty") || message.contains("no state"),
                    "Newton dispatch must detect empty system, got: {}",
                    message
                );
            }
            other => panic!("Expected InvalidSystem from Newton solver, got {:?}", other),
        }
    }

    /// Verify that `SolverStrategy::SequentialSubstitution` dispatches to the Picard implementation.
    #[test]
    fn test_solver_strategy_picard_dispatch_reaches_implementation() {
        let mut strategy = SolverStrategy::SequentialSubstitution(PicardConfig::default());
        let mut system = System::new();
        system.finalize().unwrap();
        let result = strategy.solve(&mut system);
        assert!(
            result.is_err(),
            "Picard solver must return Err for empty system"
        );
        match result {
            Err(SolverError::InvalidSystem { ref message }) => {
                assert!(
                    message.contains("Empty") || message.contains("no state"),
                    "Picard dispatch must detect empty system, got: {}",
                    message
                );
            }
            other => panic!("Expected InvalidSystem from Picard solver, got {:?}", other),
        }
    }
}
