/**
 * engine/qa-runners/playwright.js — P-c4a9e7f3
 *
 * Playwright built-in runner adapter for QA Sessions. Registered at boot by
 * engine/qa-runners.js (_registerBuiltins). Priority 50 — the safe default
 * for any HTTP target. Maestro (priority 80) wins on detect tie when the
 * project has `.maestro/`.
 *
 * Detection: always true. Playwright is the safe-default web runner, and
 * QA-session targets are overwhelmingly web apps with an HTTP spawn (PR
 * preview, branch dev server, current worktree dev server, etc.). The
 * priority gate above lets Maestro win when present; explicit
 * `runner: '...'` overrides win before detect() runs at all.
 *
 * Hooks: see engine/qa-runners.js header for the contract.
 */

const fs = require('fs');

const NAME = 'playwright';
const PRIORITY = 50;
const INSTALL_HINT =
  'Playwright is required. From the target project run `npm i -D @playwright/test` and ' +
  '`npx playwright install` (the second command installs the browser binaries). ' +
  'Confirm with `npx playwright --version`.';

function detect(/* target, project */) {
  return true;
}

function _spawnUrl(spawnInfo) {
  if (!spawnInfo) return '';
  if (spawnInfo.attrs && typeof spawnInfo.attrs.base_url === 'string') return spawnInfo.attrs.base_url;
  if (Array.isArray(spawnInfo.ports) && spawnInfo.ports.length > 0) {
    return `http://localhost:${spawnInfo.ports[0]}`;
  }
  return '';
}

function _portsLabel(spawnInfo) {
  if (!spawnInfo || !Array.isArray(spawnInfo.ports) || spawnInfo.ports.length === 0) return '(no ports)';
  return spawnInfo.ports.join(', ');
}

function generateBrief(opts) {
  opts = opts || {};
  const baseUrl = _spawnUrl(opts.spawnInfo);
  const ports = _portsLabel(opts.spawnInfo);
  const flows = opts.flowsRaw || '(no flows provided)';
  const sessionId = (opts.session && opts.session.id) || (opts.sessionId) || '<session-id>';
  const capture = opts.capture || {};
  const captureSummary = Object.entries(capture).filter(([, v]) => !!v).map(([k]) => k).join(', ') || '(none requested)';

  return [
    '## Playwright test draft',
    '',
    `Write a single Playwright test at \`engine/qa-tests/${sessionId}/test.spec.js\` that exercises:`,
    '',
    flows.trim(),
    '',
    '### Required structure',
    '',
    '```js',
    "const { test, expect } = require('@playwright/test');",
    '',
    "test('qa-session flow', async ({ page }) => {",
    baseUrl
      ? `  await page.goto('${baseUrl}');`
      : '  // Managed-spawn did not advertise base_url; build the URL from the spawn port.',
    '  // … translate each flow step into Playwright actions (locator + assertion) …',
    '});',
    '```',
    '',
    baseUrl
      ? `- Live target: \`${baseUrl}\` (ports: ${ports}). Anchor every \`page.goto\` to this URL.`
      : `- Live target ports: ${ports}. Build the URL from the port the dev server bound to.`,
    '- Keep selectors resilient — prefer `getByRole`, `getByText`, `getByTestId` over CSS where possible.',
    '- Each step the user described becomes one or more Playwright actions + a matching `expect(...).toBeVisible()` / `.toHaveURL()` / `.toHaveText()`.',
    '',
    '### Capture configuration',
    '',
    `Requested artifacts: ${captureSummary}. The EXECUTE phase wires \`--video\`, \`--screenshot\`, and trace flags ` +
      'into the `npx playwright test` invocation. The test file itself should NOT hard-code recording (let the CLI flags drive it).',
  ].join('\n');
}

function executeBrief(opts) {
  opts = opts || {};
  const sessionId = (opts.session && opts.session.id) || (opts.sessionId) || '<session-id>';
  const testFile = opts.testFile || `engine/qa-tests/${sessionId}/test.spec.js`;
  const capture = opts.capture || {};
  const outputDir = `engine/qa-artifacts/${sessionId}`;

  const flags = [`--output ${outputDir}`, '--reporter=json'];
  if (capture.video) flags.push('--video=on');
  if (capture.screenshots) flags.push('--screenshot=on');
  if (capture.logs) flags.push('--trace=on');

  const artifactTypes = [];
  if (capture.video) artifactTypes.push('`video` (webm)');
  if (capture.screenshots) artifactTypes.push('`screenshot` (png)');
  if (capture.logs) artifactTypes.push('`log` (trace.zip + stdout)');
  if (artifactTypes.length === 0) artifactTypes.push('`log` (test stdout/stderr only — no capture flags requested)');

  return [
    '## Playwright execute',
    '',
    'Run the drafted test against the live managed-spawn:',
    '',
    '```bash',
    `npx playwright test ${testFile} ${flags.join(' ')}`,
    '```',
    '',
    `Write the result sidecar to \`agents/<your-id>/qa-run-result.json\` referencing artifacts under \`${outputDir}/\`.`,
    '',
    `Artifact types: ${artifactTypes.join(', ')}`,
  ].join('\n');
}

function validateOutputDir(dir) {
  const errors = [];
  if (typeof dir !== 'string' || dir === '') {
    return { ok: false, errors: ['dir must be a non-empty string'] };
  }
  let entries;
  try { entries = fs.readdirSync(dir); }
  catch (err) {
    return { ok: false, errors: [`qa-tests dir ${dir} unreadable: ${err.message}`] };
  }
  const specFiles = entries.filter(f => /\.spec\.(js|ts|mjs|cjs)$/.test(f));
  if (specFiles.length === 0) {
    errors.push(`no Playwright test file (*.spec.{js,ts,mjs,cjs}) found in ${dir}`);
  }
  return { ok: errors.length === 0, errors };
}

module.exports = {
  name: NAME,
  spec: {
    priority: PRIORITY,
    label: 'Playwright',
    description: 'Safe-default web runner. Wins for any HTTP target unless a higher-priority runner detects.',
    installHint: INSTALL_HINT,
    detect,
    generateBrief,
    executeBrief,
    validateOutputDir,
  },
};
