"""
Sensorium Receipts — structured perceptions for the screen/vision organ.

Two granularities:
  - SensoriumReceipt: single-frame (or moment) perception with AX/VLM/voice context.
  - VideoSensorReceipt: short clip/keyframe bundle with hashes/detections/AX summaries.

These receipts are meant to keep perception lineage auditable and queryable by
time, app/window, and detected elements.
"""
from __future__ import annotations

from typing import Any, Dict, List, Optional
from datetime import datetime, timezone
from pydantic import Field

from receipts.base import BaseReceipt


class SensoriumReceipt(BaseReceipt):
    """
    Single-frame (or moment) perception snapshot.

    Carries the hashes/ids of the captured frame, AX summary, VLM detections,
    and optional voice intent that triggered the observation.
    """

    receipt_type: str = Field(default="SensoriumReceipt", alias="type")
    domain: str = Field(default="organism")
    organ: str = Field(default="sensorium")

    # Frame metadata
    frame_id: Optional[str] = Field(default=None, description="Local identifier for the frame")
    frame_hash: Optional[str] = Field(default=None, description="Hash of the frame pixels")
    frame_path: Optional[str] = Field(default=None, description="Path to stored frame (if persisted)")
    width: Optional[int] = None
    height: Optional[int] = None

    # Context
    app_bundle_id: Optional[str] = Field(default=None, description="Focused app bundle id")
    window_title: Optional[str] = Field(default=None, description="Window title at capture")
    stale_perception: bool = Field(default=False, description="True if perception is considered stale")
    stale_reason: Optional[str] = Field(default=None, description="Why perception was stale")

    # AX snapshot
    ax_hash: Optional[str] = Field(default=None, description="Hash of AX tree for this frame")
    ax_elements: List[Dict[str, Any]] = Field(
        default_factory=list, description="AX elements (subset) relevant to this perception"
    )

    # VLM / detections
    vlm_detections: List[Dict[str, Any]] = Field(
        default_factory=list, description="Structured VLM/VQA detections (label/bbox/confidence/text)"
    )

    # Voice intent that preceded this perception (if any)
    voice_intent: Optional[Dict[str, Any]] = Field(
        default=None, description="Voice intent payload tied to this perception"
    )

    @classmethod
    def now(cls, **kwargs: Any) -> "SensoriumReceipt":
        """Convenience constructor with current timestamp."""
        return cls(ts=datetime.now(timezone.utc), **kwargs)


class VideoSensorReceipt(BaseReceipt):
    """
    Short-clip / keyframe bundle.

    Carries hashes and summaries for a clip or a set of keyframes, plus
    aggregated detections and AX snippets.
    """

    receipt_type: str = Field(default="VideoSensorReceipt", alias="type")
    domain: str = Field(default="organism")
    organ: str = Field(default="sensorium")

    clip_id: str = Field(description="Identifier for this clip/keyframe bundle")
    video_id: Optional[str] = Field(default=None, description="Upstream video/source id")
    start_ts: float = Field(description="Start time (seconds) relative to video/stream")
    end_ts: float = Field(description="End time (seconds) relative to video/stream")

    frame_hashes: List[str] = Field(default_factory=list, description="Frame hashes included in this bundle")
    frame_paths: List[str] = Field(
        default_factory=list,
        description="Paths to persisted frames (optional, may be empty if not saved)",
    )
    keyframe_indices: List[int] = Field(
        default_factory=list,
        description="Indices into frame_hashes that correspond to keyframes",
    )
    clip_path: Optional[str] = Field(default=None, description="Path to persisted clip (optional)")

    # Aggregated perceptions
    vlm_events: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="Detected events/entities over the clip (label/bbox/time/text/confidence)",
    )
    ax_summaries: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="AX snippets or hashes for representative frames",
    )
    app_bundle_ids: List[str] = Field(
        default_factory=list,
        description="Apps observed during this clip",
    )
    stale_flag: bool = Field(default=False, description="True if clip is considered stale/out-of-sync")
    stale_reason: Optional[str] = None

    @classmethod
    def from_hashes(
        cls,
        clip_id: str,
        frame_hashes: List[str],
        start_ts: float,
        end_ts: float,
        frame_paths: Optional[List[str]] = None,
        **kwargs: Any,
    ) -> "VideoSensorReceipt":
        """Helper to build a receipt from basic clip info."""
        receipt_id = kwargs.pop("receipt_id", f"videosensor_{clip_id}_{int(start_ts * 1000)}")
        return cls(
            receipt_id=receipt_id,
            clip_id=clip_id,
            frame_hashes=frame_hashes,
            frame_paths=frame_paths or [],
            start_ts=start_ts,
            end_ts=end_ts,
            **kwargs,
        )
