// Copyright (C) 2026 Kiyotsugu Arai
// SPDX-License-Identifier: LGPL-3.0-or-later

// common.hpp
// Common definitions, exceptions, and configuration
//
// This file provides the common definitions, exception classes, global
// configuration, and utility functions used throughout the MKL algebra
// library.
//
// Main features:
// - Namespace definitions
// - Version information
// - MKL detection and configuration
// - Exception class hierarchy
// - Common utility functions
// - Compile-time configuration
// - Thread management
// - Exceptions and configuration related to state management

#ifndef SANGI_COMMON_HPP
#define SANGI_COMMON_HPP

// Compiler compatibility macros
#ifdef _MSC_VER
#define SANGI_FORCEINLINE __forceinline
#else
#define SANGI_FORCEINLINE __attribute__((always_inline)) inline
#endif

// ==============================================================================
// DLL export/import macros
// ==============================================================================
// SANGI_SHARED_LIB: defined for DLL builds (set by CMake)
// SANGI_BUILDING_CORE: defined while building the sangi_core library itself
//
// Usage:
//   class SANGI_API Int { ... };          -> subject to DLL export/import
//   SANGI_API int someFunction();         -> exports the function
//   Header-only templates need not be marked -> templates do not need SANGI_API
//
#ifdef SANGI_SHARED_LIB
    #ifdef _MSC_VER
        #ifdef SANGI_BUILDING_CORE
            #define SANGI_API __declspec(dllexport)
        #else
            #define SANGI_API __declspec(dllimport)
        #endif
    #else
        #define SANGI_API __attribute__((visibility("default")))
    #endif
#else
    #define SANGI_API
#endif

#include <math/core/basic_types.hpp>
#include <math/core/numeric_state.hpp>
#include <algorithm>
#include <cmath>
#include <complex>
#include <cstddef>
#include <exception>
#include <limits>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <sstream>

// Library namespace
namespace sangi {

    // Version information struct
    struct Version {
        static constexpr int major = VERSION_MAJOR;
        static constexpr int minor = VERSION_MINOR;
        static constexpr int patch = VERSION_PATCH;

        // Get the version string
        static constexpr std::string_view string() {
            return "2.0.0"; // Should ideally be generated dynamically, but the constexpr constraint forces a fixed value
        }
    };

    // Platform detection
#if defined(_WIN32) || defined(_WIN64)
#define SANGI_PLATFORM_WINDOWS
#elif defined(__APPLE__)
#define SANGI_PLATFORM_MACOS
#elif defined(__linux__)
#define SANGI_PLATFORM_LINUX
#else
#define SANGI_PLATFORM_UNKNOWN
#endif

// Compiler detection
#if defined(_MSC_VER)
#define SANGI_COMPILER_MSVC
#elif defined(__clang__)
#define SANGI_COMPILER_CLANG
#elif defined(__GNUC__)
#define SANGI_COMPILER_GCC
#else
#define SANGI_COMPILER_UNKNOWN
#endif

// C++ version detection
#if defined(_MSC_VER)
    // For Visual Studio, detect the /std:c++XX option
#if defined(_MSVC_LANG)
#if _MSVC_LANG >= 202302L
#define SANGI_CPP23
#define SANGI_CPP20
#elif _MSVC_LANG >= 202002L
#define SANGI_CPP20
#else
// Error is disabled so it still compiles under pre-C++20
// #error "C++20 or later is required"
#endif
#else
    // The /std:c++XX option was not specified
// #error "Please specify /std:c++latest or /std:c++20 or later"
#endif
#else
    // Other compilers
#if __cplusplus >= 202302L
#define SANGI_CPP23
#define SANGI_CPP20
#elif __cplusplus >= 202002L
#define SANGI_CPP20
#else
// Error is disabled so it still compiles under pre-C++20
// #error "C++20 or later is required"
#endif
#endif

// MKL / OpenBLAS BLAS backend detection
// Priority: MKL > OpenBLAS > none (self-implemented gemm_blocked fallback)
//
// - SANGI_HAS_MKL:      MKL is available; mkl.h + cblas_* + VML (vsExp, etc.) are usable
// - SANGI_HAS_OPENBLAS: OpenBLAS is available; only cblas_* is usable (no VML)
// - SANGI_HAS_CBLAS:    the cblas_* API is available (either MKL or OpenBLAS)
// - SANGI_HAS_VML:      MKL VML (vsExp / vsFmax / vsLn, etc.) is available (MKL only)
#if defined(SANGI_USE_MKL)
#include <mkl.h>
#define SANGI_HAS_MKL 1
#define SANGI_HAS_OPENBLAS 0
#define SANGI_HAS_CBLAS 1
#define SANGI_HAS_VML 1
#elif defined(SANGI_USE_OPENBLAS)
#include <cblas.h>
#define SANGI_HAS_MKL 0
#define SANGI_HAS_OPENBLAS 1
#define SANGI_HAS_CBLAS 1
#define SANGI_HAS_VML 0
#else
#define SANGI_HAS_MKL 0
#define SANGI_HAS_OPENBLAS 0
#define SANGI_HAS_CBLAS 0
#define SANGI_HAS_VML 0
#endif

    // Abstraction for the BLAS integer type
    //   MKL (ilp64):       MKL_INT = 64-bit
    //   OpenBLAS (default): blasint = 32-bit int (64-bit when built with OPENBLAS_USE64BITINT)
    //   Neither:            int
    // User code should consistently use sangi::blas_int_t. cblas_* calls cast through this typedef.
#if SANGI_HAS_MKL
    using blas_int_t = MKL_INT;
#elif SANGI_HAS_OPENBLAS
    using blas_int_t = blasint;
#else
    using blas_int_t = int;
#endif

    // Detect the available SIMD instruction set
    constexpr SimdInstructionSet detect_simd_level() {
#if defined(__AVX512F__)
        return SimdInstructionSet::AVX512;
#elif defined(__AVX2__)
        return SimdInstructionSet::AVX2;
#elif defined(__AVX__)
        return SimdInstructionSet::AVX;
#elif defined(__SSE4_2__)
        return SimdInstructionSet::SSE4_2;
#elif defined(__SSE4_1__)
        return SimdInstructionSet::SSE4_1;
#elif defined(__SSSE3__)
        return SimdInstructionSet::SSSE3;
#elif defined(__SSE3__)
        return SimdInstructionSet::SSE3;
#elif defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)
        return SimdInstructionSet::SSE2;
#elif defined(__ARM_NEON) || defined(__ARM_NEON__)
        return SimdInstructionSet::NEON;
#else
        return SimdInstructionSet::None;
#endif
    }

    // The available SIMD instruction set
    constexpr SimdInstructionSet available_simd_level = detect_simd_level();

    // Mathematical constants
    namespace constants {
        template <typename T>
        inline constexpr T pi = static_cast<T>(3.14159265358979323846);

        template <typename T>
        inline constexpr T e = static_cast<T>(2.71828182845904523536);

        template <typename T>
        inline constexpr T sqrt2 = static_cast<T>(1.41421356237309504880);

        template <typename T>
        inline constexpr T log2e = static_cast<T>(1.44269504088896340736);

        template <typename T>
        inline constexpr T log10e = static_cast<T>(0.43429448190325182765);
    }

    // Exception class hierarchy

    // Base exception class for the MKL algebra library
    class MklAlgebraException : public std::exception {
    public:
        explicit MklAlgebraException(const std::string& message) : message_(message) {}
        explicit MklAlgebraException(std::string&& message) : message_(std::move(message)) {}

        const char* what() const noexcept override {
            return message_.c_str();
        }

    private:
        std::string message_;
    };

    // Mathematical errors (invalid operations, domain errors, etc.)
    class MathError : public MklAlgebraException {
    public:
        using MklAlgebraException::MklAlgebraException;
    };

    // Dimension errors (mismatched matrix or vector dimensions)
    class DimensionError : public MklAlgebraException {
    public:
        using MklAlgebraException::MklAlgebraException;
    };

    // Index errors (out-of-range index access)
    class IndexError : public MklAlgebraException {
    public:
        using MklAlgebraException::MklAlgebraException;
    };

    // Linear algebra errors (singular matrix, etc.)
    class LinearAlgebraError : public MklAlgebraException {
    public:
        using MklAlgebraException::MklAlgebraException;
    };

    // Convergence errors (numerical algorithm fails to converge)
    class ConvergenceError : public MklAlgebraException {
    public:
        using MklAlgebraException::MklAlgebraException;
    };

    // MKL errors (an MKL function failed)
    class MklError : public MklAlgebraException {
    public:
        explicit MklError(const std::string& message, int error_code)
            : MklAlgebraException(message + " (error code: " + std::to_string(error_code) + ")"),
            error_code_(error_code) {
        }

        int error_code() const noexcept {
            return error_code_;
        }

    private:
        int error_code_;
    };

    // Errors related to numeric state
    class NumericStateError : public MathError {
    public:
        explicit NumericStateError(const std::string& message)
            : MathError(message), state_(NumericState::Normal), error_(NumericError::None) {
        }

        explicit NumericStateError(const std::string& message, NumericState state, NumericError error = NumericError::None)
            : MathError(message), state_(state), error_(error) {
        }

        NumericState getState() const noexcept {
            return state_;
        }

        NumericError getError() const noexcept {
            return error_;
        }

    private:
        NumericState state_;
        NumericError error_;
    };

    // NaN error
    class NaNError : public NumericStateError {
    public:
        explicit NaNError(const std::string& message = "Operation resulted in NaN")
            : NumericStateError(message, NumericState::NaN, NumericError::ExplicitNaN) {
        }
    };

    // Infinity error
    class InfinityError : public NumericStateError {
    public:
        explicit InfinityError(const std::string& message = "Operation resulted in infinity",
            bool is_positive = true)
            : NumericStateError(message,
                is_positive ? NumericState::PositiveInfinity
                : NumericState::NegativeInfinity,
                NumericError::None),
            is_positive_(is_positive) {
        }

        bool isPositive() const noexcept {
            return is_positive_;
        }

    private:
        bool is_positive_;
    };

    // Overflow error
    class OverflowError : public NumericStateError {
    public:
        explicit OverflowError(const std::string& message = "Numeric overflow occurred")
            : NumericStateError(message, NumericState::Overflow, NumericError::OutOfRangeInput) {
        }
    };

    // Underflow error
    class UnderflowError : public NumericStateError {
    public:
        explicit UnderflowError(const std::string& message = "Numeric underflow occurred")
            : NumericStateError(message, NumericState::Underflow, NumericError::OutOfRangeInput) {
        }
    };

    // Division-by-zero error
    class DivideByZeroError : public NumericStateError {
    public:
        explicit DivideByZeroError(const std::string& message = "Division by zero")
            : NumericStateError(message, NumericState::NaN, NumericError::DivideByZero) {
        }
    };

    // Divergence error
    class DivergenceStateError : public NumericStateError {
    public:
        explicit DivergenceStateError(const std::string& message = "Algorithm diverged",
            DivergenceDetail detail = DivergenceDetail::Diverging)
            : NumericStateError(message, NumericState::Divergent, NumericError::DivergenceError),
            detail_(detail) {
        }

        DivergenceDetail getDetail() const noexcept {
            return detail_;
        }

    private:
        DivergenceDetail detail_;
    };

    // State-error handling configuration
    struct StateErrorOptions {
        // Handling mode per error type
        StateErrorMode nan_error_mode = StateErrorMode::ReturnSpecial;            // NaN-related errors
        StateErrorMode infinity_error_mode = StateErrorMode::ReturnSpecial;       // Infinity-related errors
        StateErrorMode overflow_error_mode = StateErrorMode::ReturnSpecial;       // Overflow-related errors
        StateErrorMode underflow_error_mode = StateErrorMode::ReturnSpecial;      // Underflow-related errors
        StateErrorMode divide_by_zero_error_mode = StateErrorMode::ReturnSpecial; // Division-by-zero errors
        StateErrorMode divergence_error_mode = StateErrorMode::Exception;         // Divergence-related errors

        // Default settings
        static StateErrorOptions defaults() {
            return {};
        }

        // Exception-only mode
        static StateErrorOptions exceptions_only() {
            StateErrorOptions options;
            options.nan_error_mode = StateErrorMode::Exception;
            options.infinity_error_mode = StateErrorMode::Exception;
            options.overflow_error_mode = StateErrorMode::Exception;
            options.underflow_error_mode = StateErrorMode::Exception;
            options.divide_by_zero_error_mode = StateErrorMode::Exception;
            options.divergence_error_mode = StateErrorMode::Exception;
            return options;
        }

        // Special-values-only mode
        static StateErrorOptions special_values_only() {
            StateErrorOptions options;
            options.nan_error_mode = StateErrorMode::ReturnSpecial;
            options.infinity_error_mode = StateErrorMode::ReturnSpecial;
            options.overflow_error_mode = StateErrorMode::ReturnSpecial;
            options.underflow_error_mode = StateErrorMode::ReturnSpecial;
            options.divide_by_zero_error_mode = StateErrorMode::ReturnSpecial;
            options.divergence_error_mode = StateErrorMode::ReturnSpecial;
            return options;
        }

        // Silent mode
        static StateErrorOptions silent() {
            StateErrorOptions options;
            options.nan_error_mode = StateErrorMode::Silent;
            options.infinity_error_mode = StateErrorMode::Silent;
            options.overflow_error_mode = StateErrorMode::Silent;
            options.underflow_error_mode = StateErrorMode::Silent;
            options.divide_by_zero_error_mode = StateErrorMode::Silent;
            options.divergence_error_mode = StateErrorMode::Silent;
            return options;
        }

        // Get the mode based on a NumericError
        StateErrorMode getModeForError(NumericError error) const {
            switch (error) {
            case NumericError::DivideByZero:
                return divide_by_zero_error_mode;
            case NumericError::NaNPropagation:
            case NumericError::ExplicitNaN:
                return nan_error_mode;
            case NumericError::OutOfRangeInput:
            case NumericError::ConversionError:
            case NumericError::IntegerConversionError:
                return overflow_error_mode;
            case NumericError::DivergenceError:
                return divergence_error_mode;
            default:
                return StateErrorMode::ReturnSpecial;
            }
        }

        // Get the mode based on a NumericState
        StateErrorMode getModeForState(NumericState state) const {
            if (NumericStateTraits::isNaN(state)) {
                return nan_error_mode;
            }
            if (NumericStateTraits::isInfinite(state)) {
                return infinity_error_mode;
            }
            if (state == NumericState::Overflow) {
                return overflow_error_mode;
            }
            if (state == NumericState::Underflow) {
                return underflow_error_mode;
            }
            if (NumericStateTraits::isDivergent(state)) {
                return divergence_error_mode;
            }
            return StateErrorMode::ReturnSpecial;
        }
    };

    // Global state-error handling configuration (not thread-safe)
    inline StateErrorOptions& global_state_error_options() {
        static StateErrorOptions options = StateErrorOptions::defaults();
        return options;
    }

    // Guard class that temporarily modifies StateErrorOptions and restores them at scope exit
    class StateErrorOptionsGuard {
    private:
        StateErrorOptions& options_;
        StateErrorMode original_nan_mode_;
        StateErrorMode original_infinity_mode_;
        StateErrorMode original_overflow_mode_;
        StateErrorMode original_underflow_mode_;
        StateErrorMode original_divide_by_zero_mode_;
        StateErrorMode original_divergence_mode_;

    public:
        explicit StateErrorOptionsGuard(StateErrorOptions& options)
            : options_(options),
            original_nan_mode_(options.nan_error_mode),
            original_infinity_mode_(options.infinity_error_mode),
            original_overflow_mode_(options.overflow_error_mode),
            original_underflow_mode_(options.underflow_error_mode),
            original_divide_by_zero_mode_(options.divide_by_zero_error_mode),
            original_divergence_mode_(options.divergence_error_mode)
        {
        }

        ~StateErrorOptionsGuard() {
            // Restore the settings in the destructor
            options_.nan_error_mode = original_nan_mode_;
            options_.infinity_error_mode = original_infinity_mode_;
            options_.overflow_error_mode = original_overflow_mode_;
            options_.underflow_error_mode = original_underflow_mode_;
            options_.divide_by_zero_error_mode = original_divide_by_zero_mode_;
            options_.divergence_error_mode = original_divergence_mode_;
        }

        // Non-copyable
        StateErrorOptionsGuard(const StateErrorOptionsGuard&) = delete;
        StateErrorOptionsGuard& operator=(const StateErrorOptionsGuard&) = delete;
    };

    // Result type (utility for error handling)
    template<typename T>
    class Result {
    public:
        // Create a success result
        static Result<T> success(T value) {
            return Result(std::move(value), std::nullopt);
        }

        // Create an error result
        static Result<T> error(std::string error_message) {
            return Result(std::nullopt, std::move(error_message));
        }

        // Whether the result is a success
        bool is_success() const {
            return value_.has_value();
        }

        // Whether the result is an error
        bool is_error() const {
            return error_message_.has_value();
        }

        // Get the value (throws on error)
        const T& value() const {
            if (is_error()) {
                throw MklAlgebraException(error_message_.value());
            }
            return value_.value();
        }

        // Get the value (throws on error)
        T& value() {
            if (is_error()) {
                throw MklAlgebraException(error_message_.value());
            }
            return value_.value();
        }

        // Get the error message (empty string on success)
        std::string error_message() const {
            return error_message_.value_or("");
        }

    private:
        Result(std::optional<T> value, std::optional<std::string> error_message)
            : value_(std::move(value)), error_message_(std::move(error_message)) {
        }

        std::optional<T> value_;
        std::optional<std::string> error_message_;
    };

    // Utility functions

    // Dimension check (throws on mismatch)
    inline void check_dimensions(std::size_t expected, std::size_t actual, const std::string& message) {
        if (expected != actual) {
            throw DimensionError(message + ": expected " + std::to_string(expected) +
                ", got " + std::to_string(actual));
        }
    }

    // Index check (throws on out-of-range)
    inline void check_index(std::size_t index, std::size_t size, const std::string& message) {
        if (index >= size) {
            throw IndexError(message + ": index " + std::to_string(index) +
                " out of range for size " + std::to_string(size));
        }
    }

    // Square-matrix check (throws if not square)
    inline void check_square(std::size_t rows, std::size_t cols, const std::string& message) {
        if (rows != cols) {
            throw DimensionError(message + ": matrix is not square (" +
                std::to_string(rows) + "x" + std::to_string(cols) + ")");
        }
    }

    // Numeric-state check function
    template<typename T>
    void check_numeric_state(T value, const std::string& operation = "Operation") {
        const auto& options = global_state_error_options();

        // NaN check
        if (std::isnan(value)) {
            switch (options.nan_error_mode) {
            case StateErrorMode::Exception:
                throw NaNError(operation + " resulted in NaN");
            case StateErrorMode::ReturnSpecial:
                // Nothing to do when returning special values (value is already NaN)
                break;
            case StateErrorMode::Silent:
                // Nothing to do in silent mode
                break;
            }
        }

        // Infinity check
        if (std::isinf(value)) {
            switch (options.infinity_error_mode) {
            case StateErrorMode::Exception:
                throw InfinityError(operation + " resulted in infinity");
            case StateErrorMode::ReturnSpecial:
                // Nothing to do when returning special values (value is already infinity)
                break;
            case StateErrorMode::Silent:
                // Nothing to do in silent mode
                break;
            }
        }

        // Overflow and underflow checks are difficult to perform on
        // floating-point arithmetic, so they are not implemented here.
    }

    // Zero check before division
    inline void check_divide_by_zero(double divisor, const std::string& message = "Division") {
        if (divisor == 0.0) {
            const auto& options = global_state_error_options();
            switch (options.divide_by_zero_error_mode) {
            case StateErrorMode::Exception:
                throw DivideByZeroError(message + " by zero");
            case StateErrorMode::ReturnSpecial:
            case StateErrorMode::Silent:
                // Nothing to do in these modes (the caller produces infinity or a special value)
                break;
            }
        }
    }

    // Numeric comparison function (floating-point comparison)
    template<typename T>
    constexpr bool approximately_equal(T a, T b, T epsilon = std::numeric_limits<T>::epsilon() * T(100)) {
        if (a == b) return true;
        auto safe_abs = [](const T& x) -> T { return (x < T(0)) ? -x : x; };
        const T diff = safe_abs(a - b);
        const T norm = std::min((safe_abs(a) + safe_abs(b)), std::numeric_limits<T>::max());
        return diff < std::max(std::numeric_limits<T>::min(), epsilon * norm);
    }

    // Numeric-state to string conversion
    inline std::string numeric_state_to_string(NumericState state) {
        return NumericStateTraits::toString(state);
    }

    // Divergence-detail to string conversion
    inline std::string divergence_detail_to_string(DivergenceDetail detail) {
        return NumericStateTraits::toString(detail);
    }

    // Error-cause to string conversion
    inline std::string numeric_error_to_string(NumericError error) {
        return NumericStateTraits::toString(error);
    }

    // Convert both state and error to a string at once
    inline std::string state_and_error_to_string(NumericState state, NumericError error) {
        if (error == NumericError::None) {
            return NumericStateTraits::toString(state);
        }
        else {
            return NumericStateTraits::toString(state) + " (" +
                NumericStateTraits::toString(error) + ")";
        }
    }

    // Thread management (MKL thread-count setting, etc.)
#if SANGI_HAS_MKL
    class ThreadManager {
    public:
        // Get the number of available threads
        static int get_max_threads() {
            return mkl_get_max_threads();
        }

        // Get the current number of threads
        static int get_num_threads() {
            return mkl_get_max_threads();
        }

        // Set the number of threads
        static void set_num_threads(int num_threads) {
            mkl_set_num_threads(num_threads);
        }

        // Temporarily change the number of threads (restored on scope exit)
        class ScopedThreads {
        public:
            explicit ScopedThreads(int num_threads)
                : previous_threads_(mkl_get_max_threads()) {
                mkl_set_num_threads(num_threads);
            }

            ~ScopedThreads() {
                mkl_set_num_threads(previous_threads_);
            }

            // Non-copyable
            ScopedThreads(const ScopedThreads&) = delete;
            ScopedThreads& operator=(const ScopedThreads&) = delete;

        private:
            const int previous_threads_;
        };
    };
#else
    class ThreadManager {
    public:
        // Stub implementation when MKL is unavailable
        static int get_max_threads() {
            return std::thread::hardware_concurrency();
        }

        static int get_num_threads() {
            return 1;
        }

        static void set_num_threads(int /* num_threads */) {
            // Stub implementation does nothing
        }

        class ScopedThreads {
        public:
            explicit ScopedThreads(int /* num_threads */) {}

            // Non-copyable
            ScopedThreads(const ScopedThreads&) = delete;
            ScopedThreads& operator=(const ScopedThreads&) = delete;
        };
    };
#endif

    // Memory alignment related
    namespace memory {
        // Compute the required alignment
        constexpr std::size_t simd_alignment() {
            switch (available_simd_level) {
            case SimdInstructionSet::AVX512:
                return 64;
            case SimdInstructionSet::AVX2:
            case SimdInstructionSet::AVX:
                return 32;
            case SimdInstructionSet::SSE4_2:
            case SimdInstructionSet::SSE4_1:
            case SimdInstructionSet::SSSE3:
            case SimdInstructionSet::SSE3:
            case SimdInstructionSet::SSE2:
            case SimdInstructionSet::NEON:
                return 16;
            default:
                return 8;
            }
        }

        // Align a value up to the given alignment
        inline std::size_t align_to(std::size_t size, std::size_t alignment) {
            return (size + alignment - 1) & ~(alignment - 1);
        }

        // Allocate aligned memory
        template<typename T>
        T* aligned_alloc(std::size_t size, std::size_t alignment = simd_alignment()) {
            // Use C++17 std::aligned_alloc or an equivalent function
#if defined(SANGI_COMPILER_MSVC)
            return static_cast<T*>(_aligned_malloc(size * sizeof(T), alignment));
#elif defined(_POSIX_VERSION)
            void* ptr = nullptr;
            if (posix_memalign(&ptr, alignment, size * sizeof(T)) != 0) {
                return nullptr;
            }
            return static_cast<T*>(ptr);
#else
            // Fallback alignment-allocation implementation usable on common machines
            void* ptr = std::malloc(size * sizeof(T) + alignment);
            if (!ptr) return nullptr;
            
            std::size_t offset = alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment);
            T* aligned = reinterpret_cast<T*>(reinterpret_cast<char*>(ptr) + offset);
            
            // Store the information needed to recover the original pointer
            *(reinterpret_cast<void**>(aligned) - 1) = ptr;
            
            return aligned;
#endif
        }

        // Free aligned memory
        template<typename T>
        void aligned_free(T* ptr) {
#if defined(SANGI_COMPILER_MSVC)
            _aligned_free(ptr);
#elif defined(_POSIX_VERSION)
            free(ptr);
#else
            // Fallback alignment-free implementation usable on common machines
            if (!ptr) return;
            // Retrieve the original pointer and free it
            void* original = *(reinterpret_cast<void**>(ptr) - 1);
            std::free(original);
#endif
        }
    }

    // General-purpose numeric-state check function (updated version)
    inline void check_numeric_state(NumericState state, NumericError error, const std::string& message) {
        const auto& options = global_state_error_options();

        // Check based on the error
        if (error != NumericError::None) {
            switch (error) {
            case NumericError::DivideByZero:
                if (options.divide_by_zero_error_mode == StateErrorMode::Exception) {
                    throw DivideByZeroError(message);
                }
                break;
            case NumericError::NaNPropagation:
            case NumericError::ExplicitNaN:
            case NumericError::InvalidBitPattern:
                if (options.nan_error_mode == StateErrorMode::Exception) {
                    throw NaNError(message);
                }
                break;
            case NumericError::OutOfRangeInput:
            case NumericError::ConversionError:
            case NumericError::IntegerConversionError:
                if (options.overflow_error_mode == StateErrorMode::Exception) {
                    throw OverflowError(message);
                }
                break;
            case NumericError::DivergenceError:
                if (options.divergence_error_mode == StateErrorMode::Exception) {
                    throw DivergenceStateError(message);
                }
                break;
            default:
                // No special handling for other errors
                break;
            }
        }

        // Check based on the state
        if (NumericStateTraits::isNaN(state)) {
            if (options.nan_error_mode == StateErrorMode::Exception) {
                throw NaNError(message);
            }
            return;
        }

        // Infinity check
        if (NumericStateTraits::isInfinite(state)) {
            if (options.infinity_error_mode == StateErrorMode::Exception) {
                throw InfinityError(message, state == NumericState::PositiveInfinity);
            }
            return;
        }

        // Overflow check
        if (state == NumericState::Overflow) {
            if (options.overflow_error_mode == StateErrorMode::Exception) {
                throw OverflowError(message);
            }
            return;
        }

        // Underflow check
        if (state == NumericState::Underflow) {
            if (options.underflow_error_mode == StateErrorMode::Exception) {
                throw UnderflowError(message);
            }
            return;
        }

        // Divergence check
        if (NumericStateTraits::isDivergent(state)) {
            if (options.divergence_error_mode == StateErrorMode::Exception) {
                throw DivergenceStateError(message);
            }
            return;
        }
    }

} // namespace sangi

#endif // SANGI_COMMON_HPP