"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.genericGetAccountShape = void 0;
const index_1 = require("@ledgerhq/coin-framework/account/index");
const jsHelpers_1 = require("@ledgerhq/coin-framework/bridge/jsHelpers");
const operation_1 = require("@ledgerhq/coin-framework/operation");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const alpaca_1 = require("./alpaca");
const utils_1 = require("./utils");
const serialization_1 = require("@ledgerhq/coin-framework/serialization");
const buildSubAccounts_1 = require("./buildSubAccounts");
function isNftCoreOp(operation) {
    return (typeof operation.details?.ledgerOpType === "string" &&
        ["NFT_IN", "NFT_OUT"].includes(operation.details?.ledgerOpType));
}
function isIncomingCoreOp(operation) {
    const type = typeof operation.details?.ledgerOpType === "string"
        ? operation.details.ledgerOpType
        : operation.type;
    return type === "IN";
}
function isInternalLiveOp(operation) {
    return !!operation.extra?.internal;
}
function genericGetAccountShape(network, kind) {
    return async (info, syncConfig) => {
        const { address, initialAccount, currency, derivationMode } = info;
        const alpacaApi = (0, alpaca_1.getAlpacaApi)(currency.id, kind);
        if (alpacaApi.getChainSpecificRules) {
            const chainSpecificValidation = alpacaApi.getChainSpecificRules();
            if (chainSpecificValidation.getAccountShape) {
                chainSpecificValidation.getAccountShape(address);
            }
        }
        const accountId = (0, index_1.encodeAccountId)({
            type: "js",
            version: "2",
            currencyId: currency.id,
            xpubOrAddress: address,
            derivationMode,
        });
        const blockInfo = await alpacaApi.lastBlock();
        const balanceRes = await alpacaApi.getBalance(address);
        const nativeAsset = (0, utils_1.extractBalance)(balanceRes, "native");
        const allTokenAssetsBalances = balanceRes.filter(b => b.asset.type !== "native");
        const nativeBalance = BigInt(nativeAsset?.value ?? "0");
        const spendableBalance = BigInt(nativeBalance - BigInt(nativeAsset?.locked ?? "0"));
        // Normalize pre-alpaca operations to the new accountId to keep UI rendering consistent
        const oldOps = (initialAccount?.operations || []).map(op => op.accountId === accountId
            ? op
            : { ...op, accountId, id: (0, operation_1.encodeOperationId)(accountId, op.hash, op.type) });
        const lastPagingToken = oldOps[0]?.extra?.pagingToken || "";
        const syncHash = await (0, index_1.getSyncHash)(currency.id, syncConfig.blacklistedTokenIds);
        const syncFromScratch = !initialAccount?.blockHeight || initialAccount?.syncHash !== syncHash;
        // Calculate minHeight for pagination
        const minHeight = syncFromScratch ? 0 : (oldOps[0]?.blockHeight ?? 0) + 1;
        const paginationParams = { minHeight, order: "desc" };
        if (lastPagingToken && !syncFromScratch) {
            paginationParams.lastPagingToken = lastPagingToken;
        }
        const [newCoreOps] = await alpacaApi.listOperations(address, paginationParams);
        const newOps = newCoreOps
            .filter(op => !isNftCoreOp(op) && (!isIncomingCoreOp(op) || !op.tx.failed))
            .map(op => (0, utils_1.adaptCoreOperationToLiveOperation)(accountId, op));
        const newAssetOperations = newOps.filter(operation => operation?.extra?.assetReference &&
            operation?.extra?.assetOwner &&
            !["OPT_IN", "OPT_OUT"].includes(operation.type));
        const newInternalOperations = newOps.filter(isInternalLiveOp);
        const newSubAccounts = await (0, buildSubAccounts_1.buildSubAccounts)({
            accountId,
            allTokenAssetsBalances,
            syncConfig,
            operations: newAssetOperations,
            getTokenFromAsset: alpacaApi.getTokenFromAsset,
        });
        const subAccounts = syncFromScratch
            ? newSubAccounts
            : (0, buildSubAccounts_1.mergeSubAccounts)(initialAccount?.subAccounts ?? [], newSubAccounts);
        const newOpsWithSubs = newOps
            .filter(operation => !isInternalLiveOp(operation))
            .map(op => {
            const subOperations = (0, serialization_1.inferSubOperations)(op.hash, newSubAccounts);
            const internalOperations = newInternalOperations.filter(it => it.hash === op.hash);
            return (0, utils_1.cleanedOperation)({
                ...op,
                subOperations,
                internalOperations,
            });
        });
        // Try to refresh known pending and broadcasted operations (if not already updated)
        // Useful for integrations without explorers
        const operationsToRefresh = initialAccount?.pendingOperations.filter(pendingOp => pendingOp.hash && // operation has been broadcasted
            !newOpsWithSubs.some(newOp => pendingOp.hash === newOp.hash));
        const confirmedOperations = alpacaApi.refreshOperations && operationsToRefresh?.length
            ? await alpacaApi.refreshOperations(operationsToRefresh)
            : [];
        const newOperations = [...confirmedOperations, ...newOpsWithSubs];
        const operations = (0, jsHelpers_1.mergeOps)(syncFromScratch ? [] : oldOps, newOperations);
        const res = {
            id: accountId,
            xpub: address,
            blockHeight: operations.length === 0 ? 0 : blockInfo.height || initialAccount?.blockHeight,
            balance: new bignumber_js_1.default(nativeBalance.toString()),
            spendableBalance: new bignumber_js_1.default(spendableBalance.toString()),
            operations,
            subAccounts,
            operationsCount: operations.length,
            syncHash,
        };
        return res;
    };
}
exports.genericGetAccountShape = genericGetAccountShape;
//# sourceMappingURL=getAccountShape.js.map