import { BigNumber } from "bignumber.js";
import { useEffect, useReducer, useCallback, useRef } from "react";
import { log } from "@ledgerhq/logs";
import { getAccountBridge } from ".";
import { getMainAccount } from "../account";
import { delay } from "../promise";
import { LiveConfig } from "@ledgerhq/live-config/LiveConfig";
const initial = {
    account: null,
    parentAccount: null,
    transaction: null,
    status: {
        errors: {},
        warnings: {},
        estimatedFees: new BigNumber(0),
        amount: new BigNumber(0),
        totalSpent: new BigNumber(0),
    },
    statusOnTransaction: null,
    errorAccount: null,
    errorStatus: null,
    syncing: false,
    synced: false,
};
export const shouldSyncBeforeTx = (currency) => {
    const currencyConfig = LiveConfig.getValueByKey(`config_currency_${currency.id}`);
    const sharedConfig = LiveConfig.getValueByKey("config_currency");
    if (currencyConfig && "syncBeforeTx" in currencyConfig) {
        return currencyConfig.syncBeforeTx === true;
    }
    else {
        return sharedConfig && "syncBeforeTx" in sharedConfig && sharedConfig.syncBeforeTx === true;
    }
};
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 = getMainAccount(account, parentAccount);
                const bridge = 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,
                    syncing: false,
                    synced: false,
                };
            }
            catch (e) {
                return {
                    ...initial,
                    account,
                    parentAccount,
                    errorAccount: e,
                    syncing: false,
                    synced: false,
                };
            }
        }
        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 };
        case "onStartSync":
            return { ...state, syncing: true, synced: false };
        case "onSync":
            return { ...state, syncing: false, synced: true };
        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, syncing, synced, errorAccount, errorStatus, }, dispatch,] = useReducer(reducer, undefined, makeInit(optionalInit));
    const setAccount = useCallback((account, parentAccount) => dispatch({
        type: "setAccount",
        account,
        parentAccount,
    }), [dispatch]);
    const setTransaction = useCallback(transaction => dispatch({
        type: "setTransaction",
        transaction,
    }), [dispatch]);
    const updateTransaction = useCallback(updater => dispatch({
        type: "updateTransaction",
        updater,
    }), [dispatch]);
    const mainAccount = account ? getMainAccount(account, parentAccount) : null;
    const errorDelay = useRef(INITIAL_ERROR_RETRY_DELAY);
    const statusIsPending = useRef(false); // Stores if status already being processed
    const shouldSync = mainAccount && shouldSyncBeforeTx(mainAccount.currency);
    useEffect(() => {
        if (mainAccount === null || synced || syncing)
            return;
        if (!shouldSync)
            return; // skip sync if not required by currency config
        dispatch({ type: "onStartSync" });
        const bridge = getAccountBridge(mainAccount, null);
        const sub = bridge.sync(mainAccount, { paginationConfig: {} }).subscribe({
            error: (_) => {
                // we do not block the user in case of error for now but it should be the case
                dispatch({ type: "onSync" });
            },
            complete: () => {
                dispatch({ type: "onSync" });
            },
        });
        return () => {
            sub.unsubscribe();
        };
    }, [mainAccount, synced, syncing, shouldSync]);
    const bridgePending = transaction !== statusOnTransaction;
    // when transaction changes, prepare the transaction
    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 && !synced)
            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 ? delay(DEBOUNCE_STATUS_DELAY) : null;
            statusIsPending.current = true; // consider pending until status is resolved (error or success)
            Promise.resolve(debounce)
                .then(() => 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,
                });
                log("useBridgeTransaction", "prepareTransaction failed " + String(e));
                // After X seconds of hanging in this error case, we try again
                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, synced]);
    const bridgeError = errorAccount || errorStatus;
    useEffect(() => {
        if (bridgeError && globalOnBridgeError) {
            globalOnBridgeError(bridgeError);
        }
    }, [bridgeError]);
    return {
        transaction,
        setTransaction,
        updateTransaction,
        status,
        account,
        parentAccount,
        setAccount,
        bridgeError,
        bridgePending: bridgePending && (shouldSync ? !synced : true),
    };
};
let globalOnBridgeError = null;
// allows to globally set a bridge error catch function in order to log it / report to sentry / ...
export function setGlobalOnBridgeError(f) {
    globalOnBridgeError = f;
}
export function getGlobalOnBridgeError() {
    return globalOnBridgeError;
}
export default useBridgeTransaction;
//# sourceMappingURL=useBridgeTransaction.js.map