// @ts-check
const { test, expect } = require('playwright/test');
const { BASE_URL, waitForSxReady, trackErrors } = require('./helpers');

const TEST_PAGE = '/sx/(geography)';

/**
 * Helper: get the text content of #sx-root, normalised.
 * Strips whitespace differences so SSR and client render can be compared.
 */
async function getSxRootText(page) {
  return page.evaluate(() => {
    const root = document.getElementById('sx-root');
    if (!root) return '';
    return root.innerText.replace(/\s+/g, ' ').trim();
  });
}

/**
 * Helper: get structural snapshot of #sx-root — tag names, ids, classes, text.
 * Ignores attributes that differ between SSR and client (event handlers, etc).
 */
async function getSxRootStructure(page) {
  return page.evaluate(() => {
    function snapshot(el) {
      if (el.nodeType === 3) {
        const text = el.textContent.trim();
        return text ? { t: text } : null;
      }
      if (el.nodeType !== 1) return null;
      const node = { tag: el.tagName.toLowerCase() };
      if (el.id) node.id = el.id;
      // Collect class names, sorted for stability
      const cls = Array.from(el.classList).sort().join(' ');
      if (cls) node.cls = cls;
      // Data attributes for islands and lakes
      const island = el.getAttribute('data-sx-island');
      if (island) node.island = island;
      const lake = el.getAttribute('data-sx-lake');
      if (lake) node.lake = lake;
      // Recurse children
      const children = [];
      for (const child of el.childNodes) {
        const s = snapshot(child);
        if (s) children.push(s);
      }
      if (children.length) node.children = children;
      return node;
    }
    const root = document.getElementById('sx-root');
    return root ? snapshot(root) : null;
  });
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

test.describe('Isomorphic SSR', () => {

  test('page renders visible content without JavaScript', async ({ browser }) => {
    const context = await browser.newContext({ javaScriptEnabled: false });
    const page = await context.newPage();
    await page.goto(BASE_URL + TEST_PAGE, { waitUntil: 'domcontentloaded' });

    // sx-root should exist and have content
    const root = page.locator('#sx-root');
    await expect(root).toBeVisible();

    // Should have real HTML content (headings from the page)
    const headings = await page.locator('#sx-root h2, #sx-content h2').allTextContents();
    expect(headings.length).toBeGreaterThan(0);
    expect(headings[0]).toContain('Geography');

    // Header island should be rendered with hydration marker
    const headerIsland = page.locator('[data-sx-island="layouts/header"]');
    await expect(headerIsland).toBeVisible();

    // Header island should have content
    await expect(page.locator('[data-sx-island="layouts/header"]')).toBeVisible();

    await context.close();
  });

  test('JS and no-JS render the same DOM structure and styles', async ({ browser }) => {
    // Get SSR DOM (no JS)
    const noJsContext = await browser.newContext({ javaScriptEnabled: false });
    const noJsPage = await noJsContext.newPage();
    await noJsPage.goto(BASE_URL + TEST_PAGE, { waitUntil: 'domcontentloaded' });
    const ssrStructure = await getSxRootStructure(noJsPage);
    const ssrText = await getSxRootText(noJsPage);
    await noJsContext.close();

    // Get client DOM (with JS)
    const jsContext = await browser.newContext({ javaScriptEnabled: true });
    const jsPage = await jsContext.newPage();
    await jsPage.goto(BASE_URL + TEST_PAGE, { waitUntil: 'domcontentloaded' });
    await waitForSxReady(jsPage);
    const clientStructure = await getSxRootStructure(jsPage);
    const clientText = await getSxRootText(jsPage);
    await jsContext.close();

    // Text content must match
    expect(ssrText).toBe(clientText);

    // Structure must match — same tags, ids, classes
    expect(JSON.stringify(ssrStructure)).toBe(JSON.stringify(clientStructure));
  });

  test('header island has CSSX styling without JavaScript', async ({ browser }) => {
    const context = await browser.newContext({ javaScriptEnabled: false });
    const page = await context.newPage();
    await page.goto(BASE_URL + TEST_PAGE, { waitUntil: 'domcontentloaded' });

    // The logo should have the violet color class
    const logo = page.locator('[data-sx-island="layouts/header"] span.sx-text-violet-699');
    await expect(logo).toBeVisible();

    // Check that CSSX style tags exist in the page
    const cssxTotal = await page.evaluate(() => {
      const styles = document.querySelectorAll('style[data-sx-css]');
      let total = 0;
      styles.forEach(s => { total += s.textContent.length; });
      return total;
    });
    expect(cssxTotal).toBeGreaterThan(0);

    // The violet rule should exist somewhere
    const hasVioletRule = await page.evaluate(() => {
      const styles = document.querySelectorAll('style[data-sx-css]');
      for (const s of styles) {
        if (s.textContent.includes('sx-text-violet-699')) return true;
      }
      return false;
    });
    expect(hasVioletRule).toBe(true);

    await context.close();
  });

  test('islands hydrate and reactive signals work', async ({ page }) => {
    const t = trackErrors(page);
    await page.goto(BASE_URL + '/sx/', { waitUntil: 'domcontentloaded' });
    await waitForSxReady(page);

    await expect(page.locator('[data-sx-island="layouts/header"]')).toBeVisible({ timeout: 5000 });

    // Header island should be hydrated with reactive elements
    const reactive = page.locator('[data-sx-island="layouts/header"]').getByText('reactive');
    await expect(reactive).toBeVisible({ timeout: 5000 });

    // Reactive colour cycling on "reactive" word
    const colourBefore = await reactive.evaluate(el => el.style.color);
    await reactive.click();
    await page.waitForTimeout(300);
    const colourAfter = await reactive.evaluate(el => el.style.color);
    expect(colourAfter).not.toBe(colourBefore);
    expect(t.errors()).toEqual([]);
  });

  test('navigation links have valid URLs (no [object Object])', async ({ page }) => {
    const t = trackErrors(page);
    await page.goto(BASE_URL + '/sx/', { waitUntil: 'domcontentloaded' });
    await waitForSxReady(page);

    // Check all nav links for [object Object] — regression for FFI primitive overrides
    const brokenLinks = await page.evaluate(() => {
      const links = document.querySelectorAll('a[href]');
      const broken = [];
      for (const a of links) {
        if (a.href.includes('[object') || a.href.includes('object%20Object')) {
          broken.push({ href: a.href, text: a.textContent.trim().slice(0, 40) });
        }
      }
      return broken;
    });
    expect(brokenLinks).toEqual([]);
    expect(t.errors()).toEqual([]);
  });

  test('navigation preserves header island state', async ({ page }) => {
    const t = trackErrors(page);
    await page.goto(BASE_URL + '/sx/', { waitUntil: 'domcontentloaded' });
    await waitForSxReady(page);

    // Click "reactive" to change colour
    const reactive = page.locator('[data-sx-island="layouts/header"]').getByText('reactive');
    await reactive.click();
    await page.waitForTimeout(300);

    // Get the colour after click
    const colourBefore = await reactive.evaluate(el => el.style.color);
    expect(colourBefore).toBeTruthy();

    // Navigate via SPA link
    const geoLink = page.locator('a[sx-get*="geography"]').first();
    await geoLink.click();
    await expect(page).toHaveURL(/geography/, { timeout: 5000 });

    // Colour should be preserved (def-store keeps signals across re-hydration)
    const colourAfter = await reactive.evaluate(el => el.style.color);
    expect(colourAfter).toBe(colourBefore);
    expect(t.errors()).toEqual([]);
  });

});
