//! C-compatible error codes for FFI.
//!
//! Maps Rust `ThermoError` variants to C enum values.

use std::os::raw::c_char;

/// Error codes returned by FFI functions.
///
/// All functions that can fail return an `EntropykErrorCode`.
/// Use `entropyk_error_string()` to get a human-readable message.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntropykErrorCode {
    /// Operation succeeded.
    EntropykOk = 0,
    /// Solver did not converge.
    EntropykNonConvergence = 1,
    /// Solver timed out.
    EntropykTimeout = 2,
    /// Control variable reached saturation limit.
    EntropykControlSaturation = 3,
    /// Fluid property calculation error.
    EntropykFluidError = 4,
    /// Invalid thermodynamic state.
    EntropykInvalidState = 5,
    /// Validation error (calibration, constraints).
    EntropykValidationError = 6,
    /// Null pointer passed to function.
    EntropykNullPointer = 7,
    /// Invalid argument value.
    EntropykInvalidArgument = 8,
    /// System not finalized before operation.
    EntropykNotFinalized = 9,
    /// Topology error in system graph.
    EntropykTopologyError = 10,
    /// Component error.
    EntropykComponentError = 11,
    /// Unknown error.
    EntropykUnknown = 99,
}

impl Default for EntropykErrorCode {
    fn default() -> Self {
        Self::EntropykOk
    }
}

impl From<&entropyk::ThermoError> for EntropykErrorCode {
    fn from(err: &entropyk::ThermoError) -> Self {
        use entropyk::ThermoError;
        match err {
            ThermoError::Solver(s) => {
                let msg = s.to_string();
                if msg.contains("timeout") || msg.contains("Timeout") {
                    EntropykErrorCode::EntropykTimeout
                } else if msg.contains("saturation") || msg.contains("Saturation") {
                    EntropykErrorCode::EntropykControlSaturation
                } else {
                    EntropykErrorCode::EntropykNonConvergence
                }
            }
            ThermoError::Fluid(_) => EntropykErrorCode::EntropykFluidError,
            ThermoError::Component(_) => EntropykErrorCode::EntropykComponentError,
            ThermoError::Connection(_) => EntropykErrorCode::EntropykComponentError,
            ThermoError::Topology(_) | ThermoError::AddEdge(_) => {
                EntropykErrorCode::EntropykTopologyError
            }
            ThermoError::Calibration(_) | ThermoError::Constraint(_) => {
                EntropykErrorCode::EntropykValidationError
            }
            ThermoError::Initialization(_) => EntropykErrorCode::EntropykInvalidState,
            ThermoError::Builder(_) => EntropykErrorCode::EntropykInvalidArgument,
            ThermoError::Mixture(_) => EntropykErrorCode::EntropykFluidError,
            ThermoError::InvalidInput(_) => EntropykErrorCode::EntropykInvalidArgument,
            ThermoError::NotSupported(_) => EntropykErrorCode::EntropykInvalidArgument,
            ThermoError::NotFinalized => EntropykErrorCode::EntropykNotFinalized,
            ThermoError::Validation { .. } => EntropykErrorCode::EntropykValidationError,
        }
    }
}

impl From<entropyk::ThermoError> for EntropykErrorCode {
    fn from(err: entropyk::ThermoError) -> Self {
        Self::from(&err)
    }
}

impl From<entropyk_solver::SolverError> for EntropykErrorCode {
    fn from(err: entropyk_solver::SolverError) -> Self {
        let msg = err.to_string();
        if msg.contains("timeout") || msg.contains("Timeout") {
            EntropykErrorCode::EntropykTimeout
        } else if msg.contains("saturation") || msg.contains("Saturation") {
            EntropykErrorCode::EntropykControlSaturation
        } else {
            EntropykErrorCode::EntropykNonConvergence
        }
    }
}

impl From<entropyk_solver::TopologyError> for EntropykErrorCode {
    fn from(_: entropyk_solver::TopologyError) -> Self {
        EntropykErrorCode::EntropykTopologyError
    }
}

impl From<entropyk_solver::AddEdgeError> for EntropykErrorCode {
    fn from(_: entropyk_solver::AddEdgeError) -> Self {
        EntropykErrorCode::EntropykTopologyError
    }
}

/// Get a human-readable error message for an error code.
///
/// # Safety
///
/// The returned pointer is valid for the lifetime of the program.
/// Do NOT free the returned pointer.
#[no_mangle]
pub unsafe extern "C" fn entropyk_error_string(code: EntropykErrorCode) -> *const c_char {
    static OK: &[u8] = b"Success\0";
    static NON_CONVERGENCE: &[u8] = b"Solver did not converge\0";
    static TIMEOUT: &[u8] = b"Solver timed out\0";
    static CONTROL_SATURATION: &[u8] = b"Control variable reached saturation limit\0";
    static FLUID_ERROR: &[u8] = b"Fluid property calculation error\0";
    static INVALID_STATE: &[u8] = b"Invalid thermodynamic state\0";
    static VALIDATION_ERROR: &[u8] = b"Validation error\0";
    static NULL_POINTER: &[u8] = b"Null pointer passed to function\0";
    static INVALID_ARGUMENT: &[u8] = b"Invalid argument value\0";
    static NOT_FINALIZED: &[u8] = b"System not finalized before operation\0";
    static TOPOLOGY_ERROR: &[u8] = b"Topology error in system graph\0";
    static COMPONENT_ERROR: &[u8] = b"Component error\0";
    static UNKNOWN: &[u8] = b"Unknown error\0";

    let msg: &[u8] = match code {
        EntropykErrorCode::EntropykOk => OK,
        EntropykErrorCode::EntropykNonConvergence => NON_CONVERGENCE,
        EntropykErrorCode::EntropykTimeout => TIMEOUT,
        EntropykErrorCode::EntropykControlSaturation => CONTROL_SATURATION,
        EntropykErrorCode::EntropykFluidError => FLUID_ERROR,
        EntropykErrorCode::EntropykInvalidState => INVALID_STATE,
        EntropykErrorCode::EntropykValidationError => VALIDATION_ERROR,
        EntropykErrorCode::EntropykNullPointer => NULL_POINTER,
        EntropykErrorCode::EntropykInvalidArgument => INVALID_ARGUMENT,
        EntropykErrorCode::EntropykNotFinalized => NOT_FINALIZED,
        EntropykErrorCode::EntropykTopologyError => TOPOLOGY_ERROR,
        EntropykErrorCode::EntropykComponentError => COMPONENT_ERROR,
        EntropykErrorCode::EntropykUnknown => UNKNOWN,
    };

    msg.as_ptr() as *const c_char
}
