#ifndef MONOFORMAT_PARSEHELPERS_HPP
#define MONOFORMAT_PARSEHELPERS_HPP

#include <cstdint>
#include <cstddef>
#include <expected>
#include <span>
#include <tuple> // for std::ignore

namespace monoformat {

enum class ParseError {
    BufferSizeMismatch = 1,
    InvalidValue = 2,
};

inline std::expected<std::uint8_t, ParseError> peekU8LE(std::span<std::byte const> buffer) {
    if (buffer.size() < 1) {
        return std::unexpected(ParseError::BufferSizeMismatch);
    }
    return static_cast<std::uint8_t>(buffer[0]);
}

inline std::expected<std::uint8_t, ParseError> readU8LE(std::span<std::byte const>& buffer) {
    auto result = peekU8LE(buffer);
    if (result) {
        buffer = buffer.subspan(1);
    }
    return result;
}

inline std::expected<std::uint8_t, ParseError> peekU8BE(std::span<std::byte const> buffer) {
    if (buffer.size() < 1) {
        return std::unexpected(ParseError::BufferSizeMismatch);
    }
    return static_cast<std::uint8_t>(buffer[0]);
}

inline std::expected<std::uint8_t, ParseError> readU8BE(std::span<std::byte const>& buffer) {
    auto result = peekU8BE(buffer);
    if (result) {
        buffer = buffer.subspan(1);
    }
    return result;
}

inline std::expected<std::uint16_t, ParseError> peekU16LE(std::span<std::byte const> buffer) {
    if (buffer.size() < 2) {
        return std::unexpected(ParseError::BufferSizeMismatch);
    }
    return (static_cast<std::uint16_t>(buffer[0]) << 0u)
        | (static_cast<std::uint16_t>(buffer[1]) << 8u);
}

inline std::expected<std::uint16_t, ParseError> readU16LE(std::span<std::byte const>& buffer) {
    auto result = peekU16LE(buffer);
    if (result) {
        buffer = buffer.subspan(2);
    }
    return result;
}

inline std::expected<std::uint16_t, ParseError> peekU16BE(std::span<std::byte const> buffer) {
    if (buffer.size() < 2) {
        return std::unexpected(ParseError::BufferSizeMismatch);
    }
    return (static_cast<std::uint16_t>(buffer[1]) << 0u)
        | (static_cast<std::uint16_t>(buffer[0]) << 8u);
}

inline std::expected<std::uint16_t, ParseError> readU16BE(std::span<std::byte const>& buffer) {
    auto result = peekU16BE(buffer);
    if (result) {
        buffer = buffer.subspan(2);
    }
    return result;
}

inline std::expected<std::uint32_t, ParseError> peekU24LE(std::span<std::byte const> buffer) {
    if (buffer.size() < 3) {
        return std::unexpected(ParseError::BufferSizeMismatch);
    }
    return (static_cast<std::uint32_t>(buffer[0]) << 0u)
        | (static_cast<std::uint32_t>(buffer[1]) << 8u)
        | (static_cast<std::uint32_t>(buffer[2]) << 16u);
}

inline std::expected<std::uint32_t, ParseError> readU24LE(std::span<std::byte const>& buffer) {
    auto result = peekU24LE(buffer);
    if (result) {
        buffer = buffer.subspan(3);
    }
    return result;
}

inline std::expected<std::uint32_t, ParseError> peekU24BE(std::span<std::byte const> buffer) {
    if (buffer.size() < 3) {
        return std::unexpected(ParseError::BufferSizeMismatch);
    }
    return (static_cast<std::uint32_t>(buffer[2]) << 0u)
        | (static_cast<std::uint32_t>(buffer[1]) << 8u)
        | (static_cast<std::uint32_t>(buffer[0]) << 16u);
}

inline std::expected<std::uint32_t, ParseError> readU24BE(std::span<std::byte const>& buffer) {
    auto result = peekU24BE(buffer);
    if (result) {
        buffer = buffer.subspan(3);
    }
    return result;
}

inline std::expected<std::uint32_t, ParseError> peekU32LE(std::span<std::byte const> buffer) {
    if (buffer.size() < 4) {
        return std::unexpected(ParseError::BufferSizeMismatch);
    }
    return (static_cast<std::uint32_t>(buffer[0]) << 0u)
        | (static_cast<std::uint32_t>(buffer[1]) << 8u)
        | (static_cast<std::uint32_t>(buffer[2]) << 16u)
        | (static_cast<std::uint32_t>(buffer[3]) << 24u);
}

inline std::expected<std::uint32_t, ParseError> readU32LE(std::span<std::byte const>& buffer) {
    auto result = peekU32LE(buffer);
    if (result) {
        buffer = buffer.subspan(4);
    }
    return result;
}

inline std::expected<std::uint32_t, ParseError> peekU32BE(std::span<std::byte const> buffer) {
    if (buffer.size() < 4) {
        return std::unexpected(ParseError::BufferSizeMismatch);
    }
    return (static_cast<std::uint32_t>(buffer[3]) << 0u)
        | (static_cast<std::uint32_t>(buffer[2]) << 8u)
        | (static_cast<std::uint32_t>(buffer[1]) << 16u)
        | (static_cast<std::uint32_t>(buffer[0]) << 24u);
}

inline std::expected<std::uint32_t, ParseError> readU32BE(std::span<std::byte const>& buffer) {
    auto result = peekU32BE(buffer);
    if (result) {
        buffer = buffer.subspan(8);
    }
    return result;
}

inline std::expected<std::uint64_t, ParseError> peekU64LE(std::span<std::byte const> buffer) {
    if (buffer.size() < 8) {
        return std::unexpected(ParseError::BufferSizeMismatch);
    }
    return (static_cast<std::uint64_t>(buffer[0]) << 0u)
        | (static_cast<std::uint64_t>(buffer[1]) << 8u)
        | (static_cast<std::uint64_t>(buffer[2]) << 16u)
        | (static_cast<std::uint64_t>(buffer[3]) << 24u)
        | (static_cast<std::uint64_t>(buffer[4]) << 32u)
        | (static_cast<std::uint64_t>(buffer[5]) << 40u)
        | (static_cast<std::uint64_t>(buffer[6]) << 48u)
        | (static_cast<std::uint64_t>(buffer[7]) << 56u);
}

inline std::expected<std::uint64_t, ParseError> readU64LE(std::span<std::byte const>& buffer) {
    auto result = peekU64LE(buffer);
    if (result) {
        buffer = buffer.subspan(8);
    }
    return result;
}

inline std::expected<std::uint64_t, ParseError> peekU64BE(std::span<std::byte const> buffer) {
    if (buffer.size() < 8) {
        return std::unexpected(ParseError::BufferSizeMismatch);
    }
    return (static_cast<std::uint64_t>(buffer[7]) << 0u)
        | (static_cast<std::uint64_t>(buffer[6]) << 8u)
        | (static_cast<std::uint64_t>(buffer[5]) << 16u)
        | (static_cast<std::uint64_t>(buffer[4]) << 24u)
        | (static_cast<std::uint64_t>(buffer[3]) << 32u)
        | (static_cast<std::uint64_t>(buffer[2]) << 40u)
        | (static_cast<std::uint64_t>(buffer[1]) << 48u)
        | (static_cast<std::uint64_t>(buffer[0]) << 56u);
}

inline std::expected<std::uint64_t, ParseError> readU64BE(std::span<std::byte const>& buffer) {
    auto result = peekU64BE(buffer);
    if (result) {
        buffer = buffer.subspan(8);
    }
    return result;
}

inline std::expected<std::span<std::byte const>, ParseError> peekBuffer(std::span<std::byte const> buffer, std::size_t n) {
    if (buffer.size() < n) {
        return std::unexpected(ParseError::BufferSizeMismatch);
    }
    return buffer.subspan(0, n);
}

inline std::expected<std::span<std::byte const>, ParseError> readBuffer(std::span<std::byte const>& buffer, std::size_t n) {
    auto result = peekBuffer(buffer, n);
    if (result) {
        buffer = buffer.subspan(n);
    }
    return result;
}

inline std::expected<void, ParseError> expectEnd(std::span<std::byte const> buffer) {
    if (buffer.size() > 0) {
        return std::unexpected(ParseError::BufferSizeMismatch);
    }
    return {};
}

inline std::pair<std::uint8_t, bool> overflowingShr(std::uint8_t value, std::uint8_t bits) {
    bool overflow = false;
    if (bits >= 8) {
        bits = bits & 0x07;
        overflow = true;
    }
    return {value >> bits, overflow};
}

inline std::pair<std::uint8_t, bool> overflowingShl(std::uint8_t value, std::uint8_t bits) {
    bool overflow = false;
    if (bits >= 8) {
        bits = bits & 0x07;
        overflow = true;
    }
    return {value << bits, overflow};
}

inline std::size_t writeU8LE(std::span<std::byte>& target, std::size_t pos, std::uint8_t value) {
    if (pos < target.size()) {
        target[pos] = static_cast<std::byte>(value);
    }
    return pos + 1;
}

inline std::size_t writeU8BE(std::span<std::byte>& target, std::size_t pos, std::uint8_t value) {
    if (pos < target.size()) {
        target[pos] = static_cast<std::byte>(value);
    }
    return pos + 1;
}

inline std::size_t writeU16LE(std::span<std::byte>& target, std::size_t pos, std::uint16_t value) {
    if ((pos + 1) < target.size()) {
        target[pos + 0] = static_cast<std::byte>((value >> 0u) & 0xffu);
        target[pos + 1] = static_cast<std::byte>((value >> 8u) & 0xffu);
    }
    return pos + 2;
}

inline std::size_t writeU16BE(std::span<std::byte>& target, std::size_t pos, std::uint16_t value) {
    if ((pos + 1) < target.size()) {
        target[pos + 0] = static_cast<std::byte>((value >> 8u) & 0xffu);
        target[pos + 1] = static_cast<std::byte>((value >> 0u) & 0xffu);
    }
    return pos + 2;
}

inline std::size_t writeU24LE(std::span<std::byte>& target, std::size_t pos, std::uint32_t value) {
    if ((pos + 2) < target.size()) {
        target[pos + 0] = static_cast<std::byte>((value >> 0u) & 0xffu);
        target[pos + 1] = static_cast<std::byte>((value >> 8u) & 0xffu);
        target[pos + 2] = static_cast<std::byte>((value >> 16u) & 0xffu);
    }
    return pos + 3;
}

inline std::size_t writeU24BE(std::span<std::byte>& target, std::size_t pos, std::uint32_t value) {
    if ((pos + 2) < target.size()) {
        target[pos + 0] = static_cast<std::byte>((value >> 16u) & 0xffu);
        target[pos + 1] = static_cast<std::byte>((value >> 8u) & 0xffu);
        target[pos + 2] = static_cast<std::byte>((value >> 0u) & 0xffu);
    }
    return pos + 3;
}

inline std::size_t writeU32LE(std::span<std::byte>& target, std::size_t pos, std::uint32_t value) {
    if ((pos + 3) < target.size()) {
        target[pos + 0] = static_cast<std::byte>((value >> 0u) & 0xffu);
        target[pos + 1] = static_cast<std::byte>((value >> 8u) & 0xffu);
        target[pos + 2] = static_cast<std::byte>((value >> 16u) & 0xffu);
        target[pos + 3] = static_cast<std::byte>((value >> 24u) & 0xffu);
    }
    return pos + 4;
}

inline std::size_t writeU32BE(std::span<std::byte>& target, std::size_t pos, std::uint32_t value) {
    if ((pos + 3) < target.size()) {
        target[pos + 0] = static_cast<std::byte>((value >> 24u) & 0xffu);
        target[pos + 1] = static_cast<std::byte>((value >> 16u) & 0xffu);
        target[pos + 2] = static_cast<std::byte>((value >> 8u) & 0xffu);
        target[pos + 3] = static_cast<std::byte>((value >> 0u) & 0xffu);
    }
    return pos + 4;
}

inline std::size_t writeU64LE(std::span<std::byte>& target, std::size_t pos, std::uint64_t value) {
    if ((pos + 7) < target.size()) {
        target[pos + 0] = static_cast<std::byte>((value >> 0u) & 0xffu);
        target[pos + 1] = static_cast<std::byte>((value >> 8u) & 0xffu);
        target[pos + 2] = static_cast<std::byte>((value >> 16u) & 0xffu);
        target[pos + 3] = static_cast<std::byte>((value >> 24u) & 0xffu);
        target[pos + 4] = static_cast<std::byte>((value >> 32u) & 0xffu);
        target[pos + 5] = static_cast<std::byte>((value >> 40u) & 0xffu);
        target[pos + 6] = static_cast<std::byte>((value >> 48u) & 0xffu);
        target[pos + 7] = static_cast<std::byte>((value >> 56u) & 0xffu);
    }
    return pos + 8;
}

inline std::size_t writeU64BE(std::span<std::byte>& target, std::size_t pos, std::uint64_t value) {
    if ((pos + 3) < target.size()) {
        target[pos + 0] = static_cast<std::byte>((value >> 56u) & 0xffu);
        target[pos + 1] = static_cast<std::byte>((value >> 48u) & 0xffu);
        target[pos + 2] = static_cast<std::byte>((value >> 40u) & 0xffu);
        target[pos + 3] = static_cast<std::byte>((value >> 32u) & 0xffu);
        target[pos + 4] = static_cast<std::byte>((value >> 24u) & 0xffu);
        target[pos + 5] = static_cast<std::byte>((value >> 16u) & 0xffu);
        target[pos + 6] = static_cast<std::byte>((value >> 8u) & 0xffu);
        target[pos + 7] = static_cast<std::byte>((value >> 0u) & 0xffu);
    }
    return pos + 8;
}

inline std::size_t writeBuffer(std::span<std::byte>& target, std::size_t pos, std::span<std::byte const> value) {
    if ((pos + value.size()) > target.size()) {
        if (pos < target.size()) {
            std::size_t const n = target.size() - pos;
            std::copy(value.begin(), value.begin() + n, target.begin() + pos);
        }
    } else {
        std::copy(value.begin(), value.end(), target.begin() + pos);
    }
    return pos + value.size();
}

inline std::size_t alignNextWrite(std::span<std::byte>& target, std::size_t pos, std::size_t alignment) {
    std::ignore = target;
    if (alignment <= 1) {
        return pos;
    }
    if (pos % alignment == 0) {
        return pos;
    }
    return ((pos + alignment - 1) / alignment) * alignment;
}

} // namespace monoformat

#endif // MONOFORMAT_PARSEHELPERS_HPP
