// E2E: state persistence across page reloads.
//
// What this guards
// ────────────────
// The persistence layer (src/utils/persistence.js + App.jsx's restore
// effect) is responsible for keeping the user's work alive across
// reloads. The hook-level tests cover the save/load round-trip; the
// utility tests cover the JSON shape. What's NOT covered there: the
// actual restore effect in App.jsx, the timing of replaceElements
// + initializeHistory, and the StrictMode dual-mount we fixed on
// May 23 (which only manifests in dev mode and was the motivation
// for the replaceElements ID-preservation work).
//
// This spec drives a real reload and asserts state survives. It also
// exercises the post-May-23 initializeHistory + replaceElements
// pipeline end-to-end: if a future regression brings back the
// blank-canvas-on-undo bug, the "undo doesn't blank the canvas after
// reload" test below will catch it.

import { test, expect } from '@playwright/test';
import {
  gotoFreshEditor,
  addTextViaSidebar,
  getElementsCount,
  pressUndo,
} from './fixtures/editor.js';

test.beforeEach(async ({ page }) => {
  await gotoFreshEditor(page);
});

test('elements survive a page reload', async ({ page }) => {
  // The basic persistence promise: add elements, reload, find them
  // still there.
  await addTextViaSidebar(page, { text: 'Surviving text' });
  expect(await getElementsCount(page)).toBe(1);

  // Reload. Note we use page.reload() rather than gotoFreshEditor —
  // we want to PRESERVE state across the reload, not wipe it. The
  // localStorage save happens via the auto-save effect in App.jsx.
  // That effect skips its first run; subsequent state changes
  // trigger savePersistedState. By the time we reload, the addText
  // change has been saved.
  await page.reload();

  // Wait for the editor to remount + restore. We can't use
  // gotoFreshEditor's wait helpers directly; just wait for the
  // canvas-area and the restored layer to appear.
  await page.locator('.canvas-area').waitFor({ state: 'visible' });
  await expect(page.locator('.layers-item-name', { hasText: 'Surviving text' })).toBeVisible();
  expect(await getElementsCount(page)).toBe(1);
});

test('post-restore undo lands on the restored snapshot, not an empty canvas (May 22 regression)', async ({ page }) => {
  // The headline May 22 bug: after restoration, the FIRST user
  // action followed by Cmd+Z would walk past the restored snapshot
  // and land on an empty canvas. Root cause was initializeHistory
  // unconditionally wiping history to [empty], destroying the
  // snapshot replaceElements had pushed during restoration.
  //
  // Post-fix sequence:
  //   1. Add element (saved via auto-save).
  //   2. Reload — restoration runs, replaceElements pushes the
  //      restored snapshot at idx 0, initializeHistory is a no-op.
  //   3. Add another element — idx 1 (the just-added).
  //   4. Cmd+Z — walks idx 1 → 0, restores the snapshot from
  //      step 2 (one element), NOT an empty canvas.
  //
  // Pre-fix, step 4 would land on the empty canvas because the
  // floor at idx 0 was an empty array (wiped by initializeHistory).
  await addTextViaSidebar(page, { text: 'Restored on reload' });
  await page.reload();
  await page.locator('.canvas-area').waitFor({ state: 'visible' });
  await expect(page.locator('.layers-item-name', { hasText: 'Restored on reload' })).toBeVisible();

  // Now add a second element. This is the "first user action after
  // restoration" the bug was triggered by.
  await addTextViaSidebar(page, { text: 'New action' });
  expect(await getElementsCount(page)).toBe(2);

  // Cmd+Z: must land on the post-restore state (one element), not
  // empty. This is the assertion that would fail pre-fix.
  await pressUndo(page);
  expect(await getElementsCount(page)).toBe(1);
  await expect(page.locator('.layers-item-name', { hasText: 'Restored on reload' })).toBeVisible();
  await expect(page.locator('.layers-item-name', { hasText: 'New action' })).toHaveCount(0);
});

test('multiple elements all survive a reload', async ({ page }) => {
  // Persistence isn't a single-element special case — the entire
  // elements array gets saved. We add several with distinct text
  // so we can verify each one survives by name.
  await addTextViaSidebar(page, { text: 'Alpha' });
  await addTextViaSidebar(page, { text: 'Bravo' });
  await addTextViaSidebar(page, { text: 'Charlie' });
  expect(await getElementsCount(page)).toBe(3);

  await page.reload();
  await page.locator('.canvas-area').waitFor({ state: 'visible' });

  // All three should be in the layers panel.
  expect(await getElementsCount(page)).toBe(3);
  await expect(page.locator('.layers-item-name', { hasText: 'Alpha' })).toBeVisible();
  await expect(page.locator('.layers-item-name', { hasText: 'Bravo' })).toBeVisible();
  await expect(page.locator('.layers-item-name', { hasText: 'Charlie' })).toBeVisible();
});

test('shirt color preference survives a reload', async ({ page }) => {
  // Non-elements persistence: shirt color, size, recent colors,
  // etc. are also saved. Picking a non-default shirt color and
  // verifying it survives confirms the preferences slice of the
  // save shape works.
  //
  // Two UI quirks worth knowing for this test:
  //
  //   1. ShirtOptionsPanel is COLLAPSED by default — it shows only
  //      the summary chip + price until the user clicks to expand.
  //      The color radios live in the expanded content with
  //      `tabIndex={expanded ? 0 : -1}` so they're not even
  //      focusable when collapsed. We have to expand the panel
  //      before driving the radios. The collapse state is
  //      component-local and resets to collapsed on every fresh
  //      mount, so we have to re-expand after the reload too.
  //
  //   2. The color picker uses `role="radio"` with `aria-label`
  //      matching the bare color name (e.g. "Black") and uses
  //      `aria-checked` for the selected indicator. Earlier I
  //      guessed at `role="button"` + `aria-pressed`; both were
  //      wrong. The constants/shirt.js label is the source of
  //      truth for the accessible name.
  const optionsToggle = page.getByRole('button', { name: /shirt options/i });
  await optionsToggle.click();

  const blackRadio = page.getByRole('radio', { name: 'Black' });
  await blackRadio.click();
  // Wait for the aria-checked update before reloading so we know
  // the click registered and the save effect has had a chance to
  // fire.
  await expect(blackRadio).toHaveAttribute('aria-checked', 'true');

  await page.reload();
  await page.locator('.canvas-area').waitFor({ state: 'visible' });

  // After reload the panel is collapsed again (state is
  // component-local). Expand to inspect the restored selection.
  await page.getByRole('button', { name: /shirt options/i }).click();
  await expect(page.getByRole('radio', { name: 'Black' })).toHaveAttribute('aria-checked', 'true');
});
