"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.listenToBorrowOrderFills = listenToBorrowOrderFills;
const bn_js_1 = __importDefault(require("bn.js"));
const accounts_1 = require("../@codegen/klend/accounts");
const programId_1 = require("../@codegen/klend/programId");
const utils_1 = require("../utils");
const fraction_1 = require("./fraction");
const obligationListener_1 = require("./obligationListener");
const utils_2 = require("./utils");
// --- Fill detection helpers ---
const lamportsToSf = (lamports) => lamports.shln(fraction_1.Fraction.FRACTIONS);
function hasBorrowForMintIncreasedByAtLeast(prevBorrows, currBorrows, debtLiquidityMint, reserveToMint, minIncreaseLamports) {
    const minIncreaseSf = lamportsToSf(minIncreaseLamports);
    for (const curr of currBorrows) {
        if (!(0, utils_1.isNotNullPubkey)(curr.borrowReserve))
            continue;
        if (reserveToMint.get(curr.borrowReserve) !== debtLiquidityMint)
            continue;
        const prev = prevBorrows.find((p) => p.borrowReserve === curr.borrowReserve);
        if (!prev || !(0, utils_1.isNotNullPubkey)(prev.borrowReserve)) {
            if (curr.borrowedAmountSf.gte(minIncreaseSf))
                return true;
            continue;
        }
        if (curr.borrowedAmountSf.sub(prev.borrowedAmountSf).gte(minIncreaseSf))
            return true;
    }
    return false;
}
function buildReserveToMintMap(markets) {
    const marketMaps = new Map();
    for (const market of markets) {
        const reserveToMint = new Map();
        for (const [reserveAddress, reserve] of market.reserves) {
            reserveToMint.set(reserveAddress, reserve.getLiquidityMint());
        }
        marketMaps.set(market.getAddress(), reserveToMint);
    }
    return marketMaps;
}
async function fetchInitialObligationStates(rpc, programId, owner, commitment = 'confirmed') {
    const results = await rpc
        .getProgramAccounts(programId, {
        commitment,
        filters: (0, obligationListener_1.buildObligationFilters)(owner),
        encoding: 'base64',
    })
        .send();
    const states = new Map();
    for (const result of results) {
        if (result.account.owner !== programId)
            continue;
        const obligation = accounts_1.Obligation.decode(Buffer.from(result.account.data[0], 'base64'));
        if (obligation)
            states.set(result.pubkey, obligation);
    }
    return states;
}
// --- Fill detection logic ---
function detectFill(current, previous, reserveToMintByMarket) {
    const prevRemaining = previous.borrowOrder.remainingDebtAmount;
    if (prevRemaining.isZero())
        return null;
    const currRemaining = current.borrowOrder.remainingDebtAmount;
    const reserveToMint = reserveToMintByMarket.get(current.lendingMarket);
    if (!reserveToMint)
        return null;
    // Partial fill: remaining decreased but still > 0
    if (currRemaining.gt(new bn_js_1.default(0)) && currRemaining.lt(prevRemaining)) {
        const debtMint = previous.borrowOrder.debtLiquidityMint;
        const filledAmount = prevRemaining.sub(currRemaining);
        if (hasBorrowForMintIncreasedByAtLeast(previous.borrows, current.borrows, debtMint, reserveToMint, filledAmount)) {
            return {
                obligation: current,
                fillType: 'partial',
                debtLiquidityMint: debtMint,
                filledAmount,
                remainingDebtAmount: currRemaining,
                requestedDebtAmount: current.borrowOrder.requestedDebtAmount,
            };
        }
    }
    // Full fill: order zeroed AND borrow increased
    if (currRemaining.isZero() && current.borrowOrder.active === 0) {
        const debtMint = previous.borrowOrder.debtLiquidityMint;
        if (hasBorrowForMintIncreasedByAtLeast(previous.borrows, current.borrows, debtMint, reserveToMint, prevRemaining)) {
            return {
                obligation: current,
                fillType: 'full',
                debtLiquidityMint: debtMint,
                filledAmount: prevRemaining,
                remainingDebtAmount: new bn_js_1.default(0),
                requestedDebtAmount: previous.borrowOrder.requestedDebtAmount,
            };
        }
    }
    return null;
}
/**
 * Listen for borrow order fills via the shared AccountSubscriptionManager.
 *
 * Seeds initial obligation states via HTTP, then subscribes to the manager
 * for real-time notifications. State diffing detects partial and full fills.
 *
 * On WS reconnect, re-fetches obligation states via HTTP to avoid stale diffs
 * (e.g. multiple fills during downtime being misclassified as one).
 *
 * Returns an unsubscribe function.
 */
async function listenToBorrowOrderFills(params) {
    const programId = params.programId ?? programId_1.PROGRAM_ID;
    const commitment = params.commitment ?? 'confirmed';
    const reserveToMintByMarket = buildReserveToMintMap(params.markets);
    let previousStates = params.initialStates ?? (await fetchInitialObligationStates(params.rpc, programId, params.owner, commitment));
    const filters = (0, obligationListener_1.buildObligationFilters)(params.owner);
    const options = {
        commitment,
        // Re-fetch state on reconnect to avoid stale diffs — without this, fills
        // that happened during downtime cause misclassification (e.g. multiple
        // fills seen as one, or a cancelled order mistaken for a fill).
        onReconnect: async () => {
            previousStates = await fetchInitialObligationStates(params.rpc, programId, params.owner, commitment);
        },
    };
    return params.manager.subscribeProgramAccounts(programId, filters, (addr, buffer, slot) => {
        try {
            // Uses raw Obligation.decode (not KaminoObligation.fromAccountData) because
            // fill detection only needs borrow positions, not full hydrated obligation.
            const current = accounts_1.Obligation.decode((0, utils_2.toBuffer)(buffer));
            const previous = previousStates.get(addr);
            previousStates.set(addr, current);
            if (!previous)
                return;
            const fill = detectFill(current, previous, reserveToMintByMarket);
            if (fill)
                params.onFill({ ...fill, slot });
        }
        catch (e) {
            params.onError?.(e);
        }
    }, options);
}
//# sourceMappingURL=borrowOrderFillListener.js.map