"""Lehrplan: frequenz-getakteter, alters-gestaffelter serbischer Kernwortschatz.

Datensatz + Alters-Bänder stammen aus der Forschungs-Spec `plappi-learning-engine`
(210 Wörter Tier 1–6 + Satz-Bausteine, `curriculum_data.json` / `learning_spec.json`).
Dieses Modul ist rein (keine DB): es liefert die alters-adaptive Fokus-Wort-Auswahl
für die Lern-Engine. „WAS gelehrt wird" — das „WIE" macht die Persona (profile.py).
"""
from __future__ import annotations

import json

from . import config

_DATA = config.BASE_DIR / "backend" / "curriculum_data.json"
_SPEC = config.BASE_DIR / "backend" / "learning_spec.json"

# Fallback-Bänder, falls die Spec-Datei fehlt (Werte aus der Forschungs-Spec).
_FALLBACK_BANDS = [
    {"name": "toddler", "age_min": 1.5, "age_max": 3, "max_tier": 1,
     "focus_types": ["greeting", "noun", "pronoun", "function", "number"],
     "method": "Benenn-Spiel, Lieder, Tiergeräusche; ein serbisches Wort pro Turn, stark wiederholt.",
     "new_words_per_session": 2, "review_words_per_session": 4, "sentence_building": False},
    {"name": "preschool", "age_min": 3, "age_max": 5, "max_tier": 3,
     "focus_types": ["noun", "verb", "adjective", "pronoun", "function", "question", "number", "feeling", "phrase", "adverb"],
     "method": "Einfache Fragen, Lieder + kurze Geschichten, 2-4-Wort-Phrasen, implizit.",
     "new_words_per_session": 4, "review_words_per_session": 7, "sentence_building": True},
    {"name": "school", "age_min": 5, "age_max": 10, "max_tier": 4,
     "focus_types": ["verb", "noun", "adjective", "adverb", "function", "question", "feeling"],
     "method": "Konversation: beschreiben, Mini-Geschichten, eigene Fragen; volle einfache Sätze.",
     "new_words_per_session": 6, "review_words_per_session": 10, "sentence_building": True},
    {"name": "teen", "age_min": 10, "age_max": 15, "max_tier": 5,
     "focus_types": ["verb", "noun", "adjective", "adverb", "function", "abstract"],
     "method": "Themen vom Kind; erste explizite Grammatik-Muster leicht benannt.",
     "new_words_per_session": 8, "review_words_per_session": 12, "sentence_building": True},
    {"name": "young_adult", "age_min": 15, "age_max": 99, "max_tier": 6,
     "focus_types": ["verb", "noun", "adjective", "adverb", "function", "abstract"],
     "method": "Konversationspartner + Tutor: freie Themen, Grammatik on demand.",
     "new_words_per_session": 12, "review_words_per_session": 15, "sentence_building": True},
]


def _load():
    items, frames, bands = [], [], []
    try:
        d = json.loads(_DATA.read_text(encoding="utf-8"))
        items, frames = d.get("items", []), d.get("sentence_frames", [])
    except Exception as e:  # noqa: BLE001
        print(f"[curriculum] konnte {_DATA} nicht laden: {e}", flush=True)
    try:
        bands = json.loads(_SPEC.read_text(encoding="utf-8")).get("age_bands", [])
    except Exception:
        bands = []
    return items, frames, (bands or _FALLBACK_BANDS)


ITEMS, FRAMES, AGE_BANDS = _load()

_COLOR_DE = {"rot", "blau", "gelb", "grün", "gruen", "weiß", "weiss", "schwarz",
             "grau", "braun", "rosa", "orange", "lila", "violett", "bunt"}


def lemma_of(item: dict) -> str:
    return (item.get("lemma") or item.get("sr") or "").strip().lower()


def cluster_of(item: dict) -> str:
    """Grobes semantisches Cluster fürs Interleaving (Farben/Tiere/Zahlen trennen)."""
    t = item.get("type") or "misc"
    if t == "number":
        return "number"
    if t == "greeting":
        return "greeting"
    de = (item.get("de") or "").lower()
    if t == "adjective" and any(c in de for c in _COLOR_DE):
        return "color"
    return t


BY_LEMMA: dict[str, dict] = {}
for _it in ITEMS:
    BY_LEMMA.setdefault(lemma_of(_it), _it)


def age_band(age) -> dict:
    """Bänder sind nach age_min aufsteigend; an einer Grenze (z.B. exakt 3 J.)
    gewinnt das HÖHERE Band (mehr Wörter, Satzbau) — nimm das letzte mit age_min<=age."""
    age = age or 3
    chosen = AGE_BANDS[0]
    for b in AGE_BANDS:
        if b.get("age_min", 0) <= age:
            chosen = b
    return chosen


def target_vocab_str(limit: int | None = None) -> str:
    """`lemma = glosse [tX]`-Liste für den Transkript-Analyse-Prompt."""
    its = ITEMS[:limit] if limit else ITEMS
    return "\n".join(f"{lemma_of(i)} = {i.get('de','')} [t{i.get('tier')}]" for i in its)


def frames_for(age) -> list[dict]:
    b = age_band(age)
    mt = b.get("max_tier", 3)
    return [f for f in FRAMES if f.get("tier", 1) <= mt and f.get("age_min", 0) <= (age or 3)]


def select_focus(age, mastery_rows: list[dict], today_days: int,
                 recent_new_clusters=(), max_tier_unlocked: int | None = None) -> dict:
    """Fokus-Set dieser Sitzung: Wiederholungen zuerst (fällig), dann neue Wörter.
    Schließt gemeisterte/zurückgezogene Wörter aus. Tier/Typ/Alter-gegated, Cluster-interleaved.
    """
    b = age_band(age)
    max_tier = b.get("max_tier", 3)
    if max_tier_unlocked:                       # Fluency-Fast-Path kann Tiers freischalten
        max_tier = max(max_tier, max_tier_unlocked)
    ftypes = set(b.get("focus_types", []))
    n_rev = b.get("review_words_per_session", 6)
    n_new = b.get("new_words_per_session", 4)
    tracked = {m["lemma"] for m in mastery_rows}

    # (1) REVIEW-POOL: fällige Wörter im Lernprozess, overdue-first dann box aufsteigend
    def overdue(m):
        nd = m.get("next_due_days")
        return (today_days - nd) if nd is not None else 0
    review = [m for m in mastery_rows
              if m.get("state") in ("receptive", "practising", "productive")
              and (m.get("next_due_days") is None or m["next_due_days"] <= today_days)]
    review.sort(key=lambda m: (-overdue(m), m.get("box", 0)))
    review_sel = review[:n_rev]

    # (2) NEW-POOL: noch nicht getrackt, Tier/Typ/Alter-gegated, frequenz-sortiert
    new_pool = [i for i in ITEMS
                if lemma_of(i) not in tracked
                and i.get("tier", 99) <= max_tier
                and (i.get("type") in ftypes if ftypes else True)
                and i.get("age_min", 0) <= (age or 3)]
    new_pool.sort(key=lambda i: (i.get("tier", 9), i.get("priority", 999)))

    # (3) Cluster-Interleave: keine zwei gleichen Cluster zu dicht
    chosen, used = [], list(recent_new_clusters)
    gap = config.MIN_GAP_SAME_CLUSTER_NEW
    for i in new_pool:
        if len(chosen) >= n_new:
            break
        cl = cluster_of(i)
        if cl in used[-gap:]:
            continue
        chosen.append(i)
        used.append(cl)
    if len(chosen) < n_new:                     # auffüllen, falls Gating zu streng war
        for i in new_pool:
            if len(chosen) >= n_new:
                break
            if i not in chosen:
                chosen.append(i)

    focus = []
    for m in review_sel:
        it = BY_LEMMA.get(m["lemma"])
        if it:
            focus.append({"sr": it["sr"], "de": it.get("de", ""), "lemma": m["lemma"],
                          "kind": "review", "state": m.get("state")})
    for i in chosen:
        focus.append({"sr": i["sr"], "de": i.get("de", ""), "lemma": lemma_of(i), "kind": "new"})

    return {"band": b, "focus": focus, "new_clusters": [cluster_of(i) for i in chosen],
            "frames": frames_for(age) if b.get("sentence_building") else []}
