const DEFAULT_TOOL_OUTPUT = 'Command succeeded (no output).';
function isRecord(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function toRecord(value) {
    return isRecord(value) ? value : {};
}
function toArray(value) {
    return Array.isArray(value) ? value : [];
}
function hasArrayItems(value) {
    return Array.isArray(value) && value.length > 0;
}
export class UniversalShapeFilter {
    cfg;
    constructor(config) {
        this.cfg = config;
    }
    applyRequestFilter(payload) {
        const cfg = this.cfg;
        const allow = new Set(cfg.request.allowTopLevel);
        const src = toRecord(payload);
        const out = {};
        for (const key of Object.keys(src)) {
            if (allow.has(key)) {
                out[key] = src[key];
            }
        }
        const normalizedMessages = this.normalizeRequestMessages(out.messages, cfg.request);
        out.messages = normalizedMessages;
        this.normalizeTools(out, cfg.request);
        this.cleanupToolChoice(out);
        return out;
    }
    applyResponseFilter(payload, ctx) {
        const envFlag = String(process.env.RCC_COMPAT_FILTER_OFF_RESPONSES || '1').toLowerCase();
        const envBypass = !(envFlag === '0' || envFlag === 'false' || envFlag === 'off');
        const entryEndpoint = ctx?.entryEndpoint ?? ctx?.endpoint;
        const entry = typeof entryEndpoint === 'string' ? entryEndpoint.toLowerCase() : '';
        if (entry === '/v1/responses' || envBypass) {
            return payload;
        }
        const cfg = this.cfg;
        const src = toRecord(payload);
        const out = this.shallowPick(src, cfg.response.allowTopLevel);
        const choices = Array.isArray(src.choices) ? src.choices : [];
        out.choices = choices.map((choice, idx) => this.normalizeResponseChoice(choice, idx, cfg.response));
        if (src.usage && typeof src.usage === 'object') {
            out.usage = this.shallowPick(src.usage, cfg.response.usage?.allow || []);
        }
        return out;
    }
    shallowPick(obj, allow) {
        if (!isRecord(obj)) {
            return {};
        }
        const out = {};
        for (const key of allow) {
            if (Object.prototype.hasOwnProperty.call(obj, key)) {
                out[key] = obj[key];
            }
        }
        return out;
    }
    toObjectArgs(value) {
        if (value === null || value === undefined) {
            return {};
        }
        if (isRecord(value)) {
            return value;
        }
        if (typeof value === 'string') {
            try {
                return JSON.parse(value);
            }
            catch {
                return { raw: value };
            }
        }
        return {};
    }
    toStringArgs(value) {
        if (typeof value === 'string') {
            return value;
        }
        try {
            return JSON.stringify(value ?? {});
        }
        catch {
            return '{}';
        }
    }
    normalizeToolContent(value) {
        if (typeof value === 'string') {
            return value.trim().length ? value : DEFAULT_TOOL_OUTPUT;
        }
        if (value === null || value === undefined) {
            return DEFAULT_TOOL_OUTPUT;
        }
        try {
            const text = JSON.stringify(value);
            return text && text.length ? text : DEFAULT_TOOL_OUTPUT;
        }
        catch {
            return DEFAULT_TOOL_OUTPUT;
        }
    }
    normalizeRequestMessages(messages, requestCfg) {
        const entries = toArray(messages);
        const normalized = entries.map(entry => this.normalizeSingleMessage(entry, requestCfg));
        const withRules = this.applyMessageRules(normalized, requestCfg);
        this.pairToolResults(withRules);
        return withRules;
    }
    normalizeSingleMessage(message, requestCfg) {
        const msg = toRecord(message);
        const allowedRoles = requestCfg.messages.allowedRoles;
        const requestedRole = typeof msg.role === 'string' ? msg.role : undefined;
        const role = (requestedRole && allowedRoles.includes(requestedRole)) ? requestedRole : 'user';
        const normalized = { role };
        if (role === 'tool') {
            normalized.content = this.normalizeToolContent(msg.content);
            if (typeof msg.name === 'string') {
                normalized.name = msg.name;
            }
            if (typeof msg.tool_call_id === 'string') {
                normalized.tool_call_id = msg.tool_call_id;
            }
        }
        else if (Array.isArray(msg.content)) {
            normalized.content = msg.content.map((part) => {
                if (typeof part === 'string') {
                    return part;
                }
                if (isRecord(part)) {
                    return { ...part };
                }
                return part;
            });
        }
        else {
            normalized.content = (msg.content !== null && msg.content !== undefined) ? String(msg.content) : '';
        }
        if (role === 'assistant' && hasArrayItems(msg.tool_calls)) {
            normalized.tool_calls = this.normalizeAssistantToolCalls(msg.tool_calls, requestCfg);
            if (requestCfg.messages.assistantWithToolCallsContentNull) {
                normalized.content = null;
            }
        }
        return normalized;
    }
    normalizeAssistantToolCalls(toolCalls, requestCfg) {
        const entries = toArray(toolCalls);
        return entries.map(call => {
            const tc = toRecord(call);
            const fn = toRecord(tc.function);
            const name = typeof fn.name === 'string' ? fn.name : undefined;
            const argsValue = requestCfg.assistantToolCalls?.functionArgumentsType === 'string'
                ? this.toStringArgs(fn.arguments)
                : this.toObjectArgs(fn.arguments);
            const normalized = {
                type: typeof tc.type === 'string' ? tc.type : 'function',
                function: { ...(name ? { name } : {}), arguments: argsValue }
            };
            if (typeof tc.id === 'string') {
                normalized.id = tc.id;
            }
            return normalized;
        });
    }
    applyMessageRules(messages, requestCfg) {
        const rules = Array.isArray(requestCfg.messagesRules) ? requestCfg.messagesRules : [];
        if (!rules.length) {
            if (requestCfg.messages.suppressAssistantToolCalls) {
                return messages.filter(msg => !(msg.role === 'assistant' && hasArrayItems(msg.tool_calls)));
            }
            return messages;
        }
        const result = [];
        for (const message of messages) {
            let dropped = false;
            for (const rule of rules) {
                const when = rule.when || {};
                const matchRole = when.role ? message.role === when.role : true;
                const hasTools = hasArrayItems(message.tool_calls);
                const matchTools = typeof when.hasToolCalls === 'boolean' ? hasTools === when.hasToolCalls : true;
                if (matchRole && matchTools) {
                    if (rule.action === 'drop') {
                        dropped = true;
                        break;
                    }
                    if (rule.action === 'set' && rule.set && typeof rule.set === 'object') {
                        Object.assign(message, rule.set);
                    }
                }
            }
            if (!dropped) {
                result.push(message);
            }
        }
        return result;
    }
    pairToolResults(messages) {
        const nameById = new Map();
        for (const message of messages) {
            if (message.role === 'assistant' && hasArrayItems(message.tool_calls)) {
                for (const call of message.tool_calls) {
                    const toolCall = toRecord(call);
                    if (typeof toolCall.id !== 'string') {
                        continue;
                    }
                    const fn = toRecord(toolCall.function);
                    if (typeof fn.name === 'string') {
                        nameById.set(toolCall.id, fn.name);
                    }
                }
            }
        }
        for (const message of messages) {
            if (message.role !== 'tool') {
                continue;
            }
            const name = typeof message.name === 'string' ? message.name.trim() : '';
            if (name) {
                continue;
            }
            const toolCallId = typeof message.tool_call_id === 'string' ? message.tool_call_id : undefined;
            if (toolCallId && nameById.has(toolCallId)) {
                message.name = nameById.get(toolCallId);
            }
        }
    }
    normalizeTools(container, requestCfg) {
        if (!Array.isArray(container.tools)) {
            return;
        }
        if (!requestCfg.tools?.normalize) {
            if (requestCfg.tools?.forceToolChoiceAuto) {
                container.tool_choice = 'auto';
            }
            return;
        }
        const normalized = [];
        for (const toolEntry of container.tools) {
            const normalizedTool = this.normalizeSingleTool(toolEntry);
            normalized.push(normalizedTool);
        }
        container.tools = normalized;
        if (requestCfg.tools?.forceToolChoiceAuto) {
            container.tool_choice = 'auto';
        }
    }
    normalizeSingleTool(toolEntry) {
        const tool = toRecord(toolEntry);
        const fnTop = {
            name: typeof tool.name === 'string' ? tool.name : undefined,
            description: typeof tool.description === 'string' ? tool.description : undefined,
            parameters: tool.parameters
        };
        const fn = toRecord(tool.function);
        const name = typeof fn.name === 'string' ? fn.name : fnTop.name;
        const description = typeof fn.description === 'string' ? fn.description : fnTop.description;
        let parameters = fn.parameters !== undefined ? fn.parameters : fnTop.parameters;
        parameters = this.normalizeToolParameters(parameters, name);
        const normalizedFn = {
            ...(name ? { name } : {}),
            ...(description ? { description } : {})
        };
        if (parameters !== undefined) {
            normalizedFn.parameters = parameters;
        }
        return {
            type: 'function',
            function: normalizedFn
        };
    }
    normalizeToolParameters(input, name) {
        if (input === null || input === undefined) {
            return undefined;
        }
        let params;
        if (typeof input === 'string') {
            try {
                params = JSON.parse(input);
            }
            catch {
                params = undefined;
            }
        }
        else if (isRecord(input)) {
            params = input;
        }
        if (!params) {
            return undefined;
        }
        if (name && name.trim().toLowerCase() === 'shell') {
            return this.enforceShellSchema(params);
        }
        return params;
    }
    enforceShellSchema(schema) {
        const next = { ...schema };
        if (typeof next.type !== 'string') {
            next.type = 'object';
        }
        if (!isRecord(next.properties)) {
            next.properties = {};
        }
        const props = next.properties;
        const command = toRecord(props.command);
        const hasOneOf = Array.isArray(command.oneOf);
        if (!hasOneOf) {
            const descText = typeof command.description === 'string'
                ? command.description
                : 'Shell command. Prefer a single string; an array of argv tokens is also accepted.';
            props.command = {
                description: descText,
                oneOf: [
                    { type: 'string' },
                    { type: 'array', items: { type: 'string' } }
                ]
            };
            const requiredList = Array.isArray(next.required)
                ? next.required.filter((item) => typeof item === 'string')
                : [];
            if (!requiredList.includes('command')) {
                requiredList.push('command');
            }
            next.required = requiredList;
            if (typeof next.additionalProperties !== 'boolean') {
                next.additionalProperties = false;
            }
        }
        return next;
    }
    cleanupToolChoice(container) {
        const toolsArray = Array.isArray(container.tools) ? container.tools : [];
        if (!toolsArray.length && Object.prototype.hasOwnProperty.call(container, 'tool_choice')) {
            delete container.tool_choice;
        }
    }
    normalizeResponseChoice(choice, idx, responseCfg) {
        const choiceRecord = toRecord(choice);
        const normalized = {
            index: typeof choiceRecord.index === 'number' ? choiceRecord.index : idx
        };
        const message = toRecord(choiceRecord.message);
        normalized.message = this.normalizeResponseMessage(message, responseCfg);
        const hasToolCalls = hasArrayItems(normalized.message.tool_calls);
        normalized.finish_reason = choiceRecord.finish_reason ?? (hasToolCalls ? 'tool_calls' : null);
        return normalized;
    }
    normalizeResponseMessage(message, responseCfg) {
        const normalized = {};
        normalized.role = typeof message.role === 'string'
            ? message.role
            : (responseCfg.choices.message.roleDefault || 'assistant');
        if (hasArrayItems(message.tool_calls)) {
            normalized.tool_calls = this.normalizeResponseToolCalls(message.tool_calls, responseCfg);
            normalized.content = responseCfg.choices.message.contentNullWhenToolCalls ? null : message.content ?? '';
        }
        else {
            normalized.content = message.content ?? '';
        }
        if (typeof message.reasoning_content === 'string') {
            normalized.reasoning_content = message.reasoning_content;
        }
        if (message.audio) {
            normalized.audio = message.audio;
        }
        return normalized;
    }
    normalizeResponseToolCalls(toolCalls, responseCfg) {
        const entries = toArray(toolCalls);
        return entries.map(call => {
            const tc = toRecord(call);
            const fn = toRecord(tc.function);
            const name = typeof fn.name === 'string' ? fn.name : undefined;
            const argsObject = this.toObjectArgs(fn.arguments);
            const argsValue = responseCfg.choices.message.tool_calls?.function?.argumentsType === 'string'
                ? this.toStringArgs(argsObject)
                : argsObject;
            const normalized = {
                type: typeof tc.type === 'string' ? tc.type : 'function',
                function: { ...(name ? { name } : {}), arguments: argsValue }
            };
            if (typeof tc.id === 'string') {
                normalized.id = tc.id;
            }
            return normalized;
        });
    }
}
