"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.decodeBestMatchingLogEvent = exports.decodeLogEvent = exports.decodeBestMatchingFunctionCall = exports.decodeFunctionCall = exports.reconcileStructs = exports.reconcileStructFromDecodedTuple = exports.getInputSize = exports.DecodeError = void 0;
const debug_logging_1 = require("@splunkdlt/debug-logging");
const error_1 = require("../utils/error");
const datatypes_1 = require("./datatypes");
const signature_1 = require("./signature");
const wasm_1 = require("./wasm");
const { trace } = debug_logging_1.createModuleDebug('abi:decode');
class DecodeError extends error_1.RuntimeError {
}
exports.DecodeError = DecodeError;
function getInputSize(abi) {
    try {
        return abi.inputs
            .map(input => datatypes_1.getDataSize(datatypes_1.encodeInputType(input)))
            .reduce((total, cur) => ({ length: total.length + cur.length, exact: total.exact && cur.exact }), {
            length: 0,
            exact: true
        });
    }
    catch (e) {
        throw new DecodeError(`Failed to determine input size for ${signature_1.computeSignature(abi)}: ${e.message}`);
    }
}
exports.getInputSize = getInputSize;
function reconcileStructFromDecodedTuple(decodedTuple, inputDef) {
    var _a;
    if (datatypes_1.isTupleArray(inputDef) && Array.isArray(decodedTuple)) {
        const arrayElementInputDef = { ...inputDef, type: 'tuple' };
        return decodedTuple
            .filter(v => Array.isArray(v))
            .map(v => reconcileStructFromDecodedTuple(v, arrayElementInputDef))
            .filter(v => v != null);
    }
    if (!((_a = inputDef.components) === null || _a === void 0 ? void 0 : _a.length) || !Array.isArray(decodedTuple)) {
        return null;
    }
    const result = {};
    for (const [i, component] of inputDef.components.entries()) {
        if (!component.name) {
            // no name provided for at least one component of the tuple - skipping struct reconciliation
            return null;
        }
        if (datatypes_1.isTuple(component)) {
            const value = reconcileStructFromDecodedTuple(decodedTuple[i], component);
            if (value == null) {
                result[component.name] = decodedTuple[i];
            }
            else {
                result[component.name] = value;
            }
        }
        else {
            result[component.name] = decodedTuple[i];
        }
    }
    return result;
}
exports.reconcileStructFromDecodedTuple = reconcileStructFromDecodedTuple;
exports.reconcileStructs = (decodedValues, inputDefs) => decodedValues.map((val, i) => {
    const input = inputDefs[i];
    if (datatypes_1.isTuple(input)) {
        const structData = reconcileStructFromDecodedTuple(val, input);
        if (structData != null) {
            return structData;
        }
    }
    return val;
});
function decodeFunctionCall(data, abi, signature, anonymous, shouldReconcileStructs) {
    var _a;
    const inputs = (_a = abi.inputs) !== null && _a !== void 0 ? _a : [];
    let decodedParams = wasm_1.abiDecodeParameters(data.slice(10), inputs.map(datatypes_1.encodeInputType));
    if (shouldReconcileStructs) {
        decodedParams = exports.reconcileStructs(decodedParams, inputs);
    }
    const params = [];
    const args = {};
    for (let i = 0; i < inputs.length; i++) {
        const input = inputs[i];
        const value = decodedParams[i];
        if (input.name != null) {
            args[input.name] = value;
        }
        params.push({
            name: anonymous ? undefined : input.name,
            type: input.type,
            value
        });
    }
    return {
        name: abi.name,
        signature,
        params,
        args: anonymous ? undefined : args
    };
}
exports.decodeFunctionCall = decodeFunctionCall;
function decodeBestMatchingFunctionCall(data, abis, anonymous, reconcileStructShapeFromTuples) {
    if (abis.length === 1) {
        // short-circut most common case
        return decodeFunctionCall(data, abis[0], signature_1.computeSignature(abis[0]), anonymous, reconcileStructShapeFromTuples);
    }
    const abisWithSize = abis.map(abi => [abi, getInputSize(abi)]);
    const dataLength = (data.length - 10) / 2;
    let lastError;
    // Attempt to find function signature with exact match of input data length
    for (const [abi, { length, exact }] of abisWithSize) {
        if (dataLength === length && exact) {
            try {
                return decodeFunctionCall(data, abi, signature_1.computeSignature(abi), anonymous, reconcileStructShapeFromTuples);
            }
            catch (e) {
                lastError = e;
                if (debug_logging_1.TRACE_ENABLED) {
                    trace('Failed to decode function call using signature %s with exact size match of %d bytes', signature_1.computeSignature(abi), length);
                }
            }
        }
    }
    // Consider dynamic data types
    for (const [abi, { length, exact }] of abisWithSize) {
        if (dataLength >= length && !exact) {
            try {
                return decodeFunctionCall(data, abi, signature_1.computeSignature(abi), anonymous, reconcileStructShapeFromTuples);
            }
            catch (e) {
                lastError = e;
                if (debug_logging_1.TRACE_ENABLED) {
                    trace('Failed to decode function call using signature %s with size match of %d bytes (min size %d bytes)', signature_1.computeSignature(abi), dataLength, length);
                }
            }
        }
    }
    // Brute-force try all ABI signatures, use the first one that doesn't throw on decode
    for (const abi of abis) {
        try {
            return decodeFunctionCall(data, abi, signature_1.computeSignature(abi), anonymous, reconcileStructShapeFromTuples);
        }
        catch (e) {
            lastError = e;
        }
    }
    throw lastError !== null && lastError !== void 0 ? lastError : new DecodeError('Unable to decode');
}
exports.decodeBestMatchingFunctionCall = decodeBestMatchingFunctionCall;
function decodeLogEvent(data, topics, abi, signature, anonymous, shouldReconcileStructs = false) {
    const nonIndexedInputs = abi.inputs.filter(i => !i.indexed);
    const nonIndexedTypes = nonIndexedInputs.map(datatypes_1.encodeInputType);
    let decodedData = wasm_1.abiDecodeParameters(data.slice(2), nonIndexedTypes);
    if (shouldReconcileStructs) {
        decodedData = exports.reconcileStructs(decodedData, nonIndexedInputs);
    }
    let topicIndex = 1;
    let dataIndex = 0;
    const args = {};
    const params = abi.inputs.map(input => {
        let value;
        if (input.indexed) {
            if (datatypes_1.isArrayType(input.type)) {
                topicIndex++;
                // we can't decode arrays since there is only a hash the log
                value = [];
            }
            else {
                const rawValue = topics[topicIndex++];
                if (rawValue == null) {
                    throw new DecodeError(`Expected data in topic index=${topicIndex - 1}, but topics length is ${topics.length}`);
                }
                const [decoded] = wasm_1.abiDecodeParameters(rawValue.slice(2), [datatypes_1.encodeInputType(input)]);
                value = decoded;
                if (shouldReconcileStructs && datatypes_1.isTuple(input)) {
                    const reconciled = reconcileStructFromDecodedTuple(decoded, input);
                    if (reconciled != null) {
                        value = reconciled;
                    }
                }
            }
        }
        else {
            value = decodedData[dataIndex++];
        }
        args[input.name] = value;
        return {
            name: anonymous ? undefined : input.name,
            type: input.type,
            value
        };
    });
    return {
        name: abi.name,
        signature,
        params,
        args: anonymous ? undefined : args
    };
}
exports.decodeLogEvent = decodeLogEvent;
function decodeBestMatchingLogEvent(data, topics, abis, anonymous, reconcileStructShapeFromTuples) {
    // No need to prioritize and check event logs for hash collisions since with the longer hash
    // collisions are very unlikely
    let lastError;
    for (const abi of abis) {
        try {
            return decodeLogEvent(data, topics, abi, signature_1.computeSignature(abi), anonymous, reconcileStructShapeFromTuples);
        }
        catch (e) {
            lastError = e;
            if (debug_logging_1.TRACE_ENABLED) {
                trace('Failed to decode log event', e);
            }
        }
    }
    throw lastError !== null && lastError !== void 0 ? lastError : new DecodeError('Unable to decode log event');
}
exports.decodeBestMatchingLogEvent = decodeBestMatchingLogEvent;
