import { ANTIGRAVITY_GLOBAL_ALIAS_KEY, cacheAntigravityRequestSessionId, extractAntigravityGeminiSessionId, lookupAntigravitySessionSignatureEntry, shouldTreatAsMissingThoughtSignature } from '../antigravity-session-signature.js';
const REQUEST_FIELDS = [
    'contents',
    'systemInstruction',
    'tools',
    'toolConfig',
    'generationConfig',
    'safetySettings'
];
const ROOT_ONLY_FIELDS = [
    'model',
    'project',
    'requestId',
    'requestType',
    'userAgent',
    'action'
];
const TOOL_NAME_ALIASES = {
    'mcp__context7__query-docs': 'mcp__context7__query_docs',
    'mcp__context7__resolve-library-id': 'mcp__context7__resolve_library_id',
    'mcp__mcp-server-time__convert_time': 'mcp__mcp_server_time__convert_time',
    'mcp__mcp-server-time__get_current_time': 'mcp__mcp_server_time__get_current_time'
};
const TOOL_PARAM_WHITELIST = {
    exec_command: ['command', 'workdir'],
    write_stdin: ['session_id', 'chars', 'max_output_tokens', 'yield_time_ms', 'text'],
    apply_patch: ['patch', 'input', 'instructions', 'text']
};
function isRecord(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function extractAntigravityAliasKeyFromRequestId(value) {
    if (typeof value !== 'string') {
        return undefined;
    }
    const raw = value.trim();
    if (!raw) {
        return undefined;
    }
    const match = raw.match(/antigravity\.([^.\\s]+)/i);
    const alias = match && match[1] ? match[1].trim() : '';
    if (!alias) {
        return undefined;
    }
    return `antigravity.${alias}`.toLowerCase();
}
function resolveAntigravityAliasKey(adapterContext, requestIdHint) {
    const ctxAny = adapterContext;
    const candidates = [
        ctxAny && typeof ctxAny.runtimeKey === 'string' ? String(ctxAny.runtimeKey) : '',
        ctxAny && typeof ctxAny.providerKey === 'string' ? String(ctxAny.providerKey) : '',
        ctxAny && typeof ctxAny.providerId === 'string' ? String(ctxAny.providerId) : '',
        ctxAny && typeof ctxAny.requestId === 'string' ? String(ctxAny.requestId) : '',
        typeof requestIdHint === 'string' ? String(requestIdHint) : ''
    ].filter((v) => typeof v === 'string' && v.trim().length);
    for (const value of candidates) {
        const trimmed = value.trim();
        const lower = trimmed.toLowerCase();
        if (lower === 'antigravity') {
            continue;
        }
        if (lower.startsWith('antigravity.')) {
            const parts = trimmed.split('.');
            if (parts.length >= 2 && parts[0] && parts[1]) {
                return `${parts[0].trim()}.${parts[1].trim()}`.toLowerCase();
            }
            const extracted = extractAntigravityAliasKeyFromRequestId(trimmed);
            if (extracted)
                return extracted;
        }
        const extracted = extractAntigravityAliasKeyFromRequestId(trimmed);
        if (extracted)
            return extracted;
    }
    return 'antigravity.unknown';
}
function normalizeSchemaTypes(value) {
    if (!value || typeof value !== 'object')
        return value;
    if (Array.isArray(value))
        return value.map((entry) => normalizeSchemaTypes(entry));
    const record = value;
    const out = {};
    for (const [key, val] of Object.entries(record)) {
        if (key === 'type' && typeof val === 'string') {
            out[key] = val.toUpperCase();
            continue;
        }
        out[key] = normalizeSchemaTypes(val);
    }
    return out;
}
function pickDescription(value, fallback) {
    return typeof value === 'string' && value.trim().length ? value : fallback;
}
function normalizeToolDeclaration(decl) {
    const rawName = typeof decl.name === 'string' ? decl.name.trim() : '';
    const name = (TOOL_NAME_ALIASES[rawName] ?? rawName).trim().toLowerCase();
    const params = isRecord(decl.parameters) ? { ...decl.parameters } : {};
    const props = isRecord(params.properties) ? { ...params.properties } : {};
    if (name === 'view_image') {
        return {};
    }
    if (name === 'exec_command') {
        const command = isRecord(props.command) ? props.command : undefined;
        const cmd = isRecord(props.cmd) ? props.cmd : undefined;
        const nextProps = {
            command: {
                type: 'STRING',
                description: pickDescription(command?.description ?? cmd?.description, 'Shell command to execute.')
            },
            workdir: {
                type: 'STRING',
                description: 'Working directory.'
            }
        };
        return {
            ...decl,
            name,
            description: 'Run a shell command. Provide `cmd` (string) (alias: `command`) and optional `workdir` (string).',
            parameters: {
                type: 'OBJECT',
                properties: nextProps
            }
        };
    }
    if (name === 'write_stdin') {
        const nextProps = {};
        for (const key of TOOL_PARAM_WHITELIST.write_stdin) {
            const entry = props[key];
            const fallbackType = key === 'session_id' || key.endsWith('_tokens') || key.endsWith('_ms') ? 'NUMBER' : 'STRING';
            nextProps[key] = {
                type: (typeof entry?.type === 'string' ? entry.type : fallbackType).toUpperCase(),
                ...(entry?.description ? { description: entry.description } : {})
            };
        }
        return {
            ...decl,
            name,
            description: 'Write to an existing exec session. Provide `session_id` (number) and optional `chars` (string).',
            parameters: {
                type: 'OBJECT',
                properties: nextProps
            }
        };
    }
    if (name === 'apply_patch') {
        const nextProps = {};
        for (const key of TOOL_PARAM_WHITELIST.apply_patch) {
            const entry = props[key];
            nextProps[key] = {
                type: 'STRING',
                ...(entry?.description ? { description: entry.description } : {})
            };
        }
        return {
            ...decl,
            name,
            description: 'Edit files by providing patch text in `patch` (string). Supports "*** Begin Patch" / "*** End Patch" or GNU unified diff. `input`/`instructions`/`text` are accepted as aliases.',
            parameters: {
                type: 'OBJECT',
                properties: nextProps
            }
        };
    }
    if (name === 'list_mcp_resources') {
        return {
            ...decl,
            name,
            description: 'Lists resources provided by MCP servers. Resources allow servers to share data that provides context to language models, such as files, database schemas, or application-specific information. Prefer resources over web search when possible.',
            parameters: {
                type: 'OBJECT',
                properties: {
                    server: { type: 'STRING', minLength: 1 },
                    filter: { type: 'STRING' },
                    root: { type: 'STRING' }
                }
            }
        };
    }
    if (name === 'list_mcp_resource_templates') {
        return {
            ...decl,
            name,
            description: 'Lists resource templates provided by MCP servers. Parameterized resource templates allow servers to share data that takes parameters and provides context to language models, such as files, database schemas, or application-specific information. Prefer resource templates over web search when possible.',
            parameters: {
                type: 'OBJECT',
                properties: {
                    cursor: { type: 'STRING' },
                    server: { type: 'STRING', minLength: 1 }
                }
            }
        };
    }
    if (name === 'read_mcp_resource') {
        return {
            ...decl,
            name,
            description: 'Read a specific resource from an MCP server given the server name and resource URI.',
            parameters: {
                type: 'OBJECT',
                properties: {
                    server: {
                        type: 'STRING',
                        description: "MCP server name exactly as configured. Must match the 'server' field returned by list_mcp_resources."
                    },
                    uri: {
                        type: 'STRING',
                        description: 'Resource URI to read. Must be one of the URIs returned by list_mcp_resources.'
                    }
                },
                required: ['server', 'uri']
            }
        };
    }
    if (name === 'update_plan') {
        return {
            ...decl,
            name,
            description: 'Updates the task plan.\nProvide an optional explanation and a list of plan items, each with a step and status.\nAt most one step can be in_progress at a time.\n',
            parameters: {
                type: 'OBJECT',
                properties: {
                    explanation: { type: 'STRING' },
                    plan: {
                        type: 'ARRAY',
                        items: {
                            type: 'OBJECT',
                            properties: {
                                status: { type: 'STRING', description: 'One of: pending, in_progress, completed' },
                                step: { type: 'STRING' }
                            },
                            required: ['step', 'status']
                        },
                        description: 'The list of steps'
                    }
                },
                required: ['plan']
            }
        };
    }
    if (name === 'request_user_input') {
        return {
            ...decl,
            name,
            description: 'Request user input for one to three short questions and wait for the response.',
            parameters: {
                type: 'OBJECT',
                properties: {
                    questions: {
                        type: 'ARRAY',
                        items: {
                            type: 'OBJECT',
                            properties: {
                                header: { type: 'STRING', description: 'Short header label shown in the UI (12 or fewer chars).' },
                                id: { type: 'STRING', description: 'Stable identifier for mapping answers (snake_case).' },
                                options: {
                                    type: 'ARRAY',
                                    items: {
                                        type: 'OBJECT',
                                        properties: {
                                            description: {
                                                type: 'STRING',
                                                description: 'One short sentence explaining impact/tradeoff if selected.'
                                            },
                                            label: { type: 'STRING', description: 'User-facing label (1-5 words).' }
                                        },
                                        required: ['label', 'description']
                                    },
                                    description: 'Optional 2-3 mutually exclusive choices. Put the recommended option first and suffix its label with "(Recommended)". Only include "Other" option if we want to include a free form option. If the question is free form in nature, please do not have any option.'
                                },
                                question: { type: 'STRING', description: 'Single-sentence prompt shown to the user.' }
                            },
                            required: ['id', 'header', 'question']
                        },
                        description: 'Questions to show the user. Prefer 1 and do not exceed 3'
                    }
                },
                required: ['questions']
            }
        };
    }
    if (name === 'mcp__context7__query_docs') {
        return {
            ...decl,
            name,
            description: "Retrieves and queries up-to-date documentation and code examples from Context7 for any programming library or framework.\n\nYou must call 'resolve-library-id' first to obtain the exact Context7-compatible library ID required to use this tool, UNLESS the user explicitly provides a library ID in the format '/org/project' or '/org/project/version' in their query.\n\nIMPORTANT: Do not call this tool more than 3 times per question. If you cannot find what you need after 3 calls, use the best information you have.",
            parameters: {
                type: 'OBJECT',
                properties: {
                    libraryId: {
                        type: 'STRING',
                        description: "Exact Context7-compatible library ID (e.g., '/mongodb/docs', '/vercel/next.js', '/supabase/supabase', '/vercel/next.js/v14.3.0-canary.87') retrieved from 'resolve-library-id' or directly from user query in the format '/org/project' or '/org/project/version'."
                    },
                    query: {
                        type: 'STRING',
                        description: "The question or task you need help with. Be specific and include relevant details. Good: 'How to set up authentication with JWT in Express.js' or 'React useEffect cleanup function examples'. Bad: 'auth' or 'hooks'. IMPORTANT: Do not include any sensitive or confidential information such as API keys, passwords, credentials, or personal data in your query."
                    }
                },
                required: ['libraryId', 'query']
            }
        };
    }
    if (name === 'mcp__context7__resolve_library_id') {
        return {
            ...decl,
            name,
            description: "Resolves a package/product name to a Context7-compatible library ID and returns matching libraries.\n\nYou MUST call this function before 'query-docs' to obtain a valid Context7-compatible library ID UNLESS the user explicitly provides a library ID in the format '/org/project' or '/org/project/version' in their query.\n\nSelection Process:\n1. Analyze the query to understand what library/package the user is looking for\n2. Return the most relevant match based on:\n- Name similarity to the query (exact matches prioritized)\n- Description relevance to the query's intent\n- Documentation coverage (prioritize libraries with higher Code Snippet counts)\n- Source reputation (consider libraries with High or Medium reputation more authoritative)\n- Benchmark Score: Quality indicator (100 is the highest score)\n\nResponse Format:\n- Return the selected library ID in a clearly marked section\n- Provide a brief explanation for why this library was chosen\n- If multiple good matches exist, acknowledge this but proceed with the most relevant one\n- If no good matches exist, clearly state this and suggest query refinements\n\nFor ambiguous queries, request clarification before proceeding with a best-guess match.\n\nIMPORTANT: Do not call this tool more than 3 times per question. If you cannot find what you need after 3 calls, use the best result you have.",
            parameters: {
                type: 'OBJECT',
                properties: {
                    libraryName: {
                        type: 'STRING',
                        description: 'Library name to search for and retrieve a Context7-compatible library ID.'
                    },
                    query: {
                        type: 'STRING',
                        description: "The user's original question or task. This is used to rank library results by relevance to what the user is trying to accomplish. IMPORTANT: Do not include any sensitive or confidential information such as API keys, passwords, credentials, or personal data in your query."
                    }
                },
                required: ['query', 'libraryName']
            }
        };
    }
    if (name === 'mcp__mcp_server_time__convert_time') {
        return {
            ...decl,
            name,
            description: 'Convert time between timezones',
            parameters: {
                type: 'OBJECT',
                properties: {
                    source_timezone: {
                        type: 'STRING',
                        description: "Source IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use 'Asia/Shanghai' as local timezone if no source timezone provided by the user."
                    },
                    target_timezone: {
                        type: 'STRING',
                        description: "Target IANA timezone name (e.g., 'Asia/Tokyo', 'America/San_Francisco'). Use 'Asia/Shanghai' as local timezone if no target timezone provided by the user."
                    },
                    time: { type: 'STRING', description: 'Time to convert in 24-hour format (HH:MM)' }
                },
                required: ['source_timezone', 'time', 'target_timezone']
            }
        };
    }
    if (name === 'mcp__mcp_server_time__get_current_time') {
        return {
            ...decl,
            name,
            description: 'Get current time in a specific timezones',
            parameters: {
                type: 'OBJECT',
                properties: {
                    timezone: {
                        type: 'STRING',
                        description: "IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use 'Asia/Shanghai' as local timezone if no timezone provided by the user."
                    }
                },
                required: ['timezone']
            }
        };
    }
    return {
        ...decl,
        name,
        parameters: normalizeSchemaTypes(params)
    };
}
function normalizeToolDeclarations(node) {
    const tools = node.tools;
    if (!Array.isArray(tools))
        return;
    const nextTools = tools.map((tool) => {
        if (!tool || typeof tool !== 'object')
            return tool;
        const record = tool;
        const decls = Array.isArray(record.functionDeclarations)
            ? record.functionDeclarations
            : undefined;
        if (!decls) {
            return {
                ...record,
                ...(record.parameters ? { parameters: normalizeSchemaTypes(record.parameters) } : {})
            };
        }
        const nextDecls = decls
            .map((decl) => normalizeToolDeclaration(decl))
            .filter((decl) => Object.keys(decl).length > 0);
        return { ...record, functionDeclarations: nextDecls };
    });
    node.tools = nextTools.filter((tool) => {
        if (!tool || typeof tool !== 'object')
            return true;
        const record = tool;
        const decls = Array.isArray(record.functionDeclarations)
            ? record.functionDeclarations
            : undefined;
        return !decls || decls.length > 0;
    });
}
function normalizeFunctionCallArgs(node) {
    const contents = node.contents;
    if (!Array.isArray(contents))
        return;
    for (const item of contents) {
        if (!item || typeof item !== 'object')
            continue;
        const record = item;
        const parts = Array.isArray(record.parts) ? record.parts : [];
        for (const part of parts) {
            if (!part || typeof part !== 'object')
                continue;
            const fnCall = part.functionCall;
            if (!isRecord(fnCall))
                continue;
            const rawName = typeof fnCall.name === 'string' ? fnCall.name.trim() : '';
            const normalizedName = (TOOL_NAME_ALIASES[rawName] ?? rawName).trim();
            fnCall.name = normalizedName;
            const name = normalizedName.toLowerCase();
            const args = isRecord(fnCall.args) ? { ...fnCall.args } : undefined;
            if (!args)
                continue;
            if (name === 'exec_command') {
                if (!Object.prototype.hasOwnProperty.call(args, 'command') && Object.prototype.hasOwnProperty.call(args, 'cmd')) {
                    args.command = args.cmd;
                }
                if (Object.prototype.hasOwnProperty.call(args, 'cmd')) {
                    delete args.cmd;
                }
            }
            if (name === 'write_stdin') {
                if (!Object.prototype.hasOwnProperty.call(args, 'chars') && Object.prototype.hasOwnProperty.call(args, 'text')) {
                    args.chars = args.text;
                }
            }
            fnCall.args = args;
            part.functionCall = fnCall;
        }
    }
}
function hasAnyRequestField(node) {
    return REQUEST_FIELDS.some((key) => Object.prototype.hasOwnProperty.call(node, key));
}
function normalizeRequestNode(node) {
    const base = { ...node };
    const nested = base.request;
    if (!isRecord(nested)) {
        return base;
    }
    delete base.request;
    const merged = { ...nested };
    for (const [key, value] of Object.entries(base)) {
        if (merged[key] === undefined) {
            merged[key] = value;
        }
    }
    return merged;
}
function isWebSearchToolName(value) {
    if (typeof value !== 'string')
        return false;
    const normalized = value.trim().toLowerCase();
    return normalized === 'web_search' || normalized.startsWith('web_search_');
}
function stripWebSearchTools(requestNode) {
    const tools = requestNode.tools;
    if (!Array.isArray(tools))
        return;
    const nextTools = [];
    for (const tool of tools) {
        if (!tool || typeof tool !== 'object') {
            nextTools.push(tool);
            continue;
        }
        const record = tool;
        const fnDecls = Array.isArray(record.functionDeclarations)
            ? record.functionDeclarations
            : undefined;
        if (fnDecls) {
            const filtered = fnDecls.filter((decl) => !isWebSearchToolName(decl?.name));
            if (filtered.length === 0) {
                continue;
            }
            nextTools.push({ ...record, functionDeclarations: filtered });
            continue;
        }
        const name = record.name;
        if (isWebSearchToolName(name)) {
            continue;
        }
        nextTools.push(record);
    }
    if (nextTools.length > 0) {
        requestNode.tools = nextTools;
    }
    else {
        delete requestNode.tools;
    }
}
function shouldEnableAntigravitySignature(adapterContext) {
    // Prefer adapterContext (canonical) but fall back to transport hints for cases where
    // host doesn't populate providerId (e.g. certain v1/v2 bridges).
    if (!adapterContext || typeof adapterContext !== 'object') {
        return false;
    }
    const protocol = typeof adapterContext.providerProtocol === 'string' ? adapterContext.providerProtocol.trim().toLowerCase() : '';
    if (protocol !== 'gemini-chat') {
        return false;
    }
    const ctxAny = adapterContext;
    const providerIdOrKeyRaw = typeof ctxAny.providerId === 'string'
        ? String(ctxAny.providerId)
        : typeof ctxAny.providerKey === 'string'
            ? String(ctxAny.providerKey)
            : typeof ctxAny.runtimeKey === 'string'
                ? String(ctxAny.runtimeKey)
                : '';
    // NOTE: AdapterContext may carry providerKey (e.g. "antigravity.<alias>.<model>") instead of bare providerId.
    // Treat the first segment as the effective provider id for compatibility checks.
    const providerIdOrKey = providerIdOrKeyRaw.trim().toLowerCase();
    const effectiveProviderId = providerIdOrKey.split('.')[0] ?? '';
    // Antigravity-Manager alignment: thoughtSignature compat applies to both Antigravity and Gemini CLI
    // (both route to Google Gemini internals that enforce thoughtSignature on tool loops).
    return effectiveProviderId === 'antigravity' || effectiveProviderId === 'gemini-cli';
}
function injectAntigravityThoughtSignature(requestNode, signature) {
    if (!signature || !signature.trim())
        return;
    const contents = requestNode.contents;
    if (!Array.isArray(contents)) {
        return;
    }
    for (const item of contents) {
        if (!isRecord(item))
            continue;
        const parts = Array.isArray(item.parts) ? item.parts : [];
        for (const part of parts) {
            if (!isRecord(part))
                continue;
            if (!isRecord(part.functionCall))
                continue;
            const existing = part.thoughtSignature;
            if (shouldTreatAsMissingThoughtSignature(existing)) {
                part.thoughtSignature = signature.trim();
            }
        }
    }
}
export function wrapGeminiCliRequest(payload, adapterContext) {
    const root = { ...payload };
    const existingRequest = isRecord(root.request) ? normalizeRequestNode(root.request) : undefined;
    const requestNode = existingRequest ?? {};
    for (const key of REQUEST_FIELDS) {
        if (requestNode[key] === undefined && root[key] !== undefined) {
            requestNode[key] = root[key];
        }
        delete root[key];
    }
    for (const key of ROOT_ONLY_FIELDS) {
        if (requestNode[key] !== undefined) {
            delete requestNode[key];
        }
    }
    stripWebSearchTools(requestNode);
    normalizeToolDeclarations(requestNode);
    normalizeFunctionCallArgs(requestNode);
    const enableSignature = shouldEnableAntigravitySignature(adapterContext) ||
        (typeof requestNode.userAgent === 'string' && requestNode.userAgent.trim().toLowerCase() === 'antigravity') ||
        (typeof requestNode.requestId === 'string' && requestNode.requestId.trim().toLowerCase().startsWith('agent-')) ||
        (typeof root.userAgent === 'string' && root.userAgent.trim().toLowerCase() === 'antigravity') ||
        (typeof root.requestId === 'string' && root.requestId.trim().toLowerCase().startsWith('agent-'));
    if (enableSignature) {
        const aliasKey = resolveAntigravityAliasKey(adapterContext, root.requestId);
        // Antigravity-Manager alignment: derive sessionId from request contents (first user text / JSON fallback),
        // not from external session/conversation identifiers injected by other clients/hosts.
        const sessionId = extractAntigravityGeminiSessionId(requestNode);
        const directLookup = lookupAntigravitySessionSignatureEntry(aliasKey, sessionId, { hydrate: true });
        const lookup = typeof directLookup.signature === 'string' && directLookup.signature.trim().length
            ? directLookup
            : lookupAntigravitySessionSignatureEntry(ANTIGRAVITY_GLOBAL_ALIAS_KEY, sessionId, { hydrate: true });
        const effectiveSessionId = typeof lookup.sourceSessionId === 'string' && lookup.sourceSessionId.trim().length ? lookup.sourceSessionId.trim() : sessionId;
        // Antigravity-Manager alignment:
        if (adapterContext) {
            try {
                const ctxAny = adapterContext;
                const keys = [
                    adapterContext.requestId,
                    typeof ctxAny.clientRequestId === 'string' ? String(ctxAny.clientRequestId) : '',
                    typeof ctxAny.groupRequestId === 'string' ? String(ctxAny.groupRequestId) : ''
                ].filter((k) => typeof k === 'string' && k.trim().length);
                for (const key of keys) {
                    cacheAntigravityRequestSessionId(key, aliasKey, effectiveSessionId);
                }
            }
            catch {
                // best-effort only
            }
        }
        if (typeof lookup.signature === 'string' && lookup.signature.trim().length) {
            injectAntigravityThoughtSignature(requestNode, lookup.signature);
        }
    }
    // Cloud Code Assist request wrapper should not carry metadata/action/web_search/stream.
    delete requestNode.metadata;
    delete requestNode.action;
    delete requestNode.web_search;
    delete requestNode.stream;
    delete requestNode.sessionId;
    delete root.metadata;
    delete root.stream;
    delete root.sessionId;
    if (Object.keys(requestNode).length > 0) {
        root.request = requestNode;
    }
    else {
        delete root.request;
    }
    return root;
}
