<template>
  <div>
    <n-input
      :rules="rules"
      :value="value"
      :label="label"
      :filled="filled"
      :disabled="disabled"
      :required="required"
      :helper-text="fieldHelperText"
    >
      <template #default="{ hasError }">
        <div
          :class="[
            'pkg-n-base-attachment-field n-radius-md n-py-3 n-px-4',
            {
              'n-input--has-error': hasError,
              'n-input--disabled': disabled,
            },
          ]"
        >
          <div class="pkg-base-attachment-wrapper">
            <div class="n-flex n-align-center n-flex-wrap n-gap-2">
              <p
                :class="[
                  'n-file-input-label n-m-0 n-flex-grow n-font-normal n-text-body1',
                  { 'n-color-main-font': isSingleModeFileSelected },
                ]"
              >
                {{ placeholderText }}
              </p>

              <slot name="append-icon">
                <i
                  v-if="
                    appendIcon &&
                    !readOnly &&
                    !isServerLoading &&
                    !isSingleModeFileSelected
                  "
                  :class="[
                    appendIcon,
                    'n-action-placeholder n-icon n-icon-size-22',
                  ]"
                  @click.prevent="onOpenFileModal"
                />

                <i
                  v-if="!readOnly && isSingleModeFileSelected"
                  class="n-action-placeholder n-icon n-icon-close-square n-icon-size-22"
                  @click.prevent="utils.onDeleteFile()"
                />

                <i
                  v-if="readOnly && isSingleModeFileSelected"
                  class="n-action-placeholder n-icon n-icon-eye n-icon-size-22"
                  @click="handleSingleFileDownload"
                />

                <n-svg
                  v-if="isServerLoading"
                  name="spinner-loader"
                  :width="28"
                  :height="28"
                />
              </slot>
            </div>
          </div>
        </div>
      </template>
    </n-input>

    <!-- ! Input -->
    <input
      :id="fileInputId"
      ref="file"
      class="n-hide"
      type="file"
      :disabled="disabled || !addAttachmentAllowed"
      :multiple="isFieldMultiple"
      :accept="accept"
      @change="onSelectFiles"
    />
    <!-- ! List -->
    <slot name="list" :data="{ listData }" :onDeleteFile="utils.onDeleteFile">
      <ul v-if="isMultiple && selectedFiles.length" class="n-p-0 n-mt-2">
        <li
          v-for="(selectedFile, index) in selectedFiles"
          :key="index"
          class="n-flex n-align-center n-justify-between list-item n-px-3 n-py-2"
        >
          <div class="icon-name-wrapper n-flex n-align-center">
            <n-svg class="img" name="file" width="25" height="25" />
            <span class="file-name ms-3">
              {{
                enableServerSide
                  ? selectedFile.file.displayName
                  : selectedFile.displayName
              }}
            </span>
          </div>

          <div class="size-delete-wrapper n-flex n-align-center">
            <span class="size">
              {{
                utils.getFileSizeInKiloByte(
                  enableServerSide ? selectedFile.file.size : selectedFile.size
                )
              }}
            </span>

            <button
              class="n-align-center n-inline-flex n-justify-center"
              @click="utils.onDeleteFile(index)"
            >
              <n-svg
                v-if="!disabled"
                :id="`${deleteActionId}_${index + 1}`"
                class="img"
                width="30"
                height="30"
                name="cancel"
              />
            </button>

            <button
              class="n-align-center n-inline-flex n-justify-center"
              @click="
                utils.onDownloadFile(
                  enableServerSide ? selectedFile.file : selectedFile
                )
              "
            >
              <n-svg
                v-if="isDownloadAvailable"
                :id="`${downloadActionId}_${index + 1}`"
                class="img"
                width="20"
                height="20"
                name="download"
              />
            </button>
          </div>
        </li>
      </ul>
    </slot>
  </div>
</template>

<script>
import NInput from "../NInput/n-input.vue";
import NSvg from "../../Svgs/nSvg.vue";
import generateUtils from "./utils";

export default {
  name: "NAttachmentField",
  components: {
    NSvg,
    NInput,
  },
  props: {
    rules: {
      type: Array,
      default: () => [],
    },
    value: {
      type: Array,
      default: () => [],
    },
    fileInputId: {
      type: String,
      default: "file-input",
    },
    appendIcon: {
      type: String,
      default: "n-icon-direct-inbox",
    },
    helperText: {
      type: String,
      default: "",
    },
    chooseFileActionId: {
      type: String,
      default: "",
    },
    deleteActionId: {
      type: String,
      default: "",
    },
    downloadActionId: {
      type: String,
      default: "",
    },
    maxFileSizeInMega: {
      type: Number,
      default: 2,
    },
    maxFilesSizeInMega: {
      type: Number,
      default: 5,
    },
    maxDisplayNameLength: {
      type: Number,
      default: 15,
    },
    maxAttachments: {
      type: Number,
      default: 5,
    },

    disabled: {
      type: Boolean,
      default: false,
    },
    filled: {
      type: Boolean,
      default: false,
    },
    isMultiple: {
      type: Boolean,
      default: false,
    },
    enableFancyPreview: {
      type: Boolean,
      default: false,
    },
    resetErrorOnSelect: {
      type: Boolean,
      default: true,
    },
    activateInternalErrorPreview: {
      type: Boolean,
      default: false,
    },
    enableDownload: {
      type: Boolean,
      default: false,
    },
    readOnly: {
      type: Boolean,
      default: false,
    },
    enableFullnameDisplay: {
      type: Boolean,
      default: false,
    },
    validateOnSingleFileSize: {
      type: Boolean,
      default: false,
    },
    exportInitialFieldMeta: {
      type: Boolean,
      default: false,
    },
    required: {
      type: Boolean,
      default: false,
    },
    label: {
      type: String,
      default: "",
    },
    name: {
      type: String,
      default: "",
    },
    accept: {
      type: String,
      default: ".jpg,.pdf,.png,.jpeg",
    },
    localizations: {
      type: Object,
      default: () => ({}),
    },
    /**
     * Represents a uploadCallbackArgs object.
     * @typedef {Object} uploadCallbackArgs
     * @property {File} source - File src.
     * @property {string} base64 - base64.
     * @property {string} contentType - encodedSharepointId.
     *
     * Represents a downloadCallbackArgs object.
     *
     * @typedef {Object} downloadCallbackArgs
     * @property {string} contentType - File content type.
     * @property {string} sharepointId - SharepointId.
     * @property {string} encodedSharepointId - encodedSharepointId.
     * @property {Function} fileGenerator - Helper to download the file if needed.
     *
     * Represents a serverSideConfigs object.
     *
     * @typedef {Object} ServerSideConfigs
     * @property {string} appName  - The appName.
     * @property {string} systemCode - The systemCode.
     * @property {string} [uploadUrl]  - default will be /file/upload.
     * @property {(string|function(sharepointId, encodedSharepointId): downloadUrl)} [downloadUrl] - It can be either a string and the default is /file or a function that takes sharepointId and encodedSharepointId.
     * @property {string} [downloadUrl]  - The year the car was made.
     *
     * @property {function(downloadCallbackArgs): File} downloadCallback - To override the sharepoint download functionality.
     *
     * @property {function(uploadCallbackArgs): string} uploadCallback - To override the sharepoint upload functionality.
     * @returns {}
     *
     */
    serverSideConfigs: {
      type: Object,
      default: () => ({}),
    },
    enableServerSide: {
      type: Boolean,
      default: false,
    },
    updateParentWithFileMeta: {
      type: Boolean,
      required: false,
      default: false,
    },
    attachmentTypeId: {
      type: Number,
      default: 0,
    },
  },
  data() {
    return {
      selectedFiles: [],
      currentTotalSize: 0,
      error: "",
      isServerLoading: false,
    };
  },
  computed: {
    isSingleModeFileSelected() {
      const { isMultiple, selectedFiles } = this;

      return Boolean(!isMultiple && selectedFiles.length);
    },
    placeholderText() {
      const {
        strings,
        isServerLoading,
        isMultiple,
        selectedFiles,
        enableServerSide,
      } = this;
      const text = isServerLoading
        ? strings.serverLoadingText
        : strings.chooseFile;

      if (isMultiple) {
        return text;
      }

      if (selectedFiles.length) {
        const selectedFile = selectedFiles[0];

        return enableServerSide
          ? selectedFile.file.displayName
          : selectedFile.displayName;
      }

      return text;
    },
    fieldHelperText() {
      const { utils, accept, helperText } = this;

      return helperText || utils.getAllowedFileTypesText(accept);
    },
    addAttachmentAllowed() {
      return this.selectedFiles.length < this.maxAttachments;
    },
    isDownloadAvailable() {
      const { disabled, enableServerSide, enableDownload } = this;

      return enableDownload || disabled || enableServerSide;
    },
    hintsData() {
      return {
        maxFileSizeInMega: this.maxFileSizeInMega,
        maxFilesSizeInMega: this.maxFilesSizeInMega,
        allowedExtentions: this.accept,
      };
    },
    listData() {
      return {
        files: this.selectedFiles,
      };
    },
    totalFilesSize() {
      return this.maxFilesSizeInMega * 1024 * 1024;
    },
    updatedValue() {
      return {
        name: this.name,
        value: this.parentUpdatedData,
        isValid: this.required ? !!this.selectedFiles.length : true,
      };
    },
    parentUpdatedData() {
      const {
        selectedFiles,
        enableServerSide,
        updateParentWithFileMeta,
      } = this;

      if (!enableServerSide || (enableServerSide && updateParentWithFileMeta)) {
        return selectedFiles;
      }
      // Deep clone
      const mutatedSelectedFiles = JSON.parse(JSON.stringify(selectedFiles));

      return mutatedSelectedFiles.map((enhancedFile) => {
        const enhancedFileClone = enhancedFile;
        delete enhancedFileClone.file;

        return enhancedFileClone;
      });
    },
    strings() {
      const { localizations } = this;

      return {
        placeholder: "",
        clickHere: "اضغط هنا",
        chooseFile: "اختر ملف",
        actionName: "استعراض الملفات",
        serverLoadingText: "جاري تحميل البيانات ...",
        ...localizations,
      };
    },
    isFieldMultiple() {
      const { isMultiple, enableServerSide } = this;

      return enableServerSide ? false : isMultiple;
    },
    utils() {
      return generateUtils(this);
    },
    enhancedServerSideConfigs() {
      return {
        appName: window.$config?.sharepoint?.appName || "",
        systemCode: window.$config?.sharepoint?.systemCode || "",
        downloadUrl: window.$config?.sharepoint?.downloadUrl || "/file",
        uploadUrl: window.$config?.sharepoint?.uploadUrl || "/file/upload",
        ...this.serverSideConfigs,
      };
    },
  },
  watch: {
    value(newVal, oldVal) {
      const isEqualValues = JSON.stringify(newVal) === JSON.stringify(oldVal);

      if (this.value.length && !isEqualValues) {
        this.loadData();
      } else if (!this.value.length && !isEqualValues) {
        this.selectedFiles = [];
        this.currentTotalSize = 0;
      }
    },
  },
  mounted() {
    // initial notification to the parent
    if (this.exportInitialFieldMeta) {
      this.$emit("select", this.updatedValue);
    }

    if (this.value.length) {
      this.loadData();
    }
  },
  methods: {
    loadData() {
      const { enableServerSide } = this;

      if (enableServerSide) {
        this.loadServerSideData();
      } else {
        this.loadClientSideData();
      }
    },
    async loadClientSideData() {
      this.selectedFiles = await this.value.reduce(
        async (memo, file, index) => {
          const results = await memo;
          let generatedFile = {};
          // Don't load more than max attachments if fullfilled
          const isValidIteration = this.maxAttachments >= index + 1;

          if (isValidIteration) {
            const convertedFile = await this.utils.base64ToFilesConverter(file);

            // check if file matches (size, totalFilesSize)
            if (this.utils.isValidFile(convertedFile)) {
              generatedFile = convertedFile;
              this.currentTotalSize += convertedFile.size;
            }
          }

          return isValidIteration ? [...results, generatedFile] : results;
        },
        []
      );
    },
    async loadServerSideData() {
      const { selectedFiles, value } = this;
      this.isServerLoading = true;
      // Todo: Load from server and block upload button untill done
      const filesNotLoadedYet = selectedFiles.length
        ? value.filter((providedValue) =>
            selectedFiles.every(
              (file) => file.sharepointId !== providedValue.sharepointId
            )
          )
        : value;

      if (filesNotLoadedYet.length) {
        const enhancedExtraFiles = await filesNotLoadedYet.reduce(
          async (memo, file, index) => {
            const results = await memo;

            let generatedFile = {};
            // Don't load more than max attachments if fullfilled
            const isValidIteration = this.maxAttachments >= index + 1;

            if (isValidIteration) {
              const { downloadCallback } = this.enhancedServerSideConfigs;
              let fileFromSharepointId;
              const args = {
                contentType: file.contentType,
                sharepointId: file.sharepointId,
                encodedSharepointId: btoa(file.sharepointId),
                fileGenerator: (response) =>
                  new File([response.data], response.fileName, {
                    type: response.fileType,
                  }),
              };

              if (downloadCallback && typeof downloadCallback === "function") {
                fileFromSharepointId = await downloadCallback(args);
              } else {
                fileFromSharepointId = await this.utils.downloadFile(args);
              }

              fileFromSharepointId.displayName = this.utils.enhanceFileName(
                fileFromSharepointId
              );

              // check if file matches (size, totalFilesSize)
              if (this.utils.isValidFile(fileFromSharepointId)) {
                generatedFile = {
                  ...file,
                  file: fileFromSharepointId,
                };
                this.currentTotalSize += fileFromSharepointId.size;
              }
            }

            return isValidIteration ? [...results, generatedFile] : results;
          },
          []
        );
        this.selectedFiles = this.selectedFiles.concat(enhancedExtraFiles);
      }
      this.isServerLoading = false;
    },
    async onSelectFiles(e) {
      if (this.enableServerSide) {
        this.utils.onServerSideSelect(e);
      } else {
        this.utils.onClientSideSelect(e);
      }
    },
    onOpenFileModal() {
      const { disabled, $refs } = this;

      if (disabled) {
        return;
      }

      $refs.file.click();
    },
    reset() {
      this.selectedFiles = [];
      this.$emit("select", this.updatedValue);
    },

    handleSingleFileDownload() {
      const { utils, enableServerSide, selectedFiles } = this;
      const selectedFile = selectedFiles[0];

      utils.onDownloadFile(enableServerSide ? selectedFile.file : selectedFile);
    },
  },
};
</script>

<style scoped>
@import url("./n-attachment-field.css");
</style>
