"""Immutable feature domain objects for the analysis framework.

This module defines :class:`Feature` and :class:`FeatureSet`, which
represent the output of analyzers in a type-safe, immutable way.

No database models.  No SQLAlchemy.  These are pure domain objects.
"""

from __future__ import annotations

from collections.abc import Iterator
from dataclasses import dataclass


@dataclass(frozen=True)
class Feature:
    """A single measured feature value.

    Attributes:
        name: Feature identifier (e.g. ``"framework.version"``).
        value: The measured value.
        unit: Optional unit of measurement (e.g. ``"Hz"``).
        analyzer: Name of the analyzer that produced this feature.
        version: Version of the analyzer that produced this feature.
    """

    name: str
    value: float | str | int | bool | None
    unit: str | None = None
    analyzer: str = ""
    version: str = ""


@dataclass(frozen=True)
class FeatureSet:
    """Immutable collection of :class:`Feature` objects.

    Attributes:
        features: Tuple of all features in the set.
    """

    features: tuple[Feature, ...]

    def __init__(self, features: list[Feature] | None = None) -> None:
        """Initialize the feature set.

        Args:
            features: Optional initial list of features.  The list
                is converted to a tuple to ensure immutability.
        """
        object.__setattr__(self, "features", tuple(features) if features else ())

    def __iter__(self) -> Iterator[Feature]:
        """Iterate over features in the set."""
        return iter(self.features)

    def __len__(self) -> int:
        """Return the number of features in the set."""
        return len(self.features)

    def find_by_name(self, name: str) -> Feature | None:
        """Find a feature by its name.

        Args:
            name: The feature name to search for.

        Returns:
            The matching :class:`Feature`, or ``None`` if not found.
        """
        for f in self.features:
            if f.name == name:
                return f
        return None

    def find_all_by_analyzer(self, analyzer_name: str) -> list[Feature]:
        """Find all features produced by a specific analyzer.

        Args:
            analyzer_name: The analyzer name to filter by.

        Returns:
            A list of :class:`Feature` instances produced by the
            given analyzer.
        """
        return [f for f in self.features if f.analyzer == analyzer_name]
