import { AztecAddress, FunctionSelector } from '@aztec/circuits.js';
import { schemas } from '@aztec/foundation/schemas';
import { z } from 'zod';
const SourceCodeLocationSchema = z.object({
    filePath: z.string(),
    line: z.number(),
    column: z.number(),
    fileSource: z.string(),
    locationText: z.string(),
});
const NoirCallStackSchema = z.union([z.array(SourceCodeLocationSchema), z.array(z.string())]);
/**
 * Checks if a call stack is unresolved.
 */
export function isNoirCallStackUnresolved(callStack) {
    return typeof callStack[0] === 'string';
}
/**
 * An error during the simulation of a function call.
 */
export class SimulationError extends Error {
    constructor(originalMessage, functionErrorStack, revertData = [], noirErrorStack, options) {
        super(originalMessage, options);
        this.originalMessage = originalMessage;
        this.functionErrorStack = functionErrorStack;
        this.revertData = revertData;
        this.noirErrorStack = noirErrorStack;
        this.aztecContext = '';
        const getMessage = () => this.getMessage();
        const getStack = () => this.getStack();
        Object.defineProperties(this, {
            message: {
                configurable: false,
                enumerable: true,
                /**
                 * Getter for the custom error message. It has to be defined here because JS errors have the message property defined
                 * in the error itself, not its prototype. Thus if we define it as a class getter will be shadowed.
                 * @returns The message.
                 */
                get() {
                    return getMessage();
                },
            },
            stack: {
                configurable: false,
                enumerable: true,
                /**
                 * Getter for the custom error stack. It has to be defined here due to the same issue as the message.
                 * @returns The stack.
                 */
                get() {
                    return getStack();
                },
                /**
                 * We need a setter to avoid the error "TypeError: Cannot set property stack of #<SimulationError> which has only a getter"
                 * whenever we are traversing a nested error chain. However, we don't want to allow setting the stack, since the simulation
                 * error is always gonna be the root of the error chain.
                 * @param value
                 */
                set(_) { },
            },
        });
    }
    getMessage() {
        if (this.noirErrorStack && !isNoirCallStackUnresolved(this.noirErrorStack) && this.noirErrorStack.length) {
            return `${this.originalMessage} '${this.noirErrorStack[this.noirErrorStack.length - 1].locationText}'${this.aztecContext}`;
        }
        return this.originalMessage;
    }
    getOriginalMessage() {
        return this.originalMessage;
    }
    setOriginalMessage(message) {
        this.originalMessage = message;
    }
    /**
     * Enriches the error with the name of a contract that failed.
     * @param contractAddress - The address of the contract
     * @param contractName - The corresponding name
     */
    enrichWithContractName(contractAddress, contractName) {
        this.functionErrorStack.forEach(failingFunction => {
            if (failingFunction.contractAddress.equals(contractAddress)) {
                failingFunction.contractName = contractName;
            }
        });
    }
    /**
     * Enriches the error with the name of a function that failed.
     * @param contractAddress - The address of the contract
     * @param functionSelector - The selector of the function
     * @param functionName - The corresponding name
     */
    enrichWithFunctionName(contractAddress, functionSelector, functionName) {
        this.functionErrorStack.forEach(failingFunction => {
            if (failingFunction.contractAddress.equals(contractAddress) &&
                !!failingFunction.functionSelector &&
                failingFunction.functionSelector.equals(functionSelector)) {
                failingFunction.functionName = functionName;
            }
        });
    }
    getStack() {
        const functionCallStack = this.getCallStack();
        const noirCallStack = this.getNoirCallStack();
        // Try to resolve the contract and function names of the stack of failing functions.
        const stackLines = [
            ...functionCallStack.map(failingFunction => {
                return `at ${failingFunction.contractName ?? failingFunction.contractAddress.toString()}.${failingFunction.functionName ?? failingFunction.functionSelector?.toString() ?? 'unknown'}`;
            }),
            ...noirCallStack.map(errorLocation => typeof errorLocation === 'string'
                ? `at opcode ${errorLocation}`
                : `at ${errorLocation.locationText} (${errorLocation.filePath}:${errorLocation.line}:${errorLocation.column})`),
        ];
        return [`Simulation error: ${this.message}`, ...stackLines.reverse()].join('\n');
    }
    /**
     * The aztec function stack that failed during simulation.
     */
    getCallStack() {
        return this.functionErrorStack;
    }
    /**
     * Returns the noir call stack inside the first function that failed during simulation.
     * @returns The noir call stack.
     */
    getNoirCallStack() {
        return this.noirErrorStack || [];
    }
    /**
     * Sets the noir call stack.
     * @param callStack - The noir call stack.
     */
    setNoirCallStack(callStack) {
        this.noirErrorStack = callStack;
    }
    setAztecContext(context) {
        this.aztecContext = context;
    }
    toJSON() {
        return {
            originalMessage: this.originalMessage,
            functionErrorStack: this.functionErrorStack,
            noirErrorStack: this.noirErrorStack,
            revertData: this.revertData.map(fr => fr.toString()),
        };
    }
    static get schema() {
        return z
            .object({
            originalMessage: z.string(),
            functionErrorStack: z.array(z.object({
                contractAddress: schemas.AztecAddress,
                contractName: z.string().optional(),
                functionSelector: schemas.FunctionSelector,
                functionName: z.string().optional(),
            })),
            noirErrorStack: NoirCallStackSchema.optional(),
            revertData: z.array(schemas.Fr),
        })
            .transform(({ originalMessage, functionErrorStack, noirErrorStack, revertData }) => new SimulationError(originalMessage, functionErrorStack, revertData, noirErrorStack));
    }
    static random() {
        return new SimulationError('Random simulation error', [
            { contractAddress: AztecAddress.random(), functionSelector: FunctionSelector.random() },
        ]);
    }
}
//# sourceMappingURL=data:application/json;base64, 