"""Immutable feature definition objects for the feature registry.

This module defines the enums and :class:`FeatureDefinition` that
describe the metadata of every feature produced by analyzers.

All objects are immutable.  No database access.  No side effects.
"""

from __future__ import annotations

import enum
from dataclasses import dataclass


class FeatureCategory(enum.Enum):
    """Top-level category grouping features by analysis block.

    Categories correspond to the Music DNA block layout:

    Attributes:
        SIGNAL: Basic signal statistics (amplitude, RMS, dBFS, etc.)
        SPECTRAL: Frequency-domain features (centroid, bandwidth, etc.)
        DYNAMIC: Dynamic range characteristics (crest factor, headroom, etc.)
        RHYTHM: Temporal rhythmic features (tempo, onset rate, etc.)
        HARMONY: Harmonic features (chroma, key, tonnetz, etc.)
    """

    SIGNAL = "signal"
    SPECTRAL = "spectral"
    DYNAMIC = "dynamic"
    RHYTHM = "rhythm"
    HARMONY = "harmony"


class DataType(enum.Enum):
    """Supported data types for feature values.

    Attributes:
        FLOAT: Floating-point numeric value.
        INT: Integer numeric value.
        STRING: Textual value (e.g. key name, mode).
        BOOL: Boolean value.
    """

    FLOAT = "float"
    INT = "int"
    STRING = "string"
    BOOL = "bool"


class NormalizationStrategy(enum.Enum):
    """Normalization strategy for mapping a feature into Music DNA.

    Attributes:
        IDENTITY: No transformation; value used as-is.
        MINMAX: Scale to [0, 1] using known min/max bounds.
        LOG: Apply logarithmic compression.
        ZSCORE: Standardize to zero-mean, unit-variance.
        CATEGORICAL: One-hot or ordinal encoding for non-numeric values.
        NONE: No normalization applicable (metadata or descriptive).
    """

    IDENTITY = "identity"
    MINMAX = "minmax"
    LOG = "log"
    ZSCORE = "zscore"
    CATEGORICAL = "categorical"
    NONE = "none"


@dataclass(frozen=True)
class FeatureDefinition:
    """Immutable metadata describing one feature.

    Every feature produced by an analyzer must have a corresponding
    :class:`FeatureDefinition` registered in the
    :class:`~features.registry.FeatureRegistry`.

    Attributes:
        identifier: Unique canonical identifier in ``block.feature``
            format (e.g. ``"spectral.centroid"``).
        display_name: Human-readable name for UI display.
        category: Analysis block this feature belongs to.
        description: Short description of what the feature measures.
        unit: Unit of measurement, or ``None`` if dimensionless.
        data_type: Expected data type of the feature value.
        normalization: Strategy for normalizing this feature into
            Music DNA.
        version: Semantic version of the feature definition.
    """

    identifier: str
    display_name: str
    category: FeatureCategory
    description: str
    unit: str | None
    data_type: DataType
    normalization: NormalizationStrategy
    version: str

    def __post_init__(self) -> None:
        """Validate fields after initialization.

        Raises:
            ValueError: If identifier or display_name is empty.
        """
        if not self.identifier or not self.identifier.strip():
            raise ValueError("identifier must be a non-empty string")
        if not self.display_name or not self.display_name.strip():
            raise ValueError("display_name must be a non-empty string")
