import { isRecord } from "../utils.js";
function namespaceMcp(mcp, source) {
    if (source?.kind === "peer") {
        return undefined;
    }
    return mcp;
}
function namespaceLayer(layer) {
    return {
        ...layer,
        mcp: namespaceMcp(layer.mcp, layer.source),
    };
}
function isModelArray(value) {
    return Array.isArray(value) && value.every((item) => isRecord(item) && typeof item.id === "string");
}
function isModelsConfigRecord(value) {
    return isRecord(value) && isRecord(value.providers);
}
function deepMergePreferLeft(left, right) {
    if (isModelArray(left) && isModelArray(right)) {
        const out = left.map((model) => ({ ...model }));
        const seenIds = new Set(out.map((model) => model.id));
        for (const model of right) {
            if (!seenIds.has(model.id)) {
                seenIds.add(model.id);
                out.push({ ...model });
            }
        }
        return out;
    }
    if (!isRecord(left) || !isRecord(right)) {
        return left ?? right;
    }
    const out = { ...left };
    for (const [key, value] of Object.entries(right)) {
        out[key] = key in out ? deepMergePreferLeft(out[key], value) : value;
    }
    return out;
}
function mergeModelsWithHubPriority(left, right, hubProviders, hubModels) {
    if (!isModelsConfigRecord(left) || !isModelsConfigRecord(right)) {
        return deepMerge(left, right);
    }
    const mergedProviders = { ...left.providers };
    for (const [providerName, rightProvider] of Object.entries(right.providers)) {
        const leftProvider = mergedProviders[providerName];
        if (!hubProviders.has(providerName) || !isRecord(leftProvider) || !isRecord(rightProvider)) {
            mergedProviders[providerName] =
                leftProvider === undefined ? rightProvider : deepMerge(leftProvider, rightProvider);
            continue;
        }
        const mergedProvider = deepMergePreferLeft(leftProvider, rightProvider);
        if (isRecord(mergedProvider) &&
            Array.isArray(leftProvider.models) &&
            Array.isArray(rightProvider.models) &&
            isModelArray(leftProvider.models) &&
            isModelArray(rightProvider.models)) {
            const existingById = new Map(leftProvider.models.map((model) => [model.id, { ...model }]));
            for (const model of rightProvider.models) {
                const modelKey = `${providerName}:${model.id}`;
                if (hubModels.has(modelKey)) {
                    continue;
                }
                const existing = existingById.get(model.id);
                existingById.set(model.id, existing ? { ...deepMerge(existing, model), id: model.id } : { ...model });
            }
            mergedProviders[providerName] = {
                ...mergedProvider,
                models: [...existingById.values()],
            };
            continue;
        }
        mergedProviders[providerName] = mergedProvider;
    }
    return {
        ...left,
        ...right,
        providers: mergedProviders,
    };
}
function collectHubModelKeys(layerModels) {
    const providers = new Set();
    const models = new Set();
    if (!isModelsConfigRecord(layerModels)) {
        return { providers, models };
    }
    for (const [providerName, provider] of Object.entries(layerModels.providers)) {
        providers.add(providerName);
        if (!isRecord(provider) || !Array.isArray(provider.models)) {
            continue;
        }
        for (const model of provider.models) {
            if (isRecord(model) && typeof model.id === "string") {
                models.add(`${providerName}:${model.id}`);
            }
        }
    }
    return { providers, models };
}
function mergeModelArrays(left, right) {
    const out = left.map((model) => ({ ...model }));
    const indexById = new Map();
    for (let i = 0; i < out.length; i++) {
        indexById.set(out[i].id, i);
    }
    for (const model of right) {
        const existingIndex = indexById.get(model.id);
        if (existingIndex === undefined) {
            indexById.set(model.id, out.length);
            out.push({ ...model });
            continue;
        }
        const merged = deepMerge(out[existingIndex], model);
        out[existingIndex] = isRecord(merged)
            ? { ...merged, id: model.id }
            : { ...model };
    }
    return out;
}
function deepMerge(left, right) {
    if (isModelArray(left) && isModelArray(right)) {
        return mergeModelArrays(left, right);
    }
    if (!isRecord(left) || !isRecord(right)) {
        return right ?? left;
    }
    const out = { ...left };
    for (const [key, value] of Object.entries(right)) {
        out[key] = key in out ? deepMerge(out[key], value) : value;
    }
    return out;
}
function mergeMcp(left, right) {
    if (!isRecord(left) || !Array.isArray(left.servers)) {
        return right ?? left;
    }
    if (!isRecord(right) || !Array.isArray(right.servers)) {
        return left;
    }
    const merged = deepMerge(left, right);
    return {
        ...(isRecord(merged) ? merged : {}),
        servers: [...left.servers, ...right.servers],
    };
}
export function mergeConfigLayers(layers) {
    let auth = {};
    let models = { providers: {} };
    let settings = {};
    let mcp = { servers: [] };
    const hubModelProviders = new Set();
    const hubModelIds = new Set();
    const contextFiles = [];
    const contextFilePaths = new Set();
    let systemPrompt;
    const appendSystemPrompt = [];
    const seenAppendSystemPrompt = new Set();
    const prompts = [];
    const themes = [];
    const extensions = [];
    for (const layer of layers) {
        if (!layer) {
            continue;
        }
        const normalizedLayer = namespaceLayer(layer);
        if (normalizedLayer.auth) {
            auth = { ...auth, ...normalizedLayer.auth };
        }
        if (normalizedLayer.models !== undefined) {
            if (normalizedLayer.source?.kind === "peer") {
                models = mergeModelsWithHubPriority(models, normalizedLayer.models, hubModelProviders, hubModelIds);
            }
            else {
                models = deepMerge(models, normalizedLayer.models);
                const keys = collectHubModelKeys(normalizedLayer.models);
                for (const providerName of keys.providers) {
                    hubModelProviders.add(providerName);
                }
                for (const modelKey of keys.models) {
                    hubModelIds.add(modelKey);
                }
            }
        }
        if (normalizedLayer.settings !== undefined) {
            settings = deepMerge(settings, normalizedLayer.settings);
        }
        if (normalizedLayer.mcp !== undefined) {
            mcp = mergeMcp(mcp, normalizedLayer.mcp);
        }
        if (normalizedLayer.contextFiles) {
            for (const cf of normalizedLayer.contextFiles) {
                if (!contextFilePaths.has(cf.path)) {
                    contextFiles.push(cf);
                    contextFilePaths.add(cf.path);
                }
            }
        }
        if (normalizedLayer.systemPrompt !== undefined) {
            if (systemPrompt !== undefined && normalizedLayer.source?.kind === "peer") {
                console.warn(`[d-pi config] Peer "${normalizedLayer.source.peerId}" (${normalizedLayer.source.scope}) is overriding systemPrompt from a previous layer. Consider using appendSystemPrompt instead.`);
            }
            systemPrompt = normalizedLayer.systemPrompt;
        }
        if (normalizedLayer.appendSystemPrompt) {
            for (const prompt of normalizedLayer.appendSystemPrompt) {
                if (!seenAppendSystemPrompt.has(prompt)) {
                    seenAppendSystemPrompt.add(prompt);
                    appendSystemPrompt.push(prompt);
                }
            }
        }
        if (normalizedLayer.prompts) {
            prompts.push(...normalizedLayer.prompts);
        }
        if (normalizedLayer.themes) {
            themes.push(...normalizedLayer.themes);
        }
        if (normalizedLayer.extensions) {
            extensions.push(...normalizedLayer.extensions);
        }
    }
    return { auth, models, settings, mcp, contextFiles, systemPrompt, appendSystemPrompt, prompts, themes, extensions };
}
//# sourceMappingURL=merge-config.js.map