import { useCallback, useEffect, useState } from "react";

import { useStatusErrors } from "./useStatusErrors.js";
import { fetchStatus } from "../queue/fetchStatus.js";
import { deleteJobs } from "../queue/job/deleteJobs.js";
import { JOBS_DELETED_EVENT } from "../types/events/jobsDeleted.js";
import { STATUS_EVENT } from "../types/events/status.js";
import { STATUS_ADDITIONAL_DATA_EVENT } from "../types/events/statusAdditionalData.js";

import type { JobsDeletedEvent } from "../types/events/jobsDeleted.js";
import type { StatusEvent } from "../types/events/status.js";
import type { StatusAdditionalData } from "../types/events/statusAdditionalData.js";
import type { Job } from "../types/job.js";

function useProgress<JobData = any, AdditionalData extends Record<string, any> = object>({
    type,
    fetchStatusInterval,
    fetchAdditionalData = false,
    onAdditionalData,
    onCancel,
}: {
    type: string;
    fetchStatusInterval?: number;
    fetchAdditionalData?: false | string;
    onAdditionalData?: (data: AdditionalData) => void;
    onCancel?: () => void;
}) {
    const [cancelling, setCancelling] = useState(false);
    const [jobsStarted, setJobsStarted] = useState(false);
    const [remaining, setRemaining] = useState<number>();
    const [percent, setPercent] = useState<number>();
    const [total, setTotal] = useState<number>();
    const [failure, setFailure] = useState<number>();
    const [paused, setPaused] = useState<number>();
    const [currentJob, setCurrentJob] = useState<Job<JobData>>();
    const {
        errors: { [type]: errors },
    } = useStatusErrors();

    // Determine status
    let status: "not-started" | "done" | "failed" = "not-started";
    if (jobsStarted) {
        if (errors) {
            status = "failed";
        } else if (remaining === 0 && !failure) {
            status = "done";
        }
    }

    // Listen to status updates
    const listener = useCallback(
        (async ({
            detail: {
                currentJobs,
                remaining: { [type]: remainingArr },
                additionalData,
            },
        }: CustomEvent<StatusEvent<AdditionalData>>) => {
            if (remainingArr) {
                const { remaining, total, failure, paused } = remainingArr;
                const percent = +(((total - remaining) / total) * 100).toFixed(0);

                setCurrentJob(currentJobs[type]);
                setRemaining(remaining);
                setTotal(total);
                setFailure(failure);
                setPaused(paused);
                setPercent(percent > 0 ? percent : 1);
            } else {
                setCurrentJob(undefined);
                setRemaining(0);
                setTotal(0);
                setFailure(0);
                setPaused(0);
                setPercent(100);
            }

            // Always handle the received response, too
            if (fetchAdditionalData !== false && additionalData[fetchAdditionalData]) {
                onAdditionalData(additionalData);
            }
        }) as any,
        [type, onAdditionalData, fetchAdditionalData],
    );

    // Listen to all status updated
    useEffect(() => {
        document.addEventListener(STATUS_EVENT, listener);
        return () => {
            document.removeEventListener(STATUS_EVENT, listener);
        };
    }, [listener]);

    useEffect(() => {
        if (remaining > 0) {
            setJobsStarted(true);
        }
    }, [remaining]);

    // Let the status updater know that we want to fetch additional data
    useEffect(() => {
        if (fetchAdditionalData) {
            const listener = (async ({
                detail: {
                    settings: { additionalData },
                },
            }: CustomEvent<StatusAdditionalData>) => {
                additionalData.push(fetchAdditionalData);
            }) as any;

            document.addEventListener(STATUS_ADDITIONAL_DATA_EVENT, listener);
            return () => {
                document.removeEventListener(STATUS_ADDITIONAL_DATA_EVENT, listener);
            };
        }

        return () => {
            // Silence is golden.
        };
    }, [fetchAdditionalData]);

    const applyCancelState = useCallback(() => {
        setJobsStarted(false);
        setCurrentJob(undefined);
        setRemaining(0);
        setTotal(0);
        setFailure(0);
        setPaused(0);
        setPercent(100);
        onCancel?.();
    }, [onCancel]);

    // Listen to manual deletion of tasks in the error modal
    useEffect(() => {
        const listenerDeleted = (async ({
            detail: {
                params: { type: deletedType },
            },
        }: CustomEvent<JobsDeletedEvent>) => {
            if (deletedType === type) {
                applyCancelState();
            }
        }) as any;

        document.addEventListener(JOBS_DELETED_EVENT, listenerDeleted);

        return () => {
            document.removeEventListener(JOBS_DELETED_EVENT, listenerDeleted);
        };
    }, [listener, applyCancelState]);

    const handleCancel = useCallback(async () => {
        applyCancelState();
        setCancelling(true);
        await deleteJobs({
            type,
        });
        setCancelling(false);
    }, [type, applyCancelState]);

    // Modify the status updater
    useEffect(() => {
        if (fetchStatusInterval > 0) {
            fetchStatus(fetchStatusInterval, true);
        }

        return () => {
            fetchStatus();
        };
    }, [fetchStatusInterval]);

    return {
        errors,
        status,
        jobsStarted,
        remaining,
        percent,
        total,
        failure,
        paused,
        currentJob,
        handleCancel,
        cancelling,
    };
}

export { useProgress };
