/**
 * C-series (UI): Shopping list CRUD flows — create, complete, trash.
 *
 * Live Keycloak login per test (no cached storageState). See fixtures/login.ts
 * and the comment in $lib/supabase for why the Supabase client uses a
 * pass-through lock.
 */
import { test, expect, type Page } from '@playwright/test';
import { USERS } from '../fixtures/users.js';
import { loginAs } from '../fixtures/login.js';

const PLACEHOLDER_NEW_LIST = /weekly shop|compra semanal/i;

/** Heading for a list — may render as h2 (featured card) or h3 (grid item). */
function listHeading(page: Page, name: string) {
	return page.getByRole('heading', { name: new RegExp(`^${name}$`) });
}

/**
 * The action menu button inside the card for a specific list. The DOM has one
 * "List actions" button per active card, so we scope by the card wrapper
 * (ancestor of the heading) — this avoids the strict-mode ambiguity of picking
 * from many grid siblings.
 */
function cardActionsButton(page: Page, name: string) {
	// The card wrapper is the `<div class="relative group">` two levels above
	// the `<h3>` (or one level above `<h2>` for featured). An xpath that walks
	// up until it finds the button gives us a stable selector.
	return page
		.locator(
			`xpath=//h2[normalize-space()="${name}"]/ancestor::div[.//button[@aria-label]][1]` +
				` | //h3[normalize-space()="${name}"]/ancestor::div[.//button[@aria-label]][1]`
		)
		.first()
		.getByRole('button', { name: /list actions|acciones de lista/i });
}

async function gotoListsClean(page: Page) {
	await page.goto('/lists');
	// The masthead "New list" button is the anchor now (replaces the sticky input).
	await expect(
		page.getByRole('button', { name: /new list|nueva lista/i })
	).toBeVisible({ timeout: 15_000 });
}

async function createList(page: Page, name: string) {
	await page.getByRole('button', { name: /new list|nueva lista/i }).click();
	// Lands on /lists/[id]; the editable title input carries the placeholder.
	await expect(page).toHaveURL(/\/lists\/[0-9a-f-]+$/, { timeout: 10_000 });
	const titleInput = page.getByPlaceholder(PLACEHOLDER_NEW_LIST);
	await expect(titleInput).toBeVisible({ timeout: 10_000 });
	await titleInput.fill(name);
	// Blur + wait for the 500 ms autosave debounce + buffer.
	await titleInput.blur();
	await page.waitForTimeout(900);
	// Navigate back to /lists to give callers a consistent starting state.
	await page.goto('/lists');
	await expect(listHeading(page, name)).toBeVisible({ timeout: 10_000 });
}

test.describe('Shopping lists — member (Borja)', () => {
	test.beforeEach(async ({ page }) => {
		await loginAs(page, USERS.borja);
	});

	test('C-07: create a new shopping list', async ({ page }) => {
		await gotoListsClean(page);

		const listName = `E2E list ${Date.now()}`;
		await createList(page, listName);

		// Reload to prove the list persists server-side (not just optimistic).
		await page.reload();
		await expect(listHeading(page, listName)).toBeVisible({ timeout: 15_000 });
	});

	test('C-11: soft-delete a list — it moves to trash', async ({ page }) => {
		await gotoListsClean(page);

		const listName = `Trash me ${Date.now()}`;
		await createList(page, listName);

		// Scope the menu button to the card containing our new heading — it's
		// the nearest ancestor that also has a List actions button.
		await cardActionsButton(page, listName).click();
		await page.getByRole('button', { name: /move to trash|enviar a la papelera/i }).click();

		await expect(listHeading(page, listName)).not.toBeVisible({ timeout: 5_000 });

		// It shows up under the Trash drawer.
		await page.getByRole('button', { name: /^trash$|^papelera$/i }).first().click();
		await expect(page.getByText(listName)).toBeVisible({ timeout: 5_000 });
	});

	test('C-12: archive a list — it moves out of the active grid', async ({ page }) => {
		await gotoListsClean(page);

		const listName = `Archive me ${Date.now()}`;
		await createList(page, listName);

		await cardActionsButton(page, listName).click();
		await page.getByRole('button', { name: /^archive$|^archivar$/i }).click();

		// Active grid no longer shows the archived list.
		await expect(listHeading(page, listName)).not.toBeVisible({ timeout: 5_000 });
	});
});

test.describe('Shopping lists — guest (David, read-only)', () => {
	test.beforeEach(async ({ page }) => {
		await loginAs(page, USERS.david);
	});

	test('C-13: guest sees the seed list (read-only)', async ({ page }) => {
		await page.goto('/lists');
		await expect(
			page.getByRole('button', { name: /Casa García-López/ })
		).toBeVisible({ timeout: 15_000 });

		await expect(listHeading(page, 'Weekly shop')).toBeVisible({ timeout: 15_000 });

		// If the create-list input is even rendered for guests, typing into it
		// and pressing Enter must NOT create a committed list (RLS blocks the
		// insert; the optimistic row rolls back).
		const input = page.getByPlaceholder(PLACEHOLDER_NEW_LIST);
		if (await input.isVisible({ timeout: 2_000 }).catch(() => false)) {
			const name = `David sneaky ${Date.now()}`;
			await input.fill(name);
			await input.press('Enter');
			await page.waitForTimeout(1_500);
			await expect(listHeading(page, name)).not.toBeVisible({ timeout: 2_000 });
		}
	});
});
