import { loadPersistedConfig, savePersistedConfig, } from "./persisted-config.js";
import { ProviderOverrideSchema } from "./agent/provider-launch-config.js";
import { MutableDaemonConfigSchema, MutableDaemonConfigPatchSchema, } from "@kong-link/protocol/messages";
function getLogger(logger) {
    return logger?.child({ module: "daemon-config-store" });
}
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function deepMerge(current, patch) {
    const next = { ...current };
    for (const [key, patchValue] of Object.entries(patch)) {
        if (patchValue === undefined) {
            continue;
        }
        const currentValue = next[key];
        if (isRecord(currentValue) && isRecord(patchValue)) {
            next[key] = deepMerge(currentValue, patchValue);
            continue;
        }
        next[key] = patchValue;
    }
    return next;
}
function getValueAtPath(config, path) {
    return path
        .split(".")
        .reduce((value, segment) => (isRecord(value) ? value[segment] : undefined), config);
}
function isEqualValue(a, b) {
    return JSON.stringify(a) === JSON.stringify(b);
}
export function applyMutableProviderConfigToOverrides(baseOverrides, mutableProviders) {
    if (!baseOverrides && (!mutableProviders || Object.keys(mutableProviders).length === 0)) {
        return undefined;
    }
    const nextOverrides = { ...baseOverrides };
    for (const [providerId, providerConfig] of Object.entries(mutableProviders ?? {})) {
        nextOverrides[providerId] = {
            ...nextOverrides[providerId],
            ...ProviderOverrideSchema.strip().parse(providerConfig),
        };
    }
    return nextOverrides;
}
export class DaemonConfigStore {
    constructor(paseoHome, initial, logger) {
        this.changeListeners = new Set();
        this.fieldChangeHandlers = new Map();
        this.paseoHome = paseoHome;
        this.logger = getLogger(logger);
        this.current = MutableDaemonConfigSchema.parse(initial);
    }
    get() {
        return this.current;
    }
    patch(partial) {
        const parsedPatch = MutableDaemonConfigPatchSchema.parse(partial);
        const next = MutableDaemonConfigSchema.parse(deepMerge(this.current, parsedPatch));
        const changedFieldPaths = Array.from(this.fieldChangeHandlers.keys()).filter((path) => {
            return !isEqualValue(getValueAtPath(this.current, path), getValueAtPath(next, path));
        });
        if (changedFieldPaths.length === 0 && isEqualValue(this.current, next)) {
            return this.current;
        }
        // Persist before updating in-memory state so that if persistence fails,
        // runtime and disk stay consistent.
        this.persistConfig(next);
        this.current = next;
        for (const path of changedFieldPaths) {
            const handlers = this.fieldChangeHandlers.get(path);
            if (!handlers) {
                continue;
            }
            const value = getValueAtPath(next, path);
            for (const handler of handlers) {
                handler(value);
            }
        }
        for (const listener of this.changeListeners) {
            listener(next);
        }
        return next;
    }
    onFieldChange(path, handler) {
        const handlers = this.fieldChangeHandlers.get(path) ?? new Set();
        handlers.add(handler);
        this.fieldChangeHandlers.set(path, handlers);
        return () => {
            const currentHandlers = this.fieldChangeHandlers.get(path);
            if (!currentHandlers) {
                return;
            }
            currentHandlers.delete(handler);
            if (currentHandlers.size === 0) {
                this.fieldChangeHandlers.delete(path);
            }
        };
    }
    onChange(listener) {
        this.changeListeners.add(listener);
        return () => {
            this.changeListeners.delete(listener);
        };
    }
    persistConfig(config) {
        const persisted = loadPersistedConfig(this.paseoHome, this.logger);
        const nextPersisted = mergeMutableConfigIntoPersistedConfig({
            persisted,
            mutable: config,
        });
        savePersistedConfig(this.paseoHome, nextPersisted, this.logger);
    }
}
function mergeMutableConfigIntoPersistedConfig(params) {
    const { persisted, mutable } = params;
    const metadataGenerationProviders = readMetadataGenerationProviders(mutable);
    const providerOverrides = applyMutableProviderConfigToOverrides(persisted.agents?.providers, mutable.providers);
    const persistedAgents = persisted.agents;
    const persistedMetadataGeneration = {
        providers: metadataGenerationProviders,
    };
    const shouldPersistMetadataGeneration = metadataGenerationProviders.length > 0 || persisted.agents?.metadataGeneration !== undefined;
    let nextAgents = persisted.agents;
    if (providerOverrides && Object.keys(providerOverrides).length > 0) {
        nextAgents = {
            ...persistedAgents,
            providers: providerOverrides,
            ...(shouldPersistMetadataGeneration
                ? { metadataGeneration: persistedMetadataGeneration }
                : {}),
        };
    }
    else if (shouldPersistMetadataGeneration) {
        nextAgents = {
            ...persistedAgents,
            metadataGeneration: persistedMetadataGeneration,
        };
    }
    return {
        ...persisted,
        daemon: {
            ...persisted.daemon,
            mcp: {
                ...persisted.daemon?.mcp,
                injectIntoAgents: mutable.mcp.injectIntoAgents,
            },
            autoArchiveAfterMerge: mutable.autoArchiveAfterMerge,
            appendSystemPrompt: mutable.appendSystemPrompt,
        },
        agents: nextAgents,
    };
}
function readMetadataGenerationProviders(mutable) {
    const metadataGeneration = mutable.metadataGeneration;
    if (!isRecord(metadataGeneration)) {
        return [];
    }
    const providers = metadataGeneration["providers"];
    if (!Array.isArray(providers)) {
        return [];
    }
    return providers.flatMap((entry) => {
        if (!isRecord(entry) || typeof entry["provider"] !== "string") {
            return [];
        }
        return [
            {
                provider: entry["provider"],
                ...(typeof entry["model"] === "string" ? { model: entry["model"] } : {}),
                ...(typeof entry["thinkingOptionId"] === "string"
                    ? { thinkingOptionId: entry["thinkingOptionId"] }
                    : {}),
            },
        ];
    });
}
//# sourceMappingURL=daemon-config-store.js.map