// engine/tdd-scaffold-generator.ts — SPEC-323
// Generates TDD scaffold (failing test stubs + impl skeleton) from spec ACs.
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
// ---------------------------------------------------------------------------
// Internal helpers
// ---------------------------------------------------------------------------
/** Extract checkbox ACs from spec.md content: lines starting with "- [ ]". */
function extractAcceptanceCriteria(specContent) {
    const lines = specContent.split('\n');
    const criteria = [];
    for (const line of lines) {
        const match = /^\s*-\s*\[\s*\]\s+(.+)$/.exec(line);
        if (match?.[1]) {
            criteria.push(match[1].trim());
        }
    }
    return criteria;
}
/** Convert a criterion string to a camelCase test name. */
function criterionToTestName(criterion) {
    const cleaned = criterion
        .replace(/[^\w\s]/g, ' ')
        .replace(/\s+/g, ' ')
        .trim()
        .toLowerCase();
    const words = cleaned.split(' ').filter(Boolean);
    if (words.length === 0) {
        return 'criterion';
    }
    const [first, ...rest] = words;
    return ((first ?? 'criterion') + rest.map((w) => (w[0]?.toUpperCase() ?? '') + w.slice(1)).join(''));
}
/** Detect test framework from package.json devDependencies / dependencies. */
async function detectFramework(projectPath) {
    const pkgPath = join(projectPath, 'package.json');
    try {
        const raw = await readFile(pkgPath, 'utf8');
        const pkg = JSON.parse(raw);
        const deps = {
            ...(pkg.dependencies ?? {}),
            ...(pkg.devDependencies ?? {}),
        };
        if ('vitest' in deps) {
            return 'vitest';
        }
        if ('jest' in deps) {
            return 'jest';
        }
        if ('mocha' in deps) {
            return 'mocha';
        }
        return 'unknown';
    }
    catch {
        // Check for Python or Java markers
        const setup = await readFile(join(projectPath, 'setup.py'), 'utf8').catch(() => null);
        if (setup !== null) {
            return 'pytest';
        }
        const pom = await readFile(join(projectPath, 'pom.xml'), 'utf8').catch(() => null);
        if (pom !== null) {
            return 'junit';
        }
        return 'unknown';
    }
}
/** Extract spec slug from path for naming generated files. */
function specSlugFromPath(specPath) {
    const parts = specPath.replace(/\\/g, '/').split('/');
    // Try to find SPEC-XXX pattern in path
    for (const part of [...parts].reverse()) {
        if (/SPEC-\d+/.test(part)) {
            return part.toLowerCase().replace(/[^a-z0-9-]/g, '-');
        }
    }
    return 'spec-unknown';
}
// ---------------------------------------------------------------------------
// Test content builders
// ---------------------------------------------------------------------------
function buildVitestContent(stubs) {
    const lines = [
        "import { describe, it, expect } from 'vitest';",
        '',
        '// TDD scaffold — generated by Planu (SPEC-323)',
        '// Each test is a FAILING STUB. Implement the production code to make them pass.',
        '',
        'describe("Acceptance Criteria", () => {',
    ];
    for (const stub of stubs) {
        lines.push(`  it("should ${stub.testName}", () => {`);
        lines.push(`    // AC: ${stub.criterion}`);
        lines.push('    expect.fail("Not implemented — write the implementation first");');
        lines.push('  });');
        lines.push('');
    }
    lines.push('});');
    return lines.join('\n');
}
function buildJestContent(stubs) {
    const lines = [
        '// TDD scaffold — generated by Planu (SPEC-323)',
        '// Each test is a FAILING STUB. Implement the production code to make them pass.',
        '',
        'describe("Acceptance Criteria", () => {',
    ];
    for (const stub of stubs) {
        lines.push(`  it("should ${stub.testName}", () => {`);
        lines.push(`    // AC: ${stub.criterion}`);
        lines.push('    throw new Error("Not implemented — write the implementation first");');
        lines.push('  });');
        lines.push('');
    }
    lines.push('});');
    return lines.join('\n');
}
function buildMochaContent(stubs) {
    const lines = [
        "import assert from 'node:assert';",
        '',
        '// TDD scaffold — generated by Planu (SPEC-323)',
        '',
        'describe("Acceptance Criteria", function () {',
    ];
    for (const stub of stubs) {
        lines.push(`  it("should ${stub.testName}", function () {`);
        lines.push(`    // AC: ${stub.criterion}`);
        lines.push('    assert.fail("Not implemented — write the implementation first");');
        lines.push('  });');
        lines.push('');
    }
    lines.push('});');
    return lines.join('\n');
}
function buildPytestContent(stubs) {
    const lines = [
        '# TDD scaffold — generated by Planu (SPEC-323)',
        '# Each test is a FAILING STUB. Implement the production code to make them pass.',
        '',
        'import pytest',
        '',
    ];
    for (const stub of stubs) {
        const fnName = stub.testName.replace(/([A-Z])/g, '_$1').toLowerCase();
        lines.push(`def test_${fnName}():`);
        lines.push(`    # AC: ${stub.criterion}`);
        lines.push('    raise NotImplementedError("Not implemented — write the implementation first")');
        lines.push('');
    }
    return lines.join('\n');
}
function buildJunitContent(stubs) {
    const lines = [
        '// TDD scaffold — generated by Planu (SPEC-323)',
        'import org.junit.jupiter.api.Test;',
        'import static org.junit.jupiter.api.Assertions.*;',
        '',
        'class AcceptanceCriteriaTest {',
    ];
    for (const stub of stubs) {
        const method = stub.testName.replace(/[^a-zA-Z0-9]/g, '');
        const first = method[0]?.toUpperCase() ?? '';
        const rest = method.slice(1);
        lines.push('    @Test');
        lines.push(`    void should${first}${rest}() {`);
        lines.push(`        // AC: ${stub.criterion}`);
        lines.push('        fail("Not implemented — write the implementation first");');
        lines.push('    }');
        lines.push('');
    }
    lines.push('}');
    return lines.join('\n');
}
function buildTestContent(stubs, framework) {
    switch (framework) {
        case 'vitest':
            return buildVitestContent(stubs);
        case 'jest':
            return buildJestContent(stubs);
        case 'mocha':
            return buildMochaContent(stubs);
        case 'pytest':
            return buildPytestContent(stubs);
        case 'junit':
            return buildJunitContent(stubs);
        case 'unknown':
            return buildVitestContent(stubs);
    }
}
// ---------------------------------------------------------------------------
// Implementation skeleton builder
// ---------------------------------------------------------------------------
function buildImplSkeleton(stubs, slug) {
    const lines = [
        `// ${slug}.ts — implementation skeleton generated by Planu (SPEC-323)`,
        '// TODO: implement each function to make the TDD stubs pass.',
        '',
    ];
    for (const stub of stubs) {
        lines.push(`// AC: ${stub.criterion}`);
        lines.push(`export function ${stub.testName}(): void {`);
        lines.push('  throw new Error("Not implemented");');
        lines.push('}');
        lines.push('');
    }
    return lines.join('\n');
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Generate a TDD scaffold from a spec's acceptance criteria.
 * Returns paths and content for a failing test file and an impl skeleton.
 */
export async function generateTddScaffold(specPath, projectPath) {
    const specContent = await readFile(join(specPath, 'spec.md'), 'utf8').catch(() => readFile(specPath, 'utf8'));
    const criteria = extractAcceptanceCriteria(specContent);
    const framework = await detectFramework(projectPath);
    const slug = specSlugFromPath(specPath);
    const stubs = criteria.map((criterion) => ({
        criterion,
        testName: criterionToTestName(criterion),
        framework,
    }));
    const ext = framework === 'pytest' ? 'py' : framework === 'junit' ? 'java' : 'ts';
    const testExt = framework === 'pytest' ? '_test.py' : framework === 'junit' ? 'Test.java' : '.test.ts';
    const testDir = framework === 'pytest' ? 'tests' : framework === 'junit' ? 'src/test' : 'tests';
    const testFilePath = join(projectPath, testDir, `${slug}${testExt}`);
    const implFilePath = join(projectPath, 'src', `${slug}.${ext}`);
    const testContent = buildTestContent(stubs, framework);
    const implContent = buildImplSkeleton(stubs, slug);
    const instructions = [
        `Test file: ${testFilePath}`,
        `Implementation skeleton: ${implFilePath}`,
        `Framework detected: ${framework}`,
        `Generated ${stubs.length} failing stub(s) from acceptance criteria.`,
        'Run the tests — they should ALL fail (red phase).',
        'Implement each function in the skeleton until all tests pass (green phase).',
        'Refactor for clarity while keeping all tests green.',
    ];
    return {
        testFilePath,
        implFilePath,
        testContent,
        implContent,
        stubs,
        instructions,
        framework,
    };
}
//# sourceMappingURL=tdd-scaffold-generator.js.map