<template>
  <div :style="setMaxWidth">
    <div
      v-if="label || labelIcon"
      class="flex"
    >
      <div class="vc-input__label mb-2">
        <label class="vc-input__label-wrapper flex items-center">
          <span
            v-if="labelIcon"
            class="vc-input__label-wrapper--left-item"
          >
            <i
              :class="labelIcon"
              class="icon-lg vc-input__label-wrapper--left-item-icon text-icons"
            />
          </span>
          <span
            v-if="label"
            class="vc-input__label-wrapper-label"
          >
            {{ label }}
            <span
              v-if="required"
              class="vc-input__label-wrapper-label__required-icon"
            >*</span>
          </span>
        </label>
      </div>
    </div>
    <div class="vc-input___file-uploader-wrapper">
      <div class="vc-input__file-uploader-wrapper-label">
        <VcButton
          :type="buttonType"
          :color="buttonColor"
          :icon="buttonIcon"
          :size="buttonSize"
          @click="triggerInputClick"
        >
          {{ uploadButtonText }}
        </VcButton>
      </div>

      <input
        :id="id"
        ref="inputUploader"
        :name="id"
        :accept="accept"
        :multiple="multiple"
        :data-test="dataTestName"
        :disabled="disabled"
        :maxlength="maxLength"
        class="vc-input__file-uploader-wrapper-input"
        type="file"
        @change="onFileChange"
      >
    </div>
    <div
      v-if="!items.length"
      class="vc-input__message-wrapper"
    >
      <span class="vc-input__message-wrapper--text">
        {{ noFilesSelectedText }}
      </span>
    </div>
    <div
      v-if="errorText"
      class="vc-input__error-wrapper"
    >
      <span class="vc-input__error-wrapper--text">
        {{ errorText }}
      </span>
    </div>
    <div
      v-if="showFileName && items.length"
      class="mt-1"
    >
      <VcDelimitedList
        :list="items"
        :limit="maxLength"
        trigger="hover"
        max-width="400"
        separator=""
      >
        <template #list-item="{item}">
          <VcTag
            :label="item.name"
            :max-content="tagMaxContentWidth"
            :icon="tagIcon"
            closable
            @on-close="onTagClose(item.id)"
          />
        </template>
        <template #reference="{listLength}">
          <VcTag
            :label="`+ ${listLength}`"
          />
        </template>
        <template #other-content="{list}">
          <div
            v-for="(item, index) in list"
            :key="index"
            class="flex items-center justify-start p-2"
          >
            <VcTag
              :label="item.name"
              :icon="tagIcon"
              closable
              @on-close="onTagClose(item.id)"
            />
          </div>
        </template>
      </VcDelimitedList>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, PropType } from 'vue'
import baseInputMixin from '@/mixins/baseInput.mixin'
import { ButtonType, ButtonColor, ButtonSize } from '@/components/VcButton/VcButton.types'
import { VcDelimitedList, VcTag, VcButton } from '@/components'
import { FileUploaded } from './VcFileUploader.types'
import { getFileSize } from '@/utils/helpers'

const DEFAULT_BUTTON_SIZE = 'middle'
const DEFAULT_BUTTON_TYPE = 'default'
const DEFAULT_BUTTON_COLOR = 'primary'

export default defineComponent({
  name: 'VcFileUploader',
  components: {
    VcDelimitedList,
    VcTag,
    VcButton
  },
  mixins: [baseInputMixin],
  props: {
    files: {
      type: Array,
      default: () => []
    },
    modelValue: {
      type: String,
      default: ''
    },
    maxLength: {
      type: Number,
      default: Infinity
    },
    accept: {
      type: String,
      default: '*'
    },
    tagIcon: {
      type: String,
      default: 'vc-icon-confirm-action'
    },
    showFileName: {
      type: Boolean,
      default: true
    },
    multiple: {
      type: Boolean,
      default: false
    },
    noDataText: {
      type: String,
      default: ''
    },
    fileSizeLimit: {
      type: Number,
      default: null,
      description: 'File size limit in bytes'
    },
    fileListSizeLimit: {
      type: Number,
      default: null,
      description: 'Files list size limit in bytes'
    },
    buttonType: {
      type: String as PropType<ButtonType>,
      default: DEFAULT_BUTTON_TYPE
    },
    buttonIcon: {
      type: String,
      default: ''
    },
    buttonColor: {
      type: String as PropType<ButtonColor>,
      default: DEFAULT_BUTTON_COLOR
    },
    buttonSize: {
      type: String as PropType<ButtonSize>,
      default: DEFAULT_BUTTON_SIZE
    },
    buttonText: {
      type: String,
      default: ''
    },
    tagMaxContentWidth: {
      type: String,
      default: '400'
    }
  },
  emits: ['update:modelValue', 'input'],
  data () {
    return {
      items: [] as FileUploaded[],
      innerErrorText: '' as string
    }
  },
  computed: {
    errorText () {
      if (this.innerErrorText) {
        return this.innerErrorText
      }
      return this.error
    },
    noFilesSelectedText () {
      return this.noDataText ? this.noDataText : this.$tVU.t('uploader.no.files.selected')
    },
    uploadButtonText () {
      return this.buttonText ? this.buttonText : this.$tVU.t('uploader.upload.file')
    }
  },
  watch: {
    modelValue (val: FileUploaded[] | FileList) {
      this.parseFiles(val)
    },
    files: {
      immediate: true,
      handler (files: FileUploaded[] | FileList): void {
        this.parseFiles(files)
      }
    }
  },
  methods: {
    parseFiles (files: FileUploaded[] | FileList): void {
      files = Array.from(files)

      if (files.length && files.length > this.maxLength) {
        files = files.slice(0, this.maxLength)
      }

      this.items = files.map((item: FileUploaded, index: number) => {
        item.id = index
        return item
      })
    },
    onFileChange (e: Event): void {
      this.innerErrorText = ''
      const target = e.target as HTMLInputElement
      const targetFiles = target?.files as FileList

      if (target && targetFiles && targetFiles.length) {
        let files = Array.from(targetFiles)

        files = this.processFilesLength(files)
        files = this.processFileSize(files)

        if (this.$refs.inputUploader) {
          const inputElement = this.$refs.inputUploader as HTMLInputElement
          inputElement.value = ''
        }

        this.$emit('input', files)
        this.$emit('update:modelValue', files)
      }
    },
    processFilesLength (files: FileUploaded[]): FileUploaded[] {
      if (this.multiple && files.length > this.maxLength) {
        this.innerErrorText = this.$tVU.t('uploader.some.file.were.concat.files.limit')
        return files.slice(0, this.maxLength)
      }
      return files
    },
    processFileSize (files: FileUploaded[]): FileUploaded[] {
      if (this.multiple) {
        const filesSizeSum = files.map((file: FileUploaded): number => file.size)
          .reduce((sizeSum: number, a: number): number => sizeSum + a, 0)

        if (this.fileListSizeLimit && filesSizeSum > this.fileListSizeLimit) {
          const sizeToExceed = getFileSize(this.fileListSizeLimit)
          this.innerErrorText = this.$tVU.t('uploader.file.size.sum.should.not.exceed', { value: sizeToExceed[0], measure: sizeToExceed[1] })
          return []
        }

        if (this.fileSizeLimit) {
          files = files.filter((file: FileUploaded): boolean => {
            if (file.size > this.fileSizeLimit) {
              this.innerErrorText = this.$tVU.t('uploader.some.files.were.concat.size.limit')
              return false
            }
            return true
          })
        }

        return files
      } else {
        if (!this.fileSizeLimit) return files

        const limitSize = getFileSize(this.fileSizeLimit)
        const file = files[0]
        if (file && file.size > this.fileSizeLimit) {
          const fileSize = getFileSize(file.size)
          this.innerErrorText = this.$tVU.t('uploader.file.size.is.but.limit.is', { fileSize: fileSize[0], fileMeasure: fileSize[1], limitSize: limitSize[0], limitMeasure: limitSize[1] })
          return []
        }
        return files
      }
    },
    onTagClose (index: number): void {
      this.innerErrorText = ''
      const files = this.items.filter((item: FileUploaded) => item.id !== index)

      this.$emit('input', files)
      this.$emit('update:modelValue', files)
    },
    triggerInputClick (): void {
      if (this.$refs.inputUploader && this.$refs.inputUploader instanceof HTMLElement) {
        this.$refs.inputUploader.click()
      }
    }
  }
})
</script>

<style lang="scss" scoped>
@import 'src/assets/sass/main/mixins';

.vc-input {
  @include input;
}
</style>
