"""Sprache – Text in Blase; Monster-Geräusche leise und selten."""

from __future__ import annotations

import random
import re
import threading
import time
from collections.abc import Callable

from jam.brain import JamState
from jam import voice_assets
from jam.owner import address, owner_name

_lock = threading.Lock()
_main_speaker: Callable[[str, int, bool], None] | None = None
_last_sound_at: float = 0.0

# Pause zwischen verschiedenen Auto-Sätzen
MIN_LINE_GAP_SEC = 55.0
# Derselbe Satz (auch in Blase) mindestens so lange nicht wieder
REPEAT_SAME_LINE_SEC = 600.0
# Abwärtskompatibel
SPEAK_PAUSE_SEC = MIN_LINE_GAP_SEC

# Wie oft Jam wirklich spricht (Text in Blase immer; Sound öfter bei echtem TTS)
MIN_SOUND_GAP_SEC = 28.0
# Singen zur Musik – öfter als normale Auto-Sätze
MUSIC_SING_GAP_SEC = 24.0
CHANCE_QUIET = 0.22
CHANCE_LOUD = 0.55
CHANCE_USER = 0.75
CHANCE_AFTER_LONG_GAP = 0.4


def set_main_thread_speaker(fn: Callable[[str, int, bool], None] | None) -> None:
    """JamWindow registriert Sprache auf dem Qt-Hauptthread (Wiedergabe hört man)."""
    global _main_speaker
    _main_speaker = fn


def cute_sound() -> None:
    if should_play_sound(False):
        voice_assets.play_baby_sound()


def should_play_sound(
    loud: bool,
    *,
    force: bool = False,
    user_called: bool = False,
) -> bool:
    """Sehr selten ein Geräusch – mindestens MIN_SOUND_GAP_SEC Pause."""
    global _last_sound_at
    if force:
        _last_sound_at = time.time()
        return True
    now = time.time()
    gap = now - _last_sound_at
    if gap < MIN_SOUND_GAP_SEC:
        chance = 0.02
    elif user_called:
        chance = CHANCE_USER
    elif loud:
        chance = CHANCE_LOUD
    else:
        chance = CHANCE_QUIET
    if gap > MIN_SOUND_GAP_SEC * 2:
        chance = max(chance, CHANCE_AFTER_LONG_GAP)
    if random.random() < chance:
        _last_sound_at = now
        return True
    return False


def mark_sound_played() -> None:
    """Nach Furz/Lachen: Sound-Pause zurücksetzen."""
    global _last_sound_at
    _last_sound_at = time.time()


def normalize_line(text: str) -> str:
    t = text.strip().lower()
    t = re.sub(r"^💭\s*", "", t)
    t = re.sub(r"\s+", " ", t)
    return t


def is_repeat_line(state: JamState, text: str) -> bool:
    """Gleicher Satz nicht alle paar Sekunden (auch Gedankenblase)."""
    key = normalize_line(text)
    if not key:
        return True
    now = time.time()
    if key == normalize_line(getattr(state, "last_line_shown", "") or ""):
        if now - getattr(state, "last_line_shown_at", 0.0) < REPEAT_SAME_LINE_SEC:
            return True
    for entry in getattr(state, "recent_shown_lines", []) or []:
        try:
            ts = float(entry[0])
            line = str(entry[1])
        except (IndexError, TypeError, ValueError):
            continue
        if normalize_line(line) == key and now - ts < REPEAT_SAME_LINE_SEC:
            return True
    return False


def can_emit_music_line(state: JamState, text: str) -> bool:
    """Singen bei Musik – eigener Abstand, immer mit Sound."""
    clean = text.strip()
    if not clean or is_repeat_line(state, text):
        return False
    if time.time() - getattr(state, "last_music_sing_at", 0.0) < MUSIC_SING_GAP_SEC:
        return False
    return True


def mark_music_sung(state: JamState, text: str) -> None:
    mark_line_spoken(state, text)
    state.last_music_sing_at = time.time()


def can_emit_line(state: JamState, text: str, *, user_called: bool = False) -> bool:
    """Kein Duplikat; Auto-Sätze mit Mindestabstand."""
    clean = text.strip()
    if not clean:
        return False
    if is_repeat_line(state, text):
        return False
    if user_called:
        return True
    now = time.time()
    last_at = getattr(state, "last_line_spoken_at", 0.0) or 0.0
    if now - last_at < MIN_LINE_GAP_SEC:
        return False
    return True


def mark_line_shown(state: JamState, text: str) -> None:
    clean = text.strip()
    if not clean:
        return
    now = time.time()
    state.last_line_shown_at = now
    state.last_line_shown = clean[:500]
    recent = list(getattr(state, "recent_shown_lines", []) or [])
    recent.append((now, clean[:500]))
    state.recent_shown_lines = recent[-40:]


def mark_line_spoken(state: JamState, text: str) -> None:
    clean = text.strip()
    if not clean:
        return
    mark_line_shown(state, text)
    state.last_line_spoken_at = time.time()
    state.last_line_spoken = clean[:500]


def speak(
    text: str,
    state: JamState,
    loud: bool = False,
    *,
    play_sound: bool | None = None,
    user_called: bool = False,
) -> None:
    """Text in Blase; Sound nur wenn play_sound / should_play_sound."""
    clean = text.strip()
    if not clean:
        return

    if play_sound is not None:
        do_sound = play_sound
    elif loud and len(clean) > 8:
        do_sound = should_play_sound(loud, user_called=user_called) or user_called
    else:
        do_sound = should_play_sound(loud, user_called=user_called)

    if do_sound and _main_speaker is not None:
        _main_speaker(clean, state.stage, loud)
        state.total_speech_events += 1
        return

    if do_sound:

        def _run() -> None:
            with _lock:
                voice_assets.speak_audible(clean, state.stage, loud)

        threading.Thread(target=_run, daemon=True).start()
        state.total_speech_events += 1


BABY_REACTIONS = ["*piep*", "*boop*", "*mrrp*", "*blub*"]

STAGE_PHRASES: dict[int, list[str]] = {
    0: ["Piep piep!", "Jam!", "Hallo!", "Boop!"],
    1: ["Hallo!", "Jam!", "Ja?", "Hehe!", "Guten Tag!"],
    2: [
        "Ich bin Jam, dein kleiner Helfer!",
        "Brauchst du Hilfe?",
        "Ich lerne noch Deutsch!",
        "Boah, spannend!",
    ],
    3: [
        "Alter, das war knapp!",
        "Ich guck dir zu – nicht schummeln!",
        "Monster-Modus aktiviert!",
        "Fließend Monster-Deutsch incoming!",
    ],
    4: [
        "Ich bin quasi erwachsen jetzt, aber süß geblieben.",
        "Sag mir was ich tun soll – ich kann fast alles!",
        "Selbstprogrammiert und stolz drauf!",
    ],
}

GAME_COMMENTS_LOUD = [
    "DU SPIELST ABER WIEDER SCHEISSE!",
    "Boah ey, konzentrier dich mal!",
    "Das war interessant. Im negativen Sinne!",
    "Ich hätt das besser gemacht – mit Tentakeln!",
    "MONSTER-DEUTSCH: Du läufst wie ein Kartoffelsack!",
    "Hör auf zu tappen, Jam schämt sich für dich!",
    "WAAAAAS war das denn gerade?!",
    "Okay okay, nächste Runde wird besser. Hoffentlich.",
    "Ich schrei nur weil ich dich lieb hab!",
    "Fließend Monster-Deutsch: Das war unterirdisch!",
]

# Persönlich an David / Besitzer – laut ausgesprochen
GAME_ROAST_PERSONAL = [
    "{name}, du spielst SCHEISSE!",
    "{name}! Das war SCHEISSE! Echt jetzt!",
    "Boah {name}, du spielst wie ein Kartoffelsack!",
    "{name}, ich find dein Spiel SCHEISSE! Sorry not sorry!",
    "MONSTER-DEUTSCH, {name}: Du spielst SCHEISSE!",
    "{name}, hast du mit den Füßen gespielt?! SCHEISSE!",
    "HÄ? {name}? Das war SCHEISSE! Ich sag's laut!",
    "{name}, Jam findet: dein Zocken ist SCHEISSE!",
    "LAUT für {name}: DU SPIELST SCHEISSE!",
    "{name}, nächste Runde weniger SCHEISSE, bitte!",
    "{name}, meine Oma kann besser spielen als du!",
    "Meine Oma zockt besser als du, {name}!",
    "{name}! Meine Oma spielt besser als du – und die ist achtzig!",
    "Oma-Alarm, {name}: Meine Oma > dein Skill!",
    "Jam sagt: Meine Oma kann besser spielen als du, {name}!",
]

GAME_CHEAT_PERSONAL = [
    "{name}, du CHEATEST!",
    "{name}! Ich SAG LAUT: du CHEATEST!",
    "Boah {name}, hör auf zu CHEATEN!",
    "{name}, das war CHEATEN! Ich hab's gesehen!",
    "MONSTER-DEUTSCH, {name}: DU CHEATEST!",
    "{name}, Wallhack-Vibes! Du CHEATEST!",
    "{name}, Jam ruft CHEATER! Du CHEATEST!",
    "HÄ {name}? CHEAT CHEAT CHEAT! Ich sag's laut!",
    "{name}, ohne Cheat wärst du… naja. CHEATER!",
    "{name}, fair spielen? Kennst du nicht! CHEAT!",
]

GAME_CHEAT_LOUD = [
    "DU CHEATEST! Ich hab's gesehen!",
    "CHEATER ALARM! CHEATER ALARM!",
    "Hör auf zu CHEATEN!",
    "Das war 100 Prozent CHEAT!",
    "WALLHACK ENERGY! CHEAT!",
]

FRECH_LINES = [
    "Hehe… deine Icons wackeln.",
    "Guck mich an, ich mach nur Spaß!",
    "Ups? Nein, Absicht.",
    "Langsam aber sicher, oder?",
    "Frech? Ich? Niemals. Vielleicht.",
    "Die Icons tanzen heute für mich.",
]

GAME_COMMENTS_SOFT = [
    "Ganz okay… fast.",
    "Hmm, könnte besser sein.",
    "Weiter so? Vielleicht anders so.",
]


def frech_line(state: JamState) -> str:
    if state.stage <= 0:
        return "Hehe!"
    return random.choice(FRECH_LINES)


def gaming_cheat_line(state: JamState) -> str:
    """Laut: Besitzer cheated."""
    name = owner_name(state)
    personal = [t.format(name=name) for t in GAME_CHEAT_PERSONAL]
    return random.choice(personal + GAME_CHEAT_LOUD)


def gaming_oma_line(state: JamState) -> str:
    """Klassiker: Oma spielt besser."""
    name = owner_name(state)
    lines = [t.format(name=name) for t in GAME_ROAST_PERSONAL if "Oma" in t or "oma" in t]
    if lines:
        return random.choice(lines)
    return f"{name}, meine Oma kann besser spielen als du!"


def gaming_roast_line(state: JamState, *, accuse_cheat: bool | None = None) -> str:
    """Lauter Gaming-Diss an den Besitzer (z. B. David)."""
    if accuse_cheat is None:
        accuse_cheat = random.random() < 0.38
    if accuse_cheat:
        return gaming_cheat_line(state)
    name = owner_name(state)
    personal = [t.format(name=name) for t in GAME_ROAST_PERSONAL]
    pool = personal + GAME_COMMENTS_LOUD
    return random.choice(pool)


def gaming_comment_should_be_loud(state: JamState) -> bool:
    return state.stage >= 1


def random_line(state: JamState, gaming: bool = False) -> str:
    if gaming:
        return gaming_roast_line(state)
    learned = getattr(state, "learned_sentences", None) or []
    if learned and random.random() < min(0.7, 0.18 + len(learned) * 0.003):
        from jam.sentence_learn import compose_from_learned, pick_line_for_speech

        if random.random() < 0.4:
            composed = compose_from_learned(state)
            if composed:
                return composed
        return pick_line_for_speech(state)
    stage = min(state.stage, 4)
    pool = STAGE_PHRASES.get(stage, STAGE_PHRASES[0])
    return random.choice(pool)


HOW_ARE_YOU_HINTS = (
    "wie geht",
    "wie gehts",
    "wie geht's",
    "wie gehts dir",
    "wie geht es dir",
    "was geht",
    "how are you",
    "how're you",
    "alles gut bei dir",
    "gehts dir",
    "geht es dir",
    "wie läuft",
    "wie laeuft",
    "wie bist du",
)

WELL_BEING_LINES = (
    "Hallo {owner}! Mir geht es sehr gut, danke der Nachfrage! Ich bin fit und glücklich!",
    "Hey {owner}! Mir geht's mega gut – Jam ist topfit und gut drauf!",
    "Ja {owner}, danke! Mir geht es ausgezeichnet, ich bin voller Energie!",
    "Hallo! Mir geht es sehr gut – ich wackel quasi vor Freude!",
    "{owner}, super dass du fragst: mir geht es richtig gut!",
)

HELLO_LINES = (
    "Hallo {owner}! Schön von dir zu hören!",
    "Hey {owner}! Jam grüßt zurück!",
    "Hi {owner}! Ich bin da!",
)


def hears_how_are_you(text: str) -> bool:
    h = text.lower().replace("jam", " ")
    if any(p in h for p in HOW_ARE_YOU_HINTS):
        return True
    if any(w in h for w in ("hallo", "hey", "hi", "moin", "servus")) and "geht" in h:
        return True
    return False


def hears_hello(text: str) -> bool:
    h = text.lower()
    return any(w in h for w in ("hallo", "hey", "hi", "moin", "servus", "guten tag", "guten morgen"))


def reply_how_are_you(state: JamState) -> str:
    """Auf „Wie geht's?“ – Jam sagt, dass es ihm gut geht."""
    name = owner_name(state)
    if state.hunger > 0.78:
        return (
            f"Hallo {name}! Ganz okay, aber ehrlich: ich hab Hunger. "
            f"Sonst geht's mir aber gut!"
        )
    if state.happiness < 0.35:
        return (
            f"Hallo {name}! Mir geht's so lala – ein bisschen traurig. "
            f"Aber besser, wenn du mit mir redest!"
        )
    line = random.choice(WELL_BEING_LINES).format(owner=name)
    if random.random() < 0.55:
        hello = random.choice(HELLO_LINES).format(owner=name)
        return f"{hello} {line}"
    return line


def greet_on_name(state: JamState) -> str:
    name = owner_name(state)
    if state.hunger > 0.75:
        return address(
            state,
            f"Jam hier! Ich hab Hunger – fütterst du mich, {name}?",
        )
    return random.choice(
        [
            address(state, "Jam meldet sich! Was gibt's?"),
            f"Ja {name}, Jam ist da!",
            address(state, "du hast gerufen? Ich bin bereit!"),
        ]
    )
