// engine/browser-validator.ts — SPEC-308: Parse spec criteria for UI checkable assertions
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
const execFileAsync = promisify(execFile);
/** Patterns that indicate a visibility assertion. */
const VISIBILITY_KEYWORDS = ['visible', 'shown', 'displayed', 'appear', 'shows', 'see'];
/** Patterns that indicate a text/content assertion. */
const TEXT_KEYWORDS = ['text', 'content', 'says', 'reads', 'label', 'message', 'title'];
/** Patterns that indicate a navigation assertion. */
const NAV_KEYWORDS = ['navigate', 'redirect', 'url', 'page', 'route', 'goes to', 'opens'];
/** Patterns that indicate a form assertion. */
const FORM_KEYWORDS = ['form', 'input', 'submit', 'fill', 'field', 'button', 'click', 'select'];
/** Criterion line pattern — matches `- [ ]` and `- [x]` checkboxes. */
const CRITERION_LINE = /^\s*-\s*\[[ xX]\]\s+(.+)$/;
/** Extract the expected text value from a quoted string in the criterion. */
function extractQuotedText(text) {
    const match = /"([^"]+)"|'([^']+)'/.exec(text);
    return match?.[1] ?? match?.[2];
}
/** Build a Playwright locator hint from a criterion text. */
function buildLocator(criterion) {
    const lower = criterion.toLowerCase();
    // "Submit button" → button:has-text("Submit")
    const buttonMatch = /(\w+(?:\s+\w+)?)\s+button/i.exec(criterion);
    if (buttonMatch?.[1]) {
        return `button:has-text("${buttonMatch[1]}")`;
    }
    // "error message" → [role="alert"]
    if (lower.includes('error message') || lower.includes('error alert')) {
        return '[role="alert"]';
    }
    // "link X" → a:has-text("X")
    const linkMatch = /link\s+["']?([^"'\s]+)["']?/i.exec(criterion);
    if (linkMatch?.[1]) {
        return `a:has-text("${linkMatch[1]}")`;
    }
    // "heading X" or "title X"
    const headingMatch = /(?:heading|title)\s+["']?([^"'\n]+?)["']?\s*$/i.exec(criterion);
    if (headingMatch?.[1]) {
        return `h1:has-text("${headingMatch[1]}")`;
    }
    // "input X" or "field X"
    const inputMatch = /(?:input|field)\s+["']?([^"'\s]+)["']?/i.exec(criterion);
    if (inputMatch?.[1]) {
        return `input[name="${inputMatch[1]}"], [placeholder*="${inputMatch[1]}"]`;
    }
    return undefined;
}
/** Classify a criterion line into a UIAssertion type. */
function classifyAssertion(criterion) {
    const lower = criterion.toLowerCase();
    if (FORM_KEYWORDS.some((kw) => lower.includes(kw))) {
        return 'form';
    }
    if (NAV_KEYWORDS.some((kw) => lower.includes(kw))) {
        return 'navigation';
    }
    if (TEXT_KEYWORDS.some((kw) => lower.includes(kw))) {
        return 'text';
    }
    if (VISIBILITY_KEYWORDS.some((kw) => lower.includes(kw))) {
        return 'visibility';
    }
    return 'generic';
}
/**
 * Extract UI-testable assertions from spec acceptance criteria markdown.
 * Parses lines matching `- [ ]` or `- [x]` patterns.
 */
export function extractUIAssertions(criteriaMarkdown) {
    const assertions = [];
    for (const line of criteriaMarkdown.split('\n')) {
        const match = CRITERION_LINE.exec(line);
        if (!match?.[1]) {
            continue;
        }
        const criterion = match[1].trim();
        const type = classifyAssertion(criterion);
        const locator = buildLocator(criterion);
        const expected = extractQuotedText(criterion);
        assertions.push({
            criterion,
            type,
            ...(locator !== undefined && { locator }),
            ...(expected !== undefined && { expected }),
        });
    }
    return assertions;
}
/** Escape a string for use inside a JS template literal. */
function escapeTemplateLiteral(str) {
    return str.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
}
/**
 * Generate a complete Playwright test file content from UI assertions.
 * Uses @playwright/test syntax with test.step() per assertion.
 */
export function generatePlaywrightTest(assertions, specId, baseUrl) {
    const steps = assertions.map((a, i) => {
        const stepName = escapeTemplateLiteral(a.criterion);
        const locator = a.locator ? escapeTemplateLiteral(a.locator) : null;
        let body;
        switch (a.type) {
            case 'visibility':
                body = locator
                    ? `await expect(page.locator(\`${locator}\`)).toBeVisible();`
                    : `// TODO: add locator for: ${a.criterion}`;
                break;
            case 'text':
                body =
                    locator && a.expected
                        ? `await expect(page.locator(\`${locator}\`)).toContainText(\`${escapeTemplateLiteral(a.expected)}\`);`
                        : locator
                            ? `await expect(page.locator(\`${locator}\`)).toBeVisible();`
                            : `// TODO: verify text for: ${a.criterion}`;
                break;
            case 'navigation':
                body = a.expected
                    ? `await expect(page).toHaveURL(/${escapeTemplateLiteral(a.expected)}/);`
                    : `// TODO: verify navigation for: ${a.criterion}`;
                break;
            case 'form':
                body = locator
                    ? `await expect(page.locator(\`${locator}\`)).toBeVisible();`
                    : `// TODO: interact with form for: ${a.criterion}`;
                break;
            case 'generic':
                body = `// TODO: verify: ${a.criterion}`;
                break;
            default: {
                const _exhaustive = a.type;
                body = `// TODO: verify: ${String(_exhaustive)}`;
            }
        }
        return `    await test.step('${String(i + 1)}. ${stepName}', async () => {\n      ${body}\n    });`;
    });
    return [
        `import { test, expect } from '@playwright/test';`,
        ``,
        `// Generated by Planu — SPEC-308 browser validator`,
        `// Spec: ${specId}`,
        `// Base URL: ${baseUrl}`,
        ``,
        `test('${specId} — UI acceptance criteria', async ({ page }) => {`,
        `  await page.goto('${baseUrl}');`,
        ``,
        ...steps,
        `});`,
        ``,
    ].join('\n');
}
/**
 * Check if Playwright is available in the current environment.
 * Returns the version string (e.g. "Version 1.44.0") or null if not found.
 */
export async function checkPlaywrightAvailable() {
    try {
        const { stdout } = await execFileAsync('npx', ['playwright', '--version'], {
            timeout: 5000,
        });
        return stdout.trim() || null;
    }
    catch {
        return null;
    }
}
//# sourceMappingURL=browser-validator.js.map