// Planu — E2E Test Generator engine (SPEC-381)
// Pure functions for generating E2E tests from spec acceptance criteria.
import { readFile, readdir, mkdir, writeFile } from 'node:fs/promises';
import { join, extname } from 'node:path';
/** Extract acceptance criteria from spec.md content (checkbox lines). */
function extractAcceptanceCriteria(specContent) {
    const lines = specContent.split('\n');
    const criteria = [];
    for (const line of lines) {
        const match = /^\s*-\s*\[[ xX]?\]\s+(.+)$/.exec(line);
        if (match?.[1]) {
            criteria.push(match[1].trim());
        }
    }
    return criteria;
}
/** Extract spec title from frontmatter or first H1. */
function extractSpecTitle(specContent) {
    const titleMatch = /^#\s+(.+)$/m.exec(specContent);
    if (titleMatch?.[1]) {
        return titleMatch[1].replace(/^SPEC-\d+[:\s-]+/i, '').trim();
    }
    const fmMatch = /^title:\s*(.+)$/m.exec(specContent);
    return fmMatch?.[1]?.trim() ?? 'Spec';
}
/** Extract spec description from frontmatter or first paragraph. */
function extractSpecDescription(specContent) {
    const descMatch = /^description:\s*(.+)$/m.exec(specContent);
    if (descMatch?.[1]) {
        return descMatch[1].trim();
    }
    const lines = specContent.split('\n');
    for (const line of lines) {
        const trimmed = line.trim();
        if (trimmed &&
            !trimmed.startsWith('#') &&
            !trimmed.startsWith('-') &&
            !trimmed.startsWith('`')) {
            return trimmed;
        }
    }
    return '';
}
/** Generate Gherkin feature file content. */
export function generateGherkin(title, description, criteria) {
    const scenarios = criteria
        .map((criterion) => `  Scenario: ${criterion}\n` +
        `    Given the system is ready\n` +
        `    When the user performs the action\n` +
        `    Then ${criterion}`)
        .join('\n\n');
    const descLine = description ? `  ${description}\n\n` : '';
    return `Feature: ${title}\n${descLine}${scenarios}\n`;
}
/** Generate Playwright TypeScript test content. */
export function generatePlaywright(title, criteria) {
    const tests = criteria
        .map((criterion) => `  test('${criterion.replace(/'/g, "\\'")}', async ({ page }) => {\n` +
        `    // TODO: implement test for: ${criterion}\n` +
        `  });`)
        .join('\n\n');
    return (`import { test, expect } from '@playwright/test';\n\n` +
        `test.describe('${title.replace(/'/g, "\\'")}', () => {\n` +
        `${tests}\n` +
        `});\n`);
}
/** Generate testRigor plain English steps. */
export function generateTestRigor(title, criteria) {
    const steps = criteria.map((criterion, i) => `${i + 1}. ${criterion}`).join('\n');
    return `# ${title}\n\n${steps}\n`;
}
/** Generate plain numbered list. */
export function generatePlain(criteria) {
    return criteria.map((criterion, i) => `${i + 1}. Verify that ${criterion}`).join('\n') + '\n';
}
/** Get file extension for each framework. */
export function getFrameworkExtension(framework) {
    switch (framework) {
        case 'gherkin':
            return 'feature';
        case 'playwright':
            return 'spec.ts';
        case 'testRigor':
            return 'txt';
        case 'plain':
            return 'txt';
    }
}
/** Derive default output path for tests. */
function deriveDefaultOutputPath(specId, slug, framework) {
    const ext = getFrameworkExtension(framework);
    const fileName = `${specId}-${slug}.${ext}`;
    return join('tests', 'e2e', fileName);
}
/** Extract slug from spec directory name. */
function extractSlug(specDirName) {
    return specDirName.replace(/^SPEC-\d+-?/i, '').toLowerCase() || 'spec';
}
/** Resolve spec directory from specId and projectPath. */
async function resolveSpecDir(specId, projectPath) {
    const specsBase = join(projectPath, 'planu', 'specs');
    let entries;
    try {
        entries = await readdir(specsBase);
    }
    catch {
        return null;
    }
    const upper = specId.toUpperCase();
    const match = entries.find((e) => e.toUpperCase().startsWith(upper));
    return match ? join(specsBase, match) : null;
}
/** Generate E2E tests from a spec. */
export async function generateE2eTests(specId, projectPath, framework = 'gherkin', outputPath) {
    const specDir = await resolveSpecDir(specId, projectPath);
    if (!specDir) {
        return { isError: true, message: `Spec ${specId} not found in ${projectPath}/planu/specs/` };
    }
    let specContent;
    try {
        specContent = await readFile(join(specDir, 'spec.md'), 'utf8');
    }
    catch {
        return { isError: true, message: `Could not read spec.md for ${specId}` };
    }
    const criteria = extractAcceptanceCriteria(specContent);
    const title = extractSpecTitle(specContent);
    const description = extractSpecDescription(specContent);
    let content;
    switch (framework) {
        case 'gherkin':
            content = generateGherkin(title, description, criteria);
            break;
        case 'playwright':
            content = generatePlaywright(title, criteria);
            break;
        case 'testRigor':
            content = generateTestRigor(title, criteria);
            break;
        case 'plain':
            content = generatePlain(criteria);
            break;
    }
    const specDirName = specDir.split('/').pop() ?? specId;
    const slug = extractSlug(specDirName);
    const resolvedOutputPath = outputPath ?? deriveDefaultOutputPath(specId, slug, framework);
    const absoluteOutputPath = resolvedOutputPath.startsWith('/')
        ? resolvedOutputPath
        : join(projectPath, resolvedOutputPath);
    try {
        const dir = absoluteOutputPath.substring(0, absoluteOutputPath.lastIndexOf('/'));
        await mkdir(dir, { recursive: true });
        await writeFile(absoluteOutputPath, content, 'utf8');
    }
    catch {
        // If write fails, still return content without outputPath
        return {
            framework,
            specId,
            testCount: criteria.length,
            content,
            outputPath: null,
        };
    }
    return {
        framework,
        specId,
        testCount: criteria.length,
        content,
        outputPath: absoluteOutputPath,
    };
}
/** List existing E2E test files matching SPEC-XXX pattern. */
export async function getE2eTestStatus(projectPath) {
    const e2eDir = join(projectPath, 'tests', 'e2e');
    let files;
    try {
        files = await readdir(e2eDir);
    }
    catch {
        return { testFiles: [], total: 0 };
    }
    const specPattern = /^(SPEC-\d+)/i;
    const testFiles = [];
    for (const file of files) {
        const ext = extname(file);
        const isTestFile = file.endsWith('.feature') || file.endsWith('.spec.ts') || file.endsWith('.test.ts');
        if (!isTestFile) {
            continue;
        }
        const match = specPattern.exec(file);
        if (!match?.[1]) {
            continue;
        }
        const specId = match[1].toUpperCase();
        let framework;
        if (file.endsWith('.feature')) {
            framework = 'gherkin';
        }
        else if (ext === '.ts') {
            framework = 'playwright';
        }
        else {
            framework = 'unknown';
        }
        testFiles.push({ file, specId, framework });
    }
    return { testFiles, total: testFiles.length };
}
//# sourceMappingURL=e2e-test-generator.js.map