// @ts-check
/**
 * Hypermedia handler integration tests.
 * Verifies all example handlers respond correctly via the OCaml HTTP server.
 * Requires server running with request primitives (now, state-get, request-form, etc.)
 */
const { test, expect } = require('playwright/test');
const { loadPage, waitForSxReady, trackErrors, BASE_URL } = require('./helpers');

// Helper: wait for an sx-get/sx-post response to arrive
async function waitForSwap(page, target, timeout = 5000) {
  await page.waitForFunction(
    (sel) => {
      const el = document.querySelector(sel);
      return el && el.innerHTML.trim().length > 0;
    },
    target,
    { timeout }
  );
}

// ===========================================================================
// GET handlers — click/load interactions
// ===========================================================================

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

  test('click-to-load: button fetches content', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.click-to-load)))');
    const btn = page.locator('button:has-text("Load")').first();
    if (await btn.count() > 0) {
      await btn.click();
      await page.waitForTimeout(2000);
      // Should have received content from handler
      const root = page.locator('#sx-root');
      const text = await root.textContent();
      expect(text.length).toBeGreaterThan(10);
    }
  });

  test('tabs: clicking tab loads content', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.tabs)))');
    const tabs = page.locator('[sx-get*="tab"]');
    if (await tabs.count() >= 2) {
      await tabs.nth(1).click();
      await page.waitForTimeout(1500);
      const root = page.locator('#sx-root');
      const text = await root.textContent();
      expect(text).not.toContain('classpx');
    }
  });

  test('polling: content updates automatically', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.polling)))');
    // Wait for at least one poll cycle
    await page.waitForTimeout(3000);
    const root = page.locator('#sx-root');
    const text = await root.textContent();
    expect(text.length).toBeGreaterThan(5);
  });

  test('lazy-loading: content appears without interaction', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.lazy-loading)))');
    await page.waitForTimeout(2000);
    const root = page.locator('#sx-root');
    const text = await root.textContent();
    expect(text.length).toBeGreaterThan(10);
  });

  test('active-search: typing triggers search', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.active-search)))');
    const input = page.locator('input[placeholder*="earch"], input[name="q"]').first();
    if (await input.count() > 0) {
      await input.pressSequentially('python', { delay: 50 });
      await page.waitForTimeout(2000);
      const results = page.locator('#search-results, [id*="result"]').first();
      if (await results.count() > 0) {
        const text = await results.textContent();
        expect(text.toLowerCase()).toContain('python');
      }
    }
  });

  test('select-filter: dropdown filters content', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.select-filter)))');
    const select = page.locator('select').first();
    if (await select.count() > 0) {
      const options = await select.locator('option').allTextContents();
      if (options.length >= 2) {
        await select.selectOption({ index: 1 });
        await page.waitForTimeout(1500);
      }
    }
  });

  test('keyboard-shortcuts: key press triggers action', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.keyboard-shortcuts)))');
    await page.keyboard.press('s');
    await page.waitForTimeout(1000);
    const root = page.locator('#sx-root');
    const text = await root.textContent();
    expect(text.length).toBeGreaterThan(5);
  });

  test('loading-states: shows indicator during load', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.loading-states)))');
    const btn = page.locator('button').first();
    if (await btn.count() > 0) {
      await btn.click();
      // Should show loading state briefly then result
      await page.waitForTimeout(3000);
    }
  });

  test('infinite-scroll: loads more on scroll', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.infinite-scroll)))');
    // Wait for the intersect trigger to fire and load initial items
    await page.waitForTimeout(3000);
    const root = page.locator('#sx-root');
    const text = await root.textContent();
    expect(text.length).toBeGreaterThan(50);
  });

  test('value-select: selecting shows values', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.value-select)))');
    const select = page.locator('select').first();
    if (await select.count() > 0) {
      await select.selectOption({ index: 1 });
      await page.waitForTimeout(1500);
    }
  });
});

// ===========================================================================
// POST handlers — form submissions
// ===========================================================================

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

  test('form-submission: submitting form shows result', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.form-submission)))');
    const form = page.locator('form').first();
    const inputs = form.locator('input[type="text"], input:not([type])');
    if (await inputs.count() > 0) {
      await inputs.first().fill('test-value');
    }
    const submit = form.locator('button[type="submit"], button').first();
    await submit.click();
    await page.waitForTimeout(2000);
    const root = page.locator('#sx-root');
    const text = await root.textContent();
    expect(text).not.toContain('classpx');
  });

  test('inline-validation: invalid email shows error', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.inline-validation)))');
    const input = page.locator('input[type="email"], input[name*="email"]').first();
    if (await input.count() > 0) {
      await input.fill('not-an-email');
      await input.blur();
      await page.waitForTimeout(1500);
    }
  });

  test('reset-on-submit: form resets after submission', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.reset-on-submit)))');
    const form = page.locator('form').first();
    if (await form.count() > 0) {
      const inputs = form.locator('input');
      if (await inputs.count() > 0) {
        await inputs.first().fill('test');
      }
      const btn = form.locator('button').first();
      await btn.click();
      await page.waitForTimeout(2000);
    }
  });
});

// ===========================================================================
// DELETE/PUT handlers
// ===========================================================================

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

  test('delete-row: clicking delete removes row', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.delete-row)))');
    // Auto-accept confirm dialogs (sx-confirm triggers window.confirm)
    page.on('dialog', dialog => dialog.accept());
    const rowsBefore = await page.locator('tbody#delete-rows tr').count();
    expect(rowsBefore).toBeGreaterThan(0);
    const firstRowId = await page.locator('tbody#delete-rows tr').first().getAttribute('id');
    const deleteBtn = page.locator('tbody#delete-rows tr:first-child button').first();
    await deleteBtn.click();
    await page.waitForTimeout(2000);
    const rowsAfter = await page.locator('tbody#delete-rows tr').count();
    // Row count must strictly decrease
    expect(rowsAfter).toBe(rowsBefore - 1);
    // The specific row must be gone
    if (firstRowId) {
      await expect(page.locator(`#${firstRowId}`)).toHaveCount(0);
    }
  });

  test('edit-row: clicking edit shows form', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.edit-row)))');
    const editBtn = page.locator('button:has-text("Edit")').first();
    if (await editBtn.count() > 0) {
      await editBtn.click();
      await page.waitForTimeout(1500);
      // Should show an edit form (inputs)
      const root = page.locator('#sx-root');
      const text = await root.textContent();
      expect(text).not.toContain('classpx');
    }
  });

  test('inline-edit: edit and save round-trip', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.inline-edit)))');
    const editLink = page.locator('a:has-text("Edit"), button:has-text("Edit")').first();
    if (await editLink.count() > 0) {
      await editLink.click();
      await page.waitForTimeout(1500);
    }
  });

  test('bulk-update: select and deactivate', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.bulk-update)))');
    const checkbox = page.locator('input[type="checkbox"]').first();
    if (await checkbox.count() > 0) {
      await checkbox.check();
      const btn = page.locator('button:has-text("Deactivate")').first();
      if (await btn.count() > 0) {
        await btn.click();
        await page.waitForTimeout(2000);
        const table = page.locator('table').first();
        const text = await table.textContent();
        expect(text).not.toContain('classpx');
      }
    }
  });

  test('put-patch: form submits with PUT method', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.put-patch)))');
    const root = page.locator('#sx-root');
    const text = await root.textContent();
    expect(text.length).toBeGreaterThan(5);
  });
});

// ===========================================================================
// Special handlers — dialogs, OOB, animations, progress
// ===========================================================================

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

  test('dialogs: open and close dialog', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.dialogs)))');
    const openBtn = page.locator('button').first();
    if (await openBtn.count() > 0) {
      await openBtn.click();
      await page.waitForTimeout(1000);
    }
  });

  test('oob-swaps: response updates multiple targets', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.oob-swaps)))');
    const btn = page.locator('button').first();
    if (await btn.count() > 0) {
      await btn.click();
      await page.waitForTimeout(2000);
    }
  });

  test('animations: swap includes animation', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.animations)))');
    const btn = page.locator('button').first();
    if (await btn.count() > 0) {
      await btn.click();
      await page.waitForTimeout(1500);
    }
  });

  test('progress-bar: start shows progress', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.progress-bar)))');
    const startBtn = page.locator('button:has-text("Start")').first();
    if (await startBtn.count() > 0) {
      await startBtn.click();
      await page.waitForTimeout(3000);
    }
  });

  test('swap-positions: position swap works', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.swap-positions)))');
    const root = page.locator('#sx-root');
    const text = await root.textContent();
    expect(text.length).toBeGreaterThan(5);
  });

  test('sync-replace: synchronized replacement works', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.sync-replace)))');
    const root = page.locator('#sx-root');
    const text = await root.textContent();
    expect(text.length).toBeGreaterThan(5);
  });

  test('retry: retry on failure works', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.retry)))');
    const root = page.locator('#sx-root');
    const text = await root.textContent();
    expect(text.length).toBeGreaterThan(5);
  });

  test('json-encoding: JSON request/response works', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.json-encoding)))');
    const root = page.locator('#sx-root');
    const text = await root.textContent();
    expect(text.length).toBeGreaterThan(5);
  });

  test('vals-and-headers: custom vals/headers sent', async ({ page }) => {
    await loadPage(page, '(geography.(hypermedia.(example.vals-and-headers)))');
    const root = page.locator('#sx-root');
    const text = await root.textContent();
    expect(text.length).toBeGreaterThan(5);
  });
});
