"""Optimizer module for simplifying and optimizing sort specifications.

This module provides optimization capabilities for removing redundant or
meaningless sort criteria.
"""

from __future__ import annotations

from dataclasses import dataclass

from therismos.sorting._sorting import SortOrder, SortSpec


@dataclass(frozen=True)
class OptimizationRecord:
    """Record of an optimization transformation.

    :ivar before: The original sort spec before optimization.
    :vartype before: SortSpec
    :ivar after: The resulting sort spec after optimization.
    :vartype after: SortSpec
    :ivar reason: A description of the optimization rule that was applied.
    :vartype reason: str
    """

    before: SortSpec
    after: SortSpec
    reason: str


def optimize(
    spec: SortSpec, records: list[OptimizationRecord] | None = None
) -> tuple[SortSpec, list[OptimizationRecord]]:
    """Optimize a sort specification.

    Applies the following optimization rules:
    1. Removes criteria with SortOrder.NONE (no sorting)
    2. Removes redundant criteria (keeps last occurrence per field)

    :param spec: The sort specification to optimize.
    :type spec: SortSpec
    :param records: Optional list to collect optimization records. If None, a new list is created.
    :type records: list[OptimizationRecord] | None
    :returns: A tuple of (optimized sort spec, list of optimization records).
    :rtype: tuple[SortSpec, list[OptimizationRecord]]

    Example:
        >>> from therismos.sorting import SortSpec, SortCriterion, SortOrder
        >>> spec = SortSpec([
        ...     SortCriterion("age", SortOrder.ASCENDING),
        ...     SortCriterion("name", SortOrder.NONE),
        ...     SortCriterion("age", SortOrder.DESCENDING),
        ... ])
        >>> optimized, _ = optimize(spec)
        >>> len(optimized)
        1
        >>> optimized[0].field
        'age'
        >>> optimized[0].order
        <SortOrder.DESCENDING: -1>
    """
    if records is None:
        records = []

    # Start with the original spec
    current_spec = spec

    # Apply optimization rules
    current_spec = _remove_none_orders(current_spec, records)
    current_spec = _remove_redundant_criteria(current_spec, records)

    return current_spec, records


def _remove_none_orders(spec: SortSpec, records: list[OptimizationRecord]) -> SortSpec:
    """Remove criteria with SortOrder.NONE.

    :param spec: The sort specification to optimize.
    :type spec: SortSpec
    :param records: List to collect optimization records.
    :type records: list[OptimizationRecord]
    :returns: The optimized sort specification.
    :rtype: SortSpec
    """
    # Filter out NONE orders
    filtered = [criterion for criterion in spec if criterion.order != SortOrder.NONE]

    # If anything was removed, record it
    if len(filtered) < len(spec):
        result = SortSpec(filtered)
        records.append(
            OptimizationRecord(
                spec,
                result,
                f"Removed {len(spec) - len(filtered)} criteria with SortOrder.NONE",
            )
        )
        return result

    return spec


def _remove_redundant_criteria(spec: SortSpec, records: list[OptimizationRecord]) -> SortSpec:
    """Remove redundant criteria, keeping only the last occurrence of each field.

    When a field appears multiple times, only the last (rightmost) criterion
    is kept, as it overrides earlier ones.

    :param spec: The sort specification to optimize.
    :type spec: SortSpec
    :param records: List to collect optimization records.
    :type records: list[OptimizationRecord]
    :returns: The optimized sort specification.
    :rtype: SortSpec
    """
    # Track seen field names and their last occurrence index
    seen_fields: dict[str, int] = {}
    for i, criterion in enumerate(spec):
        seen_fields[criterion.field] = i

    # Keep only the last occurrence of each field
    result_criteria = []
    for i, criterion in enumerate(spec):
        # Keep this criterion if it's the last occurrence of its field
        if seen_fields[criterion.field] == i:
            result_criteria.append(criterion)

    # If anything was removed, record it
    if len(result_criteria) < len(spec):
        result = SortSpec(result_criteria)
        removed_count = len(spec) - len(result_criteria)
        records.append(
            OptimizationRecord(
                spec,
                result,
                f"Removed {removed_count} redundant criteria (overridden by later occurrences)",
            )
        )
        return result

    return spec


__all__ = [
    "OptimizationRecord",
    "optimize",
]
