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

// IntIO.cpp
// Implements input/output operations for multi-precision integers

#include <math/core/mp/Int/IntIO.hpp>
#include <math/core/mp/Int/IntSpecialStates.hpp>
#include <math/core/mp/Int/IntBase.hpp>
#include <math/core/mp/Int/IntOps.hpp>
#include <sstream>
#include <iomanip>
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cinttypes>
#include <cstring>
#include <vector>

namespace sangi {

    std::string IntIOUtils::fromRawWords(const std::vector<uint64_t>& words, int sign) {
        if (words.empty()) {
            return "0";
        }

        // Create a new vector with leading zeros removed
        std::vector<uint64_t> trimmed_words;
        auto it = words.rbegin();
        // Find the first non-zero value
        while (it != words.rend() && *it == 0) {
            ++it;
        }
        // Copy the remaining valid values
        while (it != words.rend()) {
            trimmed_words.push_back(*it);
            ++it;
        }

        if (trimmed_words.empty()) {
            return "0";
        }

        std::string result;
        // Generate the hexadecimal representation
        for (auto it = trimmed_words.begin(); it != trimmed_words.end(); ++it) {
            std::stringstream ss;
            ss << std::hex << std::setfill('0');
            // Zero-pad to 16 digits for all values except the first
            if (it != trimmed_words.begin()) {
                ss << std::setw(16);
            }
            ss << *it;
            result += ss.str();
        }

        // Remove leading zeros
        result.erase(0, result.find_first_not_of('0'));
        if (result.empty()) {
            return "0";
        }

        // Add the sign
        if (sign < 0) {
            result = "-" + result;
        }

        return result;
    }

    // ================================================================
    // Decimal conversion: small-size naive + large-size divide-and-conquer
    // ================================================================
    //
    // Small size (< DC_TOSTRING_THRESHOLD limbs):
    //   Extract 18 digits at a time by repeating divmod_1(10^18). O(n²) but
    //   with a small constant, avoiding the DC overhead (multi-precision divmod, pow10 composition).
    //
    // Large size:
    //   Divide-and-conquer O(M(N) log N). Pre-allocate the output buffer and
    //   write directly at each recursion level (avoiding string concatenation).

    static constexpr double LOG10_2 = 0.30102999566398120;
    static constexpr uint64_t POW10_18 = 1000000000000000000ULL;  // 10^18
    static constexpr size_t DIGITS_PER_CHUNK = 18;

    // DC threshold: below this, use naive (repeated divmod_1)
    static constexpr size_t DC_TOSTRING_THRESHOLD = 30;

    // Estimate the number of decimal digits from the bit count
    static size_t estimateDecimalDigits(size_t bitLength) {
        return static_cast<size_t>(static_cast<double>(bitLength) * LOG10_2) + 1;
    }

    // Cache of 10^k (built by repeated squaring)
    // cache[i] = 10^(2^i) — i=0: 10, i=1: 100, i=2: 10000, ...
    static std::vector<Int> s_pow10_cache;

    // Compute 10^n efficiently (with caching)
    static Int pow10_cached(size_t n) {
        if (n == 0) return Int(1);

        // Build the cache: s_pow10_cache[i] = 10^(2^i)
        while (s_pow10_cache.empty() || (size_t(1) << (s_pow10_cache.size() - 1)) < n) {
            if (s_pow10_cache.empty()) {
                s_pow10_cache.push_back(Int(10));  // 10^1
            } else {
                const Int& prev = s_pow10_cache.back();
                s_pow10_cache.push_back(prev * prev);  // 10^(2^k) = (10^(2^(k-1)))^2
            }
        }

        // Decompose n in binary and compute the product
        Int result(1);
        size_t remaining = n;
        for (size_t i = 0; remaining > 0 && i < s_pow10_cache.size(); i++) {
            if (remaining & (size_t(1) << i)) {
                result = result * s_pow10_cache[i];
                remaining &= ~(size_t(1) << i);
            }
        }
        return result;
    }

    // Write a uint64_t as an 18-digit string into the buffer
    static void u64_to_18digits(uint64_t v, char* out) {
        // Generate 18 digits from the back
        for (int i = 17; i >= 0; --i) {
            out[i] = '0' + static_cast<char>(v % 10);
            v /= 10;
        }
    }

    // Write a uint64_t as a variable-length string into the buffer, returning the digit count
    static size_t u64_to_digits(uint64_t v, char* out) {
        if (v == 0) { out[0] = '0'; return 1; }
        char tmp[20];
        size_t len = 0;
        while (v > 0) {
            tmp[len++] = '0' + static_cast<char>(v % 10);
            v /= 10;
        }
        for (size_t i = 0; i < len; i++) out[i] = tmp[len - 1 - i];
        return len;
    }

    // Naive conversion: decompose into 18-digit chunks by repeating divmod_1(10^18)
    // For small sizes (< DC_TOSTRING_THRESHOLD limbs)
    static std::string toDecimalNaive(const uint64_t* data, size_t n) {
        // Remove leading zeros
        while (n > 0 && data[n - 1] == 0) --n;
        if (n == 0) return "0";

        // 1-limb fast path: convert directly with snprintf
        if (n == 1) {
            char buf[21];
            int len = std::snprintf(buf, sizeof(buf), "%" PRIu64, data[0]);
            return std::string(buf, static_cast<size_t>(len));
        }

        // Working copy (divmod_1 writes the quotient in-place)
        // For small sizes, use a stack buffer to avoid heap allocation
        constexpr size_t STACK_LIMIT = 64;
        uint64_t stack_buf[STACK_LIMIT];
        uint64_t* work_ptr;
        std::vector<uint64_t> work_vec;
        if (n <= STACK_LIMIT) {
            std::memcpy(stack_buf, data, n * sizeof(uint64_t));
            work_ptr = stack_buf;
        } else {
            work_vec.assign(data, data + n);
            work_ptr = work_vec.data();
        }

        // Collect 18-digit chunks in reverse order
        // Maximum chunk count: ceil(n * 64 * log10(2) / 18) + 1
        std::vector<uint64_t> chunks;
        chunks.reserve(n * 4);  // ample margin

        while (n > 0) {
            uint64_t rem = mpn::divmod_1(work_ptr, work_ptr, n, POW10_18);
            chunks.push_back(rem);
            while (n > 0 && work_ptr[n - 1] == 0) --n;
        }

        // The leading chunk is variable-length; the rest are fixed at 18 digits
        size_t totalLen = 20 + (chunks.size() - 1) * DIGITS_PER_CHUNK;
        std::string result;
        result.resize(totalLen);
        char* p = result.data();

        // Most significant chunk (no zero padding)
        size_t headLen = u64_to_digits(chunks.back(), p);
        p += headLen;

        // Remaining chunks (18-digit zero padding)
        for (size_t i = chunks.size() - 1; i-- > 0; ) {
            u64_to_18digits(chunks[i], p);
            p += DIGITS_PER_CHUNK;
        }

        result.resize(static_cast<size_t>(p - result.data()));
        return result;
    }

    // DC recursion body: writes into buf[pos..pos+padDigits-1]
    // When padDigits == 0 (most significant): no leading zeros; returns the position after writing
    static void decimalSplitRec(const Int& x, char* buf, size_t pos, size_t padDigits) {
        // Base case: delegate to naive conversion
        size_t n = x.size();
        if (n < DC_TOSTRING_THRESHOLD) {
            if (x.isZero()) {
                // Write padDigits zeros
                std::memset(buf + pos, '0', padDigits);
                return;
            }
            // Stringify with naive and copy into the buffer
            std::string s = toDecimalNaive(x.data(), n);
            if (padDigits > s.size()) {
                size_t pad = padDigits - s.size();
                std::memset(buf + pos, '0', pad);
                std::memcpy(buf + pos + pad, s.data(), s.size());
            } else {
                std::memcpy(buf + pos, s.data(), s.size());
            }
            return;
        }

        // Split at half of padDigits
        size_t half = padDigits / 2;
        size_t upperPad = padDigits - half;  // upper takes the remainder (guarantees half + upperPad == padDigits)

        // Compute 10^half with caching
        Int divisor = pow10_cached(half);
        Int R;
        Int Q = IntOps::divmod(x, divisor, R);

        // Upper half: upperPad digits starting at pos
        decimalSplitRec(Q, buf, pos, upperPad);
        // Lower half: half digits starting at pos + upperPad
        decimalSplitRec(R, buf, pos + upperPad, half);
    }

    // Decimal string conversion
    static std::string toDecimalString(const Int& value) {
        if (value.isZero()) return "0";

        bool isNeg = value.isNegative();
        size_t n = value.size();

        // Small size: naive (repeated divmod_1) — no Int copy needed, references data() directly
        if (n < DC_TOSTRING_THRESHOLD) {
            std::string result = toDecimalNaive(value.data(), n);
            if (isNeg) result.insert(0, 1, '-');
            return result;
        }

        // Large size: absVal is needed (DC splits the Int)
        Int absVal = isNeg ? -value : value;
        n = absVal.size();

        // Large size: DC (pre-allocated buffer)
        // estimateDecimalDigits is ceil(bits * log10(2)) + 1, which can be 1 digit too many.
        // Allocate the buffer with margin and verify the exact digit count via naive conversion after DC conversion.
        size_t estDigits = estimateDecimalDigits(absVal.bitLength());
        size_t bufSize = estDigits + 4;
        std::string buf(bufSize, '0');

        // Write into the buffer via DC recursion
        decimalSplitRec(absVal, buf.data(), 0, estDigits);

        // Remove leading zeros
        size_t start = 0;
        while (start < buf.size() - 1 && buf[start] == '0') ++start;

        // Tail: within the estDigits-wide buffer, the result is the part excluding leading zeros.
        // However, if estDigits is 1-2 digits too many, extra zeros are appended at the tail.
        // Exact digit count: floor(log10(value)) + 1 = integer part of bitLength * log10(2) + 1
        // Computing the leading few digits with naive to determine the exact count is costly, so
        // instead: estDigits digits if value >= 10^(estDigits-1), otherwise estDigits-1 digits
        size_t actualDigits = estDigits;
        if (estDigits > 1) {
            Int threshold = pow10_cached(estDigits - 1);
            if (absVal < threshold) {
                actualDigits = estDigits - 1;
            }
        }
        // Extract actualDigits digits from the buffer starting at start
        std::string result = buf.substr(start, actualDigits);

        if (isNeg) {
            result.insert(0, 1, '-');
        }
        return result;
    }

    // ================================================================
    // General-purpose toString (arbitrary base)
    // ================================================================

    // For bases other than 10, use the conventional sequential divmod (for small numbers)
    static std::string toStringNaive(const Int& value, int base) {
        static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";

        Int x = value;
        bool isNeg = x.isNegative();
        if (isNeg) x = -x;

        std::string result;
        Int baseInt(base);
        while (!x.isZero()) {
            Int remainder;
            x = IntOps::divmod(x, baseInt, remainder);
            result.push_back(digits[remainder.toInt()]);
        }
        std::reverse(result.begin(), result.end());
        if (isNeg) result.insert(0, 1, '-');
        return result;
    }

    std::string IntIOUtils::toString(const Int& value, int base) {
        // Handle special states
        std::string specialResult = IntSpecialStates::handleToString(value);
        if (!specialResult.empty()) {
            return specialResult;
        }

        if (base < 2 || base > 36) {
            throw std::invalid_argument("Base must be between 2 and 36");
        }

        if (value.m_sign == 0) {
            return "0";
        }

        // base == 10: divide-and-conquer (O(M(N) * log N))
        if (base == 10) {
            return toDecimalString(value);
        }

        // Other bases: conventional sequential conversion
        return toStringNaive(value, base);
    }

    // ================================================================
    // fromString internal helpers
    // ================================================================

    // Convert a single character to a numeric value (invalid character: -1, separator: -2)
    static int charToDigit(char c) {
        if (c >= '0' && c <= '9') return c - '0';
        if (c >= 'a' && c <= 'z') return c - 'a' + 10;
        if (c >= 'A' && c <= 'Z') return c - 'A' + 10;
        if (c == '_' || c == '\'') return -2;  // separator
        return -1;  // invalid character
    }

    // Validate the digit portion and return a clean string with separators removed
    static std::string cleanDigits(std::string_view str, size_t start, int base) {
        std::string digits;
        digits.reserve(str.size() - start);
        for (size_t i = start; i < str.size(); ++i) {
            int d = charToDigit(str[i]);
            if (d == -2) continue;  // skip separators
            if (d < 0) {
                throw std::invalid_argument(
                    "Invalid character in string: " + std::string(1, str[i]));
            }
            if (d >= base) {
                throw std::invalid_argument("Digit out of range for specified base");
            }
            digits.push_back(str[i]);
        }
        return digits;
    }

    // Return the bit count for a power-of-two base (0 if not a power of two)
    static int bitsForPow2Base(int base) {
        switch (base) {
        case 2:  return 1;
        case 4:  return 2;
        case 8:  return 3;
        case 16: return 4;
        case 32: return 5;
        default: return 0;
        }
    }

    // ----------------------------------------------------------------
    // (1) Bit packing: power-of-two bases (2, 4, 8, 16, 32)
    //     no multiplication needed, O(n)
    // ----------------------------------------------------------------
    static Int fromStringPow2(const std::string& digits, int bitsPerDigit) {
        if (digits.empty()) return Int::Zero();

        // Compute the word count from the total bit count
        size_t totalBits = digits.size() * static_cast<size_t>(bitsPerDigit);
        size_t numWords = (totalBits + 63) / 64;

        // Allocate the word array
        std::vector<uint64_t> words(numWords, 0);

        // Process from the back (least significant digit) and pack in little-endian
        size_t bitPos = 0;
        for (size_t i = digits.size(); i-- > 0; ) {
            int d = charToDigit(digits[i]);
            size_t wordIdx = bitPos / 64;
            size_t bitIdx  = bitPos % 64;
            words[wordIdx] |= static_cast<uint64_t>(d) << bitIdx;
            // When bitsPerDigit straddles a word boundary
            if (bitIdx + bitsPerDigit > 64) {
                words[wordIdx + 1] |= static_cast<uint64_t>(d) >> (64 - bitIdx);
            }
            bitPos += bitsPerDigit;
        }

        // Remove leading zero words
        while (!words.empty() && words.back() == 0) {
            words.pop_back();
        }

        if (words.empty()) return Int::Zero();

        return Int::fromRawWords(words, 1);
    }

    // ----------------------------------------------------------------
    // (2) Horner's method: small digit counts of a general base
    //     compute result = result * base + digit sequentially
    // ----------------------------------------------------------------
    // Chunked Horner's method: process up to chunk_size digits at a time
    // For base=10, 18 digits at a time (10^18 < 2^64) reduces to multi-precision × 1-limb multiplication
    static Int fromStringHorner(const std::string& digits, int base) {
        if (digits.empty()) return Int::Zero();

        // Compute the largest k such that base^k fits in a uint64_t
        int chunk_size = 1;
        uint64_t chunk_base = static_cast<uint64_t>(base);
        {
            uint64_t limit = UINT64_MAX / base;
            uint64_t b = chunk_base;
            while (b <= limit) {
                b *= base;
                chunk_size++;
            }
            chunk_base = b / base;  // base^(chunk_size-1)
            chunk_size--;
            // chunk_base = base^chunk_size
            chunk_base = 1;
            for (int i = 0; i < chunk_size; i++) chunk_base *= base;
        }

        Int result = Int::Zero();
        size_t pos = 0;
        size_t len = digits.size();

        // First partial chunk (len % chunk_size digits)
        size_t first_chunk = len % chunk_size;
        if (first_chunk == 0 && len > 0) first_chunk = chunk_size;

        {
            uint64_t v = 0;
            for (size_t i = 0; i < first_chunk; i++) {
                v = v * base + charToDigit(digits[pos++]);
            }
            result = Int(v);
        }

        // Process the rest chunk_size digits at a time
        // multiplyWord + addWord completely eliminates temporary Int creation
        // result is built from Int(v) → always Normal → Unchecked is safe
        while (pos < len) {
            uint64_t v = 0;
            for (int i = 0; i < chunk_size; i++) {
                v = v * base + charToDigit(digits[pos++]);
            }
            IntOps::multiplyWordUnchecked(result, chunk_base);
            IntOps::addWord(result, v);
        }

        return result;
    }

    // ----------------------------------------------------------------
    // (3) Divide-and-conquer: large digit counts of a general base
    //     split the string in half, upper * base^(lower digit count) + lower
    //     complexity: O(M(n) * log n)
    // ----------------------------------------------------------------

    // Threshold between Horner and divide-and-conquer (based on decimal digit count)
    // Lowered the threshold because chunked Horner sped up the base case
    static size_t FROMSTRING_DC_THRESHOLD = 400;

    static Int fromStringDC(const std::string& digits, int base) {
        if (digits.size() <= FROMSTRING_DC_THRESHOLD) {
            return fromStringHorner(digits, base);
        }

        size_t half = digits.size() / 2;
        size_t lowerLen = digits.size() - half;

        // Upper half digits, lower lowerLen digits
        std::string upperStr = digits.substr(0, half);
        std::string lowerStr = digits.substr(half);

        Int upper = fromStringDC(upperStr, base);
        Int lower = fromStringDC(lowerStr, base);

        // upper * base^lowerLen + lower
        Int basePow = pow(Int(base), static_cast<unsigned int>(lowerLen));
        return upper * basePow + lower;
    }

    // ================================================================
    // fromString body
    // ================================================================

    Int IntIOUtils::fromString(std::string_view str, int base) {
        if (base != 0 && (base < 2 || base > 36)) {
            throw std::invalid_argument("Base must be between 2 and 36");
        }

        // Empty string check
        if (str.empty()) {
            throw std::invalid_argument("Cannot convert empty string to Int");
        }

        // Handle special strings
        if (str == "NaN") return Int::NaN();
        if (str == "Infinity" || str == "+Infinity") return Int::PositiveInfinity();
        if (str == "-Infinity") return Int::NegativeInfinity();

        // Handle the sign
        bool isNegative = false;
        size_t start = 0;
        if (str[0] == '-') {
            isNegative = true;
            start = 1;
        }
        else if (str[0] == '+') {
            start = 1;
        }

        // Handle the base prefix
        if (base == 0) {
            if (str.size() > start + 1 && str[start] == '0') {
                if (str[start + 1] == 'x' || str[start + 1] == 'X') {
                    base = 16;
                    start += 2;
                }
                else if (str[start + 1] == 'b' || str[start + 1] == 'B') {
                    base = 2;
                    start += 2;
                }
                else {
                    base = 8;
                    start += 1;
                }
            }
            else {
                base = 10;
            }
        }

        // Decimal small fast path: manual parse for 20 digits or fewer (avoids cleanDigits)
        // 18 digits or fewer: no overflow (10^18 < UINT64_MAX)
        // 19-20 digits: with overflow checking
        {
            size_t ndigits = str.size() - start;
            if (base == 10 && ndigits <= 20) {
                bool clean = true;
                for (size_t i = start; i < str.size(); i++) {
                    char c = str[i];
                    if (c < '0' || c > '9') { clean = false; break; }
                }
                if (clean) {
                    uint64_t val = 0;
                    bool overflow = false;
                    for (size_t i = start; i < str.size(); i++) {
                        uint64_t d = str[i] - '0';
                        if (ndigits >= 20) {
                            // Overflow check
                            if (val > UINT64_MAX / 10 || (val == UINT64_MAX / 10 && d > UINT64_MAX % 10)) {
                                overflow = true;
                                break;
                            }
                        }
                        val = val * 10 + d;
                    }
                    if (!overflow) {
                        if (val == 0) return Int::Zero();
                        Int result(val);
                        if (isNegative) result.negate();
                        return result;
                    }
                    // overflow → fall through to the normal path
                }
            }
        }

        // Validate and clean the digit portion
        std::string digits = cleanDigits(str, start, base);

        if (digits.empty()) return Int::Zero();

        // Algorithm selection
        Int result;
        int bitsPerDigit = bitsForPow2Base(base);
        if (bitsPerDigit > 0) {
            // Power-of-two base: bit packing O(n)
            result = fromStringPow2(digits, bitsPerDigit);
        }
        else if (digits.size() <= FROMSTRING_DC_THRESHOLD) {
            // General base, small digit count: Horner's method
            result = fromStringHorner(digits, base);
        }
        else {
            // General base, large digit count: divide-and-conquer
            result = fromStringDC(digits, base);
        }

        // Apply the sign
        if (isNegative) {
            result = -result;
        }

        return result;
    }

    std::string IntIOUtils::format(const Int& value, const FormatOptions& options) {
        // Handle special states
        std::string specialResult = IntSpecialStates::handleToString(value);
        if (!specialResult.empty()) {
            return specialResult;
        }

        // Handle normal values
        std::stringstream ss;

        // Handle the sign
        bool isNegative = value.m_sign < 0;

        // Handle based on the sign display setting
        if (options.showSign && !isNegative && value.m_sign != 0) {
            ss << "+";
        }
        else if (isNegative) {
            ss << "-";
        }

        // Display the base prefix
        if (options.showBase) {
            switch (options.base) {
            case 2: ss << "0b"; break;
            case 8: ss << "0"; break;
            case 16: ss << "0x"; break;
                // No special prefix for other bases
            }
        }

        // Uppercase/lowercase setting
        std::string rawString = toString(isNegative ? -value : value, options.base);

        // Handle minimum width and zero padding
        if (options.width > 0 && rawString.length() < static_cast<size_t>(options.width)) {
            size_t padSize = options.width - rawString.length();
            if (options.zeroPad) {
                rawString = std::string(padSize, '0') + rawString;
            }
            else {
                rawString = std::string(padSize, ' ') + rawString;
            }
        }

        // Uppercase/lowercase conversion
        if (options.uppercase) {
            std::transform(rawString.begin(), rawString.end(), rawString.begin(),
                [](unsigned char c) { return std::toupper(c); });
        }

        ss << rawString;

        return ss.str();
    }

    void IntIOUtils::setFromStringDCThreshold(size_t threshold) {
        FROMSTRING_DC_THRESHOLD = threshold;
    }

    size_t IntIOUtils::getFromStringDCThreshold() {
        return FROMSTRING_DC_THRESHOLD;
    }

    // ================================================================
    // exportBinary / importBinary
    // ================================================================

    static inline bool is_native_little_endian() {
        uint16_t x = 1;
        return *reinterpret_cast<const uint8_t*>(&x) == 1;
    }

    static inline uint64_t swap64(uint64_t v) {
        return ((v & 0x00000000000000FFULL) << 56) |
               ((v & 0x000000000000FF00ULL) << 40) |
               ((v & 0x0000000000FF0000ULL) << 24) |
               ((v & 0x00000000FF000000ULL) <<  8) |
               ((v & 0x000000FF00000000ULL) >>  8) |
               ((v & 0x0000FF0000000000ULL) >> 24) |
               ((v & 0x00FF000000000000ULL) >> 40) |
               ((v & 0xFF00000000000000ULL) >> 56);
    }

    static inline uint64_t to_target_endian(uint64_t v, IntIOUtils::Endian endian) {
        bool target_le = (endian == IntIOUtils::Endian::Little) ||
                         (endian == IntIOUtils::Endian::Native && is_native_little_endian());
        bool native_le = is_native_little_endian();
        if (target_le == native_le) return v;
        return swap64(v);
    }

    std::vector<uint8_t> IntIOUtils::exportBinary(const Int& value, Endian endian) {
        if (value.isNaN() || value.isInfinite()) {
            throw std::invalid_argument("exportBinary: NaN/Infinity cannot be serialized");
        }

        uint32_t n = 0;
        uint8_t sign_byte = 0;  // 0 = zero

        if (!value.isZero()) {
            n = static_cast<uint32_t>(value.size());
            sign_byte = value.isNegative() ? 0xFF : 0x01;
        }

        // Header: sign(1) + size(4) + data: n*8
        std::vector<uint8_t> result(5 + static_cast<size_t>(n) * 8);
        result[0] = sign_byte;
        // Write size as 4 LE bytes
        std::memcpy(&result[1], &n, 4);

        // Write the words (LSW first)
        const uint64_t* words = value.data();
        for (uint32_t i = 0; i < n; ++i) {
            uint64_t w = to_target_endian(words[i], endian);
            std::memcpy(&result[5 + i * 8], &w, 8);
        }

        return result;
    }

    Int IntIOUtils::importBinary(std::span<const uint8_t> data, Endian endian) {
        if (data.size() < 5) {
            throw std::invalid_argument("importBinary: data too short (need at least 5 bytes)");
        }

        uint8_t sign_byte = data[0];
        uint32_t n;
        std::memcpy(&n, &data[1], 4);

        if (data.size() < 5 + static_cast<size_t>(n) * 8) {
            throw std::invalid_argument("importBinary: data too short for declared word count");
        }

        if (n == 0 || sign_byte == 0) {
            return Int(0);
        }

        std::vector<uint64_t> words(n);
        for (uint32_t i = 0; i < n; ++i) {
            uint64_t w;
            std::memcpy(&w, &data[5 + i * 8], 8);
            words[i] = to_target_endian(w, endian);
        }

        int sign = (sign_byte == 0xFF) ? -1 : 1;
        return Int::fromRawWords(words, sign);
    }

} // namespace sangi