// SPEC-597-EXEMPT: canonical mapping (npm-package↔framework, keyword catalogue)
// — must enumerate framework names by design; no detection logic to migrate.
// ci-generator/yaml-builder.ts — Build GitHub Actions YAML content (SPEC-066, SPEC-325)
import { buildXcodeCloudJob, buildFastlaneTestFlightJob, buildAppStoreDeployJob, } from './ios-jobs.js';
import { buildGradleTestJob, buildPlayStoreSigningJob, buildPlayConsoleDeployJob, } from './android-jobs.js';
// -- YAML serialization helpers ----------------------------------------------
function indent(lines, spaces) {
    const pad = ' '.repeat(spaces);
    return lines.map((l) => (l === '' ? '' : `${pad}${l}`));
}
function serializeWith(withValues) {
    const lines = ['      with:'];
    for (const [key, val] of Object.entries(withValues)) {
        const serialized = typeof val === 'string' ? `'${val}'` : String(val);
        lines.push(`        ${key}: ${serialized}`);
    }
    return lines;
}
function serializeEnv(envValues) {
    const lines = ['      env:'];
    for (const [key, val] of Object.entries(envValues)) {
        lines.push(`        ${key}: ${val}`);
    }
    return lines;
}
function serializeStep(step) {
    const lines = [`    - name: ${step.name}`];
    if (step.id) {
        lines.push(`      id: ${step.id}`);
    }
    if (step.if) {
        lines.push(`      if: ${step.if}`);
    }
    if (step.uses) {
        lines.push(`      uses: ${step.uses}`);
    }
    if (step.with) {
        lines.push(...serializeWith(step.with));
    }
    if (step.run) {
        const runLines = step.run.split('\n');
        if (runLines.length === 1) {
            lines.push(`      run: ${step.run}`);
        }
        else {
            lines.push('      run: |');
            lines.push(...indent(runLines, 8));
        }
    }
    if (step.env) {
        lines.push(...serializeEnv(step.env));
    }
    return lines;
}
function serializeJob(jobId, job) {
    const lines = [`  ${jobId}:`];
    lines.push(`    name: ${job.name}`);
    lines.push(`    runs-on: ${job.runsOn}`);
    if (job.needs && job.needs.length > 0) {
        if (job.needs.length === 1) {
            lines.push(`    needs: ${job.needs[0]}`);
        }
        else {
            lines.push(`    needs: [${job.needs.join(', ')}]`);
        }
    }
    if (job.env && Object.keys(job.env).length > 0) {
        lines.push('    env:');
        for (const [key, val] of Object.entries(job.env)) {
            lines.push(`      ${key}: ${val}`);
        }
    }
    lines.push('    steps:');
    for (const step of job.steps) {
        lines.push(...serializeStep(step));
    }
    return lines;
}
function serializeTriggers(triggers) {
    const lines = ['on:'];
    for (const trigger of triggers) {
        switch (trigger) {
            case 'push':
                lines.push('  push:');
                lines.push('    branches: [main, develop]');
                break;
            case 'pull_request':
                lines.push('  pull_request:');
                lines.push('    branches: [main, develop]');
                break;
            case 'workflow_dispatch':
                lines.push('  workflow_dispatch:');
                break;
            case 'schedule':
                lines.push('  schedule:');
                lines.push("    - cron: '0 6 * * 1'");
                break;
        }
    }
    return lines;
}
/** Serialize a CIWorkflow to a YAML string. */
export function serializeWorkflow(workflow) {
    const lines = [
        `name: ${workflow.name}`,
        '',
        ...serializeTriggers(workflow.triggers),
        '',
        'jobs:',
    ];
    for (const [jobId, job] of Object.entries(workflow.jobs)) {
        lines.push('');
        lines.push(...serializeJob(jobId, job));
    }
    return lines.join('\n') + '\n';
}
// -- Step builders -----------------------------------------------------------
function checkoutStep() {
    return { name: 'Checkout code', uses: 'actions/checkout@v4' };
}
// -- Node.js job builders ----------------------------------------------------
function buildNodeSetupSteps(ctx) {
    const version = ctx.nodeVersion ?? '20';
    const steps = [checkoutStep()];
    if (ctx.packageManager === 'pnpm') {
        steps.push({ name: 'Setup pnpm', uses: 'pnpm/action-setup@v4', with: { version: 'latest' } });
    }
    steps.push({
        name: 'Setup Node.js',
        uses: 'actions/setup-node@v4',
        with: {
            'node-version': version,
            ...(ctx.packageManager === 'pnpm' && { cache: 'pnpm' }),
            ...(ctx.packageManager === 'npm' && { cache: 'npm' }),
            ...(ctx.packageManager === 'yarn' && { cache: 'yarn' }),
        },
    });
    const installCmd = ctx.packageManager === 'pnpm'
        ? 'pnpm install --frozen-lockfile'
        : ctx.packageManager === 'yarn'
            ? 'yarn install --frozen-lockfile'
            : ctx.packageManager === 'bun'
                ? 'bun install --frozen-lockfile'
                : 'npm ci';
    steps.push({ name: 'Install dependencies', run: installCmd });
    return steps;
}
// -- Python job builders -----------------------------------------------------
function buildPythonSetupSteps(ctx) {
    const version = ctx.pythonVersion ?? '3.11';
    const steps = [
        checkoutStep(),
        { name: 'Setup Python', uses: 'actions/setup-python@v5', with: { 'python-version': version } },
    ];
    if (ctx.packageManager === 'poetry') {
        steps.push({ name: 'Install Poetry', run: 'pip install poetry' });
        steps.push({ name: 'Install dependencies', run: 'poetry install --no-interaction' });
    }
    else if (ctx.packageManager === 'uv') {
        steps.push({ name: 'Install uv', uses: 'astral-sh/setup-uv@v4' });
        steps.push({ name: 'Install dependencies', run: 'uv sync' });
    }
    else {
        steps.push({ name: 'Install dependencies', run: 'pip install -r requirements.txt' });
    }
    return steps;
}
// -- Go job builders ---------------------------------------------------------
function buildGoSetupSteps(ctx) {
    const version = ctx.goVersion ?? '1.22';
    return [
        checkoutStep(),
        { name: 'Setup Go', uses: 'actions/setup-go@v5', with: { 'go-version': version, cache: true } },
    ];
}
// -- Rust job builders -------------------------------------------------------
function buildRustSetupSteps(ctx) {
    const channel = ctx.rustChannel ?? 'stable';
    return [
        checkoutStep(),
        { name: 'Setup Rust', uses: 'dtolnay/rust-toolchain@master', with: { toolchain: channel } },
        { name: 'Cache cargo', uses: 'Swatinem/rust-cache@v2' },
    ];
}
// -- Java job builders -------------------------------------------------------
function buildJavaSetupSteps(ctx) {
    const version = ctx.javaVersion ?? '21';
    const distribution = ctx.javaDistribution ?? 'temurin';
    return [
        checkoutStep(),
        {
            name: 'Setup Java',
            uses: 'actions/setup-java@v4',
            with: { 'java-version': version, distribution },
        },
        ...(ctx.javaBuildTool === 'gradle'
            ? [{ name: 'Setup Gradle', uses: 'gradle/actions/setup-gradle@v3' }]
            : []),
    ];
}
// -- Generic job builders ----------------------------------------------------
function buildGenericSetupSteps() {
    return [checkoutStep()];
}
function getSetupSteps(ctx) {
    switch (ctx.ecosystem) {
        case 'nodejs':
            return buildNodeSetupSteps(ctx);
        case 'python':
            return buildPythonSetupSteps(ctx);
        case 'go':
            return buildGoSetupSteps(ctx);
        case 'rust':
            return buildRustSetupSteps(ctx);
        case 'java':
        case 'kotlin':
            return buildJavaSetupSteps(ctx);
        case 'ruby':
        case 'php':
        case 'dart':
        case 'dotnet':
        case 'unknown':
        default:
            return buildGenericSetupSteps();
    }
}
// -- Job factories -----------------------------------------------------------
// -- Security audit step builder (SPEC-335) ----------------------------------
/**
 * Build a security audit step appropriate for the detected ecosystem.
 * The step fails CI if high/critical vulnerabilities are found.
 */
function buildSecurityAuditStep(ctx) {
    switch (ctx.ecosystem) {
        case 'nodejs': {
            const auditCmd = ctx.packageManager === 'pnpm'
                ? 'pnpm audit --audit-level=high'
                : ctx.packageManager === 'yarn'
                    ? 'yarn audit --level high'
                    : 'npm audit --audit-level=high';
            return { name: 'Security audit', run: auditCmd };
        }
        case 'python':
            return {
                name: 'Security audit',
                run: 'pip install pip-audit && pip-audit --require-hashes || pip install safety && safety check',
            };
        case 'go':
        case 'rust':
        case 'java':
        case 'kotlin':
        case 'ruby':
        case 'php':
        case 'dart':
        case 'dotnet':
        case 'unknown':
            return null;
    }
}
function buildLintJob(ctx) {
    const steps = [...getSetupSteps(ctx), { name: 'Run linter', run: ctx.lintCommand }];
    return { name: 'Lint', runsOn: 'ubuntu-latest', steps };
}
function buildTestJob(ctx) {
    const steps = [...getSetupSteps(ctx), { name: 'Run tests', run: ctx.testCommand }];
    return { name: 'Test', runsOn: 'ubuntu-latest', steps };
}
function buildBuildJob(ctx, needsJobs) {
    const steps = [...getSetupSteps(ctx), { name: 'Build project', run: ctx.buildCommand }];
    return { name: 'Build', runsOn: 'ubuntu-latest', needs: needsJobs, steps };
}
function buildDeployJob(ctx, needsJobs) {
    const steps = [
        checkoutStep(),
        { name: 'Deploy', run: 'echo "Configure your deployment steps here"' },
    ];
    if (ctx.hasDocker) {
        steps.push({ name: 'Set up Docker Buildx', uses: 'docker/setup-buildx-action@v3' }, { name: 'Build Docker image', run: 'docker build -t app:latest .' });
    }
    return {
        name: 'Deploy',
        runsOn: 'ubuntu-latest',
        needs: needsJobs,
        steps,
        env: { NODE_ENV: 'production' },
    };
}
// -- Drift job builder -------------------------------------------------------
/**
 * Build a Planu drift detection job for inclusion in a GitHub Actions workflow.
 */
export function buildDriftJob(specsDir, threshold, needsJobs) {
    const detectScript = [
        `CHANGED=$(git diff origin/main...HEAD --name-only -- '${specsDir}' | head -1)`,
        'if [ -n "$CHANGED" ]; then',
        '  echo "drift-detected=true" >> "$GITHUB_OUTPUT"',
        'else',
        '  echo "drift-detected=false" >> "$GITHUB_OUTPUT"',
        'fi',
    ].join('\n');
    const steps = [
        { name: 'Checkout code', uses: 'actions/checkout@v4', with: { 'fetch-depth': 0 } },
        {
            name: 'Detect spec drift',
            id: 'drift',
            run: detectScript,
        },
    ];
    if (threshold === 'warning' || threshold === 'critical') {
        const failScript = [
            `echo "Spec drift detected at ${threshold} threshold. Run reconcile_spec to fix."`,
            'exit 1',
        ].join('\n');
        steps.push({
            name: 'Fail on drift',
            if: "steps.drift.outputs['drift-detected'] == 'true'",
            run: failScript,
        });
    }
    return {
        name: 'Planu Drift Detection',
        runsOn: 'ubuntu-latest',
        needs: needsJobs,
        steps,
        outputs: {
            'drift-detected': "${{ steps.drift.outputs['drift-detected'] }}",
        },
    };
}
// -- Main export -------------------------------------------------------------
/**
 * Build a complete CIWorkflow from the stack context and job/trigger options.
 */
export function buildWorkflow(ctx, jobs, triggers, workflowName, driftOptions) {
    const jobMap = {};
    const jobOrder = [];
    if (jobs.includes('lint')) {
        jobMap.lint = buildLintJob(ctx);
        jobOrder.push('lint');
    }
    if (jobs.includes('test')) {
        jobMap.test = buildTestJob(ctx);
        jobOrder.push('test');
    }
    const preBuildJobs = jobOrder.filter((j) => j === 'lint' || j === 'test');
    if (jobs.includes('build')) {
        jobMap.build = buildBuildJob(ctx, preBuildJobs);
        jobOrder.push('build');
    }
    const preDeployJobs = jobOrder.filter((j) => j === 'build');
    if (jobs.includes('deploy')) {
        jobMap.deploy = buildDeployJob(ctx, preDeployJobs);
        jobOrder.push('deploy');
    }
    // SPEC-335: Add security audit job to all generated CI configs
    const securityStep = buildSecurityAuditStep(ctx);
    if (securityStep !== null) {
        const securitySteps = [...getSetupSteps(ctx), securityStep];
        jobMap['security-audit'] = {
            name: 'Security Audit',
            runsOn: 'ubuntu-latest',
            needs: jobOrder.length > 0 && jobOrder[0] !== undefined ? [jobOrder[0]] : [],
            steps: securitySteps,
        };
    }
    if (driftOptions !== undefined) {
        jobMap['planu-drift'] = buildDriftJob(driftOptions.specsDir, driftOptions.threshold, jobOrder);
    }
    const filename = workflowName
        .toLowerCase()
        .replace(/\s+/g, '-')
        .replace(/[^a-z0-9-]/g, '') + '.yml';
    return {
        name: workflowName,
        filename: `.github/workflows/${filename}`,
        triggers,
        jobs: jobMap,
    };
}
// -- Mobile workflow builder (SPEC-325) --------------------------------------
/**
 * Build a CIWorkflow that contains iOS and/or Android CI/CD jobs.
 * iOS and Android jobs run as parallel workstreams in the same workflow.
 *
 * @param mobileConfigs - One or more mobile platform configs (ios, android, or both)
 * @param triggers - GitHub Actions trigger events
 * @param workflowName - Display name of the workflow
 */
export function buildMobileWorkflow(mobileConfigs, triggers, workflowName) {
    const jobMap = {};
    for (const config of mobileConfigs) {
        if (config.platform === 'ios') {
            jobMap['ios-test'] = buildXcodeCloudJob(config);
            jobMap['ios-testflight'] = {
                ...buildFastlaneTestFlightJob(config),
                needs: ['ios-test'],
            };
            jobMap['ios-appstore'] = {
                ...buildAppStoreDeployJob(config),
                needs: ['ios-test'],
            };
        }
        else {
            jobMap['android-test'] = buildGradleTestJob(config);
            jobMap['android-sign'] = {
                ...buildPlayStoreSigningJob(config),
                needs: ['android-test'],
            };
            jobMap['android-deploy'] = buildPlayConsoleDeployJob(config);
        }
    }
    const filename = workflowName
        .toLowerCase()
        .replace(/\s+/g, '-')
        .replace(/[^a-z0-9-]/g, '') + '.yml';
    return {
        name: workflowName,
        filename: `.github/workflows/${filename}`,
        triggers,
        jobs: jobMap,
    };
}
//# sourceMappingURL=yaml-builder.js.map