"""Plan file domain models for CleverAgents.

Based on Phase 0 discovery stubs from data_models.py.
These models represent plan file operations, results, and state.
"""

from __future__ import annotations

from datetime import datetime
from enum import Enum, StrEnum

from pydantic import BaseModel, ConfigDict, Field


def _empty_string_list() -> list[str]:
    return []


def _empty_replacement_list() -> list[Replacement]:
    return []


def _empty_plan_file_result_dict() -> dict[str, PlanFileResult]:
    return {}


__all__ = [
    "Branch",
    "CurrentPlanFiles",
    "CurrentPlanState",
    "PlanApply",
    "PlanFileResult",
    "Replacement",
]


class Replacement(BaseModel):
    """A single text replacement within a file.

    Represents an old->new text replacement operation.
    """

    id: str = Field(...)
    old: str = Field(..., description="Text to be replaced")
    new: str = Field(..., description="Replacement text")
    stream_id: str = Field("", alias="streamId")
    failed: bool = Field(False, description="Whether the replacement failed")
    reason: str | None = Field(None, description="Reason for failure if failed")
    is_pending: bool = Field(False, alias="isPending")

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


class PlanFileResult(BaseModel):
    """Result of plan operations on a single file.

    Tracks all replacements and the resulting content for a file.
    """

    replacements: list[Replacement] = Field(
        default_factory=_empty_replacement_list,
        description="List of replacements applied to file",
    )
    content: str = Field("", description="Current file content after replacements")
    path: str = Field(..., description="File path")
    will_be_deleted: bool = Field(False, alias="willBeDeleted")

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


class CurrentPlanFiles(BaseModel):
    """Current state of all files in a plan.

    Maps file paths to their current result state.
    """

    files: dict[str, PlanFileResult] = Field(
        default_factory=_empty_plan_file_result_dict,
        description="Map of file paths to their plan file results",
    )

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


class PlanApply(BaseModel):
    """State of applying a plan.

    Tracks which files have been applied and any errors.
    """

    id: str = Field(...)
    plan_id: str = Field(..., alias="planId")
    commit_msg: str = Field("", alias="commitMsg")
    applied_at: datetime = Field(default_factory=datetime.now, alias="appliedAt")
    files_applied: list[str] = Field(
        default_factory=_empty_string_list, alias="filesApplied"
    )
    files_failed: list[str] = Field(
        default_factory=_empty_string_list, alias="filesFailed"
    )
    error_message: str | None = Field(None, alias="errorMessage")

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


class PlanStateStatus(StrEnum):
    """Status of the current plan state."""

    IDLE = "idle"
    BUILDING = "building"
    BUILT = "built"
    APPLYING = "applying"
    APPLIED = "applied"
    ERROR = "error"


class CurrentPlanState(BaseModel):
    """Current overall state of a plan.

    Aggregates file states and overall plan status.
    """

    plan_id: str = Field(..., alias="planId")
    status: PlanStateStatus = Field(PlanStateStatus.IDLE)
    current_plan_files: CurrentPlanFiles = Field(
        default_factory=lambda: CurrentPlanFiles(),
        alias="currentPlanFiles",
    )
    pending_builds: int = Field(0, ge=0, alias="pendingBuilds")
    context_updated_at: datetime | None = Field(None, alias="contextUpdatedAt")
    built_at: datetime | None = Field(None, alias="builtAt")
    applied_at: datetime | None = Field(None, alias="appliedAt")
    error_message: str | None = Field(None, alias="errorMessage")

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


class Branch(BaseModel):
    """A branch in a plan's version history.

    Supports plan branching/versioning where users can create
    alternate versions of a plan.
    """

    id: str = Field(...)
    org_id: str = Field(..., alias="orgId")
    plan_id: str = Field(..., alias="planId")
    parent_branch_id: str | None = Field(None, alias="parentBranchId")
    name: str = Field(..., min_length=1, max_length=255)
    status: PlanStateStatus = Field(PlanStateStatus.IDLE)
    context_tokens: int = Field(0, ge=0, alias="contextTokens")
    convo_tokens: int = Field(0, ge=0, alias="convoTokens")
    shared: bool = Field(False)
    archived: bool = Field(False)
    created_at: datetime = Field(default_factory=datetime.now, alias="createdAt")
    updated_at: datetime = Field(default_factory=datetime.now, alias="updatedAt")

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