"""Music DNA vector encoder.

This module defines :class:`MusicDNAEncoder`, which transforms a
validated :class:`~music_dna.music_dna.MusicDNA` object into a
deterministic, fixed-size :class:`~music_dna.vector.MusicDNAVector`.

The encoder depends only on MusicDNA and MusicDNALayout — it is
independent from analyzer implementations.

Encoding rules:
- Numeric (float, int): stored directly as float32.
- Boolean: ``False`` → 0.0, ``True`` → 1.0.
- Categorical (string): stored as 0.0 for now.
- Reserved dimensions: filled with 0.0.
"""

from __future__ import annotations

import numpy as np

from music_dna.exceptions import UnknownFeatureError
from music_dna.layout import MusicDNALayout
from music_dna.music_dna import MusicDNA
from music_dna.vector import MusicDNAVector, MusicDNAVectorMetadata


class MusicDNAEncoder:
    """Encodes MusicDNA objects into fixed-size float32 vectors.

    Attributes:
        layout: The :class:`MusicDNALayout` mapping features to indices.
    """

    ENCODER_VERSION = "1.0.0"
    SCHEMA_VERSION = "1.0"

    def __init__(self, layout: MusicDNALayout | None = None) -> None:
        """Initialize the encoder.

        Args:
            layout: Optional layout instance.  If ``None``, a default
                :class:`MusicDNALayout` is created.
        """
        self.layout = layout or MusicDNALayout()

    def encode(self, dna: MusicDNA, created_at: str) -> MusicDNAVector:
        """Encode a MusicDNA into a fixed-size float32 vector.

        Args:
            dna: The validated MusicDNA object to encode.
            created_at: ISO 8601 UTC timestamp string.  Provided by
                the caller to ensure determinism.

        Returns:
            An immutable :class:`MusicDNAVector`.

        Raises:
            UnknownFeatureError: If a feature identifier in the DNA
                is not in the layout.
        """
        vector = np.zeros(self.layout.dimension(), dtype=np.float32)
        feature_count = 0

        for identifier, value in dna.values.items():
            if not self.layout.contains(identifier):
                raise UnknownFeatureError(
                    f"Feature '{identifier}' is not in the layout"
                )

            index = self.layout.get_index(identifier)
            vector[index] = self._to_float32(value)
            feature_count += 1

        reserved = self.layout.dimension() - feature_count

        metadata = MusicDNAVectorMetadata(
            encoder_version=self.ENCODER_VERSION,
            layout_version=self.layout.LAYOUT_VERSION,
            created_at=created_at,
            feature_count=feature_count,
            reserved_dimensions=reserved,
        )

        return MusicDNAVector(
            track_id=dna.track_id,
            schema_version=self.SCHEMA_VERSION,
            dimension=self.layout.dimension(),
            values=vector,
            metadata=metadata,
        )

    @staticmethod
    def _to_float32(value: float | str | int | bool) -> float:
        """Convert a feature value to float32.

        Args:
            value: The feature value from MusicDNA.

        Returns:
            The value as a float.
        """
        if isinstance(value, bool):
            return 1.0 if value else 0.0
        if isinstance(value, (int, float)):
            return float(value)
        return 0.0
