/**
 * recording-to-run.e2e.test.ts
 *
 * End-to-end coverage for the path users actually take:
 *   1. browser-recorder captures a sequence of actions (we synthesize one
 *      directly — recorder unit tests already exercise the capture logic).
 *   2. compileScript turns the recording into a runnable browser-macro source.
 *   3. The compiled source is written to disk and executed via runUserScript
 *      against a real headless chromium.
 *   4. The script's returned value matches what the recorded actions would
 *      have produced if a human ran them.
 *
 * Until now each leg had unit tests, but the glue between them was only
 * verified by hand. This catches regressions where the compiler emits valid
 * code that the runtime can't actually execute.
 *
 * Gated behind SKIP_PLAYWRIGHT_E2E=1 — chromium spin-up is ~2-5s per case.
 */

import { describe, it, expect, afterEach } from 'vitest';
import { writeFileSync, unlinkSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import type { RecordedAction } from '../engine/browser-recorder.js';
import { compileScript } from './script-compiler.js';
import { runUserScript } from './script-runner.js';

const skipPlaywright = process.env['SKIP_PLAYWRIGHT_E2E'] === '1';

const tmpFiles: string[] = [];
afterEach(() => {
  for (const f of tmpFiles) {
    try { unlinkSync(f); } catch { /* already gone */ }
  }
  tmpFiles.length = 0;
});

function writeTmpScript(source: string): string {
  const path = join(tmpdir(), `recording-e2e-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.js`);
  writeFileSync(path, source, 'utf-8');
  tmpFiles.push(path);
  return path;
}

function action(
  type: RecordedAction['type'],
  extras: Partial<Omit<RecordedAction, 'type' | 'ts'>> = {},
): RecordedAction {
  return { type, ts: '2026-05-11T00:00:00Z', ...extras };
}

describe.skipIf(skipPlaywright)('recording → compileScript → runUserScript (E2E)', () => {
  it('compiles a recording with goto + click + getText and executes it', async () => {
    // Recording captured against a synthetic data: URL page so the test
    // doesn't depend on an external HTTP server.
    const html = `<!doctype html><html><body>
      <button id="go" onclick="document.getElementById('out').textContent='clicked-result'">Go</button>
      <span id="out"></span>
    </body></html>`;
    const dataUrl = 'data:text/html,' + encodeURIComponent(html);

    const recording: RecordedAction[] = [
      action('goto', { url: dataUrl }),
      action('click', { selector: '#go' }),
      action('getText', { selector: '#out' }),
    ];

    const { source } = compileScript({
      recording,
      description: 'E2E pipeline smoke test',
    });

    // Sanity check: the compiler emitted something we expect to run.
    expect(source).toContain('await page.goto(');
    expect(source).toContain("page.locator('#go').click()");
    expect(source).toContain('return __text;');

    const scriptPath = writeTmpScript(source);
    const result = await runUserScript({
      scriptPath,
      params: {},
      runtime: 'playwright',
    });

    expect(result.result).toBe('clicked-result');
  }, 30_000);

  it('threads paramHints from recording through to the runtime', async () => {
    // The recording fills a field with a known sentinel value; paramHints turn
    // that into a params.{name} reference at compile time. At runtime we pass
    // a different value for {name} and verify the page reflects the new value.
    const html = `<!doctype html><html><body>
      <input id="search" />
      <button id="submit" onclick="document.getElementById('out').textContent='searched:' + document.getElementById('search').value">Submit</button>
      <span id="out"></span>
    </body></html>`;
    const dataUrl = 'data:text/html,' + encodeURIComponent(html);

    const recording: RecordedAction[] = [
      action('goto', { url: dataUrl }),
      action('fill', { selector: '#search', value: 'recorded-keyword' }),
      action('click', { selector: '#submit' }),
      action('getText', { selector: '#out' }),
    ];

    const { source, meta } = compileScript({
      recording,
      description: 'paramHint plumbing',
      paramHints: [
        { name: 'keyword', valueToReplace: 'recorded-keyword', type: 'string' },
      ],
    });

    // The compiler should have replaced the literal with params.keyword and
    // surfaced the param spec in the frontmatter.
    expect(source).toContain('.fill(params.keyword)');
    expect(meta.params).toEqual([{ name: 'keyword', type: 'string' }]);

    const scriptPath = writeTmpScript(source);
    const result = await runUserScript({
      scriptPath,
      params: { keyword: 'replayed-keyword' },
      runtime: 'playwright',
    });

    expect(result.result).toBe('searched:replayed-keyword');
  }, 30_000);
});
