"""Named entity domain entity for NER extraction."""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING
from uuid import UUID, uuid4

if TYPE_CHECKING:
    from noteflow.domain.value_objects import MeetingId


class EntityCategory(Enum):
    """Categories for named entities.

    Maps to spaCy entity types:
        PERSON -> PERSON
        ORG -> COMPANY
        GPE/LOC/FAC -> LOCATION
        PRODUCT/WORK_OF_ART -> PRODUCT
        DATE/TIME -> DATE

    Note: TECHNICAL and ACRONYM are placeholders for future custom pattern
    matching (not currently mapped from spaCy's default NER model).
    """

    PERSON = "person"
    COMPANY = "company"
    PRODUCT = "product"
    TECHNICAL = "technical"  # Future: custom pattern matching
    ACRONYM = "acronym"  # Future: custom pattern matching
    LOCATION = "location"
    DATE = "date"
    OTHER = "other"

    @classmethod
    def from_string(cls, value: str) -> EntityCategory:
        """Convert string to EntityCategory.

        Args:
            value: Category string value.

        Returns:
            Corresponding EntityCategory.

        Raises:
            ValueError: If value is not a valid category.
        """
        try:
            return cls(value.lower())
        except ValueError as e:
            raise ValueError(f"Invalid entity category: {value}") from e


@dataclass
class NamedEntity:
    """A named entity extracted from a meeting transcript.

    Represents a person, company, product, location, or other notable term
    identified in the meeting transcript via NER processing.
    """

    id: UUID = field(default_factory=uuid4)
    meeting_id: MeetingId | None = None
    text: str = ""
    normalized_text: str = ""
    category: EntityCategory = EntityCategory.OTHER
    segment_ids: list[int] = field(default_factory=list)
    confidence: float = 0.0
    is_pinned: bool = False

    # Database primary key (set after persistence)
    db_id: UUID | None = None

    def __post_init__(self) -> None:
        """Validate entity data and compute normalized_text."""
        if not 0.0 <= self.confidence <= 1.0:
            raise ValueError(f"confidence must be between 0 and 1, got {self.confidence}")

        # Auto-compute normalized_text if not provided
        if self.text and not self.normalized_text:
            self.normalized_text = self.text.lower().strip()

    @classmethod
    def create(
        cls,
        text: str,
        category: EntityCategory,
        segment_ids: list[int],
        confidence: float,
        meeting_id: MeetingId | None = None,
    ) -> NamedEntity:
        """Create a new named entity with validation and normalization.

        Factory method that handles text normalization, segment deduplication,
        and confidence validation before entity construction.

        Args:
            text: The entity text as it appears in transcript.
            category: Classification category.
            segment_ids: Segments where entity appears (will be deduplicated and sorted).
            confidence: Extraction confidence (0.0-1.0).
            meeting_id: Optional meeting association.

        Returns:
            New NamedEntity instance with normalized fields.

        Raises:
            ValueError: If text is empty or confidence is out of range.
        """
        # Validate required text
        stripped_text = text.strip()
        if not stripped_text:
            raise ValueError("Entity text cannot be empty")

        # Normalize and deduplicate segment_ids
        unique_segments = sorted(set(segment_ids))

        return cls(
            text=stripped_text,
            normalized_text=stripped_text.lower(),
            category=category,
            segment_ids=unique_segments,
            confidence=confidence,
            meeting_id=meeting_id,
        )

    @property
    def occurrence_count(self) -> int:
        """Number of unique segments where this entity appears.

        Returns:
            Count of distinct segment IDs. Returns 0 if segment_ids is empty.
        """
        return len(self.segment_ids) if self.segment_ids else 0

    def merge_segments(self, other_segment_ids: list[int]) -> None:
        """Merge segment IDs from another occurrence of this entity.

        Args:
            other_segment_ids: Segment IDs from another occurrence.
        """
        self.segment_ids = sorted(set(self.segment_ids) | set(other_segment_ids))
