"use client";

import { useCallback, useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import useQueue from "@/hooks/useQueue";
import {
  addSuccess,
  addUploading,
  clearResult,
  selectResult,
  selectUploading,
  setFailed,
  setUploading,
  updateProgressById,
  updateUploadingById,
} from "@/utils/store/features/data/upload.slice";
import { v4 as uuid } from "uuid";
import axios from "axios";
import {
  getImageDimension,
  isEmptyObject,
  spliceObject,
} from "@/app/library/library";

const useUploadFile = ({ url }, onSuccess = null, onError = null) => {
  const dispatch = useDispatch();
  const uploadingData = useSelector(selectUploading);
  const resultUpload = useSelector(selectResult);
  const [result, setResult] = useState({
    success: [],
    failed: [],
    isFinished: false,
  });
  const [tryCount, setTryCount] = useState(0);
  const [beingUpload, setBeingUpload] = useState(false);
  const { setQueue, completed } = useQueue();
  const controller = useMemo(() => new AbortController(), []);

  // clean up state data every trying uploading data
  const cleanup = useCallback(() => {
    setResult({
      success: [],
      failed: [],
      isFinished: false,
    });
  }, []);

  // can call on finished upload
  const getResult = () => {
    const result = resultUpload;
    dispatch(clearResult());

    return result;
  };

  // for state updating, include update on display data
  const handleUploading = useCallback(
    (onUploading, uploadedData) => {
      const term = ["tempId", "progress", "error", "success"];
      const isFinished = !!onUploading;
      const status = !!uploadedData?.error ? "failed" : "success";

      const reportData = {
        ...onUploading,
        ...uploadedData,
        progress: isFinished ? 100 : onUploading.progress,
        error: !!uploadedData?.error,
        success: isFinished && !uploadedData?.error,
        status,
      };
      const currentData = spliceObject(reportData, term).spliced;

      // update progress
      dispatch(updateUploadingById(reportData));

      // update result
      setResult((prev) => ({
        ...prev,
        [status]: [...prev[status], currentData],
      }));
    },
    [dispatch]
  );

  const handleUpload = useCallback(
    async (file, allFiles) => {
      if (!file) return;
      let resultData = null;

      try {
        const response = await axios.post(url, file.data, {
          signal: controller.signal, // for abort upload
          onUploadProgress: (progressEvent) => {
            const percentCompleted = Math.round(
              (progressEvent.loaded * 50) / progressEvent.total
            );

            dispatch(
              updateProgressById({
                tempId: file.tempId,
                progress: percentCompleted,
              })
            );
          },
        });

        resultData = response.data;
        if (onSuccess && typeof onSuccess === "function") onSuccess(resultData);
        return resultData; // must return data for validate on queue
      } catch (error) {
        resultData = {
          error: error?.message || "Something went wrong",
          tempId: file.tempId,
        };

        if (onError && typeof onError === "function") onError(resultData);

        return resultData;
      } finally {
        const metaDisplay = allFiles.map((item) => ({
          ...item.meta,
          raw: item.raw, // original data for try again
        }));
        const onUploading = metaDisplay.find(
          (item) => item.tempId === resultData.tempId
        );

        // handle uploading data
        handleUploading(onUploading, resultData);
      }
    },
    [url, controller.signal, dispatch, handleUploading, onSuccess, onError]
  );

  // handle file add to queue
  const addFile = useCallback(
    async (file) => {
      try {
        setBeingUpload(true);
        if (file.length === 0 || !file) return;
        cleanup(); // clean up first
        dispatch(setUploading([]));

        const fileData = await Promise.all(
          file.map(async (item) => {
            const formData = new FormData();
            const tempId = uuid();
            const { width, height } = await getImageDimension(item);

            formData.append("file", item);
            formData.append(
              "title",
              item.name.split(".").slice(0, -1).join(".")
            );
            formData.append("filename", item?.name);
            formData.append("tempId", tempId);

            const itemDisplay = {
              uid: item?.uid,
              filename: item.name,
              url: URL.createObjectURL(item),
              dimension: { width, height },
              tempId,
              progress: 0,
              error: false,
              success: false,
            };

            dispatch(addUploading(itemDisplay));

            // meta: on display item data with progress
            // raw: original data
            // data: data for upload
            return { tempId, data: formData, meta: itemDisplay, raw: item };
          })
        );

        if (file.length > 0) {
          setQueue(fileData, handleUpload);
          setTryCount((prev) => prev + 1);
        }
      } catch (error) {
        console.log(error);
      }
    },
    [dispatch, handleUpload, setQueue, cleanup]
  );

  const finishing = useCallback(() => {
    const allData = [...result.success, ...result.failed];
    if (allData.length !== uploadingData.length || !uploadingData.length)
      return;

    const tempFailedData = result.failed?.map(
      (item) => spliceObject(item, ["raw"]).spliced
    );
    dispatch(setFailed(tempFailedData));
    dispatch(addSuccess(result.success));

    // clean up state
    cleanup();

    if (tryCount === 4 || !result.failed.length) {
      dispatch(setFailed(result.failed));
      setTryCount(0);

      if (!result.failed.length) dispatch(setUploading([]));
      setBeingUpload(false);
      return setResult((prev) => ({ ...prev, isFinished: true }));
    }

    // try again only for 3 times
    // set fileUploads to try again
    if (result.failed.length > 0 && tryCount < 4) {
      addFile(result.failed?.map((item) => item.raw));
    }
  }, [result, uploadingData, tryCount, addFile, dispatch, cleanup]);

  useEffect(() => {
    if (completed?.status) {
      finishing();
    }
  }, [completed, finishing]);

  const retry = useCallback(
    (data) => {
      if (!data || isEmptyObject(data)) return;
      setTryCount(0);

      const rawData = data.raw;
      const filterdUpload = uploadingData.filter(
        (item) => item.tempId !== data.tempId
      );

      dispatch(setUploading(filterdUpload));
      addFile([rawData]);
    },
    [uploadingData, addFile, dispatch]
  );

  const retryAll = (data) => {
    if (!data || !data.length) return;
    setTryCount(0);
    dispatch(setUploading([]));

    addFile(data);
  };

  return {
    addFile,
    status: result.isFinished,
    getResult,
    retry,
    retryAll,
    isRunning: beingUpload,
  };
};

export default useUploadFile;
