"""Goal models — structured representation of user goals.

A Goal is the processed form of a user's raw input. It carries the
original text, a normalized version, extracted context (domain, entities,
keywords), and metadata used downstream by the thinking and planning layers.
"""

from dataclasses import dataclass, field
from datetime import datetime
from typing import Any
from uuid import UUID, uuid4


@dataclass
class GoalContext:
    """Extracted context from a goal.

    Populated by GoalProcessor during analysis. Provides structured
    information about the goal's domain, key entities, action verbs,
    and estimated complexity.
    """

    domain: str = ""
    entities: list[str] = field(default_factory=list)
    keywords: list[str] = field(default_factory=list)
    actions: list[str] = field(default_factory=list)
    complexity: str = "medium"  # low, medium, high
    priority: str = "normal"   # low, normal, high, critical
    metadata: dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> dict[str, Any]:
        return {
            "domain": self.domain,
            "entities": self.entities,
            "keywords": self.keywords,
            "actions": self.actions,
            "complexity": self.complexity,
            "priority": self.priority,
            "metadata": self.metadata,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "GoalContext":
        return cls(
            domain=data.get("domain", ""),
            entities=data.get("entities", []),
            keywords=data.get("keywords", []),
            actions=data.get("actions", []),
            complexity=data.get("complexity", "medium"),
            priority=data.get("priority", "normal"),
            metadata=data.get("metadata", {}),
        )

    def merge(self, other: "GoalContext") -> "GoalContext":
        """Merge another context into this one (non-destructive)."""
        return GoalContext(
            domain=self.domain or other.domain,
            entities=list(dict.fromkeys(self.entities + other.entities)),
            keywords=list(dict.fromkeys(self.keywords + other.keywords)),
            actions=list(dict.fromkeys(self.actions + other.actions)),
            complexity=self.complexity if self.complexity != "medium" else other.complexity,
            priority=self.priority if self.priority != "normal" else other.priority,
            metadata={**other.metadata, **self.metadata},
        )


@dataclass
class GoalConstraint:
    """A constraint or requirement attached to a goal."""

    name: str = ""
    description: str = ""
    constraint_type: str = "soft"  # hard, soft, preference
    source: str = "inferred"      # explicit, inferred, system

    def to_dict(self) -> dict[str, Any]:
        return {
            "name": self.name,
            "description": self.description,
            "constraint_type": self.constraint_type,
            "source": self.source,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "GoalConstraint":
        return cls(
            name=data.get("name", ""),
            description=data.get("description", ""),
            constraint_type=data.get("constraint_type", "soft"),
            source=data.get("source", "inferred"),
        )


@dataclass
class Goal:
    """A processed user goal.

    Created by GoalProcessor from raw user input. The ``normalized``
    field is the cleaned, standardized text. The ``context`` dict is
    the flat merge of GoalContext plus any user-supplied context.
    """

    id: UUID = field(default_factory=uuid4)
    raw: str = ""
    normalized: str = ""
    context: dict[str, Any] = field(default_factory=dict)
    goal_context: GoalContext = field(default_factory=GoalContext)
    constraints: list[GoalConstraint] = field(default_factory=list)
    sub_goals: list[str] = field(default_factory=list)
    created_at: datetime = field(default_factory=datetime.utcnow)

    @property
    def domain(self) -> str:
        return self.goal_context.domain or self.context.get("domain", "general")

    @property
    def complexity(self) -> str:
        return self.goal_context.complexity

    @property
    def word_count(self) -> int:
        return len(self.normalized.split())

    def to_dict(self) -> dict[str, Any]:
        return {
            "id": str(self.id),
            "raw": self.raw,
            "normalized": self.normalized,
            "context": self.context,
            "goal_context": self.goal_context.to_dict(),
            "constraints": [c.to_dict() for c in self.constraints],
            "sub_goals": self.sub_goals,
            "created_at": self.created_at.isoformat(),
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Goal":
        return cls(
            id=UUID(data["id"]) if data.get("id") else uuid4(),
            raw=data.get("raw", ""),
            normalized=data.get("normalized", ""),
            context=data.get("context", {}),
            goal_context=GoalContext.from_dict(data.get("goal_context", {})),
            constraints=[
                GoalConstraint.from_dict(c) for c in data.get("constraints", [])
            ],
            sub_goals=data.get("sub_goals", []),
        )
