import * as fs from "fs";
import * as path from "path";
import { transformSync } from "esbuild";
/**
 * Strategy for `agency compile`: produces output for a downstream build pipeline.
 * Leaves .js/.ts imports untouched. Only rewrites .agency imports.
 */
export class CompileStrategy {
    options;
    constructor(options) {
        this.options = options;
    }
    rewriteImport(modulePath, _sourceFile) {
        if (modulePath.endsWith(".agency")) {
            return modulePath.replace(/\.agency$/, this.options.targetExt);
        }
        return modulePath;
    }
    prepareDependencies(_imports, _sourceFile) {
        // No-op — user's build pipeline handles dependencies
    }
}
/**
 * Strategy for `agency run` / `agency debug` / `agency test`: compiles and
 * immediately executes. All imports must resolve to .js files that exist on disk.
 * Compiles .ts dependencies to .js via esbuild when needed.
 */
export class RunStrategy extends CompileStrategy {
    constructor() {
        super({ targetExt: ".js" });
    }
    rewriteImport(modulePath, sourceFile) {
        if (modulePath.endsWith(".agency")) {
            return super.rewriteImport(modulePath, sourceFile);
        }
        // Always produce .js — Node needs .js at runtime
        return modulePath.replace(/\.ts$/, ".js");
    }
    prepareDependencies(imports, sourceFile) {
        const visited = new Set();
        for (const imp of imports) {
            if (!imp.startsWith("./") && !imp.startsWith("../"))
                continue;
            if (!imp.endsWith(".js"))
                continue;
            const resolved = path.resolve(path.dirname(sourceFile), imp);
            this.ensureJsExists(resolved, sourceFile, visited);
        }
    }
    ensureJsExists(jsPath, importer, visited) {
        const normalized = path.normalize(jsPath);
        if (visited.has(normalized))
            return;
        visited.add(normalized);
        if (fs.existsSync(normalized))
            return;
        const tsPath = normalized.replace(/\.js$/, ".ts");
        if (!fs.existsSync(tsPath)) {
            throw new Error(`Cannot resolve import '${path.relative(path.dirname(importer), normalized)}' from '${importer}'.\n` +
                `Tried: ${normalized}, ${tsPath} — neither file exists.`);
        }
        // Recursively ensure this file's dependencies exist first
        const tsCode = fs.readFileSync(tsPath, "utf-8");
        for (const nestedImp of this.getLocalJsImports(tsCode)) {
            const nestedResolved = path.resolve(path.dirname(tsPath), nestedImp);
            this.ensureJsExists(nestedResolved, tsPath, visited);
        }
        // Then compile this file
        const result = transformSync(tsCode, {
            loader: "ts",
            format: "esm",
            supported: { "top-level-await": true },
        });
        fs.writeFileSync(normalized, result.code);
    }
    /** Extract relative .js imports from TypeScript source code. */
    getLocalJsImports(code) {
        const imports = [];
        const pattern = /\bimport\s+(?:[^'"]+?\s+from\s+)?["']([^"']+)["']/g;
        let match;
        while ((match = pattern.exec(code)) !== null) {
            const specifier = match[1];
            if ((specifier.startsWith("./") || specifier.startsWith("../")) && specifier.endsWith(".js")) {
                imports.push(specifier);
            }
        }
        return imports;
    }
}
