"""Immutable MusicDNA dataclass and metadata.

This module defines :class:`MusicDNAMetadata` and :class:`MusicDNA`,
the canonical musical representation produced by the Music DNA Builder.

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

from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass, field
from types import MappingProxyType


@dataclass(frozen=True)
class MusicDNAMetadata:
    """Immutable metadata about how a MusicDNA was built.

    Attributes:
        builder_version: Semantic version of the MusicDNABuilder.
        feature_count: Number of features in the MusicDNA values.
        normalization_version: Semantic version of the normalizer.
        source_analyzer_versions: Mapping of analyzer name to version
            for all analyzers that contributed features.
    """

    builder_version: str
    feature_count: int
    normalization_version: str
    source_analyzer_versions: Mapping[str, str] = field(
        default_factory=lambda: MappingProxyType({})
    )

    def __post_init__(self) -> None:
        """Freeze the source_analyzer_versions mapping."""
        if not isinstance(self.source_analyzer_versions, MappingProxyType):
            object.__setattr__(
                self,
                "source_analyzer_versions",
                MappingProxyType(dict(self.source_analyzer_versions)),
            )


@dataclass(frozen=True)
class MusicDNA:
    """Immutable canonical musical representation of a track.

    Attributes:
        track_id: UUID of the track this DNA represents.
        schema_version: Music DNA schema version (e.g. ``"1.0"``).
        created_at: ISO 8601 UTC timestamp string.  Provided by the
            caller to ensure determinism.
        values: Immutable mapping of canonical feature identifier to
            normalized feature value.
        metadata: :class:`MusicDNAMetadata` with build provenance.
    """

    track_id: str
    schema_version: str
    created_at: str
    values: Mapping[str, float | str | int | bool] = field(
        default_factory=lambda: MappingProxyType({})
    )
    metadata: MusicDNAMetadata | None = None

    def __post_init__(self) -> None:
        """Freeze the values mapping."""
        if not isinstance(self.values, MappingProxyType):
            object.__setattr__(
                self,
                "values",
                MappingProxyType(dict(self.values)),
            )
