import { test, expect } from '@playwright/test';
import { editableCell, firstEntryRow, firstRealPickerOption } from './helpers';

/**
 * T-E2E-002 — inline cell editing + the bulk engine's verified-count toast
 * with Undo (entries-editing.md M5).
 *
 * Two behaviours, both asserted on state (not pixels):
 *
 *  1. Inline edit persists AND preserves URL state. Editing a cell PATCHes
 *     and updates the query cache in place — it must NOT navigate, so the
 *     filtered/sorted query string is byte-identical after the save. Covers
 *     duration (text cell) and project (EntityPicker cascade cell).
 *
 *  2. Bulk reassign over a filtered selection shows a verified result toast
 *     whose Undo restores the prior values (audit before-images, ADR #007).
 *
 * Seed expectation: the migrated fixture graph has PENDING (editable,
 * unlocked) entries. status=pending excludes both locked (applied) and
 * broken (blocked) rows, so every visible row is editable.
 */

// A deliberately filtered + sorted URL — the state we assert survives a save.
const FILTERED_URL = '/entries?status=pending&date=all&sort=date&dir=desc';

test.describe('T-E2E-002 cell-edit + bulk-with-undo', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto(FILTERED_URL);
    await expect(page.getByRole('heading', { level: 1, name: 'Entries' })).toBeVisible();
    await expect(firstEntryRow(page)).toBeVisible();
  });

  test('inline duration + project edits persist and keep the URL query state', async ({
    page,
  }) => {
    // capture the NORMALIZED url after load (TanStack may reshape the query)
    const urlBefore = page.url();

    // --- duration: a plain text cell editor ------------------------------
    const hoursCell = await editableCell(page, firstEntryRow(page), 'Hours');
    const current = (await hoursCell.innerText()).trim();
    // choose a distinct value so the editor's "unchanged == cancel" guard
    // (EntryCells.tsx TextCellEditor) doesn't short-circuit the save
    const nextDuration = current === '5:43:21' ? '6:43:21' : '5:43:21';

    await hoursCell.click();
    const durationEditor = page.locator('input.cell-editor-input');
    await expect(durationEditor).toBeVisible();
    await durationEditor.fill(nextDuration);
    await durationEditor.press('Enter');

    // duration is money-affecting → pessimistic: the cell re-renders from the
    // verified server row. The web-first assertion polls until it lands.
    await expect(await editableCell(page, firstEntryRow(page), 'Hours')).toHaveText(nextDuration);
    expect(page.url()).toBe(urlBefore); // inline edit never navigates

    // --- project: the EntityPicker cascade cell --------------------------
    const projectCell = await editableCell(page, firstEntryRow(page), 'Project');
    await projectCell.click();

    // startAt='project' → ChainCellEditor opens straight on the project
    // EntityPicker (aria-label "Pick project").
    const picker = page.getByRole('combobox', { name: 'Pick project' });
    await expect(picker).toBeVisible();
    await picker.click(); // open the listbox

    const option = firstRealPickerOption(page);
    await expect(option).toBeVisible();
    // EntityPicker option label is "{client} : {project}" (EditProject.label);
    // the entries Project cell shows just the project name (r.project). Take
    // the segment after the last " : " to compare.
    // SELECTOR-REVIEW: confirm the "{client} : {project}" label convention
    // against the running picker.
    const optionLabel = (await option.locator('.picker-label').innerText()).trim();
    const projectName = optionLabel.includes(' : ')
      ? optionLabel.split(' : ').slice(-1)[0].trim()
      : optionLabel;
    await option.click();

    await expect(await editableCell(page, firstEntryRow(page), 'Project')).toHaveText(projectName);
    expect(page.url()).toBe(urlBefore); // still no navigation
  });

  test('bulk reassign shows a verified toast whose Undo restores prior values', async ({
    page,
  }) => {
    // --- select a filtered subset ----------------------------------------
    const rowCheckboxes = page.getByRole('checkbox', { name: 'Select row' });
    await expect(rowCheckboxes.first()).toBeVisible();
    const toSelect = Math.min(2, await rowCheckboxes.count());
    for (let i = 0; i < toSelect; i++) await rowCheckboxes.nth(i).check();

    const bulkBar = page.getByRole('region', { name: 'Bulk actions' });
    await expect(bulkBar).toContainText(`${toSelect} selected`); // exact quoted count

    // --- open Edit fields, reassign the team member ----------------------
    await bulkBar.getByRole('button', { name: /^Edit fields/ }).click();
    // SELECTOR-REVIEW: the bulk panel has two unlabelled EntityPickers
    // (Project + Team member); scope by the field's <span> caption.
    const memberPicker = page
      .locator('.bulk-panel .bulk-field')
      .filter({ hasText: 'Team member' })
      .getByRole('combobox');
    await memberPicker.click();
    const memberOption = firstRealPickerOption(page);
    await expect(memberOption).toBeVisible();
    await memberOption.click();

    // --- confirm the count-quoting strip ---------------------------------
    await bulkBar.getByRole('button', { name: /^Reassign/ }).click();
    const confirm = page.getByRole('alertdialog', { name: 'Confirm bulk action' });
    await expect(confirm).toContainText(`${toSelect} entr`);
    await confirm.getByRole('button', { name: /^Confirm/ }).click();

    // --- verified result toast WITH an Undo action -----------------------
    const resultToast = page.locator('.toast-success', { hasText: 'Reassigned' });
    await expect(resultToast).toBeVisible();
    await expect(resultToast).toContainText('verified');

    // --- Undo restores the prior worker assignment -----------------------
    await resultToast.getByRole('button', { name: 'Undo' }).click();
    await expect(page.locator('.toast', { hasText: /restored/ })).toBeVisible();
  });
});
