import { __awaiter, __generator } from "tslib";
import assert from "assert";
import { MessageTransmitterClient, SvmSpokeClient } from "@across-protocol/contracts";
import { BN, BorshEventCoder } from "@coral-xyz/anchor";
import { address, appendTransactionMessageInstruction, createTransactionMessage, getAddressEncoder, getBase64EncodedWireTransaction, getProgramDerivedAddress, getU32Encoder, getU64Encoder, isAddress, pipe, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, signTransactionMessageWithSigners, } from "@solana/kit";
import bs58 from "bs58";
import { ethers } from "ethers";
import { FillType } from "../../interfaces";
import { BigNumber, getRelayDataHash, isDefined, isUint8Array } from "../../utils";
import { SVMEventNames } from "./types";
import { getTimestampForSlot } from "./SpokeUtils";
export { isSolanaError } from "@solana/kit";
/**
 * Basic void TransactionSigner type
 */
export var SolanaVoidSigner = function (simulationAddress) {
    return {
        address: address(simulationAddress),
        signAndSendTransactions: function () { return __awaiter(void 0, void 0, void 0, function () {
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, Promise.resolve([])];
                    case 1: return [2 /*return*/, _a.sent()];
                }
            });
        }); },
    };
};
/**
 * Helper to determine if the current RPC network is devnet.
 */
export function isDevnet(rpc) {
    return __awaiter(this, void 0, void 0, function () {
        var genesisHash;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, rpc.getGenesisHash().send()];
                case 1:
                    genesisHash = _a.sent();
                    return [2 /*return*/, genesisHash === "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG"];
            }
        });
    });
}
/**
 * Small utility to convert an Address to a Solana Kit branded type.
 */
export function toAddress(address) {
    return address.toBase58();
}
/**
 * For a given slot (or implicit head of chain), find the immediate preceding slot that contained a block.
 * @param provider SVM Provider instance.
 * @param opts An object containing a specific slot number, or a Solana commitment, defaulting to "confirmed".
 * @returns An object containing the slot number and the relevant timestamp for the block.
 */
export function getNearestSlotTime(provider, opts) {
    if (opts === void 0) { opts = { commitment: "confirmed" }; }
    return __awaiter(this, void 0, void 0, function () {
        var timestamp, slot, _a;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0:
                    if (!("slot" in opts)) return [3 /*break*/, 1];
                    _a = opts.slot;
                    return [3 /*break*/, 3];
                case 1: return [4 /*yield*/, provider.getSlot(opts).send()];
                case 2:
                    _a = _b.sent();
                    _b.label = 3;
                case 3:
                    slot = _a;
                    _b.label = 4;
                case 4: return [4 /*yield*/, getTimestampForSlot(provider, slot)];
                case 5:
                    timestamp = _b.sent();
                    _b.label = 6;
                case 6:
                    if (!isDefined(timestamp) && --slot) return [3 /*break*/, 4];
                    _b.label = 7;
                case 7:
                    assert(isDefined(timestamp), "Unable to resolve block time for SVM slot ".concat(slot));
                    return [2 /*return*/, { slot: slot, timestamp: timestamp }];
            }
        });
    });
}
/**
 * Resolve the latest finalized slot, and then work backwards to find the nearest slot containing a block.
 * In most cases the first-resolved slot should also have a block. Avoid making arbitrary decisions about
 * how many slots to rotate through.
 */
export function getLatestFinalizedSlotWithBlock(provider, maxSlot, maxLookback) {
    if (maxLookback === void 0) { maxLookback = 1000; }
    return __awaiter(this, void 0, void 0, function () {
        var finalizedSlot, endSlot, opts, slot, block;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, getNearestSlotTime(provider, { commitment: "finalized" })];
                case 1:
                    finalizedSlot = (_a.sent()).slot;
                    endSlot = Math.min(Number(maxSlot), Number(finalizedSlot));
                    opts = { maxSupportedTransactionVersion: 0, transactionDetails: "none", rewards: false };
                    slot = BigInt(endSlot);
                    _a.label = 2;
                case 2: return [4 /*yield*/, provider.getBlock(slot, opts).send()];
                case 3:
                    block = _a.sent();
                    if (isDefined(block) && [block.blockHeight, block.blockTime].every(isDefined)) {
                        return [3 /*break*/, 5];
                    }
                    _a.label = 4;
                case 4:
                    if (--maxLookback > 0 && --slot > 0) return [3 /*break*/, 2];
                    _a.label = 5;
                case 5:
                    if (maxLookback === 0) {
                        throw new Error("Unable to find Solana block between slots [".concat(slot, ", ").concat(endSlot, "]"));
                    }
                    return [2 /*return*/, Number(slot)];
            }
        });
    });
}
/**
 * Parses event data from a transaction.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function parseEventData(eventData) {
    if (!eventData)
        return eventData;
    if (Array.isArray(eventData)) {
        return eventData.map(parseEventData);
    }
    if (Buffer.isBuffer(eventData)) {
        return new Uint8Array(eventData);
    }
    if (typeof eventData === "object") {
        if (eventData.constructor.name === "PublicKey") {
            return address(eventData.toString());
        }
        if (BN.isBN(eventData)) {
            return BigInt(eventData.toString());
        }
        // Convert each key from snake_case to camelCase and process the value recursively.
        return Object.fromEntries(Object.entries(eventData).map(function (_a) {
            var key = _a[0], value = _a[1];
            return [snakeToCamel(key), parseEventData(value)];
        }));
    }
    return eventData;
}
/**
 * Decodes a raw event according to a supplied IDL.
 */
export function decodeEvent(idl, rawEvent) {
    var event = new BorshEventCoder(idl).decode(rawEvent);
    if (!event)
        throw new Error("Malformed rawEvent for IDL ".concat(idl.address, ": ").concat(rawEvent));
    return {
        name: event.name,
        data: parseEventData(event.data),
    };
}
/**
 * Converts a snake_case string to camelCase.
 */
function snakeToCamel(s) {
    return s.replace(/(_\w)/g, function (match) { return match[1].toUpperCase(); });
}
/**
 * Gets the event name from a raw name.
 */
export function getEventName(rawName) {
    if (Object.values(SVMEventNames).some(function (name) { return rawName.includes(name); }))
        return rawName;
    throw new Error("Unknown event name: ".concat(rawName));
}
/**
 * Unwraps any data structure and converts Address types to strings and Uint8Array to hex or BigInt.
 * Recursively processes nested objects and arrays.
 */
export function unwrapEventData(data, uint8ArrayKeysAsBigInt, currentKey) {
    if (uint8ArrayKeysAsBigInt === void 0) { uint8ArrayKeysAsBigInt = ["depositId", "outputAmount", "inputAmount"]; }
    // Handle null/undefined
    if (data == null) {
        return data;
    }
    // Handle BigInt
    if (typeof data === "bigint") {
        var bigIntKeysAsNumber = ["originChainId", "destinationChainId", "repaymentChainId", "chainId"];
        if (currentKey && bigIntKeysAsNumber.includes(currentKey)) {
            return Number(data);
        }
        return BigNumber.from(data);
    }
    // Handle Uint8Array and byte arrays
    if (data instanceof Uint8Array || isUint8Array(data)) {
        var bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
        var hex = ethers.utils.hexlify(bytes);
        if (currentKey && uint8ArrayKeysAsBigInt.includes(currentKey)) {
            return BigNumber.from(hex);
        }
        return hex;
    }
    // Handle regular arrays (non-byte arrays)
    if (Array.isArray(data)) {
        return data.map(function (item) { return unwrapEventData(item, uint8ArrayKeysAsBigInt); });
    }
    // Handle strings (potential addresses)
    if (typeof data === "string" && isAddress(data)) {
        return ethers.utils.hexlify(bs58.decode(data));
    }
    // Handle objects
    if (typeof data === "object") {
        // Special case: if an object is in the context of the fillType key, then
        // parse out the fillType from the object
        if (currentKey === "fillType") {
            var fillType = Object.keys(data)[0];
            switch (fillType) {
                case "FastFill":
                    return FillType.FastFill;
                case "ReplacedSlowFill":
                    return FillType.ReplacedSlowFill;
                case "SlowFill":
                    return FillType.SlowFill;
                default:
                    throw new Error("Unknown fill type: ".concat(fillType));
            }
        }
        // Special case: if an object is empty, return 0x
        if (Object.keys(data).length === 0) {
            return "0x";
        }
        return Object.fromEntries(Object.entries(data).map(function (_a) {
            var key = _a[0], value = _a[1];
            return [
                key,
                unwrapEventData(value, uint8ArrayKeysAsBigInt, key),
            ];
        }));
    }
    // Return primitives as is
    return data;
}
/**
 * Returns the PDA for the State account.
 * @param programId The SpokePool program ID.
 * @returns The PDA for the State account.
 */
export function getStatePda(programId) {
    return __awaiter(this, void 0, void 0, function () {
        var intEncoder, seed, statePda;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    intEncoder = getU64Encoder();
                    seed = intEncoder.encode(0);
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: programId,
                            seeds: ["state", seed],
                        })];
                case 1:
                    statePda = (_a.sent())[0];
                    return [2 /*return*/, statePda];
            }
        });
    });
}
/**
 * Returns the fill status PDA for the given relay data.
 * @param programId The SpokePool program ID.
 * @param relayData The relay data to get the fill status PDA for.
 * @param destinationChainId The destination chain ID.
 * @returns The PDA for the fill status.
 */
export function getFillStatusPda(programId, relayData, destinationChainId) {
    return __awaiter(this, void 0, void 0, function () {
        var relayDataHash, uint8RelayDataHash, fillStatusPda;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    relayDataHash = getRelayDataHash(relayData, destinationChainId);
                    uint8RelayDataHash = new Uint8Array(Buffer.from(relayDataHash.slice(2), "hex"));
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: programId,
                            seeds: ["fills", uint8RelayDataHash],
                        })];
                case 1:
                    fillStatusPda = (_a.sent())[0];
                    return [2 /*return*/, fillStatusPda];
            }
        });
    });
}
/**
 * Returns the PDA for a route account on SVM Spoke.
 * @param originToken The origin token address.
 * @param seed The seed for the route account.
 * @param routeChainId The route chain ID.
 * @returns The PDA for the route account.
 */
export function getRoutePda(originToken, seed, routeChainId) {
    return __awaiter(this, void 0, void 0, function () {
        var intEncoder, addressEncoder, pda;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    intEncoder = getU64Encoder();
                    addressEncoder = getAddressEncoder();
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: address(SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS),
                            seeds: ["route", addressEncoder.encode(originToken), intEncoder.encode(seed), intEncoder.encode(routeChainId)],
                        })];
                case 1:
                    pda = (_a.sent())[0];
                    return [2 /*return*/, pda];
            }
        });
    });
}
/**
 * Returns the PDA for the SVM Spoke's transfer liability account.
 * @param programId the address of the spoke pool.
 * @param originToken the address of the corresponding token.
 */
export function getTransferLiabilityPda(programId, originToken) {
    return __awaiter(this, void 0, void 0, function () {
        var addressEncoder, pda;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    addressEncoder = getAddressEncoder();
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: programId,
                            seeds: ["transfer_liability", addressEncoder.encode(originToken)],
                        })];
                case 1:
                    pda = (_a.sent())[0];
                    return [2 /*return*/, pda];
            }
        });
    });
}
/**
 * Returns the PDA for the SVM Spoke's root bundle account.
 * @param programId the address of the spoke pool.
 * @param rootBundleId the associated root bundle ID.
 */
export function getRootBundlePda(programId, rootBundleId) {
    return __awaiter(this, void 0, void 0, function () {
        var seedEncoder, seed, intEncoder, pda;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    seedEncoder = getU64Encoder();
                    seed = seedEncoder.encode(0);
                    intEncoder = getU32Encoder();
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: programId,
                            seeds: ["root_bundle", seed, intEncoder.encode(rootBundleId)],
                        })];
                case 1:
                    pda = (_a.sent())[0];
                    return [2 /*return*/, pda];
            }
        });
    });
}
/**
 * Returns the PDA for the SVM Spoke's instruction params account.
 * @param programId the address of the spoke pool.
 * @param signer the signer of the authenticated call.
 */
export function getInstructionParamsPda(programId, signer) {
    return __awaiter(this, void 0, void 0, function () {
        var addressEncoder, pda;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    addressEncoder = getAddressEncoder();
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: programId,
                            seeds: ["instruction_params", addressEncoder.encode(signer)],
                        })];
                case 1:
                    pda = (_a.sent())[0];
                    return [2 /*return*/, pda];
            }
        });
    });
}
/**
 * Returns the PDA for an individual's claim account.
 * @param programId the address of the spoke pool.
 * @param mint the address of the token.
 * @param tokenOwner the address of the signer which owns the claim account.
 */
export function getClaimAccountPda(programId, mint, tokenOwner) {
    return __awaiter(this, void 0, void 0, function () {
        var addressEncoder, pda;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    addressEncoder = getAddressEncoder();
                    return [4 /*yield*/, getProgramDerivedAddress({
                            programAddress: programId,
                            seeds: ["claim_account", addressEncoder.encode(mint), addressEncoder.encode(tokenOwner)],
                        })];
                case 1:
                    pda = (_a.sent())[0];
                    return [2 /*return*/, pda];
            }
        });
    });
}
/**
 * Returns the PDA for the Event Authority.
 * @returns The PDA for the Event Authority.
 */
export function getEventAuthority(programId) {
    return __awaiter(this, void 0, void 0, function () {
        var eventAuthority;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, getProgramDerivedAddress({
                        programAddress: programId,
                        seeds: ["__event_authority"],
                    })];
                case 1:
                    eventAuthority = (_a.sent())[0];
                    return [2 /*return*/, eventAuthority];
            }
        });
    });
}
/**
 * Returns the PDA for the Self Authority.
 * @returns The PDA for the Self Authority.
 */
export var getSelfAuthority = function () { return __awaiter(void 0, void 0, void 0, function () {
    var selfAuthority;
    return __generator(this, function (_a) {
        switch (_a.label) {
            case 0: return [4 /*yield*/, getProgramDerivedAddress({
                    programAddress: address(SvmSpokeClient.SVM_SPOKE_PROGRAM_ADDRESS),
                    seeds: ["self_authority"],
                })];
            case 1:
                selfAuthority = (_a.sent())[0];
                return [2 /*return*/, selfAuthority];
        }
    });
}); };
/**
 * Returns a random SVM address.
 */
export function getRandomSvmAddress() {
    var bytes = ethers.utils.randomBytes(32);
    var base58Address = bs58.encode(bytes);
    return address(base58Address);
}
/**
 * Creates a default v0 transaction skeleton.
 * @param rpcClient - The Solana client.
 * @param signer - The signer of the transaction.
 * @returns The default transaction.
 */
export var createDefaultTransaction = function (rpcClient, signer) { return __awaiter(void 0, void 0, void 0, function () {
    var latestBlockhash;
    return __generator(this, function (_a) {
        switch (_a.label) {
            case 0: return [4 /*yield*/, rpcClient.getLatestBlockhash().send()];
            case 1:
                latestBlockhash = (_a.sent()).value;
                return [2 /*return*/, pipe(createTransactionMessage({ version: 0 }), function (tx) { return setTransactionMessageFeePayerSigner(signer, tx); }, function (tx) { return setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx); })];
        }
    });
}); };
/**
 * Simulates a transaction and decodes the result using a parser function.
 * @param solanaClient - The Solana client.
 * @param ix - The instruction to simulate.
 * @param signer - The signer of the transaction.
 * @param parser - The parser function to decode the result.
 * @returns The decoded result.
 */
export var simulateAndDecode = function (solanaClient, ix, signer, parser) { return __awaiter(void 0, void 0, void 0, function () {
    var simulationTx, _a, _b, simulationResult, _c, _d, _e;
    var _f;
    return __generator(this, function (_g) {
        switch (_g.label) {
            case 0:
                _a = appendTransactionMessageInstruction;
                _b = [ix];
                return [4 /*yield*/, createDefaultTransaction(solanaClient, signer)];
            case 1:
                simulationTx = _a.apply(void 0, _b.concat([_g.sent()]));
                _d = (_c = solanaClient)
                    .simulateTransaction;
                _e = getBase64EncodedWireTransaction;
                return [4 /*yield*/, signTransactionMessageWithSigners(simulationTx)];
            case 2: return [4 /*yield*/, _d.apply(_c, [_e.apply(void 0, [_g.sent()]), {
                        encoding: "base64",
                    }])
                    .send()];
            case 3:
                simulationResult = _g.sent();
                if (!((_f = simulationResult.value.returnData) === null || _f === void 0 ? void 0 : _f.data[0])) {
                    throw new Error("No return data");
                }
                return [2 /*return*/, parser(Buffer.from(simulationResult.value.returnData.data[0], "base64"))];
        }
    });
}); };
/**
 * Returns the PDA for the CCTP nonce.
 * @param solanaClient The Solana client.
 * @param signer The signer of the transaction.
 * @param nonce The nonce to get the PDA for.
 * @param sourceDomain The source domain.
 * @returns The PDA for the CCTP nonce.
 */
export var getCCTPNoncePda = function (solanaClient, signer, nonce, sourceDomain) { return __awaiter(void 0, void 0, void 0, function () {
    var messageTransmitterPda, getNonceIx, parserFunction;
    return __generator(this, function (_a) {
        switch (_a.label) {
            case 0: return [4 /*yield*/, getProgramDerivedAddress({
                    programAddress: MessageTransmitterClient.MESSAGE_TRANSMITTER_PROGRAM_ADDRESS,
                    seeds: ["message_transmitter"],
                })];
            case 1:
                messageTransmitterPda = (_a.sent())[0];
                return [4 /*yield*/, MessageTransmitterClient.getGetNoncePdaInstruction({
                        messageTransmitter: messageTransmitterPda,
                        nonce: nonce,
                        sourceDomain: sourceDomain,
                    })];
            case 2:
                getNonceIx = _a.sent();
                parserFunction = function (buf) {
                    if (buf.length === 32) {
                        return address(bs58.encode(buf));
                    }
                    throw new Error("Invalid buffer");
                };
                return [4 /*yield*/, simulateAndDecode(solanaClient, getNonceIx, signer, parserFunction)];
            case 3: return [2 /*return*/, _a.sent()];
        }
    });
}); };
/**
 * Checks if a CCTP message is a deposit for burn event.
 * @param event The CCTP message event.
 * @returns True if the message is a deposit for burn event, false otherwise.
 */
export function isDepositForBurnEvent(event) {
    return event.type === "transfer";
}
/**
 * Convert a bigint (0 ≤ n < 2^256) to a 32-byte Uint8Array (big-endian).
 * @param n The bigint to convert.
 * @returns The 32-byte Uint8Array.
 */
export function bigintToU8a32(n) {
    if (n < BigInt(0) || n > ethers.constants.MaxUint256.toBigInt()) {
        throw new RangeError("Value must fit in 256 bits");
    }
    var hexPadded = ethers.utils.hexZeroPad("0x" + n.toString(16), 32);
    return ethers.utils.arrayify(hexPadded);
}
export var bigToU8a32 = function (bn) {
    return bigintToU8a32(typeof bn === "bigint" ? bn : BigInt(bn.toString()));
};
export var numberToU8a32 = function (n) { return bigintToU8a32(BigInt(n)); };
//# sourceMappingURL=utils.js.map