import { readFileSync } from "node:fs";
import { expect } from "e2e";
import { test, WEB } from "./t3.ts";

// Generate speech.mp3 with espeak-ng from the accompanying speech.txt, then ffmpeg.
// T3_DICTATION_AUDIO supplies that recording; only the microphone is substituted.
test("Live dictation keeps the newest words visible", WEB, async ({ t3, app, screen, browser }) => {
  const audio = new Uint8Array(readFileSync(process.env.T3_DICTATION_AUDIO!));
  await browser.route("**/dictation-fixture.mp3", route => route.fulfill({ body: audio, contentType: "audio/mpeg" }));
  await browser.addInitScript(() => {
    navigator.mediaDevices.getUserMedia = async () => {
      const context = new AudioContext();
      await context.resume();
      const data = await (await fetch("/dictation-fixture.mp3")).arrayBuffer();
      const source = context.createBufferSource();
      source.buffer = await context.decodeAudioData(data);
      const destination = context.createMediaStreamDestination();
      source.connect(destination);
      source.loop = true;
      source.start();
      return destination.stream;
    };
  });
  const project = await t3.project("dictation-proof");
  const thread = await t3.thread({ project, title: "Live dictation proof", messages: ["Ready for dictation."] });
  await t3.open("/settings/voice");
  await screen.getByRole("combobox", "Transcribe with").tap();
  await screen.getByRole("option", "Scenario", { exact: true }).tap();
  await screen.getByRole("searchbox", "Search transcription models").fill("Parakeet Unified English 0.6B");
  await screen.getByRole("button", "Download", { exact: true }).tap();
  // Setup waits do not assert the regression.
  await browser.locator("button").filter({ hasText: /^(Selected|Use model)$/ }).waitFor({ timeout: 180000 });
  const use = screen.getByRole("button", "Use model", { exact: true });
  if (await use.count()) await use.tap();
  await t3.open(thread);
  await screen.getByRole("button", "Start voice input", { exact: true }).tap();
  await browser.locator('[aria-label="Live transcription"]').filter({ hasText: /followed along/i }).waitFor({ timeout: 140000 });
  await app.screenshot("newest-words");
  const visibleEnd = await browser.evaluate(() => {
    const box = document.querySelector('[aria-label="Live transcription"]')!;
    const walker = document.createTreeWalker(box, NodeFilter.SHOW_TEXT);
    let last: Node | null = null;
    while (walker.nextNode()) if (walker.currentNode.textContent?.trim()) last = walker.currentNode;
    const range = document.createRange(); range.selectNodeContents(last!);
    const lines = [...range.getClientRects()];
    const end = lines[lines.length - 1]!;
    const bounds = box.getBoundingClientRect();
    return box.scrollHeight > box.clientHeight && end.bottom <= bounds.bottom && end.top >= bounds.top;
  });
  await browser.locator('[aria-label="Live transcription"]').hover();
  await browser.mouse.wheel(0, -2000);
  await app.screenshot("rereading-start");
  await browser.evaluate(() => {
    const box = document.querySelector('[aria-label="Live transcription"]')!;
    const initial = box.textContent!.length;
    const observer = new MutationObserver(() => {
      if (box.textContent!.length > initial + 120) {
        observer.disconnect(); box.setAttribute("data-reread-growth", "ready");
      }
    });
    observer.observe(box, { childList: true, subtree: true, characterData: true });
    return true;
  });
  await browser.locator('[data-reread-growth="ready"]').waitFor({ timeout: 60000 });
  await app.screenshot("rereading-new-words-arrive");
  const firstStillVisible = await browser.evaluate(() => {
    const box = document.querySelector('[aria-label="Live transcription"]')!;
    const walker = document.createTreeWalker(box, NodeFilter.SHOW_TEXT);
    const first = walker.nextNode()!;
    const range = document.createRange(); range.setStart(first, 0); range.setEnd(first, 10);
    const start = range.getBoundingClientRect(); const bounds = box.getBoundingClientRect();
    return start.top >= bounds.top && start.bottom <= bounds.bottom;
  });
  await expect({ newestWordsVisible: visibleEnd, rereadingRespected: firstStillVisible }).toEqual({ newestWordsVisible: true, rereadingRespected: true });
});
