"""
cortex/commitments/models.py — Data models for commitments and open loops.

Commitments are explicit obligations or promises tracked across sessions.
Open loops are unresolved threads / follow-ups that are carried forward
through sleep/wake cycles until resolved or auto-expired.

Both types are first-class objects: they have their own CRUD API, HTTP
endpoints, and participate in the boot-injection path so the agent is
aware of outstanding obligations on wake.

DB mapping notes (v8 commitment / open_loop tables):
  Commitment.content       ↔  commitment.title
  Commitment.closed_at     ↔  commitment.completed_at
  Commitment.source_turn_id↔  commitment.source_turn_id  (added in v9)
  OpenLoop.content         ↔  open_loop.title
  OpenLoop.context         ↔  open_loop.description
  OpenLoop.carry_count     ↔  open_loop.carry_count      (added in v9)
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Optional


@dataclass
class Commitment:
    """An explicit obligation or promise tracked across sessions.

    Attributes:
        id: Hex UUID primary key (DB-generated).
        owner_id: Multi-tenancy scope key.
        content: What was committed to (natural-language description).
        due_at: Optional ISO 8601 deadline timestamp.
        status: Lifecycle state — "open" | "completed" | "cancelled".
        source_turn_id: Optional reference to the conversation turn that
            created this commitment (episode_event.id or similar).
        created_at: ISO 8601 UTC creation timestamp.
        closed_at: ISO 8601 UTC timestamp when the commitment was closed
            (completed or cancelled). None while still open.
    """

    id: str
    owner_id: str
    content: str
    due_at: Optional[str]
    status: str  # "open" | "completed" | "cancelled"
    source_turn_id: Optional[str]
    created_at: str
    closed_at: Optional[str]

    # Valid status values
    OPEN = "open"
    COMPLETED = "completed"
    CANCELLED = "cancelled"
    VALID_STATUSES = frozenset({OPEN, COMPLETED, CANCELLED})


@dataclass
class OpenLoop:
    """An unresolved thread or follow-up carried forward through sleep/wake cycles.

    Open loops are created when the agent detects a pending item that needs
    future attention. Each time the agent sleeps and wakes, carry_count is
    incremented. Loops that survive more than 5 cycles are auto-resolved
    as stale.

    Attributes:
        id: Hex UUID primary key (DB-generated).
        owner_id: Multi-tenancy scope key.
        content: What the open loop is about.
        context: Optional description of what triggered the loop.
        status: Lifecycle state — "open" | "resolved".
        carry_count: Number of sleep/wake cycles this loop has survived.
            Auto-resolved when carry_count > 5.
        created_at: ISO 8601 UTC creation timestamp.
        resolved_at: ISO 8601 UTC timestamp when resolved. None if still open.
    """

    id: str
    owner_id: str
    content: str
    context: Optional[str]
    status: str  # "open" | "resolved"
    carry_count: int
    created_at: str
    resolved_at: Optional[str]

    # Valid status values
    OPEN = "open"
    RESOLVED = "resolved"
    VALID_STATUSES = frozenset({OPEN, RESOLVED})

    # Loops surviving more than this many cycles are auto-resolved as stale
    STALE_THRESHOLD = 5
