/*
 * This file is part of vospace-ui
 * Copyright (C) 2021 Istituto Nazionale di Astrofisica
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
import Vue from 'vue';
import client from 'api-client';

function resetArray(array) {
  array.splice(0, array.length);
}

export default {
  state: {
    files: [], // array of selected files
    progress: [], // array of upload progress percentages (for tracking upload progress)
    cancellations: [], // array of axios cancel tokens (for aborting uploads)
    deletionStatuses: [], // status of upload deletions (true if request is being aborted, false otherwise)
    errors: [], // errors happened during downloads
    creatingMetadata: false, // true when nodes metadata is being created (pre-upload phase)
    uploadInProgress: false, // true when data is being uploaded
  },
  mutations: {
    setFilesToUpload(state, files) {
      state.files = files;
    },
    resetUploadState(state) {
      resetArray(state.progress);
      resetArray(state.cancellations);
      resetArray(state.deletionStatuses);
      resetArray(state.errors);
    },
    setDeletion(state, data) {
      Vue.set(state.deletionStatuses, data.index, data.value);
    },
    setProgress(state, data) {
      Vue.set(state.progress, data.index, data.percent);
    },
    setCreatingMetadata(state, value) {
      state.creatingMetadata = value;
    },
    setUploadInProgress(state, value) {
      state.uploadInProgress = value;
    },
    setPreUploadResults(state, preUploadResults) {
      for (let result of preUploadResults) {
        if (Object.keys(result).includes('request')) {
          state.cancellations.push(result.source); // axios cancellation token
          state.deletionStatuses.push(null);
          state.errors.push(null);
        } else {
          state.cancellations.push(null);
          state.deletionStatuses.push(null);
          state.errors.push(result);
        }
      }
      state.uploadInProgress = true;
    },
    setError(state, data) {
      Vue.set(state.errors, data.index, data.error);
    }
  },
  actions: {
    upload({ state, rootState, commit, dispatch }, bvModal) {

      // Progress bars initialization
      for (let index = 0; index < state.files.length; index++) {
        let file = state.files[index];
        let reader = new FileReader();

        reader.addEventListener('progress', (event) => {
          if (event.loaded && event.total) {
            let percent = (event.loaded / event.total) * 100;
            commit('setProgress', { index, percent });
          }
        });

        reader.readAsDataURL(file);
      }

      commit('setCreatingMetadata', true);

      // Create nodes metadata and obtain upload URLs
      dispatch('prepareUpload')
        .then((preUploadResults) => {
          commit('setCreatingMetadata', false);

          let promises = [];
          for (let result of preUploadResults) {
            if (Object.keys(result).includes('request')) {
              promises.push(result.request); // upload http promise
            } else {
              // fictitious promise for failed pre-upload event
              promises.push(new Promise(resolve => resolve()));
            }
          }

          commit('setPreUploadResults', preUploadResults);

          // wait until all downloads have been completed (both successfully or not)
          return Promise.allSettled(promises);
        })
        .then((responses) => {

          let deletionPromises = [];
          for (let index = 0; index < responses.length; index++) {
            let error = state.errors[index];
            let response = responses[index];

            if (response.status === 'fulfilled' && error === null) {
              commit('setProgress', { index, percent: 100 });
            } else {
              if (state.deletionStatuses[index] || error === null) {
                // delete node metadata both when user aborted the upload and when it failed
                deletionPromises.push(client.deleteNodes(['/' + rootState.path + '/' + state.files[index].name], true));
                if (state.deletionStatuses[index] === null) {
                  // e.g. server became unreachable during the upload
                  commit('setError', { index, error: 'Unable to upload file' });
                }
              }
            }
          }

          // if there are some deletion promises, wait them
          return Promise.allSettled(deletionPromises);
        })
        .then(deletionResults => {
          // verify if metadata of aborted uploads has been correctly removed
          let resultIndex = 0;
          for (let i = 0; i < state.deletionStatuses.length; i++) {
            let deletionStatus = state.deletionStatuses[i];
            if (deletionStatus !== null) {
              let deletionResult = deletionResults[resultIndex];
              if (deletionResult.status === 'fulfilled') {
                commit('setDeletion', { index: i, value: false });
              } else {
                commit('setError', { index: i, error: 'Failed to delete node metadata. Manual cleanup may be required.' });
              }
              resultIndex++;
            }
          }
        })
        .finally(() => {
          // Reload current node when all uploads completed
          dispatch('setPath', rootState.path);
          commit('setCreatingMetadata', false);
          commit('setUploadInProgress', false);
          let hasErrors = state.errors.filter(e => e !== null).length > 0;
          if (!hasErrors) {
            bvModal.hide('upload-files-modal');
          }
        });
    },
    prepareUpload({ state, rootState }) {
      let names = [];
      for (let file of state.files) {
        names.push(file.name);
      }
      return new Promise((resolve, reject) => {
        client.prepareForUpload(rootState.path, names)
          .then(preUploadResponses => {
            let uploads = [];
            for (let i = 0; i < state.files.length; i++) {
              let uploadUrl = preUploadResponses[i].url;
              if (uploadUrl !== null) {
                uploads.push(client.uploadFile(uploadUrl, state.files[i]));
              } else {
                uploads.push(preUploadResponses[i].error);
              }
            }
            resolve(uploads);
          })
          .catch(error => reject(error));
      });
    },
    cancelUpload({ state, commit }, index) {
      commit('setDeletion', { index, value: true });
      state.cancellations[index].cancel();
    }
  }
}