import { throttle } from "lodash";
import { useCallback, useRef, useState } from "react";

export enum PRELOADER_STATUS {
  ERROR = "error",
  LOADING = "loading",
  WAITING = "waiting",
  DONE = "done",
  ABORT = "abort",
}

export const getMediaType = (url: string) => {
  const MEDIA_TYPES: { [key: string]: string } = {
    mp4: "video/mp4",
    webm: "video/webm",
    ogg: "video/ogg",
    mp3: "audio/mp3",
    wav: "audio/wav",
  };
  const urlExtension = url.split(".").pop()?.toLowerCase() || "";

  if (MEDIA_TYPES.hasOwnProperty(urlExtension)) {
    return MEDIA_TYPES[urlExtension];
  } else {
    return null;
  }
};

const parseUrlName = (url: string) => {
  const regex = /\/([^/]+)\.\w+$/;
  const match = url.match(regex);
  if (match && match.length >= 2) {
    return match[1].split(".")[0];
  } else {
    return "";
  }
};

export function usePreloader(params: {
  list: string[];
  /**
   * 百分比保留几位小数
   */
  decimals?: number;
  success?: Function;
  error?: Function;
  /**
   * 尝试重连次数
   * @default 3
   */
  retry?: number;
}) {
  const decimals = params.decimals ? params.decimals * 10 : 1;
  const fetchControllerStack = useRef<AbortController[]>([]);
  const _list = useRef(params.list);
  const _retry = params.retry ?? 3;

  /**
   * 总字节长度
   */
  const totalLength = useRef(0);
  const fileSizeStack = useRef<Map<string, number>>(new Map());
  /**
   * 已接收字节长度
   */
  const downloadLength = useRef(0);
  /**
   * 进度
   */
  const [percent, setPercent] = useState("0");
  const [status, setStatus] = useState<PRELOADER_STATUS>(
    PRELOADER_STATUS.WAITING
  );
  /**
   * 缓存的媒体资源本地url
   */
  const mediaUrlMap = useRef(new Map<string, string>());

  const setThrottlePercent = useCallback(
    throttle((val: string) => setPercent(val), 200),
    []
  );

  const calculateSum = (map: Map<string, number>) => {
    let sum = 0;
    for (const value of map.values()) {
      sum += value;
    }
    return sum;
  };

  const handlePreload = (url: string, retry = _retry, loadedLength = 0) => {
    const controller = new AbortController();
    const signal = controller.signal;
    const mediaType = getMediaType(url);
    let loadingLength = 0;

    return new Promise((res, rej) => {
      fetch(url, { signal })
        .then((response) => {
          const contentLength = Number(response.headers.get("Content-Length"));

          fileSizeStack.current.set(url, contentLength);

          if (response.ok) {
            return response.body;
          }
        })
        .then(async (body) => {
          if (!body) {
            rej(`request.body is not find`);
            return;
          }

          let diffed = false;
          const chunks: Uint8Array[] = [];
          const reader = body.getReader();
          const loop = true;

          while (loop) {
            const { value, done } = await reader.read();

            if (done) break;

            loadingLength += value.length;
            if (loadingLength >= loadedLength) {
              if (!diffed && loadedLength) {
                const diff = loadingLength - loadedLength;
                console.log(diff);
                downloadLength.current += diff;
                diffed = true;
              }

              downloadLength.current += value.length;
            }

            if (mediaType) {
              chunks.push(value);
            }

            if (fileSizeStack.current.size === _list.current.length) {
              const sum = calculateSum(fileSizeStack.current);
              const percent = Math.round(
                (downloadLength.current / sum) * 100 * decimals
              );

              setThrottlePercent((percent / decimals).toFixed(params.decimals));
            }
          }

          if (mediaType) {
            const blob = new Blob(chunks, { type: mediaType });
            mediaUrlMap.current.set(
              parseUrlName(url),
              URL.createObjectURL(blob)
            );
          }

          res(true);
        })
        .catch((err) => {
          if (err.name === "AbortError") {
            rej(err);
            return;
          }

          if (retry) {
            console.log("尝试重新预加载");
            setTimeout(() => {
              handlePreload(url, retry - 1, loadingLength)
                .then(res)
                .catch(rej);
            }, 2000);
          } else {
            if (mediaType) {
              mediaUrlMap.current.set(parseUrlName(url), url);
            }

            rej(err);
          }
        });

      fetchControllerStack.current.push(controller);
    });
  };

  const resetParams = () => {
    abort();

    totalLength.current = 0;
    downloadLength.current = 0;
    fileSizeStack.current.clear();
    mediaUrlMap.current.clear();
    setThrottlePercent("0");
  };

  const start = () => {
    if (status === PRELOADER_STATUS.LOADING) return;

    if (status !== PRELOADER_STATUS.WAITING) {
      resetParams();
    }

    setStatus(PRELOADER_STATUS.LOADING);

    Promise.all(_list.current.map((url) => handlePreload(url)))
      .then(() => {
        params.success?.();
        setStatus(PRELOADER_STATUS.DONE);
      })
      .catch((err: Error) => {
        if (err.name === "AbortError") return;

        abort();
        params.error?.(err);
        setStatus(PRELOADER_STATUS.ERROR);
      });
  };

  const abort = () => {
    fetchControllerStack.current.forEach((controller) => {
      controller.abort();
    });
    fetchControllerStack.current = [];

    setStatus(PRELOADER_STATUS.ABORT);
  };

  return {
    status,
    percent,
    totalLength: totalLength.current,
    downloadLength: downloadLength.current,
    mediaUrlMap: mediaUrlMap.current,
    start,
    abort,
  };
}
