import { buildChatRequestFromResponses, captureResponsesContext } from '../../conversion/responses/responses-openai-bridge.js';
import { cloneJson } from '../server-side-tools.js';
import { trimOpenAiMessagesForFollowup } from './followup-message-trimmer.js';
function extractResponsesTopLevelParameters(record) {
    const allowed = new Set([
        'temperature',
        'top_p',
        'max_output_tokens',
        'seed',
        'logit_bias',
        'user',
        'parallel_tool_calls',
        'tool_choice',
        'response_format',
        'stream'
    ]);
    const out = {};
    // Back-compat: StandardizedRequest uses max_tokens; map to Responses max_output_tokens.
    if (record.max_output_tokens === undefined && record.max_tokens !== undefined) {
        out.max_output_tokens = record.max_tokens;
    }
    for (const key of Object.keys(record)) {
        if (!allowed.has(key))
            continue;
        out[key] = record[key];
    }
    return Object.keys(out).length ? out : undefined;
}
export function extractCapturedChatSeed(source) {
    if (!source || typeof source !== 'object' || Array.isArray(source)) {
        return null;
    }
    const record = source;
    const model = typeof record.model === 'string' && record.model.trim().length ? record.model.trim() : undefined;
    const rawMessages = Array.isArray(record.messages) ? record.messages : null;
    if (rawMessages) {
        const tools = Array.isArray(record.tools) ? cloneJson(record.tools) : undefined;
        const parameters = normalizeFollowupParameters(record.parameters ?? extractResponsesTopLevelParameters(record));
        return {
            ...(model ? { model } : {}),
            messages: cloneJson(rawMessages),
            ...(tools ? { tools } : {}),
            ...(parameters ? { parameters } : {})
        };
    }
    // Backward/compat: some hosts may have captured the raw /v1/responses payload.
    const rawInput = Array.isArray(record.input) ? record.input : null;
    if (rawInput) {
        try {
            const ctx = captureResponsesContext(record);
            if (!ctx.isResponsesPayload) {
                return null;
            }
            const rebuilt = buildChatRequestFromResponses(record, ctx).request;
            const rebuiltModel = typeof rebuilt.model === 'string' && rebuilt.model.trim().length ? String(rebuilt.model).trim() : model;
            const rebuiltMessages = Array.isArray(rebuilt.messages) ? rebuilt.messages : [];
            const rebuiltTools = Array.isArray(rebuilt.tools) ? rebuilt.tools : undefined;
            const parameters = normalizeFollowupParameters(record.parameters ?? rebuilt.parameters ?? extractResponsesTopLevelParameters(record));
            return {
                ...(rebuiltModel ? { model: rebuiltModel } : {}),
                messages: cloneJson(rebuiltMessages),
                ...(rebuiltTools ? { tools: cloneJson(rebuiltTools) } : {}),
                ...(parameters ? { parameters } : {})
            };
        }
        catch {
            return null;
        }
    }
    return null;
}
export function normalizeFollowupParameters(value) {
    if (!value || typeof value !== 'object' || Array.isArray(value)) {
        return undefined;
    }
    const cloned = cloneJson(value);
    // Followup requests are always re-entered as a fresh hop:
    // - non-streaming (servertool orchestration enforces this)
    // - no inherited tool-selection hints, otherwise the resumed turn can be biased toward
    //   immediately calling tools again instead of consuming the tool outputs that were just injected.
    // Keep `parallel_tool_calls` inherited; provider compat can still disable it selectively.
    delete cloned.stream;
    delete cloned.tool_choice;
    return Object.keys(cloned).length ? cloned : undefined;
}
export function dropToolByFunctionName(tools, dropName) {
    const name = typeof dropName === 'string' ? dropName.trim() : '';
    if (!tools || !tools.length || !name) {
        return tools;
    }
    return tools.filter((tool) => {
        if (!tool || typeof tool !== 'object')
            return false;
        const fn = tool.function;
        const toolName = fn && typeof fn.name === 'string' ? fn.name : '';
        if (!toolName)
            return true;
        return toolName !== name;
    });
}
function extractAssistantMessageFromChatLike(chatResponse) {
    if (!chatResponse || typeof chatResponse !== 'object') {
        return null;
    }
    const choices = Array.isArray(chatResponse.choices)
        ? chatResponse.choices
        : [];
    if (choices.length > 0) {
        const first = choices[0] && typeof choices[0] === 'object' && !Array.isArray(choices[0])
            ? choices[0]
            : null;
        const msg = first &&
            first.message &&
            typeof first.message === 'object' &&
            !Array.isArray(first.message)
            ? first.message
            : null;
        if (msg) {
            return cloneJson(msg);
        }
    }
    // Responses-like fallback: try output_text.
    const outputText = chatResponse.output_text;
    if (typeof outputText === 'string' && outputText.trim().length) {
        return { role: 'assistant', content: outputText.trim() };
    }
    return null;
}
function buildToolMessagesFromToolOutputs(chatResponse) {
    const toolOutputs = Array.isArray(chatResponse.tool_outputs)
        ? chatResponse.tool_outputs
        : [];
    const messages = [];
    for (const entry of toolOutputs) {
        if (!entry || typeof entry !== 'object' || Array.isArray(entry))
            continue;
        const record = entry;
        const toolCallId = typeof record.tool_call_id === 'string' ? record.tool_call_id : undefined;
        if (!toolCallId)
            continue;
        const name = typeof record.name === 'string' && record.name.trim() ? record.name.trim() : 'tool';
        const rawContent = record.content;
        let contentText;
        if (typeof rawContent === 'string') {
            contentText = rawContent;
        }
        else {
            try {
                contentText = JSON.stringify(rawContent ?? {});
            }
            catch {
                contentText = String(rawContent ?? '');
            }
        }
        messages.push({
            role: 'tool',
            tool_call_id: toolCallId,
            name,
            content: contentText
        });
    }
    return messages;
}
function injectVisionSummaryIntoMessages(source, summary) {
    const messages = Array.isArray(source) ? cloneJson(source) : [];
    let injected = false;
    for (const message of messages) {
        if (!message || typeof message !== 'object')
            continue;
        const content = message.content;
        if (!Array.isArray(content))
            continue;
        const nextParts = [];
        let removed = false;
        for (const part of content) {
            if (part && typeof part === 'object') {
                const typeValue = typeof part.type === 'string'
                    ? String(part.type).toLowerCase()
                    : '';
                if (typeValue.includes('image')) {
                    removed = true;
                    nextParts.push({ type: 'text', text: '[Image omitted]' });
                    continue;
                }
            }
            nextParts.push(part);
        }
        if (removed) {
            nextParts.push({
                type: 'text',
                text: `[Vision] ${summary}`
            });
            message.content = nextParts;
            injected = true;
        }
    }
    if (!injected) {
        for (let i = messages.length - 1; i >= 0; i -= 1) {
            const msg = messages[i];
            if (!msg || typeof msg !== 'object')
                continue;
            const role = typeof msg.role === 'string'
                ? String(msg.role).toLowerCase()
                : '';
            if (role !== 'user')
                continue;
            const content = msg.content;
            if (Array.isArray(content)) {
                content.push({
                    type: 'text',
                    text: `[Vision] ${summary}`
                });
                injected = true;
                break;
            }
            if (typeof content === 'string' && content.length) {
                msg.content = `${content}\n[Vision] ${summary}`;
            }
            else {
                msg.content = `[Vision] ${summary}`;
            }
            injected = true;
            break;
        }
    }
    if (!injected) {
        messages.push({
            role: 'user',
            content: `[Vision] ${summary}`
        });
    }
    return messages;
}
function injectSystemTextIntoMessages(source, text) {
    const messages = Array.isArray(source) ? cloneJson(source) : [];
    const content = typeof text === 'string' ? text : '';
    if (!content.trim().length) {
        return messages;
    }
    const sys = { role: 'system', content };
    let insertAt = 0;
    while (insertAt < messages.length) {
        const msg = messages[insertAt];
        const role = msg && typeof msg === 'object' && !Array.isArray(msg) && typeof msg.role === 'string'
            ? String(msg.role).trim().toLowerCase()
            : '';
        if (role === 'system') {
            insertAt += 1;
            continue;
        }
        break;
    }
    messages.splice(insertAt, 0, sys);
    return messages;
}
function compactToolContentValue(value, maxChars) {
    const text = typeof value === 'string'
        ? value
        : (() => {
            try {
                return JSON.stringify(value ?? '');
            }
            catch {
                return String(value ?? '');
            }
        })();
    if (text.length <= maxChars) {
        return text;
    }
    const keepHead = Math.max(24, Math.floor(maxChars * 0.45));
    const keepTail = Math.max(24, Math.floor(maxChars * 0.35));
    const omitted = Math.max(0, text.length - keepHead - keepTail);
    const head = text.slice(0, keepHead);
    const tail = text.slice(text.length - keepTail);
    return head + '\n...[tool_output_compacted omitted=' + String(omitted) + ']...\n' + tail;
}
function compactToolContentInMessages(source, options) {
    const maxChars = Number.isFinite(options.maxChars) ? Math.max(64, Math.floor(options.maxChars)) : 1200;
    const messages = Array.isArray(source) ? cloneJson(source) : [];
    for (const message of messages) {
        if (!message || typeof message !== 'object' || Array.isArray(message)) {
            continue;
        }
        const role = typeof message.role === 'string'
            ? String(message.role).trim().toLowerCase()
            : '';
        if (role !== 'tool') {
            continue;
        }
        const content = message.content;
        message.content = compactToolContentValue(content, maxChars);
    }
    return messages;
}
function buildStandardFollowupTools() {
    // Keep this list minimal and stable. Used only as a best-effort fallback when a followup hop
    // would otherwise have no tools at all (which can cause tool-based clients to "break" mid-session).
    return [
        {
            type: 'function',
            function: {
                name: 'shell',
                description: 'Runs a shell command and returns its output.',
                parameters: {
                    type: 'object',
                    properties: {
                        command: { oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }] },
                        workdir: { type: 'string' }
                    },
                    required: ['command'],
                    additionalProperties: false
                }
            }
        },
        {
            type: 'function',
            function: {
                name: 'exec_command',
                description: 'Execute a command in a PTY and return output.',
                parameters: {
                    type: 'object',
                    properties: {
                        cmd: { type: 'string' },
                        workdir: { type: 'string' },
                        timeout_ms: { type: 'number' },
                        max_output_tokens: { type: 'number' },
                        yield_time_ms: { type: 'number' }
                    },
                    required: ['cmd'],
                    additionalProperties: false
                }
            }
        },
        {
            type: 'function',
            function: {
                name: 'continue_execution',
                description: 'No-op control tool for progress reporting without interrupting execution. Mandatory rule: if you are giving a progress-only update or are about to stop/end_turn, you MUST call continue_execution first (example arguments: {"reason":"progress_update"}). Required sequence: (1) call continue_execution, (2) provide a brief progress summary (<=5 lines), (3) immediately continue real actions. Do NOT emit finish_reason=stop/end_turn for progress-only updates. Only stop when the overall goal is complete. If waiting longer than 2 minutes is needed, use clock.schedule instead.',
                parameters: {
                    type: 'object',
                    properties: {},
                    required: [],
                    additionalProperties: false
                }
            }
        },
        {
            type: 'function',
            function: {
                name: 'review',
                description: 'Independent reviewer handoff. Use when you need an external evidence-based code review before claiming completion. The review result will be injected back to client as next-step guidance. Do not stop immediately after this tool.',
                parameters: {
                    type: 'object',
                    properties: {
                        goal: { type: 'string' },
                        context: { type: 'string' },
                        focus: { type: 'string' }
                    },
                    required: ['goal', 'context', 'focus'],
                    additionalProperties: false
                }
            }
        },
        {
            type: 'function',
            function: {
                name: 'apply_patch',
                description: 'Apply a patch to repository files.',
                parameters: {
                    type: 'object',
                    properties: {
                        patch: { type: 'string' }
                    },
                    required: ['patch'],
                    additionalProperties: false
                }
            }
        }
    ];
}
function ensureStandardToolsIfMissing(current) {
    const existing = Array.isArray(current) ? cloneJson(current) : [];
    const seen = new Set();
    for (const tool of existing) {
        if (!tool || typeof tool !== 'object' || Array.isArray(tool))
            continue;
        const fn = tool.function;
        const name = fn && typeof fn === 'object' && typeof fn.name === 'string' ? String(fn.name).trim() : '';
        if (name)
            seen.add(name);
    }
    for (const tool of buildStandardFollowupTools()) {
        const fn = tool.function;
        const name = fn && typeof fn === 'object' && typeof fn.name === 'string' ? String(fn.name).trim() : '';
        if (!name)
            continue;
        if (seen.has(name))
            continue;
        existing.push(tool);
        seen.add(name);
    }
    return existing;
}
/**
 * Build a canonical followup request body from injection ops.
 *
 * Important: this returns a protocol-agnostic "chat-like" payload:
 * { model, messages, tools?, parameters? }
 *
 * The followup is expected to re-enter HubPipeline at the chat-process entry,
 * so we must not convert to /v1/responses or /v1/messages here.
 */
export function buildServerToolFollowupChatPayloadFromInjection(args) {
    const captured = args.adapterContext && typeof args.adapterContext === 'object'
        ? args.adapterContext.capturedChatRequest
        : undefined;
    const seed = extractCapturedChatSeed(captured);
    if (!seed) {
        return null;
    }
    if (!seed.model || typeof seed.model !== 'string' || !seed.model.trim()) {
        return null;
    }
    let messages = Array.isArray(seed.messages) ? cloneJson(seed.messages) : [];
    const ops = Array.isArray(args.injection?.ops) ? args.injection.ops : [];
    // Followup is a normal request hop: inherit tool schema from the captured request and
    // let compat/tool-governance apply standard sanitization rules.
    let tools = Array.isArray(seed.tools) ? cloneJson(seed.tools) : undefined;
    const parameters = seed.parameters ? cloneJson(seed.parameters) : undefined;
    for (const op of ops) {
        if (!op || typeof op !== 'object')
            continue;
        if (op.op === 'preserve_tools') {
            // No-op: tools are preserved by default. Kept for backward compatibility.
            continue;
        }
        if (op.op === 'ensure_standard_tools') {
            tools = ensureStandardToolsIfMissing(tools);
            continue;
        }
        if (op.op === 'trim_openai_messages') {
            const maxNonSystemMessages = typeof op.maxNonSystemMessages === 'number'
                ? op.maxNonSystemMessages
                : 16;
            messages = trimOpenAiMessagesForFollowup(messages, { maxNonSystemMessages });
            continue;
        }
        if (op.op === 'compact_tool_content') {
            const maxChars = typeof op.maxChars === 'number'
                ? Math.max(64, Math.floor(op.maxChars))
                : 1200;
            messages = compactToolContentInMessages(messages, { maxChars });
            continue;
        }
        if (op.op === 'append_assistant_message') {
            const required = op.required !== false;
            const msg = extractAssistantMessageFromChatLike(args.chatResponse);
            if (!msg) {
                if (required)
                    return null;
                continue;
            }
            messages.push(msg);
            continue;
        }
        if (op.op === 'append_tool_messages_from_tool_outputs') {
            const required = op.required !== false;
            const toolMessages = buildToolMessagesFromToolOutputs(args.chatResponse);
            if (!toolMessages.length) {
                if (required)
                    return null;
                continue;
            }
            messages.push(...toolMessages);
            continue;
        }
        if (op.op === 'inject_system_text') {
            const text = typeof op.text === 'string' ? String(op.text) : '';
            if (text.trim().length) {
                messages = injectSystemTextIntoMessages(messages, text.trim());
            }
            continue;
        }
        if (op.op === 'append_user_text') {
            const text = typeof op.text === 'string' ? String(op.text) : '';
            if (text.trim().length) {
                messages.push({ role: 'user', content: text });
            }
            continue;
        }
        if (op.op === 'drop_tool_by_name') {
            const name = typeof op.name === 'string' ? String(op.name) : '';
            if (name.trim().length) {
                tools = dropToolByFunctionName(tools, name.trim());
            }
            continue;
        }
        if (op.op === 'inject_vision_summary') {
            const summary = typeof op.summary === 'string' ? String(op.summary) : '';
            if (summary.trim().length) {
                messages = injectVisionSummaryIntoMessages(messages, summary.trim());
            }
            continue;
        }
    }
    return {
        model: seed.model,
        messages,
        ...(tools ? { tools } : {}),
        ...(parameters ? { parameters } : {})
    };
}
