"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
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 react_1 = __importStar(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;
};
exports.TxQueueModal = ({ sdk, store, signAndSend, transactionActions, appStateActions, explorerUrl, }) => {
    const { t } = react_i18next_1.useTranslation(definition_1.TranslationNS.QueueModal);
    const [transactions, setTransactions] = react_1.useState(undefined);
    const [isFinalized, setIsFinalized] = react_1.useState(false);
    const [isOpen, setIsOpen] = react_1.useState(false);
    const [canDismiss, setCanDismiss] = react_1.useState(false);
    const [txHistory, setTxHistory] = 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 = 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 = 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
    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
    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]);
    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 (react_1.default.createElement(polymesh_ui_1.Icon, { Asset: polymesh_ui_1.icons.SvgClock, width: 24, height: 24, color: "placeholder" }));
            case types_1.TransactionStatus.Unapproved:
                return (react_1.default.createElement(polymesh_ui_1.Icon, { Asset: polymesh_ui_1.icons.SvgClock, width: 24, height: 24, color: "brandMain" }));
            case types_1.TransactionStatus.Running:
                return (react_1.default.createElement(polymesh_ui_1.Icon, { Asset: polymesh_ui_1.icons.SvgClock, width: 24, height: 24, color: "brandMain" }));
            case types_1.TransactionStatus.Failed:
                return (react_1.default.createElement(polymesh_ui_1.IconOutlined, { Asset: polymesh_ui_1.icons.SvgClose, width: 24, height: 24, scale: 0.75, color: "alert" }));
            case types_1.TransactionStatus.Succeeded:
                return (react_1.default.createElement(polymesh_ui_1.IconOutlined, { Asset: polymesh_ui_1.icons.SvgCheckmark, width: 24, height: 24, scale: 0.75, color: "success" }));
        }
    };
    const getText = ({ id, text, tag, status, }) => {
        const value = t(`${id}.${`${tag ? `${tag.replace(/\./g, '_')}.` : ''}`}${text}.${status}`);
        const fail = (react_1.default.createElement(react_1.default.Fragment, null,
            value,
            failUrl && text === 'status' && (react_1.default.createElement(react_1.default.Fragment, null,
                ' ',
                react_1.default.createElement(polymesh_ui_1.Link, { href: failUrl }, t('General:learnMore'))))));
        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 (react_1.default.createElement(polymesh_ui_1.Box, { key: index, mb: "m" },
            react_1.default.createElement(polymesh_ui_1.Box, { mb: "s" },
                react_1.default.createElement(polymesh_ui_1.Text, { variant: "b2m", color: "gray.2" }, getText({
                    id,
                    text: 'title',
                    tag: txTag,
                    status: tx.status || types_1.TransactionStatus.Idle,
                }))),
            tx.txHash && (react_1.default.createElement(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',
                } })),
            renderStatus && (react_1.default.createElement(polymesh_ui_1.Box, { borderRadius: "3", borderColor: "gray.4", border: "1px solid", borderWidth: "1px", mb: "4", p: "m", width: 456 },
                react_1.default.createElement(polymesh_ui_1.Flex, { flexDirection: 1 },
                    react_1.default.createElement(polymesh_ui_1.Box, null, renderStatusIcon(tx.status || types_1.TransactionStatus.Idle)),
                    react_1.default.createElement(polymesh_ui_1.Box, { mx: "s" },
                        react_1.default.createElement(polymesh_ui_1.Text, { variant: "b2m", color: "gray.2" }, getText({
                            id,
                            text: 'status',
                            tag: txTag,
                            status: tx.status || types_1.TransactionStatus.Idle,
                        }))))))));
    });
    const renderHeader = (id) => (react_1.default.createElement(react_1.default.Fragment, null,
        react_1.default.createElement(polymesh_ui_1.Box, null,
            isOngoing && react_1.default.createElement(polymesh_ui_1.LoadingDots, null),
            !isOngoing && (react_1.default.createElement(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' }))),
        react_1.default.createElement(polymesh_ui_1.Box, { ml: "m" },
            react_1.default.createElement(polymesh_ui_1.Heading, { variant: "h5" }, getText({
                id,
                text: 'heading',
                status: queueStatus.overall,
            })))));
    const toggleDrawer = () => {
        setIsOpen(!isOpen);
    };
    return (react_1.default.createElement(react_1.default.Fragment, null,
        react_1.default.createElement(polymesh_ui_1.Button, { style: {
                position: 'fixed',
                top: 3,
                left: -15,
                padding: '0 0 0 10px',
                width: 50,
                minWidth: 'unset',
                zIndex: 9999,
            }, onClick: toggleDrawer },
            react_1.default.createElement(polymesh_ui_1.Icon, { Asset: polymesh_ui_1.icons.SvgFormatListBulleted, width: 22, height: 22 })),
        react_1.default.createElement(polymesh_ui_1.ModalDrawer, { isCloseable: false, isOpen: isOpen },
            react_1.default.createElement(polymesh_ui_1.Modal.Body, { width: 500, height: "100%" },
                !showMainTx && !txHistory.length && (react_1.default.createElement(polymesh_ui_1.Box, { mt: "xl" },
                    react_1.default.createElement(polymesh_ui_1.Text, { variant: "b1m", color: "gray.1" }, "No transactions"))),
                showMainTx && (react_1.default.createElement(react_1.default.Fragment, null,
                    react_1.default.createElement(polymesh_ui_1.Flex, { my: "l" }, renderHeader(signingPackageId)),
                    renderTransactions(signingPackageId, transactions || []),
                    react_1.default.createElement(polymesh_ui_1.Flex, { justifyContent: "flex-end", mt: "l", mb: "xl" }, (canDismiss || isFinalizedWithFail) && (react_1.default.createElement(polymesh_ui_1.ButtonLarge, { id: "queue_modal_dismiss_button", onClick: onDismiss }, canDismiss && !isFinalized
                        ? 'Finalize in background'
                        : 'Close'))))),
                showMainTx && !!txHistory.length && react_1.default.createElement(polymesh_ui_1.Hr, null),
                !!txHistory.length && (react_1.default.createElement(polymesh_ui_1.CollapsableCard, { variant: "transparent", title: "Earlier Transactions", mt: "xl", defaultOpen: true }, txHistory.map((h, i) => {
                    var _a;
                    return (react_1.default.createElement(polymesh_ui_1.Box, { key: `${h.id}-${i.toString()}`, mb: "50px" }, renderTransactions(h.id, h.apiQueue || ((_a = h.queue) === null || _a === void 0 ? void 0 : _a.transactions) || [], false)));
                })))))));
};
//# sourceMappingURL=TxQueueModal.js.map