"""Pattern Knowledge Base — extracted from real reggaetón track analysis.

Data sourced from:
  - Ejemplo 1: 99.4 BPM, 4:07 min, 99 bars
  - Ejemplo 2: 132.5 BPM, 2:30 min, 61 bars

These patterns are NOT textbook dembow — they are DENSE DRUM LOOPS with
multiple hits per bar. The " textbook 1, 3, 4&" is a simplification.
Real tracks have kicks on almost EVERY 16th note position at some point.
"""

from __future__ import annotations
import random
from dataclasses import dataclass
from typing import TypedDict


# ---------------------------------------------------------------------------
# Weight tables — extracted from 16th-note position frequency counts
# ---------------------------------------------------------------------------

# Ejemplo 1 kick frequency (99.4 BPM, 16 bars observed)
# Position:  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
# Count:     9   6   2  10  17  10  7   8  11  12  4  14  12  9  11  8
#
# Key insight: position 4 (beat 2) is the densest hit — NOT beat 1.
# The dembow IS there: positions 0, 4, 8, 11, 12 are consistently high.
# But the PATTERN IS NOT sparse kick-snare — it's a full drum loop.

# Normalized weights (0.0-1.0) from frequency counts
KICK_WEIGHTS_99: list[float] = [
    9/17, 6/17, 2/17, 10/17, 17/17, 10/17, 7/17, 8/17,
    11/17, 12/17, 4/17, 14/17, 12/17, 9/17, 11/17, 8/17,
]
# Most frequent positions: 4, 11, 8, 12, 9
# These are beats: 2, 3&, 3, 4, 2.5& — NOT the textbook 1, 3, 4&

# Ejemplo 2 kick frequency (132.5 BPM, 61 bars)
# Position:  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
# Count:     7   6   3   5   8   9   9   2   3   9   6  11  5   2   6   8
KICK_WEIGHTS_132: list[float] = [
    7/11, 6/11, 3/11, 5/11, 8/11, 9/11, 9/11, 2/11,
    3/11, 9/11, 6/11, 11/11, 5/11, 2/11, 6/11, 8/11,
]
# Most frequent: position 11 (beat 3&), 5 (beat 1.5&), 6 (beat 2), 9 (beat 2.5&)

# Snare weights from Ejemplo 1
# Position:  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
# Count:     8   6   8  13  16  5  11  5  11  5   7  13  14  6  10  7
SNARE_WEIGHTS_99: list[float] = [
    8/16, 6/16, 8/16, 13/16, 16/16, 5/16, 11/16, 5/16,
    11/16, 5/16, 7/16, 13/16, 14/16, 6/16, 10/16, 7/16,
]
# Most frequent: position 4 (beat 2), 12 (beat 4), 3 (beat 1&), 11 (beat 3&)

# Hihat weights from Ejemplo 1
# Position:  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
# Count:    12  7  14  11  17  4  12  6  11  6  12  15  14  5  13  9
HIHAT_WEIGHTS_99: list[float] = [
    12/17, 7/17, 14/17, 11/17, 17/17, 4/17, 12/17, 6/17,
    11/17, 6/17, 12/17, 15/17, 14/17, 5/17, 13/17, 9/17,
]
# Most frequent: position 4 (beat 2), 11 (beat 3&), 2 (beat 1&), 12 (beat 4)


# ---------------------------------------------------------------------------
# Section energy levels — extracted from amplitude analysis
# ---------------------------------------------------------------------------

# From Ejemplo 1 amplitude data:
# - BUILD/PRE-CHORUS: -10.6 dB to -6.6 dB → energy 0.5-0.7
# - VERSE/BRIDGE: -13.2 dB to -14.1 dB → energy 0.3-0.4
# - CHORUS/DROP: -2.0 dB to -4.4 dB → energy 0.9-1.0
# - GAP/BREAK (bar 75-76): -49.4 dB to -53.5 dB → energy 0.0-0.05
# - INTRO/FILTER: -27.8 dB → filtered, building up

SECTION_ENERGY: dict[str, float] = {
    "intro":    0.3,   # filtered, building
    "verse":    0.35,  # low energy, stripped back
    "build":    0.6,   # medium energy, all elements
    "pre_chorus": 0.65, # slightly higher than verse
    "chorus":   0.95,  # maximum energy
    "drop":     1.0,   # peak loudness
    "break":    0.05,  # near silence (gap technique)
    "bridge":   0.4,   # transition, moderate
    "outro":    0.35,  # wind down
}


# ---------------------------------------------------------------------------
# Section template — real structure from Ejemplo 1
# ---------------------------------------------------------------------------
# 16 bars BUILD → 2 bars VERSE → 47 bars BUILD → 1 bar VERSE →
# 1 bar GAP (-27.8dB) → 1 bar DROP (-2.6dB) → 1 bar DROP (-2.0dB) →
# 5 bars BUILD → 1 bar BREAK (-49.4dB) → 1 bar BREAK (-53.5dB) →
# 1 bar DROP (-4.4dB) → 12 bars BUILD → 2 bars VERSE → 8 bars BUILD
#
# Key insight: The GAP/BREAK followed by LOUD DROP is a signature technique.
# 1-2 bars of near-silence creates huge contrast for the drop.

SECTION_TEMPLATE: list[dict] = [
    {"name": "build",       "bars": 16, "energy": 0.6},
    {"name": "verse",       "bars": 2,  "energy": 0.35},
    {"name": "build",       "bars": 47, "energy": 0.65},
    {"name": "verse",       "bars": 1,  "energy": 0.35},
    {"name": "gap",         "bars": 1,  "energy": 0.05},
    {"name": "drop",        "bars": 2,  "energy": 1.0},
    {"name": "build",       "bars": 5,  "energy": 0.7},
    {"name": "break",       "bars": 2,  "energy": 0.05},
    {"name": "drop",        "bars": 1,  "energy": 0.95},
    {"name": "build",       "bars": 12, "energy": 0.65},
    {"name": "verse",       "bars": 2,  "energy": 0.35},
    {"name": "build",       "bars": 8,  "energy": 0.6},
]


# ---------------------------------------------------------------------------
# Pattern bank entries — per-style variants
# ---------------------------------------------------------------------------

@dataclass
class PatternEntry:
    """A single note entry in a pattern."""
    pos: float       # 16th-note position (0.0-3.75 for one bar)
    vel: int         # base velocity
    len: float = 0.25  # note length in beats


# Classic dembow — the canonical reggaetón pattern
# NOT the textbook 1, 3, 4& — this is a DENSE drum loop pattern
# Extracted from frequency analysis: kicks at 0, 3, 4, 5, 7, 8, 9, 11, 12
DEMBOW_CLASSICO: list[PatternEntry] = [
    # Beat 1 area (positions 0, 1, 2, 3)
    PatternEntry(pos=0.0,  vel=110),
    PatternEntry(pos=3.0,  vel=95),   # beat 3
    PatternEntry(pos=4.0,  vel=100),  # beat 2 — THE densest position
    PatternEntry(pos=5.0,  vel=85),
    PatternEntry(pos=7.0,  vel=90),
    # Beat 3 area (positions 8, 9, 10, 11)
    PatternEntry(pos=8.0,  vel=100),  # beat 3
    PatternEntry(pos=9.0,  vel=90),
    PatternEntry(pos=11.0, vel=105),  # beat 3& — THE signature dembow
    PatternEntry(pos=12.0, vel=95),   # beat 4
]


# Perreo style — more aggressive, denser kicks at positions 5, 6 (beat 1.5&, 2)
PERREO_STYLE: list[PatternEntry] = [
    PatternEntry(pos=0.0,  vel=115),
    PatternEntry(pos=3.0,  vel=100),
    PatternEntry(pos=4.0,  vel=105),  # beat 2 — denser
    PatternEntry(pos=5.0,  vel=95),   # beat 2& — perreo accent
    PatternEntry(pos=6.0,  vel=90),   # beat 2
    PatternEntry(pos=7.0,  vel=85),
    PatternEntry(pos=8.0,  vel=105),
    PatternEntry(pos=9.0,  vel=90),
    PatternEntry(pos=11.0, vel=110),  # beat 3& — strong
    PatternEntry(pos=12.0, vel=100),
    PatternEntry(pos=13.0, vel=85),
]


# Trápico — half-time feel, fewer kicks
TRAPICO_STYLE: list[PatternEntry] = [
    PatternEntry(pos=0.0,  vel=110),
    PatternEntry(pos=4.0,  vel=100),  # beat 2 only
    PatternEntry(pos=8.0,  vel=105),
    PatternEntry(pos=12.0, vel=95),
]


# Denser variant — closer to real track analysis
# Uses positions with highest frequency counts: 4, 11, 8, 12, 9
DENSE_VARIANT: list[PatternEntry] = [
    PatternEntry(pos=0.0,  vel=100),
    PatternEntry(pos=3.0,  vel=90),
    PatternEntry(pos=4.0,  vel=115),  # beat 2 — densest hit
    PatternEntry(pos=5.0,  vel=90),
    PatternEntry(pos=6.0,  vel=85),
    PatternEntry(pos=7.0,  vel=80),
    PatternEntry(pos=8.0,  vel=105),  # beat 3
    PatternEntry(pos=9.0,  vel=90),
    PatternEntry(pos=10.0, vel=80),
    PatternEntry(pos=11.0, vel=110),  # beat 3&
    PatternEntry(pos=12.0, vel=100),  # beat 4
    PatternEntry(pos=13.0, vel=85),
    PatternEntry(pos=14.0, vel=90),
]


PATTERN_BANKS: dict[str, list[PatternEntry]] = {
    "dembow_classico": DEMBOW_CLASSICO,
    "perreo": PERREO_STYLE,
    "trapico": TRAPICO_STYLE,
    "dense": DENSE_VARIANT,
}


# ---------------------------------------------------------------------------
# Snare patterns — follow dembow positions
# ---------------------------------------------------------------------------

SNARE_DEMBOW_CLASSICO: list[PatternEntry] = [
    PatternEntry(pos=3.0,  vel=100),  # beat 1&
    PatternEntry(pos=4.0,  vel=110),  # beat 2 — primary
    PatternEntry(pos=11.0, vel=105),  # beat 3& — THE signature
    PatternEntry(pos=12.0, vel=95),   # beat 4
]


# ---------------------------------------------------------------------------
# Hihat patterns — 16th-note grid with dembow accents
# ---------------------------------------------------------------------------

HIHAT_DEMBOW_CLASSICO: list[PatternEntry] = [
    # All 16ths with accents on dembow positions
    PatternEntry(pos=0.0,  vel=85),   # beat 1
    PatternEntry(pos=0.25, vel=55),
    PatternEntry(pos=0.5,  vel=60),
    PatternEntry(pos=0.75, vel=55),
    PatternEntry(pos=1.0,  vel=80),   # beat 2
    PatternEntry(pos=1.25, vel=55),
    PatternEntry(pos=1.5,  vel=60),
    PatternEntry(pos=1.75, vel=55),
    PatternEntry(pos=2.0,  vel=85),   # beat 3
    PatternEntry(pos=2.25, vel=55),
    PatternEntry(pos=2.5,  vel=80),   # beat 3& — accent
    PatternEntry(pos=2.75, vel=55),
    PatternEntry(pos=3.0,  vel=70),   # beat 3
    PatternEntry(pos=3.25, vel=55),
    PatternEntry(pos=3.5,  vel=75),   # beat 4&
    PatternEntry(pos=3.75, vel=55),
]


# Offbeat hihat — for breakdowns/intervals
HIHAT_OFFBEAT: list[PatternEntry] = [
    PatternEntry(pos=0.5,  vel=75),
    PatternEntry(pos=1.5,  vel=70),
    PatternEntry(pos=2.5,  vel=75),
    PatternEntry(pos=3.5,  vel=70),
]


# ---------------------------------------------------------------------------
# Chord progression — i-VI-III-VII from real track analysis
# ---------------------------------------------------------------------------

CHORD_PROGRESSION_MINOR: list[tuple[int, list[int]]] = [
    # (root_semitone_offset, [intervals from root])
    (0,  [0, 3, 7]),   # i (Am): root, minor 3rd, perfect 5th
    (8,  [0, 4, 7]),   # VI (F): root, major 3rd, perfect 5th
    (4,  [0, 3, 7]),   # III (C): root, minor 3rd, perfect 5th
    (10, [0, 4, 7]),   # VII (G): root, major 3rd, perfect 5th
]


# ---------------------------------------------------------------------------
# Bass pattern — follows kick positions with tresillo grouping
# ---------------------------------------------------------------------------

BASS_TRESILLO_POSITIONS: list[float] = [
    0.0, 0.75, 1.5,   # first group of 3
    2.25, 3.0, 3.75,  # second group of 3
]


# ---------------------------------------------------------------------------
# Section-aware generator presets
# ---------------------------------------------------------------------------

SECTION_GENERATOR_MAP: dict[str, dict[str, str]] = {
    "intro": {
        "kick": "dembow_classico",
        "snare": "dembow_classico",
        "hihat": "offbeat",
    },
    "verse": {
        "kick": "dembow_classico",
        "snare": "dembow_classico",
        "hihat": "dembow_classico",
    },
    "build": {
        "kick": "dense",
        "snare": "dembow_classico",
        "hihat": "dembow_classico",
    },
    "pre_chorus": {
        "kick": "dense",
        "snare": "dembow_classico",
        "hihat": "dembow_classico",
    },
    "chorus": {
        "kick": "dense",
        "snare": "dembow_classico",
        "hihat": "dembow_classico",
    },
    "drop": {
        "kick": "dense",
        "snare": "dembow_classico",
        "hihat": "dembow_classico",
    },
    "break": {
        "kick": "offbeat",
        "snare": "offbeat",
        "hihat": "offbeat",
    },
    "gap": {
        "kick": "offbeat",
        "snare": "offbeat",
        "hihat": "offbeat",
    },
    "bridge": {
        "kick": "dembow_classico",
        "snare": "dembow_classico",
        "hihat": "offbeat",
    },
    "outro": {
        "kick": "trapico",
        "snare": "dembow_classico",
        "hihat": "dembow_classico",
    },
}


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

def get_pattern_bank(name: str) -> list[PatternEntry]:
    """Return the pattern bank by name."""
    return PATTERN_BANKS.get(name, DEMBOW_CLASSICO)


def get_section_energy(section_name: str) -> float:
    """Get energy level for a section type."""
    return SECTION_ENERGY.get(section_name.lower(), 0.5)


def get_section_generator_map(section_name: str) -> dict[str, str]:
    """Get pattern bank assignments for a section type."""
    return SECTION_GENERATOR_MAP.get(section_name.lower(), SECTION_GENERATOR_MAP["verse"])


# ---------------------------------------------------------------------------
# Structure Templates — genre-aware section arrangement patterns
# ---------------------------------------------------------------------------

# Templates are lists of section names that define the high-level arrangement.
# Bar counts and energy levels are randomized within bounds when generated.
SECTION_TEMPLATES: dict[str, list[str]] = {
    "extracted_real_tracks": [
        "build", "verse", "build", "verse", "gap", "drop", "build", "break",
        "drop", "build", "verse", "build",
    ],
    "standard": [
        "intro", "verse", "chorus", "verse", "chorus", "bridge", "chorus", "outro",
    ],
    "with_pre_chorus": [
        "intro", "verse", "pre_chorus", "chorus", "verse", "pre_chorus", "chorus", "outro",
    ],
    "breakdown_drop": [
        "intro", "verse", "chorus", "break", "drop", "verse", "chorus", "outro",
    ],
}

# Default bar count and energy per section type (used as baseline for randomization)
_SECTION_DEFAULTS: dict[str, tuple[int, float]] = {
    "intro":      (4,  0.3),
    "verse":      (8,  0.35),
    "build":      (16, 0.6),
    "pre_chorus": (4,  0.65),
    "chorus":     (8,  0.95),
    "drop":       (4,  1.0),
    "break":      (2,  0.05),
    "gap":        (2,  0.05),
    "bridge":     (8,  0.4),
    "outro":      (8,  0.35),
}


def generate_structure(
    genre_config: dict,
    bpm: float,
    key: str,
    seed: int | None = None,
) -> list[dict]:
    """Generate section structure from template with randomization.

    Args:
        genre_config: Loaded genre JSON dict
        bpm: BPM for potential future use
        key: Musical key for potential future use
        seed: Random seed for reproducibility (default: None = unseeded).
              When set, reseeds the global random state.

    Returns:
        List of section dicts with 'name', 'bars', 'energy' keys.
        Each call without seed produces different bar counts and energy levels.
    """
    if seed is not None:
        random.seed(seed)

    templates_dict = genre_config.get("structure", {}).get("templates", SECTION_TEMPLATES)
    template_names = list(templates_dict.keys())
    template_key = random.choice(template_names)
    template_sections = templates_dict[template_key]

    sections: list[dict] = []
    for s in template_sections:
        # Handle both string names (from SECTION_TEMPLATES) and dicts (from JSON)
        if isinstance(s, str):
            name = s
            base_bars, base_energy = _SECTION_DEFAULTS.get(name, (4, 0.5))
        else:
            name = s.get("name", "unknown")
            base_bars = s.get("bars", 4)
            base_energy = s.get("energy", 0.5)

        # Randomize bar counts ±2, energy ±0.1
        bars = max(1, base_bars + random.randint(-2, 2))
        energy = max(0.0, min(1.0, base_energy + random.uniform(-0.1, 0.1)))
        sections.append({"name": name, "bars": bars, "energy": round(energy, 2)})

    return sections
