/**
 * Preset loader — resolves and loads external PresetDefinition files.
 *
 * Supports two reference formats:
 * - Relative path (./foo.ts, ../presets/bar.ts) → loaded from disk via jiti
 * - npm package name (my-preset-package) → resolved via Node require
 *
 * Uses jiti for synchronous TypeScript/JavaScript loading, matching the
 * sync constraint of createAppContext() and createRegistry().
 *
 * @see packages/eval/src/composition-root.ts — where this is called
 */
import { existsSync } from "fs";
import { resolve } from "path";
import { pathToFileURL } from "url";
import { createJiti } from "jiti";
import { resolveAilfAlias } from "../../adapters/config-sources/ailf-resolver.js";
/** Thrown for preset-specific load errors (distinguishes from third-party errors) */
class PresetLoadError extends Error {
    constructor(message) {
        super(message);
        this.name = "PresetLoadError";
    }
}
/**
 * Load external presets from file paths or npm package names.
 *
 * @param refs - Preset references (relative paths or package names)
 * @param rootDir - Base directory for resolving relative paths
 * @returns Loaded PresetDefinition array (preserves input order)
 * @throws If a preset file is not found or doesn't export a valid PresetDefinition
 */
export function loadExternalPresets(refs, rootDir) {
    const presets = [];
    for (const ref of refs) {
        const preset = loadSinglePreset(ref, rootDir);
        presets.push(preset);
    }
    return presets;
}
function loadSinglePreset(ref, rootDir) {
    const isPath = ref.startsWith("./") || ref.startsWith("../") || ref.startsWith("/");
    const filePath = isPath ? resolve(rootDir, ref) : ref;
    // Containment check: resolved path must be within rootDir to prevent
    // path traversal (e.g., "../../other-project/secret.ts")
    if (isPath) {
        const resolvedRoot = resolve(rootDir);
        if (!resolve(filePath).startsWith(resolvedRoot)) {
            throw new PresetLoadError(`Preset "${ref}" resolves outside the project root (${resolvedRoot}). ` +
                `Preset paths must be within the project directory.`);
        }
        if (!existsSync(filePath)) {
            throw new PresetLoadError(`Preset file not found: ${filePath} (resolved from "${ref}" relative to ${rootDir})`);
        }
    }
    try {
        const alias = resolveAilfAlias(filePath);
        const jiti = createJiti(pathToFileURL(rootDir).href, {
            interopDefault: true,
            requireCache: true,
            ...(alias ? { alias } : {}),
        });
        // jiti() is the synchronous loader
        const mod = jiti(filePath);
        const preset = extractDefault(mod);
        if (!preset || typeof preset !== "object") {
            throw new PresetLoadError(`Preset "${ref}" does not export a default value. ` +
                `Use \`export default definePreset({ ... })\`.`);
        }
        // Validate minimal shape
        const p = preset;
        if (!p.name || !p.mode || !p.manifest) {
            throw new PresetLoadError(`Preset "${ref}" is missing required fields. ` +
                `A preset must have at least \`name\`, \`mode\`, and \`manifest\`.`);
        }
        return p;
    }
    catch (cause) {
        if (cause instanceof PresetLoadError) {
            throw cause; // Re-throw our own errors
        }
        const message = cause instanceof Error ? cause.message : String(cause);
        throw new Error(`Failed to load preset "${ref}": ${message}`, {
            cause: cause,
        });
    }
}
/**
 * Extract the default export from a module, handling both ESM and CJS patterns.
 */
function extractDefault(mod) {
    if (mod === null || mod === undefined)
        return undefined;
    if (typeof mod !== "object")
        return mod;
    const obj = mod;
    // ESM: { default: value }
    if ("default" in obj)
        return obj.default;
    // CJS: the module itself is the value
    return obj;
}
