import { createHash, randomUUID } from 'node:crypto';
const DEFAULT_SYSTEM_TEXT = "You are Claude Code, Anthropic's official CLI for Claude.";
const DEFAULT_USER_ID_ENV = 'ROUTECODEX_CLAUDE_CODE_USER_ID';
const DEFAULT_ACCOUNT_SEED_ENV = 'ROUTECODEX_CLAUDE_CODE_ACCOUNT_SEED';
const CLAUDE_CODE_USER_ID_REGEX = /^user_[0-9a-f]{64}_account__session_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const HEX_32_REGEX = /^[0-9a-f]{32}$/i;
const SESSION_TOKEN_REGEX = /session[_:\-\s]?([0-9a-f]{8,}(?:-[0-9a-f]{4,}){0,5})/i;
function isRecord(value) {
    return Boolean(value && typeof value === 'object' && !Array.isArray(value));
}
function readTrimmedString(value) {
    if (typeof value !== 'string') {
        return undefined;
    }
    const trimmed = value.trim();
    return trimmed.length ? trimmed : undefined;
}
function shouldInjectThinking(value) {
    if (value === undefined || value === null) {
        return true;
    }
    if (typeof value === 'boolean') {
        return value !== false;
    }
    if (isRecord(value)) {
        const type = readTrimmedString(value.type);
        return !type;
    }
    if (typeof value === 'string') {
        return value.trim().length === 0;
    }
    return true;
}
function resolveEffort(model) {
    const modelId = readTrimmedString(model)?.toLowerCase() || '';
    return modelId.startsWith('glm-5') ? 'high' : 'medium';
}
function ensureAdaptiveThinking(root) {
    if (shouldInjectThinking(root.thinking)) {
        root.thinking = { type: 'adaptive' };
    }
}
function ensureOutputEffort(root) {
    const effort = resolveEffort(root.model);
    if (isRecord(root.output_config)) {
        const outputConfig = root.output_config;
        if (!readTrimmedString(outputConfig.effort)) {
            outputConfig.effort = effort;
        }
        return;
    }
    root.output_config = { effort };
}
function isClaudeCodeUserId(value) {
    const trimmed = readTrimmedString(value);
    if (!trimmed)
        return false;
    return CLAUDE_CODE_USER_ID_REGEX.test(trimmed);
}
function sha256Hex(value) {
    return createHash('sha256').update(value).digest('hex');
}
function formatUuidFromHex32(hex32) {
    const hex = hex32.toLowerCase();
    return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
}
function uuidFromSeed(seed) {
    const hex = sha256Hex(seed);
    // Make it UUIDv4-ish (version nibble "4", variant "8") so regex validators accept it.
    const chars = hex.split('');
    if (chars.length >= 32) {
        chars[12] = '4';
        chars[16] = '8';
    }
    return formatUuidFromHex32(chars.slice(0, 32).join(''));
}
function normalizeSessionUuid(candidate) {
    const raw = readTrimmedString(candidate);
    if (!raw)
        return undefined;
    const match = raw.match(SESSION_TOKEN_REGEX);
    const trimmed = (match && match[1] ? match[1] : raw).trim();
    if (UUID_REGEX.test(trimmed)) {
        return trimmed.toLowerCase();
    }
    const compact = trimmed.replace(/-/g, '');
    if (HEX_32_REGEX.test(compact)) {
        return formatUuidFromHex32(compact);
    }
    return uuidFromSeed(trimmed);
}
function resolveClaudeCodeUserId(metadata, adapterContext) {
    const existing = readTrimmedString(metadata.user_id);
    if (isClaudeCodeUserId(existing)) {
        return existing;
    }
    const envUserId = readTrimmedString(process.env[DEFAULT_USER_ID_ENV]);
    if (isClaudeCodeUserId(envUserId)) {
        return envUserId;
    }
    const clientHeaders = isRecord(metadata.clientHeaders) ? metadata.clientHeaders : undefined;
    const ctx = isRecord(adapterContext) ? adapterContext : undefined;
    const sessionUuid = normalizeSessionUuid(existing) ||
        normalizeSessionUuid(envUserId) ||
        normalizeSessionUuid(readTrimmedString(clientHeaders?.session_id)) ||
        normalizeSessionUuid(readTrimmedString(clientHeaders?.['anthropic-session-id'])) ||
        normalizeSessionUuid(readTrimmedString(clientHeaders?.['x-session-id'])) ||
        normalizeSessionUuid(readTrimmedString(clientHeaders?.conversation_id)) ||
        normalizeSessionUuid(readTrimmedString(clientHeaders?.['anthropic-conversation-id'])) ||
        normalizeSessionUuid(readTrimmedString(clientHeaders?.['openai-conversation-id'])) ||
        normalizeSessionUuid(readTrimmedString(metadata.sessionId)) ||
        normalizeSessionUuid(readTrimmedString(metadata.conversationId)) ||
        normalizeSessionUuid(readTrimmedString(ctx?.sessionId)) ||
        normalizeSessionUuid(readTrimmedString(ctx?.conversationId));
    const accountSeed = readTrimmedString(process.env[DEFAULT_ACCOUNT_SEED_ENV]) ||
        readTrimmedString(process.env.USER) ||
        'routecodex';
    try {
        const accountHash = sha256Hex(accountSeed);
        const session = sessionUuid ?? randomUUID();
        return `user_${accountHash}_account__session_${session}`;
    }
    catch {
        // As a last resort, keep compatibility best-effort.
        const session = sessionUuid ?? randomUUID();
        return `user_${'0'.repeat(64)}_account__session_${session}`;
    }
    // unreachable
}
function normalizeSystemBlocks(system) {
    const blocks = [];
    const pushText = (text, extra) => {
        const trimmed = text.trim();
        if (!trimmed) {
            return;
        }
        blocks.push({
            ...(extra ?? {}),
            type: 'text',
            text: trimmed
        });
    };
    if (typeof system === 'string') {
        pushText(system);
        return blocks;
    }
    if (Array.isArray(system)) {
        for (const entry of system) {
            if (typeof entry === 'string') {
                pushText(entry);
                continue;
            }
            if (!isRecord(entry)) {
                continue;
            }
            const text = typeof entry.text === 'string' ? entry.text : '';
            if (text) {
                const extra = { ...entry };
                delete extra.type;
                delete extra.text;
                pushText(text, extra);
            }
        }
        return blocks;
    }
    if (isRecord(system)) {
        const text = typeof system.text === 'string' ? system.text : '';
        if (text) {
            const extra = { ...system };
            delete extra.type;
            delete extra.text;
            pushText(text, extra);
        }
    }
    return blocks;
}
function dedupeSystemBlocksByText(blocks) {
    const seen = new Set();
    const result = [];
    for (const block of blocks) {
        const text = typeof block.text === 'string' ? block.text.trim() : '';
        if (!text)
            continue;
        if (seen.has(text))
            continue;
        seen.add(text);
        result.push({ ...block, type: 'text', text });
    }
    return result;
}
function prependUserContent(messages, blocks) {
    if (!Array.isArray(messages) || blocks.length === 0) {
        return;
    }
    const first = messages[0];
    if (isRecord(first) && typeof first.role === 'string' && first.role.toLowerCase() === 'user') {
        const existing = first.content;
        if (typeof existing === 'string') {
            const injected = blocks.map((b) => b.text).filter(Boolean).join('\n\n');
            first.content = existing.trim() ? `${injected}\n\n${existing}` : injected;
            return;
        }
        if (Array.isArray(existing)) {
            first.content = [...blocks, ...existing];
            return;
        }
        first.content = [...blocks];
        return;
    }
    messages.unshift({ role: 'user', content: [...blocks] });
}
/**
 * Canonicalizes the Anthropic `system` prompt into a single text block.
 * Optionally preserves previous system blocks by prepending them to the first user message,
 * so request semantics are preserved after normalization.
 */
export function applyAnthropicClaudeCodeSystemPromptCompat(payload, config, adapterContext) {
    if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
        return payload;
    }
    const root = payload;
    const systemText = (typeof config?.systemText === 'string' && config.systemText.trim())
        ? config.systemText.trim()
        : DEFAULT_SYSTEM_TEXT;
    // Some Claude-Code-gated Anthropic proxies require the top-level `metadata` field to exist.
    // We don't inspect/repair semantics: just ensure the field is present as an object.
    if (!isRecord(root.metadata)) {
        root.metadata = {};
    }
    // Some proxies also require `metadata.user_id` to be present.
    // Fill it with Claude Code's canonical `user_<sha256>_account__session_<uuid>` shape.
    try {
        const userId = resolveClaudeCodeUserId(root.metadata, adapterContext);
        const current = readTrimmedString(root.metadata.user_id);
        if (userId && !isClaudeCodeUserId(current)) {
            root.metadata.user_id = userId;
        }
    }
    catch {
        // best-effort: never block compat due to metadata shaping failures
    }
    const preserveExisting = config?.preserveExistingSystemAsUserMessage !== false;
    const existingBlocks = dedupeSystemBlocksByText(normalizeSystemBlocks(root.system))
        .filter((b) => b.text !== systemText);
    // Normalize: force system into a single text block.
    root.system = [{ type: 'text', text: systemText }];
    ensureAdaptiveThinking(root);
    ensureOutputEffort(root);
    if (preserveExisting && existingBlocks.length) {
        const messages = Array.isArray(root.messages) ? root.messages : [];
        if (messages.length || root.messages !== undefined) {
            prependUserContent(messages, existingBlocks);
            root.messages = messages;
        }
    }
    return root;
}
