import { expect, test, type Locator, type Page } from "@playwright/test";

// Regression guard for #366 (macOS): the sidebar's "Agent Orchestrator" brand
// must never sit under the fixed TitlebarNav cluster, and the wordmark must stay
// readable. The original bug was board routes (`/` and `/projects/:id`) having no
// topbar, so the sidebar stayed at top-0 and the brand landed in the cluster's
// 56px lane. It is now fixed structurally — the shell renders the topbar on every
// route, so the sidebar always hangs below the titlebar band — and these tests
// lock that invariant in: if a topbar-less route is ever reintroduced, they fail.
//
// macOS-only: TitlebarNav (and the bug) gate on navigator.userAgent looking like
// a Mac, read once at module load. Force a Mac UA so this is deterministic
// regardless of the host/CI OS.
test.use({
	userAgent:
		"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
});

const brand = (page: Page) => page.getByText("Agent Orchestrator", { exact: true });

// Two boxes overlap iff they intersect on both axes.
function overlaps(a: { x: number; y: number; width: number; height: number }, b: typeof a) {
	return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
}

// The brand <span> has `truncate` (overflow:hidden), so it stays "visible" even
// when clipped to nothing. Compare scroll vs client width to prove the wordmark
// is actually fully rendered, not just present-but-clipped.
async function isTruncated(span: Locator) {
	return span.evaluate((el) => el.scrollWidth > el.clientWidth + 1);
}

async function expectBrandClearsCluster(page: Page) {
	const cluster = page.locator(".titlebar-nav");
	await expect(cluster).toBeVisible();
	const span = brand(page);
	await expect(span).toBeVisible();

	const clusterBox = await cluster.boundingBox();
	const brandBox = await span.boundingBox();
	expect(clusterBox).not.toBeNull();
	expect(brandBox).not.toBeNull();

	expect(overlaps(brandBox!, clusterBox!)).toBe(false);
	expect(await isTruncated(span)).toBe(false);
}

test("home board route: brand clears the macOS titlebar cluster and stays readable", async ({ page }) => {
	await page.goto("/");
	await expect(page.getByText("Projects")).toBeVisible();
	await expectBrandClearsCluster(page);
});

test("project board route: brand clears the macOS titlebar cluster and stays readable", async ({ page }) => {
	await page.goto("/");
	await expect(page.getByText("Projects")).toBeVisible();

	// In-app nav to /projects/:id (a hard load boots the router at the board).
	await page.getByRole("button", { name: "Open api-gateway dashboard" }).click();
	// The active project row marks itself aria-current=page once navigation lands.
	await expect(page.locator('[aria-current="page"]')).toBeVisible();

	await expectBrandClearsCluster(page);
});

test("brand stays put and readable when navigating board → session", async ({ page }) => {
	await page.goto("/");
	await expect(page.getByText("Projects")).toBeVisible();

	const boardBrandBox = await brand(page).boundingBox();
	expect(boardBrandBox).not.toBeNull();

	await page.getByRole("button", { name: "Open Split terminal mux responsibilities" }).click();
	await expect(page.locator(".dashboard-app-header")).toBeVisible();

	const sessionBrandBox = await brand(page).boundingBox();
	expect(sessionBrandBox).not.toBeNull();
	// Persistent shell element: no vertical/horizontal jump across the transition.
	expect(Math.abs(sessionBrandBox!.x - boardBrandBox!.x)).toBeLessThanOrEqual(1);
	expect(Math.abs(sessionBrandBox!.y - boardBrandBox!.y)).toBeLessThanOrEqual(1);
	await expectBrandClearsCluster(page);
});
