import { defineStore } from "pinia";
import { useFileStore } from "./file";
import { files as api } from "@/api";
import throttle from "lodash/throttle";
import buttons from "@/utils/buttons";

// TODO: make this into a user setting
const UPLOADS_LIMIT = 5;

const beforeUnload = (event: Event) => {
  event.preventDefault();
  // To remove >> is deprecated
  // event.returnValue = "";
};

// Utility function to format bytes into a readable string
function formatSize(bytes: number): string {
  if (bytes === 0) return "0 Bytes";

  const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + " " + sizes[i];
}

export const useUploadStore = defineStore("upload", {
  // convert to a function
  state: (): {
    id: number;
    sizes: number[];
    progress: Progress[];
    queue: UploadItem[];
    uploads: Uploads;
    speedMbyte: number;
    eta: number;
    error: Error | null;
  } => ({
    id: 0,
    sizes: [],
    progress: [],
    queue: [],
    uploads: {},
    speedMbyte: 0,
    eta: 0,
    error: null,
  }),
  getters: {
    // user and jwt getter removed, no longer needed
    getProgress: (state) => {
      if (state.progress.length === 0) {
        return 0;
      }

      const totalSize = state.sizes.reduce((a, b) => a + b, 0);

      // TODO: this looks ugly but it works with ts now
      const sum = state.progress.reduce((acc, val) => +acc + +val) as number;
      return Math.ceil((sum / totalSize) * 100);
    },
    getProgressDecimal: (state) => {
      if (state.progress.length === 0) {
        return 0;
      }

      const totalSize = state.sizes.reduce((a, b) => a + b, 0);

      // TODO: this looks ugly but it works with ts now
      const sum = state.progress.reduce((acc, val) => +acc + +val) as number;
      return ((sum / totalSize) * 100).toFixed(2);
    },
    getTotalProgressBytes: (state) => {
      if (state.progress.length === 0 || state.sizes.length === 0) {
        return "0 Bytes";
      }
      const sum = state.progress.reduce((acc, val) => +acc + +val, 0) as number;
      return formatSize(sum);
    },
    getTotalSize: (state) => {
      if (state.sizes.length === 0) {
        return "0 Bytes";
      }
      const totalSize = state.sizes.reduce((a, b) => a + b, 0);
      return formatSize(totalSize);
    },
    filesInUploadCount: (state) => {
      return Object.keys(state.uploads).length + state.queue.length;
    },
    filesInUpload: (state) => {
      const files = [];

      for (const index in state.uploads) {
        const upload = state.uploads[index];
        const id = upload.id;
        const type = upload.type;
        const name = upload.file.name;
        const size = state.sizes[id];
        const isDir = upload.file.isDir;
        const progress = isDir
          ? 100
          : Math.ceil(((state.progress[id] as number) / size) * 100);

        files.push({
          id,
          name,
          progress,
          type,
          isDir,
        });
      }

      return files.sort((a, b) => a.progress - b.progress);
    },
    uploadSpeed: (state) => {
      return state.speedMbyte;
    },
    getETA: (state) => state.eta,
  },
  actions: {
    // no context as first argument, use `this` instead
    setProgress({ id, loaded }: { id: number; loaded: Progress }) {
      this.progress[id] = loaded;
    },
    setError(error: Error) {
      this.error = error;
    },
    reset() {
      this.id = 0;
      this.sizes = [];
      this.progress = [];
      this.queue = [];
      this.uploads = {};
      this.speedMbyte = 0;
      this.eta = 0;
      this.error = null;
    },
    addJob(item: UploadItem) {
      this.queue.push(item);
      this.sizes[this.id] = item.file.size;
      this.id++;
    },
    moveJob() {
      const item = this.queue[0];
      this.queue.shift();
      this.uploads[item.id] = item;
    },
    removeJob(id: number) {
      delete this.uploads[id];
    },
    upload(item: UploadItem) {
      const uploadsCount = Object.keys(this.uploads).length;

      const isQueueEmpty = this.queue.length == 0;
      const isUploadsEmpty = uploadsCount == 0;

      if (isQueueEmpty && isUploadsEmpty) {
        window.addEventListener("beforeunload", beforeUnload);
        buttons.loading("upload");
      }

      this.addJob(item);
      this.processUploads();
    },
    finishUpload(item: UploadItem) {
      this.setProgress({ id: item.id, loaded: item.file.size > 0 });
      this.removeJob(item.id);
      this.processUploads();
    },
    async processUploads() {
      const uploadsCount = Object.keys(this.uploads).length;

      const isBellowLimit = uploadsCount < UPLOADS_LIMIT;
      const isQueueEmpty = this.queue.length == 0;
      const isUploadsEmpty = uploadsCount == 0;

      const isFinished = isQueueEmpty && isUploadsEmpty;
      const canProcess = isBellowLimit && !isQueueEmpty;

      if (isFinished) {
        const fileStore = useFileStore();
        window.removeEventListener("beforeunload", beforeUnload);
        buttons.success("upload");
        this.reset();
        fileStore.reload = true;
      }

      if (canProcess) {
        const item = this.queue[0];
        this.moveJob();

        if (item.file.isDir) {
          await api.post(item.path).catch(this.setError);
        } else {
          const onUpload = throttle(
            (event: ProgressEvent) =>
              this.setProgress({
                id: item.id,
                loaded: event.loaded,
              }),
            100,
            { leading: true, trailing: false }
          );

          await api
            .post(item.path, item.file.file as File, item.overwrite, onUpload)
            .catch(this.setError);
        }

        this.finishUpload(item);
      }
    },
    setUploadSpeed(value: number) {
      this.speedMbyte = value;
    },
    setETA(value: number) {
      this.eta = value;
    },
    // easily reset state using `$reset`
    clearUpload() {
      this.$reset();
    },
  },
});
