"""Core chunkers shipped with gnos."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, List

from .api import Chunker, ChunkerContext, ManifestChunk, SplitChunk, SplitResult


@dataclass(frozen=True)
class IdentityV1(Chunker):
    name: str = "identity-v1"

    def split(self, data: bytes, params: Dict[str, Any], _ctx: ChunkerContext) -> SplitResult:
        return SplitResult(chunks=[SplitChunk(data=data)], params=params)

    def reconstruct(self, chunks: List[ManifestChunk], _params: Dict[str, Any]) -> bytes:
        if len(chunks) != 1:
            raise ValueError("identity-v1 requires exactly one chunk")
        return chunks[0].data


@dataclass(frozen=True)
class SequentialV1(Chunker):
    name: str = "sequential-v1"

    def split(self, data: bytes, params: Dict[str, Any], _ctx: ChunkerContext) -> SplitResult:
        chunk_size = params.get("chunk_size")
        if not isinstance(chunk_size, int) or chunk_size <= 0:
            raise ValueError("sequential-v1 requires params.chunk_size as positive int")
        parts = [data[i : i + chunk_size] for i in range(0, len(data), chunk_size)] or [b""]
        return SplitResult(chunks=[SplitChunk(data=p) for p in parts], params=params)

    def reconstruct(self, chunks: List[ManifestChunk], _params: Dict[str, Any]) -> bytes:
        return b"".join(c.data for c in chunks)


_MAGIC = b"GNCH"
_VERSION = 0x01
_FLAGS_V1 = 0x00
_HEADER_LEN = 4 + 1 + 1 + 8


def _encode_frame(payload: bytes) -> bytes:
    length = len(payload).to_bytes(8, byteorder="big", signed=False)
    return _MAGIC + bytes([_VERSION]) + bytes([_FLAGS_V1]) + length + payload


def _decode_frame(data: bytes) -> bytes:
    if len(data) < _HEADER_LEN:
        raise ValueError("frame too short")
    if data[:4] != _MAGIC:
        raise ValueError("invalid frame magic")
    version = data[4]
    if version != _VERSION:
        raise ValueError("unsupported frame version")
    flags = data[5]
    if flags != _FLAGS_V1:
        raise ValueError("unsupported frame flags")
    payload_len = int.from_bytes(data[6:14], byteorder="big", signed=False)
    if len(data) != _HEADER_LEN + payload_len:
        raise ValueError("frame length mismatch")
    return data[_HEADER_LEN:]


@dataclass(frozen=True)
class FramedSequentialV1(Chunker):
    name: str = "framed-sequential-v1"

    def split(self, data: bytes, params: Dict[str, Any], _ctx: ChunkerContext) -> SplitResult:
        max_payload_bytes = params.get("max_payload_bytes")
        if not isinstance(max_payload_bytes, int) or max_payload_bytes <= 0:
            raise ValueError("framed-sequential-v1 requires params.max_payload_bytes as positive int")
        frames: List[bytes] = []
        for i in range(0, len(data), max_payload_bytes):
            payload = data[i : i + max_payload_bytes]
            frames.append(_encode_frame(payload))
        if not frames:
            frames.append(_encode_frame(b""))
        return SplitResult(chunks=[SplitChunk(data=f) for f in frames], params=params)

    def reconstruct(self, chunks: List[ManifestChunk], _params: Dict[str, Any]) -> bytes:
        out = bytearray()
        for c in chunks:
            out.extend(_decode_frame(c.data))
        return bytes(out)
