"""
Extract BPM and musical key from sample filenames.

Covers naming conventions across multiple sample libraries:
- "98bpm yera drumloop.wav"
- "@16bloody - 98bpm vente .wav"
- "Midilatino_Sativa_A_Min_94BPM_Lead.wav"
- "SS_RNBL_Amor_Music_89_F_maj.wav"
- "90bpm reggaeton antiguo drumloop.wav"
- "(extra) 100bpm pop drumloop.wav"
- "Midilatino_Cupid_G#m_140BPM_Bass.wav"
- "LOOP 31 92bpm @dastin.prod.wav"
"""

import re
from typing import Optional, Tuple
from pathlib import Path

_NOTE_MAP = {
    "c": 0, "c#": 1, "db": 1, "d": 2, "d#": 3, "eb": 3,
    "e": 4, "f": 5, "f#": 6, "gb": 6, "g": 7, "g#": 8,
    "ab": 8, "a": 9, "a#": 10, "bb": 10, "b": 11,
}

_KEY_ALIASES = {
    "cm": "Cm", "c#m": "C#m", "dbm": "Cm", "dm": "Dm", "ebm": "D#m",
    "em": "Em", "fm": "Fm", "f#m": "F#m", "gbm": "F#m", "gm": "Gm",
    "g#m": "G#m", "abm": "G#m", "am": "Am", "a#m": "A#m", "bbm": "A#m", "bm": "Bm",
    "cmin": "Cm", "c#min": "C#m", "dmin": "Dm", "emin": "Em",
    "fmin": "Fm", "f#min": "F#m", "gmin": "Gm", "g#min": "G#m",
    "amin": "Am", "bmin": "Bm", "ebmin": "D#m", "bbmin": "A#m",
    "dbmajor": "C#Maj", "ebmajor": "D#Maj",
}


def parse_bpm(filename: str) -> Optional[float]:
    """Extract BPM from a filename. Returns None if not found."""
    name = Path(filename).stem
    patterns = [
        re.compile(r"(\d{2,3})\s*bpm", re.IGNORECASE),
        re.compile(r"bpm\s*(\d{2,3})", re.IGNORECASE),
        re.compile(r"[_\s](\d{2,3})[_\s]", re.IGNORECASE),
        re.compile(r"(\d{2,3})BPM", re.IGNORECASE),
    ]
    for pat in patterns:
        m = pat.search(name)
        if m:
            val = float(m.group(1))
            if 40.0 <= val <= 300.0:
                return val
    nums = re.findall(r"(\d{2,3})", name)
    for n in nums:
        val = float(n)
        if 60.0 <= val <= 200.0:
            likely = any(kw in name.lower() for kw in [
                "bpm", "loop", "beat", "drum", "groove", "perc"
            ])
            if likely:
                return val
    return None


def _normalize_key(note: str, quality: str) -> Optional[str]:
    note_lower = note.lower().replace("\u266f", "#").replace("\u266d", "b")
    semitone = _NOTE_MAP.get(note_lower)
    if semitone is None:
        return None
    for name, val in _NOTE_MAP.items():
        if val == semitone:
            if len(name) == 1:
                root = name.upper()
            else:
                root = name[0].upper() + name[1:]
            break
    else:
        root = note
    return f"{root}m" if quality == "minor" else f"{root}Maj"


def parse_key(filename: str) -> Optional[str]:
    """Extract musical key from a filename. Returns 'Am', 'C#m', 'FMaj', etc."""
    name = Path(filename).stem

    # Pattern 1: Note_Quality separated by underscores/dashes/dots
    # Examples: A_Min, G#_Maj, F#_Min, C_minor, D#_m, E_maj
    m = re.search(
        r"[_\s\-\.]([A-Ga-g][#.\u266f\u266d]?)[_\s\-\.](Min|Maj|Major|Minor|min|maj|m|minor)[_\s\-\.]",
        name, re.IGNORECASE
    )
    if m:
        note = m.group(1)
        quality_raw = m.group(2).lower()
        quality = "minor" if quality_raw.startswith("min") or quality_raw == "m" else "major"
        return _normalize_key(note, quality)

    # Pattern 2: Compact form like Am, C#m, Gm, BbMaj
    m = re.search(r"[_\s\-\.]([A-Ga-g][#.\u266f\u266d]?)(m|min|Maj|major|minor)[_\s\-\.Bb\d]",
                  name, re.IGNORECASE)
    if m:
        note = m.group(1)
        quality_raw = m.group(2).lower()
        quality = "minor" if quality_raw.startswith("m") and quality_raw != "maj" else "major"
        if quality_raw in ("m", "min", "minor"):
            quality = "minor"
        return _normalize_key(note, quality)

    # Pattern 3: _Cmin, _F#min, _G#m (no separator after quality)
    m = re.search(r"[_\s\-\.]([A-Ga-g][#.\u266f\u266d]?)(m|min|Maj|major|minor)(?:[_\s\-\.]|BPM|bpm|$)",
                  name, re.IGNORECASE)
    if m:
        note = m.group(1)
        quality_raw = m.group(2).lower()
        quality = "minor" if quality_raw in ("m", "min", "minor") else "major"
        return _normalize_key(note, quality)

    # Pattern 4: SS_RNBL style - _F_maj, _C_min, _D#_Min
    m = re.search(r"[_\s\-]([A-Ga-g][#.\u266f\u266d]?)_(maj|min|m|Maj|Min)[_\s\-\.]",
                  name, re.IGNORECASE)
    if m:
        note = m.group(1)
        quality_raw = m.group(2).lower()
        quality = "minor" if quality_raw in ("min", "m") else "major"
        return _normalize_key(note, quality)

    # Pattern 5: Bare note name (less reliable, major by default)
    m = re.search(r"[_\s]([A-Ga-g][#.\u266f\u266d]?)[_\s\-\.]", name)
    if m:
        bare = m.group(1)
        root_lower = bare.lower().replace("\u266f", "#").replace("\u266d", "b")
        if root_lower in _NOTE_MAP and len(bare) <= 2:
            return _normalize_key(bare, "major")

    return None


def parse_sample_metadata(filename: str) -> dict:
    return {
        "bpm": parse_bpm(filename),
        "key": parse_key(filename),
    }