"""Bounded Matroska/WebM metadata reader for native TC260 AIGC labels.

TC260-PG-20257A stores the label as a Matroska ``SimpleTag`` whose
``TagName`` is ``AIGC`` and whose ``TagString`` is the normative JSON object.
The walker seeks over unrelated elements such as clusters instead of reading
their payloads.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, BinaryIO

if TYPE_CHECKING:
    from collections.abc import Iterator
    from pathlib import Path

from remove_ai_watermarks.metadata import MAX_TC260_VALUE_BYTES, parse_tc260_aigc_json

_EBML_MAGIC = b"\x1aE\xdf\xa3"
_SEGMENT_ID = 0x18538067
_TAGS_ID = 0x1254C367
_TAG_ID = 0x7373
_SIMPLE_TAG_ID = 0x67C8
_TAG_NAME_ID = 0x45A3
_TAG_STRING_ID = 0x4487
_MAX_TAG_NAME_BYTES = 256


def _vint_length(first: int, *, maximum: int) -> int | None:
    """Return an EBML variable-integer length from its first byte."""
    mask = 0x80
    for length in range(1, maximum + 1):
        if first & mask:
            return length
        mask >>= 1
    return None


def _read_element_header(
    stream: BinaryIO,
    pos: int,
    limit: int,
) -> tuple[int, int, int] | None:
    """Return ``(element_id, payload_start, element_end)`` inside ``limit``."""
    if pos < 0 or pos >= limit:
        return None
    stream.seek(pos)
    first_raw = stream.read(1)
    if not first_raw:
        return None
    id_length = _vint_length(first_raw[0], maximum=4)
    if id_length is None or pos + id_length >= limit:
        return None
    element_id_raw = first_raw + stream.read(id_length - 1)
    if len(element_id_raw) != id_length:
        return None
    element_id = int.from_bytes(element_id_raw, "big")

    size_first_raw = stream.read(1)
    if not size_first_raw:
        return None
    size_length = _vint_length(size_first_raw[0], maximum=8)
    if size_length is None:
        return None
    size_rest = stream.read(size_length - 1)
    if len(size_rest) != size_length - 1:
        return None
    marker = 1 << (8 - size_length)
    size_value = int.from_bytes(bytes([size_first_raw[0] & (marker - 1)]) + size_rest, "big")
    payload_start = pos + id_length + size_length
    if payload_start > limit:
        return None
    unknown_size = size_value == (1 << (7 * size_length)) - 1
    element_end = limit if unknown_size else payload_start + size_value
    if element_end > limit:
        return None
    return element_id, payload_start, element_end


def _iter_elements(
    stream: BinaryIO,
    start: int,
    end: int,
) -> Iterator[tuple[int, int, int]]:
    """Yield valid direct children from one bounded EBML region."""
    pos = start
    while pos < end:
        header = _read_element_header(stream, pos, end)
        if header is None:
            return
        element_id, payload_start, element_end = header
        yield element_id, payload_start, element_end
        if element_end <= pos:
            return
        pos = element_end


def _read_bounded(
    stream: BinaryIO,
    start: int,
    end: int,
    maximum: int,
) -> bytes | None:
    size = end - start
    if size < 0 or size > maximum:
        return None
    stream.seek(start)
    value = stream.read(size)
    return value if len(value) == size else None


def _simple_tag_payloads(
    stream: BinaryIO,
    start: int,
    end: int,
) -> tuple[bytes, ...]:
    name: bytes | None = None
    values: list[bytes] = []
    for element_id, payload_start, element_end in _iter_elements(stream, start, end):
        if element_id == _TAG_NAME_ID:
            name = _read_bounded(stream, payload_start, element_end, _MAX_TAG_NAME_BYTES)
        elif element_id == _TAG_STRING_ID:
            value = _read_bounded(stream, payload_start, element_end, MAX_TC260_VALUE_BYTES)
            if value is not None:
                values.append(value)
    if name != b"AIGC":
        return ()
    return tuple(value for value in values if parse_tc260_aigc_json(value) is not None)


def tc260_aigc_payloads(path: str | Path) -> tuple[bytes, ...]:
    """Read validated TC260 values from Matroska/WebM ``SimpleTag`` entries."""
    found: list[bytes] = []
    try:
        with open(path, "rb") as stream:
            if stream.read(4) != _EBML_MAGIC:
                return ()
            stream.seek(0, 2)
            file_size = stream.tell()
            for element_id, payload_start, element_end in _iter_elements(stream, 0, file_size):
                if element_id != _SEGMENT_ID:
                    continue
                for child_id, child_start, child_end in _iter_elements(stream, payload_start, element_end):
                    if child_id != _TAGS_ID:
                        continue
                    for tag_id, tag_start, tag_end in _iter_elements(stream, child_start, child_end):
                        if tag_id != _TAG_ID:
                            continue
                        for simple_id, simple_start, simple_end in _iter_elements(stream, tag_start, tag_end):
                            if simple_id == _SIMPLE_TAG_ID:
                                found.extend(_simple_tag_payloads(stream, simple_start, simple_end))
    except OSError:
        return ()
    return tuple(found)
