// @ts-check
/**
 * Demo interaction tests — verify every demo actually functions.
 */
const { test, expect } = require('playwright/test');
const { loadPage, trackErrors } = require('./helpers');

function island(page, pattern) {
  return page.locator(`[data-sx-island*="${pattern}"]`);
}

async function assertNoClassLeak(page, scope) {
  const loc = scope ? page.locator(scope).first() : page.locator('#sx-root');
  const text = await loc.textContent();
  expect(text).not.toContain('classpx');
  expect(text).not.toContain('classborder');
  expect(text).not.toContain('classbg-');
}


// ===========================================================================
// Reactive island demos
// ===========================================================================

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

  test('counter: + and − change count and doubled', async ({ page }) => {
    await loadPage(page, '(geography.(reactive.(examples.counter)))');
    const el = island(page, 'counter');
    await expect(el).toBeVisible({ timeout: 10000 });
    const buttons = el.locator('button');
    await expect(buttons).toHaveCount(2);

    const plus = buttons.last();
    await plus.click(); await plus.click(); await plus.click();
    await page.waitForTimeout(300);
    const text = await el.textContent();
    expect(text).toContain('3');
    expect(text).toContain('doubled');
    expect(text).toContain('6');

    await buttons.first().click();
    await page.waitForTimeout(300);
    expect(await el.textContent()).toContain('2');
  });

  test('temperature: +/− change celsius and fahrenheit', async ({ page }) => {
    await loadPage(page, '(geography.(reactive.(examples.temperature)))');
    const el = island(page, 'temperature');
    await expect(el).toBeVisible({ timeout: 10000 });
    const buttons = el.locator('button');
    await buttons.last().click();
    await buttons.last().click();
    await page.waitForTimeout(300);
    const text = await el.textContent();
    expect(text).toContain('°C');
    expect(text).toContain('°F');
  });

  test('stopwatch: start shows elapsed time', async ({ page }) => {
    await loadPage(page, '(geography.(reactive.(examples.stopwatch)))');
    const el = island(page, 'stopwatch');
    await expect(el).toBeVisible({ timeout: 10000 });
    const textBefore = await el.textContent();
    await el.locator('button').first().click();
    await page.waitForTimeout(1200);
    expect(await el.textContent()).not.toBe(textBefore);
  });

  test('input-binding: typing updates live preview', async ({ page }) => {
    await loadPage(page, '(geography.(reactive.(examples.input-binding)))');
    const el = island(page, 'input-binding');
    await expect(el).toBeVisible({ timeout: 10000 });
    await el.locator('input').first().fill('playwright test');
    await page.waitForTimeout(300);
    expect(await el.textContent()).toContain('playwright test');
  });

  test('dynamic-class: toggle changes element styling', async ({ page }) => {
    await loadPage(page, '(geography.(reactive.(examples.dynamic-class)))');
    const el = island(page, 'dynamic-class');
    await expect(el).toBeVisible({ timeout: 10000 });
    const htmlBefore = await el.innerHTML();
    await el.locator('button').first().click();
    await page.waitForTimeout(300);
    expect(await el.innerHTML()).not.toBe(htmlBefore);
  });

  test('reactive-list: add button increases items', async ({ page }) => {
    await loadPage(page, '(geography.(reactive.(examples.reactive-list)))');
    const el = island(page, 'reactive-list');
    await expect(el).toBeVisible({ timeout: 10000 });
    const textBefore = await el.textContent();
    await el.locator('button').first().click();
    await page.waitForTimeout(300);
    expect(await el.textContent()).not.toBe(textBefore);
  });

  test('stores: writer and reader share state', async ({ page }) => {
    await loadPage(page, '(geography.(reactive.(examples.stores)))');
    const writer = island(page, 'store-writer');
    const reader = island(page, 'store-reader');
    await expect(writer).toBeVisible({ timeout: 10000 });
    await expect(reader).toBeVisible({ timeout: 10000 });
    expect((await writer.textContent()).length).toBeGreaterThan(0);
    expect((await reader.textContent()).length).toBeGreaterThan(0);
  });

  test('refs: focus button focuses input', async ({ page }) => {
    await loadPage(page, '(geography.(reactive.(examples.refs)))');
    const el = island(page, 'refs');
    await expect(el).toBeVisible({ timeout: 10000 });
    const textBefore = await el.textContent();
    await el.locator('button').first().click();
    await page.waitForTimeout(300);
    const focused = await page.evaluate(() => document.activeElement?.tagName);
    const textAfter = await el.textContent();
    expect(textAfter !== textBefore || focused === 'INPUT').toBeTruthy();
  });

  test('portal: button toggles portal content', async ({ page }) => {
    await loadPage(page, '(geography.(reactive.(examples.portal)))');
    const el = island(page, 'portal');
    await expect(el).toBeVisible({ timeout: 10000 });
    const before = await page.locator('#portal-root').innerHTML();
    await el.locator('button').first().click();
    await page.waitForTimeout(300);
    expect(await page.locator('#portal-root').innerHTML()).not.toBe(before);
  });

  test('imperative: button triggers DOM manipulation', async ({ page }) => {
    await loadPage(page, '(geography.(reactive.(examples.imperative)))');
    const el = island(page, 'imperative');
    await expect(el).toBeVisible({ timeout: 10000 });
    const textBefore = await el.textContent();
    await el.locator('button').first().click();
    await page.waitForTimeout(300);
    expect(await el.textContent()).not.toBe(textBefore);
  });

  test('error-boundary: trigger shows boundary message', async ({ page }) => {
    await loadPage(page, '(geography.(reactive.(examples.error-boundary)))');
    const el = island(page, 'error-boundary');
    await expect(el).toBeVisible({ timeout: 10000 });
    const btn = el.locator('button').filter({ hasText: /error|trigger|throw/i }).first();
    if (await btn.count() > 0) {
      await btn.click();
      await page.waitForTimeout(300);
      expect((await el.textContent()).toLowerCase()).toMatch(/error|caught|boundary/);
    }
  });

  test('event-bridge: sender triggers receiver', async ({ page }) => {
    await loadPage(page, '(geography.(reactive.(examples.event-bridge-demo)))');
    const el = island(page, 'event-bridge');
    await expect(el).toBeVisible({ timeout: 10000 });
    const textBefore = await el.textContent();
    await el.locator('button').first().click();
    await page.waitForTimeout(300);
    expect(await el.textContent()).not.toBe(textBefore);
  });

  test('resource: shows loading then resolved data', async ({ page }) => {
    await loadPage(page, '(geography.(reactive.(examples.resource)))');
    const el = island(page, 'resource');
    await expect(el).toBeVisible({ timeout: 10000 });
    await expect(el).toContainText('Ada', { timeout: 5000 });
  });
});


// ===========================================================================
// Marshes demos
// ===========================================================================

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

  test('hypermedia-feeds: reactive +/− works', async ({ page }) => {
    await loadPage(page, '(geography.(marshes.hypermedia-feeds))');
    const el = island(page, 'marsh-product');
    await expect(el).toBeVisible({ timeout: 10000 });
    const plusBtn = el.locator('button:has-text("+")').first();
    if (await plusBtn.count() > 0) {
      const textBefore = await el.textContent();
      await plusBtn.click();
      await page.waitForTimeout(300);
      expect(await el.textContent()).not.toBe(textBefore);
    }
    await assertNoClassLeak(page, '[data-sx-island*="marsh-product"]');
  });

  test('on-settle: settle evaluates after swap', async ({ page }) => {
    await loadPage(page, '(geography.(marshes.on-settle))');
    const el = island(page, 'marsh-settle');
    await expect(el).toBeVisible({ timeout: 10000 });
    const btn = el.locator('button').first();
    if (await btn.count() > 0) {
      const textBefore = await el.textContent();
      await btn.click();
      await page.waitForTimeout(4000);
      expect(await el.textContent()).not.toBe(textBefore);
    }
    await assertNoClassLeak(page, '[data-sx-island*="marsh-settle"]');
  });

  test('server-signals: server writes to client signal', async ({ page }) => {
    await loadPage(page, '(geography.(marshes.server-signals))');
    const writer = island(page, 'marsh-store-writer');
    const reader = island(page, 'marsh-store-reader');
    await expect(writer).toBeVisible({ timeout: 10000 });
    await expect(reader).toBeVisible({ timeout: 10000 });
    // Click a price button and verify cross-island signal propagation
    const priceBtn = writer.locator('button').first();
    if (await priceBtn.count() > 0) {
      const readerBefore = await reader.textContent();
      await priceBtn.click();
      await page.waitForTimeout(500);
      const readerAfter = await reader.textContent();
      expect(readerAfter).not.toBe(readerBefore);
    }
  });

  test('view-transform: view toggle changes rendering', async ({ page }) => {
    await loadPage(page, '(geography.(marshes.view-transform))');
    const el = island(page, 'marsh-view-transform');
    await expect(el).toBeVisible({ timeout: 10000 });
    // Fetch catalog first — view toggle only changes rendering of loaded items
    const fetchBtn = el.locator('button:has-text("Fetch Catalog")');
    if (await fetchBtn.count() > 0) {
      await fetchBtn.click();
      await page.waitForTimeout(4000);
    }
    const viewBtns = el.locator('button');
    if (await viewBtns.count() >= 2) {
      const textBefore = await el.textContent();
      await viewBtns.nth(1).click();
      await page.waitForTimeout(500);
      expect(await el.textContent()).not.toBe(textBefore);
    }
  });
});


// ===========================================================================
// Server health — no JS errors across demo pages
// ===========================================================================

test.describe('Server health', () => {

  test('no JS errors on reactive demo pages', async ({ page }) => {
    const errors = [];
    page.on('pageerror', err => errors.push(err.message));
    const demos = ['counter', 'temperature', 'stopwatch', 'input-binding',
      'dynamic-class', 'reactive-list', 'stores', 'resource'];
    for (const demo of demos) {
      await loadPage(page, `(geography.(reactive.(examples.${demo})))`);
    }
    const real = errors.filter(e => !e.includes('net::ERR') && !e.includes('fetch'));
    expect(real).toEqual([]);
  });

  test('no JS errors on marshes pages', async ({ page }) => {
    const errors = [];
    page.on('pageerror', err => errors.push(err.message));
    const pages = ['hypermedia-feeds', 'on-settle', 'server-signals',
      'signal-triggers', 'view-transform'];
    for (const p of pages) {
      await loadPage(page, `(geography.(marshes.${p}))`);
    }
    const real = errors.filter(e => !e.includes('net::ERR') && !e.includes('fetch'));
    expect(real).toEqual([]);
  });
});
