import { __assign, __awaiter, __generator } from "tslib";
import { BN, utils } from "@coral-xyz/anchor";
import { PublicKey, } from "@solana/web3.js";
import { deserialize } from "borsh";
import { publicKeyToEvmAddress, u8Array32ToInt } from "./conversionUtils";
/**
 * Finds a program address with a given label and optional extra seeds.
 */
export function findProgramAddress(label, program, extraSeeds) {
    var seeds = [Buffer.from(utils.bytes.utf8.encode(label))];
    if (extraSeeds) {
        for (var _i = 0, extraSeeds_1 = extraSeeds; _i < extraSeeds_1.length; _i++) {
            var extraSeed = extraSeeds_1[_i];
            if (typeof extraSeed === "string") {
                seeds.push(Buffer.from(utils.bytes.utf8.encode(extraSeed)));
            }
            else if (Array.isArray(extraSeed)) {
                seeds.push(Buffer.from(extraSeed));
            }
            else if (Buffer.isBuffer(extraSeed)) {
                seeds.push(extraSeed);
            }
            else {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                seeds.push(extraSeed.toBuffer());
            }
        }
    }
    var res = PublicKey.findProgramAddressSync(seeds, program);
    return { publicKey: res[0], bump: res[1] };
}
/**
 * Reads events from a transaction.
 */
export function readEvents(connection_1, txSignature_1, programs_1) {
    return __awaiter(this, arguments, void 0, function (connection, txSignature, programs, commitment) {
        var txResult;
        if (commitment === void 0) { commitment = "confirmed"; }
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, connection.getTransaction(txSignature, { commitment: commitment, maxSupportedTransactionVersion: 0 })];
                case 1:
                    txResult = _a.sent();
                    if (txResult === null)
                        return [2 /*return*/, []];
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    return [2 /*return*/, processEventFromTx(txResult, programs)];
            }
        });
    });
}
/**
 * Processes events from a transaction.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function processEventFromTx(txResult, programs) {
    var _a, _b, _c, _d;
    var eventAuthorities = new Map();
    for (var _i = 0, programs_1 = programs; _i < programs_1.length; _i++) {
        var program = programs_1[_i];
        eventAuthorities.set(program.programId.toString(), findProgramAddress("__event_authority", program.programId).publicKey);
    }
    var events = [];
    // Resolve any potential addresses that were passed from address lookup tables.
    var messageAccountKeys = txResult.transaction.message.getAccountKeys({
        accountKeysFromLookups: (_a = txResult.meta) === null || _a === void 0 ? void 0 : _a.loadedAddresses,
    });
    for (var _e = 0, _f = (_c = (_b = txResult.meta) === null || _b === void 0 ? void 0 : _b.innerInstructions) !== null && _c !== void 0 ? _c : []; _e < _f.length; _e++) {
        var ixBlock = _f[_e];
        for (var _g = 0, _h = ixBlock.instructions; _g < _h.length; _g++) {
            var ix = _h[_g];
            for (var _j = 0, programs_2 = programs; _j < programs_2.length; _j++) {
                var program = programs_2[_j];
                var ixProgramId = messageAccountKeys.get(ix.programIdIndex);
                var singleIxAccount = ix.accounts.length === 1 ? messageAccountKeys.get(ix.accounts[0]) : undefined;
                if (ixProgramId !== undefined &&
                    singleIxAccount !== undefined &&
                    program.programId.equals(ixProgramId) &&
                    ((_d = eventAuthorities.get(ixProgramId.toString())) === null || _d === void 0 ? void 0 : _d.equals(singleIxAccount))) {
                    var ixData = utils.bytes.bs58.decode(ix.data);
                    var eventData = utils.bytes.base64.encode(Buffer.from(new Uint8Array(ixData).slice(8)));
                    var event_1 = program.coder.events.decode(eventData);
                    events.push({
                        program: program.programId,
                        data: event_1 === null || event_1 === void 0 ? void 0 : event_1.data,
                        name: event_1 === null || event_1 === void 0 ? void 0 : event_1.name,
                    });
                }
            }
        }
    }
    return events;
}
/**
 * Helper function to wait for an event to be emitted. Should only be used in tests where txSignature is known to emit.
 */
export function readEventsUntilFound(connection, txSignature, programs) {
    return __awaiter(this, void 0, void 0, function () {
        var startTime, txResult;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    startTime = Date.now();
                    txResult = null;
                    _a.label = 1;
                case 1:
                    if (!(Date.now() - startTime < 5000)) return [3 /*break*/, 4];
                    return [4 /*yield*/, connection.getTransaction(txSignature, {
                            commitment: "confirmed",
                            maxSupportedTransactionVersion: 0,
                        })];
                case 2:
                    // 5 seconds timeout to wait to find the event.
                    txResult = _a.sent();
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    if (txResult !== null)
                        return [2 /*return*/, processEventFromTx(txResult, programs)];
                    return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 50); })];
                case 3:
                    _a.sent(); // 50 ms delay between retries.
                    return [3 /*break*/, 1];
                case 4: throw new Error("No event found within 5 seconds");
            }
        });
    });
}
/**
 * Retrieves a specific event by name from a list of events.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function getEvent(events, program, eventName) {
    for (var _i = 0, events_1 = events; _i < events_1.length; _i++) {
        var event_2 = events_1[_i];
        if (event_2.name === eventName && program.toString() === event_2.program.toString()) {
            return event_2.data;
        }
    }
    throw new Error("Event " + eventName + " not found");
}
/**
 * Reads all events for a specific program.
 */
export function readProgramEvents(connection_1, program_1) {
    return __awaiter(this, arguments, void 0, function (connection, 
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    program, finality, options) {
        var allSignatures, signatures, eventsWithSlots;
        var _this = this;
        if (finality === void 0) { finality = "confirmed"; }
        if (options === void 0) { options = { limit: 1000 }; }
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    allSignatures = [];
                    _a.label = 1;
                case 1: return [4 /*yield*/, connection.getSignaturesForAddress(program.programId, options, finality)];
                case 2:
                    signatures = _a.sent();
                    allSignatures.push.apply(allSignatures, signatures);
                    // Update options for the next batch. Set before to the last fetched signature.
                    if (signatures.length > 0) {
                        options = __assign(__assign({}, options), { before: signatures[signatures.length - 1].signature });
                    }
                    if (options.limit && signatures.length < options.limit)
                        return [3 /*break*/, 4]; // Exit early if the number of signatures < limit
                    _a.label = 3;
                case 3: return [3 /*break*/, 1];
                case 4: return [4 /*yield*/, Promise.all(allSignatures.map(function (signature) { return __awaiter(_this, void 0, void 0, function () {
                        var events;
                        return __generator(this, function (_a) {
                            switch (_a.label) {
                                case 0: return [4 /*yield*/, readEvents(connection, signature.signature, [program], finality)];
                                case 1:
                                    events = _a.sent();
                                    return [2 /*return*/, events.map(function (event) { return (__assign(__assign({}, event), { confirmationStatus: signature.confirmationStatus || "Unknown", blockTime: signature.blockTime || 0, signature: signature.signature, slot: signature.slot, name: event.name || "Unknown" })); })];
                            }
                        });
                    }); }))];
                case 5:
                    eventsWithSlots = _a.sent();
                    return [2 /*return*/, eventsWithSlots.flat()]; // Flatten the array of events & return.
            }
        });
    });
}
/**
 * Subscribes to CPI events for a program.
 */
export function subscribeToCpiEventsForProgram(connection, 
// eslint-disable-next-line @typescript-eslint/no-explicit-any
program, 
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callback) {
    var _this = this;
    var subscriptionId = connection.onLogs(new PublicKey(findProgramAddress("__event_authority", program.programId).publicKey.toString()), function (logs) { return __awaiter(_this, void 0, void 0, function () {
        var _a;
        return __generator(this, function (_b) {
            switch (_b.label) {
                case 0:
                    _a = callback;
                    return [4 /*yield*/, readEvents(connection, logs.signature, [program], "confirmed")];
                case 1:
                    _a.apply(void 0, [_b.sent()]);
                    return [2 /*return*/];
            }
        });
    }); }, "confirmed");
    return subscriptionId;
}
/**
 * Class for DepositId.
 */
var DepositId = /** @class */ (function () {
    function DepositId(properties) {
        this.value = properties.value;
    }
    return DepositId;
}());
/**
 * Borsh schema for deserializing DepositId.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
var depositIdSchema = new Map([[DepositId, { kind: "struct", fields: [["value", [32]]] }]] // Fixed array [u8; 32]
);
/**
 * Parses depositId: checks if only the first 4 bytes are non-zero and returns a u32 or deserializes the full array.
 */
function parseDepositId(value) {
    var restAreZero = value.slice(4).every(function (byte) { return byte === 0; });
    if (restAreZero) {
        // Parse the first 4 bytes as a little-endian u32
        var u32Value = new DataView(value.buffer).getUint32(0, true); // true for little-endian
        return u32Value.toString();
    }
    // Deserialize the full depositId using the Borsh schema
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    var depositId = deserialize(depositIdSchema, DepositId, Buffer.from(value));
    return new BN(depositId.value).toString();
}
/**
 * Stringifies a CPI event.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function stringifyCpiEvent(obj, eventName) {
    var _a, _b;
    if ((_b = (_a = obj === null || obj === void 0 ? void 0 : obj.constructor) === null || _a === void 0 ? void 0 : _a.toString()) === null || _b === void 0 ? void 0 : _b.includes("PublicKey")) {
        if (obj.toString().startsWith("111111111111")) {
            // First 12 bytes are 0 for EVM addresses.
            return publicKeyToEvmAddress(obj);
        }
        return obj.toString();
    }
    else if (BN.isBN(obj)) {
        return obj.toString();
    }
    else if (typeof obj === "bigint") {
        return obj.toString();
    }
    else if (Array.isArray(obj) && obj.length == 32) {
        return Buffer.from(obj).toString("hex"); // Hex representation for fixed-length arrays
    }
    else if (Array.isArray(obj)) {
        return obj.map(function (obj) { return stringifyCpiEvent(obj, eventName); });
    }
    else if (obj !== null && typeof obj === "object") {
        return Object.fromEntries(Object.entries(obj).map(function (_a) {
            var key = _a[0], value = _a[1];
            if (Array.isArray(value) && value.length === 32) {
                if (key === "depositId" || key === "deposit_id") {
                    // Parse depositId using the helper function
                    var parsedValue = parseDepositId(new Uint8Array(value));
                    return [key, parsedValue];
                }
                else if (eventName.toLowerCase() === "fundsdeposited" &&
                    (key === "outputAmount" || key === "output_amount")) {
                    return [key, u8Array32ToInt(new Uint8Array(value)).toString()];
                }
                else if ((eventName.toLowerCase() === "filledrelay" || eventName.toLowerCase() === "requestedslowfill") &&
                    (key === "inputAmount" || key === "input_amount")) {
                    return [key, u8Array32ToInt(new Uint8Array(value)).toString()];
                }
            }
            return [key, stringifyCpiEvent(value, eventName)];
        }));
    }
    return obj;
}
//# sourceMappingURL=solanaProgramUtils.js.map