#!/usr/bin/env python

# THIS FILE WAS GENERATED BY A LLM.
# The tests help improve coverage but are not hand crafted.

import sys

sys.path.insert(0, "..")
import pytest
from tibs import BitOrder, ByteOrder, Dtype, DtypeKind, MutableView, Tibs, Mutibs, View


class TestTibsCreation:
    def test_from_bin_simple(self):
        assert Tibs.from_bin("101") == Tibs('0b101')

    def test_from_bin_with_prefix(self):
        assert Tibs.from_bin("0b110") == Tibs('0b110')

    def test_from_oct_simple(self):
        assert Tibs.from_oct("75") == Tibs('0o75')

    def test_from_oct_with_prefix(self):
        assert Tibs.from_oct("0o64") == Tibs('0o64')

    def test_from_hex_simple(self):
        assert Tibs.from_hex("af") == Tibs('0xaf')

    def test_from_hex_with_prefix(self):
        assert Tibs.from_hex("0xbe") == Tibs('0xbe')

    def test_from_u_basic(self):
        assert Tibs.from_u(42, 8) == Tibs('0b00101010')

    def test_from_i_positive(self):
        assert Tibs.from_i(42, 8) == Tibs('0b00101010')

    def test_from_i_negative(self):
        assert Tibs.from_i(-42, 8) == Tibs('0b11010110')

    def test_from_f16(self):
        assert len(Tibs.from_f(3.14, 16)) == 16

    def test_from_f32(self):
        assert len(Tibs.from_f(3.14, 32)) == 32

    def test_from_f64(self):
        assert len(Tibs.from_f(3.14, 64)) == 64

    def test_from_bytes(self):
        assert Tibs.from_bytes(b"abc") == Tibs('0x616263')

    def test_from_string_bin(self):
        assert Tibs.from_string("0b101") == Tibs('0b101')

    def test_from_string_hex(self):
        assert Tibs.from_string("0xabc") == Tibs('0xabc')

    def test_from_bools(self):
        assert Tibs.from_bools([True, False, 1, 0]) == Tibs('0b1010')

    def test_from_zeros(self):
        assert Tibs.from_zeros(5) == Tibs('0b00000')

    def test_from_ones(self):
        assert Tibs.from_ones(5) == Tibs('0b11111')

    def test_from_random(self):
        assert len(Tibs.from_random(100)) == 100

    def test_from_joined(self):
        assert Tibs.from_joined(["0b11", "0o7", "0xf"]) == Tibs('0b111111111')

    def test_constructor_delegation(self):
        assert Tibs("0b101") == Tibs.from_string("0b101")
        assert Tibs(b"abc") == Tibs.from_bytes(b"abc")
        assert Tibs([True, False]) == Tibs.from_bools([True, False])

    def test_from_slice(self):
        a = Tibs("0b101100111000")
        b = a[2:10]
        assert b == Tibs('0b11001110')
        c = Tibs(b)
        assert c == Tibs('0b11001110')


class TestMutibsCreation:
    def test_from_bin_simple(self):
        assert Mutibs.from_bin("101") == Tibs('0b101')

    def test_from_oct_simple(self):
        assert Mutibs.from_oct("75") == Tibs('0o75')

    def test_from_hex_simple(self):
        assert Mutibs.from_hex("af") == Tibs('0xaf')

    def test_from_u_basic(self):
        assert Mutibs.from_u(42, 8) == Tibs('0b00101010')

    def test_from_i_positive(self):
        assert Mutibs.from_i(42, 8) == Tibs('0b00101010')

    def test_from_f32(self):
        assert len(Mutibs.from_f(3.14, 32)) == 32

    def test_from_bytes(self):
        assert Mutibs.from_bytes(b"abc") == Tibs('0x616263')

    def test_from_string_bin(self):
        assert Mutibs.from_string("0b101") == Tibs('0b101')

    def test_from_bools(self):
        assert Mutibs.from_bools([True, False, 1, 0]) == Tibs('0b1010')

    def test_from_zeros(self):
        assert Mutibs.from_zeros(5) == Tibs('0b00000')

    def test_from_ones(self):
        assert Mutibs.from_ones(5) == Tibs('0b11111')

    def test_from_random(self):
        assert len(Mutibs.from_random(100)) == 100

    def test_from_joined(self):
        assert Mutibs.from_joined(["0b11", "0o7", "0xf"]) == Tibs('0b111111111')

    def test_constructor_delegation(self):
        assert Mutibs("0b101") == Mutibs.from_string("0b101")
        assert Mutibs(b"abc") == Mutibs.from_bytes(b"abc")
        assert Mutibs([True, False]) == Mutibs.from_bools([True, False])

    def test_from_slice(self):
        a = Tibs("0b101100111000")
        b = a[2:10]
        assert b == Tibs('0b11001110')
        c = Mutibs(b)
        assert c == Tibs('0b11001110')


class TestTibsMethods:
    def test_eq(self):
        assert Tibs("0xf") == Tibs('0b1111')
        assert not (Tibs("0xf") == Tibs('0b1110'))

    def test_str(self):
        assert str(Tibs("0b101")) == "0b101"

    def test_repr(self):
        assert repr(Tibs("0b101")) == "Tibs('0b101')"


class TestMutibsMethods:
    def test_eq(self):
        assert Mutibs("0xf") == Tibs('0b1111')
        assert not (Mutibs("0xf") == Tibs('0b1110'))

    def test_str(self):
        assert str(Mutibs("0b101")) == "0b101"

    def test_repr(self):
        assert repr(Mutibs("0b101")) == "Mutibs('0b101')"

    def test_set_index(self):
        m = Mutibs("0b000")
        m[1] = 1
        assert m == Tibs('0b010')

    def test_set_slice(self):
        m = Mutibs("0b00000")
        m[1:4] = "0b111"
        assert m == Tibs('0b01110')

    def test_ixor(self):
        m = Mutibs("0b1100")
        m ^= "0b1010"
        assert m == Tibs('0b0110')

    def test_ior(self):
        m = Mutibs("0b1100")
        m |= "0b1010"
        assert m == Tibs('0b1110')

    def test_iand(self):
        m = Mutibs("0b1100")
        m &= "0b1010"
        assert m == Tibs('0b1000')

    def test_set_from_sequence(self):
        m = Mutibs("0b00000")
        m.set([1, 3])
        assert m == Tibs('0b01010')

    def test_set_from_slice(self):
        m = Mutibs("0b00000000")
        m[1:7:2] = [1, 1, 1]
        assert m == Tibs('0b01010100')

    def test_insert_uses_logical_position(self):
        m = Mutibs("0b1100")

        # Build the expected logical bit sequence (the order used by indexing/iteration).
        expected_logical_bits = list(m.to_tibs())
        expected_logical_bits.insert(1, True)

        m.insert(1, "0b1")

        assert list(m.to_tibs()) == expected_logical_bits


class TestIterators:
    def test_bool_iterator(self):
        t = Tibs("0b101")
        assert list(t) == [True, False, True]

    def test_find_all_iterator(self):
        t = Tibs("0b101101")
        assert list(t.find_all_iter("0b101")) == [0, 3]

    def test_chunks_iterator(self):
        t = Tibs("0b101101")
        chunks = list(t.chunks_iter(3))
        assert len(chunks) == 2
        assert chunks[0] == Tibs('0b101')
        assert chunks[1] == Tibs('0b101')


class TestErrorHandling:

    def test_from_i_errors(self):
        with pytest.raises(OverflowError):
            Tibs.from_i(128, 8)  # Overflows (signed)
        with pytest.raises(OverflowError):
            Tibs.from_i(-129, 8)  # Overflows (signed)

    def test_from_f_errors(self):
        with pytest.raises(ValueError):
            Tibs.from_f(1.0, 12)  # Invalid length

    def test_from_bytes_errors(self):
        with pytest.raises(ValueError):
            Tibs.from_bytes(b"abc", offset=30)
        with pytest.raises(ValueError):
            Tibs.from_bytes(b"abc", length=30)

    def test_adding_nonsense(self):
        with pytest.raises(ValueError):
            Mutibs.from_bools([0]) + "hello"

    def test_slice_assignment_errors(self):
        a = Mutibs('0b000')
        with pytest.raises(IndexError):
            a[5] = 1
        with pytest.raises(IndexError):
            del a[5]


class TestAdvancedFeatures:
    def test_rol(self):
        s = Mutibs("0b0001")
        s.rotate_left(1)
        assert s == Tibs('0b0010')

    def test_ror(self):
        s = Mutibs("0b1000")
        s.rotate_right(1)
        assert s == Tibs('0b0100')

    def test_byte_swap(self):
        s = Mutibs("0x0123456789abcdef")
        s.byte_swap()
        assert s == Tibs('0xefcdab8967452301')

    def test_to_f(self):
        f = 3.14159
        t16 = Tibs.from_f(f, 16)
        assert t16.to_f() != f  # Should be different
        t32 = Tibs.from_f(f, 32)
        assert abs(t32.to_f() - f) < 1e-6
        t64 = Tibs.from_f(f, 64)
        assert t64.to_f() == f

    def test_to_u_i(self):
        assert Tibs("0b11111111").to_u() == 255
        assert Tibs("0b11111111").to_i() == -1
        assert Tibs("0b01111111").to_i() == 127

    def test_find_all_byte_aligned(self):
        a = Tibs("0x00ff00ff")
        assert a.find_all(Tibs('0xff'), byte_aligned=True) == [8, 24]


class TestComplexInteractions:
    def test_overlapping_replace(self):
        s = Mutibs("0b101010")
        s.replace("0b10", "0b1010")
        assert s == Tibs('0b101010101010')

    def test_find_all_overlapping(self):
        s = Tibs("0b1010101")
        assert s.find_all(Tibs('0b101')) == [0, 2, 4]

    def test_chunks_with_remainder(self):
        s = Tibs.from_zeros(10)
        chunks = list(s.chunks(3))
        assert len(chunks) == 4
        assert len(chunks[0]) == 3
        assert len(chunks[1]) == 3
        assert len(chunks[2]) == 3
        assert len(chunks[3]) == 1

    def test_set_slice_with_self_overlap(self):
        m = Mutibs("0b11110000")
        m[2:6] = m[0:4]
        assert m == Tibs('0b11111100')

    def test_int_representation_edges(self):
        assert Tibs.from_u(255, 8).to_i() == -1
        assert Tibs.from_i(-128, 8).to_u() == 128
        with pytest.raises(ValueError):
            Tibs("0b" + "1" * 129).to_u()
        with pytest.raises(ValueError):
            Tibs("0b" + "1" * 129).to_i()

    def test_mixed_type_operations(self):
        m = Mutibs("0b1010")
        t = Tibs("0b0101")
        m |= t
        assert m == Tibs('0b1111')

    def test_chained_mutations(self):
        m = Mutibs("0x1234")
        m.rotate_left(4)
        m.invert()
        m.rotate_right(8)
        assert m == Tibs('0xbedc')

    def test_immutable_slice_mutation_check(self):
        t = Tibs("0x12345678")
        s = t[8:24]
        m = s.to_mutibs()
        m.invert()
        assert t == Tibs('0x12345678')
        assert s != m
        assert s == Tibs('0x3456')

    def test_from_joined_mixed_types(self):
        t = Tibs("0b11")
        m = Mutibs("0b00")
        result = Tibs.from_joined([t, m, "0xff", [1, 0, 1]])
        assert result == Tibs('0b110011111111101')

    def test_delete_and_insert(self):
        m = Mutibs("0xabcdef")
        del m[8:16]
        assert m == Tibs('0xabef')
        m.insert(8, "0b11110000")
        assert m == Tibs('0xabf0ef')

    def test_replace_with_empty_string(self):
        s = Mutibs("0b101010")
        s.replace("0b10", "")
        assert s == Tibs('0b')


class TestSliceOperations:
    def test_slice_eq(self):
        a = Tibs("0b101100111000")
        b = a[2:10]
        c = Tibs("0b11001110")
        assert b == c

    def test_slice_logical_or(self):
        a = Tibs("0b00001111")
        b = Tibs("0b10101010")
        s_a = a[0:8]
        s_b = b[0:8]
        assert s_a | s_b == Tibs('0b10101111')

    def test_slice_logical_and(self):
        a = Tibs("0b00001111")
        b = Tibs("0b10101010")
        s_a = a[0:8]
        s_b = b[0:8]
        assert s_a & s_b == Tibs('0b00001010')

    def test_slice_logical_xor(self):
        a = Tibs("0b00001111")
        b = Tibs("0b10101010")
        s_a = a[0:8]
        s_b = b[0:8]
        assert s_a ^ s_b == Tibs('0b10100101')

    def test_slice_logical_or_offset(self):
        a = Tibs("0b100001111")  # 9 bits
        b = Tibs("0b010101010")  # 9 bits
        s_a = a[1:9]  # offset = 1
        s_b = b[1:9]  # offset = 1
        assert s_a | s_b == Tibs('0b10101111')

    def test_slice_logical_and_offset(self):
        a = Tibs("0b100001111")
        b = Tibs("0b010101010")
        s_a = a[1:9]
        s_b = b[1:9]
        assert s_a & s_b == Tibs('0b00001010')

    def test_find_slice(self):
        a = Tibs("0b101100111000101100")
        b = Tibs("0b11001110")
        c = a[2:10]
        assert c == b
        assert a.find(c) == 2

    def test_find_all_slice(self):
        a = Tibs("0b110011100011001110")
        b = Tibs("0b11001110")
        c = a[0:8]
        assert c == b
        assert list(a.find_all(c)) == [0, 10]

    def test_join_slice(self):
        a = Tibs("0b101100111000")
        b = a[2:10]
        c = Tibs.from_joined([b, b])
        assert c == Tibs('0b1100111011001110')

    def test_mutibs_from_slice(self):
        a = Tibs("0b101100111000")
        b = a[2:10]
        m = Mutibs(b)
        m.invert()
        assert m != b
        assert a == Tibs('0b101100111000')

    def test_logical_op_stepped_slice(self):
        a = Tibs("0b00110011")
        b = Tibs("0b01010101")
        c = a[::2]  # 0101
        d = b[::2]  # 0000
        assert c | d == Tibs('0b0101')
        assert c & d == Tibs('0b0000')
        assert c ^ d == Tibs('0b0101')

    def test_mixed_tibs_mutibs_slice_ops(self):
        a = Tibs("0b11110000")
        b = Mutibs("0b10101010")
        s_a = a[0:8]
        s_b = b[0:8]
        assert s_a & s_b == Tibs('0b10100000')

    def test_mutibs_slice_is_immutable_copy(self):
        a = Mutibs("0b00000000")
        b = a[2:6]
        b[1] = 1
        # The original is unchanged
        assert a == Tibs('0b00000000')

    def test_slice_logical_xor_offset(self):
        a = Tibs("0b100001111")
        b = Tibs("0b010101010")
        s_a = a[1:9]
        s_b = b[1:9]
        assert s_a ^ s_b == Tibs('0b10100101')


class TestKnownRegressions:
    def test_rotate_empty_selected_slice_is_noop(self):
        m = Mutibs("0b101010")
        m.rotate_left(3, start=2, end=2)
        m.rotate_right(1, start=4, end=4)
        assert m == Tibs('0b101010')

    def test_set_negative_step_range(self):
        m = Mutibs("0b00000000")
        m.set(range(7, 3, -1))
        assert m.to_bin() == "00001111"

    def test_unset_negative_step_range(self):
        m = Mutibs("0b11111111")
        m.unset(range(7, 3, -1))
        assert m.to_bin() == "11110000"


class TestDocsMismatchRegressions:
    def test_find_all_empty_needle_raises_value_error_docs_contract(self):
        t = Tibs("0b101")
        with pytest.raises(ValueError):
            print(list(t.find_all("")))

    def test_tibs_from_random_positional_seed_is_deterministic_docs_example(self):
        a = Tibs.from_random(256, seed=b"a_seed")
        b = Tibs.from_random(256, seed=b"a_seed")
        assert a == b

    def test_mutibs_from_random_positional_seed_is_deterministic_docs_example(self):
        a = Mutibs.from_random(256, seed=b"a_seed")
        b = Mutibs.from_random(256, seed=b"a_seed")
        assert a == b

    def test_mutibs_insert_out_of_range_raises_value_error_docs_contract(self):
        m = Mutibs("0b101")
        m.insert(len(m) + 1, "0b1")
        assert m == Tibs('0b1011')

    def test_tibs_to_u_empty_raises_value_error_docs_contract(self):
        with pytest.raises(ValueError):
            Tibs().to_u()

    def test_tibs_to_i_empty_raises_value_error_docs_contract(self):
        with pytest.raises(ValueError):
            Tibs().to_i()


class TestKnownLogicFaults:
    def test_set_range_descending_to_minus_one_includes_zero(self):
        m = Mutibs.from_zeros(4)
        m.set(range(3, -1, -1))
        assert m.to_bin() == "1111"

    def test_unset_range_descending_to_minus_one_includes_zero(self):
        m = Mutibs.from_ones(4)
        m.unset(range(3, -1, -1))
        assert m.to_bin() == "0000"

def _delete_expected(bits: list[bool], key) -> list[bool]:
    expected = bits.copy()
    del expected[key]
    return expected


@pytest.mark.parametrize(
    "key",
    [
        slice(None, 8),
        slice(8, None),
        slice(4, 20),
        slice(-16, -4),
        slice(10, 10),
        slice(None, None, 2),
        slice(1, None, 3),
        slice(None, None, -1),
        slice(20, 2, -3),
    ],
)
def test_del_slice_matches_python_list_semantics(key):
    # Use a non-trivial pattern so direction/indexing errors are obvious.
    m = Mutibs.from_hex("abcdef")
    before = list(m.to_tibs())

    expected = _delete_expected(before, key)
    del m[key]

    assert list(m.to_tibs()) == expected


def _set_expected(bits: list[bool], key, value: list[bool]) -> list[bool]:
    expected = bits.copy()
    expected[key] = value
    return expected


@pytest.mark.parametrize(
    "key,value",
    [
        (slice(None, 8), [True, False, True, False]),
        (slice(8, None), [False, True, False]),
        (slice(4, 20), [True] * 5),
        (slice(-16, -4), [False, False, True, True]),
        (slice(10, 10), [True, False, True]),
    ],
)
def test_set_slice_matches_python_list_semantics(key, value):
    m = Mutibs.from_hex("abcdef")
    before = list(m.to_tibs())
    replacement = Tibs.from_bools(value)

    expected = _set_expected(before, key, list(replacement))
    m[key] = replacement

    assert list(m.to_tibs()) == expected


@pytest.mark.parametrize("key", [slice(None, None, 2), slice(1, None, 3), slice(20, 2, -3)])
def test_set_extended_slice_matches_python_list_semantics(key):
    m = Mutibs.from_hex("abcdef")
    before = list(m.to_tibs())

    target_len = len(before[key])
    value = [(i % 2) == 0 for i in range(target_len)]
    replacement = Tibs.from_bools(value)

    expected = _set_expected(before, key, list(replacement))
    m[key] = replacement

    assert list(m.to_tibs()) == expected


def test_set_extended_slice_length_mismatch_raises():
    m = Mutibs.from_hex("abcdef")
    with pytest.raises(ValueError):
        m[::2] = [1, 0]


def test_del_full_and_single_index_regression():
    m = Mutibs.from_hex("1234")
    before = list(m.to_tibs())

    m2 = Mutibs.from_hex("1234")
    del m2[:]
    assert list(m2.to_tibs()) == []

    del m[0]
    expected = before.copy()
    del expected[0]
    assert list(m.to_tibs()) == expected



def _find_expected(
        bits: list[bool],
        needle: list[bool],
        start: int | None,
        end: int | None,
        reverse: bool,
        byte_aligned: bool,
) -> int | None:
    n = len(bits)
    s = 0 if start is None else start
    e = n if end is None else end
    if s < 0:
        s += n
    if e < 0:
        e += n
    m = len(needle)
    if m == 0 or m > (e - s):
        return None

    if reverse:
        i_range = range(e - m, s - 1, -1)
    else:
        i_range = range(s, e - m + 1)

    for i in i_range:
        if byte_aligned and (i % 8 != 0):
            continue
        if bits[i:i + m] == needle:
            return i
    return None


@pytest.mark.parametrize(
    "needle,start,end,byte_aligned",
    [
        ([1, 1], None, None, False),
        ([1, 0, 1], 3, 28, False),
        ([0, 1], -20, -1, False),
        ([1, 1, 0], 0, 32, True),
        ([0, 0, 1], 5, 31, True),
    ],
)
def test_find_rfind_match_python_reference(needle, start, end, byte_aligned):
    t = Tibs("0xd35a1c9e")
    bits = list(t)
    needle_bits = [bool(x) for x in needle]

    expected_find = _find_expected(bits, needle_bits, start, end, False, byte_aligned)
    expected_rfind = _find_expected(bits, needle_bits, start, end, True, byte_aligned)

    assert t.find(needle, start=start, end=end, byte_aligned=byte_aligned) == expected_find
    assert t.rfind(needle, start=start, end=end, byte_aligned=byte_aligned) == expected_rfind


def _rotate_left_expected(bits: list[bool], start: int, end: int, n: int) -> list[bool]:
    expected = bits.copy()
    segment = expected[start:end]
    if not segment:
        return expected
    n %= len(segment)
    expected[start:end] = segment[n:] + segment[:n]
    return expected


class TestConcreteRegressionCases:
    def test_find_byte_aligned_empty_subrange_returns_none(self):
        t = Tibs("0x11223344")
        assert t.find("0x11", start=1, end=1, byte_aligned=True) is None

    def test_find_all_byte_aligned_empty_subrange_returns_empty_list(self):
        t = Tibs("0x11223344")
        assert t.find_all(Tibs('0x11'), start=1, end=1, byte_aligned=True) == []

    def test_find_all_single_bit_respects_slice_and_alignment(self):
        t = Tibs("0b1000000010000000100000001")

        assert t.find_all("0b1") == [0, 8, 16, 24]
        assert t.find_all("0b1", start=1, end=24, byte_aligned=True) == [8, 16]

        zeros = Tibs("0b011111110111111101111111")
        assert zeros.find_all("0b0", byte_aligned=True) == [0, 8, 16]

    def test_find_all_single_pass_preserves_overlapping_matches(self):
        t = Tibs("0b11111")
        assert t.find_all("0b111") == [0, 1, 2]

    def test_find_all_byte_aligned_respects_subrange(self):
        t = Tibs("0xabcdabcd")
        assert t.find_all("0xabcd", start=8, end=32, byte_aligned=True) == [16]

    def test_mutibs_find_all_uses_same_search_semantics(self):
        m = Mutibs("0b11111")
        assert m.find_all("0b111") == [0, 1, 2]
        assert m.find_all("0b1", byte_aligned=True) == [0]

    def test_rfind_reverse_search_finds_prefix_match(self):
        t = Tibs("0b0111")
        assert t.rfind("0b011") == 0

    def test_mutibs_rotate_left_partial_slice_matches_logical_rotation(self):
        m = Mutibs("0b10110010")
        expected = _rotate_left_expected(list(m.to_tibs()), 1, 7, 2)

        m.rotate_left(2, start=1, end=7)

        assert list(m.to_tibs()) == expected

    def test_tibs_rotated_left_partial_slice_matches_logical_rotation(self):
        t = Tibs("0b10110010")
        expected = _rotate_left_expected(list(t), 1, 7, 2)

        rotated = t.rotated_left(2, start=1, end=7)

        assert list(rotated) == expected


def test_decode_malformed_zstd_with_impossible_padding_raises_value_error():
    malformed = bytes.fromhex("110d28b52ffd240001000099e9d851")
    with pytest.raises(ValueError):
        Mutibs.decode(malformed)



def test_decode_malformed_zstd_with_impossible_padding_raises_value_error():
    # Valid zstd frame for empty bytes, wrapped in the tibs zstd container
    # with an impossible bit_padding of 1 for zero decompressed bits.
    malformed = bytes.fromhex("110d28b52ffd240001000099e9d851")
    with pytest.raises(ValueError):
        Tibs.decode(malformed)


def test_bit_ops_unequal_offsets():
    a = Tibs('0b11001010_01110100_10111100')
    b = Tibs('0b10110101_11001001_00101110')
    left = a[3:21]
    right = b[5:23]

    expected_or = [x or y for x, y in zip(left, right)]
    expected_and = [x and y for x, y in zip(left, right)]
    expected_xor = [x != y for x, y in zip(left, right)]

    assert list(left | right) == expected_or
    assert list(left & right) == expected_and
    assert list(left ^ right) == expected_xor


class TestAdditionalCoverage:
    def test_tibs_set_unset_and_inverted_return_new_instances(self):
        t = Tibs("0b10110010")

        assert t.set_at(range(1, 8, 2)) == Tibs("0b11110111")
        assert Tibs("0b11111111").unset_at([-1, -3, 0]) == Tibs("0b01111010")
        assert Tibs("0b10000001").inverted(range(1, 7)) == Tibs("0b11111111")
        assert t == Tibs("0b10110010")

    def test_reverse_iterators_have_obvious_order_and_limits(self):
        t = Tibs("0b1010110010")

        assert list(t.rfind_all_iter("0b10")) == [8, 5, 2, 0]
        assert [chunk.bin for chunk in t.rchunks_iter(3)] == ["010", "110", "010", "1"]
        assert [chunk.bin for chunk in t.rchunks_iter(3, count=2)] == ["010", "110"]

    def test_tibs_byte_swapped_slice_returns_new_instance(self):
        t = Tibs("0x001122334455")

        assert t.byte_swapped(start=8, end=40) == Tibs("0x004433221155")
        assert t.byte_swapped(2, start=8, end=40) == Tibs("0x002211443355")
        assert t == Tibs("0x001122334455")

    def test_to_raw_data_reports_offset_and_length_for_unaligned_slice(self):
        t = Tibs("0x123456")[4:20]

        raw_bytes, offset, length = t.to_raw_data()

        assert raw_bytes == b"\x12\x34\x56"
        assert offset == 4
        assert length == 16
        assert t == Tibs("0x2345")

    def test_mutibs_append_pop_and_empty_pop_error(self):
        m = Mutibs("0b10")

        assert m.append(1) is None
        assert m == Tibs("0b101")
        assert m.pop() is True
        assert m.pop() is False
        assert m == Tibs("0b1")

        empty = Mutibs()
        with pytest.raises(IndexError):
            empty.pop()

    def test_mutibs_as_tibs_moves_data_out_and_empties_source(self):
        m = Mutibs("0x1234")

        moved = m.as_tibs()

        assert moved == Tibs("0x1234")
        assert m == Tibs()
        assert len(m) == 0

    def test_mutibs_as_raw_data_moves_data_out_and_empties_source(self):
        m = Mutibs("0x1234")

        assert m.as_raw_data() == (b"\x12\x34", 0, 16)
        assert m == Tibs()
        assert len(m) == 0

    def test_mutable_view_field_write_updates_underlying_source(self):
        m = Mutibs("0x0000")
        field = m.lsb0.le.field(15, 8)

        assert field.u == 0
        field.u = 0xab

        assert field.u == 0xab
        assert m == Mutibs("0x00ab")

    def test_shift_operators_zero_fill_and_keep_length(self):
        t = Tibs("0b1011")

        assert t << 2 == Tibs("0b1100")
        assert t >> 2 == Tibs("0b0010")
        assert t == Tibs("0b1011")

        m = Mutibs("0b1011")
        m <<= 2
        assert m == Tibs("0b1100")
        m >>= 2
        assert m == Tibs("0b0011")

    def test_reflected_add_and_multiply_accept_promotable_values(self):
        assert "0b11" + Tibs("0b00") == Tibs("0b1100")
        assert 3 * Tibs("0b10") == Tibs("0b101010")
        assert Mutibs("0b01") * 3 == Tibs("0b010101")

    def test_mutibs_decode_malformed_zstd_with_impossible_padding_raises_value_error(self):
        malformed = bytes.fromhex("110d28b52ffd240001000099e9d851")
        with pytest.raises(ValueError):
            Mutibs.decode(malformed)

    def test_bytes_apis_reject_integer_sequences(self):
        for cls in (Tibs, Mutibs):
            for value in ([0x61, 0x62], (0x61, 0x62)):
                with pytest.raises(TypeError):
                    cls.from_bytes(value)

        m = Mutibs("0x0000")
        for value in ([0x61, 0x62], (0x61, 0x62)):
            with pytest.raises(TypeError):
                m.write_bytes(value)
            with pytest.raises(TypeError):
                m.view().write_bytes(value)


class TestDtypeViewMutableViewEdgeCoverage:
    def test_dtype_spec_is_normalized_and_usable(self):
        d = Dtype("  U16_LE  ")

        assert repr(d) == "Dtype('u16_le')"
        assert d.kind is DtypeKind.Uint
        assert d.length == 16
        assert d.byte_order is ByteOrder.Little
        assert d.pack(0x1234) == Tibs("0x3412")
        assert d.unpack("0x3412") == 0x1234

    def test_dtype_from_params_accepts_none_byte_order_and_properties_are_read_only(self):
        d = Dtype.from_params(DtypeKind.Int, 8, None)

        assert repr(d) == "Dtype('i8')"
        assert d.byte_order is ByteOrder.Unspecified

        for name in ("kind", "length", "byte_order"):
            with pytest.raises(AttributeError):
                setattr(d, name, None)

    def test_dtype_unpack_methods_accept_negative_ranges_and_bytes_like_inputs(self):
        d = Dtype("u8")
        bits = Tibs.from_bytes(b"\x01\x02\x03\x04")

        assert d.unpack(bits, -24, -16) == 2
        assert d.unpack_values(bits, -24, -8) == [2, 3]
        assert list(d.unpack_values_iter(bits, -32, -8)) == [1, 2, 3]
        assert d.unpack_values(bytearray(b"\x05\x06")) == [5, 6]
        assert d.unpack_values(memoryview(b"\x07\x08")) == [7, 8]

    def test_dtype_pack_values_reports_late_item_overflow(self):
        with pytest.raises(OverflowError):
            Dtype("u4").pack_values([1, 2, 16])

    def test_dtype_bytes_pack_rejects_overlong_values(self):
        for value in (b"abc", bytearray(b"abc"), memoryview(b"abc")):
            with pytest.raises(ValueError):
                Dtype("bytes16").pack(value)
            with pytest.raises(ValueError):
                Tibs.from_value("bytes16", value)
            with pytest.raises(ValueError):
                Mutibs.from_value("bytes16", value)

    def test_dtype_bytes_pack_values_rejects_overlong_items(self):
        value_sets = [
            [b"ab", b"abc"],
            [bytearray(b"ab"), bytearray(b"abc")],
            [memoryview(b"ab"), memoryview(b"abc")],
        ]

        for values in value_sets:
            with pytest.raises(ValueError):
                Dtype("bytes16").pack_values(values)
            with pytest.raises(ValueError):
                Tibs.from_values("bytes16", values)
            with pytest.raises(ValueError):
                Mutibs.from_values("bytes16", values)

    def test_dtype_bytes_pack_rejects_integer_sequences(self):
        for value in ([0x61, 0x62], (0x61, 0x62)):
            with pytest.raises(TypeError):
                Dtype("bytes16").pack(value)
            with pytest.raises(TypeError):
                Tibs.from_value("bytes16", value)
            with pytest.raises(TypeError):
                Mutibs.from_value("bytes16", value)

    def test_dtype_float_specs_reject_unsupported_widths_at_construction(self):
        for length in (8, 24, 128):
            with pytest.raises(ValueError):
                Dtype(f"f{length}")
            with pytest.raises(ValueError):
                Dtype.from_params(DtypeKind.Float, length)

    def test_dtype_bytes_specs_reject_non_byte_widths_at_construction(self):
        for length in (1, 7, 9):
            with pytest.raises(ValueError):
                Dtype(f"bytes{length}")
            with pytest.raises(ValueError):
                Dtype.from_params(DtypeKind.Bytes, length)

    def test_dtype_text_specs_reject_non_digit_aligned_widths_at_construction(self):
        cases = [
            ("hex", DtypeKind.Hex, 7),
            ("oct", DtypeKind.Oct, 8),
        ]

        for prefix, kind, length in cases:
            with pytest.raises(ValueError):
                Dtype(f"{prefix}{length}")
            with pytest.raises(ValueError):
                Dtype.from_params(kind, length)

    def test_view_from_indices_empty_selection_is_empty_and_unusable_as_number(self):
        v = View.from_indices(Tibs("0xff"), [])

        assert len(v) == 0
        assert v.bin == ""
        assert v.to_tibs() == Tibs()
        assert v.to_mutibs() == Mutibs()

        with pytest.raises(ValueError):
            _ = v.u
        with pytest.raises(ValueError):
            v.field(0, 0)

    def test_view_from_indices_respects_layout_for_two_byte_selection(self):
        source = Tibs("0x1234")

        lsb0 = View.from_indices(source, range(16), bit_order=BitOrder.Lsb0)
        little = View.from_indices(source, range(16), byte_order=ByteOrder.Little)

        assert lsb0.hex == "3412"
        assert little.hex == "3412"
        assert lsb0.to_tibs() == little.to_tibs() == Tibs("0x3412")

    def test_view_non_byte_field_rejects_byte_oriented_reinterpretation(self):
        field = Tibs("0xff").field(0, 3)

        assert field.bin == "1111"
        with pytest.raises(ValueError):
            _ = field.le
        with pytest.raises(ValueError):
            _ = field.lsb0

    def test_mutable_view_empty_selection_allows_empty_write_only(self):
        m = Mutibs("0xff")
        v = MutableView.from_indices(m, [])

        assert len(v) == 0
        assert v.bin == ""
        assert v.write_bin("") is None
        assert m == Mutibs("0xff")

        with pytest.raises(ValueError):
            v.write_bin("1")

    def test_mutable_view_sparse_byte_write_updates_selected_source_bits(self):
        m = Mutibs("0x0000")
        v = MutableView.from_indices(m, [0, 2, 4, 6, 8, 10, 12, 14])

        assert v.bytes == b"\x00"
        v.bytes = b"\xff"

        assert v.bytes == b"\xff"
        assert m == Mutibs("0xaaaa")

    def test_mutable_view_revalidates_selected_indices_after_source_shrinks(self):
        m = Mutibs("0xff")
        v = MutableView.from_indices(m, [6, 7])

        assert v.bin == "11"
        del m[6:]

        with pytest.raises(ValueError):
            len(v)
        with pytest.raises(ValueError):
            _ = v.bin

    def test_mutable_view_signed_write_failure_leaves_source_unchanged(self):
        m = Mutibs.from_zeros(8)

        m.view().i = -1
        assert m == Mutibs("0xff")

        original = m.to_tibs()
        with pytest.raises(OverflowError):
            m.view().i = -129

        assert m == original

    def test_mutable_view_to_mutibs_materializes_copy(self):
        m = Mutibs("0x1234")

        copy = m.lsb0.to_mutibs()
        copy[0] = True

        assert copy == Mutibs("0xb412")
        assert m == Mutibs("0x1234")
