# INSTRUMENT-AUSNAHME: Victor-Go — Holmes/Watson v4: Original-Dialog + v3-Settings + Emil-Epilog
"""
Exakter Dialog aus watson_emi_gen.py (Arztrechnung, Zirkus, LGS, Textaufgaben)
+ Emil-Epilog (3 neue Clips)
+ v3-Settings: stability hoch, similarity niedrig, kein SSML, speaker_boost=False
+ TAIL_MS=600 + 80ms fade-out (kein abgehackter Atmer)
Output: watson_demo_clips/watson_v4.mp3
"""
import json, time, subprocess, base64, urllib.request, urllib.error
from pathlib import Path
from pydub import AudioSegment, effects

API_KEY = "sk_cac6e576af419777a078b639869a5b3f36ae04eca65d6e37"
CLIPS   = Path("/Users/victorholland/Vibe Coding/dispatcher/cockpit/watson_demo_clips")
LOG     = Path("/tmp/watson_v4_gen.log")
MODEL   = "eleven_multilingual_v2"
WATSON  = "T080SqoEEtD27526TTTa"
HOLMES  = "kMg8CgFduU4YUWmbRBx1"
CLIPS.mkdir(exist_ok=True)
LOG.write_text("")

LEAD_MS    = 30
TAIL_MS    = 600
FADE_OUT_MS= 80

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

def h(stability=0.95, similarity=0.30, style=0.0):
    return {"stability": stability, "similarity_boost": similarity,
            "style": style, "use_speaker_boost": False}

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

DIALOG = [
    # ── Eröffnung: Watsons Arztrechnung ──
    ("d01","holmes",HOLMES, h(),
     "Watson. Ich habe heute Morgen Ihre Arztrechnung auf dem Frühstückstisch gefunden. "
     "Haben Sie die selbst ausgerechnet?"),
    ("d02","watson",WATSON, w(0.55,0.42,0.45),
     "Ich habe Medizin studiert, Holmes. Natürlich beherrsche ich die Grundlagen."),
    ("d03","holmes",HOLMES, h(0.95,0.30,0.0),
     "Sie haben sich um vierundzwanzig Schilling verrechnet."),
    ("d04","watson",WATSON, w(0.28,0.41,0.75),
     "Das ist... das ist unmöglich."),
    ("d05","holmes",HOLMES, h(),
     "Es ist sehr möglich. Und ich werde Ihnen erklären, warum. "
     "Setzen Sie sich, Watson. Das wird ein wenig dauern."),

    # ── Lineare Funktionen ──
    ("d06","holmes",HOLMES, h(),
     "Eine Arztrechnung ist eine lineare Funktion. "
     "Die Form lautet: Ypsilon gleich m mal iks plus n. "
     "iks ist die Anzahl Ihrer Hausbesuche. Ypsilon sind die Gesamtkosten. "
     "m ist der Preis pro Besuch. Und n — das kleine n — ist die Grundgebühr. "
     "Die feste Pauschale, bevor ein einziger Schritt getan wurde."),
    ("d07","watson",WATSON, w(0.52,0.42,0.42),
     "Die Grundgebühr. Ja, die habe ich... mitgerechnet."),
    ("d08","holmes",HOLMES, h(),
     "Nein, Watson. Sie haben sie vergessen. "
     "Stellen Sie sich Patientin Nummer vierzehn in Baker Street vor. "
     "Grundgebühr zwei Schilling, ein Schilling pro Besuch. "
     "Drei Besuche: Ypsilon gleich drei plus zwei. Macht fünf Schilling. "
     "Sie haben drei Schilling abgerechnet. Das n war nicht auf Ihrer Rechnung."),
    ("d09","watson",WATSON, w(0.42,0.41,0.52),
     "Ich habe das n... unterschlagen?"),
    ("d10","holmes",HOLMES, h(),
     "Nicht aus Bosheit. Aus Unwissenheit. "
     "Wenn iks null ist — also wenn gar kein Besuch stattgefunden hat — "
     "was kostet es dann?"),
    ("d11","watson",WATSON, w(0.50,0.42,0.42),
     "Null? Nein... zwei Schilling. Die Grundgebühr."),
    ("d12","holmes",HOLMES, h(),
     "Endlich. n ist genau das: der Ypsilon-Wert wenn iks gleich null ist. "
     "Er sitzt auf der Ypsilon-Achse. Man liest ihn ab — einfach ablesen, wie eine Hausnummer. "
     "Sie müssen nicht rechnen. Sie müssen nur hinschauen."),
    ("d13","watson",WATSON, w(0.52,0.42,0.40),
     "Wie eine Hausnummer. Das... das verstehe ich jetzt."),
    ("d14","holmes",HOLMES, h(),
     "Ihr Patient — der Buchhalter aus der Nummer elf — hat es auch verstanden. "
     "Er hat Ihnen einen sehr höflichen Brief geschrieben."),
    ("d15","watson",WATSON, w(0.40,0.41,0.55),
     "Er hat mir einen Brief geschrieben?!"),
    ("d16","holmes",HOLMES, h(),
     "Vier Seiten. Sehr sorgfältig. Mit Tabellen."),

    # ── Pythagoras — der Zirkusfall ──
    ("d17","holmes",HOLMES, h(),
     "Weiter. Pythagoras. Erinnern Sie sich an den Fall im Zirkus, letzten Oktober?"),
    ("d18","watson",WATSON, w(0.40,0.41,0.58),
     "Wir... wir hatten vereinbart, den nicht mehr zu erwähnen."),
    ("d19","holmes",HOLMES, h(),
     "Sie wollten wissen, ob der Drahtseilartist tatsächlich aus zwölf Metern gestürzt war. "
     "Das Zelt hatte beiderseits eine Grundbreite von acht Metern. Die Dachschräge war gleich. "
     "Sie haben was getan?"),
    ("d20","watson",WATSON, w(0.38,0.41,0.60),
     "Ich habe... geschätzt."),
    ("d21","holmes",HOLMES, h(0.95,0.30,0.0),
     "Sie haben geschätzt."),
    ("d22","watson",WATSON, w(0.35,0.41,0.65),
     "Es war ein Zirkus! Es roch nach Elefant!"),
    ("d23","holmes",HOLMES, h(),
     "Die Lösung brauchte zwei Schritte. "
     "Stellen Sie sich das Zelt als gleichschenkliges Dreieck vor: Spitze oben, Basis unten. "
     "Ich ziehe eine senkrechte Linie von der Spitze zur Mitte der Basis. "
     "Acht Meter pro Seite — also vier Meter bis zur Mitte. "
     "Jetzt habe ich zwei rechtwinklige Dreiecke: "
     "vier Meter waagerecht, die gesuchte Höhe senkrecht, die Dachschräge als Hypotenuse. "
     "Und nun? a-Quadrat plus b-Quadrat gleich c-Quadrat."),
    ("d24","watson",WATSON, w(0.48,0.42,0.48),
     "Vier-Quadrat plus Höhen-Quadrat gleich Dachschräge-Quadrat. "
     "Wenn ich die Dachschräge gemessen hätte..."),
    ("d25","holmes",HOLMES, h(),
     "Dann hätten Sie ohne Schätzung gewusst: der Artiste konnte nicht aus zwölf Metern gestürzt sein. "
     "Aus neun Komma zwei Metern, ja. Aber nicht aus zwölf."),
    ("d26","watson",WATSON, w(0.40,0.41,0.55),
     "Dann war es wirklich der falsche Mann."),
    ("d27","holmes",HOLMES, h(),
     "Ich habe es Ihnen damals gesagt."),
    ("d28","watson",WATSON, w(0.38,0.41,0.58),
     "Sie haben es immer gesagt."),

    # ── LGS — zwei Verdächtige ──
    ("d29","holmes",HOLMES, h(),
     "Gleichungssysteme. Zwei Verdächtige, beide ohne Alibi. "
     "Sie wissen nur: zusammen waren sie neunzehn Stunden in der Stadt. "
     "Und der erste war genau drei Stunden länger da als der zweite. "
     "Wie lange war jeder in der Stadt?"),
    ("d30","watson",WATSON, w(0.52,0.42,0.42),
     "Der zweite sei iks... dann ist der erste iks plus drei. "
     "Zusammen: iks plus iks plus drei gleich neunzehn. "
     "Also zwei iks gleich sechzehn. iks gleich acht."),
    ("d31","holmes",HOLMES, h(),
     "Der zweite acht Stunden. Der erste elf. Richtig."),
    ("d32","watson",WATSON, w(0.42,0.42,0.50),
     "Das war... das mache ich die ganze Zeit. Nur ohne die Gleichung aufzuschreiben."),
    ("d33","holmes",HOLMES, h(),
     "Eben. Und ohne Aufschreiben macht man Fehler. "
     "Grafisch ist es sogar noch schöner. "
     "Sie verfolgen zwei Verdächtige durch die Stadt. "
     "Jeder hinterlässt eine Spur — eine Gerade auf der Karte. "
     "iks ist die Zeit, Ypsilon ist der Ort. "
     "Wo die beiden Geraden sich kreuzen — dort waren beide gleichzeitig am gleichen Ort. "
     "Die Lösung liegt nicht in einer Gleichung. Sie liegt in einem Punkt auf der Karte."),
    ("d34","watson",WATSON, w(0.50,0.42,0.42),
     "Der Schnittpunkt ist der Tatort."),
    ("d35","holmes",HOLMES, h(),
     "Der Schnittpunkt ist immer der Tatort. "
     "Merken Sie sich das, Watson. Es gilt für Algebra, für Geometrie — "
     "und für jeden Fall, den wir je gelöst haben."),

    # ── Textaufgaben — Zeugenaussage ──
    ("d36","watson",WATSON, w(0.52,0.42,0.40),
     "Und die Textaufgaben? Das ist doch eigentlich nur gesunder Menschenverstand."),
    ("d37","holmes",HOLMES, h(),
     "Gesunder Menschenverstand hat noch keine einzige Gleichung bewiesen. "
     "Übung: Ein Zeuge sagt: 'Der ältere Bruder ist dreimal so alt wie seine Schwester. "
     "In fünf Jahren ist er nur noch doppelt so alt.' "
     "Schreiben Sie das auf."),
    ("d38","watson",WATSON, w(0.50,0.42,0.45),
     "Die Schwester sei s. Der Bruder ist drei s. "
     "In fünf Jahren: drei s plus fünf gleich zwei mal s plus fünf."),
    ("d39","holmes",HOLMES, h(),
     "Weiter."),
    ("d40","watson",WATSON, w(0.50,0.42,0.42),
     "Drei s plus fünf gleich zwei s plus zehn. "
     "s gleich fünf. Die Schwester ist fünf. Der Bruder fünfzehn."),
    ("d41","holmes",HOLMES, h(0.90,0.28,0.0),
     "Watson."),
    ("d42","watson",WATSON, w(0.45,0.41,0.48),
     "Holmes."),
    ("d43","holmes",HOLMES, h(0.88,0.28,0.0),
     "Das war ausgezeichnet."),

    # ── Ausklang ──
    ("d44","watson",WATSON, w(0.32,0.41,0.72),
     "War das... war das ein Kompliment?"),
    ("d45","holmes",HOLMES, h(),
     "Es war eine Beobachtung."),
    ("d46","watson",WATSON, w(0.40,0.41,0.62),
     "Holmes. Sie haben mich gelobt. Ich werde es ins Tagebuch schreiben."),
    ("d47","holmes",HOLMES, h(),
     "Wenn Sie dort auch Ihre Rechnungen ablegen, "
     "empfehle ich dringend ein zweites Kapitel für die Grundrechenarten."),
    ("d48","watson",WATSON, w(0.42,0.41,0.58),
     "Vierundzwanzig Schilling. Ich fasse es nicht."),
    ("d49","holmes",HOLMES, h(),
     "Der Buchhalter fasst es auch nicht. "
     "Er hat übrigens gefragt, ob Sie ihm eine neue Rechnung schicken möchten. "
     "Mit Grundgebühr."),
    ("d50","watson",WATSON, w(0.38,0.41,0.65),
     "Das ist... das ist wirklich nicht nötig."),
    ("d51","holmes",HOLMES, h(),
     "Er besteht darauf. Vier Seiten. Mit Tabellen."),
    ("d52","watson",WATSON, w(0.42,0.41,0.60),
     "Holmes, manchmal hasse ich Sie sehr."),
    ("d53","holmes",HOLMES, h(),
     "Das ist auch eine lineare Funktion, Watson. "
     "Je mehr ich Ihnen beibringen muss — desto größer der Wert auf der Ypsilon-Achse."),

    # ── Emil-Epilog ──
    ("d54","holmes",HOLMES, h(0.92,0.30,0.0),
     "Und übrigens, Emil — machen Sie sich keine Sorgen wegen morgen. "
     "Sie sind ja nun nicht auf den Kopf gefallen."),
    ("d55","watson",WATSON, w(0.65,0.44,0.14),
     "Gehen Sie in diese Prüfung, junger Mann, mit dem Kopf oben. Wir glauben an Sie."),
    ("d56","holmes",HOLMES, h(0.90,0.28,0.0),
     "Watson hat recht. Was — ich gebe es ungern zu — gar nicht so selten vorkommt."),
]

PAUSE_AFTER = {
    "d01": 400, "d03": 300, "d05": 700,
    "d08": 600, "d10": 700, "d11": 200, "d13": 400, "d15": 200,
    "d16": 900,
    "d17": 500, "d19": 800, "d21": 400, "d22": 600, "d23": 300,
    "d25": 500, "d27": 300,
    "d28": 900,
    "d29": 800, "d31": 400, "d33": 300,
    "d35": 700,
    "d37": 700, "d38": 300, "d39": 700,
    "d40": 900, "d41": 500, "d42": 700, "d43": 700,
    "d44": 300, "d45": 500,
    "d49": 400, "d51": 300,
    "d53": 1200,  # großer Atemzug vor Epilog
    "d54": 700,
    "d55": 400,
}

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

# ─── Generierung ─────────────────────────────────────────────────────────────
log(f"=== Watson v4 — {len(DIALOG)} Clips (Original-Dialog + Epilog, v3-Settings) ===")
errors = 0
texts = [row[4] for row in DIALOG]

for i, (did, sp, voice_id, vs, text) in enumerate(DIALOG):
    out_mp3 = CLIPS / f"{did}_{sp}.mp3"
    out_ts  = CLIPS / f"{did}_{sp}.timestamps.json"

    payload = {"text": text, "model_id": MODEL, "voice_settings": vs}
    if i > 0:   payload["previous_text"] = texts[i-1]
    if i < len(DIALOG)-1: payload["next_text"] = texts[i+1]

    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"
    )
    try:
        with urllib.request.urlopen(req, timeout=90) as r:
            resp = json.loads(r.read())
        out_mp3.write_bytes(base64.b64decode(resp["audio_base64"]))
        out_ts.write_text(json.dumps(resp["alignment"], ensure_ascii=False))
        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")
    except urllib.error.HTTPError as e:
        log(f"  ✗ {did} HTTP {e.code}: {e.read()[:150]}")
        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 ===")
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)
    clipped = norm[cut_start:cut_end].fade_out(FADE_OUT_MS)
    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_v4.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 v4",
                    "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")
