// E2E: text element CRUD via the Text tab.
//
// What this guards
// ────────────────
// The most common editing flow in the app: open the Text tab, type a
// message, add it to the canvas, optionally edit or delete it.
// Vitest covers each piece (TextTab's local state, useDesignEditor's
// addElement, LayersPanel rendering) but not the whole pipeline as
// the user experiences it. This spec runs the actual integration.
//
// Why E2E rather than RTL component tests
// ───────────────────────────────────────
// TextTab is leaf-ish but composed with Sidebar, App's selectedElement
// logic, and useDesignEditor — testing it in RTL would require either
// mocking half the app (brittle) or rendering the real App with jsdom
// (which then breaks on Konva, because jsdom doesn't have a real
// canvas). Playwright drives the actual Chromium where everything
// works, including Konva.
//
// Selector strategy
// ─────────────────
// We use accessible queries throughout — getByRole, getByLabel,
// getByText. CSS class selectors would be tighter to the
// implementation and break on any styling refactor; the accessible
// roles describe what the user is looking at, which is stable across
// styling changes. The cost is occasional verbosity (`getByRole('tab',
// { name: 'Text' })` vs `.tab-text`) but the test stays valid as
// styles iterate.

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

test.beforeEach(async ({ page }) => {
  // Every test starts on the editor with empty state. See
  // gotoFreshEditor's docblock for the full reset sequence —
  // localStorage + service workers + caches all wiped.
  await gotoFreshEditor(page);
});

test('starts with zero elements', async ({ page }) => {
  // Empty-state check. The LayersPanel renders a banner instead of
  // a title when there are no elements, so getElementsCount returns
  // 0 by checking for the title's absence.
  expect(await getElementsCount(page)).toBe(0);
  // The empty-state message is the canonical "no layers" indicator.
  // We assert it's visible to confirm we're really on an empty
  // editor — a partially-loaded editor without elements would also
  // have a 0 count but no empty-state banner.
  await expect(page.getByText('No elements yet')).toBeVisible();
});

test('adds a text element via the sidebar and shows it in the Layers panel', async ({ page }) => {
  // The headline text-editing flow. addTextViaSidebar drives the
  // Text tab → fill textarea → click Add → wait for the layer count
  // to increment. After it returns, we make user-observable
  // assertions about what's on the canvas.
  await addTextViaSidebar(page, { text: 'Hello world' });

  // Count is now 1.
  expect(await getElementsCount(page)).toBe(1);

  // The LayersPanel row for the new element. LayersPanel uses
  // `getName(el)` which returns the text content for text elements,
  // so "Hello world" should appear as the row label.
  await expect(page.locator('.layers-item-name', { hasText: 'Hello world' })).toBeVisible();
});

test('adding two text elements lists both in the Layers panel, newest on top', async ({ page }) => {
  // LayersPanel docblock: "Lists all elements on the canvas in
  // render order (last in array = topmost)." The panel renders
  // top-down with the most recently added on top, mirroring how
  // users mentally stack layers in design tools.
  await addTextViaSidebar(page, { text: 'First' });
  await addTextViaSidebar(page, { text: 'Second' });

  expect(await getElementsCount(page)).toBe(2);

  // Order check: read all row names and verify "Second" precedes
  // "First". allTextContents resolves to an array in DOM order;
  // for a flex column that's top-to-bottom.
  const names = await page.locator('.layers-item-name').allTextContents();
  expect(names.indexOf('Second')).toBeLessThan(names.indexOf('First'));
});

test('editing the textarea after selecting a text element updates the layer name', async ({ page }) => {
  // Bidirectional editing: selecting an existing text element on
  // the canvas (or via the layers panel, which is what we do here
  // since we don't want to deal with canvas pixel coordinates)
  // should put the Text tab into edit mode against that element.
  // Typing into the textarea then flows back to update the element's
  // text on canvas AND in the layers panel.
  await addTextViaSidebar(page, { text: 'Original' });

  // Select the layer's row to make it the active element.
  await page.locator('.layers-item-main', { hasText: 'Original' }).click();

  // The Text tab auto-switches on selection (desktop auto-tab-switch
  // effect in App.jsx). The textarea should now contain the
  // selected element's text.
  const textarea = page.getByLabel('Your message');
  await expect(textarea).toHaveValue('Original');

  // Edit it. fill() clears + types, mirroring a user selecting all
  // and re-typing.
  await textarea.fill('Updated');

  // The layer name in the panel should reflect the new text. The
  // update is debounced (300ms in useDesignEditor.updateElement),
  // and the layer name reads from element.text directly, so it
  // shows the new value as soon as state propagates.
  await expect(page.locator('.layers-item-name', { hasText: 'Updated' })).toBeVisible();

  // And the count is still 1 — editing doesn't add a new element.
  expect(await getElementsCount(page)).toBe(1);
});

test('deleting a text element from the Layers panel removes it', async ({ page }) => {
  await addTextViaSidebar(page, { text: 'Doomed' });
  await addTextViaSidebar(page, { text: 'Survivor' });

  expect(await getElementsCount(page)).toBe(2);

  // Each row has a trash-can delete button as a sibling of the
  // main row button. LayersPanel labels it
  // `aria-label="Delete ${getName(element)}"`, so we can target
  // the specific row's delete button by accessible name.
  await page.getByRole('button', { name: 'Delete Doomed' }).click();

  expect(await getElementsCount(page)).toBe(1);
  // The remaining row is the one we didn't touch.
  await expect(page.locator('.layers-item-name', { hasText: 'Survivor' })).toBeVisible();
  // And the deleted row is gone.
  await expect(page.locator('.layers-item-name', { hasText: 'Doomed' })).toHaveCount(0);
});
