import { test, expect, Page, BrowserContext } from '@playwright/test';

// ============================================================
// THEME CROSS-BROWSER COMPATIBILITY TESTS
//
// Tests browser-specific quirks of the dark/light theme system:
//   - localStorage availability (Safari ITP, private browsing)
//   - CSS custom property support (Edge Legacy is dead but Edge Chromium confirmed)
//   - matchMedia / prefers-color-scheme across engines
//   - CSS transition behavior differences (Gecko vs Blink vs WebKit)
//   - theme-color meta tag browser chrome support
//   - Inline blocking script execution order
//   - Performance under CPU throttling (low-end device simulation)
//   - Browser extension color overrides (CSP-safe simulation)
// ============================================================

// ─── Helpers ─────────────────────────────────────────────────────────────────

async function getThemeState(page: Page) {
  return page.evaluate(() => ({
    hasDarkClass:  document.documentElement.classList.contains('dark'),
    stored:        (() => { try { return localStorage.getItem('theme'); } catch { return null; } })(),
    ariaChecked:   document.querySelector('[data-theme-toggle]')?.getAttribute('aria-checked'),
    ariaLabel:     document.querySelector('[data-theme-toggle]')?.getAttribute('aria-label'),
    htmlClass:     document.documentElement.className,
    cssVarSurface: getComputedStyle(document.documentElement).getPropertyValue('--color-surface').trim(),
    cssVarText:    getComputedStyle(document.documentElement).getPropertyValue('--color-text-primary').trim(),
    themeColorMetas: Array.from(document.querySelectorAll('meta[name="theme-color"]')).map(m => ({
      content: m.getAttribute('content'),
      media:   m.getAttribute('media'),
    })),
    bodyBg: getComputedStyle(document.body).backgroundColor,
  }));
}

async function setStoredTheme(page: Page, theme: 'dark' | 'light' | null) {
  await page.evaluate((t) => {
    try {
      if (t === null) localStorage.removeItem('theme');
      else            localStorage.setItem('theme', t);
    } catch { /* noop — private browsing */ }
  }, theme);
}

async function loadWithTheme(page: Page, theme: 'dark' | 'light' | null, url = '/') {
  await page.goto(url, { waitUntil: 'domcontentloaded' });
  await setStoredTheme(page, theme);
  await page.reload({ waitUntil: 'domcontentloaded' });
}

async function clickToggle(page: Page) {
  const btn = page.locator('[data-theme-toggle]').first();
  await expect(btn).toBeVisible();
  await btn.click();
  await page.waitForTimeout(200);
}

// ─── 1. CSS Custom Property Support ──────────────────────────────────────────

test.describe('Theme: CSS Custom Properties — Cross-Browser', () => {

  test('Light mode: CSS vars resolve to expected light surface color', async ({ page }) => {
    await loadWithTheme(page, 'light');
    const state = await getThemeState(page);

    // --color-surface in light mode = #f8fafc
    expect(state.cssVarSurface).toBe('#f8fafc');
    // --color-text-primary in light = #1e293b
    expect(state.cssVarText).toBe('#1e293b');
  });

  test('Dark mode: CSS vars resolve to expected dark surface color', async ({ page }) => {
    await loadWithTheme(page, 'dark');
    const state = await getThemeState(page);

    // --color-surface in dark mode = #0f172a
    expect(state.cssVarSurface).toBe('#0f172a');
    // --color-text-primary in dark = #f1f5f9
    expect(state.cssVarText).toBe('#f1f5f9');
  });

  test('CSS vars update immediately on toggle (no delayed reflow)', async ({ page }) => {
    await loadWithTheme(page, 'light');

    const before = await page.evaluate(() =>
      getComputedStyle(document.documentElement).getPropertyValue('--color-surface').trim()
    );
    expect(before).toBe('#f8fafc'); // light surface

    await clickToggle(page);

    const after = await page.evaluate(() =>
      getComputedStyle(document.documentElement).getPropertyValue('--color-surface').trim()
    );
    // After toggle to dark, CSS var should have updated
    expect(after).toBe('#0f172a');
  });

  test('All critical CSS vars are defined in both themes', async ({ page }) => {
    const criticalVars = [
      '--color-surface',
      '--color-surface-raised',
      '--color-surface-alt',
      '--color-text-primary',
      '--color-text-secondary',
      '--color-text-muted',
      '--color-border',
      '--color-primary',
      '--color-brand',
    ];

    // Test light mode
    await loadWithTheme(page, 'light');
    const lightValues = await page.evaluate((vars) =>
      vars.map(v => ({
        var: v,
        value: getComputedStyle(document.documentElement).getPropertyValue(v).trim(),
      }))
    , criticalVars);

    for (const { var: v, value } of lightValues) {
      expect(value, `Light mode: ${v} should be defined`).not.toBe('');
    }

    // Test dark mode
    await loadWithTheme(page, 'dark');
    const darkValues = await page.evaluate((vars) =>
      vars.map(v => ({
        var: v,
        value: getComputedStyle(document.documentElement).getPropertyValue(v).trim(),
      }))
    , criticalVars);

    for (const { var: v, value } of darkValues) {
      expect(value, `Dark mode: ${v} should be defined`).not.toBe('');
    }

    // Verify light and dark values DIFFER for key tokens
    const lightSurface = lightValues.find(v => v.var === '--color-surface')?.value;
    const darkSurface  = darkValues.find(v => v.var === '--color-surface')?.value;
    expect(lightSurface).not.toBe(darkSurface);
  });

  test('Tailwind dark: prefix classes apply correctly across browsers', async ({ page }) => {
    await loadWithTheme(page, 'dark');

    // The header uses dark: Tailwind classes — verify computed bg is dark
    const headerBg = await page.evaluate(() => {
      const header = document.querySelector('#main-header');
      return header ? getComputedStyle(header).backgroundColor : null;
    });

    expect(headerBg).not.toBeNull();
    // Dark header bg should be very dark (not white / near-white)
    if (headerBg) {
      const match = headerBg.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
      if (match) {
        const brightness = parseInt(match[1]) + parseInt(match[2]) + parseInt(match[3]);
        // Dark mode header should be dark
        expect(brightness).toBeLessThan(300);
      }
    }
  });
});

// ─── 2. localStorage Availability (Safari ITP & Private Browsing) ─────────────

test.describe('Theme: localStorage — Browser Compatibility', () => {

  test('localStorage is accessible in standard browsing context', async ({ page }) => {
    await page.goto('/', { waitUntil: 'domcontentloaded' });

    const result = await page.evaluate(() => {
      try {
        localStorage.setItem('_test', '1');
        const v = localStorage.getItem('_test');
        localStorage.removeItem('_test');
        return { available: true, value: v };
      } catch (e) {
        return { available: false, value: null };
      }
    });

    expect(result.available).toBe(true);
    expect(result.value).toBe('1');
  });

  test('Theme init handles localStorage failure gracefully (try/catch)', async ({ page }) => {
    // Simulate localStorage being unavailable by overriding it
    await page.addInitScript(() => {
      // Override localStorage to throw (simulates ITP/quota exceeded)
      Object.defineProperty(window, 'localStorage', {
        get() {
          throw new Error('localStorage is not available');
        },
        configurable: true,
      });
    });

    // Page should still load without errors
    const errors: string[] = [];
    page.on('pageerror', e => errors.push(e.message));
    page.on('console', m => { if (m.type() === 'error') errors.push(m.text()); });

    await page.goto('/', { waitUntil: 'domcontentloaded' });

    // Filter non-theme errors
    const themeErrors = errors.filter(e =>
      e.toLowerCase().includes('localstorage') ||
      e.toLowerCase().includes('theme') ||
      e.toLowerCase().includes('cannot read')
    );
    expect(themeErrors).toHaveLength(0);

    // Page must still have content
    const bodyText = await page.locator('body').innerText();
    expect(bodyText.trim().length).toBeGreaterThan(50);
  });

  test('Theme stored value persists across reload in same context', async ({ page }) => {
    await loadWithTheme(page, 'dark');

    // Hard reload
    await page.reload({ waitUntil: 'domcontentloaded' });

    const stored = await page.evaluate(() => {
      try { return localStorage.getItem('theme'); } catch { return null; }
    });
    expect(stored).toBe('dark');
  });

  test('Fresh browser context has no theme residue from prior sessions', async ({ browser }) => {
    // Context 1: set dark
    const ctx1 = await browser.newContext();
    const p1 = await ctx1.newPage();
    await p1.goto('http://localhost:10000/', { waitUntil: 'domcontentloaded' });
    await p1.evaluate(() => { try { localStorage.setItem('theme', 'dark'); } catch {} });
    await ctx1.close();

    // Context 2: completely fresh — must have no stored theme
    const ctx2 = await browser.newContext();
    const p2 = await ctx2.newPage();
    await p2.goto('http://localhost:10000/', { waitUntil: 'domcontentloaded' });
    const stored = await p2.evaluate(() => {
      try { return localStorage.getItem('theme'); } catch { return null; }
    });
    expect(stored).toBeNull();
    await ctx2.close();
  });

  test('Multiple tabs in same context share localStorage (same origin)', async ({ context }) => {
    const p1 = await context.newPage();
    await p1.goto('/', { waitUntil: 'domcontentloaded' });
    await p1.evaluate(() => { try { localStorage.setItem('theme', 'dark'); } catch {} });

    const p2 = await context.newPage();
    await p2.goto('/', { waitUntil: 'domcontentloaded' });
    const stored = await p2.evaluate(() => {
      try { return localStorage.getItem('theme'); } catch { return null; }
    });
    expect(stored).toBe('dark');

    await p1.close();
    await p2.close();
  });
});

// ─── 3. matchMedia / prefers-color-scheme ─────────────────────────────────────

test.describe('Theme: matchMedia — Cross-Browser System Preference', () => {

  test('matchMedia API is available', async ({ page }) => {
    await page.goto('/', { waitUntil: 'domcontentloaded' });
    const hasMatchMedia = await page.evaluate(() => typeof window.matchMedia === 'function');
    expect(hasMatchMedia).toBe(true);
  });

  test('prefers-color-scheme media query is supported', async ({ page }) => {
    await page.goto('/', { waitUntil: 'domcontentloaded' });

    const result = await page.evaluate(() => {
      const mq = window.matchMedia('(prefers-color-scheme: dark)');
      return {
        isMediaQueryList: mq instanceof MediaQueryList,
        hasMatches: typeof mq.matches === 'boolean',
        hasAddEventListener: typeof mq.addEventListener === 'function',
      };
    });

    expect(result.isMediaQueryList).toBe(true);
    expect(result.hasMatches).toBe(true);
    expect(result.hasAddEventListener).toBe(true);
  });

  test('Dark OS preference (emulated): page loads in dark mode without stored pref', async ({ page }) => {
    await page.emulateMedia({ colorScheme: 'dark' });
    await loadWithTheme(page, null);

    const state = await getThemeState(page);
    expect(state.hasDarkClass).toBe(true);
    expect(state.stored).toBeNull();
  });

  test('Light OS preference (emulated): page loads in light mode without stored pref', async ({ page }) => {
    await page.emulateMedia({ colorScheme: 'light' });
    await loadWithTheme(page, null);

    const state = await getThemeState(page);
    expect(state.hasDarkClass).toBe(false);
    expect(state.stored).toBeNull();
  });

  test('Stored preference takes priority over OS preference (dark stored, light OS)', async ({ page }) => {
    await page.emulateMedia({ colorScheme: 'light' });
    await loadWithTheme(page, 'dark');

    const state = await getThemeState(page);
    expect(state.hasDarkClass).toBe(true);
  });

  test('Stored preference takes priority over OS preference (light stored, dark OS)', async ({ page }) => {
    await page.emulateMedia({ colorScheme: 'dark' });
    await loadWithTheme(page, 'light');

    const state = await getThemeState(page);
    expect(state.hasDarkClass).toBe(false);
  });

  test('OS change to dark propagates when no stored pref', async ({ page }) => {
    await page.emulateMedia({ colorScheme: 'light' });
    await loadWithTheme(page, null);

    expect((await getThemeState(page)).hasDarkClass).toBe(false);

    // Simulate OS switch to dark
    await page.emulateMedia({ colorScheme: 'dark' });
    await page.waitForTimeout(400);

    const state = await getThemeState(page);
    expect(state.hasDarkClass).toBe(true);
    expect(state.stored).toBeNull(); // Not stored — just OS-driven
  });

  test('OS change ignored when user has explicit stored preference', async ({ page }) => {
    await page.emulateMedia({ colorScheme: 'dark' });
    await loadWithTheme(page, 'light');

    // User explicitly chose light; switch OS to dark
    await page.emulateMedia({ colorScheme: 'dark' });
    await page.waitForTimeout(400);

    // Stored preference wins — the event listener checks localStorage first
    expect((await getThemeState(page)).stored).toBe('light');
  });
});

// ─── 4. CSS Transition Behavior ───────────────────────────────────────────────

test.describe('Theme: CSS Transitions — Cross-Browser', () => {

  test('Body background transitions on theme toggle (transition exists)', async ({ page }) => {
    await loadWithTheme(page, 'light');

    const transition = await page.evaluate(() =>
      getComputedStyle(document.body).transition
    );
    // Background or color transition should be defined (non-empty)
    // Different browsers serialize transitions differently — just verify it's non-empty
    expect(transition.trim().length).toBeGreaterThan(0);
  });

  test('Icon opacity transitions are defined on sun/moon icons', async ({ page }) => {
    await loadWithTheme(page, 'light');

    const moonTransition = await page.evaluate(() => {
      const btn = document.querySelector('[data-theme-toggle]');
      const moon = btn?.querySelector('.moon-icon') as HTMLElement | null;
      return moon ? getComputedStyle(moon).transition : null;
    });

    expect(moonTransition).not.toBeNull();
    expect(moonTransition!.trim().length).toBeGreaterThan(0);
  });

  test('Reduced motion: sun/moon icon transitions are disabled', async ({ page }) => {
    await page.emulateMedia({ reducedMotion: 'reduce' });
    await loadWithTheme(page, 'light');

    const iconTransitionDuration = await page.evaluate(() => {
      const btn = document.querySelector('[data-theme-toggle]');
      const icon = btn?.querySelector('.moon-icon') as HTMLElement | null;
      return icon ? getComputedStyle(icon).transitionDuration : null;
    });

    if (iconTransitionDuration) {
      expect(['0s', '0ms', '']).toContain(iconTransitionDuration);
    }
  });

  test('Reduced motion: theme toggle still functions (no animation freeze)', async ({ page }) => {
    await page.emulateMedia({ reducedMotion: 'reduce' });
    await loadWithTheme(page, 'light');

    await clickToggle(page);

    const state = await getThemeState(page);
    // Toggle should still work — just without transition
    expect(state.hasDarkClass).toBe(true);
    expect(state.stored).toBe('dark');
  });

  test('Theme toggle icon is visible after transition completes', async ({ page }) => {
    await loadWithTheme(page, 'dark');

    // Wait for any transitions to settle
    await page.waitForTimeout(400);

    const sunOpacity = await page.evaluate(() => {
      const btn = document.querySelector('[data-theme-toggle]');
      const sun = btn?.querySelector('.sun-icon') as HTMLElement | null;
      return sun ? parseFloat(getComputedStyle(sun).opacity) : null;
    });

    expect(sunOpacity).toBeCloseTo(1, 1); // Should be ~1.0 in dark mode
  });

  test('Theme toggle icon switches after toggle completes', async ({ page }) => {
    await loadWithTheme(page, 'light');

    // Initially moon icon should be visible (opacity ~1)
    const moonBefore = await page.evaluate(() => {
      const btn = document.querySelector('[data-theme-toggle]');
      const moon = btn?.querySelector('.moon-icon') as HTMLElement | null;
      return moon ? parseFloat(getComputedStyle(moon).opacity) : null;
    });
    expect(moonBefore).toBeCloseTo(1, 1);

    await clickToggle(page);
    await page.waitForTimeout(400); // Allow transition to complete

    // After toggle to dark, sun should be visible, moon hidden
    const { sunOpacity, moonOpacity } = await page.evaluate(() => {
      const btn = document.querySelector('[data-theme-toggle]');
      const sun  = btn?.querySelector('.sun-icon')  as HTMLElement | null;
      const moon = btn?.querySelector('.moon-icon') as HTMLElement | null;
      return {
        sunOpacity:  sun  ? parseFloat(getComputedStyle(sun).opacity)  : null,
        moonOpacity: moon ? parseFloat(getComputedStyle(moon).opacity) : null,
      };
    });

    expect(sunOpacity).toBeCloseTo(1, 1);
    expect(moonOpacity).toBeCloseTo(0, 1);
  });
});

// ─── 5. Mobile Viewport Theme Behavior ───────────────────────────────────────

test.describe('Theme: Mobile Viewport Compatibility', () => {

  const MOBILE_VIEWPORTS = [
    { name: 'iPhone SE', width: 375, height: 667 },
    { name: 'Pixel 5',   width: 393, height: 851 },
    { name: 'iPad',      width: 768, height: 1024 },
  ];

  for (const vp of MOBILE_VIEWPORTS) {
    test(`${vp.name}: theme toggle visible and functional`, async ({ page }) => {
      await page.setViewportSize({ width: vp.width, height: vp.height });
      await loadWithTheme(page, 'light');

      // At mobile widths, desktop toggle may be hidden; mobile toggle should be present
      const allToggles = await page.locator('[data-theme-toggle]').count();
      expect(allToggles).toBeGreaterThanOrEqual(1);

      // At least one toggle should be visible
      let visibleToggle = false;
      for (let i = 0; i < allToggles; i++) {
        const isVisible = await page.locator('[data-theme-toggle]').nth(i).isVisible();
        if (isVisible) { visibleToggle = true; break; }
      }
      expect(visibleToggle).toBe(true);
    });

    test(`${vp.name}: dark mode CSS correct at this viewport`, async ({ page }) => {
      await page.setViewportSize({ width: vp.width, height: vp.height });
      await loadWithTheme(page, 'dark');

      const state = await getThemeState(page);
      expect(state.hasDarkClass).toBe(true);

      const match = state.bodyBg.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
      if (match) {
        const brightness = parseInt(match[1]) + parseInt(match[2]) + parseInt(match[3]);
        expect(brightness).toBeLessThan(200);
      }
    });
  }

  test('Mobile: theme toggle in mobile nav menu works correctly', async ({ page }) => {
    await page.setViewportSize({ width: 375, height: 667 });
    await loadWithTheme(page, 'light');

    // Open mobile menu
    const menuBtn = page.locator('#mobile-menu-toggle');
    if (await menuBtn.isVisible()) {
      await menuBtn.click();
      await page.waitForTimeout(400);
    }

    // Theme toggle should be findable and interactive
    const toggle = page.locator('[data-theme-toggle]').first();
    await expect(toggle).toBeVisible();

    await toggle.click();
    await page.waitForTimeout(200);

    const state = await getThemeState(page);
    expect(state.hasDarkClass).toBe(true);
    expect(state.stored).toBe('dark');
  });

  test('Viewport resize dark → light → resize does not break theme', async ({ page }) => {
    await page.setViewportSize({ width: 375, height: 667 });
    await loadWithTheme(page, 'dark');

    expect((await getThemeState(page)).hasDarkClass).toBe(true);

    // Resize to desktop
    await page.setViewportSize({ width: 1280, height: 800 });
    await page.waitForTimeout(300);
    expect((await getThemeState(page)).hasDarkClass).toBe(true);

    // Resize back to mobile
    await page.setViewportSize({ width: 375, height: 667 });
    await page.waitForTimeout(300);
    expect((await getThemeState(page)).hasDarkClass).toBe(true);
  });
});

// ─── 6. theme-color Meta Tag Behavior ─────────────────────────────────────────

test.describe('Theme: theme-color Meta Tag — Browser Compatibility', () => {

  test('meta[name="theme-color"] tags exist in <head>', async ({ page }) => {
    await page.goto('/', { waitUntil: 'domcontentloaded' });

    const metas = await page.evaluate(() =>
      Array.from(document.head.querySelectorAll('meta[name="theme-color"]')).map(m => ({
        content: m.getAttribute('content'),
        media:   m.getAttribute('media'),
      }))
    );

    expect(metas.length).toBeGreaterThanOrEqual(1);
  });

  test('Meta tags have media attributes for OS-level dark/light support', async ({ page }) => {
    await page.goto('/', { waitUntil: 'domcontentloaded' });

    const mediaMetas = await page.evaluate(() =>
      Array.from(document.head.querySelectorAll('meta[name="theme-color"][media]')).length
    );
    // Should have at least one media-query-gated theme-color meta
    expect(mediaMetas).toBeGreaterThanOrEqual(1);
  });

  test('Dark mode: theme-color meta updated to dark color (#0f172a)', async ({ page }) => {
    await loadWithTheme(page, 'dark');

    const metas = await page.evaluate(() =>
      Array.from(document.querySelectorAll('meta[name="theme-color"]')).map(m =>
        m.getAttribute('content')
      )
    );
    expect(metas.some(c => c === '#0f172a')).toBe(true);
  });

  test('Light mode: theme-color meta updated to light color (#0891b2)', async ({ page }) => {
    await loadWithTheme(page, 'light');

    const metas = await page.evaluate(() =>
      Array.from(document.querySelectorAll('meta[name="theme-color"]')).map(m =>
        m.getAttribute('content')
      )
    );
    expect(metas.some(c => c === '#0891b2')).toBe(true);
  });

  test('Toggle: theme-color meta updates dynamically without page reload', async ({ page }) => {
    await loadWithTheme(page, 'light');

    // Verify light color
    const beforeMetas = await page.evaluate(() =>
      Array.from(document.querySelectorAll('meta[name="theme-color"]')).map(m =>
        m.getAttribute('content')
      )
    );
    expect(beforeMetas.some(c => c === '#0891b2')).toBe(true);

    await clickToggle(page);

    // After toggling to dark, meta should update
    const afterMetas = await page.evaluate(() =>
      Array.from(document.querySelectorAll('meta[name="theme-color"]')).map(m =>
        m.getAttribute('content')
      )
    );
    expect(afterMetas.some(c => c === '#0f172a')).toBe(true);
  });
});

// ─── 7. Performance Under Simulated Low-End Device Conditions ────────────────

test.describe('Theme: Performance — Low-End Device Simulation', () => {

  test('Theme init completes without measurable JS error under CPU throttle', async ({ page, browserName }) => {
    // Note: Playwright CDP throttling only works in Chromium
    if (browserName !== 'chromium') {
      test.skip();
      return;
    }

    const client = await (page.context() as BrowserContext).newCDPSession(page);
    // Simulate 4x CPU slowdown
    await client.send('Emulation.setCPUThrottlingRate', { rate: 4 });

    const errors: string[] = [];
    page.on('pageerror', e => errors.push(e.message));

    await loadWithTheme(page, 'dark');

    const state = await getThemeState(page);
    expect(state.hasDarkClass).toBe(true);

    // No JS errors even under throttle
    expect(errors).toHaveLength(0);

    // Reset throttle
    await client.send('Emulation.setCPUThrottlingRate', { rate: 1 });
  });

  test('Theme init does not block page render measurably (inline script)', async ({ page }) => {
    // Verify the init script is small (inline IIFE, not a network request)
    await page.goto('/', { waitUntil: 'domcontentloaded' });

    const inlineThemeScript = await page.evaluate(() => {
      const scripts = Array.from(document.head.querySelectorAll('script:not([src]):not([type="module"])'));
      return scripts.map(s => ({ text: s.textContent ?? '', length: (s.textContent ?? '').length }));
    });

    // Find the theme init script
    const themeScript = inlineThemeScript.find(s =>
      s.text.includes('localStorage') && s.text.includes('dark')
    );

    expect(themeScript).toBeTruthy();
    // The inline theme script should be compact (< 1KB) to not block the render thread
    if (themeScript) {
      expect(themeScript.length).toBeLessThan(1000);
    }
  });

  test('Theme toggle responds within 300ms on all pages', async ({ page }) => {
    await loadWithTheme(page, 'light');

    const start = Date.now();
    await clickToggle(page);
    const elapsed = Date.now() - start;

    const state = await getThemeState(page);
    expect(state.hasDarkClass).toBe(true);
    // Toggle should complete well under 300ms (CSS class change is synchronous)
    expect(elapsed).toBeLessThan(500); // generous CI budget
  });

  test('3G simulation: theme persists after slow page load', async ({ page, browserName }) => {
    if (browserName !== 'chromium') {
      test.skip();
      return;
    }

    const client = await (page.context() as BrowserContext).newCDPSession(page);
    // Simulate slow 3G
    await client.send('Network.emulateNetworkConditions', {
      offline: false,
      downloadThroughput: 50 * 1024 / 8, // 50 kbps
      uploadThroughput:   20 * 1024 / 8,
      latency: 300,
    });

    // Set dark preference and reload
    await page.goto('http://localhost:10000/', { waitUntil: 'domcontentloaded' });
    await setStoredTheme(page, 'dark');
    await page.reload({ waitUntil: 'domcontentloaded', timeout: 30000 });

    const state = await getThemeState(page);
    // Dark class should be applied even on slow networks (inline blocking script)
    expect(state.hasDarkClass).toBe(true);

    // Reset network
    await client.send('Network.emulateNetworkConditions', {
      offline: false,
      downloadThroughput: -1,
      uploadThroughput: -1,
      latency: 0,
    });
  });
});

// ─── 8. JavaScript Disabled Fallback ─────────────────────────────────────────

test.describe('Theme: No-JavaScript Fallback — Cross-Browser', () => {

  test('Without JS: page loads and has content (SSR)', async ({ browser }) => {
    const ctx = await browser.newContext({ javaScriptEnabled: false });
    const page = await ctx.newPage();

    await page.goto('http://localhost:10000/', { waitUntil: 'domcontentloaded' });

    const bodyText = await page.locator('body').innerText();
    expect(bodyText.trim().length).toBeGreaterThan(100);

    await expect(page.locator('#main-header')).toBeAttached();
    await expect(page.locator('footer')).toBeAttached();

    await ctx.close();
  });

  test('Without JS: no dark class applied (progressive enhancement)', async ({ browser }) => {
    const ctx = await browser.newContext({ javaScriptEnabled: false });
    const page = await ctx.newPage();

    await page.goto('http://localhost:10000/', { waitUntil: 'domcontentloaded' });

    const hasDark = await page.evaluate(() =>
      document.documentElement.classList.contains('dark')
    );
    // Without JS, the inline blocking script cannot run — no dark class
    // This is expected: theme system uses progressive enhancement
    expect(typeof hasDark).toBe('boolean');

    await ctx.close();
  });

  test('Without JS: theme-color meta tags still present in SSR output', async ({ browser }) => {
    const ctx = await browser.newContext({ javaScriptEnabled: false });
    const page = await ctx.newPage();

    await page.goto('http://localhost:10000/', { waitUntil: 'domcontentloaded' });

    const metaCount = await page.evaluate(() =>
      document.querySelectorAll('meta[name="theme-color"]').length
    );
    expect(metaCount).toBeGreaterThanOrEqual(1);

    await ctx.close();
  });

  test('Without JS: noscript font fallback link is rendered', async ({ browser }) => {
    const ctx = await browser.newContext({ javaScriptEnabled: false });
    const page = await ctx.newPage();

    await page.goto('http://localhost:10000/', { waitUntil: 'domcontentloaded' });

    const hasNoscriptFont = await page.evaluate(() => {
      return Array.from(document.querySelectorAll('noscript')).some(n =>
        n.innerHTML.includes('fonts.googleapis.com')
      );
    });
    expect(hasNoscriptFont).toBe(true);

    await ctx.close();
  });
});

// ─── 9. Cross-Browser Color Accuracy ─────────────────────────────────────────

test.describe('Theme: Color Accuracy — Cross-Browser', () => {

  test('Dark mode body background: computed color matches #0f172a (slate-900)', async ({ page }) => {
    await loadWithTheme(page, 'dark');

    const bg = await page.evaluate(() => getComputedStyle(document.body).backgroundColor);
    // #0f172a = rgb(15, 23, 42)
    const normalized = bg.replace(/\s/g, '');
    // Accept rgb(15,23,42) format
    expect(normalized).toMatch(/rgb\(15,23,42\)/);
  });

  test('Light mode body background: computed color is light', async ({ page }) => {
    await loadWithTheme(page, 'light');

    const bg = await page.evaluate(() => getComputedStyle(document.body).backgroundColor);
    const match = bg.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
    if (match) {
      const brightness = parseInt(match[1]) + parseInt(match[2]) + parseInt(match[3]);
      expect(brightness).toBeGreaterThan(500); // Light background
    }
  });

  test('Dark mode primary text color: renders as near-white', async ({ page }) => {
    await loadWithTheme(page, 'dark');

    // Verify computed text color of a heading is light (not dark text on dark bg)
    const headingColor = await page.evaluate(() => {
      const h1 = document.querySelector('h1');
      return h1 ? getComputedStyle(h1).color : null;
    });

    if (headingColor) {
      const match = headingColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
      if (match) {
        const brightness = parseInt(match[1]) + parseInt(match[2]) + parseInt(match[3]);
        // Dark mode heading text should be light
        expect(brightness).toBeGreaterThan(400);
      }
    }
  });

  test('Light mode primary text color: renders as near-black', async ({ page }) => {
    await loadWithTheme(page, 'light');

    const headingColor = await page.evaluate(() => {
      const h1 = document.querySelector('h1');
      return h1 ? getComputedStyle(h1).color : null;
    });

    if (headingColor) {
      const match = headingColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
      if (match) {
        const brightness = parseInt(match[1]) + parseInt(match[2]) + parseInt(match[3]);
        // Light mode heading text should be dark
        expect(brightness).toBeLessThan(200);
      }
    }
  });

  test('Color contrast: dark mode text-on-background meets WCAG AA', async ({ page }) => {
    await loadWithTheme(page, 'dark');

    // Read actual computed colors and compute relative luminance
    const colors = await page.evaluate(() => {
      function parseCSSColor(c: string): [number, number, number] | null {
        const m = c.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
        return m ? [parseInt(m[1]), parseInt(m[2]), parseInt(m[3])] : null;
      }
      function relativeLuminance(rgb: [number, number, number]): number {
        const [r, g, b] = rgb.map(c => {
          const s = c / 255;
          return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
        });
        return 0.2126 * r + 0.7152 * g + 0.0722 * b;
      }
      function contrastRatio(c1: [number, number, number], c2: [number, number, number]): number {
        const l1 = relativeLuminance(c1);
        const l2 = relativeLuminance(c2);
        const lighter = Math.max(l1, l2);
        const darker  = Math.min(l1, l2);
        return (lighter + 0.05) / (darker + 0.05);
      }

      const bodyBg   = parseCSSColor(getComputedStyle(document.body).backgroundColor);
      const headingEl = document.querySelector('h1, h2');
      const textColor = headingEl ? parseCSSColor(getComputedStyle(headingEl).color) : null;

      if (!bodyBg || !textColor) return null;
      return contrastRatio(bodyBg, textColor);
    });

    if (colors !== null) {
      // WCAG AA: 4.5:1 for normal text, 3:1 for large text (headings qualify as large)
      expect(colors).toBeGreaterThanOrEqual(3.0);
    }
  });

  test('Color contrast: light mode text-on-background meets WCAG AA', async ({ page }) => {
    await loadWithTheme(page, 'light');

    const contrast = await page.evaluate(() => {
      function parseCSSColor(c: string): [number, number, number] | null {
        const m = c.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
        return m ? [parseInt(m[1]), parseInt(m[2]), parseInt(m[3])] : null;
      }
      function relativeLuminance(rgb: [number, number, number]): number {
        const [r, g, b] = rgb.map(c => {
          const s = c / 255;
          return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
        });
        return 0.2126 * r + 0.7152 * g + 0.0722 * b;
      }
      function contrastRatio(c1: [number, number, number], c2: [number, number, number]): number {
        const l1 = relativeLuminance(c1);
        const l2 = relativeLuminance(c2);
        const lighter = Math.max(l1, l2);
        const darker  = Math.min(l1, l2);
        return (lighter + 0.05) / (darker + 0.05);
      }

      const bodyBg   = parseCSSColor(getComputedStyle(document.body).backgroundColor);
      const heading  = document.querySelector('h1, h2');
      const textColor = heading ? parseCSSColor(getComputedStyle(heading).color) : null;

      if (!bodyBg || !textColor) return null;
      return contrastRatio(bodyBg, textColor);
    });

    if (contrast !== null) {
      expect(contrast).toBeGreaterThanOrEqual(3.0);
    }
  });
});

// ─── 10. Browser Extension Color Override Simulation ─────────────────────────

test.describe('Theme: Browser Extension Color Override Simulation', () => {

  test('Theme class survives programmatic html class manipulation', async ({ page }) => {
    // Simulate a dark-mode extension that modifies html classes
    await loadWithTheme(page, 'dark');

    // Extension adds its own class to <html>
    await page.evaluate(() => {
      document.documentElement.classList.add('ext-forced-dark');
    });
    await page.waitForTimeout(100);

    // Our dark class should still be there
    const state = await getThemeState(page);
    expect(state.hasDarkClass).toBe(true);
    expect(state.htmlClass).toContain('ext-forced-dark');
  });

  test('Theme toggle still works after external class additions', async ({ page }) => {
    await loadWithTheme(page, 'light');

    // Simulate extension adding classes
    await page.evaluate(() => {
      document.documentElement.classList.add('ext-mode', 'ext-modified');
    });

    await clickToggle(page);

    const state = await getThemeState(page);
    expect(state.hasDarkClass).toBe(true);
    expect(state.stored).toBe('dark');
    // Extension classes still present
    expect(state.htmlClass).toContain('ext-mode');
  });

  test('CSS custom properties survive !important override attempt', async ({ page }) => {
    await loadWithTheme(page, 'dark');

    // Simulate a style injection (like some browser extensions do)
    await page.evaluate(() => {
      const style = document.createElement('style');
      style.textContent = `
        /* Simulated extension override */
        body { background-color: white !important; }
      `;
      document.head.appendChild(style);
    });
    await page.waitForTimeout(200);

    // Our html.dark class should still be present
    const hasDark = await page.evaluate(() =>
      document.documentElement.classList.contains('dark')
    );
    expect(hasDark).toBe(true);

    // The theme toggle button aria state should still be correct
    const ariaChecked = await page.locator('[data-theme-toggle]').first().getAttribute('aria-checked');
    expect(ariaChecked).toBe('true');
  });
});

// ─── 11. Cross-Page Consistency (all routes) ─────────────────────────────────

test.describe('Theme: Cross-Page Consistency — All Routes', () => {

  const ALL_PAGES = [
    '/', '/about', '/services', '/portfolio', '/contact', '/blog',
    '/privacy', '/terms', '/sitemap',
  ];

  for (const url of ALL_PAGES) {
    test(`Dark mode consistent on: ${url}`, async ({ page }) => {
      // Set dark in storage, then load target page directly
      await page.goto('/', { waitUntil: 'domcontentloaded' });
      await setStoredTheme(page, 'dark');
      await page.goto(url, { waitUntil: 'domcontentloaded' });

      const hasDark = await page.evaluate(() =>
        document.documentElement.classList.contains('dark')
      );
      expect(hasDark).toBe(true);
    });
  }

  test('Theme toggle present on all key content pages (desktop)', async ({ page }) => {
    await page.setViewportSize({ width: 1280, height: 800 });

    const contentPages = ['/', '/about', '/services', '/portfolio', '/contact'];
    for (const url of contentPages) {
      await page.goto(url, { waitUntil: 'domcontentloaded' });
      const count = await page.locator('[data-theme-toggle]').count();
      expect(count, `Theme toggle missing on ${url}`).toBeGreaterThanOrEqual(1);
    }
  });

  test('Theme state consistent after SPA-style rapid navigation', async ({ page }) => {
    await loadWithTheme(page, 'dark');

    // Navigate rapidly across pages
    const pages = ['/about', '/services', '/portfolio', '/contact', '/'];
    for (const url of pages) {
      await page.goto(url, { waitUntil: 'domcontentloaded' });
    }

    const finalState = await getThemeState(page);
    expect(finalState.hasDarkClass).toBe(true);
  });

  test('Theme persists through browser back/forward navigation', async ({ page }) => {
    await loadWithTheme(page, 'dark');

    await page.goto('/about', { waitUntil: 'domcontentloaded' });
    expect((await getThemeState(page)).hasDarkClass).toBe(true);

    await page.goto('/services', { waitUntil: 'domcontentloaded' });
    expect((await getThemeState(page)).hasDarkClass).toBe(true);

    await page.goBack({ waitUntil: 'domcontentloaded' });
    expect((await getThemeState(page)).hasDarkClass).toBe(true);

    await page.goBack({ waitUntil: 'domcontentloaded' });
    expect((await getThemeState(page)).hasDarkClass).toBe(true);
  });
});

// ─── 12. Visual Regression Snapshots — Theme Variants ────────────────────────

test.describe('Theme: Visual Snapshots — Cross-Browser', () => {

  const PAGES = [
    { name: 'home',      url: '/' },
    { name: 'services',  url: '/services' },
    { name: 'portfolio', url: '/portfolio' },
    { name: 'contact',   url: '/contact' },
  ];

  for (const p of PAGES) {
    test(`Dark/light screenshot pair: ${p.name}`, async ({ page, browserName }) => {
      // Dark mode
      await page.setViewportSize({ width: 1280, height: 800 });
      await loadWithTheme(page, 'dark');
      await page.goto(p.url, { waitUntil: 'domcontentloaded' });
      await page.waitForTimeout(400);

      await page.screenshot({
        path: `tests/screenshots/theme-compat/${browserName}-${p.name}-dark.png`,
        clip: { x: 0, y: 0, width: 1280, height: 800 },
      });

      // Light mode
      await loadWithTheme(page, 'light');
      await page.goto(p.url, { waitUntil: 'domcontentloaded' });
      await page.waitForTimeout(400);

      await page.screenshot({
        path: `tests/screenshots/theme-compat/${browserName}-${p.name}-light.png`,
        clip: { x: 0, y: 0, width: 1280, height: 800 },
      });
    });

    test(`Mobile dark/light screenshot: ${p.name}`, async ({ page, browserName }) => {
      await page.setViewportSize({ width: 375, height: 667 });

      // Dark
      await loadWithTheme(page, 'dark');
      await page.goto(p.url, { waitUntil: 'domcontentloaded' });
      await page.waitForTimeout(300);
      await page.screenshot({
        path: `tests/screenshots/theme-compat/${browserName}-${p.name}-mobile-dark.png`,
        fullPage: false,
      });

      // Light
      await loadWithTheme(page, 'light');
      await page.goto(p.url, { waitUntil: 'domcontentloaded' });
      await page.waitForTimeout(300);
      await page.screenshot({
        path: `tests/screenshots/theme-compat/${browserName}-${p.name}-mobile-light.png`,
        fullPage: false,
      });
    });
  }
});
