import { VoiceCore, CalibrationPanel } from "@beatbox/voice-core";

    const $ = (id: string) => document.getElementById(id)!;
    const log = (msg: string) => {
      $("log").textContent = `${new Date().toLocaleTimeString()}  ${msg}\n${
        $("log").textContent ?? ""
      }`;
    };

    const vc = new VoiceCore({ appId: "vad-demo", speakerId: "demo-speaker" });

    vc.listeningState((s) => {
      $("dot").textContent = s.icon;
      $("state").textContent = s.present
        ? "VOICE"
        : s.listening
          ? "listening"
          : "idle";
      $("label").textContent = s.label ? `(${s.label})` : "";
    });

    // Bind the training-state panel to the core's exposed training state.
    const tsEl = (key: string) =>
      document.querySelector<HTMLElement>(`[data-ts="${key}"]`)!;
    vc.trainingState((ts) => {
      tsEl("dsp").textContent = ts.dspTuned ? "yes" : "no";
      tsEl("fp").textContent = ts.dspFingerprint || "—";
      tsEl("clf").textContent = ts.classifierReady ? "ready" : "not trained";
      tsEl("sounds").textContent =
        ts.soundLabels.length > 0
          ? ts.soundLabels.map((s) => `${s.label}(${s.frames})`).join(", ")
          : "none";
      tsEl("word").textContent = ts.wordModelReady ? "ready" : "not trained";
      tsEl("words").textContent =
        ts.words.length > 0 ? ts.words.join(", ") : "none";
      tsEl("seqs").textContent = String(ts.wordSequences);
      tsEl("thresh").textContent = ts.wordDetectThreshold.toFixed(2);
      const slider = $("thresh-slider") as HTMLInputElement;
      const out = $("thresh-out") as HTMLOutputElement;
      if (ts.wordModelReady) {
        slider.disabled = false;
        slider.value = String(ts.wordDetectThreshold);
        out.textContent = ts.wordDetectThreshold.toFixed(2);
      }
    });

    // Live wake-word threshold control.
    (($("thresh-slider") as HTMLInputElement).addEventListener("input", (e) => {
      const v = Number((e.target as HTMLInputElement).value);
      ($("thresh-out") as HTMLOutputElement).textContent = v.toFixed(2);
      vc.setActivationWordThreshold(v);
    }));

    let vaCount = 0;
    vc.onVoiceActivity((ev) => {
      if (!ev.present) return;
      vaCount++;
      if (vaCount % 15 === 0) {
        log(
          `voice '${ev.label}' conf=${(ev.confidence * 100) | 0}% ` +
            `(${vaCount} frames)`,
        );
      }
    });

    // Activation-word (wake-word) recognition via a simple RNN.
    vc.onActivationWord((word, confidence) => {
      log(`🔔 activation word '${word}' detected (${(confidence * 100) | 0}%)`);
    });

    // Live wake-word diagnostics: display the raw per-label scores so poor
    // recognition / constant false-positives are debuggable at a glance.
    vc.onActivationWordScores((scores, best) => {
      const dist = Object.entries(scores)
        .map(([l, p]) => `${l}=${p.toFixed(2)}`)
        .join("  ");
      $("word-scores").textContent = `${dist}   →  argmax=${best ?? "—"}`;
    });

    // ---- Step 1: connect mic ------------------------------------------------
    $("init").addEventListener("click", async () => {
      await vc.initialize();
      await vc.connectMic();
      log("mic connected; loop running");
      ($("cal-dsp") as HTMLButtonElement).disabled = false;
      ($("cal-phoneme") as HTMLButtonElement).disabled = false;
      ($("train-word") as HTMLButtonElement).disabled = false;
      ($("init") as HTMLButtonElement).disabled = true;
    });

    const cal = () => $("cal") as CalibrationPanel;
    const showCal = () => {
      cal().hidden = false;
    };

    // ---- Step 2: DSP level calibration (standalone / explicit) --------------
    $("cal-dsp").addEventListener("click", () => {
      showCal();
      log("DSP calibration — follow the bouncing ball (make sound in-window)");
      cal().configure({
        voiceCore: vc,
        dspOnly: true,
        onDspTuned: (rationale) => {
          log("DSP auto-tuned:");
          for (const r of rationale) log(`  · ${r}`);
        },
        onComplete: () => log("DSP calibration complete"),
      });
      log("DSP calibration ready — press Start on the panel");
    });

    // ---- Step 3: phoneme/sound discriminative training (explicit) -----------
    $("cal-phoneme").addEventListener("click", () => {
      const label =
        prompt("Sound/phoneme label to train:", "hiss") ?? "";
      if (!label) return;
      showCal();
      log(`phoneme training for '${label}' — follow the bouncing ball`);
      cal().configure({
        voiceCore: vc,
        // Explicitly do NOT tune DSP here; that's a separate step.
        autoTuneDsp: false,
        piggybackDspOnFirstSound: false,
        dspAlreadyTuned: vc.isDspTuned(),
        sounds: [
          {
            label,
            prompt: `Make the "${label}" sound in the window`,
            icon: "🔊",
            sustained: true,
            cycles: 3,
          },
        ],
        onSoundTrained: (l, frames) =>
          log(`trained '${l}' → ${frames} signal frame(s)`),
        onComplete: () => log(`phoneme '${label}' training complete`),
      });
      log("phoneme training ready — press Start on the panel");
    });

    // ---- Step 4: wake-word training (explicit, separate) --------------------
    $("train-word").addEventListener("click", () => {
      const word = prompt("Enter the activation word to train:", "hey-demo");
      if (!word) return;
      showCal();
      log(`wake-word calibration for '${word}' — follow the bouncing ball`);
      cal().configure({
        voiceCore: vc,
        autoTuneDsp: false,
        piggybackDspOnFirstSound: false,
        dspAlreadyTuned: vc.isDspTuned(),
        words: [
          {
            word,
            prompt: `Say "${word}"`,
            icon: "🔔",
            repetitions: 6,
            onEpoch: (e, loss, acc) => {
              if (e % 10 === 0) {
                log(
                  `  epoch ${e}: loss=${loss.toFixed(3)} acc=${acc.toFixed(3)}`,
                );
              }
            },
          },
        ],
        onWordTrained: (w, examples, finalAcc) =>
          log(
            `wake word '${w}' trained — examples=${examples} ` +
              `acc=${finalAcc.toFixed(3)}; watch live scores + tune threshold`,
          ),
        onComplete: () => log("wake-word calibration complete"),
      });
      log("wake-word calibration ready — press Start on the panel");
    });