// ── Clover events discovery (THROWAWAY) ─────────────────────────
//
// Drives the fixture HTML through scripted interactions for every Clover field
// and dumps the captured event log to console. The output is meant to be read
// by a human (the SDK author) once, used to inform the deferred-error logic
// and the README event-model docs, then this whole `_discovery/` folder is
// DELETED.
//
// Not part of the regular test suite — uses its own playwright config so it
// doesn't run on CI by accident.

import { test, type Page, type FrameLocator } from '@playwright/test';

// `process` and `__dirname` are Node globals; this spec runs under Playwright's
// Node loader. We avoid `import 'path'` so the spec typechecks even without
// `@types/node` (Playwright's CLI executes it regardless of strict TS errors,
// but this keeps the file clean for IDE consumers).
declare const process: { env: Record<string, string | undefined> };

const PAKMS = process.env.CLOVER_PAKMS;
const MERCHANT = process.env.CLOVER_MERCHANT_ID;
const SDK_URL = process.env.CLOVER_SDK_URL; // optional override

// Fixture is served by the config's `webServer` (python http.server on port 4173)
// because Clover's iframes target the parent origin via postMessage; `file://`
// resolves to `null` and Clover drops all event delivery.
const FIXTURE_URL_BASE = '/fixture.html';

test.skip(
  !PAKMS || !MERCHANT,
  'Set CLOVER_PAKMS and CLOVER_MERCHANT_ID env vars to run discovery.',
);

interface Capture {
  readonly ts: number;
  readonly category: string;
  readonly [key: string]: unknown;
}

function fixtureUrl(): string {
  const qs = new URLSearchParams({ pakms: PAKMS!, merchant: MERCHANT! });
  if (SDK_URL) qs.set('sdk', SDK_URL);
  return `${FIXTURE_URL_BASE}?${qs.toString()}`;
}

async function readCaptures(page: Page): Promise<Capture[]> {
  return await page.evaluate(() => (window as unknown as { __captures: Capture[] }).__captures);
}

async function clearCaptures(page: Page): Promise<void> {
  await page.evaluate(() => {
    (window as unknown as { __captures: Capture[] }).__captures = [];
  });
}

async function waitReady(page: Page): Promise<void> {
  await page.waitForFunction(
    () => (window as unknown as { __ready: boolean }).__ready === true,
    null,
    { timeout: 20_000 },
  );
  // Allow any post-mount handshake postMessages to settle.
  await page.waitForTimeout(500);
}

function frameFor(page: Page, fieldId: string): FrameLocator {
  return page.frameLocator(`#${fieldId} iframe`);
}

async function focusField(page: Page, fieldId: string): Promise<void> {
  await frameFor(page, fieldId).locator('input').click();
  await page.waitForTimeout(80);
}

async function typeField(page: Page, fieldId: string, text: string): Promise<void> {
  const input = frameFor(page, fieldId).locator('input');
  await input.click();
  await input.pressSequentially(text, { delay: 40 });
}

async function clearField(page: Page, fieldId: string): Promise<void> {
  const input = frameFor(page, fieldId).locator('input');
  await input.click();
  // Select-all + delete works across browsers inside iframes. We try Control+A
  // first (Linux/Win) then Meta+A (macOS); the second is a no-op on the wrong OS.
  await input.press('Control+a');
  await input.press('Meta+a');
  await input.press('Delete');
}

async function blurAll(page: Page): Promise<void> {
  // Clicking outside any field's iframe in the parent document removes focus.
  await page.locator('#status').click();
  await page.waitForTimeout(150);
}

interface Step {
  readonly label: string;
  readonly run: () => Promise<void>;
}

function dump(caps: readonly Capture[]): void {
  for (const c of caps) {
    // eslint-disable-next-line no-console
    console.log(JSON.stringify(c));
  }
}

async function runSequence(page: Page, fieldName: string, steps: readonly Step[]): Promise<void> {
  // eslint-disable-next-line no-console
  console.log(`\n══════════════════════════════════════════════════════════`);
  // eslint-disable-next-line no-console
  console.log(`══ FIELD: ${fieldName}`);
  // eslint-disable-next-line no-console
  console.log(`══════════════════════════════════════════════════════════`);
  for (const step of steps) {
    await clearCaptures(page);
    // eslint-disable-next-line no-console
    console.log(`\n── STEP: ${step.label} ──`);
    try {
      await step.run();
    } catch (e) {
      // eslint-disable-next-line no-console
      console.log(`(step threw: ${String(e)})`);
    }
    // Settle: allow Clover's change/blur events to propagate before we read.
    await page.waitForTimeout(400);
    dump(await readCaptures(page));
  }
}

test('Discover Clover field events', async ({ page }) => {
  page.on('console', (msg) => {
    // Forward browser console to test output for visibility into fixture-side errors.
    if (msg.type() === 'error' || msg.type() === 'warning') {
      // eslint-disable-next-line no-console
      console.log(`[browser/${msg.type()}] ${msg.text()}`);
    }
  });
  page.on('pageerror', (err) => {
    // eslint-disable-next-line no-console
    console.log(`[browser/pageerror] ${err.message}`);
  });

  await page.goto(fixtureUrl());
  await waitReady(page);

  // eslint-disable-next-line no-console
  console.log(`\n══════════════════════════════════════════════════════════`);
  // eslint-disable-next-line no-console
  console.log(`══ INIT (everything captured before any interaction)`);
  // eslint-disable-next-line no-console
  console.log(`══════════════════════════════════════════════════════════`);
  dump(await readCaptures(page));

  // ── cardNumber ──
  await runSequence(page, 'cardNumber', [
    { label: 'focus then blur (empty)', run: async () => { await focusField(page, 'cardNumber'); await blurAll(page); } },
    { label: 'type "4"',                run: async () => { await focusField(page, 'cardNumber'); await typeField(page, 'cardNumber', '4'); } },
    { label: 'type "242" (now "4242")', run: async () => { await typeField(page, 'cardNumber', '242'); } },
    { label: 'blur with "4242"',        run: async () => { await blurAll(page); } },
    { label: 'focus + type 12 more digits (full Visa)', run: async () => { await focusField(page, 'cardNumber'); await typeField(page, 'cardNumber', '424242424242'); } },
    { label: 'blur with full valid Visa', run: async () => { await blurAll(page); } },
    { label: 'clear + type bad-Luhn "1234567890123456"', run: async () => { await clearField(page, 'cardNumber'); await typeField(page, 'cardNumber', '1234567890123456'); } },
    { label: 'blur with bad-Luhn',      run: async () => { await blurAll(page); } },
  ]);

  // ── cardDate ──
  await runSequence(page, 'cardDate', [
    { label: 'focus + blur (empty)',     run: async () => { await focusField(page, 'cardDate'); await blurAll(page); } },
    { label: 'type "0"',                 run: async () => { await focusField(page, 'cardDate'); await typeField(page, 'cardDate', '0'); } },
    { label: 'type "1" (now "01")',      run: async () => { await typeField(page, 'cardDate', '1'); } },
    { label: 'type "25" (now "0125")',   run: async () => { await typeField(page, 'cardDate', '25'); } },
    { label: 'blur with "01/25"',        run: async () => { await blurAll(page); } },
    { label: 'clear + type "1325" (invalid month)', run: async () => { await clearField(page, 'cardDate'); await typeField(page, 'cardDate', '1325'); } },
    { label: 'blur with bad month',      run: async () => { await blurAll(page); } },
    { label: 'clear + type "0120" (past)', run: async () => { await clearField(page, 'cardDate'); await typeField(page, 'cardDate', '0120'); } },
    { label: 'blur with past date',      run: async () => { await blurAll(page); } },
  ]);

  // ── cardCvv ──
  await runSequence(page, 'cardCvv', [
    { label: 'focus + blur (empty)',     run: async () => { await focusField(page, 'cardCvv'); await blurAll(page); } },
    { label: 'type "1"',                 run: async () => { await focusField(page, 'cardCvv'); await typeField(page, 'cardCvv', '1'); } },
    { label: 'type "23" (now "123")',    run: async () => { await typeField(page, 'cardCvv', '23'); } },
    { label: 'blur with "123"',          run: async () => { await blurAll(page); } },
    { label: 'focus + add "4" (now "1234")', run: async () => { await focusField(page, 'cardCvv'); await typeField(page, 'cardCvv', '4'); } },
    { label: 'blur with "1234"',         run: async () => { await blurAll(page); } },
    { label: 'clear + type "12" (short)', run: async () => { await clearField(page, 'cardCvv'); await typeField(page, 'cardCvv', '12'); } },
    { label: 'blur with short CVV',      run: async () => { await blurAll(page); } },
  ]);

  // ── cardPostalCode ──
  await runSequence(page, 'cardPostalCode', [
    { label: 'focus + blur (empty)',     run: async () => { await focusField(page, 'cardPostalCode'); await blurAll(page); } },
    { label: 'type "9"',                 run: async () => { await focusField(page, 'cardPostalCode'); await typeField(page, 'cardPostalCode', '9'); } },
    { label: 'type "0210" (now "90210")', run: async () => { await typeField(page, 'cardPostalCode', '0210'); } },
    { label: 'blur with "90210"',        run: async () => { await blurAll(page); } },
    { label: 'clear + type "ABC"',       run: async () => { await clearField(page, 'cardPostalCode'); await typeField(page, 'cardPostalCode', 'ABC'); } },
    { label: 'blur with "ABC"',          run: async () => { await blurAll(page); } },
  ]);

  // ── cardName ──
  await runSequence(page, 'cardName', [
    { label: 'focus + blur (empty)',     run: async () => { await focusField(page, 'cardName'); await blurAll(page); } },
    { label: 'type "J"',                 run: async () => { await focusField(page, 'cardName'); await typeField(page, 'cardName', 'J'); } },
    { label: 'type "ohn Doe" (now "John Doe")', run: async () => { await typeField(page, 'cardName', 'ohn Doe'); } },
    { label: 'blur with "John Doe"',     run: async () => { await blurAll(page); } },
    { label: 'clear + blur (back to empty)', run: async () => { await clearField(page, 'cardName'); await blurAll(page); } },
  ]);

  // ── cardEmail ──
  await runSequence(page, 'cardEmail', [
    { label: 'focus + blur (empty)',     run: async () => { await focusField(page, 'cardEmail'); await blurAll(page); } },
    { label: 'type "j"',                 run: async () => { await focusField(page, 'cardEmail'); await typeField(page, 'cardEmail', 'j'); } },
    { label: 'type "@" (now "j@")',      run: async () => { await typeField(page, 'cardEmail', '@'); } },
    { label: 'type "x.co" (now "j@x.co")', run: async () => { await typeField(page, 'cardEmail', 'x.co'); } },
    { label: 'blur with valid-looking',  run: async () => { await blurAll(page); } },
    { label: 'clear + type "not-an-email"', run: async () => { await clearField(page, 'cardEmail'); await typeField(page, 'cardEmail', 'not-an-email'); } },
    { label: 'blur with malformed email', run: async () => { await blurAll(page); } },
    { label: 'clear + blur (back to empty)', run: async () => { await clearField(page, 'cardEmail'); await blurAll(page); } },
  ]);

  // eslint-disable-next-line no-console
  console.log('\n══ DISCOVERY COMPLETE ══');
});
