"""
Constitutional Sensory Receipt Schemas.

Phase I Embodiment: "Make perception constitutional before making perception powerful."

This module defines the core sensory observation receipts for Loom's embodied
perception system. Every observation must flow through Guardian Clause 0
(the Perception Membrane) before becoming a valid Percept.

Tri-Temporal Model:
  - event_time: When the real-world phenomenon occurred (e.g., camera shutter)
  - observed_at: When the system observed/ingested the data
  - receipt_at: When this receipt was created (standard ts field)

Sensory Provenance Chain:
  Observation → [Membrane] → Percept → Decision → Action → Memory

Key Receipts:
  - VisionObservationReceipt: Raw visual capture metadata (not the image itself)
  - VoiceObservationReceipt: Raw audio capture metadata (not the audio itself)
  - PerceptReceipt: Membrane-approved observation ready for downstream use
  - PerceptionMembraneReceipt: Guardian Clause 0 decision trace
"""

from __future__ import annotations

import hashlib
import uuid
from datetime import datetime, timezone
from enum import Enum
from typing import Any, Dict, List, Literal, Optional, Tuple

from pydantic import Field, field_validator, model_validator

from receipts.base import BaseReceipt, Domain
from receipts.compat.pyd import BaseModel, ConfigDictLike


# =============================================================================
# ENUMS & CONSTANTS
# =============================================================================


class SensoryModality(str, Enum):
    """Sensory input modalities supported by the perception system."""

    VISION = "vision"
    VOICE = "voice"
    AUDIO = "audio"  # Non-voice audio (ambient, alerts, music)
    HAPTIC = "haptic"  # Future: touch/force feedback
    PROPRIOCEPTIVE = "proprioceptive"  # Future: body state awareness


class VisionCaptureMode(str, Enum):
    """Fidelity levels for visual capture."""

    SEMANTIC_ONLY = "semantic_only"  # Text/structure extraction, no pixels
    LOW_RES = "low_res"  # Downsampled, possibly grayscale
    FOVEA = "fovea"  # High-res crop of specific region
    FULL_FRAME = "full_frame"  # Complete capture (requires elevated consent)


class VoiceCaptureMode(str, Enum):
    """Fidelity levels for voice capture."""

    VAD_ONLY = "vad_only"  # Voice activity detection only
    TRANSCRIPT = "transcript"  # Speech-to-text only (audio discarded)
    LOW_FIDELITY = "low_fidelity"  # Compressed audio
    HIGH_FIDELITY = "high_fidelity"  # Full audio capture (requires elevated consent)


class ConsentLevel(str, Enum):
    """Consent tiers for sensory data collection."""

    NONE = "none"  # No consent on record
    IMPLICIT = "implicit"  # Inferred from context (e.g., user initiated voice mode)
    SESSION = "session"  # Consent granted for current session
    PERSISTENT = "persistent"  # Standing consent on file
    ELEVATED = "elevated"  # Explicit consent for high-fidelity capture


class MembraneVerdict(str, Enum):
    """Outcomes from Guardian Clause 0 (Perception Membrane)."""

    APPROVED = "approved"  # Observation may proceed to Percept
    DOWNGRADED = "downgraded"  # Approved at lower fidelity than requested
    DEFERRED = "deferred"  # Needs additional consent before proceeding
    REJECTED = "rejected"  # Observation blocked (dignity/consent violation)


class DataMinimizationPolicy(str, Enum):
    """Policies governing what data is retained vs. discarded."""

    AGGRESSIVE = "aggressive"  # Discard raw data immediately after processing
    STANDARD = "standard"  # Retain for session, discard after
    EXTENDED = "extended"  # Retain for audit period
    COURT_GRADE = "court_grade"  # Full retention with chain of custody


# =============================================================================
# SOURCE METADATA MODELS
# =============================================================================


class VisionSourceMetadata(BaseModel):
    """Metadata about the source of a visual observation."""

    model_config = ConfigDictLike(frozen=True)

    # Source identification
    device_id: Optional[str] = Field(
        default=None,
        description="Unique identifier for the capture device (hashed if PII-sensitive)"
    )
    device_type: str = Field(
        default="screen",
        description="Type of capture: screen | camera | screenshot | upload"
    )

    # Context
    app_name: Optional[str] = Field(
        default=None,
        description="Active application at capture time"
    )
    window_title_hash: Optional[str] = Field(
        default=None,
        description="SHA-256 hash of window title (privacy-preserving)"
    )
    display_index: Optional[int] = Field(
        default=None,
        description="Display number for multi-monitor setups"
    )

    # Region (for partial captures)
    region_x: Optional[int] = Field(default=None, description="X coordinate of capture region")
    region_y: Optional[int] = Field(default=None, description="Y coordinate of capture region")
    region_width: Optional[int] = Field(default=None, description="Width of capture region")
    region_height: Optional[int] = Field(default=None, description="Height of capture region")

    # Quality indicators
    capture_latency_ms: Optional[int] = Field(
        default=None,
        description="Time from trigger to capture completion"
    )
    estimated_staleness_ms: Optional[int] = Field(
        default=None,
        description="Estimated age of the captured content"
    )


class VoiceSourceMetadata(BaseModel):
    """Metadata about the source of a voice/audio observation."""

    model_config = ConfigDictLike(frozen=True)

    # Source identification
    device_id: Optional[str] = Field(
        default=None,
        description="Unique identifier for the audio input device"
    )
    device_type: str = Field(
        default="microphone",
        description="Type of capture: microphone | system_audio | voip | upload"
    )

    # Audio characteristics
    sample_rate_hz: Optional[int] = Field(
        default=None,
        description="Audio sample rate (e.g., 16000, 44100)"
    )
    channels: Optional[int] = Field(default=None, description="Number of audio channels")
    duration_ms: Optional[int] = Field(
        default=None,
        description="Duration of the audio segment in milliseconds"
    )

    # Context
    app_name: Optional[str] = Field(
        default=None,
        description="Active application during capture (e.g., Zoom, Chrome)"
    )
    session_id: Optional[str] = Field(
        default=None,
        description="Session identifier for multi-turn conversations"
    )

    # Quality indicators
    vad_confidence: Optional[float] = Field(
        default=None,
        ge=0.0,
        le=1.0,
        description="Voice Activity Detection confidence"
    )
    snr_db: Optional[float] = Field(
        default=None,
        description="Signal-to-noise ratio in decibels"
    )
    capture_latency_ms: Optional[int] = Field(
        default=None,
        description="Time from audio to observation creation"
    )


# =============================================================================
# CONTENT FINGERPRINTS (Privacy-Preserving)
# =============================================================================


class ContentFingerprint(BaseModel):
    """Privacy-preserving fingerprint of sensory content.

    We store fingerprints/hashes instead of raw content to enable:
    - Duplicate detection
    - Provenance tracking
    - Audit without content exposure
    """

    model_config = ConfigDictLike(frozen=True)

    # Primary hash
    content_hash: str = Field(
        ...,
        description="SHA-256 hash of the raw content"
    )
    hash_algorithm: str = Field(
        default="sha256",
        description="Algorithm used for content_hash"
    )

    # Perceptual hashes (for similarity detection)
    perceptual_hash: Optional[str] = Field(
        default=None,
        description="Perceptual hash for near-duplicate detection (pHash, simhash, etc.)"
    )
    perceptual_algorithm: Optional[str] = Field(
        default=None,
        description="Algorithm used for perceptual_hash"
    )

    # Size metadata
    raw_size_bytes: Optional[int] = Field(
        default=None,
        description="Size of raw content before processing"
    )
    processed_size_bytes: Optional[int] = Field(
        default=None,
        description="Size of content after compression/downsampling"
    )


# =============================================================================
# SAMPLING POLICY
# =============================================================================


class SamplingPolicy(BaseModel):
    """Policy governing when and how sensory data is sampled.

    Constitutional constraint: The system must be auditable about
    what triggered any observation and why that policy was in effect.
    """

    model_config = ConfigDictLike(frozen=True)

    policy_id: str = Field(
        ...,
        description="Unique identifier for the active sampling policy"
    )
    policy_version: str = Field(
        default="v1",
        description="Version of the policy definition"
    )

    # Trigger
    trigger_type: str = Field(
        ...,
        description="What triggered this observation: user_request | periodic | event | proactive"
    )
    trigger_event: Optional[str] = Field(
        default=None,
        description="Specific event that triggered observation (e.g., 'user_said_look')"
    )

    # Rate limits
    max_observations_per_minute: Optional[int] = Field(
        default=None,
        description="Rate limit for this modality"
    )
    min_interval_ms: Optional[int] = Field(
        default=None,
        description="Minimum time between observations"
    )

    # Scope
    scope: str = Field(
        default="session",
        description="Policy scope: session | task | persistent"
    )


# =============================================================================
# GUARDIAN CLAUSE 0 - PERCEPTION MEMBRANE
# =============================================================================


class PerceptionMembraneVerdict(BaseModel):
    """Result of Guardian Clause 0 evaluation.

    This is the constitutional gate that ALL sensory observations must pass
    before becoming valid Percepts for downstream processing.
    """

    model_config = ConfigDictLike(frozen=True)

    verdict: MembraneVerdict = Field(
        ...,
        description="Overall membrane decision"
    )

    # Consent validation
    consent_status: ConsentLevel = Field(
        ...,
        description="Consent level on record for this observation"
    )
    consent_sufficient: bool = Field(
        ...,
        description="Whether consent meets requirements for requested fidelity"
    )
    consent_receipt_id: Optional[str] = Field(
        default=None,
        description="Receipt ID of the consent grant (if applicable)"
    )

    # Dignity check
    dignity_score: float = Field(
        ...,
        ge=0.0,
        le=1.0,
        description="Computed dignity score for this observation context"
    )
    dignity_floor_met: bool = Field(
        ...,
        description="Whether dignity score >= 0.15 floor"
    )

    # Data minimization
    minimization_applied: bool = Field(
        ...,
        description="Whether data minimization was applied"
    )
    original_fidelity: str = Field(
        ...,
        description="Requested fidelity level"
    )
    approved_fidelity: str = Field(
        ...,
        description="Approved fidelity level (may be downgraded)"
    )

    # Policy compliance
    sampling_policy_valid: bool = Field(
        ...,
        description="Whether the sampling policy is valid and in effect"
    )
    rate_limit_ok: bool = Field(
        ...,
        description="Whether observation is within rate limits"
    )

    # Rejection reason (if applicable)
    rejection_reason: Optional[str] = Field(
        default=None,
        description="Explanation for rejection or downgrade"
    )
    rejection_code: Optional[str] = Field(
        default=None,
        description="Machine-readable rejection code"
    )

    # Audit
    clause_version: str = Field(
        default="clause_0_v1",
        description="Version of Guardian Clause 0 that made this decision"
    )
    evaluation_ms: Optional[int] = Field(
        default=None,
        description="Time taken to evaluate the membrane"
    )


# =============================================================================
# CORE SENSORY RECEIPTS
# =============================================================================


class VisionObservationReceipt(BaseReceipt):
    """Receipt emitted for every visual observation attempt.

    This receipt captures metadata about visual capture, not the image itself.
    Raw image data should NEVER be stored in receipts - only fingerprints.

    Constitutional Requirements:
    1. Tri-temporal stamps for complete audit trail
    2. Source metadata for provenance
    3. Content fingerprint (not raw content)
    4. Sampling policy reference
    5. Guardian Clause 0 verdict (if evaluated)
    """

    receipt_type: Literal["VisionObservationReceipt"] = Field(
        default="VisionObservationReceipt",
        frozen=True
    )
    domain: str = Field(default="sensory")

    # ===== TRI-TEMPORAL STAMPS =====
    event_time: datetime = Field(
        ...,
        description="When the real-world visual event occurred (camera shutter moment)"
    )
    observed_at: datetime = Field(
        ...,
        description="When the system observed/ingested the visual data"
    )
    # Note: receipt_at is the standard 'ts' field from BaseReceipt

    # ===== MODALITY & FIDELITY =====
    modality: Literal["vision"] = Field(default="vision", frozen=True)
    capture_mode: VisionCaptureMode = Field(
        ...,
        description="Fidelity level of the capture"
    )
    requested_mode: Optional[VisionCaptureMode] = Field(
        default=None,
        description="Originally requested fidelity (if different from capture_mode)"
    )

    # ===== SOURCE METADATA =====
    source: VisionSourceMetadata = Field(
        ...,
        description="Metadata about the capture source"
    )

    # ===== CONTENT FINGERPRINT =====
    fingerprint: ContentFingerprint = Field(
        ...,
        description="Privacy-preserving fingerprint of the captured content"
    )

    # ===== SAMPLING POLICY =====
    sampling_policy: SamplingPolicy = Field(
        ...,
        description="Policy that governed this observation"
    )

    # ===== GUARDIAN CLAUSE 0 (Optional at creation, required before Percept) =====
    membrane_verdict: Optional[PerceptionMembraneVerdict] = Field(
        default=None,
        description="Guardian Clause 0 evaluation result"
    )

    # ===== DIGNITY INDICATORS =====
    consent_level: ConsentLevel = Field(
        ...,
        description="Consent level at observation time"
    )
    data_minimization_policy: DataMinimizationPolicy = Field(
        default=DataMinimizationPolicy.STANDARD,
        description="Data retention policy in effect"
    )
    hidden_capture: bool = Field(
        default=False,
        description="Was this capture initiated without explicit user awareness?"
    )
    user_initiated: bool = Field(
        default=True,
        description="Was this capture directly requested by the user?"
    )

    # ===== PROCESSING METADATA =====
    processing_chain: List[str] = Field(
        default_factory=list,
        description="List of processing steps applied (e.g., ['downscale', 'grayscale'])"
    )
    semantic_extraction: Optional[Dict[str, Any]] = Field(
        default=None,
        description="Semantic content extracted (text, UI elements, etc.)"
    )

    # ===== LINEAGE =====
    percept_id: Optional[str] = Field(
        default=None,
        description="ID of the PerceptReceipt this observation became (if approved)"
    )

    @field_validator("hidden_capture")
    @classmethod
    def _validate_hidden_capture_dignity(cls, v: bool, info) -> bool:
        """Constitutional: Hidden captures require elevated consent."""
        # This is a soft validation - the Membrane will enforce hard rules
        if v:
            # Log warning - real enforcement happens in Membrane
            pass
        return v


class VoiceObservationReceipt(BaseReceipt):
    """Receipt emitted for every voice/audio observation attempt.

    This receipt captures metadata about audio capture, not the audio itself.
    Raw audio should NEVER be stored in receipts - only fingerprints and transcripts.

    Constitutional Requirements:
    1. Tri-temporal stamps for complete audit trail
    2. Source metadata for provenance
    3. Content fingerprint (not raw audio)
    4. Sampling policy reference
    5. Guardian Clause 0 verdict (if evaluated)
    """

    receipt_type: Literal["VoiceObservationReceipt"] = Field(
        default="VoiceObservationReceipt",
        frozen=True
    )
    domain: str = Field(default="sensory")

    # ===== TRI-TEMPORAL STAMPS =====
    event_time: datetime = Field(
        ...,
        description="When the audio was actually produced (speech moment)"
    )
    observed_at: datetime = Field(
        ...,
        description="When the system captured/ingested the audio"
    )
    # Note: receipt_at is the standard 'ts' field from BaseReceipt

    # ===== MODALITY & FIDELITY =====
    modality: Literal["voice"] = Field(default="voice", frozen=True)
    capture_mode: VoiceCaptureMode = Field(
        ...,
        description="Fidelity level of the capture"
    )
    requested_mode: Optional[VoiceCaptureMode] = Field(
        default=None,
        description="Originally requested fidelity (if different from capture_mode)"
    )

    # ===== SOURCE METADATA =====
    source: VoiceSourceMetadata = Field(
        ...,
        description="Metadata about the audio source"
    )

    # ===== CONTENT FINGERPRINT =====
    fingerprint: ContentFingerprint = Field(
        ...,
        description="Privacy-preserving fingerprint of the captured audio"
    )

    # ===== TRANSCRIPT (if applicable) =====
    transcript: Optional[str] = Field(
        default=None,
        description="Speech-to-text transcript (if capture_mode includes transcription)"
    )
    transcript_hash: Optional[str] = Field(
        default=None,
        description="SHA-256 hash of transcript for privacy-preserving storage"
    )
    transcript_confidence: Optional[float] = Field(
        default=None,
        ge=0.0,
        le=1.0,
        description="Confidence score of speech recognition"
    )
    asr_model: Optional[str] = Field(
        default=None,
        description="ASR model used for transcription"
    )

    # ===== SAMPLING POLICY =====
    sampling_policy: SamplingPolicy = Field(
        ...,
        description="Policy that governed this observation"
    )

    # ===== GUARDIAN CLAUSE 0 (Optional at creation, required before Percept) =====
    membrane_verdict: Optional[PerceptionMembraneVerdict] = Field(
        default=None,
        description="Guardian Clause 0 evaluation result"
    )

    # ===== DIGNITY INDICATORS =====
    consent_level: ConsentLevel = Field(
        ...,
        description="Consent level at observation time"
    )
    data_minimization_policy: DataMinimizationPolicy = Field(
        default=DataMinimizationPolicy.AGGRESSIVE,  # Voice is more sensitive
        description="Data retention policy in effect"
    )
    hidden_capture: bool = Field(
        default=False,
        description="Was this capture initiated without explicit user awareness?"
    )
    user_initiated: bool = Field(
        default=True,
        description="Was this capture directly requested by the user?"
    )

    # ===== SPEAKER METADATA (privacy-preserving) =====
    speaker_id_hash: Optional[str] = Field(
        default=None,
        description="Hashed speaker identifier (for multi-speaker scenarios)"
    )
    is_user_voice: Optional[bool] = Field(
        default=None,
        description="Whether this is the primary user's voice"
    )

    # ===== PROCESSING METADATA =====
    processing_chain: List[str] = Field(
        default_factory=list,
        description="List of processing steps applied (e.g., ['vad', 'denoise', 'asr'])"
    )

    # ===== LINEAGE =====
    percept_id: Optional[str] = Field(
        default=None,
        description="ID of the PerceptReceipt this observation became (if approved)"
    )


class PerceptReceipt(BaseReceipt):
    """Receipt for a Membrane-approved observation ready for downstream use.

    A Percept is the constitutional unit of perception - an observation that
    has passed Guardian Clause 0 and may now participate in decision-making.

    The PerceptReceipt creates the provenance link:
      Observation → Percept → Decision → Action → Memory
    """

    receipt_type: Literal["PerceptReceipt"] = Field(
        default="PerceptReceipt",
        frozen=True
    )
    domain: str = Field(default="sensory")

    # ===== OBSERVATION LINKAGE =====
    observation_receipt_id: str = Field(
        ...,
        description="Receipt ID of the source VisionObservation or VoiceObservation"
    )
    observation_type: str = Field(
        ...,
        description="Type of source observation (VisionObservationReceipt, VoiceObservationReceipt)"
    )
    observation_hash: str = Field(
        ...,
        description="Hash of the observation receipt for integrity verification"
    )

    # ===== MEMBRANE VERDICT (required - Percepts only exist after approval) =====
    membrane_verdict: PerceptionMembraneVerdict = Field(
        ...,
        description="Guardian Clause 0 evaluation that approved this Percept"
    )
    membrane_receipt_id: str = Field(
        ...,
        description="Receipt ID of the PerceptionMembraneReceipt"
    )

    # ===== MODALITY =====
    modality: SensoryModality = Field(
        ...,
        description="Sensory modality of this percept"
    )
    approved_fidelity: str = Field(
        ...,
        description="Fidelity level approved by the Membrane"
    )

    # ===== SEMANTIC CONTENT (what the system 'perceived') =====
    semantic_summary: Optional[str] = Field(
        default=None,
        description="Human-readable summary of what was perceived"
    )
    semantic_vector_id: Optional[str] = Field(
        default=None,
        description="Reference to embedding vector in MemoryGraph"
    )
    entities_detected: List[str] = Field(
        default_factory=list,
        description="Named entities or objects detected"
    )
    intent_signals: Dict[str, float] = Field(
        default_factory=dict,
        description="Intent signals extracted (e.g., {'question': 0.8, 'command': 0.2})"
    )

    # ===== DOWNSTREAM LINEAGE =====
    decision_receipt_ids: List[str] = Field(
        default_factory=list,
        description="Receipt IDs of decisions that used this Percept"
    )
    action_receipt_ids: List[str] = Field(
        default_factory=list,
        description="Receipt IDs of actions that resulted from this Percept"
    )
    memory_node_id: Optional[str] = Field(
        default=None,
        description="MemoryGraph node ID where this Percept is stored"
    )

    # ===== DIGNITY TRACKING =====
    dignity_score_at_approval: float = Field(
        ...,
        ge=0.0,
        le=1.0,
        description="Dignity score when this Percept was approved"
    )
    consent_level_at_approval: ConsentLevel = Field(
        ...,
        description="Consent level when this Percept was approved"
    )


class PerceptionMembraneReceipt(BaseReceipt):
    """Receipt documenting a Guardian Clause 0 (Perception Membrane) evaluation.

    This receipt provides the audit trail for the constitutional gate that
    governs all sensory perception in the system.

    Every membrane evaluation is logged, whether approved, rejected, or deferred.
    """

    receipt_type: Literal["PerceptionMembraneReceipt"] = Field(
        default="PerceptionMembraneReceipt",
        frozen=True
    )
    domain: str = Field(default="governance")

    # ===== OBSERVATION BEING EVALUATED =====
    observation_receipt_id: str = Field(
        ...,
        description="Receipt ID of the observation being evaluated"
    )
    observation_type: str = Field(
        ...,
        description="Type of observation (VisionObservationReceipt, VoiceObservationReceipt)"
    )
    modality: SensoryModality = Field(
        ...,
        description="Sensory modality being evaluated"
    )

    # ===== VERDICT =====
    verdict: PerceptionMembraneVerdict = Field(
        ...,
        description="Complete membrane evaluation result"
    )

    # ===== EVALUATION CONTEXT =====
    profile: str = Field(
        ...,
        description="Trust profile used for evaluation"
    )
    risk_tier: str = Field(
        default="standard",
        description="Risk tier applied to this evaluation"
    )

    # ===== CONSENT EVALUATION =====
    consent_receipt_id: Optional[str] = Field(
        default=None,
        description="Receipt ID of consent grant being evaluated"
    )
    consent_scope: Optional[str] = Field(
        default=None,
        description="Scope of the consent (e.g., 'vision:screen', 'voice:microphone')"
    )
    consent_expires_at: Optional[datetime] = Field(
        default=None,
        description="When the consent expires"
    )

    # ===== DIGNITY EVALUATION =====
    dignity_factors: Dict[str, float] = Field(
        default_factory=dict,
        description="Factors that contributed to dignity score"
    )

    # ===== POLICY EVALUATION =====
    sampling_policy_id: str = Field(
        ...,
        description="ID of the sampling policy evaluated"
    )
    rate_limit_current: Optional[int] = Field(
        default=None,
        description="Current observation count in rate limit window"
    )
    rate_limit_max: Optional[int] = Field(
        default=None,
        description="Maximum observations allowed in rate limit window"
    )

    # ===== DOWNSTREAM =====
    percept_receipt_id: Optional[str] = Field(
        default=None,
        description="Receipt ID of resulting Percept (if approved)"
    )


# =============================================================================
# HELPER FUNCTIONS
# =============================================================================


def compute_content_hash(content: bytes, algorithm: str = "sha256") -> str:
    """Compute hash of raw content for fingerprinting."""
    if algorithm == "sha256":
        return hashlib.sha256(content).hexdigest()
    elif algorithm == "blake3":
        try:
            import blake3
            return blake3.blake3(content).hexdigest()
        except ImportError:
            return hashlib.sha256(content).hexdigest()
    else:
        raise ValueError(f"Unknown hash algorithm: {algorithm}")


def create_vision_observation(
    event_time: datetime,
    capture_mode: VisionCaptureMode,
    source: VisionSourceMetadata,
    content_hash: str,
    sampling_policy: SamplingPolicy,
    consent_level: ConsentLevel,
    *,
    user_initiated: bool = True,
    raw_size_bytes: Optional[int] = None,
) -> VisionObservationReceipt:
    """Factory function to create a VisionObservationReceipt.

    Args:
        event_time: When the visual event occurred
        capture_mode: Fidelity level of capture
        source: Source metadata
        content_hash: SHA-256 hash of raw content
        sampling_policy: Policy governing this observation
        consent_level: Consent level on record
        user_initiated: Whether user explicitly requested this capture
        raw_size_bytes: Size of raw content (optional)

    Returns:
        VisionObservationReceipt ready for Membrane evaluation
    """
    now = datetime.now(timezone.utc)

    fingerprint = ContentFingerprint(
        content_hash=content_hash,
        hash_algorithm="sha256",
        raw_size_bytes=raw_size_bytes,
    )

    return VisionObservationReceipt(
        receipt_id=f"vobs_{uuid.uuid4().hex}",
        event_time=event_time,
        observed_at=now,
        capture_mode=capture_mode,
        source=source,
        fingerprint=fingerprint,
        sampling_policy=sampling_policy,
        consent_level=consent_level,
        user_initiated=user_initiated,
        hidden_capture=not user_initiated,
    )


def create_voice_observation(
    event_time: datetime,
    capture_mode: VoiceCaptureMode,
    source: VoiceSourceMetadata,
    content_hash: str,
    sampling_policy: SamplingPolicy,
    consent_level: ConsentLevel,
    *,
    user_initiated: bool = True,
    transcript: Optional[str] = None,
    raw_size_bytes: Optional[int] = None,
) -> VoiceObservationReceipt:
    """Factory function to create a VoiceObservationReceipt.

    Args:
        event_time: When the audio was produced
        capture_mode: Fidelity level of capture
        source: Source metadata
        content_hash: SHA-256 hash of raw audio
        sampling_policy: Policy governing this observation
        consent_level: Consent level on record
        user_initiated: Whether user explicitly requested this capture
        transcript: Speech-to-text transcript (if available)
        raw_size_bytes: Size of raw audio (optional)

    Returns:
        VoiceObservationReceipt ready for Membrane evaluation
    """
    now = datetime.now(timezone.utc)

    fingerprint = ContentFingerprint(
        content_hash=content_hash,
        hash_algorithm="sha256",
        raw_size_bytes=raw_size_bytes,
    )

    transcript_hash = None
    if transcript:
        transcript_hash = hashlib.sha256(transcript.encode()).hexdigest()

    return VoiceObservationReceipt(
        receipt_id=f"vbsv_{uuid.uuid4().hex}",
        event_time=event_time,
        observed_at=now,
        capture_mode=capture_mode,
        source=source,
        fingerprint=fingerprint,
        sampling_policy=sampling_policy,
        consent_level=consent_level,
        user_initiated=user_initiated,
        hidden_capture=not user_initiated,
        transcript=transcript,
        transcript_hash=transcript_hash,
    )


# =============================================================================
# EXPORTS
# =============================================================================

__all__ = [
    # Enums
    "SensoryModality",
    "VisionCaptureMode",
    "VoiceCaptureMode",
    "ConsentLevel",
    "MembraneVerdict",
    "DataMinimizationPolicy",
    # Metadata models
    "VisionSourceMetadata",
    "VoiceSourceMetadata",
    "ContentFingerprint",
    "SamplingPolicy",
    # Membrane verdict
    "PerceptionMembraneVerdict",
    # Core receipts
    "VisionObservationReceipt",
    "VoiceObservationReceipt",
    "PerceptReceipt",
    "PerceptionMembraneReceipt",
    # Helpers
    "compute_content_hash",
    "create_vision_observation",
    "create_voice_observation",
]
