// Verifies the consolidated row-actions kebab menu introduced as part
// of task #191 — the three previously-separate row overlays (delete,
// row color, merge cells) are now collapsed into a single menu so they
// no longer collide visually inside narrow cells. Each sub-action
// keeps its original testid, so the rest of the suite + any external
// tooling that targeted them keeps working without changes.
import { test, expect } from "@playwright/test";

async function loginViaUi(page, username, password) {
  await page.goto("/login");
  await page.locator("#username").fill(username);
  await page.locator("#password").fill(password);
  await Promise.all([
    page.waitForURL((url) => !url.pathname.endsWith("/login"), {
      timeout: 15_000,
    }),
    page.locator('form button[type="submit"]').click(),
  ]);
}

async function ensureEditModeOn(page) {
  const toggle = page.getByTestId("em-edit-mode-toggle");
  await expect(toggle).toBeVisible();
  if ((await toggle.getAttribute("aria-pressed")) === "false") {
    await toggle.click();
  }
  await expect(toggle).toHaveAttribute("aria-pressed", "true");
}

test("Schedule: row actions menu exposes delete + color + merge in a single popover and supports back navigation", async ({
  page,
}) => {
  await page.addInitScript(() => {
    try {
      window.localStorage.setItem("tx-lang", "en");
    } catch {
      /* localStorage may not be ready before navigation */
    }
  });
  await loginViaUi(page, "admin", "admin123");
  await page.goto("/executive-meetings");
  await ensureEditModeOn(page);

  // Exactly one consolidated kebab trigger per row, replacing what used
  // to be three separate overlay icons (Trash, Palette, Combine).
  // We require at least one row to be present — `>= 1` keeps the assertion
  // resilient to the dev DB seed varying across runs while still confirming
  // the kebab is mounted at all.
  const kebabs = page.locator('[data-testid^="em-row-actions-"]');
  const kebabCount = await kebabs.count();
  expect(kebabCount).toBeGreaterThanOrEqual(1);
  // Should be exactly one kebab per visible meeting row — rule out
  // duplicate triggers leaking across multiple cells of the same row.
  const visibleRows = await page
    .locator('tr[data-testid^="em-row-"]')
    .count();
  if (visibleRows > 0) {
    expect(kebabCount).toBe(visibleRows);
  }
  const kebab = kebabs.first();

  // Closed state: none of the legacy testids are mounted yet.
  await expect(page.locator('[data-testid^="em-delete-row-"]')).toHaveCount(0);
  await expect(page.getByTestId("em-row-color-trigger")).toHaveCount(0);
  await expect(page.locator('[data-testid^="em-merge-trigger-"]')).toHaveCount(
    0,
  );

  // The kebab is opacity-0 until row hover, so force the click.
  await kebab.click({ force: true });

  // Main menu shows all three legacy testids — preserves selectors used
  // elsewhere in the suite.
  await expect(page.locator('[data-testid^="em-delete-row-"]')).toHaveCount(1);
  await expect(page.getByTestId("em-row-color-trigger")).toHaveCount(1);
  await expect(page.locator('[data-testid^="em-merge-trigger-"]')).toHaveCount(
    1,
  );

  // Drill into the Row color sub-view: Delete + Merge get hidden,
  // a Back affordance + the swatch row appear.
  await page.getByTestId("em-row-color-trigger").click();
  await expect(page.locator('[data-testid^="em-delete-row-"]')).toHaveCount(0);
  await expect(page.locator('[data-testid^="em-merge-trigger-"]')).toHaveCount(
    0,
  );
  await expect(page.getByTestId("em-row-actions-back")).toBeVisible();

  // Back returns to the main 3-item view.
  await page.getByTestId("em-row-actions-back").click();
  await expect(page.locator('[data-testid^="em-delete-row-"]')).toHaveCount(1);
  await expect(page.getByTestId("em-row-color-trigger")).toHaveCount(1);
  await expect(page.locator('[data-testid^="em-merge-trigger-"]')).toHaveCount(
    1,
  );

  // Drill into the Merge sub-view — three merge options appear and the
  // other items disappear.
  await page.locator('[data-testid^="em-merge-trigger-"]').click();
  await expect(
    page.locator('[data-testid^="em-merge-opt-meeting-attendees-"]'),
  ).toHaveCount(1);
  await expect(
    page.locator('[data-testid^="em-merge-opt-meeting-time-"]'),
  ).toHaveCount(1);
  await expect(page.locator('[data-testid^="em-merge-opt-all-"]')).toHaveCount(
    1,
  );
  await expect(page.locator('[data-testid^="em-delete-row-"]')).toHaveCount(0);
  await expect(page.getByTestId("em-row-color-trigger")).toHaveCount(0);

  // Escape closes the popover entirely.
  await page.keyboard.press("Escape");
  await expect(page.locator('[data-testid^="em-delete-row-"]')).toHaveCount(0);
  await expect(page.getByTestId("em-row-color-trigger")).toHaveCount(0);
  await expect(page.locator('[data-testid^="em-merge-trigger-"]')).toHaveCount(
    0,
  );
});

// Regression guard: when a user picks a row color (e.g. red), the
// chosen tint must wrap the *entire* row, including the leading "#"
// (number) cell. Previously the # cell was hard-coded to bg-white and
// stayed visually disconnected from the rest of the row, which broke
// the meaning of the color (e.g. a "danger" red row with a white #
// looked half-flagged). This test picks the red swatch via the
// consolidated kebab menu, verifies the # cell adopts the same
// background as the meeting cell next to it, then resets to "default"
// and confirms the # cell returns to white.
test("Schedule: picking a row color tints the # cell to match the rest of the row", async ({
  page,
}) => {
  await page.addInitScript(() => {
    try {
      window.localStorage.setItem("tx-lang", "en");
    } catch {
      /* ignore */
    }
  });
  await loginViaUi(page, "admin", "admin123");
  await page.goto("/executive-meetings");
  // Reset persisted row colors so we always start from the default
  // (white) state, regardless of leftover state from prior runs.
  await page.evaluate(() => {
    try {
      window.localStorage.removeItem("em-schedule-row-colors-v1");
    } catch {
      /* ignore */
    }
  });
  await page.reload();
  await ensureEditModeOn(page);

  // We need a row whose # cell starts WHITE — i.e. it's not a current
  // meeting (data-current-meeting="true" → highlightColor wash) and
  // not a cancelled / status-flagged row (bg-red-600 badge). Both of
  // those higher-priority states would mask the regression.
  // `data-current-meeting` lives on the <tr> itself, so the
  // `:not([data-current-meeting="true"])` CSS selector is the
  // reliable way to exclude current rows (Playwright's
  // `filter({ hasNot })` looks at *descendants*, which never matches
  // an attribute on the parent <tr>). To also exclude the red-badge
  // rows, we then walk through the candidates and pick the first one
  // whose # cell's computed background is actually white.
  await page
    .locator('tr[data-testid^="em-row-"]')
    .first()
    .waitFor({ state: "visible", timeout: 10_000 });
  const candidateRows = page.locator(
    'tr[data-testid^="em-row-"]:not([data-current-meeting="true"])',
  );
  const candidateCount = await candidateRows.count();
  test.skip(
    candidateCount === 0,
    "No non-current rows present; cannot exercise the row-color tint regression.",
  );

  let targetRow = null;
  let numberCell = null;
  for (let i = 0; i < candidateCount; i++) {
    const row = candidateRows.nth(i);
    const cell = row.locator("td").first();
    const bg = await cell.evaluate(
      (el) => getComputedStyle(el).backgroundColor,
    );
    if (/255,\s*255,\s*255/.test(bg)) {
      targetRow = row;
      numberCell = cell;
      break;
    }
  }
  test.skip(
    targetRow === null,
    "No row with a default white # cell; every visible row is current or cancelled.",
  );
  await expect(targetRow).toBeVisible();

  // Open the row's kebab → Row color → red swatch.
  const kebab = targetRow.locator('[data-testid^="em-row-actions-"]').first();
  await kebab.click({ force: true });
  await page.getByTestId("em-row-color-trigger").click();
  await page.getByTestId("em-row-color-red").click();
  await page.keyboard.press("Escape");

  // The red swatch is #fee2e2 → rgb(254, 226, 226). The # cell should
  // now share that background. We poll because the row tint applies
  // through React state and may take a frame to settle.
  await expect
    .poll(
      async () =>
        numberCell.evaluate((el) => getComputedStyle(el).backgroundColor),
      { timeout: 5_000 },
    )
    .toMatch(/254,\s*226,\s*226/);

  // Reset to "default" and confirm the # cell returns to white.
  await kebab.click({ force: true });
  await page.getByTestId("em-row-color-trigger").click();
  await page.getByTestId("em-row-color-default").click();
  await page.keyboard.press("Escape");

  await expect
    .poll(
      async () =>
        numberCell.evaluate((el) => getComputedStyle(el).backgroundColor),
      { timeout: 5_000 },
    )
    .toMatch(/255,\s*255,\s*255/);
});
