"""Data models for extraction results."""

from dataclasses import dataclass
from typing import Optional
from uuid import UUID


@dataclass
class TableBoundary:
    """Represents the boundaries of a detected table in an Excel sheet.

    Attributes:
        sheet: Name of the sheet containing the table
        table_id: Sequential ID for this table within the sheet (starts at 1)
        start_row: First row of the table (1-indexed, includes header)
        end_row: Last row of the table (1-indexed)
        start_col: First column letter (e.g., "A")
        end_col: Last column letter (e.g., "F")
        title: Optional title from merged rows above the table
        title: Optional table title from merged row above the table
    """

    sheet: str
    table_id: int
    start_row: int
    end_row: int
    start_col: str
    end_col: str
    title: Optional[str] = None

    def __post_init__(self):
        """Validate boundary integrity after initialization."""
        if self.start_row > self.end_row:
            raise ValueError(
                f"Invalid table boundary: start_row ({self.start_row}) "
                f"must be <= end_row ({self.end_row})"
            )

        if self.table_id < 1:
            raise ValueError(f"table_id must be >= 1, got {self.table_id}")

        if self.start_row < 1:
            raise ValueError(f"start_row must be >= 1, got {self.start_row}")

        if self.end_row < 1:
            raise ValueError(f"end_row must be >= 1, got {self.end_row}")

    def to_dict(self) -> dict:
        """Convert TableBoundary to dictionary for JSON serialization.

        Returns:
            Dictionary with all boundary fields
        """
        result = {
            "sheet": self.sheet,
            "table_id": self.table_id,
            "start_row": self.start_row,
            "end_row": self.end_row,
            "start_col": self.start_col,
            "end_col": self.end_col,
            "title": self.title,
        }
        if self.title:
            result["title"] = self.title
        return result


@dataclass
class HeaderInfo:
    """Represents metadata about a multi-level column header.

    Attributes:
        display: Flattened column name with " > " separator (e.g., "Sales > Q1")
        levels: List of header values from top to bottom (e.g., ["Sales", "Q1"])
        depth: Number of levels in the hierarchy (1-4)
        column_letter: Excel column letter (e.g., "A", "B")
    """

    display: str
    levels: list[str]
    depth: int
    column_letter: str

    def __post_init__(self):
        """Validate header info integrity after initialization."""
        if self.depth < 1 or self.depth > 4:
            raise ValueError(
                f"Invalid header depth: {self.depth}. Must be between 1 and 4."
            )

        if not self.levels:
            raise ValueError("Header levels cannot be empty")

        if len(self.levels) != self.depth:
            raise ValueError(
                f"Header depth ({self.depth}) must match number of levels ({len(self.levels)})"
            )

        if not self.column_letter:
            raise ValueError("Column letter cannot be empty")

    def to_dict(self) -> dict:
        """Convert HeaderInfo to dictionary for JSON serialization.

        Returns:
            Dictionary with all header info fields
        """
        return {
            "display": self.display,
            "levels": self.levels,
            "depth": self.depth,
            "column_letter": self.column_letter,
        }


@dataclass
class MergeMetadata:
    """Represents metadata about a cell that was propagated from a merged range.

    Attributes:
        cell_coordinate: Excel coordinate of the cell (e.g., "A2")
        merged_from_range: Excel coordinate range that this cell was merged from (e.g., "A2:A4")
        original_value: The value that was propagated from the master cell
    """

    cell_coordinate: str
    merged_from_range: str
    original_value: str

    def __post_init__(self):
        """Validate merge metadata integrity after initialization."""
        if not self.cell_coordinate:
            raise ValueError("Cell coordinate cannot be empty")

        if not self.merged_from_range:
            raise ValueError("Merged from range cannot be empty")

        # Validate format: should be like "A2:A4"
        if ":" not in self.merged_from_range:
            raise ValueError(
                f"Invalid merged range format: {self.merged_from_range}. "
                "Expected format: 'A2:A4'"
            )

    def to_dict(self) -> dict:
        """Convert MergeMetadata to dictionary for JSON serialization.

        Returns:
            Dictionary with all merge metadata fields
        """
        return {
            "cell_coordinate": self.cell_coordinate,
            "merged_from_range": self.merged_from_range,
            "original_value": self.original_value,
        }


@dataclass
class SymbolEntry:
    """Represents a single symbol-meaning pair from a dictionary sheet.

    Attributes:
        symbol: The symbol string (e.g., "○", "×", "07")
        meaning: The meaning/description of the symbol
        context: Optional context where this symbol is used
    """

    symbol: str
    meaning: str
    context: Optional[str] = None

    def __post_init__(self):
        """Validate symbol entry integrity after initialization."""
        if not self.symbol:
            raise ValueError("Symbol cannot be empty")
        if len(self.symbol) > 50:
            raise ValueError(f"Symbol too long (max 50 chars): {self.symbol}")
        if not self.meaning:
            raise ValueError("Meaning cannot be empty")

    def to_dict(self) -> dict:
        """Convert SymbolEntry to dictionary for JSON serialization."""
        result = {
            "symbol": self.symbol,
            "meaning": self.meaning,
        }
        if self.context:
            result["context"] = self.context
        return result


@dataclass
class DetectedDictionary:
    """Represents a detected symbol dictionary sheet.

    Attributes:
        sheet_name: Name of the sheet identified as a dictionary
        detection_method: How the dictionary was detected ("name" or "structure")
        confidence: Confidence score (0.0 to 1.0)
        symbols: List of extracted symbol entries
    """

    sheet_name: str
    detection_method: str  # "name" or "structure"
    confidence: float
    symbols: list[SymbolEntry]

    def __post_init__(self):
        """Validate detected dictionary integrity after initialization."""
        if not self.sheet_name:
            raise ValueError("Sheet name cannot be empty")
        if self.detection_method not in ("name", "structure"):
            raise ValueError(
                f"Invalid detection method: {self.detection_method}. "
                "Must be 'name' or 'structure'"
            )
        if not 0.0 <= self.confidence <= 1.0:
            raise ValueError(
                f"Invalid confidence: {self.confidence}. Must be between 0.0 and 1.0"
            )

    def to_dict(self) -> dict:
        """Convert DetectedDictionary to dictionary for JSON serialization."""
        return {
            "sheet_name": self.sheet_name,
            "detection_method": self.detection_method,
            "confidence": self.confidence,
            "symbols": [s.to_dict() for s in self.symbols],
            "symbol_count": len(self.symbols),
        }


@dataclass
class SymbolDetectionSummary:
    """Summary of symbol dictionary detection results.

    Attributes:
        dictionaries_found: Number of dictionary sheets detected
        total_symbols: Total symbols extracted across all dictionaries
        detected_sheets: List of detected dictionary information
        warnings: List of warning messages
    """

    dictionaries_found: int
    total_symbols: int
    detected_sheets: list[dict]
    warnings: list[str]

    def to_dict(self) -> dict:
        """Convert SymbolDetectionSummary to dictionary for JSON serialization."""
        return {
            "dictionaries_found": self.dictionaries_found,
            "total_symbols": self.total_symbols,
            "detected_sheets": self.detected_sheets,
            "warnings": self.warnings,
        }


@dataclass
class SymbolResolutionSummary:
    """Summary of symbol resolution results for a document.

    Attributes:
        document_id: UUID of the processed document
        records_processed: Total number of records processed
        symbols_resolved: Total count of symbols resolved
        unresolved_count: Count of unique unknown symbols encountered
        unresolved_symbols: List of unique unknown symbol strings
        warnings: List of warning messages during resolution
    """

    document_id: UUID
    records_processed: int
    symbols_resolved: int
    unresolved_count: int
    unresolved_symbols: list[str]
    warnings: list[str]

    def to_dict(self) -> dict:
        """Convert SymbolResolutionSummary to dictionary for JSON serialization."""
        return {
            "document_id": str(self.document_id),
            "records_processed": self.records_processed,
            "symbols_resolved": self.symbols_resolved,
            "unresolved_count": self.unresolved_count,
            "unresolved_symbols": self.unresolved_symbols,
            "warnings": self.warnings,
        }


@dataclass
class ExtractedRecord:
    """Represents a single structured record extracted from a table row.

    Attributes:
        content: Dictionary mapping header names to cell values
        headers: List of header names (keys) in the record
        _source: Source metadata (document_id, filename, sheet, table_id, row, col_range)
        _merge_metadata: Optional metadata about merged cells in this record
        resolved_content: Optional dictionary with symbol-resolved values
        _legacy_metadata: Optional metadata from DETAILED pipeline (images, flowcharts, shapes)
        tags: Optional list of semantic tags (TagAssignment objects or dicts with confidence scores)
    """

    content: dict
    headers: list[str]
    _source: dict
    _merge_metadata: Optional[dict] = None
    resolved_content: Optional[dict] = None
    _legacy_metadata: Optional[dict] = None
    tags: Optional[list] = None

    def __post_init__(self):
        """Validate extracted record integrity after initialization."""
        if not self.content:
            raise ValueError("Record content cannot be empty")

        if not self.headers:
            raise ValueError("Record headers cannot be empty")

        # Validate required source fields
        required_source_fields = [
            "document_id",
            "filename",
            "sheet",
            "table_id",
            "row",
            "col_range",
        ]
        for field in required_source_fields:
            if field not in self._source:
                raise ValueError(
                    f"Missing required source field: {field}. "
                    f"Required fields: {required_source_fields}"
                )

        # Validate document_id is a UUID (if string, try to parse it)
        if isinstance(self._source["document_id"], str):
            try:
                UUID(self._source["document_id"])
            except ValueError:
                raise ValueError(
                    f"Invalid document_id UUID: {self._source['document_id']}"
                )

    def to_dict(self) -> dict:
        """Convert ExtractedRecord to dictionary for JSON serialization.

        Returns:
            Dictionary with all record fields
        """
        result = {
            "content": self.content,
            "headers": self.headers,
            "_source": self._source,
        }

        if self._merge_metadata:
            result["_merge_metadata"] = self._merge_metadata

        if self.resolved_content:
            result["resolved_content"] = self.resolved_content

        if self.tags:
            # Serialize TagAssignment objects to dicts if needed
            serialized_tags = []
            for tag in self.tags:
                if hasattr(tag, "model_dump"):
                    serialized_tags.append(tag.model_dump())
                elif hasattr(tag, "dict"):
                    serialized_tags.append(tag.dict())
                elif isinstance(tag, dict):
                    serialized_tags.append(tag)
                else:
                    # Skip invalid types
                    pass
            result["tags"] = serialized_tags

        return result


@dataclass
class CrossRefResolutionSummary:
    """Summary of cross-reference detection and resolution results.

    Attributes:
        document_id: UUID of the processed document
        references_found: Total number of cross-references detected
        references_resolved: Number of references successfully resolved
        unresolved_count: Number of references that could not be resolved
        unresolved_refs: List of unresolved reference texts
        warnings: List of warning messages during detection/resolution
    """

    document_id: UUID
    references_found: int
    references_resolved: int
    unresolved_count: int
    unresolved_refs: list[str]
    warnings: list[str]

    def to_dict(self) -> dict:
        """Convert CrossRefResolutionSummary to dictionary for JSON serialization."""
        return {
            "document_id": str(self.document_id),
            "references_found": self.references_found,
            "references_resolved": self.references_resolved,
            "unresolved_count": self.unresolved_count,
            "unresolved_refs": self.unresolved_refs,
            "warnings": self.warnings,
        }
