"""FRAMEIMPL-01: Frame identity는 Pixel이나 role별 재발급 순번을 소유하지 않는다."""

from dataclasses import dataclass
from math import isfinite


@dataclass(frozen=True)
class FrameTime:
    anchor_id: str | None = None
    anchor_wall_time_ms: int | None = None
    anchor_rtp_timestamp: int | None = None
    rtp_clock_rate: int | None = None
    source_rtp_timestamp: int | None = None
    source_rtp_timestamp_ext: int | None = None
    source_time_wall_ms: int | None = None
    sync_status: str = "UNAVAILABLE"
    time_source: str | None = None
    # Private identity metadata; frame_snapshot does not export these fields to Redis.
    invalidation_reason: str | None = None
    invalidated_at_wall_ms: int | None = None
    invalidated_frames: int = 0

    def __post_init__(self):
        if self.sync_status not in {"UNAVAILABLE", "RAW_ONLY", "ESTIMATED", "SYNCED"}:
            raise ValueError("invalid sync status")


@dataclass(frozen=True)
class FrameEnvelope:
    server_id: str
    source_id: str
    stream_id: str
    run_id: str
    run_generation: int
    frame_seq: int
    pts_ns: int | None
    ingest_time_wall_ms: int
    frame_width: int
    frame_height: int
    config_version: str | int | None = None
    frame_time: FrameTime | None = None

    def __post_init__(self):
        if any(type(v) is not str or not v for v in (
            self.server_id, self.source_id, self.stream_id, self.run_id,
        )):
            raise ValueError("invalid frame identity")
        if min(self.frame_width, self.frame_height) <= 0:
            raise ValueError("invalid frame dimensions")
        if self.frame_seq < 0 or self.run_generation < 1:
            raise ValueError("invalid frame identity")


@dataclass(frozen=True)
class DetectionObservation:
    frame: FrameEnvelope
    model_key: str
    class_id: int
    score: float
    bbox_xyxy: tuple[float, float, float, float]

    def __post_init__(self):
        _validate_result(self)
        if self.class_id < 0:
            raise ValueError("invalid canonical class id")


@dataclass(frozen=True)
class PoseObservation:
    frame: FrameEnvelope
    model_key: str
    score: float
    bbox_xyxy: tuple[float, float, float, float]
    keypoints: tuple[tuple[float, float, float], ...]

    def __post_init__(self):
        _validate_result(self)
        points = tuple(tuple(point) for point in self.keypoints)
        if len(points) != 17 or any(len(p) != 3 or any(not isfinite(x) for x in p)
                                    or not 0 <= p[2] <= 1 for p in points):
            raise ValueError("invalid normalized keypoints")
        object.__setattr__(self, "keypoints", points)


def _validate_result(result):
    box = tuple(result.bbox_xyxy)
    if (len(box) != 4 or any(not isfinite(x) or not 0 <= x <= 1 for x in box)
            or box[0] > box[2] or box[1] > box[3]
            or not isfinite(result.score) or not 0 <= result.score <= 1):
        raise ValueError("invalid normalized observation")
    object.__setattr__(result, "bbox_xyxy", box)
