/**
 * Migration orchestrator.
 *
 * Flow:
 *   1. Check git worktree is clean (bail if not)
 *   2. Run detection
 *   3. Print report
 *   4. Interactive confirmation (unless --yes)
 *   5. Apply codemods
 *   6. Run typecheck
 *   7. Print final summary
 */
import { existsSync, writeFileSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { detect } from './detect';
import { printReport, printStep, printStepDone, printStepFailed, printStepSkipped, printWarning, printError, printSuccess, printManualSummary, printChangeSummary, } from './report';
import { deleteGeneratedDir } from './transforms/generated';
import { transformAppTs } from './transforms/app-ts';
import { transformRouteFile } from './transforms/routes';
import { generateAgentBarrel, generateApiBarrel } from './transforms/barrels';
import { transformPackageJson } from './transforms/package-json';
// ---------------------------------------------------------------------------
// Git worktree cleanliness check
// ---------------------------------------------------------------------------
async function isGitWorktreeClean(projectDir) {
    try {
        const result = await Bun.spawn(['git', 'status', '--porcelain'], {
            cwd: projectDir,
            stdout: 'pipe',
            stderr: 'pipe',
        });
        const output = await new Response(result.stdout).text();
        return output.trim() === '';
    }
    catch {
        // git not available or not a git repo — allow migration to proceed
        return true;
    }
}
function isGitRepo(projectDir) {
    try {
        const result = Bun.spawnSync(['git', 'rev-parse', '--is-inside-work-tree'], {
            cwd: projectDir,
            stdout: 'pipe',
            stderr: 'pipe',
        });
        return result.exitCode === 0;
    }
    catch {
        return false;
    }
}
// ---------------------------------------------------------------------------
// Interactive confirm
// ---------------------------------------------------------------------------
async function confirm(message) {
    process.stdout.write(`  ${message} ${'\x1b[2m'}[y/N]${'\x1b[0m'} `);
    // Read a single line from stdin
    const line = await new Promise((resolve) => {
        let buf = '';
        process.stdin.setEncoding('utf8');
        process.stdin.resume();
        process.stdin.once('data', (chunk) => {
            buf += chunk.toString();
            process.stdin.pause();
            resolve(buf.trim());
        });
    });
    return line.toLowerCase() === 'y' || line.toLowerCase() === 'yes';
}
// ---------------------------------------------------------------------------
// Typecheck
// ---------------------------------------------------------------------------
async function runTypecheck(projectDir) {
    try {
        const proc = Bun.spawn(['bunx', 'tsc', '--noEmit', '--skipLibCheck'], {
            cwd: projectDir,
            stdout: 'pipe',
            stderr: 'pipe',
        });
        await proc.exited;
        const stdout = await new Response(proc.stdout).text();
        const stderr = await new Response(proc.stderr).text();
        const combined = [stdout, stderr].filter(Boolean).join('\n').trim();
        return { ok: proc.exitCode === 0, output: combined };
    }
    catch (e) {
        return { ok: false, output: String(e) };
    }
}
// ---------------------------------------------------------------------------
// Bun install
// ---------------------------------------------------------------------------
async function runBunInstall(projectDir) {
    try {
        const proc = Bun.spawn(['bun', 'install', '--silent'], {
            cwd: projectDir,
            stdout: 'pipe',
            stderr: 'pipe',
        });
        await proc.exited;
        return {
            ok: proc.exitCode === 0,
            error: proc.exitCode !== 0 ? 'bun install failed' : undefined,
        };
    }
    catch (e) {
        return { ok: false, error: String(e) };
    }
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
export async function migrate(opts = {}) {
    const projectDir = resolve(opts.projectDir ?? process.cwd());
    if (!existsSync(projectDir)) {
        printError(`Project directory does not exist: ${projectDir}`);
        return { ok: false, reason: 'Project directory not found' };
    }
    // ── 1. Git worktree check ──────────────────────────────────────────────
    if (isGitRepo(projectDir)) {
        const isClean = await isGitWorktreeClean(projectDir);
        if (!isClean) {
            printError('Git worktree is not clean.\n\n' +
                '       The migration tool rewrites source files. Please commit or stash\n' +
                '       your current changes before running the migration, so you can\n' +
                '       easily review the diff or roll back if needed.\n\n' +
                '       Run:  git status\n' +
                '         or:  git stash');
            return { ok: false, reason: 'Git worktree is not clean' };
        }
    }
    else {
        printWarning('Project is not in a git repository. It is strongly recommended to\n' +
            '       version-control your project before running the migration.');
        if (!opts.yes) {
            const proceed = await confirm('Proceed without git? This cannot be undone.');
            if (!proceed) {
                console.log('\n  Migration cancelled.\n');
                return { ok: false, reason: 'User cancelled' };
            }
        }
    }
    // ── 2. Detection ──────────────────────────────────────────────────────
    console.log('\n  Scanning project for v1 patterns…');
    const detection = await detect(projectDir);
    // ── 3. Report ─────────────────────────────────────────────────────────
    printReport(detection);
    if (detection.findings.length === 0) {
        return { ok: true, changedFiles: [] };
    }
    if (opts.dryRun) {
        console.log('  (dry-run mode — no files modified)\n');
        return { ok: true, changedFiles: [] };
    }
    // Bail if there are manual-only findings with no automatable counterparts
    const hasAuto = detection.findings.some((f) => f.severity === 'auto');
    const hasGuided = detection.findings.some((f) => f.severity === 'guided');
    if (!hasAuto && !hasGuided) {
        console.log('  All findings require manual action. No automated transforms available.\n');
        printManualSummary(detection);
        return { ok: true, changedFiles: [] };
    }
    // ── 4. Confirmation ───────────────────────────────────────────────────
    if (!opts.yes) {
        const proceed = await confirm('Apply the auto-fixable and guided transforms listed above?');
        if (!proceed) {
            console.log('\n  Migration cancelled.\n');
            return { ok: false, reason: 'User cancelled' };
        }
    }
    console.log();
    const changedFiles = [];
    const allChangeSummary = [];
    // ── 5a. Delete src/generated/ ─────────────────────────────────────────
    if (detection.generatedDir) {
        printStep('Deleting src/generated/');
        try {
            deleteGeneratedDir(detection.generatedDir);
            changedFiles.push('src/generated/');
            printStepDone('directory removed');
        }
        catch (e) {
            printStepFailed(String(e));
        }
    }
    // Read original app.ts source before any transforms (needed by config transform)
    const originalAppSrc = detection.appTsPath ? await Bun.file(detection.appTsPath).text() : null;
    // ── 5b. Transform app.ts ──────────────────────────────────────────────
    if (detection.appTsPath && originalAppSrc !== null) {
        printStep('Transforming app.ts');
        const result = transformAppTs(originalAppSrc, detection);
        if (result.complexityError) {
            printStepFailed('complexity guard triggered');
            printWarning(result.complexityError);
        }
        else if (result.source !== null && result.changes.length > 0) {
            writeFileSync(detection.appTsPath, result.source, 'utf8');
            changedFiles.push('app.ts');
            allChangeSummary.push({ file: 'app.ts', changes: result.changes });
            printStepDone(result.changes.length + ' change(s)');
        }
        else {
            printStepSkipped('no mechanical changes needed');
        }
    }
    // Note: analytics/workbench stay in createApp() in v2 - no config file migration needed
    // ── 5c. Transform v1 route files ──────────────────────────────────────
    if (detection.v1RouteFiles.length > 0) {
        console.log(`\n  Transforming ${detection.v1RouteFiles.length} route file(s):`);
        for (const routeFile of detection.v1RouteFiles) {
            const relPath = routeFile.replace(projectDir + '/', '');
            printStep(relPath);
            const src = await Bun.file(routeFile).text();
            const result = transformRouteFile(src);
            if (result.complexityError) {
                printStepFailed('complexity guard triggered');
                printWarning(`${relPath}: ${result.complexityError}`);
            }
            else if (result.source !== null && result.changes.length > 0) {
                writeFileSync(routeFile, result.source, 'utf8');
                changedFiles.push(relPath);
                allChangeSummary.push({ file: relPath, changes: result.changes });
                printStepDone();
            }
            else {
                printStepSkipped('already v2 style');
            }
        }
    }
    // ── 5e. Generate src/api/index.ts barrel ─────────────────────────────
    const apiBarrelFinding = detection.findings.find((f) => f.id === 'missing-api-barrel' || f.id === 'api-barrel-stub');
    if (apiBarrelFinding) {
        printStep('Generating src/api/index.ts barrel');
        const barrel = generateApiBarrel(projectDir);
        if (barrel) {
            const apiIndexPath = join(projectDir, 'src', 'api', 'index.ts');
            writeFileSync(apiIndexPath, barrel, 'utf8');
            changedFiles.push('src/api/index.ts');
            allChangeSummary.push({
                file: 'src/api/index.ts',
                changes: ['Generated API router barrel with AppRouter type export'],
            });
            printStepDone();
        }
        else {
            printStepSkipped('no route files found');
        }
    }
    // ── 5f. Generate src/agent/index.ts barrel ────────────────────────────
    if (!detection.hasAgentBarrel) {
        printStep('Generating src/agent/index.ts barrel');
        const barrel = generateAgentBarrel(projectDir);
        if (barrel) {
            const agentIndexPath = join(projectDir, 'src', 'agent', 'index.ts');
            writeFileSync(agentIndexPath, barrel, 'utf8');
            changedFiles.push('src/agent/index.ts');
            allChangeSummary.push({
                file: 'src/agent/index.ts',
                changes: ['Generated agent barrel exporting default array of all agents'],
            });
            printStepDone();
        }
        else {
            printStepSkipped('no agent files found');
        }
    }
    // ── 5g. Update @agentuity/* packages to ^2.0.0 ────────────────────────
    if (detection.outdatedPackages.length > 0) {
        printStep('Updating @agentuity/* packages to ^2.0.0');
        const packageJsonPath = join(projectDir, 'package.json');
        try {
            const currentContent = await Bun.file(packageJsonPath).text();
            const result = transformPackageJson(currentContent, detection.outdatedPackages);
            if (result.content && result.updated.length > 0) {
                writeFileSync(packageJsonPath, result.content, 'utf8');
                changedFiles.push('package.json');
                allChangeSummary.push({
                    file: 'package.json',
                    changes: result.updated,
                });
                printStepDone(`${result.updated.length} package(s) updated`);
            }
            else {
                printStepSkipped('no changes needed');
            }
        }
        catch (e) {
            printStepFailed(String(e));
        }
    }
    console.log();
    // ── Print applied changes ──────────────────────────────────────────────
    printChangeSummary(allChangeSummary);
    // ── 6. Install dependencies ───────────────────────────────────────────
    const hasPackageJson = existsSync(join(projectDir, 'package.json'));
    if (hasPackageJson && changedFiles.length > 0) {
        printStep('Running bun install');
        const install = await runBunInstall(projectDir);
        if (install.ok) {
            printStepDone('done');
        }
        else {
            printStepFailed(String(install.error));
        }
    }
    // ── 7. Typecheck ─────────────────────────────────────────────────────
    const hasTsConfig = existsSync(join(projectDir, 'tsconfig.json'));
    if (hasTsConfig && changedFiles.length > 0) {
        printStep('Running TypeScript type check');
        const tc = await runTypecheck(projectDir);
        if (tc.ok) {
            printStepDone('no errors');
        }
        else {
            printStepFailed('type errors found');
            console.log('\n  TypeScript errors after migration:');
            console.log(tc.output
                .split('\n')
                .map((l) => `    ${l}`)
                .join('\n'));
            printWarning('Type errors detected. Review the changes above and fix manually.\n' +
                '       The git diff will show exactly what was changed.');
        }
    }
    else if (!hasTsConfig) {
        printWarning('No tsconfig.json found — skipping type check.');
    }
    // ── 7. Manual summary ─────────────────────────────────────────────────
    printManualSummary(detection);
    if (changedFiles.length > 0) {
        printSuccess(`Migration complete! ${changedFiles.length} file(s) modified.\n` +
            `  Review the changes with: git diff`);
    }
    else {
        printSuccess('Migration complete! No files needed to be changed.');
    }
    return { ok: true, changedFiles };
}
//# sourceMappingURL=migrate.js.map