import { ethers } from "ethers";
import { arrayify } from "ethers/lib/utils";
export var RelayerPayloadId;
(function (RelayerPayloadId) {
    RelayerPayloadId[RelayerPayloadId["Delivery"] = 1] = "Delivery";
    RelayerPayloadId[RelayerPayloadId["Redelivery"] = 2] = "Redelivery";
})(RelayerPayloadId || (RelayerPayloadId = {}));
export var ExecutionInfoVersion;
(function (ExecutionInfoVersion) {
    ExecutionInfoVersion[ExecutionInfoVersion["EVM_V1"] = 0] = "EVM_V1";
})(ExecutionInfoVersion || (ExecutionInfoVersion = {}));
export var DeliveryStatus;
(function (DeliveryStatus) {
    DeliveryStatus["WaitingForVAA"] = "Waiting for VAA";
    DeliveryStatus["PendingDelivery"] = "Pending Delivery";
    DeliveryStatus["DeliverySuccess"] = "Delivery Success";
    DeliveryStatus["ReceiverFailure"] = "Receiver Failure";
    DeliveryStatus["ForwardRequestSuccess"] = "Forward Request Success";
    DeliveryStatus["ForwardRequestFailure"] = "Forward Request Failure";
    DeliveryStatus["ThisShouldNeverHappen"] = "This should never happen. Contact Support.";
    DeliveryStatus["DeliveryDidntHappenWithinRange"] = "Delivery didn't happen within given block range";
})(DeliveryStatus || (DeliveryStatus = {}));
export var RefundStatus;
(function (RefundStatus) {
    RefundStatus["RefundSent"] = "Refund Sent";
    RefundStatus["RefundFail"] = "Refund Fail";
    RefundStatus["CrossChainRefundSent"] = "Cross Chain Refund Sent";
    RefundStatus["CrossChainRefundFailProviderNotSupported"] = "Cross Chain Refund Fail - Provider does not support the refund chain";
    RefundStatus["CrossChainRefundFailNotEnough"] = "Cross Chain Refund Fail - Refund too low for cross chain refund";
})(RefundStatus || (RefundStatus = {}));
export function parseRefundStatus(index) {
    return index === 0
        ? RefundStatus.RefundSent
        : index === 1
            ? RefundStatus.RefundFail
            : index === 2
                ? RefundStatus.CrossChainRefundSent
                : index === 3
                    ? RefundStatus.CrossChainRefundFailProviderNotSupported
                    : index === 4
                        ? RefundStatus.CrossChainRefundFailNotEnough
                        : RefundStatus.CrossChainRefundFailProviderNotSupported;
}
export var VaaKeyType;
(function (VaaKeyType) {
    VaaKeyType[VaaKeyType["EMITTER_SEQUENCE"] = 0] = "EMITTER_SEQUENCE";
    VaaKeyType[VaaKeyType["VAAHASH"] = 1] = "VAAHASH";
})(VaaKeyType || (VaaKeyType = {}));
export function parseWormholeRelayerPayloadType(stringPayload) {
    const payload = typeof stringPayload === "string" ? arrayify(stringPayload) : stringPayload;
    if (payload[0] != RelayerPayloadId.Delivery &&
        payload[0] != RelayerPayloadId.Redelivery) {
        throw new Error("Unrecognized payload type " + payload[0]);
    }
    return payload[0];
}
export function createVaaKey(chainId, emitterAddress, sequence) {
    return {
        chainId,
        emitterAddress,
        sequence: ethers.BigNumber.from(sequence),
    };
}
export function parseWormholeRelayerSend(bytes) {
    let idx = 0;
    const payloadId = bytes.readUInt8(idx);
    if (payloadId !== RelayerPayloadId.Delivery) {
        throw new Error(`Expected Delivery payload type (${RelayerPayloadId.Delivery}), found: ${payloadId}`);
    }
    idx += 1;
    const targetChainId = bytes.readUInt16BE(idx);
    idx += 2;
    const targetAddress = bytes.slice(idx, idx + 32);
    idx += 32;
    let payload;
    [payload, idx] = parsePayload(bytes, idx);
    const requestedReceiverValue = ethers.BigNumber.from(Uint8Array.prototype.subarray.call(bytes, idx, idx + 32));
    idx += 32;
    const extraReceiverValue = ethers.BigNumber.from(Uint8Array.prototype.subarray.call(bytes, idx, idx + 32));
    idx += 32;
    let encodedExecutionInfo;
    [encodedExecutionInfo, idx] = parsePayload(bytes, idx);
    const refundChainId = bytes.readUInt16BE(idx);
    idx += 2;
    const refundAddress = bytes.slice(idx, idx + 32);
    idx += 32;
    const refundDeliveryProvider = bytes.slice(idx, idx + 32);
    idx += 32;
    const sourceDeliveryProvider = bytes.slice(idx, idx + 32);
    idx += 32;
    const senderAddress = bytes.slice(idx, idx + 32);
    idx += 32;
    const numMessages = bytes.readUInt8(idx);
    idx += 1;
    let messages = [];
    for (let i = 0; i < numMessages; ++i) {
        const res = parseVaaKey(bytes, idx);
        idx = res[1];
        messages.push(res[0]);
    }
    return {
        targetChainId,
        targetAddress,
        payload,
        requestedReceiverValue,
        extraReceiverValue,
        encodedExecutionInfo,
        refundChainId,
        refundAddress,
        refundDeliveryProvider,
        sourceDeliveryProvider,
        senderAddress,
        vaaKeys: messages,
    };
}
function parsePayload(bytes, idx) {
    const length = bytes.readUInt32BE(idx);
    idx += 4;
    const payload = bytes.slice(idx, idx + length);
    idx += length;
    return [payload, idx];
}
function parseVaaKey(bytes, idx) {
    const version = bytes.readUInt8(idx);
    idx += 1;
    const chainId = bytes.readUInt16BE(idx);
    idx += 2;
    const emitterAddress = bytes.slice(idx, idx + 32);
    idx += 32;
    const sequence = ethers.BigNumber.from(Uint8Array.prototype.subarray.call(bytes, idx, idx + 8));
    idx += 8;
    return [
        {
            chainId,
            emitterAddress,
            sequence,
        },
        idx,
    ];
}
export function parseEVMExecutionInfoV1(bytes, idx) {
    idx += 31;
    const version = bytes.readUInt8(idx);
    idx += 1;
    if (version !== ExecutionInfoVersion.EVM_V1) {
        throw new Error("Unexpected Execution Info version");
    }
    const gasLimit = ethers.BigNumber.from(Uint8Array.prototype.subarray.call(bytes, idx, idx + 32));
    idx += 32;
    const targetChainRefundPerGasUnused = ethers.BigNumber.from(Uint8Array.prototype.subarray.call(bytes, idx, idx + 32));
    idx += 32;
    return [{ gasLimit, targetChainRefundPerGasUnused }, idx];
}
export function parseWormholeRelayerResend(bytes) {
    let idx = 0;
    const payloadId = bytes.readUInt8(idx);
    if (payloadId !== RelayerPayloadId.Redelivery) {
        throw new Error(`Expected Delivery payload type (${RelayerPayloadId.Redelivery}), found: ${payloadId}`);
    }
    idx += 1;
    const parsedKey = parseVaaKey(bytes, idx);
    const key = parsedKey[0];
    idx = parsedKey[1];
    const targetChainId = bytes.readUInt16BE(idx);
    idx += 2;
    const newRequestedReceiverValue = ethers.BigNumber.from(Uint8Array.prototype.subarray.call(bytes, idx, idx + 32));
    idx += 32;
    let newEncodedExecutionInfo;
    [newEncodedExecutionInfo, idx] = parsePayload(bytes, idx);
    const newSourceDeliveryProvider = bytes.slice(idx, idx + 32);
    idx += 32;
    const newSenderAddress = bytes.slice(idx, idx + 32);
    idx += 32;
    return {
        deliveryVaaKey: key,
        targetChainId,
        newRequestedReceiverValue,
        newEncodedExecutionInfo,
        newSourceDeliveryProvider,
        newSenderAddress,
    };
}
export function executionInfoToString(encodedExecutionInfo) {
    const [parsed] = parseEVMExecutionInfoV1(encodedExecutionInfo, 0);
    return `Gas limit: ${parsed.gasLimit}, Target chain refund per unit gas unused: ${parsed.targetChainRefundPerGasUnused}`;
}
export function deliveryInstructionsPrintable(ix) {
    return {
        targetChainId: ix.targetChainId.toString(),
        targetAddress: ix.targetAddress.toString("hex"),
        payload: ix.payload.toString("base64"),
        requestedReceiverValue: ix.requestedReceiverValue.toString(),
        extraReceiverValue: ix.requestedReceiverValue.toString(),
        encodedExecutionInfo: executionInfoToString(ix.encodedExecutionInfo),
        refundChainId: ix.refundChainId.toString(),
        refundAddress: ix.refundAddress.toString("hex"),
        refundDeliveryProvider: ix.refundDeliveryProvider.toString("hex"),
        sourceDeliveryProvider: ix.sourceDeliveryProvider.toString("hex"),
        senderAddress: ix.senderAddress.toString("hex"),
        vaaKeys: ix.vaaKeys.map(vaaKeyPrintable),
    };
}
export function vaaKeyPrintable(ix) {
    var _a, _b, _c;
    return {
        chainId: (_a = ix.chainId) === null || _a === void 0 ? void 0 : _a.toString(),
        emitterAddress: (_b = ix.emitterAddress) === null || _b === void 0 ? void 0 : _b.toString("hex"),
        sequence: (_c = ix.sequence) === null || _c === void 0 ? void 0 : _c.toString(),
    };
}
export function redeliveryInstructionPrintable(ix) {
    return {
        deliveryVaaKey: vaaKeyPrintable(ix.deliveryVaaKey),
        targetChainId: ix.targetChainId.toString(),
        newRequestedReceiverValue: ix.newRequestedReceiverValue.toString(),
        newEncodedExecutionInfo: executionInfoToString(ix.newEncodedExecutionInfo),
        newSourceDeliveryProvider: ix.newSourceDeliveryProvider.toString("hex"),
        newSenderAddress: ix.newSenderAddress.toString("hex"),
    };
}
export function packOverrides(overrides) {
    const packed = [
        ethers.utils.solidityPack(["uint8"], [1]).substring(2),
        ethers.utils
            .solidityPack(["uint256"], [overrides.newReceiverValue])
            .substring(2),
        ethers.utils
            .solidityPack(["uint32"], [overrides.newExecutionInfo.length])
            .substring(2),
        overrides.newExecutionInfo.toString("hex"),
        overrides.redeliveryHash.toString("hex"), //toString('hex') doesn't add the 0x prefix
    ].join("");
    return "0x" + packed;
}
export function parseForwardFailureError(bytes) {
    let idx = 4;
    idx += 32;
    if (bytes.length <= idx) {
        return `Delivery Provider failed in performing forward`;
    }
    try {
        const amountOfFunds = ethers.BigNumber.from(Uint8Array.prototype.subarray.call(bytes, idx, idx + 32));
        idx += 32;
        const amountOfFundsNeeded = ethers.BigNumber.from(Uint8Array.prototype.subarray.call(bytes, idx, idx + 32));
        return `Not enough funds leftover for forward: Had ${ethers.utils.formatEther(amountOfFunds)} and needed ${ethers.utils.formatEther(amountOfFundsNeeded)}.`;
    }
    catch (err) {
        return `Delivery Provider unexpectedly failed in performing forward`;
    }
}
export function parseOverrideInfoFromDeliveryEvent(bytes) {
    let idx = 0;
    const version = bytes.readUInt8(idx);
    idx += 1;
    const newReceiverValue = ethers.BigNumber.from(Uint8Array.prototype.subarray.call(bytes, idx, idx + 32));
    idx += 32;
    let newExecutionInfo;
    [newExecutionInfo, idx] = parsePayload(bytes, idx);
    const redeliveryHash = bytes.slice(idx, idx + 32);
    idx += 32;
    return {
        newReceiverValue,
        newExecutionInfo,
        redeliveryHash,
    };
}
/*
 * Helpers
 */
export function dbg(x, msg) {
    if (msg) {
        console.log("[DEBUG] " + msg);
    }
    console.log(x);
    return x;
}
