/**
 * TsConfigLoader — loads TypeScript/JavaScript configuration files via jiti.
 *
 * Provides the runtime loading layer for TypeScript configuration files
 * (ailf.config.ts, config/models.ts, tasks/*.task.ts, etc.). Uses `jiti`
 * for zero-config TypeScript import with ESM/CJS support.
 *
 * The loader:
 * 1. Imports the file via jiti (strips types, handles ESM interop)
 * 2. Extracts the default export
 * 3. Returns the raw object — validation is the caller's responsibility
 *
 * @see docs/design-docs/architecture-overhaul/typescript-configuration.md
 */
import { existsSync } from "fs";
import { pathToFileURL } from "node:url";
import { createJiti } from "jiti";
import { resolveAilfAlias } from "./ailf-resolver.js";
// ---------------------------------------------------------------------------
// jiti instance factory — resolves imports relative to the loaded file
// ---------------------------------------------------------------------------
/**
 * Create a jiti instance that resolves bare-specifier imports relative to
 * the given file path, not relative to this loader module.
 *
 * This is critical for pnpm workspaces: a task file at `.ailf/tasks/foo.task.ts`
 * importing `@sanity/ailf` must resolve through the dependency graph
 * visible from the task file's directory, not from deep inside packages/eval/.
 *
 * We pass a `file://` URL (not a bare path) so jiti uses ESM resolution,
 * which matches the `"import"` condition in package.json exports maps.
 *
 * When the user's project cannot resolve `@sanity/ailf` (fresh directory
 * without a local install), we register an alias pointing at the CLI's own
 * bundled copy so the load still succeeds. See `ailf-resolver.ts`.
 */
function createJitiForFile(filePath) {
    const alias = resolveAilfAlias(filePath);
    return createJiti(pathToFileURL(filePath).href, {
        // Interop: handle both `export default` and `module.exports`
        interopDefault: true,
        // Don't require file extensions in imports
        requireCache: true,
        ...(alias ? { alias } : {}),
    });
}
/**
 * Load a TypeScript or JavaScript config file and return its default export.
 *
 * Does NOT validate the result — the caller is responsible for passing
 * the returned value through the appropriate Zod schema.
 *
 * @param filePath - Absolute path to the .ts or .js file
 * @returns The default export of the file, or an error
 */
export async function loadTsConfig(filePath) {
    if (!existsSync(filePath)) {
        return { ok: false, error: `File not found: ${filePath}`, path: filePath };
    }
    try {
        const jiti = createJitiForFile(filePath);
        const mod = await jiti.import(filePath);
        const value = extractDefault(mod);
        if (value === undefined || value === null) {
            return {
                ok: false,
                error: `No default export found in ${filePath}. Config files must use \`export default\`.`,
                path: filePath,
            };
        }
        return { ok: true, value: value, path: filePath };
    }
    catch (cause) {
        const message = cause instanceof Error ? cause.message : String(cause);
        return {
            ok: false,
            error: `Failed to load ${filePath}: ${message}`,
            path: filePath,
        };
    }
}
/**
 * Resolve a config file path with the TS > JS > YAML > JSON priority chain.
 *
 * Given a base path without extension (e.g., `"/path/to/config/models"`),
 * returns the first existing file in priority order, or null if none exist.
 *
 * @param basePath - Path without extension
 * @returns The resolved path with extension, or null
 */
export function resolveConfigPath(basePath) {
    const candidates = [
        { ext: ".ts", format: "ts" },
        { ext: ".js", format: "ts" }, // JS files also go through jiti
        { ext: ".yaml", format: "yaml" },
        { ext: ".yml", format: "yaml" },
        { ext: ".json", format: "json" },
    ];
    for (const { ext, format } of candidates) {
        const candidate = basePath + ext;
        if (existsSync(candidate)) {
            return { path: candidate, format };
        }
    }
    return null;
}
/**
 * Check if a higher-priority TS/JS file exists alongside a YAML/JSON file.
 *
 * Returns a warning message if both exist, or null if no conflict.
 */
export function checkFormatConflict(basePath, activeFormat) {
    if (activeFormat !== "ts")
        return null;
    // Check if a YAML file also exists
    for (const ext of [".yaml", ".yml"]) {
        if (existsSync(basePath + ext)) {
            return (`Both TypeScript and YAML config files exist for ${basePath}. ` +
                `Using TypeScript file; YAML file (${basePath + ext}) is ignored.`);
        }
    }
    return null;
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
 * Extract the default export from a module, handling ESM/CJS interop.
 *
 * Handles these shapes:
 * - `{ default: value }` (ESM default export)
 * - `value` (already the export)
 * - `{ default: { default: value } }` (double-wrapped by interop)
 */
function extractDefault(mod) {
    if (mod === null || mod === undefined)
        return mod;
    // If it's an object with a `default` property, unwrap it
    if (typeof mod === "object" && "default" in mod) {
        const inner = mod.default;
        // Handle double-wrapping
        if (inner !== null && typeof inner === "object" && "default" in inner) {
            return inner.default;
        }
        return inner;
    }
    return mod;
}
