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

// IntBase.hpp
// Base definitions for the multi-precision integer class

#ifndef SANGI_INT_BASE_HPP
#define SANGI_INT_BASE_HPP

#include "../../common.hpp"         // SANGI_API
#include "../../basic_types.hpp"
#include "../../numeric_state.hpp"  // includes the definition of NumericStateTraits
#include "UInt128.hpp"
#include "SboWords.hpp"
#include <vector>
#include <string>
#include <string_view>
#include <cstdint>
#include <limits>
#include <iosfwd>   // forward declarations of std::ostream, std::istream
#include <span>     // C++20 span for zero-copy operations
#include <compare>  // C++20 three-way comparison operator
#include <utility>      // std::pair
#include <type_traits>  // std::is_integral_v, std::enable_if_t

namespace sangi {

    // Forward declarations
    class IntOps;
    class IntSpecialStates;
    class IntIOUtils;

    /**
     * @brief Multi-precision integer class
     *
     * A class representing arbitrary-precision integers. Internally, the value is held as
     * a variable-length array of 64-bit words.
     */
    class SANGI_API Int {
    public:
        // No upper bound on word count — arbitrary precision limited only by memory
        // (SboWords is size_t-based, so there is no type constraint either)

        // Constructor
        Int();
        Int(int8_t value)  : Int(static_cast<int>(value)) {}
        Int(uint8_t value) : Int(static_cast<int>(value)) {}
        Int(short value)   : Int(static_cast<int>(value)) {}
        Int(unsigned short value) : Int(static_cast<int>(value)) {}
        Int(int value);
        Int(uint64_t value);
        Int(int64_t value);
        // Constructor from string
        explicit Int(std::string_view str, int base = 10);
        Int(const Int& other);
        Int(Int&& other) noexcept;

        // Destructor
        ~Int() = default;

        // Assignment operator
        Int& operator=(const Int& other);
        Int& operator=(Int&& other) noexcept;
        Int& operator=(int value);

        // Generate special values
        [[nodiscard]] static Int Zero() { return Int(0); }
        [[nodiscard]] static Int One() { return Int(1); }
        [[nodiscard]] static Int NaN();
        [[nodiscard]] static Int PositiveInfinity();
        [[nodiscard]] static Int NegativeInfinity();
        [[nodiscard]] static Int fromState(NumericState state, NumericError error = NumericError::None) {
            Int result;

            // Initialize appropriately based on the state
            switch (state) {
            case NumericState::Normal:
                // Ordinary value (initialized to zero)
                result = Int(0);
                break;

            case NumericState::NaN:
                // NaN state
                result.m_state = NumericState::NaN;
                result.m_error = error;
                result.m_sign = 0;  // For NaN, the sign is meaningless
                result.m_words.clear();  // Clear the word array
                break;

            case NumericState::PositiveInfinity:
                // Positive infinity
                result.m_state = NumericState::PositiveInfinity;
                result.m_error = error;
                result.m_sign = 1;  // Positive sign
                result.m_words.clear();  // Clear the word array
                break;

            case NumericState::NegativeInfinity:
                // Negative infinity
                result.m_state = NumericState::NegativeInfinity;
                result.m_error = error;
                result.m_sign = -1;  // Negative sign
                result.m_words.clear();  // Clear the word array
                break;

            case NumericState::ComplexInfinity:
                // Complex infinity
                result.m_state = NumericState::ComplexInfinity;
                result.m_error = error;
                result.m_sign = 0;  // The sign is meaningless
                result.m_words.clear();  // Clear the word array
                break;

            case NumericState::Overflow:
                // Overflow
                result.m_state = NumericState::Overflow;
                result.m_error = error;
                result.m_sign = 1;  // Overflow is normally in the positive direction
                result.m_words.clear();  // Clear the word array
                break;

            case NumericState::Underflow:
                // Underflow (rare for integers, but supported for state completeness)
                result.m_state = NumericState::Underflow;
                result.m_error = error;
                result.m_sign = 1;  // Underflow is normally in the positive direction
                result.m_words.clear();  // Clear the word array
                break;

            case NumericState::PositiveZero:
                // Positive zero (not usually distinguished for multi-precision integers, but supported for state completeness)
                result.m_state = NumericState::PositiveZero;
                result.m_error = error;
                result.m_sign = 0;  // The sign of zero is 0
                result.m_words.clear();  // Clear the word array
                break;

            case NumericState::NegativeZero:
                // Negative zero (not usually distinguished for multi-precision integers, but supported for state completeness)
                result.m_state = NumericState::NegativeZero;
                result.m_error = error;
                result.m_sign = 0;  // The sign of zero is 0
                result.m_words.clear();  // Clear the word array
                break;

            case NumericState::Divergent:
            case NumericState::Oscillating:
            case NumericState::SlowConvergence:
            case NumericState::NotConverged:
            case NumericState::TruncatedConvergence:
                // Convergence-related state
                result.m_state = state;
                result.m_error = error;
                result.m_sign = 0;  // The sign is meaningless
                result.m_words.clear();  // Clear the word array
                break;

            default:
                // Other special states
                result.m_state = state;
                result.m_error = error;
                result.m_sign = 0;  // Unsigned by default
                result.m_words.clear();  // Clear the word array
                break;
            }

            return result;
        }
        [[nodiscard]] static Int fromRawWords(const std::vector<uint64_t>& words, int sign) {
            // Create a new Int object
            Int result;

            // Return 0 for an empty array
            if (words.empty()) {
                result.m_words.clear();
                result.m_sign = 0;  // The sign of 0 is 0
                result.m_state = NumericState::Normal;
                result.m_error = NumericError::None;
                return result;
            }

            // Copy the word array
            result.m_words.assign(words.begin(), words.end());

            // Set the sign (validating input)
            if (sign > 0) {
                result.m_sign = 1;  // Positive value
            }
            else if (sign < 0) {
                result.m_sign = -1; // Negative value
            }
            else {
                result.m_sign = 0;  // 0 (interpreted as 0 when sign is 0, even if word array is non-empty)
                result.m_words.clear();  // Clear the word array since the value is 0
            }

            // Set the state to normal
            result.m_state = NumericState::Normal;
            result.m_error = NumericError::None;

            // Strip leading zeros and normalize
            result.normalize();

            return result;
        }

        // span-based fromRawWords (zero-copy optimization)
        [[nodiscard]] static Int fromRawWords(std::span<const uint64_t> words, int sign) {
            // Create a new Int object
            Int result;

            // Return 0 for an empty array
            if (words.empty()) {
                result.m_words.clear();
                result.m_sign = 0;
                result.m_state = NumericState::Normal;
                result.m_error = NumericError::None;
                return result;
            }

            // Copy from span to vector (ultimately required)
            result.m_words.assign(words.begin(), words.end());

            // Set the sign
            if (sign > 0) {
                result.m_sign = 1;
            }
            else if (sign < 0) {
                result.m_sign = -1;
            }
            else {
                result.m_sign = 0;
                result.m_words.clear();
            }

            // Set the state to normal
            result.m_state = NumericState::Normal;
            result.m_error = NumericError::None;

            // Normalize
            result.normalize();

            return result;
        }

        // Fast path for already MSB-normalized data (skips normalize())
        // Precondition: the MSB word of words is non-zero (no leading zeros)
        [[nodiscard]] static Int fromRawWordsPreNormalized(std::span<const uint64_t> words, int sign) {
            Int result;
            if (words.empty()) {
                result.m_words.clear();
                result.m_sign = 0;
                result.m_state = NumericState::Normal;
                result.m_error = NumericError::None;
                return result;
            }
            result.m_words.assign(words.begin(), words.end());
            result.m_sign = (sign > 0) ? 1 : (sign < 0) ? -1 : 0;
            if (result.m_sign == 0) result.m_words.clear();
            result.m_state = NumericState::Normal;
            result.m_error = NumericError::None;
            return result;
        }

        // Factory methods for special values (camelCase version)
        [[nodiscard]] static Int nan();
        [[nodiscard]] static Int infinity();
        [[nodiscard]] static Int negInfinity();

        // State query methods
        [[nodiscard]] constexpr bool isZero() const { return m_state == NumericState::Normal && m_words.empty(); }
        [[nodiscard]] constexpr bool isOne() const {
            // false for special states
            if (m_state != NumericState::Normal) {
                return false;
            }

            // false unless the sign is positive
            if (m_sign <= 0) {
                return false;
            }

            // false unless the word count is 1
            if (m_words.size() != 1) {
                return false;
            }

            // true if the single word's value is 1
            return m_words[0] == 1;
        }
        [[nodiscard]] constexpr bool isPositive() const { return m_sign > 0 && !isZero(); }
        [[nodiscard]] constexpr bool isNegative() const { return m_sign < 0; }
        [[nodiscard]] constexpr bool isNaN() const { return m_state == NumericState::NaN; }
        [[nodiscard]] constexpr bool isInfinite() const {
            return m_state == NumericState::PositiveInfinity ||
                m_state == NumericState::NegativeInfinity;
        }
        [[nodiscard]] constexpr bool isSpecialState() const { return m_state != NumericState::Normal; }
        [[nodiscard]] constexpr int getSign() const { return m_sign; }
        [[nodiscard]] constexpr NumericState getState() const { return m_state; }
        [[nodiscard]] constexpr NumericError getError() const { return m_error; }
        [[nodiscard]] constexpr bool isNormal() const { return m_state == NumericState::Normal; }
        [[nodiscard]] constexpr bool isDivergent() const { return NumericStateTraits::isDivergent(m_state); }
        [[nodiscard]] constexpr DivergenceDetail getDivergenceDetail() const { return DivergenceDetail::None; }

        // Even/odd predicate
        [[nodiscard]] bool isEven() const {
            // false for special states
            if (isSpecialState()) return false;
            // Zero is even
            if (isZero()) return true;
            // Even if the least significant bit is 0
            return !getBit(0);
        }

        [[nodiscard]] bool isOdd() const {
            // false for special states
            if (isSpecialState()) return false;
            // Zero is even (not odd)
            if (isZero()) return false;
            // Odd if the least significant bit is 1
            return getBit(0);
        }

        // Type-range predicate
        [[nodiscard]] constexpr bool fitsByte() const {
            if (isSpecialState()) return false;
            if (m_words.size() > 1) return false;
            if (m_words.empty()) return true;
            uint64_t w = m_words[0];
            if (m_sign >= 0) {
                return w <= 127;
            } else {
                return w <= 128;
            }
        }

        [[nodiscard]] constexpr bool fitsUByte() const {
            if (isSpecialState()) return false;
            if (m_sign < 0) return false;
            if (m_words.size() > 1) return false;
            if (m_words.empty()) return true;
            return m_words[0] <= 255;
        }

        [[nodiscard]] constexpr bool fitsShort() const {
            if (isSpecialState()) return false;
            if (m_words.size() > 1) return false;
            if (m_words.empty()) return true;
            uint64_t w = m_words[0];
            if (m_sign >= 0) {
                return w <= 32767;
            } else {
                return w <= 32768;
            }
        }

        [[nodiscard]] constexpr bool fitsUShort() const {
            if (isSpecialState()) return false;
            if (m_sign < 0) return false;
            if (m_words.size() > 1) return false;
            if (m_words.empty()) return true;
            return m_words[0] <= 65535;
        }

        [[nodiscard]] constexpr bool fitsInt() const {
            // Special states do not fit
            if (isSpecialState()) return false;
            // int range: [-2^31, 2^31-1]
            if (m_words.size() > 1) return false;
            if (m_words.empty()) return true;  // Zero
            uint64_t w = m_words[0];
            if (m_sign >= 0) {
                return w <= static_cast<uint64_t>(std::numeric_limits<int>::max());
            } else {
                return w <= (static_cast<uint64_t>(std::numeric_limits<int>::max()) + 1);
            }
        }

        [[nodiscard]] constexpr bool fitsUInt() const {
            if (isSpecialState()) return false;
            if (m_sign < 0) return false;
            if (m_words.size() > 1) return false;
            if (m_words.empty()) return true;
            return m_words[0] <= static_cast<uint64_t>(std::numeric_limits<unsigned int>::max());
        }

        [[nodiscard]] constexpr bool fitsInt64() const {
            // Special states do not fit
            if (isSpecialState()) return false;
            // int64_t range: [-2^63, 2^63-1]
            if (m_words.size() > 1) return false;
            if (m_words.empty()) return true;  // Zero
            uint64_t w = m_words[0];
            if (m_sign >= 0) {
                return w <= static_cast<uint64_t>(std::numeric_limits<int64_t>::max());
            } else {
                return w <= (static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1);
            }
        }

        [[nodiscard]] constexpr bool fitsUInt64() const {
            // Special states do not fit
            if (isSpecialState()) return false;
            // Negative numbers do not fit in uint64_t
            if (m_sign < 0) return false;
            // uint64_t range: [0, 2^64-1]
            if (m_words.size() > 1) return false;
            // Always fits when one word or fewer
            return true;
        }

        // Return the digit count in the specified base
        [[nodiscard]] size_t digitCount(int base = 10) const;

        // Fast digit-count estimate in the specified base (GMP mpz_sizeinbase compatible)
        // Exact for power-of-two bases; otherwise exact or one greater
        [[nodiscard]] size_t sizeInBase(int base = 10) const;

        // Integer log2 (= bitLength - 1). Returns 0 for values <= 0
        [[nodiscard]] size_t ilog2() const;

        // Integer log10 (= digitCount(10) - 1). Returns 0 for values <= 0
        [[nodiscard]] size_t ilog10() const;

        // Divisibility test
        [[nodiscard]] bool isDivisible(const Int& divisor) const;

        // Congruence test
        [[nodiscard]] bool isCongruent(const Int& other, const Int& modulus) const;

        // Swap two Int values
        void swap(Int& other) noexcept {
            m_words.swap(other.m_words);
            std::swap(m_sign, other.m_sign);
            std::swap(m_state, other.m_state);
            std::swap(m_error, other.m_error);
        }

        // Word access methods
        [[nodiscard]] constexpr uint64_t word(size_t index) const { return (index < m_words.size()) ? m_words[index] : 0; }
        [[nodiscard]] constexpr size_t size() const { return m_words.size(); }
        [[nodiscard]] std::vector<uint64_t> words() const { return std::vector<uint64_t>(m_words.begin(), m_words.end()); }
        [[nodiscard]] const uint64_t* data() const noexcept { return m_words.data(); }

        // Remove leading/trailing zero words in place. Returns the number of low-end words removed.
        // Used by Float::normalize() for exponent adjustment.
        size_t trimZeroWords();

        // Numeric conversions
        [[nodiscard]] int toInt() const;
        [[nodiscard]] int64_t toInt64() const;
        [[nodiscard]] uint64_t toUInt64() const;
        [[nodiscard]] size_t toSizeT() const;
        [[nodiscard]] double toDouble() const;
        // double * 2^exp decomposition (equivalent to GMP mpz_get_d_2exp)
        // Return d in [0.5, 1.0); *exp is the exponent of 2. |this| = d * 2^(*exp)
        // When zero, d=0.0 and *exp=0
        [[nodiscard]] double toDouble2Exp(int64_t* exp) const;
        [[nodiscard]] std::string toString(int base = 10) const;

        // String-related methods
        [[nodiscard]] std::string toHexString(bool uppercase = false) const;
        [[nodiscard]] std::string toBinaryString() const;
        [[nodiscard]] std::string toOctalString() const;
        [[nodiscard]] static Int fromString(std::string_view str, int base = 10);

        // Bit-manipulation methods
        [[nodiscard]] bool getBit(size_t position) const;
        void setBit(size_t position);
        void clearBit(size_t position);
        void complementBit(size_t position);
        void combit(size_t position) { complementBit(position); }  ///< Alias equivalent to GMP mpz_combit
        [[nodiscard]] size_t bitLength() const;
        [[nodiscard]] size_t countLeadingZeros() const;
        [[nodiscard]] size_t countTrailingZeros() const;

        // Divisible by 2^b (equivalent to GMP mpz_divisible_2exp_p)
        // true if the lower b bits are all 0. Always true when b == 0.
        [[nodiscard]] bool isDivisibleBy2Exp(size_t b) const;

        // Whether (this - c) is divisible by 2^b (equivalent to GMP mpz_congruent_2exp_p)
        // true if the lower b bits of this and c match.
        [[nodiscard]] bool isCongruent2Exp(const Int& c, size_t b) const;

        // Population count (number of set bits; equivalent to GMP mpz_popcount)
        // Non-negative integer: return the number of set bits
        // Negative integer: since two's-complement has infinitely many 1 bits, return SIZE_MAX
        // (GMP compatible: ULONG_MAX for negatives)
        [[nodiscard]] size_t popcount() const;

        // Find the position of the first set/clear bit
        // GMP: mpz_scan0, mpz_scan1
        [[nodiscard]] size_t scanBit0(size_t startPos = 0) const;
        [[nodiscard]] size_t scanBit1(size_t startPos = 0) const;

        // Hamming distance (number of differing bit positions)
        // GMP: mpz_hamdist
        [[nodiscard]] static size_t hammingDistance(const Int& a, const Int& b);

        // Unary operators (member functions)
        [[nodiscard]] Int operator-() const& {
            if (m_sign == 0) [[likely]] return *this;
            if (m_state == NumericState::Normal) [[likely]] {
                Int result = *this;
                result.m_sign = -m_sign;
                return result;
            }
            return negate_slow();
        }
        [[nodiscard]] Int operator-() && {
            if (m_sign == 0) [[likely]] return std::move(*this);
            if (m_state == NumericState::Normal) [[likely]] {
                m_sign = -m_sign;
                return std::move(*this);
            }
            return negate_slow();
        }
        [[nodiscard]] Int operator+() const { return *this; }

        // Compound assignment operators (as member functions)
        Int& operator+=(const Int& other);
        Int& operator-=(const Int& other);
        Int& operator*=(const Int& other);
        Int& operator/=(const Int& other);
        Int& operator%=(const Int& other);

        // Single-word compound assignment (equivalent to GMP _ui/_si: avoids building a temporary Int)
        // The template accepts all integer types and dispatches to uint64_t/int64_t internally
        template<typename T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, int> = 0>
        Int& operator+=(T rhs) {
            if constexpr (std::is_signed_v<T>) return plusEqScalar(static_cast<int64_t>(rhs));
            else return plusEqScalar(static_cast<uint64_t>(rhs));
        }
        template<typename T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, int> = 0>
        Int& operator-=(T rhs) {
            if constexpr (std::is_signed_v<T>) return minusEqScalar(static_cast<int64_t>(rhs));
            else return minusEqScalar(static_cast<uint64_t>(rhs));
        }
        template<typename T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, int> = 0>
        Int& operator*=(T rhs) {
            if constexpr (std::is_signed_v<T>) return mulEqScalar(static_cast<int64_t>(rhs));
            else return mulEqScalar(static_cast<uint64_t>(rhs));
        }
        template<typename T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, int> = 0>
        Int& operator/=(T rhs) {
            if constexpr (std::is_signed_v<T>) return divEqScalar(static_cast<int64_t>(rhs));
            else return divEqScalar(static_cast<uint64_t>(rhs));
        }
        template<typename T, std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, int> = 0>
        Int& operator%=(T rhs) {
            if constexpr (std::is_signed_v<T>) return modEqScalar(static_cast<int64_t>(rhs));
            else return modEqScalar(static_cast<uint64_t>(rhs));
        }

    private:
        [[nodiscard]] Int negate_slow() const;  // NaN/Infinity path for operator-()
        Int& plusEqScalar(int64_t rhs);
        Int& plusEqScalar(uint64_t rhs);
        Int& minusEqScalar(int64_t rhs);
        Int& minusEqScalar(uint64_t rhs);
        Int& mulEqScalar(int64_t rhs);
        Int& mulEqScalar(uint64_t rhs);
        Int& divEqScalar(int64_t rhs);
        Int& divEqScalar(uint64_t rhs);
        Int& modEqScalar(int64_t rhs);
        Int& modEqScalar(uint64_t rhs);
    public:

        // Bitwise operators (as member functions)
        Int& operator&=(const Int& other);
        Int& operator|=(const Int& other);
        Int& operator<<=(int shift);
        Int& operator>>=(int shift);

        // Power assignment operator (a ^= b is equivalent to a = pow(a, b))
        Int& operator^=(const Int& other);

        // State-management methods
        /// Negate the sign in place (O(1), no copy).
        /// Since `-a` (operator-) involves a copy, prefer
        /// `a.negate()` when assigning the result back.
        void negate() {
            if (m_state == NumericState::Normal) [[likely]] {
                m_sign = -m_sign;
            }
        }

        void setSign(int sign) {
            m_sign = sign;
            // When in the Normal state with zero value (m_words empty), the sign must be 0
            if (sign != 0 && m_state == NumericState::Normal && m_words.empty()) [[unlikely]] {
                m_sign = 0;
            }
        }
        void setState(NumericState state, NumericError error = NumericError::None);

        // Friend declarations for comparison operators (C++20 three-way)
        friend std::partial_ordering operator<=>(const Int& lhs, const Int& rhs);
        friend bool operator==(const Int& lhs, const Int& rhs);

        // Friend declarations for bitwise operators
        friend Int operator~(const Int& value);
        friend Int operator~(Int&& value);

        // Friend declarations for stream operators
        friend std::ostream& operator<<(std::ostream& os, const Int& value);
        friend std::istream& operator>>(std::istream& is, Int& value);

        // Friend declarations for utility functions
        friend class IntOps;
        friend class IntSpecialStates;
        friend class IntIOUtils;
        friend class Float;
        friend class FloatOps;
        friend class IntDivision;
        friend class IntMultiplication;
        friend class IntRandom;
        friend class IntSqrt;

        friend Int abs(const Int& value);
        friend Int abs(Int&& value);

    private:
        // Internal state
        SboWords m_words;  // Array of 64-bit words representing the value (SBO: 4 words inline)
        int m_sign;                    // Sign
        NumericState m_state;           // Numeric state
        NumericError m_error;           // Error information

        // Helper function for absolute-value comparison
        static int compareAbsoluteValues(const Int& lhs, const Int& rhs);

        // Method that normalizes the internal state
        void normalize();

        // Set a special state
        void setNaN(NumericError error = NumericError::None);
    };

    //------------------------------------------------------------------------------
    // Global operator declarations
    //------------------------------------------------------------------------------

    // Arithmetic operators
    [[nodiscard]] Int operator+(const Int& lhs, const Int& rhs);
    [[nodiscard]] Int operator+(Int&& lhs, const Int& rhs);
    [[nodiscard]] Int operator+(const Int& lhs, Int&& rhs);
    [[nodiscard]] Int operator+(Int&& lhs, Int&& rhs);
    [[nodiscard]] Int operator-(const Int& lhs, const Int& rhs);
    [[nodiscard]] Int operator-(Int&& lhs, const Int& rhs);
    [[nodiscard]] Int operator-(const Int& lhs, Int&& rhs);
    [[nodiscard]] Int operator-(Int&& lhs, Int&& rhs);
    [[nodiscard]] Int operator*(const Int& lhs, const Int& rhs);
    [[nodiscard]] Int operator/(const Int& lhs, const Int& rhs);
    [[nodiscard]] Int operator/(Int&& lhs, const Int& rhs);
    [[nodiscard]] Int operator%(const Int& lhs, const Int& rhs);
    [[nodiscard]] Int operator%(Int&& lhs, const Int& rhs);

    // Single-word arithmetic operators — implementation functions (IntOperators.cpp)
    // User code should use the template version
    [[nodiscard]] Int addScalar(const Int& lhs, uint64_t rhs);
    [[nodiscard]] Int addScalar(Int&& lhs, uint64_t rhs);
    [[nodiscard]] Int addScalar(const Int& lhs, int64_t rhs);
    [[nodiscard]] Int addScalar(Int&& lhs, int64_t rhs);
    [[nodiscard]] Int subScalar(const Int& lhs, uint64_t rhs);
    [[nodiscard]] Int subScalar(Int&& lhs, uint64_t rhs);
    [[nodiscard]] Int subScalar(const Int& lhs, int64_t rhs);
    [[nodiscard]] Int subScalar(Int&& lhs, int64_t rhs);
    [[nodiscard]] Int rsubScalar(uint64_t lhs, const Int& rhs);
    [[nodiscard]] Int rsubScalar(int64_t lhs, const Int& rhs);
    [[nodiscard]] Int mulScalar(const Int& lhs, uint64_t rhs);
    [[nodiscard]] Int mulScalar(Int&& lhs, uint64_t rhs);
    [[nodiscard]] Int mulScalar(const Int& lhs, int64_t rhs);
    [[nodiscard]] Int mulScalar(Int&& lhs, int64_t rhs);
    [[nodiscard]] Int divScalar(const Int& lhs, uint64_t rhs);
    [[nodiscard]] Int divScalar(Int&& lhs, uint64_t rhs);
    [[nodiscard]] Int divScalar(const Int& lhs, int64_t rhs);
    [[nodiscard]] Int divScalar(Int&& lhs, int64_t rhs);
    [[nodiscard]] uint64_t modScalarU(const Int& lhs, uint64_t rhs);
    [[nodiscard]] Int modScalar(const Int& lhs, int64_t rhs);

    // Single-word comparison operators — implementation functions
    [[nodiscard]] std::partial_ordering cmpScalar(const Int& lhs, int64_t rhs);
    [[nodiscard]] bool eqScalar(const Int& lhs, int64_t rhs);
    [[nodiscard]] std::partial_ordering cmpScalar(const Int& lhs, uint64_t rhs);
    [[nodiscard]] bool eqScalar(const Int& lhs, uint64_t rhs);

    // Template operators: accept all integer types and avoid overload ambiguity
    namespace detail {
        template<typename T> struct is_scalar_int : std::bool_constant<
            std::is_integral_v<T> && !std::is_same_v<T, bool>> {};
        template<typename T> struct is_signed_scalar : std::bool_constant<
            is_scalar_int<T>::value && std::is_signed_v<T>> {};
        template<typename T> struct is_unsigned_scalar : std::bool_constant<
            is_scalar_int<T>::value && std::is_unsigned_v<T>> {};
    }

    template<typename T, std::enable_if_t<detail::is_scalar_int<T>::value, int> = 0>
    [[nodiscard]] Int operator+(const Int& lhs, T rhs) {
        if constexpr (std::is_signed_v<T>) return addScalar(lhs, static_cast<int64_t>(rhs));
        else return addScalar(lhs, static_cast<uint64_t>(rhs));
    }
    template<typename T, std::enable_if_t<detail::is_scalar_int<T>::value, int> = 0>
    [[nodiscard]] Int operator+(Int&& lhs, T rhs) {
        if constexpr (std::is_signed_v<T>) return addScalar(std::move(lhs), static_cast<int64_t>(rhs));
        else return addScalar(std::move(lhs), static_cast<uint64_t>(rhs));
    }
    template<typename T, std::enable_if_t<detail::is_scalar_int<T>::value, int> = 0>
    [[nodiscard]] Int operator+(T lhs, const Int& rhs) {
        if constexpr (std::is_signed_v<T>) return addScalar(rhs, static_cast<int64_t>(lhs));
        else return addScalar(rhs, static_cast<uint64_t>(lhs));
    }
    template<typename T, std::enable_if_t<detail::is_scalar_int<T>::value, int> = 0>
    [[nodiscard]] Int operator-(const Int& lhs, T rhs) {
        if constexpr (std::is_signed_v<T>) return subScalar(lhs, static_cast<int64_t>(rhs));
        else return subScalar(lhs, static_cast<uint64_t>(rhs));
    }
    template<typename T, std::enable_if_t<detail::is_scalar_int<T>::value, int> = 0>
    [[nodiscard]] Int operator-(Int&& lhs, T rhs) {
        if constexpr (std::is_signed_v<T>) return subScalar(std::move(lhs), static_cast<int64_t>(rhs));
        else return subScalar(std::move(lhs), static_cast<uint64_t>(rhs));
    }
    template<typename T, std::enable_if_t<detail::is_scalar_int<T>::value, int> = 0>
    [[nodiscard]] Int operator-(T lhs, const Int& rhs) {
        if constexpr (std::is_signed_v<T>) return rsubScalar(static_cast<int64_t>(lhs), rhs);
        else return rsubScalar(static_cast<uint64_t>(lhs), rhs);
    }
    template<typename T, std::enable_if_t<detail::is_scalar_int<T>::value, int> = 0>
    [[nodiscard]] Int operator*(const Int& lhs, T rhs) {
        if constexpr (std::is_signed_v<T>) return mulScalar(lhs, static_cast<int64_t>(rhs));
        else return mulScalar(lhs, static_cast<uint64_t>(rhs));
    }
    template<typename T, std::enable_if_t<detail::is_scalar_int<T>::value, int> = 0>
    [[nodiscard]] Int operator*(Int&& lhs, T rhs) {
        if constexpr (std::is_signed_v<T>) return mulScalar(std::move(lhs), static_cast<int64_t>(rhs));
        else return mulScalar(std::move(lhs), static_cast<uint64_t>(rhs));
    }
    template<typename T, std::enable_if_t<detail::is_scalar_int<T>::value, int> = 0>
    [[nodiscard]] Int operator*(T lhs, const Int& rhs) {
        if constexpr (std::is_signed_v<T>) return mulScalar(rhs, static_cast<int64_t>(lhs));
        else return mulScalar(rhs, static_cast<uint64_t>(lhs));
    }
    template<typename T, std::enable_if_t<detail::is_scalar_int<T>::value, int> = 0>
    [[nodiscard]] Int operator/(const Int& lhs, T rhs) {
        if constexpr (std::is_signed_v<T>) return divScalar(lhs, static_cast<int64_t>(rhs));
        else return divScalar(lhs, static_cast<uint64_t>(rhs));
    }
    template<typename T, std::enable_if_t<detail::is_scalar_int<T>::value, int> = 0>
    [[nodiscard]] Int operator/(Int&& lhs, T rhs) {
        if constexpr (std::is_signed_v<T>) return divScalar(std::move(lhs), static_cast<int64_t>(rhs));
        else return divScalar(std::move(lhs), static_cast<uint64_t>(rhs));
    }
    template<typename T, std::enable_if_t<detail::is_unsigned_scalar<T>::value, int> = 0>
    [[nodiscard]] uint64_t operator%(const Int& lhs, T rhs) {
        return modScalarU(lhs, static_cast<uint64_t>(rhs));
    }
    template<typename T, std::enable_if_t<detail::is_signed_scalar<T>::value, int> = 0>
    [[nodiscard]] Int operator%(const Int& lhs, T rhs) {
        return modScalar(lhs, static_cast<int64_t>(rhs));
    }

    // Single-word comparison operators
    template<typename T, std::enable_if_t<detail::is_signed_scalar<T>::value, int> = 0>
    [[nodiscard]] std::partial_ordering operator<=>(const Int& lhs, T rhs) {
        return cmpScalar(lhs, static_cast<int64_t>(rhs));
    }
    template<typename T, std::enable_if_t<detail::is_signed_scalar<T>::value, int> = 0>
    [[nodiscard]] bool operator==(const Int& lhs, T rhs) {
        return eqScalar(lhs, static_cast<int64_t>(rhs));
    }
    template<typename T, std::enable_if_t<detail::is_unsigned_scalar<T>::value, int> = 0>
    [[nodiscard]] std::partial_ordering operator<=>(const Int& lhs, T rhs) {
        return cmpScalar(lhs, static_cast<uint64_t>(rhs));
    }
    template<typename T, std::enable_if_t<detail::is_unsigned_scalar<T>::value, int> = 0>
    [[nodiscard]] bool operator==(const Int& lhs, T rhs) {
        return eqScalar(lhs, static_cast<uint64_t>(rhs));
    }

    // Increment/decrement operators
    Int& operator++(Int& value);
    [[nodiscard]] Int operator++(Int& value, int);
    Int& operator--(Int& value);
    [[nodiscard]] Int operator--(Int& value, int);

    // Bitwise operators
    [[nodiscard]] Int operator&(const Int& lhs, const Int& rhs);
    [[nodiscard]] Int operator&(Int&& lhs, const Int& rhs);
    [[nodiscard]] Int operator&(const Int& lhs, Int&& rhs);
    [[nodiscard]] Int operator&(Int&& lhs, Int&& rhs);
    [[nodiscard]] Int operator|(const Int& lhs, const Int& rhs);
    [[nodiscard]] Int operator|(Int&& lhs, const Int& rhs);
    [[nodiscard]] Int operator|(const Int& lhs, Int&& rhs);
    [[nodiscard]] Int operator|(Int&& lhs, Int&& rhs);
    [[nodiscard]] Int operator~(const Int& value);
    [[nodiscard]] Int operator~(Int&& value);
    [[nodiscard]] Int operator<<(const Int& lhs, int shift);
    [[nodiscard]] Int operator<<(Int&& lhs, int shift);
    [[nodiscard]] Int operator>>(const Int& lhs, int shift);
    [[nodiscard]] Int operator>>(Int&& lhs, int shift);

    // Power operator (mathematical notation: a ^ b = pow(a, b))
    [[nodiscard]] Int operator^(const Int& lhs, const Int& rhs);

    // Bitwise XOR (function form)
    [[nodiscard]] Int bitwiseXor(const Int& lhs, const Int& rhs);
    [[nodiscard]] Int bitwiseXor(Int&& lhs, const Int& rhs);
    [[nodiscard]] Int bitwiseXor(const Int& lhs, Int&& rhs);
    [[nodiscard]] Int bitwiseXor(Int&& lhs, Int&& rhs);

    // Comparison operators (C++20 three-way)
    std::partial_ordering operator<=>(const Int& lhs, const Int& rhs);
    bool operator==(const Int& lhs, const Int& rhs);

    // Stream operators
    std::ostream& operator<<(std::ostream& os, const Int& value);
    std::istream& operator>>(std::istream& is, Int& value);

    // Utility functions
    [[nodiscard]] Int abs(const Int& value);
    [[nodiscard]] Int abs(Int&& value);
    [[nodiscard]] Int gcd(const Int& a, const Int& b);
    [[nodiscard]] Int lcm(const Int& a, const Int& b);
    [[nodiscard]] Int pow(const Int& base, unsigned int exponent);
    [[nodiscard]] Int pow(const Int& base, const Int& exponent);
    [[nodiscard]] Int powMod(const Int& base, const Int& exponent, const Int& modulus);
    [[nodiscard]] std::size_t bitCount(const Int& value);

    // Factor removal (GMP mpz_remove compatible)
    // Remove all instances of factor from value, returning {remaining value, count}
    [[nodiscard]] std::pair<Int, uint64_t> removeFactor(const Int& value, const Int& factor);

    // swap function (defined inside the namespace for ADL)
    inline void swap(Int& a, Int& b) noexcept {
        a.swap(b);
    }
    [[nodiscard]] bool isProbablePrime(const Int& value, int iterations = 25);

} // namespace sangi

#endif // SANGI_INT_BASE_HPP