import nodeFs from "node:fs/promises";
import { join } from "node:path";
import { hasOwnErrorCode } from "../error-codes.js";
import { assertFsCanonicalContainedPathIfPresent } from "../path-boundary.js";
export const defaultSourceConfig = Object.freeze({
    judge: Object.freeze({
        agent: "claude-code",
        model: "opus-4.7"
    }),
    out: "runs",
    weights: Object.freeze({
        tests: 0.7,
        judge: 0.3
    }),
    clone_cache_dir: null
});
export async function loadSourceConfig(source, fs = nodeFs) {
    const configPath = join(source.rootDir, ".poe-code-eval.json");
    if (!(await assertFsCanonicalContainedPathIfPresent(fs, source.rootDir, configPath, "source.config"))) {
        return cloneDefaultConfig();
    }
    let raw;
    try {
        raw = await fs.readFile(configPath, "utf8");
    }
    catch (error) {
        if (isMissingPath(error)) {
            return cloneDefaultConfig();
        }
        throw error;
    }
    let parsed;
    try {
        parsed = JSON.parse(raw);
    }
    catch (error) {
        throw new Error(`Failed to parse ${configPath}: ${getErrorMessage(error)}`);
    }
    if (!isRecord(parsed)) {
        throw new Error(`${configPath} must contain a JSON object.`);
    }
    const merged = deepMerge(cloneDefaultConfig(), parsed);
    validateSourceConfig(merged, configPath);
    return merged;
}
function cloneDefaultConfig() {
    return {
        judge: { ...defaultSourceConfig.judge },
        out: defaultSourceConfig.out,
        weights: { ...defaultSourceConfig.weights },
        clone_cache_dir: defaultSourceConfig.clone_cache_dir
    };
}
function deepMerge(base, patch) {
    const result = Object.fromEntries(Object.entries(base));
    for (const [key, value] of Object.entries(patch)) {
        if (value === undefined) {
            continue;
        }
        const existing = Object.hasOwn(result, key) ? result[key] : undefined;
        if (isRecord(existing) && isRecord(value)) {
            Object.defineProperty(result, key, {
                value: deepMerge(existing, value),
                enumerable: true,
                configurable: true,
                writable: true
            });
            continue;
        }
        Object.defineProperty(result, key, {
            value,
            enumerable: true,
            configurable: true,
            writable: true
        });
    }
    return result;
}
function validateSourceConfig(config, configPath) {
    const judge = requireRecord(config.judge, configPath, "judge");
    requireNonBlankString(judge.agent, configPath, "judge.agent");
    requireNonBlankString(judge.model, configPath, "judge.model");
    requireNonBlankString(config.out, configPath, "out");
    const weights = requireRecord(config.weights, configPath, "weights");
    requireWeight(weights.tests, configPath, "weights.tests");
    requireWeight(weights.judge, configPath, "weights.judge");
    if (config.clone_cache_dir !== null) {
        requireNonBlankString(config.clone_cache_dir, configPath, "clone_cache_dir");
    }
}
function requireRecord(value, configPath, fieldPath) {
    if (isRecord(value)) {
        return value;
    }
    throw invalidConfigField(configPath, fieldPath, "object", value);
}
function requireNonBlankString(value, configPath, fieldPath) {
    if (typeof value !== "string") {
        throw invalidConfigField(configPath, fieldPath, "string", value);
    }
    if (value.trim().length === 0) {
        throw invalidConfigField(configPath, fieldPath, "non-blank string", value);
    }
}
function requireWeight(value, configPath, fieldPath) {
    if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
        throw invalidConfigField(configPath, fieldPath, "number from 0 through 1", value);
    }
}
function invalidConfigField(configPath, fieldPath, expected, received) {
    return new Error(`${configPath} (${fieldPath}): expected ${expected}, received ${formatReceived(received)}.`);
}
function formatReceived(value) {
    if (typeof value === "string") {
        return JSON.stringify(value);
    }
    return String(value);
}
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function isMissingPath(error) {
    return hasOwnErrorCode(error, "ENOENT") || hasOwnErrorCode(error, "ENOTDIR");
}
function getErrorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
