import { expect } from "e2e";
import { test, WEB } from "./t3.ts";

test("Wide terminal glyphs and cursors occupy their assigned cells", WEB, async ({ t3, app, screen, browser }) => {
  const project = await t3.project("terminal-demo", { "demo.sh": "#!/bin/bash\nprintf 'あいうえお¦\\nabcdefghij¦\\n'\n" });
  await t3.thread({ project, title: "Terminal grid", messages: [] });
  await t3.open();
  await browser.locator("[data-thread-item]").first().waitFor({ state: "attached", timeout: 120_000 });
  const threadPath = await browser.evaluate(() => {
    const key = document.querySelector("[data-thread-item]")!.getAttribute("data-thread-item")!;
    const separator = key.indexOf(":");
    return "/" + encodeURIComponent(key.slice(0, separator)) + "/" + encodeURIComponent(key.slice(separator + 1));
  });
  await app.open("/settings/appearance#terminal-font");
  await screen.getByRole("textbox", "Terminal font family").fill("HackGen35 Console NF");
  await screen.getByRole("combobox", "Terminal font size").tap();
  await screen.getByRole("option", "18 px", { exact: true }).tap();
  await screen.getByRole("button", "Use dark mode").tap();
  await screen.getByRole("textbox", "Terminal font family").scrollIntoView();
  await app.screenshot("typography-font-18px");
  await app.open(threadPath);
  await screen.getByRole("button", "Toggle terminal drawer").tap();
  const input = screen.getByRole("textbox", "Terminal input");
  await input.waitFor({ state: "attached" });
  await browser.evaluate(() => {
    const state = { markers: {} as Record<string, number>, texts: [] as string[] };
    (window as any).__gridProof = state;
    const original = CanvasRenderingContext2D.prototype.fillText;
    CanvasRenderingContext2D.prototype.fillText = function(text, x, y, maxWidth) {
      if (this.canvas.parentElement?.querySelector('[aria-label="Terminal input"]')) {
        state.texts.push(text);
        state.texts = state.texts.slice(-500);
        if (text.includes("¦")) {
          const natural = this.measureText(text).width;
          const scale = maxWidth === undefined ? 1 : Math.min(1, maxWidth / natural);
          state.markers[String(y)] = x + this.measureText(text.split("¦")[0]!).width * scale;
        }
      }
      if (maxWidth === undefined) original.call(this, text, x, y);
      else original.call(this, text, x, y, maxWidth);
    };
  });
  const send = async (command: string) => {
    await input.fill(command);
    await input.press("Enter");
  };
  const settle = async () => {
    await browser.evaluate(async () => {
      const canvas = document.querySelector('[aria-label="Terminal input"]')?.parentElement?.querySelector("canvas")!;
      for (let attempt = 0; attempt < 20; attempt++) {
        const first = canvas.toDataURL();
        await new Promise(resolve => setTimeout(resolve, 500));
        if (first === canvas.toDataURL()) return;
      }
      throw new Error("Terminal canvas did not settle");
    });
  };
  await send("export PS1='$ '; printf '\\033[2 q'; clear");
  await send("printf 'あいうえお¦\\nabcdefghij¦\\n'");
  await settle();
  await app.screenshot("markers");
  const markerDelta = await browser.evaluate(() => {
    const positions = Object.values((window as any).__gridProof.markers) as number[];
    return Math.abs(positions[positions.length - 1]! - positions[positions.length - 2]!);
  });
  await input.fill("echo あいうえおかきくけこ");
  await settle();
  await app.screenshot("typed-input");
  await input.press("Control+c");
  await send("printf '\\033[2 qBlock: あいうえお\\033[2D'; read -rsn1");
  await settle();
  await app.screenshot("block-cursor-on-kana");
  await browser.evaluate(() => (document.activeElement as HTMLElement)?.blur());
  await settle();
  await app.screenshot("unfocused-outline-on-kana");
  await input.press("Control+c");
  await send("printf '\\033[4 qUnderline: あいうえお\\033[2D'; read -rsn1; printf '\\033[2 q'");
  await settle();
  await app.screenshot("underline-cursor-on-kana");
  console.log(JSON.stringify({ markerDelta }));
  expect(markerDelta).toBeLessThanOrEqual(1);
});
