"""Pydantic models for CRDT document parsing results.

This module defines data models for parsed CRDT documents containing
ProseMirror schema structures for various social media platforms.
"""

from __future__ import annotations

from collections.abc import Mapping
from datetime import UTC, datetime
from enum import StrEnum
from types import MappingProxyType
from typing import Any

from pydantic import BaseModel, ConfigDict, Field


class Platform(StrEnum):
    """Supported social media platforms."""

    TWITTER = "twitter"
    LINKEDIN = "linkedin"
    INSTAGRAM = "instagram"
    TIKTOK = "tiktok"
    BLUESKY = "bluesky"
    THREADS = "threads"


class PlatformShape(StrEnum):
    """Shape taxonomy for platform content structure."""

    THREAD = "thread"
    SINGLE = "single"


PLATFORM_SHAPES: Mapping[Platform, PlatformShape] = MappingProxyType(
    {
        Platform.TWITTER: PlatformShape.THREAD,
        Platform.BLUESKY: PlatformShape.THREAD,
        Platform.THREADS: PlatformShape.THREAD,
        Platform.LINKEDIN: PlatformShape.SINGLE,
        Platform.INSTAGRAM: PlatformShape.SINGLE,
        Platform.TIKTOK: PlatformShape.SINGLE,
    }
)


class ContentType(StrEnum):
    """Content types for posts."""

    POST = "POST"
    FEED = "FEED"
    REEL = "REEL"
    STORY = "STORY"
    CAROUSEL = "CAROUSEL"


class PostVisibility(StrEnum):
    """LinkedIn post visibility settings."""

    PUBLIC = "PUBLIC"
    CONNECTIONS = "CONNECTIONS"
    LOGGED_IN = "LOGGED_IN"


# ==================== Platform Content Models ====================


class AutoNumberConfig(BaseModel):
    """Configuration for thread auto-numbering.

    Attributes:
        enabled: Whether auto-numbering is active
        position: Where to insert number in tweet ("start" or "end")
        format_style: Format template (e.g., "#1")
        skip_first: Cards to skip from start
        skip_last: Cards to skip from end
    """

    enabled: bool = False
    position: str = "start"
    format_style: str = "#1"
    skip_first: int = 1
    skip_last: int = 1

    model_config = ConfigDict(frozen=True)


class TwitterCardContent(BaseModel):
    """Represents content of a single Twitter card (tweet).

    Attributes:
        id: Unique identifier for the card within the document
        text: Text content of the tweet
        media: List of media URLs (space-separated URLs split into list)
        is_quoted: Whether this card quotes another tweet
        quoted_card_id: ID of the quoted card within the document
        quoted_tweet_id: Extracted tweet ID from og URL when quoteOg is true
    """

    id: str | None = None
    text: str = ""
    media: list[str] = Field(default_factory=list)
    is_quoted: bool = False
    quoted_card_id: str | None = None
    quoted_tweet_id: str | None = None

    model_config = ConfigDict(frozen=True)


class LinkedInPostContent(BaseModel):
    """Represents content of a LinkedIn post.

    Attributes:
        id: Post node ID from schema
        text: Text content of the post
        media: List of media URLs
        pdf: PDF data string
        visibility: Post visibility setting
        og: Open Graph link preview URL
        pdf_images: PDF image data string
    """

    id: str | None = None
    text: str = ""
    media: list[str] = Field(default_factory=list)
    pdf: str | None = None
    visibility: PostVisibility = PostVisibility.PUBLIC
    og: str | None = None
    pdf_images: str | None = None

    model_config = ConfigDict(frozen=True)


class LinkedInCommentContent(BaseModel):
    """Represents content of a LinkedIn first comment.

    Attributes:
        id: Comment node ID from schema
        text: Text content of the comment
        media: List of media URLs attached to the comment
        hidden: Whether the comment is hidden
    """

    id: str | None = None
    text: str = ""
    media: list[str] = Field(default_factory=list)
    hidden: bool = True

    model_config = ConfigDict(frozen=True)


class InstagramPostContent(BaseModel):
    """Represents content of an Instagram post.

    Attributes:
        id: Post node ID from schema
        caption: Caption text of the post
        media: List of media URLs
        content_type: Type of Instagram content
        location: Location ID string
        music: Music string
    """

    id: str | None = None
    caption: str = ""
    media: list[str] = Field(default_factory=list)
    content_type: ContentType = ContentType.POST
    location: str | None = None
    music: str | None = None
    share_to_feed: bool = True
    location_name: str | None = None
    cover_url: str | None = None

    model_config = ConfigDict(frozen=True)


class InstagramCommentContent(BaseModel):
    """Represents content of an Instagram first comment.

    Attributes:
        id: Comment node ID from schema
        text: Text content of the comment
    """

    id: str | None = None
    text: str = ""

    model_config = ConfigDict(frozen=True)


class TikTokPostContent(BaseModel):
    """Represents content of a TikTok post.

    Attributes:
        id: Post node ID from schema
        title: Title/description of the video
        media: List of media URLs
        location: Location string
        music: Music string
    """

    id: str | None = None
    title: str = ""
    media: list[str] = Field(default_factory=list)
    location: str | None = None
    music: str | None = None

    model_config = ConfigDict(frozen=True)


class BlueSkyCardContent(BaseModel):
    """Represents content of a single Bluesky card (post in thread).

    Attributes:
        id: Unique identifier for the card within the document
        text: Text content of the post
        media: List of media URLs (space-separated URLs split into list)
        og: Link card embed URL
    """

    id: str | None = None
    text: str = ""
    media: list[str] = Field(default_factory=list)
    og: str | None = None

    model_config = ConfigDict(frozen=True)


class ThreadsCardContent(BaseModel):
    """Represents content of a single Threads card (post in thread).

    Attributes:
        id: Unique identifier for the card within the document
        text: Text content of the post
        media: List of media URLs (space-separated URLs split into list)
        og: Link card embed URL
    """

    id: str | None = None
    text: str = ""
    media: list[str] = Field(default_factory=list)
    og: str | None = None

    model_config = ConfigDict(frozen=True)


# ==================== Document Result Models ====================


class ParsedTwitterDocument(BaseModel):
    """Result of parsing a Twitter CRDT document.

    Attributes:
        cards: List of parsed Twitter cards (tweets in thread)
        auto_number_config: Thread-level auto-numbering config from twitterthread node
        raw_attributes: Original document attributes
        parsed_at: Timestamp when document was parsed
    """

    cards: list[TwitterCardContent] = Field(default_factory=list)
    auto_number_config: AutoNumberConfig | None = None
    raw_attributes: dict[str, Any] = Field(default_factory=dict)
    parsed_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

    model_config = ConfigDict(frozen=True)


class ParsedLinkedInDocument(BaseModel):
    """Result of parsing a LinkedIn CRDT document.

    Attributes:
        post: Parsed LinkedIn post content
        comment: Optional first comment content
        raw_attributes: Original document attributes
        parsed_at: Timestamp when document was parsed
    """

    post: LinkedInPostContent
    comment: LinkedInCommentContent | None = None
    raw_attributes: dict[str, Any] = Field(default_factory=dict)
    parsed_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

    model_config = ConfigDict(frozen=True)


class ParsedInstagramDocument(BaseModel):
    """Result of parsing an Instagram CRDT document.

    Attributes:
        post: Parsed Instagram post content
        raw_attributes: Original document attributes
        parsed_at: Timestamp when document was parsed
    """

    post: InstagramPostContent
    comment: InstagramCommentContent | None = None
    raw_attributes: dict[str, Any] = Field(default_factory=dict)
    parsed_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

    model_config = ConfigDict(frozen=True)


class ParsedTikTokDocument(BaseModel):
    """Result of parsing a TikTok CRDT document.

    Attributes:
        post: Parsed TikTok post content
        raw_attributes: Original document attributes
        parsed_at: Timestamp when document was parsed
    """

    post: TikTokPostContent
    raw_attributes: dict[str, Any] = Field(default_factory=dict)
    parsed_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

    model_config = ConfigDict(frozen=True)


class ParsedBlueSkyDocument(BaseModel):
    """Result of parsing a Bluesky CRDT document.

    Attributes:
        cards: List of parsed Bluesky cards (posts in thread)
        auto_number_config: Thread-level auto-numbering config from blueskythread node
        raw_attributes: Original document attributes
        parsed_at: Timestamp when document was parsed
    """

    cards: list[BlueSkyCardContent] = Field(default_factory=list)
    auto_number_config: AutoNumberConfig | None = None
    raw_attributes: dict[str, Any] = Field(default_factory=dict)
    parsed_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

    model_config = ConfigDict(frozen=True)


class ParsedThreadsDocument(BaseModel):
    """Result of parsing a Threads CRDT document.

    Attributes:
        cards: List of parsed Threads cards (posts in thread)
        auto_number_config: Thread-level auto-numbering config from threadContainer node
        raw_attributes: Original document attributes
        parsed_at: Timestamp when document was parsed
    """

    cards: list[ThreadsCardContent] = Field(default_factory=list)
    auto_number_config: AutoNumberConfig | None = None
    raw_attributes: dict[str, Any] = Field(default_factory=dict)
    parsed_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

    model_config = ConfigDict(frozen=True)


# Type alias for any parsed document result
type ParsedDocument = (
    ParsedTwitterDocument
    | ParsedLinkedInDocument
    | ParsedInstagramDocument
    | ParsedTikTokDocument
    | ParsedBlueSkyDocument
    | ParsedThreadsDocument
)


# ==================== Conversion Models ====================


class ConversionWarning(BaseModel):
    """A warning generated during platform conversion."""

    code: str
    field: str
    message: str

    model_config = ConfigDict(frozen=True)


class OriginalCard(BaseModel):
    """Preserved data from a single card in thread→single merging."""

    id: str | None = None
    text: str = ""
    media: list[str] = Field(default_factory=list)

    model_config = ConfigDict(frozen=True)


class OriginalCardStructure(BaseModel):
    """Preserved card boundaries from thread→single merging."""

    cards: list[OriginalCard] = Field(default_factory=list)
    auto_number_config: AutoNumberConfig | None = None

    model_config = ConfigDict(frozen=True)


class ConversionExtras(BaseModel):
    """Unmappable fields carried from source document."""

    source_platform: Platform
    source_fields: dict[str, Any] = Field(default_factory=dict)
    original_cards: OriginalCardStructure | None = None

    model_config = ConfigDict(frozen=True)


class ConversionResult(BaseModel):
    """Result of converting a parsed document to another platform."""

    document: ParsedDocument
    warnings: list[ConversionWarning] = Field(default_factory=list)
    extras: ConversionExtras
    source_platform: Platform
    target_platform: Platform
    converted_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

    model_config = ConfigDict(frozen=True)
