// Browser check for the relate picker (lib/host/blog.sx). Runs against an
// ephemeral host server seeded with a host post + 25 candidates by
// run-picker-check.sh, which copies this spec into the Playwright env and sets
// SX_TEST_URL.
//
// TRIMMED to the irreducibly-real-browser cases. The picker's interactive
// behaviours — populate-on-load, debounced filter, sentinel paging, relate→delete
// row, error/retry visible state — are now SX engine tests in
// web/tests/test-relate-picker.sx (they drive the SAME engine against a mock DOM,
// no Chromium). Its server contract + persistence are SX conformance tests in
// lib/host/tests/blog.sx. What remains here needs a live boosted-SPA browser:
//   1. a boosted form POST swaps in place (bind-boost-form regression), and
//   2. the picker re-binds its triggers on content brought in by a boosted SPA
//      nav (the case an inline <script> picker silently failed).
const { test, expect } = require('playwright/test');

const USER = process.env.SX_ADMIN_USER || 'admin';
const PASS = process.env.SX_ADMIN_PASSWORD || 'letmein';
const HOST = 'picker-host';        // the post whose edit page we drive
// the Related picker box (the edit page now has one picker per kind)
const REL = '.relate-picker[data-kind="related"]';
const RELF = `${REL} .rp-filter`;
const RELR = `${REL} .rp-results`;
const RELROWS = `${RELR} li:not(.rp-more)`;   // candidate rows (exclude the sentinel)

// boot-init marks <html data-sx-ready="true"> once the WASM kernel + web stack
// load. WASM compile + asset fetches, so allow generous time.
async function waitReady(page) {
  await expect(page.locator('html[data-sx-ready="true"]')).toHaveCount(1, { timeout: 45000 });
}

// Navigate to a GUARDED path; the host redirects to /login?next=…, so fill the
// form and we should land back on the original path (exercises the auth flow).
async function loginTo(page, path) {
  await page.goto(path);
  await page.waitForURL(/\/login/);
  await page.fill('input[name="username"]', USER);
  await page.fill('input[name="password"]', PASS);
  await page.click('button[type="submit"]');
  await page.waitForURL((u) => !u.pathname.startsWith('/login'));
}

// Log in directly (for reaching PUBLIC pages while authenticated).
async function login(page) {
  await page.goto('/login');
  await page.fill('input[name="username"]', USER);
  await page.fill('input[name="password"]', PASS);
  await page.click('button[type="submit"]');
  await page.waitForURL((u) => !u.pathname.startsWith('/login'));
}

test.describe('relate picker (browser-only)', () => {
  test('relating a candidate adds it to the current list AND removing keeps the picker', async ({ page }) => {
    // The whole in-page flow the user reported broken — no reloads. Relating a
    // candidate re-renders the editor: the post moves into the current-relations
    // list and the picker re-loads its candidates (it is NOT blanked). Removing it
    // re-renders the editor back: the post leaves the current list and the picker
    // still offers candidates.
    test.setTimeout(75000);
    await loginTo(page, `/${HOST}/edit`);
    await waitReady(page);
    await page.evaluate(() => { window.__noReload = true; });
    // relate Item 13 from the picker
    await page.fill(RELF, 'Item 13');
    await expect.poll(() => page.locator(RELROWS).count(), { timeout: 10000 }).toBe(1);
    await page.locator(`${RELROWS} button`).first().click();
    const relLink = page.locator('a[href="/picker-item-13/"]');
    // ISSUE 1: it now appears in the CURRENT relations list (added, not just removed)
    await expect(relLink).toHaveCount(1, { timeout: 12000 });
    // and the re-rendered picker still offers candidates (not blanked)
    await expect.poll(() => page.locator(RELROWS).count(), { timeout: 12000 }).toBeGreaterThan(0);
    // now remove it via its current-list remove button
    await page.locator('li:has(a[href="/picker-item-13/"]) button').click();
    await expect(relLink).toHaveCount(0, { timeout: 12000 });   // left the current list
    // ISSUE 2: removing must NOT clear "the list of posts to relate"
    await expect.poll(() => page.locator(RELROWS).count(), { timeout: 12000 }).toBeGreaterThan(0);
    expect(await page.evaluate(() => window.__noReload)).toBe(true);   // all in-page, no reload
    // and the relation truly persisted gone (reload shows it not present)
    await page.reload();
    await waitReady(page);
    await expect(page.locator('a[href="/picker-item-13/"]')).toHaveCount(0);
  });

  test('relating a candidate persists the relation', async ({ page }) => {
    test.setTimeout(75000);
    await loginTo(page, `/${HOST}/edit`);
    await waitReady(page);
    await page.fill(RELF, 'Item 07');
    await expect.poll(() => page.locator(RELROWS).count(), { timeout: 10000 }).toBe(1);
    await page.locator(`${RELROWS} button`).first().click();
    await expect(page.locator('a[href="/picker-item-07/"]')).toHaveCount(1, { timeout: 12000 });
    // persisted across a reload
    await page.reload();
    await waitReady(page);
    await expect(page.locator('a[href="/picker-item-07/"]')).toHaveCount(1);
    // and visible on the public post page
    await page.goto(`/${HOST}/`);
    await expect(page.getByRole('heading', { name: 'Related posts' })).toBeVisible();
    await expect(page.locator('body')).toContainText('Picker Item 07');
  });

  test('picker populates after a boosted SPA nav to the edit page', async ({ page }) => {
    // Reach the edit page by CLICKING its link (a boosted SPA nav), not page.goto.
    // The old inline <script> picker never ran on swapped-in content, so the list
    // stayed empty here. The declarative form's "load" trigger is re-bound by the
    // engine on swap, so it populates — that's the regression this guards.
    await login(page);
    await page.goto(`/${HOST}/`);              // public post page, logged in
    await waitReady(page);
    await page.evaluate(() => { window.__noReload = true; });
    await page.locator(`a[href="/${HOST}/edit"]`).first().click();
    await page.waitForURL((u) => u.pathname === `/${HOST}/edit`, { timeout: 15000 });
    expect(await page.evaluate(() => window.__noReload)).toBe(true);   // it was a SPA nav, no full reload
    // the picker, brought in by the swap, loaded its first page of candidates
    await expect.poll(() => page.locator(RELROWS).count(), { timeout: 12000 }).toBeGreaterThanOrEqual(1);
    await expect(page.locator(RELR)).toContainText('Picker Item');
  });
});
