"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FilterTypeNotSupportedError = exports.SelectorTopicMismatchError = exports.TopicsMismatchError = exports.DataMismatchError = exports.InputNotFoundError = exports.ArgsMismatchError = void 0;
exports.assertArgs = assertArgs;
exports.decode = decode;
exports.encode = encode;
exports.format = format;
exports.from = from;
exports.fromAbi = fromAbi;
exports.getSelector = getSelector;
const abitype = require("abitype");
const AbiItem = require("./AbiItem.js");
const AbiParameters = require("./AbiParameters.js");
const Address = require("./Address.js");
const Bytes = require("./Bytes.js");
const Errors = require("./Errors.js");
const Hash = require("./Hash.js");
const Hex = require("./Hex.js");
const Cursor = require("./internal/cursor.js");
const errors_js_1 = require("./internal/errors.js");
function assertArgs(abiEvent, args, matchArgs) {
    if (!args || !matchArgs)
        throw new ArgsMismatchError({
            abiEvent,
            expected: args,
            given: matchArgs,
        });
    function isEqual(input, value, arg) {
        if (input.type === 'address')
            return Address.isEqual(value, arg);
        if (input.type === 'string')
            return Hash.keccak256(Bytes.fromString(value)) === arg;
        if (input.type === 'bytes')
            return Hash.keccak256(value) === arg;
        return value === arg;
    }
    if (Array.isArray(args) && Array.isArray(matchArgs)) {
        for (const [index, value] of matchArgs.entries()) {
            if (value === null || value === undefined)
                continue;
            const input = abiEvent.inputs[index];
            if (!input)
                throw new InputNotFoundError({
                    abiEvent,
                    name: `${index}`,
                });
            const value_ = Array.isArray(value) ? value : [value];
            let equal = false;
            for (const value of value_) {
                if (isEqual(input, value, args[index]))
                    equal = true;
            }
            if (!equal)
                throw new ArgsMismatchError({
                    abiEvent,
                    expected: args,
                    given: matchArgs,
                });
        }
    }
    if (typeof args === 'object' &&
        !Array.isArray(args) &&
        typeof matchArgs === 'object' &&
        !Array.isArray(matchArgs))
        for (const [key, value] of Object.entries(matchArgs)) {
            if (value === null || value === undefined)
                continue;
            const input = abiEvent.inputs.find((input) => input.name === key);
            if (!input)
                throw new InputNotFoundError({ abiEvent, name: key });
            const value_ = Array.isArray(value) ? value : [value];
            let equal = false;
            for (const value of value_) {
                if (isEqual(input, value, args[key]))
                    equal = true;
            }
            if (!equal)
                throw new ArgsMismatchError({
                    abiEvent,
                    expected: args,
                    given: matchArgs,
                });
        }
}
function decode(...parameters) {
    const [abiEvent, log] = (() => {
        if (Array.isArray(parameters[0])) {
            const [abi, name, log] = parameters;
            return [fromAbi(abi, name), log];
        }
        return parameters;
    })();
    const { data, topics } = log;
    const [selector_, ...argTopics] = topics;
    const selector = getSelector(abiEvent);
    if (selector_ !== selector)
        throw new SelectorTopicMismatchError({
            abiEvent,
            actual: selector_,
            expected: selector,
        });
    const { inputs } = abiEvent;
    const isUnnamed = inputs?.every((x) => !('name' in x && x.name));
    let args = isUnnamed ? [] : {};
    const indexedInputs = inputs.filter((x) => 'indexed' in x && x.indexed);
    for (let i = 0; i < indexedInputs.length; i++) {
        const param = indexedInputs[i];
        const topic = argTopics[i];
        if (!topic)
            throw new TopicsMismatchError({
                abiEvent,
                param: param,
            });
        args[isUnnamed ? i : param.name || i] = (() => {
            if (param.type === 'string' ||
                param.type === 'bytes' ||
                param.type === 'tuple' ||
                param.type.match(/^(.*)\[(\d+)?\]$/))
                return topic;
            const decoded = AbiParameters.decode([param], topic) || [];
            return decoded[0];
        })();
    }
    const nonIndexedInputs = inputs.filter((x) => !('indexed' in x && x.indexed));
    if (nonIndexedInputs.length > 0) {
        if (data && data !== '0x') {
            try {
                const decodedData = AbiParameters.decode(nonIndexedInputs, data);
                if (decodedData) {
                    if (isUnnamed)
                        args = [...args, ...decodedData];
                    else {
                        for (let i = 0; i < nonIndexedInputs.length; i++) {
                            const index = inputs.indexOf(nonIndexedInputs[i]);
                            args[nonIndexedInputs[i].name || index] = decodedData[i];
                        }
                    }
                }
            }
            catch (err) {
                if (err instanceof AbiParameters.DataSizeTooSmallError ||
                    err instanceof Cursor.PositionOutOfBoundsError)
                    throw new DataMismatchError({
                        abiEvent,
                        data: data,
                        parameters: nonIndexedInputs,
                        size: Hex.size(data),
                    });
                throw err;
            }
        }
        else {
            throw new DataMismatchError({
                abiEvent,
                data: '0x',
                parameters: nonIndexedInputs,
                size: 0,
            });
        }
    }
    return Object.values(args).length > 0 ? args : undefined;
}
function encode(...parameters) {
    const [abiEvent, args] = (() => {
        if (Array.isArray(parameters[0])) {
            const [abi, name, args] = parameters;
            return [fromAbi(abi, name), args];
        }
        const [abiEvent, args] = parameters;
        return [abiEvent, args];
    })();
    let topics = [];
    if (args && abiEvent.inputs) {
        const indexedInputs = abiEvent.inputs.filter((param) => 'indexed' in param && param.indexed);
        const args_ = Array.isArray(args)
            ? args
            : Object.values(args).length > 0
                ? (indexedInputs?.map((x, i) => args[x.name ?? i]) ?? [])
                : [];
        if (args_.length > 0) {
            const encode = (param, value) => {
                if (param.type === 'string')
                    return Hash.keccak256(Hex.fromString(value));
                if (param.type === 'bytes')
                    return Hash.keccak256(value);
                if (param.type === 'tuple' || param.type.match(/^(.*)\[(\d+)?\]$/))
                    throw new FilterTypeNotSupportedError(param.type);
                return AbiParameters.encode([param], [value]);
            };
            topics =
                indexedInputs?.map((param, i) => {
                    if (Array.isArray(args_[i]))
                        return args_[i].map((_, j) => encode(param, args_[i][j]));
                    return typeof args_[i] !== 'undefined' && args_[i] !== null
                        ? encode(param, args_[i])
                        : null;
                }) ?? [];
        }
    }
    const selector = (() => {
        if (abiEvent.hash)
            return abiEvent.hash;
        return getSelector(abiEvent);
    })();
    return { topics: [selector, ...topics] };
}
function format(abiEvent) {
    return abitype.formatAbiItem(abiEvent);
}
function from(abiEvent, options = {}) {
    return AbiItem.from(abiEvent, options);
}
function fromAbi(abi, name, options) {
    const item = AbiItem.fromAbi(abi, name, options);
    if (item.type !== 'event')
        throw new AbiItem.NotFoundError({ name, type: 'event' });
    return item;
}
function getSelector(abiItem) {
    return AbiItem.getSignatureHash(abiItem);
}
class ArgsMismatchError extends Errors.BaseError {
    constructor({ abiEvent, expected, given, }) {
        super('Given arguments do not match the expected arguments.', {
            metaMessages: [
                `Event: ${format(abiEvent)}`,
                `Expected Arguments: ${!expected ? 'None' : ''}`,
                expected ? (0, errors_js_1.prettyPrint)(expected) : undefined,
                `Given Arguments: ${!given ? 'None' : ''}`,
                given ? (0, errors_js_1.prettyPrint)(given) : undefined,
            ],
        });
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'AbiEvent.ArgsMismatchError'
        });
    }
}
exports.ArgsMismatchError = ArgsMismatchError;
class InputNotFoundError extends Errors.BaseError {
    constructor({ abiEvent, name, }) {
        super(`Parameter "${name}" not found on \`${format(abiEvent)}\`.`);
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'AbiEvent.InputNotFoundError'
        });
    }
}
exports.InputNotFoundError = InputNotFoundError;
class DataMismatchError extends Errors.BaseError {
    constructor({ abiEvent, data, parameters, size, }) {
        super([
            `Data size of ${size} bytes is too small for non-indexed event parameters.`,
        ].join('\n'), {
            metaMessages: [
                `Non-indexed Parameters: (${AbiParameters.format(parameters)})`,
                `Data:   ${data} (${size} bytes)`,
            ],
        });
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'AbiEvent.DataMismatchError'
        });
        Object.defineProperty(this, "abiEvent", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "data", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "parameters", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "size", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.abiEvent = abiEvent;
        this.data = data;
        this.parameters = parameters;
        this.size = size;
    }
}
exports.DataMismatchError = DataMismatchError;
class TopicsMismatchError extends Errors.BaseError {
    constructor({ abiEvent, param, }) {
        super([
            `Expected a topic for indexed event parameter${param.name ? ` "${param.name}"` : ''} for "${format(abiEvent)}".`,
        ].join('\n'));
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'AbiEvent.TopicsMismatchError'
        });
        Object.defineProperty(this, "abiEvent", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.abiEvent = abiEvent;
    }
}
exports.TopicsMismatchError = TopicsMismatchError;
class SelectorTopicMismatchError extends Errors.BaseError {
    constructor({ abiEvent, actual, expected, }) {
        super(`topics[0]="${actual}" does not match the expected topics[0]="${expected}".`, {
            metaMessages: [`Event: ${format(abiEvent)}`, `Selector: ${expected}`],
        });
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'AbiEvent.SelectorTopicMismatchError'
        });
    }
}
exports.SelectorTopicMismatchError = SelectorTopicMismatchError;
class FilterTypeNotSupportedError extends Errors.BaseError {
    constructor(type) {
        super(`Filter type "${type}" is not supported.`);
        Object.defineProperty(this, "name", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 'AbiEvent.FilterTypeNotSupportedError'
        });
    }
}
exports.FilterTypeNotSupportedError = FilterTypeNotSupportedError;
//# sourceMappingURL=AbiEvent.js.map