// History navigation tests — back/forward buttons (HTMX 4.0 style)
// Verifies that popstate re-renders content, restores scroll position,
// and matches a fresh load of the same URL.

const { test, expect } = require('playwright/test');
const { BASE_URL, waitForSxReady, loadPage, trackErrors } = require('./helpers');

test.describe('History navigation', () => {
  let t;
  test.beforeEach(({ page }) => { t = trackErrors(page); });
  test.afterEach(() => { expect(t.errors()).toEqual([]); });

  test('back button restores previous page content', async ({ page }) => {
    await loadPage(page, '(geography)');
    await expect(page.locator('#sx-content')).toContainText('Geography');

    // Navigate forward
    await page.click('a[href*="geography.(reactive)"]:not([href*="runtime"])');
    await expect(page).toHaveURL(/reactive/, { timeout: 5000 });
    await expect(page.locator('#sx-content')).toContainText('Reactive Islands', { timeout: 5000 });

    // Back button
    await page.goBack();
    await expect(page).toHaveURL(/geography/, { timeout: 5000 });
    await expect(page).not.toHaveURL(/reactive/);

    // Content must update to match the URL
    await expect(page.locator('#sx-content')).toContainText('Geography', { timeout: 5000 });
  });

  test('forward button restores next page content', async ({ page }) => {
    await loadPage(page, '(geography)');

    // Navigate forward
    await page.click('a[href*="geography.(reactive)"]:not([href*="runtime"])');
    await expect(page).toHaveURL(/reactive/, { timeout: 5000 });
    await expect(page.locator('#sx-content')).toContainText('Reactive Islands', { timeout: 5000 });

    // Back
    await page.goBack();
    await expect(page).toHaveURL(/geography/, { timeout: 5000 });
    await expect(page.locator('#sx-content')).toContainText('Geography', { timeout: 5000 });

    // Forward
    await page.goForward();
    await expect(page).toHaveURL(/reactive/, { timeout: 5000 });
    await expect(page.locator('#sx-content')).toContainText('Reactive Islands', { timeout: 5000 });
  });

  test('back button after multiple navigations', async ({ page }) => {
    await loadPage(page, '');

    // Navigate: home -> geography -> reactive
    await page.click('a[sx-get*="(geography)"]');
    await expect(page).toHaveURL(/geography/, { timeout: 5000 });
    await page.waitForTimeout(1000);

    await page.click('a[href*="geography.(reactive)"]:not([href*="runtime"])');
    await expect(page).toHaveURL(/reactive/, { timeout: 5000 });
    await page.waitForTimeout(1000);

    // Back to geography
    await page.goBack();
    await expect(page).toHaveURL(/geography/, { timeout: 5000 });
    await expect(page).not.toHaveURL(/reactive/);
    await expect(page.locator('#sx-content')).toContainText('Geography', { timeout: 5000 });

    // Back to home
    await page.goBack();
    await expect(page).toHaveURL(/\/sx\/?$/, { timeout: 5000 });
  });

  test('back button is SPA nav, not full reload', async ({ page }) => {
    await loadPage(page, '(geography)');

    // Set marker that survives SPA nav but not full reload
    await page.evaluate(() => window.__history_marker = true);

    // Navigate forward
    await page.click('a[href*="geography.(reactive)"]:not([href*="runtime"])');
    await expect(page).toHaveURL(/reactive/, { timeout: 5000 });

    // Marker should survive forward SPA nav
    expect(await page.evaluate(() => window.__history_marker)).toBe(true);

    // Back button
    await page.goBack();
    await expect(page).toHaveURL(/geography/, { timeout: 5000 });

    // Marker should survive back nav (no full reload)
    expect(await page.evaluate(() => window.__history_marker)).toBe(true);
  });

  test('nav sidebar updates on back button', async ({ page }) => {
    await loadPage(page, '(geography)');
    await expect(page.locator('#sx-nav')).toContainText('Geography');

    // Navigate forward
    await page.click('a[href*="geography.(reactive)"]:not([href*="runtime"])');
    await expect(page).toHaveURL(/reactive/, { timeout: 5000 });

    // Back button
    await page.goBack();
    await expect(page).toHaveURL(/geography/, { timeout: 5000 });

    // Nav should show Geography content, not Reactive Islands
    await expect(page.locator('#sx-nav')).toContainText('Geography', { timeout: 5000 });
  });

  test('layout preserved after back button', async ({ page }) => {
    await loadPage(page, '(geography)');

    await page.click('a[href*="geography.(reactive)"]:not([href*="runtime"])');
    await expect(page).toHaveURL(/reactive/, { timeout: 5000 });

    await page.goBack();
    await expect(page).toHaveURL(/geography/, { timeout: 5000 });

    // Key layout elements must exist and not be duplicated
    const layout = await page.evaluate(() => ({
      contentCount: document.querySelectorAll('#sx-content').length,
      navCount: document.querySelectorAll('#sx-nav').length,
      headerCount: document.querySelectorAll('[data-sx-island="layouts/header"]').length,
    }));
    expect(layout.contentCount).toBe(1);
    expect(layout.navCount).toBe(1);
    expect(layout.headerCount).toBe(1);
  });

  test('scroll position saved on forward nav', async ({ page }) => {
    await loadPage(page, '(geography)');

    // Scroll down
    await page.evaluate(() => window.scrollTo(0, 200));
    await page.waitForTimeout(100);

    // Navigate forward — should save scroll via replaceState
    await page.click('a[href*="geography.(reactive)"]:not([href*="runtime"])');
    await expect(page).toHaveURL(/reactive/, { timeout: 5000 });

    // Check that the previous history entry has scrollY saved
    const state = await page.evaluate(() => {
      // Can't read previous entry state directly, but we can check
      // that pushState was called with state on the current entry
      return window.history.state;
    });
    // Current entry (the new page) won't have scrollY,
    // but we can verify the mechanism works by going back
    await page.goBack();
    await expect(page).toHaveURL(/geography/, { timeout: 5000 });

    // The page should have restored (browser handles the state object)
    // We verify the content loaded correctly as the primary check
    await expect(page.locator('#sx-content')).toContainText('Geography', { timeout: 5000 });
  });

  test('back button content matches fresh load', async ({ page }) => {
    await loadPage(page, '(geography)');

    // Navigate forward
    await page.click('a[href*="geography.(reactive)"]:not([href*="runtime"])');
    await expect(page).toHaveURL(/reactive/, { timeout: 5000 });
    await page.waitForTimeout(1000);

    // Go back
    await page.goBack();
    await expect(page).toHaveURL(/geography/, { timeout: 5000 });
    await page.waitForTimeout(1000);

    // Snapshot the back-button DOM
    const backContent = await page.evaluate(() =>
      document.querySelector('#sx-content')?.textContent?.trim().substring(0, 200));

    // Fresh load the same URL
    await page.goto(page.url(), { waitUntil: 'domcontentloaded' });
    await waitForSxReady(page);

    const freshContent = await page.evaluate(() =>
      document.querySelector('#sx-content')?.textContent?.trim().substring(0, 200));

    // Back-button content should match fresh load
    expect(backContent).toBe(freshContent);
  });
});
