//! Python exception types mapped from Entropyk errors.

use pyo3::create_exception;
use pyo3::exceptions::PyException;
use pyo3::prelude::*;

// Exception hierarchy:
//   EntropykError (base)
//     ├── SolverError
//     │     ├── TimeoutError
//     │     └── ControlSaturationError
//     ├── FluidError
//     ├── ComponentError
//     ├── TopologyError
//     └── ValidationError

create_exception!(
    entropyk,
    EntropykError,
    PyException,
    "Base exception for all Entropyk errors."
);
create_exception!(
    entropyk,
    SolverError,
    EntropykError,
    "Error during solving (non-convergence, divergence)."
);
create_exception!(
    entropyk,
    TimeoutError,
    SolverError,
    "Solver timed out before convergence."
);
create_exception!(
    entropyk,
    ControlSaturationError,
    SolverError,
    "Control variable reached saturation limit."
);
create_exception!(
    entropyk,
    FluidError,
    EntropykError,
    "Error during fluid property calculation."
);
create_exception!(
    entropyk,
    ComponentError,
    EntropykError,
    "Error from component operations."
);
create_exception!(
    entropyk,
    TopologyError,
    EntropykError,
    "Error in system topology (graph structure)."
);
create_exception!(
    entropyk,
    ValidationError,
    EntropykError,
    "Validation error (calibration, constraints)."
);

/// Registers all exception types in the Python module.
pub fn register_exceptions(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add("EntropykError", m.py().get_type::<EntropykError>())?;
    m.add("SolverError", m.py().get_type::<SolverError>())?;
    m.add("TimeoutError", m.py().get_type::<TimeoutError>())?;
    m.add(
        "ControlSaturationError",
        m.py().get_type::<ControlSaturationError>(),
    )?;
    m.add("FluidError", m.py().get_type::<FluidError>())?;
    m.add("ComponentError", m.py().get_type::<ComponentError>())?;
    m.add("TopologyError", m.py().get_type::<TopologyError>())?;
    m.add("ValidationError", m.py().get_type::<ValidationError>())?;
    Ok(())
}

/// Converts a `ThermoError` into the appropriate Python exception.
#[allow(dead_code)]
pub fn thermo_error_to_pyerr(err: entropyk::ThermoError) -> PyErr {
    use entropyk::ThermoError;
    match &err {
        ThermoError::Solver(solver_err) => {
            let msg = err.to_string();
            let solver_msg = solver_err.to_string();
            // Check for timeout and control saturation sub-types
            if solver_msg.contains("timeout") || solver_msg.contains("Timeout") {
                TimeoutError::new_err(msg)
            } else if solver_msg.contains("saturation") || solver_msg.contains("Saturation") {
                ControlSaturationError::new_err(msg)
            } else if solver_msg.contains("validation") || solver_msg.contains("Validation") {
                ValidationError::new_err(msg)
            } else {
                SolverError::new_err(msg)
            }
        }
        ThermoError::Fluid(_) => FluidError::new_err(err.to_string()),
        ThermoError::Component(_) | ThermoError::Connection(_) => {
            ComponentError::new_err(err.to_string())
        }
        ThermoError::Topology(_) | ThermoError::AddEdge(_) => {
            TopologyError::new_err(err.to_string())
        }
        ThermoError::Calibration(_) | ThermoError::Constraint(_) => {
            ValidationError::new_err(err.to_string())
        }
        // Map Validation errors (mass/energy balance violations) to ValidationError
        ThermoError::Validation { .. } => ValidationError::new_err(err.to_string()),
        ThermoError::Initialization(_)
        | ThermoError::Builder(_)
        | ThermoError::Mixture(_)
        | ThermoError::InvalidInput(_)
        | ThermoError::NotSupported(_)
        | ThermoError::NotFinalized => EntropykError::new_err(err.to_string()),
    }
}
