"""Little-endian .NET BinaryReader/BinaryWriter helpers."""

from __future__ import annotations

import struct
from typing import BinaryIO


def read_7bit_int(stream: BinaryIO) -> int:
    value = 0
    shift = 0
    while True:
        raw = stream.read(1)
        if not raw:
            raise EOFError("truncated .NET 7-bit int")
        value |= (raw[0] & 0x7F) << shift
        if not raw[0] & 0x80:
            return value
        shift += 7
        if shift > 35:
            raise ValueError("invalid .NET 7-bit int")


def write_7bit_int(stream: BinaryIO, value: int) -> None:
    if value < 0:
        raise ValueError("7-bit int must be non-negative")
    while value >= 0x80:
        stream.write(bytes([(value & 0x7F) | 0x80]))
        value >>= 7
    stream.write(bytes([value & 0x7F]))


def read_dotnet_string(stream: BinaryIO) -> str:
    length = read_7bit_int(stream)
    raw = stream.read(length)
    if len(raw) != length:
        raise EOFError("truncated .NET string")
    return raw.decode("utf-8")


def write_dotnet_string(stream: BinaryIO, text: str) -> None:
    raw = text.encode("utf-8")
    write_7bit_int(stream, len(raw))
    stream.write(raw)


def read_i32(stream: BinaryIO) -> int:
    raw = stream.read(4)
    if len(raw) != 4:
        raise EOFError("truncated int32")
    return struct.unpack("<i", raw)[0]


def read_i64(stream: BinaryIO) -> int:
    raw = stream.read(8)
    if len(raw) != 8:
        raise EOFError("truncated int64")
    return struct.unpack("<q", raw)[0]


def read_bool(stream: BinaryIO) -> bool:
    raw = stream.read(1)
    if not raw:
        raise EOFError("truncated bool")
    return raw[0] != 0


def write_bool(stream: BinaryIO, value: bool) -> None:
    stream.write(b"\x01" if value else b"\x00")
