import { style, status } from "../util/theme.js";
import { getProjectRoot } from "../util/fs.js";
import { checkDeepSeekBalance } from "./deepseek/deepseek-balance.js";
import { createDeepSeekReadOnlyTaskRunner } from "./deepseek/deepseek-provider.js";
import { forceDisableDeepSeek, getDeepSeekProviderStatus, resolveDeepSeekApiKey, } from "./deepseek/deepseek-config.js";
import { ProviderHealthRegistry } from "./health.js";
import { createProviderTaskRunner } from "./provider-task-runner.js";
import { DEFAULT_AUTHORITY_PROVIDER } from "./types.js";
import { createDeepSeekProvider } from "./deepseek-provider.js";
import { createProviderRouter } from "./provider-router.js";
import { createOpenAICompatibleReadOnlyTaskRunner } from "./openai-compatible-runner.js";
import { createCodexCliAdvisoryTaskRunner } from "./codex-cli-runner.js";
import { providerDoctorStatus, readProviderRegistry } from "./model-registry.js";
import { createContextBroker } from "../runtime/context-broker.js";
import { createRuntimeRouter } from "../runtime/runtime-router.js";
export async function createProviderBackedTaskRunner(options) {
    const providerPolicy = options.providerPolicy ?? "auto";
    const kimiOptions = options.kimi ?? { cwd: options.cwd ?? getProjectRoot() };
    const runnerCwd = kimiOptions.cwd ?? options.cwd ?? getProjectRoot();
    const kimiRunner = createDisabledLegacyKimiRunner();
    const providerHealth = new ProviderHealthRegistry();
    const providers = [];
    let deepseekRunnerRef;
    const providerRunners = {};
    const providerModels = {};
    for (const provider of options.providers ?? []) {
        providers.push(provider);
        const providerId = provider.id;
        providerRunners[providerId] = createAgentProviderTaskRunner(provider);
        providerModels[providerId] = {
            model: providerId,
            capabilities: ["read", "review", "qa", "advisory", "write"],
        };
    }
    const allowDeepSeek = providerPolicy === "auto" || providerPolicy === "deepseek";
    const deepseekStatus = allowDeepSeek ? await getDeepSeekProviderStatus() : undefined;
    const deepseekKey = allowDeepSeek && deepseekStatus?.enabled
        ? await resolveDeepSeekApiKey()
        : undefined;
    const deepseekCheck = allowDeepSeek && deepseekStatus?.enabled && deepseekKey?.apiKey
        ? await checkDeepSeekBalance({ apiKey: deepseekKey.apiKey })
        : undefined;
    if (deepseekCheck?.available) {
        const deepseekRunner = createDeepSeekReadOnlyTaskRunner({
            apiKey: deepseekKey?.apiKey,
            allowAdvisoryFileNodes: options.allowDeepSeekAdvisoryFileNodes ?? true,
            promptPrefix: options.deepseekPromptPrefix,
        });
        deepseekRunnerRef = deepseekRunner;
        const deepseekProvider = createDeepSeekProvider({ runner: deepseekRunner });
        providers.push(deepseekProvider);
        providerHealth.markDeepSeekAvailable();
    }
    else if (deepseekCheck) {
        providerHealth.markDeepSeekUnavailable(deepseekCheck.reason ?? "DeepSeek unavailable");
        if (deepseekCheck.reason?.includes("402")) {
            await forceDisableDeepSeek(deepseekCheck.reason, { disabledBy: "provider-402" });
            console.error(status.warn(`DeepSeek forced disabled: ${deepseekCheck.reason}`));
            console.error(style.gray("Configured authority fallback is active. Run /deepseek-enable after topping up."));
        }
    }
    else if (allowDeepSeek && deepseekStatus?.enabled === false) {
        providerHealth.markDeepSeekUnavailable(deepseekStatus.disabledReason ?? "DeepSeek disabled");
        console.error(status.warn(`DeepSeek disabled: ${deepseekStatus.disabledReason ?? "disabled by user"}`));
        console.error(style.gray("Configured authority fallback is active. Run /deepseek-enable to re-enable."));
    }
    const registry = await readProviderRegistry();
    for (const entry of registry) {
        if (!shouldUseOpenAICompatibleProvider(entry, providerPolicy))
            continue;
        const apiKey = entry.apiKeyEnv ? process.env[entry.apiKeyEnv] : undefined;
        if (apiKey && entry.baseUrl) {
            providerRunners[entry.id] = createOpenAICompatibleReadOnlyTaskRunner({
                provider: entry.id,
                baseUrl: entry.baseUrl,
                apiKey,
                apiKeyEnv: entry.apiKeyEnv,
                model: entry.defaultModel,
                promptPrefix: options.deepseekPromptPrefix,
                headers: openAICompatibleHeaders(entry, process.env),
                contextWindow: entry.contextWindow,
                reservedOutputTokens: entry.reservedOutputTokens,
            });
            providerModels[entry.id] = providerModelDefault(entry);
        }
        else if (providerPolicy === entry.id) {
            console.error(status.warn(`${providerDisplayName(entry.id)} unavailable: missing ${entry.apiKeyEnv ?? "API key env"} or base URL. Configured authority fallback is active.`));
        }
    }
    const codex = registry.find((entry) => entry.id === "codex");
    if (codex && codex.enabled && (providerPolicy === "auto" || providerPolicy === "codex")) {
        // Don't override custom providers passed via options.providers
        const hasCustomCodex = (options.providers ?? []).some((p) => p.id === "codex");
        if (!hasCustomCodex) {
            const codexStatus = await providerDoctorStatus("codex");
            if (codexStatus.available) {
                providerRunners.codex = createCodexCliAdvisoryTaskRunner({
                    cwd: runnerCwd,
                    model: codex.defaultModel,
                    contextWindow: codex.contextWindow,
                    reservedOutputTokens: codex.reservedOutputTokens,
                });
                providerModels.codex = providerModelDefault(codex);
            }
            else if (providerPolicy === "codex") {
                console.error(status.warn("Codex unavailable or unauthenticated; configured authority fallback is active."));
            }
        }
    }
    const router = createProviderRouter({
        providers,
        defaultStrategy: "cost-aware",
    });
    // Create runtime instances for the RuntimeRouter
    const runtimes = [];
    runtimes.push(...(options.runtimes ?? []));
    const runtimeRouter = createRuntimeRouter({
        runtimes,
        fallbackChain: options.fallbackChain,
    });
    // Create the base task runner (existing provider-task-runner with Kimi/DeepSeek routing)
    const baseRunner = createProviderTaskRunner({
        kimiRunner,
        deepseekRunner: deepseekRunnerRef,
        authorityProvider: resolveProviderBackedAuthorityProvider(providerPolicy, providerRunners),
        authorityRunner: providerRunners[resolveProviderBackedAuthorityProvider(providerPolicy, providerRunners)],
        providerRunners,
        providerModels,
        providerPolicy,
        providerHealth,
        onDeepSeekDisabled: async (event) => {
            await forceDisableDeepSeek(event.reason, {
                disabledBy: event.reason.includes("402") ? "provider-402" : "provider-availability",
            });
            console.error(status.warn(`DeepSeek forced disabled: ${event.reason}`));
            console.error(style.gray(`Node ${event.nodeId} used the configured authority fallback. Run /deepseek-enable after fixing balance/auth.`));
        },
    });
    // Wrap provider routing with ContextBroker budget metadata. Provider routing
    // remains authoritative: RuntimeRouter failures are result values, not always
    // thrown exceptions, so running it first can bypass provider fallback metadata.
    const contextBroker = createContextBroker({ projectRoot: kimiOptions.cwd ?? options.cwd ?? getProjectRoot() });
    const wrappedRunner = {
        onThinking: baseRunner.onThinking,
        fork: baseRunner.fork,
        async run(node, env, signal, context) {
            let budgetReport;
            try {
                budgetReport = (await contextBroker.buildCapsule(node)).report;
            }
            catch (err) {
                const errorMsg = err instanceof Error ? err.message : String(err);
                process.stderr.write(`[omk] ContextBroker failed for node ${node.id}: ${errorMsg}\n`);
            }
            const taskResult = await baseRunner.run(node, env, signal, context);
            taskResult.metadata = {
                ...(taskResult.metadata ?? {}),
                ...(budgetReport ? { _budgetReport: budgetReport } : {}),
            };
            return taskResult;
        },
    };
    // Attach metadata for executor introspection
    wrappedRunner._runtimeRouter = runtimeRouter;
    wrappedRunner._providers = providers;
    wrappedRunner._router = router;
    wrappedRunner._contextBroker = contextBroker;
    return wrappedRunner;
}
function shouldUseOpenAICompatibleProvider(entry, providerPolicy) {
    if (entry.id === "deepseek")
        return false;
    if (entry.kind !== "openai-compatible")
        return false;
    if (!entry.enabled)
        return false;
    return providerPolicy === "auto" || providerPolicy === entry.id;
}
function providerModelDefault(entry) {
    return {
        model: entry.defaultModel,
        capabilities: entry.capabilities,
    };
}
function openAICompatibleHeaders(entry, env) {
    const headers = { ...(entry.headers ?? {}) };
    if (entry.id === "openrouter") {
        setHeaderFromEnv(headers, "HTTP-Referer", env.OPENROUTER_HTTP_REFERER ?? env.OPENROUTER_REFERER);
        setHeaderFromEnv(headers, "X-OpenRouter-Title", env.OPENROUTER_X_TITLE ?? env.OPENROUTER_APP_TITLE);
    }
    return Object.keys(headers).length > 0 ? headers : undefined;
}
function setHeaderFromEnv(headers, name, value) {
    const trimmed = value?.trim();
    if (trimmed)
        headers[name] = trimmed;
}
function createAgentProviderTaskRunner(provider) {
    return {
        async run(node, env, signal, context) {
            return provider.run({
                node,
                env,
                signal: signal ?? new AbortController().signal,
                attempt: Number(env.OMK_PROVIDER_ATTEMPT ?? 1),
                runContext: context,
            });
        },
    };
}
function resolveProviderBackedAuthorityProvider(providerPolicy, providerRunners) {
    if (providerPolicy === "codex" && providerRunners.codex)
        return "codex";
    if (providerRunners.codex)
        return "codex";
    if (providerPolicy !== "auto" && providerPolicy !== "deepseek" && providerRunners[providerPolicy]) {
        return providerPolicy;
    }
    return DEFAULT_AUTHORITY_PROVIDER;
}
function createDisabledLegacyKimiRunner() {
    return {
        async run() {
            return {
                success: false,
                exitCode: 78,
                stdout: "",
                stderr: "Legacy provider runner is disabled unless explicitly configured.",
                metadata: { provider: "legacy", disabled: true },
            };
        },
    };
}
function providerDisplayName(provider) {
    if (provider === "deepseek")
        return "DeepSeek";
    if (provider === "qwen")
        return "Qwen";
    if (provider === "codex")
        return "Codex";
    if (provider === "openrouter")
        return "OpenRouter";
    if (provider === "kimi")
        return "Kimi";
    return provider;
}
