"""Context domain model for CleverAgents.

Based on Phase 0 discovery and ADR-004 (Pydantic Validation).
"""

from __future__ import annotations

from datetime import datetime
from enum import StrEnum
from pathlib import Path

from pydantic import BaseModel, ConfigDict, Field, field_validator


def _empty_context_list() -> list[Context]:
    return []


def _empty_token_diffs() -> dict[str, dict[str, int]]:
    return {}


class ContextType(StrEnum):
    """Type of context item."""

    FILE = "file"
    DIRECTORY = "directory"
    URL = "url"
    NOTE = "note"
    IMAGE = "image"


class ContextFile(BaseModel):
    """A single file in the context."""

    path: Path = Field(..., description="File path")
    content: str | None = Field(None, description="File content")
    file_hash: str | None = Field(None, description="SHA256 hash of file")
    size: int = Field(0, ge=0, description="File size in bytes")

    @field_validator("path")
    @classmethod
    def validate_path(cls: type[ContextFile], v: Path) -> Path:
        """Ensure path is resolved."""
        return v.resolve()

    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True,
        arbitrary_types_allowed=True,
    )


class Context(BaseModel):
    """Domain model for plan context.

    Context represents the files and information available to a plan.
    """

    id: int | None = Field(None)
    plan_id: int = Field(..., gt=0)
    type: ContextType = Field(ContextType.FILE)
    path: str = Field(..., min_length=1, description="Path or URL")
    content: str | None = Field(None, description="Content if loaded")
    file_hash: str | None = Field(None, description="Content hash")
    size: int = Field(0, ge=0, description="Size in bytes")
    added_at: datetime = Field(default_factory=datetime.now)

    # For directory contexts
    files: list[ContextFile] = Field(default_factory=lambda: [])

    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True,
        use_enum_values=True,
    )


class MaxContextCount(StrEnum):
    """Enum describing supported max context size presets."""

    MB_25 = "25MB"


class ContextUpdateResult(BaseModel):
    """Result metadata emitted after updating the plan context."""

    updated_contexts: list[Context] = Field(
        default_factory=_empty_context_list, alias="updatedContexts"
    )
    token_diffs_by_id: dict[str, dict[str, int]] = Field(
        default_factory=_empty_token_diffs, alias="tokenDiffsById"
    )
    tokens_diff: int = Field(..., alias="tokensDiff", ge=0)
    total_tokens: int = Field(..., alias="totalTokens", ge=0)
    num_files: int = Field(..., alias="numFiles", ge=0)
    num_urls: int = Field(..., alias="numUrls", ge=0)
    num_images: int = Field(..., alias="numImages", ge=0)
    num_trees: int = Field(..., alias="numTrees", ge=0)
    num_maps: int = Field(..., alias="numMaps", ge=0)
    max_tokens: int = Field(..., alias="maxTokens", ge=0)

    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True,
        populate_by_name=True,
        use_enum_values=True,
    )

    @field_validator("token_diffs_by_id")
    @classmethod
    def validate_token_diffs(
        cls: type[ContextUpdateResult], value: dict[str, dict[str, int]]
    ) -> dict[str, dict[str, int]]:
        """Ensure nested token diff mappings are well-formed."""

        for context_id, token_map in value.items():
            for token_type, token_value in token_map.items():
                if token_value < 0:
                    msg = (
                        "Token diff counts must be non-negative "
                        f"({context_id}:{token_type})"
                    )
                    raise ValueError(msg)
        return value


class SummaryForUpdateContextParams(BaseModel):
    """Summary payload produced for context update statistics."""

    num_files: int = Field(..., alias="numFiles", ge=0)
    num_trees: int = Field(..., alias="numTrees", ge=0)
    num_urls: int = Field(..., alias="numUrls", ge=0)
    num_maps: int = Field(..., alias="numMaps", ge=0)
    tokens_diff: int = Field(..., alias="tokensDiff", ge=0)
    total_tokens: int = Field(..., alias="totalTokens", ge=0)

    model_config = ConfigDict(
        str_strip_whitespace=True,
        validate_assignment=True,
        populate_by_name=True,
    )
