import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { getAgentDir } from "@sheason/pi-coding-agent";
import { getWorkspaceDir } from "./config.js";
import { ensureModelsResourceIds } from "./resource-ids.js";
import { isRecord } from "./utils.js";
const LOCAL_PI_DIR_NAME = ".pi";
const MODELS_FILE_NAME = "models.json";
const MERGED_MODELS_FILE_NAME = "merged-models.json";
export function getHubModelsConfigPaths(cwd, agentDir = getAgentDir()) {
    return {
        globalModelsFile: join(agentDir, MODELS_FILE_NAME),
        localModelsFile: join(cwd, LOCAL_PI_DIR_NAME, MODELS_FILE_NAME),
        mergedModelsFile: join(getWorkspaceDir(cwd), MERGED_MODELS_FILE_NAME),
    };
}
export function materializeMergedModelsConfig(cwd, agentDir = getAgentDir()) {
    const paths = getHubModelsConfigPaths(cwd, agentDir);
    for (const path of [paths.globalModelsFile, paths.localModelsFile]) {
        ensureModelsResourceIds(path);
    }
    const globalConfig = readModelsConfig(paths.globalModelsFile);
    const localConfig = readModelsConfig(paths.localModelsFile);
    const mergedConfig = mergeModelsConfig(globalConfig, localConfig);
    mkdirSync(getWorkspaceDir(cwd), { recursive: true });
    writeFileSync(paths.mergedModelsFile, `${JSON.stringify(mergedConfig, null, 2)}\n`, "utf8");
    return {
        mergedModelsFile: paths.mergedModelsFile,
        sourceFiles: [paths.globalModelsFile, paths.localModelsFile].filter((path) => existsSync(path)),
    };
}
function readModelsConfig(path) {
    if (!existsSync(path)) {
        return { providers: {} };
    }
    return JSON.parse(readFileSync(path, "utf8"));
}
function mergeModelsConfig(globalConfig, localConfig) {
    const providers = {};
    const providerNames = new Set([
        ...Object.keys(globalConfig.providers ?? {}),
        ...Object.keys(localConfig.providers ?? {}),
    ]);
    for (const providerName of providerNames) {
        providers[providerName] = mergeProviderConfig(globalConfig.providers?.[providerName], localConfig.providers?.[providerName]);
    }
    return { providers };
}
function mergeProviderConfig(globalProvider, localProvider) {
    if (!globalProvider) {
        return structuredClone(localProvider ?? {});
    }
    if (!localProvider) {
        return structuredClone(globalProvider);
    }
    const merged = {
        ...globalProvider,
        ...localProvider,
    };
    merged.headers = mergeStringRecord(globalProvider.headers, localProvider.headers);
    merged.compat = mergeUnknownRecord(globalProvider.compat, localProvider.compat);
    merged.modelOverrides = mergeModelOverrides(globalProvider.modelOverrides, localProvider.modelOverrides);
    merged.models = mergeModelConfigs(globalProvider.models, localProvider.models);
    return removeUndefinedFields(merged);
}
function mergeModelConfigs(globalModels, localModels) {
    if (!globalModels && !localModels) {
        return undefined;
    }
    const mergedById = new Map();
    for (const model of globalModels ?? []) {
        mergedById.set(model.id, structuredClone(model));
    }
    for (const model of localModels ?? []) {
        const previous = mergedById.get(model.id);
        mergedById.set(model.id, previous ? mergeModelConfig(previous, model) : structuredClone(model));
    }
    return [...mergedById.values()];
}
function mergeModelConfig(globalModel, localModel) {
    const merged = {
        ...globalModel,
        ...localModel,
    };
    const globalHeaders = asStringRecord(globalModel.headers);
    const localHeaders = asStringRecord(localModel.headers);
    if (globalHeaders || localHeaders) {
        merged.headers = mergeStringRecord(globalHeaders, localHeaders);
    }
    const globalCompat = asUnknownRecord(globalModel.compat);
    const localCompat = asUnknownRecord(localModel.compat);
    if (globalCompat || localCompat) {
        merged.compat = mergeUnknownRecord(globalCompat, localCompat);
    }
    const globalCost = asUnknownRecord(globalModel.cost);
    const localCost = asUnknownRecord(localModel.cost);
    if (globalCost || localCost) {
        merged.cost = mergeUnknownRecord(globalCost, localCost);
    }
    return removeUndefinedFields(merged);
}
function mergeModelOverrides(globalOverrides, localOverrides) {
    if (!globalOverrides && !localOverrides) {
        return undefined;
    }
    const merged = {};
    const modelIds = new Set([...Object.keys(globalOverrides ?? {}), ...Object.keys(localOverrides ?? {})]);
    for (const modelId of modelIds) {
        merged[modelId] = mergeUnknownRecord(globalOverrides?.[modelId], localOverrides?.[modelId]) ?? {};
    }
    return merged;
}
function mergeStringRecord(globalRecord, localRecord) {
    if (!globalRecord && !localRecord) {
        return undefined;
    }
    return {
        ...(globalRecord ?? {}),
        ...(localRecord ?? {}),
    };
}
function mergeUnknownRecord(globalRecord, localRecord) {
    if (!globalRecord && !localRecord) {
        return undefined;
    }
    return deepMergeRecords(globalRecord ?? {}, localRecord ?? {});
}
function deepMergeValue(globalValue, localValue) {
    if (isRecord(globalValue) && isRecord(localValue)) {
        return deepMergeRecords(globalValue, localValue);
    }
    return localValue;
}
function deepMergeRecords(globalRecord, localRecord) {
    const merged = { ...globalRecord };
    for (const [key, value] of Object.entries(localRecord)) {
        merged[key] = key in merged ? deepMergeValue(merged[key], value) : value;
    }
    return merged;
}
function asStringRecord(value) {
    if (!value || typeof value !== "object") {
        return undefined;
    }
    return value;
}
function asUnknownRecord(value) {
    if (!value || typeof value !== "object") {
        return undefined;
    }
    return value;
}
function removeUndefinedFields(value) {
    return Object.fromEntries(Object.entries(value).filter(([, entryValue]) => entryValue !== undefined));
}
//# sourceMappingURL=models-config.js.map