import { config as runtimeConfigDefault } from "../config.js";
/** Normalize a possibly-Partial RouteRuntimeContext into a strict RuntimeContext.
 *
 *  IMPORTANT: This MUTATES `ctx` in place and returns the same object (object
 *  identity preserved). The runtime mutates fields on the original ctx after
 *  route registration — e.g. `markOAuthReady()` flips `oauthReadyState`,
 *  `startServer()` sets `serverActualPort` once the listener binds. Snapshotting
 *  here would break those updates because deep code (like `waitForOAuthReady`)
 *  re-reads `ctx.oauthReadyState`.
 *
 *  - Live fields (oauthReadyState, oauthReadyPromise, serverActualPort, openai,
 *    apiKey, ...) keep their original references via in-place fill.
 *  - Missing config nests are merged from `runtimeConfigDefault` per top-level
 *    key, so partial fixtures (e.g. `{ config: { storage: {...} } }`) still see
 *    real `oauth`/`ids`/`limits` defaults at deep call sites.
 *
 *  Use this at the top of any function that crosses from `RouteRuntimeContext`
 *  into deep typed code. Per GPT Pro's P05 review: RouteRuntimeContext stays
 *  boundary-only; deep lib code should operate on strict RuntimeContext. */
export function requireRuntimeContext(ctx) {
    const target = (ctx ?? {});
    target.config = mergeRuntimeConfig(target.config);
    if (target.apiKey === undefined && Object.prototype.hasOwnProperty.call(target, "apiKey") === false) {
        target.apiKey = undefined;
    }
    if (target.hasApiKey === undefined)
        target.hasApiKey = false;
    if (target.grokPort === undefined) {
        target.grokPort = target.config.grokProvider?.proxyPort ?? 18645;
    }
    if (target.grokUrl === undefined) {
        const grokCfg = target.config.grokProvider;
        const host = grokCfg?.proxyHost ?? "127.0.0.1";
        const port = target.grokActualPort ?? target.grokPort ?? grokCfg?.proxyPort ?? 18645;
        target.grokUrl = `http://${host}:${port}/v1`;
    }
    if (target.oauthPort === undefined) {
        target.oauthPort = target.config.oauth?.proxyPort ?? 11782;
    }
    if (target.oauthReadyPromise === undefined)
        target.oauthReadyPromise = null;
    if (target.oauthUrl === undefined) {
        target.oauthUrl = `http://127.0.0.1:${target.config.oauth?.proxyPort ?? target.oauthPort ?? 11782}`;
    }
    if (target.openai === undefined)
        target.openai = null;
    if (target.packageVersion === undefined)
        target.packageVersion = "0.0.0";
    if (target.rootDir === undefined)
        target.rootDir = process.cwd();
    if (target.serverConfiguredPort === undefined) {
        target.serverConfiguredPort = target.config.server?.port ?? 11783;
    }
    if (target.serverUrl === undefined) {
        const port = target.serverActualPort ?? target.serverConfiguredPort ?? 11783;
        target.serverUrl = `http://localhost:${port}`;
    }
    if (target.startedAt === undefined)
        target.startedAt = Date.now();
    if (target.xaiApiKey === undefined && !Object.prototype.hasOwnProperty.call(target, 'xaiApiKey'))
        target.xaiApiKey = undefined;
    if (target.hasXaiApiKey === undefined)
        target.hasXaiApiKey = false;
    if (target.xaiApiKeySource === undefined)
        target.xaiApiKeySource = undefined;
    if (target.geminiApiKey === undefined && !Object.prototype.hasOwnProperty.call(target, 'geminiApiKey'))
        target.geminiApiKey = undefined;
    if (target.hasGeminiApiKey === undefined)
        target.hasGeminiApiKey = false;
    if (target.geminiApiKeySource === undefined)
        target.geminiApiKeySource = undefined;
    if (target.vertexServiceAccountJson === undefined && !Object.prototype.hasOwnProperty.call(target, 'vertexServiceAccountJson'))
        target.vertexServiceAccountJson = undefined;
    if (target.vertexProjectId === undefined)
        target.vertexProjectId = undefined;
    if (target.hasVertexKey === undefined)
        target.hasVertexKey = false;
    return target;
}
/** Per-top-level-key merge: caller's nested config keys win, missing nests
 *  fall back to `runtimeConfigDefault`. Avoids deep-clone snapshotting so
 *  callers can still observe live-mutated config values if they exist. */
function mergeRuntimeConfig(partial) {
    if (!partial)
        return runtimeConfigDefault;
    const merged = {};
    for (const k of Object.keys(runtimeConfigDefault)) {
        const fromPartial = partial[k];
        if (fromPartial && typeof fromPartial === "object") {
            merged[k] = {
                ...runtimeConfigDefault[k],
                ...fromPartial,
            };
        }
        else {
            merged[k] = runtimeConfigDefault[k];
        }
    }
    for (const k of Object.keys(partial)) {
        if (!(k in merged))
            merged[k] = partial[k];
    }
    return merged;
}
/** Stub-friendly default for tests. Do NOT use in production boot paths. */
export function createTestRuntimeContext(over = {}) {
    const now = Date.now();
    const base = {
        apiKey: undefined,
        apiKeySource: undefined,
        config: {},
        grokActualPort: undefined,
        grokPort: 18645,
        grokUrl: "http://127.0.0.1:18645/v1",
        hasApiKey: false,
        oauthActualPort: undefined,
        oauthPort: 11782,
        oauthReadyPromise: null,
        oauthReadyState: undefined,
        oauthUrl: "http://127.0.0.1:11782",
        openai: null,
        packageVersion: "0.0.0-test",
        rootDir: process.cwd(),
        serverActualPort: undefined,
        serverConfiguredPort: 11783,
        serverUrl: "http://127.0.0.1:11783",
        startedAt: now,
        xaiApiKey: undefined,
        xaiApiKeySource: undefined,
        hasXaiApiKey: false,
        geminiApiKey: undefined,
        geminiApiKeySource: undefined,
        hasGeminiApiKey: false,
        vertexServiceAccountJson: undefined,
        vertexProjectId: undefined,
        hasVertexKey: false,
    };
    return { ...base, ...over };
}
