from __future__ import annotations

import hashlib
import io
import json
import os
import re
import secrets
import shutil
import stat
import zipfile
from dataclasses import dataclass
from pathlib import Path, PurePosixPath
from typing import Any

import numpy as np
from PIL import Image

from k1link.artifacts import utc_now_iso, write_json_atomic
from k1link.device_plugins.xgrids_k1.analyze.valid_fov import (
    K1ValidFovMask,
    validate_k1_valid_fov_mask,
)

ANNOTATION_WORKSPACE_SCHEMA = "missioncore.perception-annotation-workspace/v1"
ANNOTATION_WORKSPACE_IDENTITY_SCHEMA = (
    "missioncore.perception-annotation-workspace-identity/v1"
)
PRELABEL_SCHEMA = "missioncore.perception-evaluation-prelabels/v1"
PRELABEL_IDENTITY_SCHEMA = "missioncore.perception-evaluation-prelabels-identity/v1"
PRELABEL_FRAME_SCHEMA = "missioncore.perception-evaluation-prelabel-frame/v1"
EVALUATION_PACK_SCHEMA = "missioncore.perception-evaluation-pack/v1"
EVALUATION_PACK_IDENTITY_SCHEMA = "missioncore.perception-evaluation-pack-identity/v1"
ANNOTATION_CONTRACT_SCHEMA = "missioncore.perception-annotation-contract/v1"
WORKSPACE_PROFILE = "cvat-coco-plus-segmentation-mask/v1"

MAX_JSON_BYTES = 64 * 1024 * 1024
MAX_JSONL_LINE_BYTES = 2 * 1024 * 1024
_SHA256 = re.compile(r"^[a-f0-9]{64}$")
_SAFE_GENERATION = re.compile(r"^annotation-workspace-[a-f0-9]{64}$")
_SAFE_PRODUCER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$")

_CATEGORY_COLORS: dict[int, tuple[int, int, int]] = {
    0: (0, 0, 0),
    1: (220, 20, 60),
    2: (255, 140, 0),
    3: (255, 215, 0),
    4: (0, 102, 204),
    5: (128, 0, 128),
    6: (128, 128, 128),
    7: (64, 64, 64),
    8: (244, 164, 96),
    9: (139, 90, 43),
    10: (124, 252, 0),
    11: (0, 100, 0),
    12: (135, 206, 235),
    13: (255, 0, 255),
    14: (210, 105, 30),
    15: (70, 130, 180),
    255: (255, 255, 255),
}


class AnnotationWorkspaceError(RuntimeError):
    """An E2 annotation handoff is incomplete, mutable, or inconsistent."""


@dataclass(frozen=True, slots=True)
class AnnotationWorkspace:
    generation_id: str
    root: Path
    manifest_path: Path
    images_archive_path: Path
    instance_archive_path: Path
    semantic_archive_path: Path
    review_template_path: Path
    frame_count: int
    draft_instance_count: int


@dataclass(frozen=True, slots=True)
class _PrelabelFrame:
    image_id: int
    frame_index: int
    session_seconds: float
    role: str
    group_id: str
    instances: tuple[dict[str, Any], ...]
    instance_map: np.ndarray[Any, np.dtype[np.uint16]]
    semantic_map: np.ndarray[Any, np.dtype[np.uint8]]


def prepare_annotation_workspace(
    *,
    evaluation_pack_root: Path,
    prelabels_root: Path,
    valid_fov_root: Path,
    output_root: Path,
    producer_files: tuple[tuple[str, str], ...],
) -> AnnotationWorkspace:
    """Prepare immutable CVAT imports without promoting draft labels to truth."""

    pack_root, pack, contract, pack_frames = _load_evaluation_pack(evaluation_pack_root)
    valid_fov = validate_k1_valid_fov_mask(valid_fov_root)
    _validate_valid_fov_binding(pack, valid_fov)
    prelabel_root, prelabels, prelabel_frames = _load_prelabels(
        prelabels_root,
        pack=pack,
        pack_frames=pack_frames,
        valid_fov=valid_fov,
    )
    producers = _validate_producer_files(producer_files)
    categories = _contract_categories(contract)
    identity = {
        "schema_version": ANNOTATION_WORKSPACE_IDENTITY_SCHEMA,
        "profile": WORKSPACE_PROFILE,
        "evaluation_pack_id": pack["generation_id"],
        "evaluation_manifest_sha256": _sha256_file(pack_root / "manifest.json"),
        "annotation_contract_sha256": _sha256_file(pack_root / "annotation-contract.json"),
        "prelabel_result_id": prelabels["result_id"],
        "prelabel_result_sha256": _sha256_file(prelabel_root / "result.json"),
        "valid_fov_generation_id": valid_fov.generation_id,
        "valid_fov_mask_sha256": _sha256_file(valid_fov.mask_path),
        "resolution": [valid_fov.width, valid_fov.height],
        "frame_count": len(pack_frames),
        "draft_instance_count": sum(len(frame.instances) for frame in prelabel_frames),
        "category_colors": {
            str(category_id): list(_CATEGORY_COLORS[category_id])
            for category_id in [0, *(row["id"] for row in categories), 255]
        },
        "formats": {
            "images": "zip-png/v1",
            "instances": "COCO 1.0-compatible uncompressed RLE",
            "semantic": "CVAT Segmentation Mask 1.1-compatible RGB masks",
        },
        "producer_files": producers,
    }
    identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
    generation_id = f"annotation-workspace-{identity_sha256}"
    parent = _prepare_private_directory(output_root)
    final = parent / generation_id
    if final.exists():
        return validate_annotation_workspace(
            final,
            evaluation_pack_root=pack_root,
            prelabels_root=prelabel_root,
            valid_fov_root=valid_fov.root,
        )

    staging = parent / f".{generation_id}.{secrets.token_hex(12)}.incomplete"
    published = False
    try:
        cvat_root = staging / "cvat"
        instance_annotations = cvat_root / "instance-coco" / "annotations"
        instance_annotations.mkdir(mode=0o700, parents=True)
        (cvat_root / "semantic-mask").mkdir(mode=0o700)

        names = _frame_names(pack_frames)
        coco = _coco_document(pack_frames, prelabel_frames, categories, names)
        coco_path = instance_annotations / "instances_default.json"
        write_json_atomic(coco_path, coco)

        labels = _labels_document(categories)
        labels_path = cvat_root / "labels.json"
        write_json_atomic(labels_path, labels)

        source_map_path = staging / "source-map.jsonl"
        _write_source_map(source_map_path, pack_frames, names)
        review_path = staging / "review-template.json"
        write_json_atomic(
            review_path,
            _review_template(generation_id, pack["generation_id"], pack_frames, names),
        )
        shutil.copyfile(valid_fov.mask_path, staging / "valid-fov-mask.png")

        semantic_payloads = _semantic_payloads(prelabel_frames, names, categories)
        labelmap = _labelmap(categories).encode("utf-8")
        subset = "".join(f"{Path(name).stem}\n" for name in names).encode("utf-8")
        (cvat_root / "semantic-mask" / "labelmap.txt").write_bytes(labelmap)
        (cvat_root / "semantic-mask" / "default.txt").write_bytes(subset)

        _write_zip(
            cvat_root / "images.zip",
            tuple(
                (name, (pack_root / "images" / "valid-fov-fill" / name).read_bytes())
                for name in names
            ),
        )
        _write_zip(
            cvat_root / "instance-coco.zip",
            (("annotations/instances_default.json", coco_path.read_bytes()),),
        )
        semantic_entries = [
            ("labelmap.txt", labelmap),
            ("ImageSets/Segmentation/default.txt", subset),
        ]
        semantic_entries.extend(
            (f"SegmentationClass/{name}", payload)
            for name, payload in zip(names, semantic_payloads, strict=True)
        )
        _write_zip(cvat_root / "semantic-mask.zip", tuple(semantic_entries))

        readme_path = staging / "README.md"
        readme_path.write_text(
            _readme(generation_id, pack["generation_id"], prelabels["result_id"], len(names)),
            encoding="utf-8",
            newline="\n",
        )
        for path in staging.rglob("*"):
            if path.is_file():
                _fsync_file(path)
                os.chmod(path, 0o600)
        artifacts = [
            _artifact(path, staging)
            for path in sorted(staging.rglob("*"))
            if path.is_file()
        ]
        manifest = {
            "schema_version": ANNOTATION_WORKSPACE_SCHEMA,
            "generation_id": generation_id,
            "identity_sha256": identity_sha256,
            "identity": identity,
            "created_at_utc": utc_now_iso(),
            "state": "prepared-unreviewed-model-draft",
            "ground_truth": False,
            "artifacts": artifacts,
            "next_gate": (
                "load both synchronized CVAT tasks, complete two-pass human review, export "
                "reviewed annotations, then seal a separate ground-truth generation"
            ),
        }
        write_json_atomic(staging / "manifest.json", manifest)
        os.chmod(staging / "manifest.json", 0o600)
        _fsync_tree(staging)
        os.replace(staging, final)
        _fsync_directory(parent)
        published = True
    finally:
        if not published and staging.exists():
            shutil.rmtree(staging)

    return validate_annotation_workspace(
        final,
        evaluation_pack_root=pack_root,
        prelabels_root=prelabel_root,
        valid_fov_root=valid_fov.root,
    )


def validate_annotation_workspace(
    workspace_root: Path,
    *,
    evaluation_pack_root: Path | None = None,
    prelabels_root: Path | None = None,
    valid_fov_root: Path | None = None,
) -> AnnotationWorkspace:
    root = workspace_root.expanduser().resolve(strict=True)
    if not root.is_dir() or _SAFE_GENERATION.fullmatch(root.name) is None:
        raise AnnotationWorkspaceError("annotation workspace root is invalid")
    manifest = _read_json_object(root / "manifest.json", root)
    identity = manifest.get("identity")
    identity_sha256 = manifest.get("identity_sha256")
    if (
        manifest.get("schema_version") != ANNOTATION_WORKSPACE_SCHEMA
        or manifest.get("generation_id") != root.name
        or manifest.get("state") != "prepared-unreviewed-model-draft"
        or manifest.get("ground_truth") is not False
        or not isinstance(identity, dict)
        or identity.get("schema_version") != ANNOTATION_WORKSPACE_IDENTITY_SCHEMA
        or identity.get("profile") != WORKSPACE_PROFILE
        or not isinstance(identity_sha256, str)
        or _SHA256.fullmatch(identity_sha256) is None
        or root.name != f"annotation-workspace-{identity_sha256}"
        or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
    ):
        raise AnnotationWorkspaceError("annotation workspace identity is inconsistent")
    artifacts = _validate_artifacts(root, manifest.get("artifacts"))
    required = {
        "README.md",
        "review-template.json",
        "source-map.jsonl",
        "valid-fov-mask.png",
        "cvat/images.zip",
        "cvat/instance-coco.zip",
        "cvat/instance-coco/annotations/instances_default.json",
        "cvat/labels.json",
        "cvat/semantic-mask.zip",
        "cvat/semantic-mask/default.txt",
        "cvat/semantic-mask/labelmap.txt",
    }
    if not required.issubset(artifacts):
        raise AnnotationWorkspaceError("annotation workspace artifacts are incomplete")
    frame_count = _positive_integer(identity.get("frame_count"), "frame count")
    instance_count = _nonnegative_integer(
        identity.get("draft_instance_count"), "draft instance count"
    )
    resolution = identity.get("resolution")
    if (
        not isinstance(resolution, list)
        or len(resolution) != 2
        or any(not isinstance(value, int) or isinstance(value, bool) for value in resolution)
    ):
        raise AnnotationWorkspaceError("annotation workspace resolution is invalid")
    width, height = resolution

    source_rows = _read_jsonl(root / "source-map.jsonl", root)
    review = _read_json_object(root / "review-template.json", root)
    names = [str(row.get("file_name")) for row in source_rows]
    if (
        len(source_rows) != frame_count
        or len(set(names)) != frame_count
        or review.get("annotation_workspace_id") != root.name
        or review.get("state") != "unreviewed"
        or not isinstance(review.get("frames"), list)
        or len(review["frames"]) != frame_count
        or [row.get("file_name") for row in review["frames"]] != names
    ):
        raise AnnotationWorkspaceError("annotation review template changed")

    coco = _read_json_object(
        root / "cvat" / "instance-coco" / "annotations" / "instances_default.json",
        root,
    )
    _validate_coco(coco, names, width, height, instance_count)
    mask = _load_mask(root / "valid-fov-mask.png", (width, height))
    _validate_archives(root, names, coco, mask)

    if evaluation_pack_root is not None:
        pack_root, pack, _contract, pack_frames = _load_evaluation_pack(evaluation_pack_root)
        if (
            identity.get("evaluation_pack_id") != pack["generation_id"]
            or identity.get("evaluation_manifest_sha256")
            != _sha256_file(pack_root / "manifest.json")
            or len(pack_frames) != frame_count
        ):
            raise AnnotationWorkspaceError("annotation workspace evaluation binding changed")
    if prelabels_root is not None:
        prelabel_resolved = prelabels_root.expanduser().resolve(strict=True)
        result = _read_json_object(prelabel_resolved / "result.json", prelabel_resolved)
        if (
            identity.get("prelabel_result_id") != result.get("result_id")
            or identity.get("prelabel_result_sha256")
            != _sha256_file(prelabel_resolved / "result.json")
        ):
            raise AnnotationWorkspaceError("annotation workspace prelabel binding changed")
    if valid_fov_root is not None:
        valid_fov = validate_k1_valid_fov_mask(valid_fov_root)
        if (
            identity.get("valid_fov_generation_id") != valid_fov.generation_id
            or identity.get("valid_fov_mask_sha256") != _sha256_file(valid_fov.mask_path)
            or not np.array_equal(mask, _load_mask(valid_fov.mask_path, (width, height)))
        ):
            raise AnnotationWorkspaceError("annotation workspace valid-FOV binding changed")

    return AnnotationWorkspace(
        generation_id=root.name,
        root=root,
        manifest_path=root / "manifest.json",
        images_archive_path=root / "cvat" / "images.zip",
        instance_archive_path=root / "cvat" / "instance-coco.zip",
        semantic_archive_path=root / "cvat" / "semantic-mask.zip",
        review_template_path=root / "review-template.json",
        frame_count=frame_count,
        draft_instance_count=instance_count,
    )


def _load_evaluation_pack(
    encoded_root: Path,
) -> tuple[Path, dict[str, Any], dict[str, Any], list[dict[str, Any]]]:
    root = encoded_root.expanduser().resolve(strict=True)
    manifest = _read_json_object(root / "manifest.json", root)
    identity = manifest.get("identity")
    identity_sha256 = manifest.get("identity_sha256")
    if (
        manifest.get("schema_version") != EVALUATION_PACK_SCHEMA
        or manifest.get("generation_id") != root.name
        or not isinstance(identity, dict)
        or identity.get("schema_version") != EVALUATION_PACK_IDENTITY_SCHEMA
        or not isinstance(identity_sha256, str)
        or _SHA256.fullmatch(identity_sha256) is None
        or root.name != f"evaluation-pack-{identity_sha256}"
        or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
    ):
        raise AnnotationWorkspaceError("evaluation pack identity is invalid")
    artifacts = _validate_artifacts(root, manifest.get("artifacts"))
    if "annotation-contract.json" not in artifacts:
        raise AnnotationWorkspaceError("evaluation annotation contract is unavailable")
    contract = _read_json_object(root / "annotation-contract.json", root)
    frames = identity.get("frames")
    if (
        contract.get("schema_version") != ANNOTATION_CONTRACT_SCHEMA
        or contract.get("evaluation_identity_sha256") != identity_sha256
        or not isinstance(frames, list)
        or not frames
    ):
        raise AnnotationWorkspaceError("evaluation annotation contract is invalid")
    for position, frame in enumerate(frames, start=1):
        if not isinstance(frame, dict) or frame.get("image_id") != position:
            raise AnnotationWorkspaceError("evaluation frame order is invalid")
        name = _frame_name(frame)
        if f"images/valid-fov-fill/{name}" not in artifacts:
            raise AnnotationWorkspaceError("evaluation frame artifact is unavailable")
    return root, manifest, contract, frames


def _load_prelabels(
    encoded_root: Path,
    *,
    pack: dict[str, Any],
    pack_frames: list[dict[str, Any]],
    valid_fov: K1ValidFovMask,
) -> tuple[Path, dict[str, Any], tuple[_PrelabelFrame, ...]]:
    root = encoded_root.expanduser().resolve(strict=True)
    result = _read_json_object(root / "result.json", root)
    identity = result.get("identity")
    identity_sha256 = result.get("identity_sha256")
    if (
        result.get("schema_version") != PRELABEL_SCHEMA
        or result.get("result_id") != root.name
        or result.get("review_state") != "unreviewed-model-draft"
        or not isinstance(identity, dict)
        or identity.get("schema_version") != PRELABEL_IDENTITY_SCHEMA
        or identity.get("evaluation_pack_id") != pack.get("generation_id")
        or identity.get("evaluation_identity_sha256") != pack.get("identity_sha256")
        or identity.get("valid_fov_generation_id") != valid_fov.generation_id
        or not isinstance(identity_sha256, str)
        or _SHA256.fullmatch(identity_sha256) is None
        or result.get("result_id") != f"evaluation-prelabels-{identity_sha256}"
        or not _prelabel_identity_matches(identity, identity_sha256)
    ):
        raise AnnotationWorkspaceError("prelabel result identity is invalid")
    artifacts = _validate_artifacts(root, result.get("artifacts"))
    rows = _read_jsonl(root / "frames.jsonl", root)
    if len(rows) != len(pack_frames):
        raise AnnotationWorkspaceError("prelabel frame count changed")
    valid_mask = _load_mask(valid_fov.mask_path, (valid_fov.width, valid_fov.height)) != 0
    admitted_labels = set(range(16))
    frames: list[_PrelabelFrame] = []
    for order, (row, source) in enumerate(zip(rows, pack_frames, strict=True), start=1):
        if (
            row.get("schema_version") != PRELABEL_FRAME_SCHEMA
            or row.get("review_state") != "unreviewed-model-draft"
            or any(
                row.get(key) != source.get(key)
                for key in ("image_id", "frame_index", "role", "group_id")
            )
            or row.get("session_seconds") != source.get("session_seconds")
            or not isinstance(row.get("instances"), list)
        ):
            raise AnnotationWorkspaceError("prelabel frame binding changed")
        instance_relative = f"instance-prelabels/image-{order:03d}.png"
        semantic_relative = f"semantic-prelabels/image-{order:03d}.png"
        if instance_relative not in artifacts or semantic_relative not in artifacts:
            raise AnnotationWorkspaceError("prelabel masks are unavailable")
        with Image.open(root / instance_relative) as opened:
            instance_map = np.asarray(opened, dtype=np.uint16).copy()
        with Image.open(root / semantic_relative) as opened:
            semantic_map = np.asarray(opened, dtype=np.uint8).copy()
        expected_shape = (valid_fov.height, valid_fov.width)
        instances = tuple(row["instances"])
        ids = set(int(value) for value in np.unique(instance_map))
        expected_ids = {0, *range(1, len(instances) + 1)}
        semantic_ids = set(int(value) for value in np.unique(semantic_map))
        if (
            instance_map.shape != expected_shape
            or semantic_map.shape != expected_shape
            or ids != expected_ids
            or not semantic_ids.issubset(admitted_labels)
            or np.any(instance_map[~valid_mask] != 0)
            or np.any(semantic_map[~valid_mask] != 0)
        ):
            raise AnnotationWorkspaceError("prelabel mask content is invalid")
        for expected_id, instance in enumerate(instances, start=1):
            if (
                not isinstance(instance, dict)
                or instance.get("instance_id") != expected_id
                or instance.get("review_state") != "unreviewed-model-draft"
                or not isinstance(instance.get("draft_category_id"), int)
                or not 1 <= int(instance["draft_category_id"]) <= 15
                or int(np.count_nonzero(instance_map == expected_id))
                != instance.get("mask_pixels")
            ):
                raise AnnotationWorkspaceError("prelabel instance metadata changed")
        frames.append(
            _PrelabelFrame(
                image_id=order,
                frame_index=int(source["frame_index"]),
                session_seconds=float(source["session_seconds"]),
                role=str(source["role"]),
                group_id=str(source["group_id"]),
                instances=instances,
                instance_map=instance_map,
                semantic_map=semantic_map,
            )
        )
    return root, result, tuple(frames)


def _prelabel_identity_matches(identity: dict[str, Any], expected_sha256: str) -> bool:
    if hashlib.sha256(_canonical_json(identity)).hexdigest() == expected_sha256:
        return True
    # The first E2 producer hashed TARGET_NAMES while its JSON keys were integers.
    # JSON persisted those keys as strings, changing lexical sort order after reload.
    # Admit only that exact, reversible representation bug; all payload hashes and the
    # serialized result.json remain bound separately by the annotation workspace.
    target_categories = identity.get("target_categories")
    if not isinstance(target_categories, dict) or not target_categories:
        return False
    if any(not isinstance(key, str) or not key.isdecimal() for key in target_categories):
        return False
    legacy_identity = dict(identity)
    legacy_identity["target_categories"] = {
        int(key): value for key, value in target_categories.items()
    }
    return hashlib.sha256(_canonical_json(legacy_identity)).hexdigest() == expected_sha256


def _validate_valid_fov_binding(pack: dict[str, Any], valid_fov: K1ValidFovMask) -> None:
    identity = pack.get("identity")
    if not isinstance(identity, dict):
        raise AnnotationWorkspaceError("evaluation pack identity is unavailable")
    if (
        identity.get("valid_fov_generation_id") != valid_fov.generation_id
        or identity.get("calibration_sha256") != valid_fov.calibration_sha256
        or identity.get("calibration_slot") != valid_fov.calibration_slot
        or identity.get("source_id") != valid_fov.source_id
        or identity.get("resolution") != [valid_fov.width, valid_fov.height]
    ):
        raise AnnotationWorkspaceError("valid-FOV does not match the evaluation pack")


def _contract_categories(contract: dict[str, Any]) -> list[dict[str, Any]]:
    encoded = contract.get("categories")
    if not isinstance(encoded, list) or len(encoded) != 15:
        raise AnnotationWorkspaceError("annotation category contract is invalid")
    categories: list[dict[str, Any]] = []
    for expected_id, row in enumerate(encoded, start=1):
        if (
            not isinstance(row, dict)
            or row.get("id") != expected_id
            or not isinstance(row.get("name"), str)
            or row.get("kind") not in {"thing", "stuff"}
        ):
            raise AnnotationWorkspaceError("annotation category contract changed")
        categories.append(row)
    return categories


def _coco_document(
    source_frames: list[dict[str, Any]],
    prelabel_frames: tuple[_PrelabelFrame, ...],
    categories: list[dict[str, Any]],
    names: list[str],
) -> dict[str, Any]:
    images = [
        {
            "id": int(row["image_id"]),
            "file_name": name,
            "width": 800,
            "height": 600,
        }
        for row, name in zip(source_frames, names, strict=True)
    ]
    annotations: list[dict[str, Any]] = []
    annotation_id = 1
    for frame in prelabel_frames:
        for instance in frame.instances:
            instance_id = int(instance["instance_id"])
            mask = frame.instance_map == instance_id
            ys, xs = np.nonzero(mask)
            if xs.size == 0 or ys.size == 0:
                raise AnnotationWorkspaceError("prelabel instance mask is empty")
            left = int(xs.min())
            top = int(ys.min())
            right = int(xs.max()) + 1
            bottom = int(ys.max()) + 1
            annotations.append(
                {
                    "id": annotation_id,
                    "image_id": frame.image_id,
                    "category_id": int(instance["draft_category_id"]),
                    "segmentation": {
                        "size": [int(mask.shape[0]), int(mask.shape[1])],
                        "counts": _uncompressed_coco_rle(mask),
                    },
                    "area": int(mask.sum()),
                    "bbox": [left, top, right - left, bottom - top],
                    "iscrowd": 1,
                    "attributes": {
                        "missioncore_review_state": "unreviewed-model-draft",
                        "missioncore_source_score": instance.get("score"),
                        "missioncore_source_category": instance.get("source_model_category"),
                    },
                }
            )
            annotation_id += 1
    thing_categories = [row for row in categories if row["kind"] == "thing"]
    return {
        "info": {
            "description": "Mission Core E2 model-assisted draft; human review required",
            "version": "1.0",
        },
        "licenses": [],
        "images": images,
        "annotations": annotations,
        "categories": [
            {"id": row["id"], "name": row["name"], "supercategory": "thing"}
            for row in thing_categories
        ],
    }


def _uncompressed_coco_rle(mask: np.ndarray[Any, np.dtype[np.bool_]]) -> list[int]:
    pixels = mask.reshape(-1, order="F").astype(np.uint8, copy=False)
    if pixels.size == 0:
        return []
    changes = np.flatnonzero(pixels[1:] != pixels[:-1]) + 1
    lengths = np.diff(np.concatenate(([0], changes, [pixels.size])))
    if int(pixels[0]) == 1:
        lengths = np.concatenate(([0], lengths))
    return [int(value) for value in lengths]


def _semantic_payloads(
    frames: tuple[_PrelabelFrame, ...],
    names: list[str],
    categories: list[dict[str, Any]],
) -> tuple[bytes, ...]:
    del names, categories
    palette = np.zeros((256, 3), dtype=np.uint8)
    for category_id, color in _CATEGORY_COLORS.items():
        palette[category_id] = color
    payloads: list[bytes] = []
    for frame in frames:
        image = Image.fromarray(palette[frame.semantic_map], mode="RGB")
        buffer = io.BytesIO()
        image.save(buffer, format="PNG", optimize=False)
        payloads.append(buffer.getvalue())
    return tuple(payloads)


def _labels_document(categories: list[dict[str, Any]]) -> dict[str, Any]:
    def label(row: dict[str, Any]) -> dict[str, Any]:
        color = _CATEGORY_COLORS[int(row["id"])]
        return {
            "id": row["id"],
            "name": row["name"],
            "kind": row["kind"],
            "color": "#" + "".join(f"{value:02X}" for value in color),
        }

    return {
        "schema_version": "missioncore.perception-cvat-labels/v1",
        "instance_task": [label(row) for row in categories if row["kind"] == "thing"],
        "semantic_task": [label(row) for row in categories]
        + [
            {
                "id": 255,
                "name": "ambiguous_or_unresolvable",
                "kind": "ignore",
                "color": "#FFFFFF",
            }
        ],
    }


def _labelmap(categories: list[dict[str, Any]]) -> str:
    rows = ["background:0,0,0::"]
    for row in categories:
        color = ",".join(str(value) for value in _CATEGORY_COLORS[int(row["id"])])
        rows.append(f"{row['name']}:{color}::")
    rows.append("ambiguous_or_unresolvable:255,255,255::")
    return "\n".join(rows) + "\n"


def _frame_names(frames: list[dict[str, Any]]) -> list[str]:
    return [_frame_name(row) for row in frames]


def _frame_name(frame: dict[str, Any]) -> str:
    return f"image-{int(frame['image_id']):03d}-frame-{int(frame['frame_index']):06d}.png"


def _write_source_map(path: Path, frames: list[dict[str, Any]], names: list[str]) -> None:
    with path.open("x", encoding="utf-8", newline="\n") as stream:
        for row, name in zip(frames, names, strict=True):
            payload = {
                "image_id": row["image_id"],
                "file_name": name,
                "frame_index": row["frame_index"],
                "sequence": row["sequence"],
                "segment_sha256": row["segment_sha256"],
                "session_seconds": row["session_seconds"],
                "role": row["role"],
                "group_id": row["group_id"],
            }
            stream.write(json.dumps(payload, sort_keys=True, separators=(",", ":")) + "\n")
        stream.flush()
        os.fsync(stream.fileno())


def _review_template(
    generation_id: str,
    evaluation_pack_id: object,
    frames: list[dict[str, Any]],
    names: list[str],
) -> dict[str, Any]:
    return {
        "schema_version": "missioncore.perception-annotation-review/v1",
        "annotation_workspace_id": generation_id,
        "evaluation_pack_id": evaluation_pack_id,
        "state": "unreviewed",
        "annotator": "",
        "reviewer": "",
        "frames": [
            {
                "image_id": row["image_id"],
                "file_name": name,
                "instance_annotation_status": "draft-loaded",
                "semantic_annotation_status": "draft-loaded",
                "review_status": "unreviewed",
                "notes": "",
            }
            for row, name in zip(frames, names, strict=True)
        ],
    }


def _readme(generation_id: str, pack_id: object, prelabel_id: object, count: int) -> str:
    return f"""# Mission Core LAB E2 annotation workspace

Workspace: `{generation_id}`

This package contains {count} synchronized fixed-valid-FOV images. Its labels are
model-assisted drafts, not ground truth and not an accuracy measurement.

## CVAT import

1. Create an image task for instance review with the seven `thing` labels from
   `cvat/labels.json`; upload `cvat/images.zip` as task data.
2. Upload `cvat/instance-coco.zip` as `COCO 1.0` annotations. The RLE masks use
   `iscrowd=1` only to preserve pixel masks during CVAT import; each annotation is
   still one draft object and must be reviewed.
3. Create a second image task for dense semantic review with the semantic labels
   from `cvat/labels.json`; upload the same `cvat/images.zip`.
4. Upload `cvat/semantic-mask.zip` as `Segmentation Mask 1.1` annotations.
5. Keep both tasks in lexicographic order. `source-map.jsonl` binds every shared
   filename to the exact evaluation frame and session timestamp.
6. Complete all pixels inside `valid-fov-mask.png`, review every thing instance,
   and use `ambiguous_or_unresolvable` only where the contract permits it.
7. Export both reviewed tasks and complete a mutable copy of
   `review-template.json`. A separate command will validate and seal the accepted
   exports as ground truth; do not edit this generated workspace.

Bindings:

- evaluation pack: `{pack_id}`
- draft prelabels: `{prelabel_id}`

The two-task split is deliberate: COCO preserves editable thing instances while
Segmentation Mask preserves the dense thing/stuff pixel map. Equal filenames are
the synchronization key.
"""


def _validate_coco(
    coco: dict[str, Any], names: list[str], width: int, height: int, instance_count: int
) -> None:
    images = coco.get("images")
    annotations = coco.get("annotations")
    categories = coco.get("categories")
    if (
        not isinstance(images, list)
        or not isinstance(annotations, list)
        or not isinstance(categories, list)
        or len(images) != len(names)
        or len(annotations) != instance_count
        or [row.get("file_name") for row in images if isinstance(row, dict)] != names
    ):
        raise AnnotationWorkspaceError("COCO draft content changed")
    image_ids = {row.get("id") for row in images if isinstance(row, dict)}
    category_ids = {row.get("id") for row in categories if isinstance(row, dict)}
    for expected_id, row in enumerate(annotations, start=1):
        if not isinstance(row, dict):
            raise AnnotationWorkspaceError("COCO annotation is invalid")
        segmentation = row.get("segmentation")
        if not isinstance(segmentation, dict):
            raise AnnotationWorkspaceError("COCO segmentation is invalid")
        counts = segmentation.get("counts")
        if (
            row.get("id") != expected_id
            or row.get("image_id") not in image_ids
            or row.get("category_id") not in category_ids
            or row.get("iscrowd") != 1
            or not isinstance(counts, list)
            or not counts
            or any(
                not isinstance(value, int) or isinstance(value, bool) or value < 0
                for value in counts
            )
            or sum(counts) != width * height
            or segmentation.get("size") != [height, width]
            or row.get("area") != sum(counts[1::2])
        ):
            raise AnnotationWorkspaceError("COCO RLE annotation changed")


def _validate_archives(
    root: Path, names: list[str], coco: dict[str, Any], valid_mask: np.ndarray[Any, Any]
) -> None:
    with zipfile.ZipFile(root / "cvat" / "images.zip") as archive:
        _safe_zip_names(archive)
        if archive.namelist() != names:
            raise AnnotationWorkspaceError("CVAT image archive order changed")
        for name in names:
            with Image.open(io.BytesIO(archive.read(name))) as opened:
                if opened.size != (valid_mask.shape[1], valid_mask.shape[0]):
                    raise AnnotationWorkspaceError("CVAT image dimensions changed")
    with zipfile.ZipFile(root / "cvat" / "instance-coco.zip") as archive:
        _safe_zip_names(archive)
        if archive.namelist() != ["annotations/instances_default.json"]:
            raise AnnotationWorkspaceError("CVAT COCO archive structure changed")
        archived = json.loads(archive.read(archive.namelist()[0]))
        if archived != coco:
            raise AnnotationWorkspaceError("CVAT COCO archive content changed")
    expected_semantic = [
        "labelmap.txt",
        "ImageSets/Segmentation/default.txt",
        *(f"SegmentationClass/{name}" for name in names),
    ]
    admitted_colors = set(_CATEGORY_COLORS.values())
    with zipfile.ZipFile(root / "cvat" / "semantic-mask.zip") as archive:
        _safe_zip_names(archive)
        if archive.namelist() != expected_semantic:
            raise AnnotationWorkspaceError("CVAT semantic archive structure changed")
        for name in names:
            with Image.open(io.BytesIO(archive.read(f"SegmentationClass/{name}"))) as opened:
                rgb = np.asarray(opened.convert("RGB"), dtype=np.uint8)
            if rgb.shape != (*valid_mask.shape, 3):
                raise AnnotationWorkspaceError("CVAT semantic mask dimensions changed")
            colors = {
                tuple(int(value) for value in row)
                for row in np.unique(rgb.reshape(-1, 3), axis=0)
            }
            if not colors.issubset(admitted_colors) or np.any(rgb[valid_mask == 0] != 0):
                raise AnnotationWorkspaceError("CVAT semantic mask labels changed")


def _safe_zip_names(archive: zipfile.ZipFile) -> None:
    for name in archive.namelist():
        path = PurePosixPath(name)
        if path.is_absolute() or ".." in path.parts or not path.parts:
            raise AnnotationWorkspaceError("annotation archive path is unsafe")


def _write_zip(path: Path, entries: tuple[tuple[str, bytes], ...]) -> None:
    with zipfile.ZipFile(path, "x") as archive:
        for name, payload in entries:
            info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0))
            info.compress_type = zipfile.ZIP_DEFLATED
            info.external_attr = (stat.S_IFREG | 0o600) << 16
            archive.writestr(info, payload, compresslevel=9)


def _validate_artifacts(root: Path, encoded: object) -> dict[str, dict[str, Any]]:
    if not isinstance(encoded, list) or not encoded:
        raise AnnotationWorkspaceError("artifact inventory is unavailable")
    artifacts: dict[str, dict[str, Any]] = {}
    for row in encoded:
        if not isinstance(row, dict) or not isinstance(row.get("path"), str):
            raise AnnotationWorkspaceError("artifact descriptor is invalid")
        relative = PurePosixPath(row["path"])
        if relative.is_absolute() or ".." in relative.parts or not relative.parts:
            raise AnnotationWorkspaceError("artifact path is unsafe")
        name = relative.as_posix()
        path = root.joinpath(*relative.parts).resolve(strict=True)
        metadata = path.stat()
        digest = row.get("sha256")
        if (
            name in artifacts
            or not path.is_file()
            or not path.is_relative_to(root)
            or stat.S_ISLNK(metadata.st_mode)
            or row.get("byte_length") != metadata.st_size
            or not isinstance(digest, str)
            or _SHA256.fullmatch(digest) is None
            or _sha256_file(path) != digest
        ):
            raise AnnotationWorkspaceError("annotation artifact changed")
        artifacts[name] = row
    return artifacts


def _read_json_object(path: Path, root: Path) -> dict[str, Any]:
    resolved = path.resolve(strict=True)
    if (
        not resolved.is_file()
        or not resolved.is_relative_to(root)
        or resolved.stat().st_size > MAX_JSON_BYTES
    ):
        raise AnnotationWorkspaceError("JSON artifact is unavailable")
    try:
        payload = json.loads(resolved.read_text(encoding="utf-8"))
    except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
        raise AnnotationWorkspaceError("JSON artifact is invalid") from exc
    if not isinstance(payload, dict):
        raise AnnotationWorkspaceError("JSON artifact is not an object")
    return payload


def _read_jsonl(path: Path, root: Path) -> list[dict[str, Any]]:
    resolved = path.resolve(strict=True)
    if not resolved.is_file() or not resolved.is_relative_to(root):
        raise AnnotationWorkspaceError("JSONL artifact is unavailable")
    rows: list[dict[str, Any]] = []
    try:
        with resolved.open("r", encoding="utf-8") as stream:
            for line in stream:
                if len(line.encode("utf-8")) > MAX_JSONL_LINE_BYTES:
                    raise AnnotationWorkspaceError("JSONL row is too large")
                row = json.loads(line)
                if not isinstance(row, dict):
                    raise AnnotationWorkspaceError("JSONL row is invalid")
                rows.append(row)
    except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
        raise AnnotationWorkspaceError("JSONL artifact is invalid") from exc
    return rows


def _load_mask(path: Path, size: tuple[int, int]) -> np.ndarray[Any, np.dtype[np.uint8]]:
    try:
        with Image.open(path) as opened:
            image = opened.copy()
    except OSError as exc:
        raise AnnotationWorkspaceError("valid-FOV mask is unavailable") from exc
    if image.mode != "L" or image.size != size:
        raise AnnotationWorkspaceError("valid-FOV mask dimensions changed")
    mask = np.asarray(image, dtype=np.uint8)
    if not set(int(value) for value in np.unique(mask)).issubset({0, 255}):
        raise AnnotationWorkspaceError("valid-FOV mask values changed")
    return mask


def _validate_producer_files(files: tuple[tuple[str, str], ...]) -> list[dict[str, str]]:
    if not 1 <= len(files) <= 8:
        raise AnnotationWorkspaceError("annotation producer file set is invalid")
    normalized: list[dict[str, str]] = []
    seen: set[str] = set()
    for name, digest in files:
        if (
            not isinstance(name, str)
            or _SAFE_PRODUCER.fullmatch(name) is None
            or name in seen
            or not isinstance(digest, str)
            or _SHA256.fullmatch(digest) is None
        ):
            raise AnnotationWorkspaceError("annotation producer file identity is invalid")
        normalized.append({"name": name, "sha256": digest})
        seen.add(name)
    return normalized


def _artifact(path: Path, root: Path) -> dict[str, Any]:
    return {
        "path": path.relative_to(root).as_posix(),
        "byte_length": path.stat().st_size,
        "sha256": _sha256_file(path),
    }


def _positive_integer(value: object, label: str) -> int:
    if not isinstance(value, int) or isinstance(value, bool) or value <= 0:
        raise AnnotationWorkspaceError(f"{label} is invalid")
    return value


def _nonnegative_integer(value: object, label: str) -> int:
    if not isinstance(value, int) or isinstance(value, bool) or value < 0:
        raise AnnotationWorkspaceError(f"{label} is invalid")
    return value


def _prepare_private_directory(path: Path) -> Path:
    root = path.expanduser().resolve()
    root.mkdir(mode=0o700, parents=True, exist_ok=True)
    if not root.is_dir() or root.is_symlink():
        raise AnnotationWorkspaceError("annotation output root is invalid")
    os.chmod(root, 0o700)
    return root


def _sha256_file(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        for chunk in iter(lambda: stream.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def _canonical_json(value: object) -> bytes:
    return json.dumps(
        value,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
        allow_nan=False,
    ).encode("utf-8")


def _fsync_file(path: Path) -> None:
    with path.open("rb") as stream:
        os.fsync(stream.fileno())


def _fsync_directory(path: Path) -> None:
    descriptor = os.open(path, os.O_RDONLY)
    try:
        os.fsync(descriptor)
    finally:
        os.close(descriptor)


def _fsync_tree(root: Path) -> None:
    for path in sorted(root.rglob("*"), reverse=True):
        if path.is_dir():
            _fsync_directory(path)
    _fsync_directory(root)
