# INSTRUMENT-AUSNAHME: Victor-Go — Holmes/Watson v2: Neues Skript, frischer Dialog, Atemfix
"""
Generiert alle 55 Clips neu:
  - Neuer Dialog: lustig, emotional, verrückte Beispiele, Freundes-Geplänkel
  - Atemfix: speaker_boost=False, similarity_boost 0.45-0.55, style 0.0 für Holmes
  - Post-Processing: FFmpeg Noise Gate pro Clip (highpass + agate)
  - Epilog: Holmes ermutigt Emil, Watson mit "junger Mann"
  - Mix: SSML-Filter-Trim + PAUSE_AFTER
  - Output: watson_demo_clips/watson_v2.mp3
"""
import json, time, subprocess, base64, urllib.request, urllib.error
from pathlib import Path

API_KEY = "sk_cac6e576af419777a078b639869a5b3f36ae04eca65d6e37"
CLIPS   = Path("/Users/victorholland/Vibe Coding/dispatcher/cockpit/watson_demo_clips")
LOG     = Path("/tmp/watson_v2_gen.log")
MODEL   = "eleven_multilingual_v2"

WATSON = "T080SqoEEtD27526TTTa"
HOLMES = "kMg8CgFduU4YUWmbRBx1"

CLIPS.mkdir(exist_ok=True)
LOG.write_text("")

def log(msg):
    print(msg, flush=True)
    with LOG.open("a") as f:
        f.write(msg + "\n")

# Breathing-Fix: speaker_boost=False, similarity_boost deutlich runter
def vs_holmes(stability=0.80, similarity=0.55, style=0.0):
    return {"stability": stability, "similarity_boost": similarity,
            "style": style, "use_speaker_boost": False}

def vs_watson(stability=0.50, similarity=0.45, style=0.28):
    return {"stability": stability, "similarity_boost": similarity,
            "style": style, "use_speaker_boost": False}

# ─── Neuer Dialog ─────────────────────────────────────────────────────────────
# Format: (clip_id, speaker_label, voice_id, voice_settings, text)
DIALOG = [
    # Eröffnung
    ("d01","holmes",HOLMES, vs_holmes(0.80,0.55,0.0),
     "Watson. Ich habe ein kleines Rätsel für Sie. Erschrecken Sie bitte nicht."),
    ("d02","watson",WATSON, vs_watson(0.52,0.45,0.20),
     "Holmes, es ist halb zehn. Ich sitze in meinem Sessel. Sagen Sie mir nicht, dass das Mathematik wird."),
    ("d03","holmes",HOLMES, vs_holmes(0.78,0.54,0.0),
     "Mathematik. Emil schaut zu. Bemühen Sie sich."),
    ("d04","watson",WATSON, vs_watson(0.48,0.44,0.24),
     "Na wunderbar. Was für ein herrlicher Abend."),

    # Lineare Funktionen
    ("d05","holmes",HOLMES, vs_holmes(0.82,0.56,0.0),
     "Mrs. Hudson verlangt für unser Zimmer in der Baker Street zwei Shilling Grundgebühr — plus einen halben Shilling pro Nacht. Wieviel zahlen wir nach zehn Nächten?"),
    ("d06","watson",WATSON, vs_watson(0.50,0.45,0.22),
     "Zwei Shilling Grundgebühr... plus zehn mal ein halber... das sind fünf... macht sieben Shilling."),
    ("d07","holmes",HOLMES, vs_holmes(0.80,0.55,0.0),
     "Richtig. Das nennt man eine lineare Funktion. y gleich m mal x plus b. Die Grundgebühr ist b — der Preis pro Nacht ist m. Einfach, nicht wahr?"),
    ("d08","watson",WATSON, vs_watson(0.55,0.46,0.18),
     "Erschreckend einfach, ja."),
    ("d09","holmes",HOLMES, vs_holmes(0.76,0.53,0.0),
     "Fast alles im Leben ist erschreckend einfach, Watson. Man muss nur hinschauen."),
    ("d10","watson",WATSON, vs_watson(0.42,0.44,0.30),
     "Und wenn ich nach zehn Shilling insgesamt frage — nach wievielen Nächten erreichen wir die?"),
    ("d11","holmes",HOLMES, vs_holmes(0.80,0.55,0.0),
     "Das ist die richtige Frage. x gleich zehn minus zwei, geteilt durch null Komma fünf. Macht sechzehn Nächte."),
    ("d12","watson",WATSON, vs_watson(0.54,0.45,0.20),
     "Das stimmt. Ich habe es im Kopf nachgerechnet."),
    ("d13","holmes",HOLMES, vs_holmes(0.78,0.54,0.0),
     "Ich weiß. Ich habe es an Ihren Lippen abgelesen."),

    # Themenübergang Pythagoras
    ("d14","holmes",HOLMES, vs_holmes(0.82,0.56,0.0),
     "Kommen wir zu Pythagoras."),
    ("d15","watson",WATSON, vs_watson(0.38,0.43,0.40),
     "Schon der Name macht mir Bauchweh."),
    ("d16","holmes",HOLMES, vs_holmes(0.80,0.55,0.0),
     "Watson. Ein Einbrecher — rein hypothetisch natürlich — steht drei Meter von einer vier Meter hohen Wand entfernt. Wie lang muss seine Leiter sein?"),
    ("d17","watson",WATSON, vs_watson(0.32,0.43,0.50),
     "Ah — das ist Pythagoras! Drei hoch zwei ist neun, vier hoch zwei sechzehn... zusammen fünfundzwanzig... Wurzel fünf! Fünf Meter Leiter!"),
    ("d18","holmes",HOLMES, vs_holmes(0.74,0.52,0.0),
     "Korrekt. Ich bin... beeindruckt."),
    ("d19","watson",WATSON, vs_watson(0.36,0.43,0.44),
     "Sie sind nie beeindruckt."),
    ("d20","holmes",HOLMES, vs_holmes(0.76,0.53,0.0),
     "Ich sagte, ich bin beeindruckt. Nicht, dass ich es zeige."),
    ("d21","watson",WATSON, vs_watson(0.28,0.44,0.55),
     "Holmes — war das eben ein Kompliment?"),
    ("d22","holmes",HOLMES, vs_holmes(0.80,0.55,0.0),
     "Es war eine Beobachtung. Fahren Sie fort."),
    ("d23","watson",WATSON, vs_watson(0.54,0.46,0.20),
     "Der Satz des Pythagoras: a im Quadrat plus b im Quadrat gleich c im Quadrat. c ist immer die Hypotenuse — die längste Seite, die dem rechten Winkel gegenüberliegt."),
    ("d24","holmes",HOLMES, vs_holmes(0.80,0.55,0.0),
     "Und das gilt ausschließlich bei rechtwinkligen Dreiecken. Das ist entscheidend."),
    ("d25","watson",WATSON, vs_watson(0.40,0.44,0.35),
     "Was ist, wenn der Einbrecher keinen rechten Winkel hat?"),
    ("d26","holmes",HOLMES, vs_holmes(0.78,0.54,0.0),
     "Dann ist er ein schlechter Einbrecher."),

    # Themenübergang LGS
    ("d27","holmes",HOLMES, vs_holmes(0.82,0.56,0.0),
     "Jetzt kommt das Schwere. Gleichungssysteme und Textaufgaben."),
    ("d28","watson",WATSON, vs_watson(0.50,0.45,0.25),
     "Hier bin ich — offen gestanden — tatsächlich schwach."),
    ("d29","holmes",HOLMES, vs_holmes(0.78,0.54,0.0),
     "Das weiß ich. Es ist Ihr Kryptonit, Watson."),
    ("d30","watson",WATSON, vs_watson(0.44,0.44,0.32),
     "Mein was?"),
    ("d31","holmes",HOLMES, vs_holmes(0.80,0.55,0.0),
     "Aufgabe: Ich kaufe zwei Würste und ein Bier für sieben Pence. Drei Würste und zwei Bier kosten elf Pence. Was kostet eine Wurst — was kostet ein Bier?"),
    ("d32","watson",WATSON, vs_watson(0.38,0.43,0.42),
     "Hmm... wenn zwei Würste und ein Bier sieben Pence sind... dann könnte eine Wurst... drei... nein, zwei... ich verliere völlig den Faden."),
    ("d33","holmes",HOLMES, vs_holmes(0.76,0.53,0.0),
     "Stopp. Das ist der klassische Fehler. Sie rechnen, bevor Sie denken. Erst Variablen."),
    ("d34","watson",WATSON, vs_watson(0.52,0.45,0.22),
     "Also... w für Wurst, b für Bier?"),
    ("d35","holmes",HOLMES, vs_holmes(0.80,0.55,0.0),
     "Gut. Erste Gleichung — direkt aus dem Text."),
    ("d36","watson",WATSON, vs_watson(0.54,0.46,0.20),
     "Zwei w plus b gleich sieben."),
    ("d37","holmes",HOLMES, vs_holmes(0.80,0.55,0.0),
     "Zweite?"),
    ("d38","watson",WATSON, vs_watson(0.52,0.45,0.22),
     "Drei w plus zwei b gleich elf."),
    ("d39","holmes",HOLMES, vs_holmes(0.80,0.55,0.0),
     "Ausgezeichnet. Jetzt: multiplizieren Sie die erste Gleichung mit zwei."),
    ("d40","watson",WATSON, vs_watson(0.55,0.46,0.20),
     "Vier w plus zwei b gleich vierzehn."),
    ("d41","holmes",HOLMES, vs_holmes(0.80,0.55,0.0),
     "Und subtrahieren Sie davon die zweite Gleichung."),
    ("d42","watson",WATSON, vs_watson(0.36,0.43,0.45),
     "Vier w minus drei w... das ist ein w. Und vierzehn minus elf ist drei. Also... eine Wurst kostet drei Pence?"),
    ("d43","holmes",HOLMES, vs_holmes(0.74,0.52,0.0),
     "Korrekt."),
    ("d44","watson",WATSON, vs_watson(0.34,0.43,0.48),
     "Und dann: b gleich sieben minus sechs... ein Pence für ein Bier! Holmes — ich hab's!"),
    ("d45","holmes",HOLMES, vs_holmes(0.72,0.52,0.0),
     "Watson. Das. War. Korrekt."),
    ("d46","watson",WATSON, vs_watson(0.52,0.45,0.22),
     "Das war... das war gar nicht so schrecklich."),
    ("d47","holmes",HOLMES, vs_holmes(0.80,0.55,0.0),
     "Es war überhaupt nicht schrecklich. Die Methode: erst Text lesen, Variablen benennen, Gleichungen aufschreiben — dann, und erst dann, rechnen."),
    ("d48","watson",WATSON, vs_watson(0.58,0.46,0.18),
     "Erst Text. Dann Variablen. Dann Gleichungen. Dann rechnen. Immer in dieser Reihenfolge."),
    ("d49","holmes",HOLMES, vs_holmes(0.80,0.55,0.0),
     "Genau so. Und nicht anders."),

    # Epilog
    ("d50","watson",WATSON, vs_watson(0.60,0.47,0.16),
     "Also, Emil — das war's für heute Abend. Lineare Funktionen, Pythagoras, Gleichungssysteme mit Textaufgaben. Ganz schön viel für einen einzigen Abend in der Baker Street."),
    ("d51","holmes",HOLMES, vs_holmes(0.78,0.54,0.0),
     "Und übrigens, Emil — machen Sie sich keine Sorgen wegen morgen. Sie sind ja nun nicht auf den Kopf gefallen."),
    ("d52","watson",WATSON, vs_watson(0.64,0.48,0.14),
     "Gehen Sie in diese Prüfung, junger Mann, mit dem Kopf oben. Wir glauben an Sie."),
    ("d53","holmes",HOLMES, vs_holmes(0.76,0.53,0.0),
     "Watson hat recht. Was ich — ungern, aber ehrlich — gar nicht so selten zugeben muss."),
    ("d54","watson",WATSON, vs_watson(0.35,0.43,0.45),
     "Holmes!"),
    ("d55","holmes",HOLMES, vs_holmes(0.72,0.52,0.0),
     "In mathematischen Dingen jedenfalls."),
]

# ─── Pausen nach Clips ───────────────────────────────────────────────────────
PAUSE_AFTER = {
    "d04": 500,   # Watson akzeptiert widerstrebend
    "d09": 400,   # Holmes' philosophischer Satz hängt in der Luft
    "d13": 600,   # Holmes liest Watsons Lippen — Watson verdaut
    "d14": 900,   # Themenübergang Pythagoras
    "d17": 700,   # Watson berechnet aufgeregt
    "d18": 800,   # Holmes gibt zu beeindruckt zu sein
    "d22": 500,   # Holmes weicht Kompliment aus
    "d26": 1000,  # Themenübergang LGS
    "d29": 400,   # Holmes nennt Watsons Schwäche
    "d33": 600,   # Holmes korrigiert — Pause vor der Methode
    "d35": 300,   # Kurze Pause bevor Watson erste Gleichung aufschreibt
    "d37": 200,
    "d39": 400,
    "d41": 500,
    "d43": 700,   # Holmes bestätigt — Triumph-Moment
    "d45": 800,   # Holmes' seltenes ausdrückliches Lob
    "d46": 500,
    "d49": 1000,  # Übergang zum Epilog
    "d51": 700,   # Holmes ermutigt Emil
    "d53": 500,
    "d54": 300,
}

LEAD_MS = 30
TAIL_MS = 200

# ─── FFmpeg Noise Gate ───────────────────────────────────────────────────────
def apply_noise_gate(mp3_path: Path) -> bool:
    """Noise Gate in-place: highpass 120Hz + agate"""
    tmp = mp3_path.with_suffix(".tmp.mp3")
    cmd = [
        "ffmpeg", "-y", "-i", str(mp3_path),
        "-af", "highpass=f=120,agate=threshold=0.015:ratio=4:attack=10:release=200",
        "-codec:a", "libmp3lame", "-b:a", "192k",
        str(tmp)
    ]
    r = subprocess.run(cmd, capture_output=True)
    if r.returncode == 0:
        tmp.rename(mp3_path)
        return True
    else:
        if tmp.exists():
            tmp.unlink()
        return False

# ─── ElevenLabs API ──────────────────────────────────────────────────────────
BREAK = '<break time="600ms"/>'

def generate_clip(did, sp, voice_id, voice_settings, text, context_texts):
    out_mp3 = CLIPS / f"{did}_{sp}.mp3"
    out_ts  = CLIPS / f"{did}_{sp}.timestamps.json"

    ssml_text = f"<speak>{BREAK}{text}{BREAK}</speak>"
    payload = {
        "text": ssml_text,
        "model_id": MODEL,
        "voice_settings": voice_settings,
    }
    prev_text, next_text = context_texts
    if prev_text:
        payload["previous_text"] = prev_text
    if next_text:
        payload["next_text"] = next_text

    req = urllib.request.Request(
        f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}/with-timestamps",
        data=json.dumps(payload).encode(),
        headers={"xi-api-key": API_KEY, "Content-Type": "application/json",
                 "Accept": "application/json"},
        method="POST"
    )
    with urllib.request.urlopen(req, timeout=60) as r:
        resp = json.loads(r.read())

    audio_bytes = base64.b64decode(resp["audio_base64"])
    out_mp3.write_bytes(audio_bytes)
    out_ts.write_text(json.dumps(resp["alignment"], ensure_ascii=False))

    gate_ok = apply_noise_gate(out_mp3)
    gate_str = "gate✓" if gate_ok else "gate✗"

    dur = float(subprocess.check_output(
        ["ffprobe","-v","quiet","-show_entries","format=duration","-of","csv=p=0",str(out_mp3)],
        text=True).strip())
    log(f"  ✓ {did} ({sp:8s})  {dur:.2f}s  {gate_str}")
    return True

# ─── Generierung ─────────────────────────────────────────────────────────────
log("=== Watson v2 — Clip-Generierung (Atemfix + neuer Dialog) ===")
errors = 0
texts = [row[4] for row in DIALOG]

for i, (did, sp, voice_id, vs, text) in enumerate(DIALOG):
    prev_text = texts[i-1] if i > 0 else ""
    next_text = texts[i+1] if i < len(DIALOG)-1 else ""
    try:
        generate_clip(did, sp, voice_id, vs, text, (prev_text, next_text))
    except urllib.error.HTTPError as e:
        body = e.read()[:200]
        log(f"  ✗ {did} HTTP {e.code}: {body}")
        errors += 1
    except Exception as e:
        log(f"  ✗ {did}: {e}")
        errors += 1
    time.sleep(0.4)

log(f"\n{len(DIALOG)-errors}/{len(DIALOG)} Clips OK, {errors} Fehler")

# ─── Mix ─────────────────────────────────────────────────────────────────────
log("\n=== Mix ===")
from pydub import AudioSegment, effects
import re

def get_speech_bounds(ts_path, audio_len_ms):
    try:
        alignment = json.loads(ts_path.read_text())
        chars  = alignment.get("characters", [])
        starts = alignment.get("character_start_times_seconds", [])
        ends   = alignment.get("character_end_times_seconds", [])
        if not chars:
            raise ValueError("leer")
        in_tag, real = False, []
        for c, s, e in zip(chars, starts, ends):
            if c == '<': in_tag = True
            if not in_tag: real.append((c, s, e))
            if c == '>': in_tag = False
        if not real:
            raise ValueError("keine Sprachzeichen")
        return int(real[0][1] * 1000), int(real[-1][2] * 1000)
    except Exception:
        return 30, audio_len_ms - 30

trimmed = []
for did, sp, *_ in DIALOG:
    mp3 = CLIPS / f"{did}_{sp}.mp3"
    ts  = CLIPS / f"{did}_{sp}.timestamps.json"
    if not mp3.exists():
        log(f"  ✗ {did} fehlt")
        continue
    raw  = AudioSegment.from_mp3(str(mp3))
    norm = effects.normalize(raw, headroom=1.0)
    s_ms, e_ms = get_speech_bounds(ts, len(norm))
    cut_start = max(0, s_ms - LEAD_MS)
    cut_end   = min(len(norm), e_ms + TAIL_MS)
    if cut_end - cut_start < 200:
        cut_start, cut_end = 0, len(norm)
        log(f"  {did} Fallback → komplett")
    clipped = norm[cut_start:cut_end]
    trimmed.append((did, clipped))
    log(f"  {did} ({sp:8s})  {len(clipped):5d}ms")

if not trimmed:
    raise SystemExit("Keine Clips!")

result = trimmed[0][1]
for i in range(1, len(trimmed)):
    did_prev = trimmed[i-1][0]
    _, seg   = trimmed[i]
    pause_ms = PAUSE_AFTER.get(did_prev, 0)
    if pause_ms > 0:
        result = result + AudioSegment.silent(duration=pause_ms, frame_rate=44100)
    result = result.append(seg, crossfade=0)

OUT = CLIPS / "watson_v2.mp3"
log(f"\nGesamt: {len(result)/1000:.1f}s — Exportiere...")
result.export(str(OUT), format="mp3", bitrate="320k",
              tags={"title":"Holmes & Watson — Mathe für Emil v2",
                    "artist":"Watson Demo", "album":"Baker Street Hörspiel"})
log(f"Fertig: {OUT.stat().st_size//1024} KB")
print(f"\n✓ {OUT.name}  {OUT.stat().st_size//1024} KB  {len(result)/1000:.1f}s")
