"""
Contextra AI Analytics — Scenario Execution Context (v2 구동 정책)

역할:
  - 프레임 단위 시나리오 평가의 표준 입력 계약.
  - ai_engine 철학 이식: dict-soup data_bus 대신 타입화된 컨텍스트 객체.
    엔진은 evaluate(ctx) 하나만 받는다 — 프레임 메타/탐지/zone이 한 곳에서 온다.

설계 원칙:
  - zones는 픽셀 공간 (DB 정규화 좌표를 ScenarioRunner가 스케일링).
  - 컨텍스트는 프레임당 1회 빌드, 모든 활성 엔진에 공유 (불변 사용 전제).
  - 이벤트 출력 계약(ScenarioEvent)은 v1과 동일 — 시나리오 팀 영향 없음.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple

from .base_scenario import Detection, ZoneROI


@dataclass(frozen=True)
class FrameMeta:
    """프레임 식별 메타데이터 (이벤트 감사/저장 키)."""
    private_key: str          # 프레임 고유 키 (PG frames.private_key)
    stream_id: str            # CCTV 스트림 ID
    site_id: str              # 사이트 ID
    timestamp_ms: int         # UTC epoch ms
    frame_size: Optional[Tuple[int, int]] = None   # (w, h) 픽셀 — zone 스케일링 기준


@dataclass
class ScenarioContext:
    """1프레임의 시나리오 평가 전체 입력.

    ScenarioRunner가 det consume()에서 빌드해 활성 엔진들에게 전달한다.

    Attributes:
        meta: 프레임 식별 메타 (frozen — 엔진이 수정 불가).
        detections: 이 프레임의 det 결과 (track_id 포함, 픽셀 xyxy).
            Phase 3 IoU 트래커가 track_id를 채우므로 상태머신 기반
            시나리오(침입/배회)가 track 단위로 동작한다.
        zones: 논리 zone 이름 → 픽셀 공간 ZoneROI 맵.
            DB의 정규화(0~1) 좌표를 현재 프레임 해상도로 변환한 것.
            version 변경/해상도 변경 시에만 재스케일링 (Runner 책임).

    Usage (엔진 측):
        def evaluate(self, ctx: ScenarioContext) -> List[ScenarioEvent]:
            zone = ctx.get_zone("restricted_zone")
            if zone is None:
                return []   # Runner가 이미 가드했지만 방어적 체크
            for det in ctx.detections:
                ...
    """
    meta: FrameMeta
    detections: List[Detection] = field(default_factory=list)
    zones: Dict[str, ZoneROI] = field(default_factory=dict)

    # ---- 편의 프로퍼티 -------------------------------------------------

    @property
    def now_ms(self) -> int:
        """현재 프레임 타임스탬프 (ms). 상태머신 시간 게이트 기준."""
        return self.meta.timestamp_ms

    @property
    def stream_id(self) -> str:
        return self.meta.stream_id

    def get_zone(self, name: str) -> Optional[ZoneROI]:
        """논리 zone 이름으로 픽셀 공간 ZoneROI 조회 (없으면 None)."""
        return self.zones.get(name)

    def detections_of(self, class_name: str) -> List[Detection]:
        """클래스별 탐지 필터 (registry dependencies 검증용)."""
        return [d for d in self.detections if d.class_name == class_name]
