"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TxQueueModal = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const polymesh_ui_1 = require("@polymathnetwork/polymesh-ui");
const types_1 = require("@polymathnetwork/polymesh-sdk/types");
const react_i18next_1 = require("react-i18next");
const definition_1 = require("./definition");
// A store of data that was harvested from the previous tx events.
let previousTx = {};
const getTxTag = (tx) => tx.tag || `${tx.method.section}.${tx.method.method}`;
const getQueueStatus = (queue = []) => {
    const status = {
        total: queue.length,
        state: {},
        overall: types_1.TransactionStatus.Idle,
    };
    queue.forEach((q) => {
        if (status.state[q.status])
            status.state[q.status] += 1;
        else
            status.state[q.status] = 1;
    });
    let overall = types_1.TransactionStatus.Idle;
    if ((status.state[types_1.TransactionStatus.Idle] &&
        status.state[types_1.TransactionStatus.Idle] > 0) ||
        (status.state[types_1.TransactionStatus.Unapproved] &&
            status.state[types_1.TransactionStatus.Unapproved] > 0) ||
        (status.state[types_1.TransactionStatus.Running] &&
            status.state[types_1.TransactionStatus.Running] > 0)) {
        overall = types_1.TransactionStatus.Running;
    }
    else if (status.state[types_1.TransactionStatus.Succeeded] &&
        status.state[types_1.TransactionStatus.Succeeded] === status.total) {
        overall = types_1.TransactionStatus.Succeeded;
    }
    else if (status.state[types_1.TransactionStatus.Failed]) {
        overall = types_1.TransactionStatus.Failed;
    }
    status.overall = overall;
    return status;
};
const TxQueueModal = ({ sdk, store, signAndSend, transactionActions, appStateActions, explorerUrl, }) => {
    const { t } = (0, react_i18next_1.useTranslation)(definition_1.TranslationNS.QueueModal);
    const [transactions, setTransactions] = (0, react_1.useState)(undefined);
    const [isFinalized, setIsFinalized] = (0, react_1.useState)(false);
    const [isOpen, setIsOpen] = (0, react_1.useState)(false);
    const [canDismiss, setCanDismiss] = (0, react_1.useState)(false);
    const [txHistory, setTxHistory] = (0, react_1.useState)([]);
    const { dispatch, getState } = store;
    const { transaction: { signingPackage }, polymesh: { inFlight }, } = getState();
    const { id: signingPackageId, queue, apiQueue, confirmationStatus, onCompleted, onDismissed, failUrl, } = signingPackage || {};
    const targetQueue = apiQueue || (queue === null || queue === void 0 ? void 0 : queue.transactions) || [];
    const queueStatus = getQueueStatus(transactions);
    const isOngoing = !isFinalized && queueStatus.overall === types_1.TransactionStatus.Running;
    const hasFailed = !!queueStatus.state[types_1.TransactionStatus.Failed];
    const isFinalizedWithFail = isFinalized && hasFailed;
    const showMainTx = isOngoing || hasFailed;
    const clearModalState = () => {
        setIsFinalized(false);
        setCanDismiss(false);
        setTransactions(undefined);
        dispatch(transactionActions.clearTransactions());
    };
    const reset = () => {
        if (signingPackage.apiQueue) {
            signingPackage.apiQueue = signingPackage.apiQueue.filter((e) => e.status &&
                [types_1.TransactionStatus.Succeeded, types_1.TransactionStatus.Failed].includes(e.status));
        }
        setTxHistory([signingPackage, ...txHistory]);
        setTimeout(() => {
            clearModalState();
        }, 500);
    };
    const onDone = () => {
        setIsOpen(false);
        const result = types_1.TransactionStatus.Succeeded;
        dispatch(transactionActions.transactionsCompleted({
            signingPackageId,
            queue,
            apiQueue,
            result,
        }));
        dispatch(transactionActions.setConfirmationStatus(definition_1.ConfirmationStatus.None));
        if (inFlight)
            dispatch(appStateActions.setInFlight(false));
        if (onCompleted)
            onCompleted();
        reset();
    };
    const onDismiss = () => {
        setIsOpen(false);
        if (onDismissed)
            onDismissed();
        if (isFinalizedWithFail)
            reset();
    };
    const onTransactionStatusChange = (tx, txQueueObj) => {
        setTransactions([...txQueueObj.transactions]);
    };
    const onProcessedByMiddleware = (0, react_1.useCallback)((error) => {
        dispatch(transactionActions.queueProcessedByMiddleware({
            signingPackageId,
            queue,
            apiQueue,
            error,
        }));
    }, [transactionActions, signingPackageId, queue, apiQueue, dispatch]);
    const onQueueStatusChange = (newTxQueue) => {
        if (newTxQueue.status === types_1.TransactionQueueStatus.Succeeded ||
            newTxQueue.status === types_1.TransactionQueueStatus.Failed) {
            setIsFinalized(true);
        }
        if (newTxQueue.status === types_1.TransactionQueueStatus.Succeeded) {
            onDone();
        }
    };
    const processApiQueue = (0, react_1.useCallback)(() => {
        let currentTxIndex = 0;
        const setCurrentTxState = (state) => {
            signingPackage.apiQueue[currentTxIndex] = Object.assign(Object.assign({}, signingPackage.apiQueue[currentTxIndex]), state);
            const txs = transactions || [];
            const tempQueue = [...txs];
            tempQueue.splice(currentTxIndex, 1, Object.assign(Object.assign({}, txs[currentTxIndex]), state));
            setTransactions([...tempQueue]);
        };
        const processCurrentTx = () => __awaiter(void 0, void 0, void 0, function* () {
            const txs = transactions || [];
            const tx = txs[currentTxIndex];
            try {
                setCurrentTxState({ status: types_1.TransactionStatus.Running });
                // Submit the transaction
                if (tx.type === definition_1.ApiQueueItemType.Batch) {
                    const batchParams = tx.params.map((param) => {
                        if (param.method !== undefined &&
                            typeof param.method === 'function' &&
                            param.params !== undefined) {
                            const methodParams = param.params.map((p) => {
                                if (typeof p === 'function') {
                                    return p(previousTx);
                                }
                                return p;
                            });
                            return param.method(...methodParams);
                        }
                        return null;
                    });
                    tx.params = [batchParams];
                }
                else {
                    tx.params = tx.params.map((param) => {
                        // this param is actually a function that reads the value of a param
                        // from previous tx store.
                        if (typeof param === 'function') {
                            return param(previousTx);
                        }
                        return param;
                    });
                }
                const [status, isError, data] = yield signAndSend(sdk, tx.method, tx.params, tx.dataResolver);
                previousTx = Object.assign(Object.assign({}, previousTx), data);
                setCurrentTxState({
                    status: isError
                        ? types_1.TransactionStatus.Failed
                        : types_1.TransactionStatus.Succeeded,
                    txHash: status.hash.toString(),
                });
                if (isError && tx.blocking) {
                    setIsFinalized(true);
                }
                else if (currentTxIndex < txs.length - 1) {
                    currentTxIndex += 1;
                    processCurrentTx();
                }
                else {
                    setIsFinalized(true);
                    if (!hasFailed)
                        onDone();
                }
            }
            catch (e) {
                if (e.message === 'Cancelled') {
                    setCurrentTxState({ status: types_1.TransactionStatus.Rejected });
                }
                else {
                    setCurrentTxState({ status: types_1.TransactionStatus.Failed });
                }
            }
        });
        processCurrentTx();
    }, [hasFailed, sdk, signAndSend, transactions]); // eslint-disable-line
    // Kick-off on confirmation
    (0, react_1.useEffect)(() => {
        const runQueue = () => __awaiter(void 0, void 0, void 0, function* () {
            setTransactions(targetQueue);
            if (queue) {
                try {
                    queue.onTransactionStatusChange(onTransactionStatusChange);
                    queue.onStatusChange(onQueueStatusChange);
                    queue.onProcessedByMiddleware(onProcessedByMiddleware);
                    yield queue.run();
                }
                catch (e) { } // eslint-disable-line no-empty
            }
        });
        if (confirmationStatus === definition_1.ConfirmationStatus.Done) {
            runQueue();
            setIsOpen(true);
        }
    }, [confirmationStatus]); // eslint-disable-line
    // Kick-off API queue processing after transactions are set
    (0, react_1.useEffect)(() => {
        var _a;
        if (apiQueue &&
            transactions &&
            ((_a = transactions[0]) === null || _a === void 0 ? void 0 : _a.status) === types_1.TransactionStatus.Idle &&
            !isFinalized) {
            processApiQueue();
        }
    }, [transactions, isFinalized, processApiQueue, apiQueue]);
    (0, react_1.useEffect)(() => {
        if (transactions) {
            const doneTxs = transactions.filter((tx) => !!tx.txHash);
            setCanDismiss(!!doneTxs.length);
        }
    }, [transactions]); // eslint-disable-line
    const renderStatusIcon = (status) => {
        switch (status) {
            case types_1.TransactionStatus.Idle:
                return ((0, jsx_runtime_1.jsx)(polymesh_ui_1.Icon, { Asset: polymesh_ui_1.icons.SvgClock, width: 24, height: 24, color: "placeholder" }, void 0));
            case types_1.TransactionStatus.Unapproved:
                return ((0, jsx_runtime_1.jsx)(polymesh_ui_1.Icon, { Asset: polymesh_ui_1.icons.SvgClock, width: 24, height: 24, color: "brandMain" }, void 0));
            case types_1.TransactionStatus.Running:
                return ((0, jsx_runtime_1.jsx)(polymesh_ui_1.Icon, { Asset: polymesh_ui_1.icons.SvgClock, width: 24, height: 24, color: "brandMain" }, void 0));
            case types_1.TransactionStatus.Failed:
                return ((0, jsx_runtime_1.jsx)(polymesh_ui_1.IconOutlined, { Asset: polymesh_ui_1.icons.SvgClose, width: 24, height: 24, scale: 0.75, color: "alert" }, void 0));
            case types_1.TransactionStatus.Succeeded:
                return ((0, jsx_runtime_1.jsx)(polymesh_ui_1.IconOutlined, { Asset: polymesh_ui_1.icons.SvgCheckmark, width: 24, height: 24, scale: 0.75, color: "success" }, void 0));
        }
    };
    const getText = ({ id, text, tag, status, }) => {
        const value = t(`${id}.${`${tag ? `${tag.replace(/\./g, '_')}.` : ''}`}${text}.${status}`);
        const fail = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [value, failUrl && text === 'status' && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [' ', (0, jsx_runtime_1.jsx)(polymesh_ui_1.Link, Object.assign({ href: failUrl }, { children: t('General:learnMore') }), void 0)] }, void 0))] }, void 0));
        if (status === types_1.TransactionStatus.Failed)
            return fail;
        return value;
    };
    const renderTransactions = (id, txs, renderStatus = true) => txs === null || txs === void 0 ? void 0 : txs.map((tx, index) => {
        const txTag = tx.status && tx.status === types_1.TransactionStatus.Failed
            ? 'General'
            : getTxTag(tx);
        return ((0, jsx_runtime_1.jsxs)(polymesh_ui_1.Box, Object.assign({ mb: "m" }, { children: [(0, jsx_runtime_1.jsx)(polymesh_ui_1.Box, Object.assign({ mb: "s" }, { children: (0, jsx_runtime_1.jsx)(polymesh_ui_1.Text, Object.assign({ variant: "b2m", color: "gray.2" }, { children: getText({
                            id,
                            text: 'title',
                            tag: txTag,
                            status: tx.status || types_1.TransactionStatus.Idle,
                        }) }), void 0) }), void 0), tx.txHash && ((0, jsx_runtime_1.jsx)(polymesh_ui_1.TransactionHash, { hash: tx.txHash, link: `${explorerUrl}/transaction/${tx.txHash}`, copyLink: true, size: 55, flexProps: {
                        flexDirection: 'row',
                        justifyContent: 'flex-start',
                        mb: 'm',
                    }, buttonProps: {
                        copyId: 'queue_modal_txhash_copy_button',
                        linkId: 'queue_modal_txhash_link_button',
                    } }, void 0)), renderStatus && ((0, jsx_runtime_1.jsx)(polymesh_ui_1.Box, Object.assign({ borderRadius: "3", borderColor: "gray.4", border: "1px solid", borderWidth: "1px", mb: "4", p: "m", width: 456 }, { children: (0, jsx_runtime_1.jsxs)(polymesh_ui_1.Flex, Object.assign({ flexDirection: 1 }, { children: [(0, jsx_runtime_1.jsx)(polymesh_ui_1.Box, { children: renderStatusIcon(tx.status || types_1.TransactionStatus.Idle) }, void 0), (0, jsx_runtime_1.jsx)(polymesh_ui_1.Box, Object.assign({ mx: "s" }, { children: (0, jsx_runtime_1.jsx)(polymesh_ui_1.Text, Object.assign({ variant: "b2m", color: "gray.2" }, { children: getText({
                                        id,
                                        text: 'status',
                                        tag: txTag,
                                        status: tx.status || types_1.TransactionStatus.Idle,
                                    }) }), void 0) }), void 0)] }), void 0) }), void 0))] }), `txTag${index.toString()}`));
    });
    const renderHeader = (id) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(polymesh_ui_1.Box, { children: [isOngoing && (0, jsx_runtime_1.jsx)(polymesh_ui_1.LoadingDots, {}, void 0), !isOngoing && ((0, jsx_runtime_1.jsx)(polymesh_ui_1.IconCircled, { Asset: hasFailed ? polymesh_ui_1.icons.SvgClose : polymesh_ui_1.icons.SvgCheckmark, width: 48, height: 48, scale: 0.75, bg: hasFailed ? 'red.1' : 'green.2', color: hasFailed ? 'alert' : 'success' }, void 0))] }, void 0), (0, jsx_runtime_1.jsx)(polymesh_ui_1.Box, Object.assign({ ml: "m" }, { children: (0, jsx_runtime_1.jsx)(polymesh_ui_1.Heading, Object.assign({ variant: "h5" }, { children: getText({
                        id,
                        text: 'heading',
                        status: queueStatus.overall,
                    }) }), void 0) }), void 0)] }, void 0));
    const toggleDrawer = () => {
        setIsOpen(!isOpen);
    };
    return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(polymesh_ui_1.Button, Object.assign({ style: {
                    position: 'fixed',
                    top: 3,
                    left: -15,
                    padding: '0 0 0 10px',
                    width: 50,
                    minWidth: 'unset',
                    zIndex: 9999,
                }, onClick: toggleDrawer }, { children: (0, jsx_runtime_1.jsx)(polymesh_ui_1.Icon, { Asset: polymesh_ui_1.icons.SvgFormatListBulleted, width: 22, height: 22 }, void 0) }), void 0), (0, jsx_runtime_1.jsx)(polymesh_ui_1.ModalDrawer, Object.assign({ isCloseable: false, isOpen: isOpen }, { children: (0, jsx_runtime_1.jsxs)(polymesh_ui_1.Modal.Body, Object.assign({ width: 500, height: "100%" }, { children: [!showMainTx && !txHistory.length && ((0, jsx_runtime_1.jsx)(polymesh_ui_1.Box, Object.assign({ mt: "xl" }, { children: (0, jsx_runtime_1.jsx)(polymesh_ui_1.Text, Object.assign({ variant: "b1m", color: "gray.1" }, { children: "No transactions" }), void 0) }), void 0)), showMainTx && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(polymesh_ui_1.Flex, Object.assign({ my: "l" }, { children: renderHeader(signingPackageId) }), void 0), renderTransactions(signingPackageId, transactions || []), (0, jsx_runtime_1.jsx)(polymesh_ui_1.Flex, Object.assign({ justifyContent: "flex-end", mt: "l", mb: "xl" }, { children: (canDismiss || isFinalizedWithFail) && ((0, jsx_runtime_1.jsx)(polymesh_ui_1.ButtonLarge, Object.assign({ id: "queue_modal_dismiss_button", onClick: onDismiss }, { children: canDismiss && !isFinalized
                                            ? 'Finalize in background'
                                            : 'Close' }), void 0)) }), void 0)] }, void 0)), showMainTx && !!txHistory.length && (0, jsx_runtime_1.jsx)(polymesh_ui_1.Hr, {}, void 0), !!txHistory.length && ((0, jsx_runtime_1.jsx)(polymesh_ui_1.CollapsableCard, Object.assign({ variant: "transparent", title: "Earlier Transactions", mt: "xl", defaultOpen: true }, { children: txHistory.map((h, i) => {
                                var _a;
                                return ((0, jsx_runtime_1.jsx)(polymesh_ui_1.Box, Object.assign({ mb: "50px" }, { children: renderTransactions(h.id, h.apiQueue || ((_a = h.queue) === null || _a === void 0 ? void 0 : _a.transactions) || [], false) }), `${h.id}-${i.toString()}`));
                            }) }), void 0))] }), void 0) }), void 0)] }, void 0));
};
exports.TxQueueModal = TxQueueModal;
//# sourceMappingURL=TxQueueModal.js.map