"""Accidental — chromatic alteration as SI subclass."""

from __future__ import annotations

from flexohr.paradigms.pitchspace.abstract import Octaves
from flexohr.paradigms.pitchspace.interval import SpecificInterval as SI
from flexohr.paradigms.pitchspace.interval import SpecificIntervalClass as SIC

_SHARP = "\u266f"  # ♯
_FLAT = "\u266d"  # ♭
_NATURAL = "\u266e"  # ♮


class Accidental(SI):
    """Chromatic alteration as a specific interval with octave 0.

    Invariant: the octave component is always ``Octaves(0)``.
    The value on the line of fifths is ``n * 7`` where *n* is the number
    of sharps (positive) or flats (negative).
    """

    @staticmethod
    def convert_init_value(value):
        if isinstance(value, SI):
            return SIC(value.interval_class.fifths), Octaves(0)
        if isinstance(value, SIC):
            return SIC(value.fifths), Octaves(0)
        if isinstance(value, tuple):
            return SIC(value[0]), Octaves(0)
        if isinstance(value, int):
            return SIC(value), Octaves(0)
        raise TypeError(
            f"Accidental cannot be constructed from {type(value).__name__}. "
            f"Use Accidental.from_int(), .from_string(), or pass an SI/SIC."
        )

    @classmethod
    def from_int(cls, n: int) -> Accidental:
        """Create from a count of sharps (positive) or flats (negative)."""
        return cls(SIC(n * 7))

    @classmethod
    def from_string(cls, s: str) -> Accidental:
        """Parse accidental string.

        Accepted: ``♯``, ``♭``, ``♮``, ``#``, ``b``, ``-`` (music21),
        and multiples (``##``, ``bb``, ``--``, ``♯♯``, ``♭♭``).
        """
        if not s or s == _NATURAL:
            return cls.from_int(0)
        if all(c in ("#", _SHARP) for c in s):
            return cls.from_int(len(s))
        if all(c in ("b", _FLAT) for c in s):
            return cls.from_int(-len(s))
        if all(c == "-" for c in s):
            return cls.from_int(-len(s))
        raise ValueError(f"Cannot parse accidental string: {s!r}")

    @property
    def n(self) -> int:
        """Count of sharps (positive) or flats (negative)."""
        return self.interval_class.fifths // 7

    def to_string(self) -> str:
        """Unicode accidental string."""
        n = self.n
        if n > 0:
            return _SHARP * n
        if n < 0:
            return _FLAT * -n
        return _NATURAL

    def __repr__(self):
        return f"acc({self.to_string()})"

    def __str__(self):
        return f"acc({self.to_string()})"

    def __hash__(self):
        return hash(("Accidental", self.interval_class.fifths))

    def __eq__(self, other):
        if isinstance(other, Accidental):
            return self.interval_class.fifths == other.interval_class.fifths
        return NotImplemented

    def __add__(self, other):
        if isinstance(other, Accidental):
            return Accidental.from_int(self.n + other.n)
        return super().__add__(other)

    def __sub__(self, other):
        if isinstance(other, Accidental):
            return Accidental.from_int(self.n - other.n)
        return super().__sub__(other)

    def __neg__(self):
        return Accidental.from_int(-self.n)

    def __radd__(self, other):
        return NotImplemented


Accidental.NATURAL = Accidental.from_int(0)
Accidental.SHARP = Accidental.from_int(1)
Accidental.FLAT = Accidental.from_int(-1)
Accidental.DOUBLE_SHARP = Accidental.from_int(2)
Accidental.DOUBLE_FLAT = Accidental.from_int(-2)
Accidental.TRIPLE_SHARP = Accidental.from_int(3)
Accidental.TRIPLE_FLAT = Accidental.from_int(-3)
