"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getGlobalOnBridgeError = exports.setGlobalOnBridgeError = void 0;
const bignumber_js_1 = require("bignumber.js");
const react_1 = require("react");
const logs_1 = require("@ledgerhq/logs");
const _1 = require(".");
const account_1 = require("../account");
const promise_1 = require("../promise");
const initial = {
    account: null,
    parentAccount: null,
    transaction: null,
    status: {
        errors: {},
        warnings: {},
        estimatedFees: new bignumber_js_1.BigNumber(0),
        amount: new bignumber_js_1.BigNumber(0),
        totalSpent: new bignumber_js_1.BigNumber(0),
    },
    statusOnTransaction: null,
    errorAccount: null,
    errorStatus: null,
};
const makeInit = (optionalInit) => () => {
    let s = initial;
    if (optionalInit) {
        const patch = optionalInit();
        const { account, parentAccount, transaction } = patch;
        if (account) {
            s = reducer(s, {
                type: "setAccount",
                account,
                parentAccount,
            });
        }
        if (transaction) {
            s = reducer(s, {
                type: "setTransaction",
                transaction,
            });
        }
    }
    return s;
};
const reducer = (state, action) => {
    switch (action.type) {
        case "setAccount": {
            const { account, parentAccount } = action;
            try {
                const mainAccount = (0, account_1.getMainAccount)(account, parentAccount);
                const bridge = (0, _1.getAccountBridge)(account, parentAccount);
                const subAccountId = account.type !== "Account" && account.id;
                let t = bridge.createTransaction(mainAccount);
                if (
                // @ts-expect-error transaction.mode is not available on all union types. type guard is required
                state.transaction?.mode &&
                    // @ts-expect-error transaction.mode is not available on all union types. type guard is required
                    state.transaction.mode !== t.mode) {
                    t = bridge.updateTransaction(t, {
                        // @ts-expect-error transaction.mode is not available on all union types. type guard is required
                        mode: state.transaction.mode,
                    });
                }
                if (subAccountId) {
                    t = { ...t, subAccountId };
                }
                return {
                    ...initial,
                    account,
                    parentAccount,
                    transaction: t,
                };
            }
            catch (e) {
                return {
                    ...initial,
                    account,
                    parentAccount,
                    errorAccount: e,
                };
            }
        }
        case "setTransaction":
            if (state.transaction === action.transaction)
                return state;
            return { ...state, transaction: action.transaction };
        case "updateTransaction": {
            if (!state.transaction)
                return state;
            const transaction = action.updater(state.transaction);
            if (state.transaction === transaction)
                return state;
            return { ...state, transaction };
        }
        case "onStatus":
            return {
                ...state,
                errorStatus: null,
                transaction: action.transaction,
                status: action.status,
                statusOnTransaction: action.transaction,
            };
        case "onStatusError":
            if (action.error === state.errorStatus)
                return state;
            return { ...state, errorStatus: action.error };
        default:
            return state;
    }
};
const INITIAL_ERROR_RETRY_DELAY = 1000;
const ERROR_RETRY_DELAY_MULTIPLIER = 1.5;
const DEBOUNCE_STATUS_DELAY = 300;
const useBridgeTransaction = (optionalInit) => {
    const [{ account, parentAccount, transaction, status, statusOnTransaction, errorAccount, errorStatus }, dispatch,] = (0, react_1.useReducer)(reducer, undefined, makeInit(optionalInit));
    const setAccount = (0, react_1.useCallback)((account, parentAccount) => dispatch({
        type: "setAccount",
        account,
        parentAccount,
    }), [dispatch]);
    const setTransaction = (0, react_1.useCallback)(transaction => dispatch({
        type: "setTransaction",
        transaction,
    }), [dispatch]);
    const updateTransaction = (0, react_1.useCallback)(updater => dispatch({
        type: "updateTransaction",
        updater,
    }), [dispatch]);
    const mainAccount = account ? (0, account_1.getMainAccount)(account, parentAccount) : null;
    const errorDelay = (0, react_1.useRef)(INITIAL_ERROR_RETRY_DELAY);
    const statusIsPending = (0, react_1.useRef)(false); // Stores if status already being processed
    const bridgePending = transaction !== statusOnTransaction;
    // when transaction changes, prepare the transaction
    (0, react_1.useEffect)(() => {
        let ignore = false;
        let errorTimeout;
        // If bridge is not pending, transaction change is due to
        // the last onStatus dispatch (prepareTransaction changed original transaction) and must be ignored
        if (!bridgePending)
            return;
        if (mainAccount && transaction) {
            // We don't debounce first status refresh, but any subsequent to avoid multiple calls
            // First call is immediate
            const debounce = statusIsPending.current ? (0, promise_1.delay)(DEBOUNCE_STATUS_DELAY) : null;
            statusIsPending.current = true; // consider pending until status is resolved (error or success)
            Promise.resolve(debounce)
                .then(() => (0, _1.getAccountBridge)(mainAccount, null))
                .then(async (bridge) => {
                if (ignore)
                    return;
                const preparedTransaction = await bridge.prepareTransaction(mainAccount, transaction);
                if (ignore)
                    return;
                const status = await bridge.getTransactionStatus(mainAccount, preparedTransaction);
                if (ignore)
                    return;
                return {
                    preparedTransaction,
                    status,
                };
            })
                .then(result => {
                if (ignore || !result)
                    return;
                const { preparedTransaction, status } = result;
                errorDelay.current = INITIAL_ERROR_RETRY_DELAY; // reset delay
                statusIsPending.current = false; // status is now synced with transaction
                dispatch({
                    type: "onStatus",
                    status,
                    transaction: preparedTransaction,
                });
            }, e => {
                if (ignore)
                    return;
                statusIsPending.current = false;
                dispatch({
                    type: "onStatusError",
                    error: e,
                });
                (0, logs_1.log)("useBridgeTransaction", "prepareTransaction failed " + String(e));
                // After X seconds of hanging in this error case, we try again
                (0, logs_1.log)("useBridgeTransaction", "retrying prepareTransaction...");
                errorTimeout = setTimeout(() => {
                    // $FlowFixMe (mobile)
                    errorDelay.current *= ERROR_RETRY_DELAY_MULTIPLIER; // increase delay
                    // $FlowFixMe
                    const transactionCopy = {
                        ...transaction,
                    };
                    dispatch({
                        type: "setTransaction",
                        transaction: transactionCopy,
                    }); // $FlowFixMe (mobile)
                }, errorDelay.current);
            });
        }
        return () => {
            ignore = true;
            if (errorTimeout) {
                clearTimeout(errorTimeout);
                errorTimeout = null;
            }
        };
    }, [transaction, mainAccount, bridgePending, dispatch]);
    const bridgeError = errorAccount || errorStatus;
    (0, react_1.useEffect)(() => {
        if (bridgeError && globalOnBridgeError) {
            globalOnBridgeError(bridgeError);
        }
    }, [bridgeError]);
    return {
        transaction,
        setTransaction,
        updateTransaction,
        status,
        account,
        parentAccount,
        setAccount,
        bridgeError,
        bridgePending,
    };
};
let globalOnBridgeError = null;
// allows to globally set a bridge error catch function in order to log it / report to sentry / ...
function setGlobalOnBridgeError(f) {
    globalOnBridgeError = f;
}
exports.setGlobalOnBridgeError = setGlobalOnBridgeError;
function getGlobalOnBridgeError() {
    return globalOnBridgeError;
}
exports.getGlobalOnBridgeError = getGlobalOnBridgeError;
exports.default = useBridgeTransaction;
//# sourceMappingURL=useBridgeTransaction.js.map