"""
Streaming Response API for MCAL

Provides streaming support for the add() operation, yielding partial results
as they become available. This improves perceived latency by showing progress
immediately rather than waiting for complete extraction.

Issue #10: Streaming Response API

Key benefits:
- User sees progress immediately (not 22s blank screen)
- Partial results usable before completion
- Better UX for demo and production use

Usage:
    async for event in mcal.add_stream(messages, user_id):
        if event.type == StreamEventType.INTENT_EXTRACTED:
            print(f"Found intent: {event.data.content}")
        elif event.type == StreamEventType.DECISION_EXTRACTED:
            print(f"Found decision: {event.data.decision}")
        elif event.type == StreamEventType.COMPLETE:
            print(f"Total time: {event.data.timing.total_ms}ms")
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any, Optional, Union

from .models import IntentNode, IntentGraph, DecisionTrail


class StreamEventType(str, Enum):
    """Types of events emitted during streaming extraction."""
    
    # Progress events
    STARTED = "started"                    # Extraction started
    PHASE_STARTED = "phase_started"        # New phase (facts/intents/decisions)
    PHASE_COMPLETE = "phase_complete"      # Phase finished
    
    # Partial result events
    FACT_EXTRACTED = "fact_extracted"      # Single fact from Mem0
    INTENT_EXTRACTED = "intent_extracted"  # Single intent node extracted
    INTENT_UPDATED = "intent_updated"      # Existing intent updated
    DECISION_EXTRACTED = "decision_extracted"  # Single decision extracted
    
    # Status events
    CACHE_HIT = "cache_hit"                # Results from cache
    ERROR = "error"                        # Non-fatal error
    
    # Completion events
    COMPLETE = "complete"                  # All extraction finished


class ExtractionPhase(str, Enum):
    """Phases of the extraction process."""
    FACTS = "facts"
    INTENTS = "intents"
    DECISIONS = "decisions"


@dataclass
class StreamProgress:
    """Progress information for streaming updates."""
    phase: ExtractionPhase
    current: int = 0
    total: Optional[int] = None
    elapsed_ms: int = 0
    message: str = ""


@dataclass
class CacheHitInfo:
    """Information about a cache hit."""
    hit_type: str  # "full" or "partial"
    messages_cached: int
    messages_to_process: int
    saved_time_ms: int


@dataclass
class PhaseResult:
    """Result from a completed phase."""
    phase: ExtractionPhase
    items_count: int
    duration_ms: int


@dataclass
class StreamEvent:
    """
    A single event in the streaming extraction process.
    
    Events are yielded as extraction progresses, allowing clients to:
    - Show real-time progress
    - Display partial results immediately
    - Track timing per phase
    
    Attributes:
        type: The type of event
        timestamp: When the event occurred
        data: Event-specific payload (varies by type)
        progress: Optional progress information
    """
    type: StreamEventType
    timestamp: datetime = field(default_factory=datetime.now)
    data: Optional[Union[
        IntentNode, 
        DecisionTrail, 
        dict,  # For facts (MemoryEntry dict)
        PhaseResult,
        CacheHitInfo,
        Any,  # For COMPLETE event (AddResult from mcal module)
        str,  # For errors
    ]] = None
    progress: Optional[StreamProgress] = None
    
    def to_dict(self) -> dict:
        """Convert to dictionary for JSON serialization."""
        result = {
            "type": self.type.value,
            "timestamp": self.timestamp.isoformat(),
        }
        
        if self.data is not None:
            if hasattr(self.data, "model_dump"):
                result["data"] = self.data.model_dump()
            elif hasattr(self.data, "__dict__"):
                result["data"] = self.data.__dict__
            elif isinstance(self.data, dict):
                result["data"] = self.data
            else:
                result["data"] = str(self.data)
        
        if self.progress is not None:
            result["progress"] = {
                "phase": self.progress.phase.value,
                "current": self.progress.current,
                "total": self.progress.total,
                "elapsed_ms": self.progress.elapsed_ms,
                "message": self.progress.message,
            }
        
        return result


# =============================================================================
# Stream Builder Helpers
# =============================================================================

def event_started() -> StreamEvent:
    """Create a STARTED event."""
    return StreamEvent(type=StreamEventType.STARTED)


def event_phase_started(phase: ExtractionPhase, message: str = "") -> StreamEvent:
    """Create a PHASE_STARTED event."""
    return StreamEvent(
        type=StreamEventType.PHASE_STARTED,
        progress=StreamProgress(phase=phase, message=message)
    )


def event_phase_complete(
    phase: ExtractionPhase, 
    items_count: int, 
    duration_ms: int
) -> StreamEvent:
    """Create a PHASE_COMPLETE event."""
    return StreamEvent(
        type=StreamEventType.PHASE_COMPLETE,
        data=PhaseResult(phase=phase, items_count=items_count, duration_ms=duration_ms)
    )


def event_fact_extracted(fact: dict, progress: Optional[StreamProgress] = None) -> StreamEvent:
    """Create a FACT_EXTRACTED event."""
    return StreamEvent(
        type=StreamEventType.FACT_EXTRACTED,
        data=fact,
        progress=progress
    )


def event_intent_extracted(
    intent: IntentNode, 
    progress: Optional[StreamProgress] = None
) -> StreamEvent:
    """Create an INTENT_EXTRACTED event."""
    return StreamEvent(
        type=StreamEventType.INTENT_EXTRACTED,
        data=intent,
        progress=progress
    )


def event_intent_updated(
    intent: IntentNode,
    progress: Optional[StreamProgress] = None
) -> StreamEvent:
    """Create an INTENT_UPDATED event."""
    return StreamEvent(
        type=StreamEventType.INTENT_UPDATED,
        data=intent,
        progress=progress
    )


def event_decision_extracted(
    decision: DecisionTrail,
    progress: Optional[StreamProgress] = None
) -> StreamEvent:
    """Create a DECISION_EXTRACTED event."""
    return StreamEvent(
        type=StreamEventType.DECISION_EXTRACTED,
        data=decision,
        progress=progress
    )


def event_cache_hit(
    hit_type: str,
    messages_cached: int,
    messages_to_process: int,
    saved_time_ms: int
) -> StreamEvent:
    """Create a CACHE_HIT event."""
    return StreamEvent(
        type=StreamEventType.CACHE_HIT,
        data=CacheHitInfo(
            hit_type=hit_type,
            messages_cached=messages_cached,
            messages_to_process=messages_to_process,
            saved_time_ms=saved_time_ms
        )
    )


def event_error(message: str) -> StreamEvent:
    """Create an ERROR event."""
    return StreamEvent(
        type=StreamEventType.ERROR,
        data=message
    )


def event_complete(result: Any) -> StreamEvent:
    """Create a COMPLETE event with final results."""
    return StreamEvent(
        type=StreamEventType.COMPLETE,
        data=result
    )
