"""Estimation result domain model for CleverAgents.

Provides the ``EstimationResult`` value object returned by the
estimation actor after the Strategize phase completes and before
Execute begins.  All fields are optional — the estimation actor
populates whichever fields it can compute.

The result is informational only; it does **not** gate the
Execute phase.

Based on ``docs/specification.md`` estimation actor sections and
issue #890.
"""

from __future__ import annotations

from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field, field_validator

__all__ = ["EstimationResult"]


class EstimationResult(BaseModel):
    """Cost, time, and risk estimation produced by the estimation actor.

    Every field is optional so that estimation actors can populate only
    the metrics they are able to compute.  The ``summary`` field
    provides a human-readable overview and ``raw_output`` preserves the
    unprocessed actor response for auditing.
    """

    estimated_cost_usd: float | None = Field(
        default=None,
        ge=0.0,
        description="Estimated LLM cost in USD",
    )
    estimated_tokens: int | None = Field(
        default=None,
        ge=0,
        description="Estimated total token usage",
    )
    estimated_steps: int | None = Field(
        default=None,
        ge=0,
        description="Expected number of execution steps",
    )
    estimated_child_plans: int | None = Field(
        default=None,
        description="Expected number of child plans",
    )
    estimated_time_seconds: float | None = Field(
        default=None,
        ge=0.0,
        description="Estimated execution time in seconds",
    )
    risk_level: Literal["low", "medium", "high", "critical"] | None = Field(
        default=None,
        description="Risk assessment: low, medium, high, critical",
    )
    risk_factors: tuple[str, ...] = Field(
        default_factory=tuple,
        description="Identified risk factors",
    )
    summary: str = Field(
        default="",
        max_length=10_000,
        description="Human-readable estimation summary",
    )
    raw_output: str = Field(
        default="",
        max_length=100_000,
        description="Raw actor output before parsing",
    )

    model_config = ConfigDict(frozen=True, allow_inf_nan=False)

    @field_validator("risk_factors")
    @classmethod
    def _cap_risk_factors(cls, v: tuple[str, ...]) -> tuple[str, ...]:
        if len(v) > 100:
            raise ValueError("risk_factors must contain at most 100 items")
        return v

    def as_display_dict(self) -> dict[str, Any]:
        """Return a dict of non-empty fields suitable for display/CLI output.

        Only fields that are set (not ``None`` and not empty) are
        included, keeping serialized output compact.

        .. note::

           Three call-sites currently duplicate this logic and should
           migrate to this method:

           - ``Plan.as_cli_dict()`` in ``plan.py``
           - ``_build_plan_summary()`` in ``cli/commands/plan.py``
           - ``_show_plan_detail()`` in ``cli/commands/plan.py``
        """
        d: dict[str, Any] = {}
        if self.estimated_cost_usd is not None:
            d["estimated_cost_usd"] = self.estimated_cost_usd
        if self.estimated_tokens is not None:
            d["estimated_tokens"] = self.estimated_tokens
        if self.estimated_steps is not None:
            d["estimated_steps"] = self.estimated_steps
        if self.estimated_child_plans is not None:
            d["estimated_child_plans"] = self.estimated_child_plans
        if self.estimated_time_seconds is not None:
            d["estimated_time_seconds"] = self.estimated_time_seconds
        if self.risk_level is not None:
            d["risk_level"] = self.risk_level
        if self.risk_factors:
            d["risk_factors"] = list(self.risk_factors)
        if self.summary:
            d["summary"] = self.summary
        return d
