"""Post-processing mix calibrator for SongDefinition.

Applies role-based volume, EQ, pan, sends, and master chain upgrades
after track construction and before RPPBuilder.
"""

from __future__ import annotations

from ..core.schema import SongDefinition, TrackDef, PluginDef
from .presets import VOLUME_PRESETS, EQ_PRESETS, PAN_PRESETS, SEND_PRESETS


class Calibrator:
    """Post-processing mix calibrator for SongDefinition."""

    @staticmethod
    def apply(song: SongDefinition) -> SongDefinition:
        """Apply role-based volume, EQ, pan, sends, and master chain calibration.

        Mutates song in-place and returns it.
        Skips tracks named 'Reverb' or 'Delay' (return tracks).
        """
        Calibrator._calibrate_volumes(song)
        Calibrator._calibrate_eq(song)
        Calibrator._calibrate_pans(song)
        Calibrator._calibrate_sends(song)
        Calibrator._swap_master_chain(song)
        return song

    # ------------------------------------------------------------------
    # Role resolution
    # ------------------------------------------------------------------

    @staticmethod
    def _resolve_role(track_name: str) -> str | None:
        """Map track name to role key, or None if unrecognized.

        Uses substring matching to be tolerant of display names
        like '808 Bass', 'Drumloop', etc.
        """
        lower = track_name.strip().lower()
        # Return tracks
        if lower in ("reverb", "delay"):
            return None
        # Direct matches
        if lower in ("drumloop", "chords", "lead", "clap", "pad", "perc"):
            return lower
        # Substring matches
        if "bass" in lower:
            return "bass"
        if "drum" in lower:
            return "drumloop"
        if "perc" in lower:
            return "perc"
        if "clap" in lower or "snare" in lower:
            return "clap"
        if "chord" in lower:
            return "chords"
        if "lead" in lower or "synth" in lower or "melody" in lower:
            return "lead"
        if "pad" in lower or "atmos" in lower or "ambient" in lower:
            return "pad"
        return None

    # ------------------------------------------------------------------
    # Volume calibration
    # ------------------------------------------------------------------

    @staticmethod
    def _calibrate_volumes(song: SongDefinition) -> None:
        """Set track volumes from VOLUME_PRESETS by role.

        Skips return tracks and unknown roles.
        """
        for track in song.tracks:
            role = Calibrator._resolve_role(track.name)
            if role is None:
                continue
            if role in VOLUME_PRESETS:
                track.volume = VOLUME_PRESETS[role]

    # ------------------------------------------------------------------
    # Pan calibration
    # ------------------------------------------------------------------

    @staticmethod
    def _calibrate_pans(song: SongDefinition) -> None:
        """Set track pan from PAN_PRESETS by role.

        Skips return tracks and unknown roles.
        """
        for track in song.tracks:
            role = Calibrator._resolve_role(track.name)
            if role is None:
                continue
            if role in PAN_PRESETS:
                track.pan = PAN_PRESETS[role]

    # ------------------------------------------------------------------
    # Send calibration
    # ------------------------------------------------------------------

    @staticmethod
    def _calibrate_sends(song: SongDefinition) -> None:
        """Set send_level dict for reverb/delay from SEND_PRESETS.

        Reverb is at index N (number of content tracks), Delay at N+1.
        Skips return tracks.
        """
        # Count return tracks to compute correct indices
        num_content = sum(
            1 for t in song.tracks
            if Calibrator._resolve_role(t.name) is not None
        )
        num_return = len(song.tracks) - num_content
        reverb_idx = num_content
        delay_idx = num_content + 1

        for track in song.tracks:
            role = Calibrator._resolve_role(track.name)
            if role is None:
                continue
            if role in SEND_PRESETS:
                rv, dy = SEND_PRESETS[role]
                track.send_level[reverb_idx] = rv
                track.send_level[delay_idx] = dy

    # ------------------------------------------------------------------
    # EQ calibration (ReaEQ injection)
    # ------------------------------------------------------------------

    @staticmethod
    def _calibrate_eq(song: SongDefinition) -> None:
        """Prepend a ReaEQ PluginDef with HPF/LPF params to each non-return track.

        Skips tracks without a recognized role (unknown roles keep existing plugins).
        """
        from ..reaper_builder import PLUGIN_REGISTRY

        reaeq_entry = PLUGIN_REGISTRY.get("ReaEQ")
        reaeq_path = reaeq_entry[1] if reaeq_entry else "reaeq.dll"

        for track in song.tracks:
            role = Calibrator._resolve_role(track.name)
            if role is None:
                continue
            if role not in EQ_PRESETS:
                continue

            eq_params = EQ_PRESETS[role]
            reaeq = PluginDef(
                name="ReaEQ",
                path=reaeq_path,
                index=0,
                params=dict(eq_params),
            )
            # Rewrite indices and prepend
            for p in track.plugins:
                p.index += 1
            track.plugins.insert(0, reaeq)

    # ------------------------------------------------------------------
    # Master chain upgrade
    # ------------------------------------------------------------------

    @staticmethod
    def _swap_master_chain(song: SongDefinition) -> None:
        """Replace master_plugins with Ozone 12 triplet.

        Falls back to Pro-Q_3/Pro-C_2/Pro-L_2 if any Ozone plugin is
        missing from PLUGIN_REGISTRY.
        """
        from ..reaper_builder import PLUGIN_REGISTRY

        ozone_triplet = [
            "Ozone_12_Equalizer",
            "Ozone_12_Dynamics",
            "Ozone_12_Maximizer",
        ]
        fallback_triplet = ["Pro-Q_3", "Pro-C_2", "Pro-L_2"]

        # Check Ozone availability
        all_ozone_available = all(k in PLUGIN_REGISTRY for k in ozone_triplet)

        song.master_plugins = ozone_triplet if all_ozone_available else fallback_triplet
