// Drives the sw-button gallery entry with TRUSTED key events via CDP,
// reading state through the shadow roots between steps.  Covers: standalone
// tab traversal + native activation + focused hotkey/i, the in-column claims
// (Enter/Space, column-scoped hotkeys, x/Delete on aux, i, disabled
// claim-and-decline), the two-column scoping proof, and activation feedback
// (the sw-held / sw-pressed classes; the 120ms flash is caught with a
// MutationObserver installed before the keystroke, not with a screenshot race).
// Ported from the Python original (demoscene.meta screenshots, stage-button).

import { type Scenario, type ScenarioContext, shot } from "../checks.ts";
import { openGallery } from "../gallery.ts";
import { sleep } from "../harness.ts";

// State through the shadow roots: which element holds real focus (host chain),
// each demo's log line, and the columns' focused rows.
const STATE_JS = `
(() => {
  const shell = document.querySelector("sw-gallery").shadowRoot;
  const out = { realFocus: null, standalone: null, columns: [] };
  const active = shell.activeElement;
  const sa = shell.querySelector("swg-button-standalone");
  if (sa) {
    const sr = sa.shadowRoot;
    out.standalone = {
      log: sr.querySelector(".log")?.textContent ?? "",
      logIsInfo: sr.querySelector(".log")?.classList.contains("info") ?? false,
      focusedLabel: sr.activeElement?.label ?? null,
    };
    if (sa === active) out.realFocus = "standalone/" + (sr.activeElement?.label ?? "?");
  }
  for (const col of shell.querySelectorAll("swg-button-column")) {
    const sr = col.shadowRoot;
    const container = sr.querySelector("[data-mc-container]");
    const label = col.getAttribute("label");
    if (col === active) out.realFocus = label + "/container";
    out.columns.push({
      label,
      rowFocused: sr.querySelector(".mc-row-focused")?.dataset?.mcRow ?? null,
      focusWithin: container.classList.contains("mc-focus-within"),
      log: sr.querySelector(".log")?.textContent ?? "",
      logIsInfo: sr.querySelector(".log")?.classList.contains("info") ?? false,
      cargoRows: sr.querySelectorAll("[data-mc-aux]").length,
    });
  }
  return JSON.stringify(out);
})()
`;

interface SwColumn {
	label: string;
	rowFocused: string | null;
	focusWithin: boolean;
	log: string;
	logIsInfo: boolean;
	cargoRows: number;
}

interface SwState {
	realFocus: string | null;
	standalone: { log: string; logIsInfo: boolean; focusedLabel: string | null };
	columns: SwColumn[];
}

const READY_JS =
	"document.readyState === 'complete' && !!document.querySelector('sw-gallery')?.shadowRoot?.querySelector('swg-button-standalone')";

async function run(ctx: ScenarioContext): Promise<void> {
	const { cdp, checks } = ctx;
	const check = (name: string, cond: boolean, detail = "") => checks.check(name, cond, detail);
	const st = async (): Promise<SwState> => JSON.parse((await cdp.js(STATE_JS)) as string);
	const centerOf = async (jsElExpr: string): Promise<{ x: number; y: number }> => {
		const box = await cdp.js(`
		(() => {
		  const r = (${jsElExpr}).getBoundingClientRect();
		  return JSON.stringify({x: r.x + r.width/2, y: r.y + r.height/2});
		})()
		`);
		return JSON.parse(box as string);
	};

	await openGallery(ctx, "#sw-button", READY_JS);

	// --- standalone variant ---
	// The gallery seats initial focus in the entry's first tab stop on open:
	// for #sw-button that is the first standalone button.  (Seating happens a
	// frame after render; poll briefly.)
	let landed = false;
	let s = await st();
	for (let i = 0; i < 20; i++) {
		s = await st();
		if (s.realFocus === "standalone/Dock at station") {
			landed = true;
			break;
		}
		await sleep(50);
	}
	check("initial-focus-seats-standalone-button", landed, JSON.stringify(s.realFocus));

	// Native Enter activation.
	await cdp.key("Enter");
	s = await st();
	check("standalone-enter-activates", s.standalone.log === "docked", s.standalone.log);

	// The focused button's own hotkey.  (Tab to units first: its hotkey is 7,
	// distinct from Enter's effect, so the log proves the hotkey path.)
	await cdp.key("Tab");
	s = await st();
	check("tab-to-units", s.realFocus === "standalone/units", JSON.stringify(s.realFocus));
	await cdp.key("7");
	s = await st();
	check("standalone-hotkey-activates", s.standalone.log === "units opened", s.standalone.log);

	// i for info on the focused button.
	await cdp.key("i");
	s = await st();
	check(
		"standalone-i-informs",
		s.standalone.log === "info: opens the units registry" && s.standalone.logIsInfo,
		s.standalone.log,
	);

	// The hotkey answers only while ITS button is focused: units' 7 from dock does nothing.
	await cdp.key("Tab", { shift: true }); // back to dock
	await cdp.key("7");
	s = await st();
	check(
		"standalone-hotkey-needs-focus",
		s.standalone.log === "info: opens the units registry",
		s.standalone.log,
	);

	// Disabled: still a tab stop, Enter dead, i answers.  (Four tabs: the
	// standalone row gained the active 'cruise' example at stage-migrate-infocard:
	// dock -> units -> transfer -> cruise -> self destruct.)
	for (let i = 0; i < 4; i++) {
		await cdp.key("Tab");
	}
	s = await st();
	check(
		"disabled-is-a-tab-stop",
		s.realFocus === "standalone/self destruct",
		JSON.stringify(s.realFocus),
	);
	await cdp.key("Enter");
	s = await st();
	check(
		"disabled-enter-dead",
		s.standalone.log === "info: opens the units registry",
		s.standalone.log,
	);
	await cdp.key("i");
	s = await st();
	check(
		"disabled-i-answers",
		s.standalone.log === "info: offline: requires command authorization",
		s.standalone.log,
	);

	// Mouse click on the dock button (trusted click via CDP).
	const SA_ROOT =
		'document.querySelector("sw-gallery").shadowRoot.querySelector("swg-button-standalone")';
	let pt = await centerOf(
		`[...${SA_ROOT}.shadowRoot.querySelectorAll("sw-button")].find(x => x.label === "Dock at station")`,
	);
	await cdp.click(pt.x, pt.y);
	s = await st();
	check("standalone-mouse-click-activates", s.standalone.log === "docked", s.standalone.log);

	// --- ops console (single column) variant ---
	// The click above moved real focus onto the dock button, so Tab from there
	// walks the remaining standalone buttons before reaching the container.
	landed = false;
	for (let i = 0; i < 8; i++) {
		await cdp.key("Tab");
		s = await st();
		if (s.realFocus === "ops console/container") {
			landed = true;
			break;
		}
	}
	check("tab-into-ops-console", landed, JSON.stringify(s.realFocus));
	const ops = (v: SwState): SwColumn => v.columns[0] as SwColumn;
	check("ops-first-row-dock", ops(s).rowFocused === "dock", JSON.stringify(ops(s)));

	// Enter on the focused button row.
	await cdp.key("Enter");
	s = await st();
	check("row-enter-activates", ops(s).log === "docked", ops(s).log);

	// Column-scoped hotkey: b (beacon) is a JUMP -- focus moves to the beacon
	// row, nothing activates (see-before-commit: Enter is the commit).
	await cdp.key("b");
	s = await st();
	check("column-hotkey-moves-focus", ops(s).rowFocused === "beacon", JSON.stringify(ops(s)));
	check("column-hotkey-does-not-activate", ops(s).log === "docked", ops(s).log);
	await cdp.key("Enter");
	s = await st();
	check("enter-after-jump-activates", ops(s).log === "beacon cycled", ops(s).log);

	// ... and d jumps back; Space mirrors Enter as the commit.
	await cdp.key("d");
	s = await st();
	check(
		"jump-back-to-dock",
		ops(s).rowFocused === "dock" && ops(s).log === "beacon cycled",
		JSON.stringify(ops(s)),
	);
	await cdp.key(" ");
	s = await st();
	check("row-space-activates", ops(s).log === "docked", ops(s).log);

	// i on the focused row's button.
	await cdp.key("i");
	s = await st();
	check(
		"row-i-informs",
		ops(s).log === "info: requests docking clearance" && ops(s).logIsInfo,
		ops(s).log,
	);

	// The disabled purge row: focusable, Space claims but declines.
	await cdp.key("ArrowDown");
	await cdp.key("ArrowDown");
	s = await st();
	check("disabled-row-focusable", ops(s).rowFocused === "purge", JSON.stringify(ops(s)));
	const MAIN_SCROLL =
		"document.querySelector('sw-gallery').shadowRoot.querySelector('main').scrollTop";
	const before = await cdp.js(MAIN_SCROLL);
	await cdp.key(" ");
	s = await st();
	const after = await cdp.js(MAIN_SCROLL);
	check(
		"disabled-space-claimed-not-acted",
		ops(s).log === "info: requests docking clearance",
		ops(s).log,
	);
	check("disabled-space-no-scroll", before === after, `${before} -> ${after}`);
	await cdp.key("i");
	s = await st();
	check(
		"disabled-row-i-answers",
		ops(s).log === "info: offline: filters already clean",
		ops(s).log,
	);

	// Cargo rows: x from main jettisons via the aux button.
	await cdp.key("ArrowDown");
	s = await st();
	check("into-cargo", ops(s).rowFocused === "ore crate", JSON.stringify(ops(s)));
	await cdp.key("Enter");
	s = await st();
	check("cargo-enter-inspects", ops(s).log === "ore crate inspected", ops(s).log);
	await cdp.key("x");
	s = await st();
	check(
		"x-jettisons-via-aux-button",
		ops(s).log === "ore crate jettisoned" && ops(s).cargoRows === 1,
		JSON.stringify(ops(s)),
	);
	// Delete works too; first arrow onto the surviving crate row's aux and use Enter instead.
	s = await st();
	check("focus-fell-to-next-crate", ops(s).rowFocused === "strut bundle", JSON.stringify(ops(s)));
	await cdp.key("ArrowRight");
	await cdp.key("i");
	s = await st();
	check("aux-i-informs", ops(s).log.includes("jettisons this crate"), ops(s).log);
	await cdp.key("Enter");
	s = await st();
	check(
		"enter-on-aux-jettisons",
		ops(s).log === "strut bundle jettisoned" && ops(s).cargoRows === 0,
		JSON.stringify(ops(s)),
	);

	// A trusted mouse click on a row-mode sw-button: exactly ONE activation.
	// The controller synthesizes Enter from unclaimed row clicks (pointer
	// activation for plain rows); sw-button claims its own clicks so the
	// synthesis skips it -- a broken dedup would log two activations here.
	const OPS_BEACON = `document.querySelector("sw-gallery").shadowRoot
	    .querySelector("swg-button-column").shadowRoot
	    .querySelector('[data-mc-row="beacon"] sw-button')`;
	await cdp.js(
		`window.__acts = 0; (${OPS_BEACON}).addEventListener('click', () => { window.__acts += 1 })`,
	);
	pt = await centerOf(OPS_BEACON);
	await cdp.click(pt.x, pt.y);
	s = await st();
	check("row-button-click-activates", ops(s).log === "beacon cycled", ops(s).log);
	const acts = await cdp.js("window.__acts");
	check("row-button-click-activates-exactly-once", acts === 1, JSON.stringify(acts));

	// --- scoping variant: two columns, both mark d ---
	await cdp.key("Tab");
	s = await st();
	check("tab-into-port", s.realFocus === "port console/container", JSON.stringify(s.realFocus));
	const port = (v: SwState): SwColumn => v.columns[1] as SwColumn;
	const starboard = (v: SwState): SwColumn => v.columns[2] as SwColumn;
	// The focused row's OWN hotkey does nothing: not a jump (already here),
	// not an activation (double-tap must not out-power single-tap).
	await cdp.key("d"); // focus sits on port's dock row
	s = await st();
	check(
		"own-hotkey-does-nothing",
		port(s).rowFocused === "dock" && port(s).log === "",
		JSON.stringify(port(s)),
	);
	// From another row, d jumps only in the focused column.
	await cdp.key("ArrowDown"); // -> beacon row
	await cdp.key("d");
	s = await st();
	check(
		"d-jumps-in-port",
		port(s).rowFocused === "dock" && port(s).log === "",
		JSON.stringify(port(s)),
	);
	check(
		"starboard-never-hears-d",
		starboard(s).rowFocused === "dock" && starboard(s).log === "",
		JSON.stringify(starboard(s)),
	);
	await cdp.key("Enter");
	s = await st();
	check("port-enter-commits", port(s).log === "docked", port(s).log);
	await cdp.key("Tab");
	s = await st();
	check(
		"tab-into-starboard",
		s.realFocus === "starboard console/container",
		JSON.stringify(s.realFocus),
	);
	await cdp.key("c"); // starboard's beacon letter: a jump
	s = await st();
	check(
		"starboard-own-letter-jumps",
		starboard(s).rowFocused === "beacon",
		JSON.stringify(starboard(s)),
	);
	await cdp.key("Enter");
	s = await st();
	check("starboard-enter-commits", starboard(s).log === "beacon cycled", starboard(s).log);
	await cdp.key("d"); // jump back to starboard's dock
	await cdp.key("Enter");
	s = await st();
	check(
		"d-jumps-in-starboard",
		starboard(s).rowFocused === "dock" && starboard(s).log === "docked",
		JSON.stringify(starboard(s)),
	);
	check(
		"port-log-unchanged",
		port(s).log === "docked" && !port(s).focusWithin,
		JSON.stringify(port(s)),
	);

	// --- scroll safety: a hotkey jump to an off-screen row scrolls it into view ---
	// Browsers do not scroll aria-activedescendant targets on their own; the
	// controller does, on every focus identity change.  Shrink the viewport so
	// the page overflows its scroller, park the scroller at the bottom (the ops
	// console's dock row far off-screen above), and jump to it with d.
	await cdp.cmd("Emulation.setDeviceMetricsOverride", {
		width: 1440,
		height: 400,
		deviceScaleFactor: 1,
		mobile: false,
	});
	landed = false;
	for (let i = 0; i < 16; i++) {
		await cdp.key("Tab");
		s = await st();
		if (s.realFocus === "ops console/container") {
			landed = true;
			break;
		}
	}
	check("scroll-tab-into-ops", landed, JSON.stringify(s.realFocus));
	check("scroll-memory-not-on-dock", ops(s).rowFocused !== "dock", JSON.stringify(ops(s)));
	const DOCK_VIS = `
	(() => {
	  const m = document.querySelector('sw-gallery').shadowRoot.querySelector('main');
	  const row = document.querySelector('sw-gallery').shadowRoot
	    .querySelector('swg-button-column').shadowRoot
	    .querySelector('[data-mc-row="dock"]');
	  const mr = m.getBoundingClientRect(), rr = row.getBoundingClientRect();
	  return JSON.stringify({scrollTop: m.scrollTop,
	    visible: rr.bottom > mr.top && rr.top < mr.bottom});
	})()`;
	await cdp.js(
		"const m = document.querySelector('sw-gallery').shadowRoot.querySelector('main'); m.scrollTop = m.scrollHeight",
	);
	const visBefore = JSON.parse((await cdp.js(DOCK_VIS)) as string) as {
		scrollTop: number;
		visible: boolean;
	};
	check("scroll-setup-dock-off-screen", !visBefore.visible, JSON.stringify(visBefore));
	await cdp.key("d"); // the jump: focus lands on dock, and the ring must be visible
	s = await st();
	const visAfter = JSON.parse((await cdp.js(DOCK_VIS)) as string) as {
		scrollTop: number;
		visible: boolean;
	};
	check("scroll-jump-landed-on-dock", ops(s).rowFocused === "dock", JSON.stringify(ops(s)));
	check(
		"scroll-jump-scrolled-into-view",
		visAfter.visible && visAfter.scrollTop !== visBefore.scrollTop,
		JSON.stringify([visBefore, visAfter]),
	);
	await shot(cdp, "hotkey-jump.png");
	await cdp.cmd("Emulation.clearDeviceMetricsOverride");

	// --- activation feedback: the sw-held / sw-pressed classes ---
	// Reload to reset the demos; initial focus re-seats on the first standalone button.
	await cdp.cmd("Page.reload", { ignoreCache: true });
	for (let i = 0; i < 50; i++) {
		if ((await cdp.js(READY_JS)) === true) break;
		await sleep(100);
	}
	for (let i = 0; i < 20; i++) {
		s = await st();
		if (s.realFocus === "standalone/Dock at station") break;
		await sleep(50);
	}

	// A recorder beats a screenshot race: the observer is installed before the
	// keystroke, so a 120ms flash cannot slip between CDP roundtrips.
	// Each arm disconnects the previous observer: stale observers otherwise
	// increment whatever __flash is current when their callbacks run.
	// find: a JS expression yielding the sw-button host to watch.
	const armFlash = async (find: string): Promise<void> => {
		await cdp.js(`
		(() => {
		  window.__flash?.obs.disconnect();
		  const b = ${find};
		  const obs = new MutationObserver(() => {
		    if (b.classList.contains("sw-pressed")) window.__flash.count += 1;
		  });
		  obs.observe(b, { attributes: true, attributeFilter: ["class"] });
		  window.__flash = { el: b, count: 0, obs };
		})()
		`);
	};
	const flashSeen = async (): Promise<boolean> => {
		for (let i = 0; i < 30; i++) {
			if ((await cdp.js("window.__flash.count > 0")) === true) return true;
			await sleep(20);
		}
		return false;
	};
	const flashCleared = async (): Promise<boolean> => {
		for (let i = 0; i < 30; i++) {
			if ((await cdp.js("!window.__flash.el.classList.contains('sw-pressed')")) === true)
				return true;
			await sleep(20);
		}
		return false;
	};

	const SA_BUTTON = (label: string) =>
		`[...document.querySelector("sw-gallery").shadowRoot
		    .querySelector("swg-button-standalone").shadowRoot
		    .querySelectorAll("sw-button")].find(x => x.label === "${label}")`;

	// Keyboard activation flashes: trusted Enter on the focused standalone button.
	await armFlash(SA_BUTTON("Dock at station"));
	await cdp.key("Enter");
	check("flash-on-trusted-enter", await flashSeen());
	check("flash-clears-after-press-ms", await flashCleared());

	// The focused button's hotkey flashes too (tab to units, hotkey 7).
	await cdp.key("Tab");
	await armFlash(SA_BUTTON("units"));
	await cdp.key("7");
	check("flash-on-trusted-hotkey", await flashSeen());
	check("hotkey-flash-clears", await flashCleared());

	// Held under a trusted pointer: press without release, look, release.
	pt = await centerOf(SA_BUTTON("Dock at station"));
	await cdp.mouseDown(pt.x, pt.y);
	const held = await cdp.js(`(${SA_BUTTON("Dock at station")}).classList.contains('sw-held')`);
	check("held-while-pointer-down", held === true);
	// The held look IS the flash look, and it sits still: the one screenshot
	// a 120ms transient can't reliably give us.
	await shot(cdp, "press-flash.png");
	await cdp.mouseUp(pt.x, pt.y);
	const released = await cdp.js(`!(${SA_BUTTON("Dock at station")}).classList.contains('sw-held')`);
	check("held-releases-on-pointer-up", released === true);
	// The release click flashes Dock; let that settle before the disabled check
	// so no mutation from it is in flight when the observer moves.
	for (let i = 0; i < 30; i++) {
		if (
			(await cdp.js(`(${SA_BUTTON("Dock at station")}).classList.contains('sw-pressed')`)) !== true
		)
			break;
		await sleep(20);
	}

	// Disabled: neither held nor flashed (nothing happened; nothing may claim otherwise).
	pt = await centerOf(SA_BUTTON("self destruct"));
	await armFlash(SA_BUTTON("self destruct"));
	await cdp.mouseDown(pt.x, pt.y);
	const heldDisabled = await cdp.js(
		`(${SA_BUTTON("self destruct")}).classList.contains('sw-held')`,
	);
	await cdp.mouseUp(pt.x, pt.y);
	check("disabled-never-held", heldDisabled !== true);
	await sleep(300); // give a wrong flash time to have happened
	check("disabled-never-flashed", (await cdp.js("window.__flash.count > 0")) !== true);

	// Row mode: Enter on the focused row flashes its button; a column-scoped
	// hotkey JUMP flashes nothing (a focus move is navigation -- ring movement
	// and flash must stay distinct vocabularies), and the Enter that follows
	// flashes the landed row's button.
	landed = false;
	for (let i = 0; i < 8; i++) {
		await cdp.key("Tab");
		s = await st();
		if (s.realFocus === "ops console/container") {
			landed = true;
			break;
		}
	}
	check("feedback-tab-into-ops", landed, JSON.stringify(s.realFocus));
	const COL_BUTTON = (row: string) =>
		`document.querySelector("sw-gallery").shadowRoot
		    .querySelector("swg-button-column").shadowRoot
		    .querySelector('[data-mc-row="${row}"] sw-button')`;
	await armFlash(COL_BUTTON("dock"));
	await cdp.key("Enter");
	check("row-flash-on-trusted-enter", await flashSeen());
	check("row-flash-clears", await flashCleared());
	await armFlash(COL_BUTTON("beacon"));
	await cdp.key("b"); // a jump: focus moves to beacon, nothing activates
	await sleep(300); // give a wrong flash time to have happened
	s = await st();
	check("no-flash-on-hotkey-jump", (await cdp.js("window.__flash.count > 0")) !== true);
	check("jump-landed-on-beacon", ops(s).rowFocused === "beacon", JSON.stringify(ops(s)));
	await cdp.key("Enter"); // the commit: now the flash names the actor
	check("flash-on-enter-after-jump", await flashSeen());

	// Plain megacolumn rows (no widget): the demo host flashes the row itself.
	await cdp.js("location.hash = '#megacolumn'");
	await cdp.cmd("Page.reload", { ignoreCache: true });
	for (let i = 0; i < 50; i++) {
		if (
			(await cdp.js(
				"document.readyState === 'complete' && !!document.querySelector('sw-gallery')?.shadowRoot?.querySelector('swg-megacolumn-demo')",
			)) === true
		)
			break;
		await sleep(100);
	}
	landed = false;
	for (let i = 0; i < 20; i++) {
		if (
			(await cdp.js(`document.querySelector("sw-gallery").shadowRoot
				.querySelector("swg-megacolumn-demo").shadowRoot
				.querySelector("[data-mc-container]").classList.contains("mc-focus-within")`)) === true
		) {
			landed = true;
			break;
		}
		await sleep(50);
	}
	check("plain-rows-focus-seated", landed);
	await armFlash(`document.querySelector("sw-gallery").shadowRoot
	    .querySelector("swg-megacolumn-demo").shadowRoot
	    .querySelector('[data-mc-row="beacon"]')`);
	await cdp.key("Enter");
	check("plain-row-flash-on-trusted-enter", await flashSeen());
	check("plain-row-flash-clears", await flashCleared());
}

export const swDrive: Scenario = { name: "sw", run };
