import path from "node:path";
import { promises as fs } from "node:fs";
function normalizeTemplateModels(template) {
    const dedup = new Set();
    const appendModel = (value) => {
        const normalized = String(value || '').trim();
        if (normalized) {
            dedup.add(normalized);
        }
    };
    if (Array.isArray(template.models)) {
        template.models.forEach(appendModel);
    }
    appendModel(template.model);
    return [...dedup];
}
async function readJsonFile(filePath, fallback) {
    try {
        const raw = await fs.readFile(filePath, "utf-8");
        const parsed = JSON.parse(raw);
        if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
            return parsed;
        }
        return { ...fallback };
    }
    catch {
        return { ...fallback };
    }
}
async function writeJsonFile(filePath, content) {
    const parent = path.dirname(filePath);
    await fs.mkdir(parent, { recursive: true });
    await fs.writeFile(filePath, JSON.stringify(content, null, 2), "utf-8");
}
function deepMergeConfig(target, source) {
    if (!source || typeof source !== "object" || Array.isArray(source)) {
        return target;
    }
    const result = { ...target };
    for (const [key, value] of Object.entries(source)) {
        if (value === undefined) {
            continue;
        }
        if (value !== null &&
            typeof value === "object" &&
            !Array.isArray(value) &&
            result[key] !== null &&
            typeof result[key] === "object" &&
            !Array.isArray(result[key])) {
            result[key] = deepMergeConfig(result[key], value);
        }
        else {
            result[key] = value;
        }
    }
    return result;
}
function normalizeCodexBaseUrl(baseUrl) {
    return normalizeOpenAiCompatibleBaseUrl(baseUrl);
}
function escapeTomlString(value) {
    return String(value || "").replace(/\\/g, "\\\\").replace(/"/g, '\\"');
}
function parseTomlStringValue(value) {
    const trimmed = String(value || "").trim();
    if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
        return trimmed.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
    }
    return trimmed;
}
function parseTomlBooleanValue(value) {
    const normalized = String(value || "").trim().toLowerCase();
    if (normalized === "true")
        return true;
    if (normalized === "false")
        return false;
    return undefined;
}
function parseCodexConfigToml(raw) {
    const config = { model_providers: {} };
    let currentProvider = null;
    for (const line of raw.split(/\r?\n/)) {
        const trimmed = line.trim();
        if (!trimmed || trimmed.startsWith("#")) {
            continue;
        }
        const providerMatch = trimmed.match(/^\[model_providers\.([^\]]+)\]$/);
        if (providerMatch) {
            currentProvider = providerMatch[1];
            config.model_providers = config.model_providers || {};
            config.model_providers[currentProvider] = config.model_providers[currentProvider] || {};
            continue;
        }
        if (trimmed.startsWith("[")) {
            currentProvider = null;
            continue;
        }
        const keyValueMatch = trimmed.match(/^([A-Za-z0-9_-]+)\s*=\s*(.+)$/);
        if (!keyValueMatch) {
            continue;
        }
        const [, key, rawValue] = keyValueMatch;
        if (currentProvider) {
            const provider = config.model_providers?.[currentProvider] || {};
            if (key === "requires_openai_auth") {
                const parsed = parseTomlBooleanValue(rawValue);
                if (parsed !== undefined)
                    provider.requires_openai_auth = parsed;
            }
            else if (["name", "base_url", "wire_api", "env_key", "env_key_instructions"].includes(key)) {
                provider[key] = parseTomlStringValue(rawValue);
            }
            config.model_providers = { ...(config.model_providers || {}), [currentProvider]: provider };
            continue;
        }
        if (["model", "model_provider", "openai_base_url"].includes(key)) {
            config[key] = parseTomlStringValue(rawValue);
        }
    }
    return config;
}
function serializeCodexConfig(config) {
    const lines = [];
    if (config.model) {
        lines.push(`model = "${escapeTomlString(config.model)}"`);
    }
    if (config.model_provider) {
        lines.push(`model_provider = "${escapeTomlString(config.model_provider)}"`);
    }
    if (config.openai_base_url) {
        lines.push(`openai_base_url = "${escapeTomlString(config.openai_base_url)}"`);
    }
    const providers = config.model_providers || {};
    for (const [providerId, provider] of Object.entries(providers)) {
        lines.push("");
        lines.push(`[model_providers.${providerId}]`);
        if (provider.name)
            lines.push(`name = "${escapeTomlString(provider.name)}"`);
        if (provider.base_url)
            lines.push(`base_url = "${escapeTomlString(provider.base_url)}"`);
        if (provider.wire_api)
            lines.push(`wire_api = "${escapeTomlString(provider.wire_api)}"`);
        if (provider.env_key)
            lines.push(`env_key = "${escapeTomlString(provider.env_key)}"`);
        if (provider.env_key_instructions) {
            lines.push(`env_key_instructions = "${escapeTomlString(provider.env_key_instructions)}"`);
        }
        if (provider.requires_openai_auth !== undefined) {
            lines.push(`requires_openai_auth = ${provider.requires_openai_auth ? "true" : "false"}`);
        }
    }
    return `${lines.join("\n")}\n`;
}
export function buildToolSpecificAiConfig(template) {
    let baseUrl = String(template.baseUrl || "").trim();
    const apiKey = String(template.apiKey || "").trim();
    const models = normalizeTemplateModels(template);
    const model = models[0] || String(template.model || "").trim();
    if (!baseUrl || !apiKey || !model) {
        return null;
    }
    if (baseUrl &&
        !baseUrl.startsWith("http://") &&
        !baseUrl.startsWith("https://")) {
        baseUrl = `https://${baseUrl}`;
    }
    baseUrl = baseUrl.replace(/\/+$/, "");
    const openCodeBaseUrl = normalizeOpenAiCompatibleBaseUrl(baseUrl);
    const codexBaseUrl = normalizeCodexBaseUrl(baseUrl);
    const opencodeModels = Object.fromEntries(models.map((modelName) => [modelName, {
            name: modelName,
            modalities: {
                input: ["text", "image"],
                output: ["text"],
            },
        }]));
    return {
        claudecode: {
            env: {
                ANTHROPIC_BASE_URL: baseUrl,
                ANTHROPIC_AUTH_TOKEN: apiKey,
                ANTHROPIC_MODEL: model,
            },
        },
        codex: {
            model,
            model_provider: "default",
            model_providers: {
                default: {
                    name: "Default",
                    base_url: codexBaseUrl,
                    wire_api: "responses",
                    env_key: "OPENAI_API_KEY",
                    env_key_instructions: "Set OPENAI_API_KEY in your environment",
                },
            },
        },
        opencode: {
            provider: {
                default: {
                    npm: "@ai-sdk/openai-compatible",
                    options: {
                        baseURL: openCodeBaseUrl,
                        apiKey,
                    },
                    models: opencodeModels,
                },
            },
        },
    };
}
/**
 * 规范化 OpenAI-compatible 的 OpenCode baseURL，确保缺省地址自动补齐 /v1。
 */
function normalizeOpenAiCompatibleBaseUrl(baseUrl) {
    const normalized = String(baseUrl || "").trim().replace(/\/+$/, "");
    if (!normalized) {
        return normalized;
    }
    return normalized.endsWith("/v1") ? normalized : `${normalized}/v1`;
}
export class AiConfigService {
    constructor(paths) {
        this.paths = paths;
    }
    async readClaudeCodeConfig() {
        return readJsonFile(this.paths.claudecodeConfigFile, {});
    }
    async readOpenCodeConfig() {
        return readJsonFile(this.paths.opencodeConfigFile, {});
    }
    async readCodexConfig() {
        try {
            const raw = await fs.readFile(this.paths.codexConfigFile, "utf-8");
            return parseCodexConfigToml(raw);
        }
        catch {
            return {};
        }
    }
    async writeClaudeCodeConfig(config) {
        await writeJsonFile(this.paths.claudecodeConfigFile, config);
    }
    async writeOpenCodeConfig(config) {
        await writeJsonFile(this.paths.opencodeConfigFile, config);
    }
    async writeCodexConfig(config) {
        const parent = path.dirname(this.paths.codexConfigFile);
        await fs.mkdir(parent, { recursive: true });
        await fs.writeFile(this.paths.codexConfigFile, serializeCodexConfig(config), "utf-8");
    }
    async applyClaudeCodeConfig(patch) {
        const current = await this.readClaudeCodeConfig();
        const merged = deepMergeConfig(current, patch);
        await this.writeClaudeCodeConfig(merged);
        return merged;
    }
    async applyOpenCodeConfig(patch) {
        const current = await this.readOpenCodeConfig();
        const merged = deepMergeConfig(current, patch);
        await this.writeOpenCodeConfig(merged);
        return merged;
    }
    async applyCodexConfig(patch) {
        const current = await this.readCodexConfig();
        const merged = deepMergeConfig(current, patch);
        await this.writeCodexConfig(merged);
        return merged;
    }
    async applyAiConfigTemplate(template) {
        const configs = buildToolSpecificAiConfig(template);
        if (!configs) {
            return null;
        }
        const claudecode = await this.applyClaudeCodeConfig(configs.claudecode);
        const codex = await this.applyCodexConfig(configs.codex);
        const opencode = await this.applyOpenCodeConfig(configs.opencode);
        return { claudecode, codex, opencode };
    }
}
