import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useContext, useEffect, useRef, useState } from 'react';
import { Subscription } from 'rxjs';
import styled from '@darajs/styled-components';
import { ProgressBar } from '@darajs/ui-components';
import {} from '../api/websocket';
import { useTaskContext } from '../shared/context/global-task-context';
import websocketCtx from '../shared/context/websocket-context';
import {} from '../types/core';
const POLLING_INTERVAL = 100;
const FAKE_PROGRESS_INTERVAL = 100;
const ESTIMATE_RATIO = 0.85;
/**
 * Progress estimator function - roughly `y = 1 - exp(x)`.
 *
 * @param time time in milliseconds
 * @param timeConstant seconds to reach 63%, then 86%, then 95%, then 98%
 */
function estimateProgress(time, timeConstant = 10) {
    // This reaches in 63% in 10s, 86% in 20s
    return 1 - Math.exp((-1 * time) / (FAKE_PROGRESS_INTERVAL * 10 * timeConstant));
}
/**
 * Build a generator which will return fake progress
 *
 * @param progressStart starting point
 * @param progressEnd ending point
 * @param estimatedTime estimated time the faking should take; if specified, the fake progress
 *  will use 'real'/estimated values for 60% of the estimated time before changing to a fake algo
 */
function fakeProgressGenerator(progressStart, progressEnd, estimatedTime) {
    const difference = progressEnd - progressStart;
    const estimatedProgressUpdates = [];
    let timeConstant = 10;
    // If estimated time is set, fake progress will start with estimated updates
    if (estimatedTime && estimatedTime !== 0) {
        const estimatedDifference = ESTIMATE_RATIO * difference;
        const estimatedProgressTime = ESTIMATE_RATIO * estimatedTime;
        const numberOfUpdates = estimatedProgressTime / FAKE_PROGRESS_INTERVAL;
        const estimatedProgressChunk = estimatedDifference / numberOfUpdates;
        let t = 0;
        let p = progressStart;
        // Compute list of 'estimated' (estimated) progress updates
        while (t < estimatedProgressTime) {
            p += estimatedProgressChunk;
            t += FAKE_PROGRESS_INTERVAL;
            estimatedProgressUpdates.push(p);
        }
        // Set time constant (time to reach 63% of the target) to the remaining chunk of estimated time
        timeConstant = (1 - ESTIMATE_RATIO) * (estimatedTime / 1000);
    }
    // Build internal generator which returns estimated updates, then fake updates
    function* getNextUpdate() {
        let startFrom = progressStart;
        // First we return estimated updates
        while (estimatedProgressUpdates.length > 0) {
            const update = estimatedProgressUpdates.shift();
            startFrom = update;
            yield update;
        }
        let i = 0;
        // Then keep returning fake updates based on the estimate function
        while (true) {
            // Estimated is in range 0-1
            const estimated = estimateProgress(FAKE_PROGRESS_INTERVAL * i, timeConstant);
            // Rescale estimated to range of startFrom->progressEnd
            yield estimated * (progressEnd - startFrom) + startFrom;
            i++;
        }
    }
    return getNextUpdate();
}
const ProgressWrapper = styled.div `
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 1rem;
`;
const ProgressHeader = styled.h4 `
    font-size: 1.2rem;
`;
const ProgressMessage = styled.span `
    font-size: ${(props) => props.theme.font.size};
`;
/**
 * Retrieve a list of running tasks if there are any
 *
 * @param tasksContext
 * @param variablesRef
 */
function findRunningTasks(tasksContext, variablesRef) {
    var _a;
    // check if there are variables the component is subscribed to, and there are tasks running
    if (!(variablesRef && ((_a = variablesRef === null || variablesRef === void 0 ? void 0 : variablesRef.current) === null || _a === void 0 ? void 0 : _a.size) > 0 && tasksContext.hasRunningTasks())) {
        return [];
    }
    return tasksContext.getVariableTasks(...variablesRef.current.values());
}
/**
 * ProgressTracker component can be used as a placeholder to replace the standard loading spinner.
 * If a task is running, data is captured from the task function and a ProgressBar with live updates in shown.
 */
function ProgressTracker(props) {
    const taskContext = useTaskContext();
    const { client: wsClient } = useContext(websocketCtx);
    const [latestProgressUpdate, setLatestProgressUpdate] = useState(null); // latest progress message
    const [progress, setProgress] = useState(null);
    const fakeInterval = useRef(null);
    const [subscribedTaskIds, setSubscribedTaskIds] = useState([]);
    const [restartTrigger, setRestartTrigger] = useState(0); // used to trigger a restart of polling for a running task
    useEffect(() => {
        if (!wsClient) {
            return;
        }
        let progressSubscription = null;
        // Start polling and looking for running tasks
        const timer = setInterval(() => {
            const taskIds = findRunningTasks(taskContext, props.variablesRef);
            // Once tasks are found, subscribe to their progress updates and stop polling
            // This handles a component having multiple tasks running - it's a limitation that only one of them
            // can be tracked; this is only an issue if a component calls useVariable for multiple inputs
            if (taskIds.length > 0) {
                setSubscribedTaskIds(taskIds);
                progressSubscription = wsClient.progressUpdates$(...taskIds).subscribe((notif) => {
                    setLatestProgressUpdate(notif.message);
                });
                clearInterval(timer);
            }
        }, POLLING_INTERVAL);
        return () => {
            // Clean up intervals and subscriptions
            clearInterval(timer);
            if (fakeInterval.current) {
                clearInterval(fakeInterval.current);
            }
            progressSubscription === null || progressSubscription === void 0 ? void 0 : progressSubscription.unsubscribe();
        };
    }, [restartTrigger, wsClient]);
    /**
     * Handle cancellations of subscribed task ids.
     */
    useEffect(() => {
        if (!wsClient) {
            return;
        }
        let subscription = null;
        if (subscribedTaskIds.length > 0) {
            subscription = wsClient.taskStatusUpdates$(...subscribedTaskIds).subscribe((newStatus) => {
                if (newStatus === 'CANCELED') {
                    // Reset component
                    if (fakeInterval.current) {
                        clearInterval(fakeInterval.current);
                    }
                    setLatestProgressUpdate(null);
                    setSubscribedTaskIds([]);
                    setProgress(null);
                    // Restart polling
                    setRestartTrigger((v) => v + 1);
                }
            });
        }
        return () => {
            subscription === null || subscription === void 0 ? void 0 : subscription.unsubscribe();
        };
    }, [subscribedTaskIds, wsClient]);
    /**
     * Handle progress updates.
     * This is in a chained effect rather than in the subscription handler since it needs
     * access to the latest state.
     */
    useEffect(() => {
        var _a, _b;
        if (!latestProgressUpdate) {
            return;
        }
        // Clear faking process interval if its set
        if (fakeInterval.current) {
            clearInterval(fakeInterval.current);
        }
        // whether we received a message to start fake mode
        const shouldStartFakeProgress = latestProgressUpdate.message.startsWith('FAKE_PROGRESS__');
        if (!shouldStartFakeProgress) {
            // If not, just update progress
            setProgress({
                message: latestProgressUpdate.message,
                progress: latestProgressUpdate.progress,
            });
            return;
        }
        // Start fake mode and update message to the sent message
        const [, estimatedTime, message] = latestProgressUpdate.message.split('__');
        setProgress({
            message: message,
            progress: (_a = progress === null || progress === void 0 ? void 0 : progress.progress) !== null && _a !== void 0 ? _a : 0,
        });
        const progressGenerator = fakeProgressGenerator((_b = progress === null || progress === void 0 ? void 0 : progress.progress) !== null && _b !== void 0 ? _b : 0, latestProgressUpdate.progress, parseFloat(estimatedTime));
        fakeInterval.current = setInterval(() => {
            const nextValue = progressGenerator.next().value;
            setProgress({
                message: message,
                progress: nextValue,
            });
        }, FAKE_PROGRESS_INTERVAL);
    }, [latestProgressUpdate]);
    // If no task is running, just show a standard loading bar
    if (subscribedTaskIds.length === 0) {
        return props.fallback;
    }
    return (_jsxs(ProgressWrapper, { children: [_jsx(ProgressHeader, { children: "Task in progress" }), progress && (_jsxs(_Fragment, { children: [_jsx(ProgressMessage, { children: progress.message }), _jsx(ProgressBar, { progress: parseFloat(progress.progress.toFixed(2)) })] }))] }));
}
export default ProgressTracker;
//# sourceMappingURL=progress-tracker.js.map