"""Scale — OHR subclass for the pitchspace paradigm."""

from __future__ import annotations

import functools
from dataclasses import dataclass
from typing import Any

from flexohr.core.component import Component
from flexohr.core.ohr import OHR
from flexohr.harmony.chord_tables import COLLECTION_TYPE_INTERVALS
from flexohr.harmony.enums import props as _props
from flexohr.harmony.harmony_enums import CollectionType
from flexohr.paradigms.pitchspace.accidental import Accidental
from flexohr.paradigms.pitchspace.interval import GenericIntervalClass as GIC
from flexohr.paradigms.pitchspace.interval import SpecificIntervalClass as SIC
from flexohr.paradigms.pitchspace.scale_degrees import (
    GSD,
    GenericScaleDegree,
    SpecificScaleDegree,
    SpecificScaleDegreeInterval,
)


@dataclass(frozen=True, slots=True)
class Scale(OHR):
    """An OHR whose ordered, horizontal body consists of SD components."""

    @classmethod
    def from_collection_type(cls, collection_type: CollectionType, root: Any = None) -> Scale:
        intervals = COLLECTION_TYPE_INTERVALS[collection_type]
        body = tuple(
            Component(
                SpecificScaleDegree(
                    degree=GIC(i),
                    accidental=Accidental.NATURAL,
                    sic=sic,
                    collection_type=collection_type,
                )
            )
            for i, sic in enumerate(intervals)
        )
        ref_comp = Component(root) if root is not None else None
        return cls(
            reference_component=ref_comp,
            reference_ohr=None,
            body=body,
            ordered=True,
            horizontal=True,
            properties=_props(collection_type),
        )

    @property
    def collection_type(self) -> CollectionType | None:
        return self.get_property("collection_type", default=None)

    def get_scale_degree(self, selector: GSD | GIC | int | SpecificScaleDegreeInterval) -> SpecificScaleDegree:
        if isinstance(selector, int) and not isinstance(selector, GIC):
            return self._resolve_gsd(GenericScaleDegree(selector, Accidental.NATURAL))
        if isinstance(selector, GIC):
            return self._resolve_gsd(GenericScaleDegree(degree=selector, accidental=Accidental.NATURAL))
        if isinstance(selector, GenericScaleDegree):
            return self._resolve_gsd(selector)
        if isinstance(selector, SpecificScaleDegreeInterval):
            return self._resolve_gsd(GenericScaleDegree(degree=selector.degree, accidental=selector.accidental))
        raise TypeError(f"get_scale_degree() accepts int, GIC, GSD, or SDI, got {type(selector).__name__}")

    def _resolve_gsd(self, gsd: GenericScaleDegree) -> SpecificScaleDegree:
        ct = self.collection_type
        idx = int(gsd.degree) % len(self.body)
        diatonic_sd = self.body[idx].value
        diatonic_sic = diatonic_sd.sic
        target_sic = SIC(diatonic_sic.fifths + gsd.accidental.interval_class.fifths)
        return SpecificScaleDegree(
            degree=GIC(idx),
            accidental=gsd.accidental,
            sic=target_sic,
            collection_type=ct,
        )

    def make_scale_degree(self, sic: SIC) -> SpecificScaleDegree:
        """Given an SIC, compute the SD with the correct degree and accidental."""
        target_gic = GIC(sic.steps)
        diatonic_sic = self.get_scale_degree(target_gic).sic
        accidental = Accidental(sic - diatonic_sic)
        return SpecificScaleDegree(
            degree=target_gic,
            accidental=accidental,
            sic=sic,
            collection_type=self.collection_type,
        )

    def contains_sic(self, sic: SIC) -> bool:
        """Whether the SIC corresponds to a diatonic (0-accidental) degree."""
        target_gic = GIC(sic.steps)
        return self.get_scale_degree(target_gic).sic.fifths == sic.fifths


@functools.lru_cache(maxsize=None)
def _get_scale_cached(collection_type: CollectionType) -> Scale:
    return Scale.from_collection_type(collection_type)


def get_scale(collection_type: CollectionType, reference_component: Any = None) -> Scale:
    """Scale factory. Cached when *reference_component* is ``None``."""
    if reference_component is None:
        return _get_scale_cached(collection_type)
    return Scale.from_collection_type(collection_type, root=reference_component)
