"""
Collaboration WebSocket message schemas.

Provides typed messages for real-time collaborative editing including:
- Operational transformation operations
- Cursor position sharing
- Selection highlighting
- Document locking

Usage:
    from lightwave.schema.pydantic.contracts.websockets.collaboration import (
        CollabOperationMessage,
        CollabCursorMessage,
        CollabLockRequestInbound,
    )

    # Broadcast operation to collaborators
    op = CollabOperationMessage(
        document_id="doc-123",
        operation_id="op-456",
        operations=[{"type": "insert", "pos": 10, "text": "Hello"}],
        base_version=5,
        user_id="user-789",
    )
    await broadcast_to_document(op.to_ws_json())
"""

from datetime import datetime
from typing import Any, Literal

from pydantic import Field

from lightwave.schema.pydantic.contracts.websockets.base import WSMessage

# =============================================================================
# Position and Selection Schemas
# =============================================================================


class CursorPosition(WSMessage):
    """Cursor position in a document."""

    line: int = Field(..., ge=0, description="Line number (0-indexed)")
    column: int = Field(..., ge=0, description="Column number (0-indexed)")
    offset: int | None = Field(None, ge=0, description="Absolute offset if available")


class TextSelection(WSMessage):
    """Text selection range."""

    start: CursorPosition = Field(..., description="Selection start")
    end: CursorPosition = Field(..., description="Selection end")
    is_reversed: bool = Field(False, description="Whether selection is reversed")


# =============================================================================
# Operation Types
# =============================================================================


class OTOperation(WSMessage):
    """Single operational transformation operation."""

    op_type: Literal["insert", "delete", "retain"] = Field(
        ...,
        alias="type",
        description="Operation type",
    )
    # For insert
    text: str | None = Field(None, description="Text to insert")
    # For delete/retain
    count: int | None = Field(None, ge=1, description="Characters to delete/retain")
    # Position
    position: int = Field(..., ge=0, description="Position in document")
    # Attributes (for rich text)
    attributes: dict[str, Any] | None = Field(None, description="Format attributes")


# =============================================================================
# Bidirectional Messages
# =============================================================================


class CollabOperationMessage(WSMessage):
    """
    Operational transformation operation (bidirectional).

    Sent by client when making edits, broadcast by server to other clients.
    """

    type: Literal["collab.operation"] = "collab.operation"
    document_id: str = Field(..., description="Document being edited")
    operation_id: str = Field(..., description="Unique operation ID")
    operations: list[OTOperation] = Field(
        ...,
        min_length=1,
        description="OT operations",
    )
    base_version: int = Field(..., ge=0, description="Version this is based on")
    user_id: str = Field(..., description="User who made the edit")
    user_name: str | None = Field(None, description="User display name")
    server_version: int | None = Field(
        None,
        description="Server-assigned version (outbound only)",
    )


class CollabCursorMessage(WSMessage):
    """
    Cursor position update (bidirectional).

    Sent when user moves cursor, broadcast to show collaborator cursors.
    """

    type: Literal["collab.cursor"] = "collab.cursor"
    document_id: str = Field(..., description="Document ID")
    user_id: str = Field(..., description="User ID")
    user_name: str | None = Field(None, description="Display name")
    position: CursorPosition = Field(..., description="Cursor position")
    color: str | None = Field(None, description="Cursor color (hex)")


class CollabSelectionMessage(WSMessage):
    """
    Selection update (bidirectional).

    Sent when user selects text, broadcast to show collaborator selections.
    """

    type: Literal["collab.selection"] = "collab.selection"
    document_id: str = Field(..., description="Document ID")
    user_id: str = Field(..., description="User ID")
    user_name: str | None = Field(None, description="Display name")
    selection: TextSelection | None = Field(
        None,
        description="Selection range (null = no selection)",
    )
    color: str | None = Field(None, description="Highlight color (hex)")


# =============================================================================
# Inbound Messages (Client -> Server)
# =============================================================================


class CollabLockRequestInbound(WSMessage):
    """
    Request to lock a section of document.

    Used for exclusive editing of complex elements (tables, images, etc.).
    """

    type: Literal["collab.lock_request"] = "collab.lock_request"
    document_id: str = Field(..., description="Document ID")
    lock_key: str = Field(..., description="Identifier for what to lock")
    lock_type: Literal["element", "range", "document"] = Field(
        "element",
        description="Type of lock",
    )
    range: TextSelection | None = Field(
        None,
        description="Range to lock (for range locks)",
    )
    timeout_seconds: int = Field(
        60,
        ge=10,
        le=600,
        description="Lock timeout",
    )


class CollabUnlockInbound(WSMessage):
    """
    Release a lock.

    Client sends this when done with exclusive editing.
    """

    type: Literal["collab.unlock"] = "collab.unlock"
    document_id: str = Field(..., description="Document ID")
    lock_key: str = Field(..., description="Lock to release")


# =============================================================================
# Outbound Messages (Server -> Client)
# =============================================================================


class CollabLockGrantedOutbound(WSMessage):
    """
    Lock was granted.

    Sent to client who requested lock.
    """

    type: Literal["collab.lock_granted"] = "collab.lock_granted"
    document_id: str = Field(..., description="Document ID")
    lock_key: str = Field(..., description="Lock identifier")
    expires_at: datetime = Field(..., description="When lock expires")
    user_id: str = Field(..., description="User who holds lock")


class CollabLockDeniedOutbound(WSMessage):
    """
    Lock was denied.

    Sent when another user already holds the lock.
    """

    type: Literal["collab.lock_denied"] = "collab.lock_denied"
    document_id: str = Field(..., description="Document ID")
    lock_key: str = Field(..., description="Lock identifier")
    held_by: str = Field(..., description="User who holds the lock")
    held_by_name: str | None = Field(None, description="Lock holder's name")
    retry_after_ms: int = Field(
        5000,
        description="Suggested retry delay",
    )


class CollabConflictOutbound(WSMessage):
    """
    Operation conflict detected.

    Sent when operation cannot be cleanly merged.
    """

    type: Literal["collab.conflict"] = "collab.conflict"
    document_id: str = Field(..., description="Document ID")
    operation_id: str = Field(..., description="Conflicting operation ID")
    reason: str = Field(..., description="Conflict reason")
    server_version: int = Field(..., description="Current server version")
    requires_resync: bool = Field(
        False,
        description="Whether full resync is needed",
    )
