import { test, expect, type Page } from '@playwright/test';
import { VISUAL } from '../visual.config';

/**
 * Theme baselines.
 *
 * These exist so a change to the shared theme can be reviewed as a diff rather
 * than trusted. The gallery route renders every primitive the theme controls,
 * so one screenshot per app per scheme per width covers the whole surface.
 *
 * Update baselines deliberately, never reflexively:
 *   npx playwright test -c apps/e2e/visual.config.ts --update-snapshots
 * A diff you did not intend is the entire point of the suite.
 */

const WIDTHS = [
  { name: 'desktop', width: 1440, height: 1200 },
  { name: 'tablet', width: 768, height: 1200 },
] as const;

const SCHEMES = ['light', 'dark'] as const;

const APPS = [
  { name: 'backoffice', url: VISUAL.backofficeUrl },
  { name: 'portal', url: VISUAL.portalUrl },
] as const;

/**
 * Set the scheme the way the app itself does — the pre-paint script in
 * index.html reads this key. Setting it before navigation means the very first
 * paint is already correct, so no screenshot catches a flash of the wrong one.
 */
async function gotoGallery(page: Page, baseUrl: string, scheme: string) {
  await page.addInitScript((value) => {
    window.localStorage.setItem('mantine-color-scheme-value', value);
  }, scheme);

  await page.goto(`${baseUrl}/__gallery`, { waitUntil: 'networkidle' });

  // The gallery is static, but web fonts are not: screenshotting before they
  // settle bakes a fallback-font baseline that every later run then fails
  // against.
  await page.evaluate(() => document.fonts.ready);
  await expect(page.getByRole('heading', { name: 'Theme Gallery' })).toBeVisible();
}

for (const app of APPS) {
  for (const scheme of SCHEMES) {
    for (const size of WIDTHS) {
      test(`${app.name} gallery — ${scheme} — ${size.name}`, async ({ page }) => {
        await page.setViewportSize({ width: size.width, height: size.height });
        await gotoGallery(page, app.url, scheme);

        await expect(page).toHaveScreenshot(
          `${app.name}-gallery-${scheme}-${size.name}.png`,
          { fullPage: true },
        );
      });
    }
  }

  /**
   * The focus ring, captured while actually focused.
   *
   * The full-page shots above can't show this: nothing is focused in them, so
   * a regression that removed the ring entirely would leave them all green.
   * Keyboard focus specifically, because `:focus-visible` deliberately does
   * not match a mouse click.
   */
  test(`${app.name} — focus ring is visible`, async ({ page }) => {
    await page.setViewportSize({ width: 1440, height: 900 });
    await gotoGallery(page, app.url, 'light');

    const section = page.locator('section, div').filter({ hasText: 'Focus states' }).last();
    await section.scrollIntoViewIfNeeded();

    const button = page.getByRole('button', { name: 'Button', exact: true });
    await button.focus();
    await expect(button).toBeFocused();

    // Assert the ring in computed styles as well as pixels. A screenshot alone
    // would still pass if the outline came from somewhere unintended, and a
    // token that failed to resolve leaves an empty string rather than an error.
    const ring = await button.evaluate((el) => {
      const s = getComputedStyle(el);
      return {
        width: s.outlineWidth,
        style: s.outlineStyle,
        token: getComputedStyle(document.documentElement)
          .getPropertyValue('--ema-focus-ring')
          .trim(),
      };
    });
    expect(ring.style).toBe('solid');
    expect(ring.width).toBe('2px');
    expect(ring.token).not.toBe('');

    await expect(button).toHaveScreenshot(`${app.name}-focus-ring.png`);
  });
}

/**
 * The skip link, on a real app shell.
 *
 * Not on the gallery route: the point of a skip link is bypassing the nav, and
 * the gallery has none. The login page is the shell-less public route both apps
 * share, so this uses the landing route instead — it carries the chrome without
 * needing a session.
 *
 * A skip link is invisible until focused, which means a broken one and a
 * working one look identical in every screenshot. Only a focus test separates
 * them.
 */
test.describe('skip link', () => {
  for (const app of APPS) {
    test(`${app.name} — reveals on focus and targets main`, async ({ page }) => {
      await page.goto(`${app.url}/`, { waitUntil: 'networkidle' });

      const link = page.locator('.ema-skip-link');
      if ((await link.count()) === 0) {
        // The public landing route does not mount the app shell in every app;
        // skipping is honest here, where asserting absence would be wrong.
        test.skip(true, 'landing route does not mount the app shell');
        return;
      }

      // Off-screen until focused...
      await expect(link).not.toBeInViewport();

      await page.keyboard.press('Tab');
      await expect(link).toBeFocused();
      await expect(link).toBeInViewport();

      // ...and it must point at something that exists.
      const href = await link.getAttribute('href');
      expect(href).toBe('#ema-main-content');
    });
  }
});
