<template>
  <div class="snt-export">
    <v-dialog
      v-model="dialog"
      :persistent="submitting"
      content-class="snt-export__content"
      width="500px"
      scrollable
      @input="changeDialog($event)">
      <template v-slot:activator="{ on, attrs }">
        <slot
          name="activator"
          :attrs="attrs"
          :submitting="submitting"
          :disabled="disabled"
          :on="on">
          <v-btn
            v-bind="attrs"
            :loading="submitting"
            :disabled="disabled"
            color="primary"
            depressed
            v-on="on">
            <v-icon left>
              mdi-download
            </v-icon>
            {{ $t('app.export') }}
          </v-btn>
        </slot>
      </template>
      <v-card>
        <v-card-title>
          <div class="primary--text">
            Export
          </div>
          <v-spacer />
          <v-btn
            color="#A9A9A9"
            x-small
            icon
            @click="close()">
            <v-icon small>
              mdi-close
            </v-icon>
          </v-btn>
        </v-card-title>
        <v-card-text v-if="files.length">
          <div
            v-for="(file, i) in files"
            :key="`download-file-${i}`">
            <v-divider v-if="i" />
            <div class="d-flex my-5">
              <div class="mr-4">
                <v-icon size="30">
                  {{ fileType === '.csv' ? '$snt_csv_file_format_extension' : '$snt_xlsx_file_format_extension' }}
                </v-icon>
              </div>
              <div class="w-100">
                <div class="d-flex align-center gap-2 mb-1">
                  <div class="body-2">
                    {{ file.filename }}{{ fileType }}
                  </div>
                  <v-spacer />
                  <template v-if="file.cancelled">
                    <div class="caption secondary--text">
                      Cancelled
                    </div>
                    <v-btn
                      color="primary"
                      x-small
                      icon
                      @click="refreshFile(i)">
                      <v-icon>
                        mdi-download
                      </v-icon>
                    </v-btn>
                  </template>
                  <template v-else-if="file.error">
                    <div class="caption error--text">
                      Download failed
                    </div>
                    <v-btn
                      color="primary"
                      x-small
                      icon
                      @click="refreshFile(i)">
                      <v-icon>
                        mdi-refresh
                      </v-icon>
                    </v-btn>
                  </template>
                  <div
                    v-else-if="file.progress >= 100"
                    class="caption success--text">
                    Download completed
                  </div>
                  <template v-else>
                    <div class="caption primary--text">
                      {{ file.progress | toLocaleString }}%
                    </div>
                    <v-btn
                      color="secondary"
                      x-small
                      icon
                      @click="cancelFile(i)">
                      <v-icon>
                        mdi-close
                      </v-icon>
                    </v-btn>
                  </template>
                </div>
                <v-progress-linear
                  :value="file.progress"
                  :buffer-value="100"
                  :color="file.error ? 'error': (file.progress < 100 ? 'primary' : 'success')"
                  background-color="#E4E4E4" />
              </div>
            </div>
          </div>
          <div class="export__actions">
            <v-btn
              v-if="completed"
              color="success"
              depressed
              rounded
              small
              @click="setDialog(false)">
              เสร็จสิ้น
            </v-btn>
            <v-btn
              v-else
              color="error"
              outlined
              rounded
              small
              @click="cancelFileAll()">
              หยุดทั้งหมด
            </v-btn>
          </div>
        </v-card-text>
        <template v-else>
          <v-card-text>
            <v-row>
              <v-col cols="6">
                <div class="grey--text mb-2">
                  ประเภทของไฟล์:
                </div>
                <v-select
                  v-model="fileType"
                  :items="fileTypes"
                  :style="{ width: '165px'}"
                  outlined
                  dense
                  @input="changeFileType($event)" />
              </v-col>
              <v-col cols="6">
                <v-radio-group
                  v-model="mode"
                  row>
                  <v-radio
                    label="แยกไฟล์"
                    value="MANY_FILE"
                    class="my-2" />
                  <v-radio
                    :disabled="['.csv'].includes(fileType)"
                    label="แยกเวิร์กชีต"
                    value="MANY_SHEET"
                    class="my-2" />
                  <v-radio
                    label="ไฟล์เดียว"
                    value="SINGLE_FILE"
                    class="my-2" />
                </v-radio-group>
              </v-col>
              <v-col cols="12">
                <div class="grey--text mb-2">
                  {{ perPageLabel }}:
                </div>
                <div class="w-100">
                  <v-slider
                    v-model="perPage"
                    :disabled="['SINGLE_FILE'].includes(mode)"
                    :max="max"
                    :min="min"
                    :step="step"
                    :thumb-size="16"
                    class="slider-perpage"
                    thumb-label="always" />
                </div>
              </v-col>
            </v-row>
          </v-card-text>
          <v-card-actions>
            <v-spacer />
            <v-btn
              class="px-4"
              color="primary"
              text
              rounded
              @click="close()">
              {{ $t('app.cancel') }}
            </v-btn>
            <v-btn
              :loading="submitting"
              :disabled="disabled"
              class="px-4"
              color="primary"
              depressed
              rounded
              @click="submit()">
              Export file
            </v-btn>
          </v-card-actions>
        </template>
      </v-card>
    </v-dialog>
  </div>
</template>

<script>
import { ExportToCsv } from 'export-to-csv'
import * as XLSX from 'xlsx'
import Queue from 'js-queue'

export default {
  props: {
    headers: {
      type: Array,
      default: () => ([])
    },
    callback: {
      type: Function,
      default: () => {}
    },
    min: {
      type: Number,
      default: 100
    },
    serverSidePerPage: {
      type: Number,
      default: 100
    },
    max: {
      type: Number,
      default: 1000
    },
    step: {
      type: Number,
      default: 50
    },
    delay: {
      type: Number,
      default: 150
    },
    extract: Boolean,
    disabled: Boolean
  },
  data () {
    return {
      submitting: false,
      dialog: false,
      perPage: 500,
      mode: 'MANY_FILE',
      fileTypes: ['.xlsx', '.csv'],
      fileType: '.xlsx',
      files: []
    }
  },
  computed: {
    perPageLabel () {
      return this.mode === 'MANY_SHEET' ? 'จำนวนแถวต่อเวิร์กชีต' : 'จำนวนแถวต่อไฟล์'
    },
    completed () {
      return this.files.every((file) => file.progress >= 100)
    }
  },
  methods: {
    changeFileType (fileType) {
      if (fileType === '.csv' && this.mode === 'MANY_SHEET') {
        this.mode = 'MANY_FILE'
      }
    },
    exportCSV (raws = [], filename = `Data-${ +new Date() }`) {
      const context = new ExportToCsv({
        fieldSeparator: ',',
        filename,
        quoteStrings: '"',
        decimalSeparator: '.',
        showLabels: true,
        showTitle: false,
        title: '',
        useTextFile: false,
        useBom: true,
        useKeysAsHeaders: false,
        headers: this.headers
      })
      const values = raws.length ? raws : [{}]
      context.generateCsv(values)
    },
    exportExcel (sheets = [], options) {
      const wb = XLSX.utils.book_new()
      sheets.forEach((sheet) => {
        const newWs = XLSX.utils.aoa_to_sheet(this.$_.concat([this.headers], sheet))
        XLSX.utils.book_append_sheet(wb, newWs)
      })
      XLSX.writeFile(wb, `${ options.filename }.xlsx`)
    },
    extractItems (raws = []) {
      let items = []
      raws.forEach((row) => {
        const cols = []
        let subRows = []
        row.forEach((col) => {
          if (this.$_.isArray(col)) {
            subRows = this.$_.concat(subRows, col)
          } else {
            cols.push(col)
          }
        })
        items.push(cols)
        items = this.$_.concat(items, subRows)
      })
      return items
    },
    export (raws = [], options = {}) {
      if (this.fileType === '.csv') {
        this.exportCSV(raws, options.filename)
      } else if (this.fileType === '.xlsx') {
        if (options.split) {
          this.exportExcel(raws, options)
        } else {
          this.exportExcel([raws], options)
        }
      }
    },
    refreshFile (index) {
      this.files[index].cancelled = false
      this.loadFile(this.files[index], index)
    },
    cancelFile (index) {
      this.files[index].cancelled = true
      this.loadFile(this.files[index], index)
    },
    cancelFileAll () {
      const files = this.files
      files.forEach((file, i) => {
        if (file.progress < 100 && !file.error) {
          this.cancelFile(i)
        }
      })
    },
    async submit () {
      if (this.disabled) return
      this.submitting = true
      this.files = []
      const { nodesCount } = await this.callback({
        page: 1,
        perPage: 1
      })
      const time = this.$dayjs(new Date()).format('DD-MM-YYYY')
      if (!nodesCount) {
        this.files.push({
          filename: `Data_${ time }`,
          page: 1,
          perPage: 1,
          nodesCount,
          progress: 0
        })
        await this.loadFiles(this.files)
      } else if (this.mode === 'MANY_FILE') {
        const perPage = this.perPage
        const filesCount = Math.ceil(nodesCount / perPage)
        for (let fileNumber = 1; fileNumber <= filesCount; fileNumber++) {
          const nextPage = fileNumber
          let nextPerPage = perPage
          if (fileNumber === 1) {
            nextPerPage = perPage > nodesCount ? nodesCount : perPage
          }
          this.files.push({
            filename: `Data-Page-${fileNumber}_${ time }`,
            page: nextPage,
            perPage: nextPerPage,
            nodesCount,
            progress: 0
          })
        }
        await this.loadFiles(this.files)
      } else if (this.mode === 'MANY_SHEET') {
        this.files.push({
          filename: `Data-Sheets_${ time }`,
          page: 1,
          perPage: nodesCount,
          progress: 0,
          nodesCount,
          split: this.perPage
        })
        await this.loadFiles(this.files)
      } else {
        this.files.push({
          filename: `Data_${ time }`,
          page: 1,
          perPage: nodesCount,
          nodesCount,
          progress: 0
        })
        await this.loadFiles(this.files)
      }
      this.submitting = false
    },
    async loadFile (file, i) {
      if (file.cancelled) return Promise.resolve()
      const self = this
      const serverSidePerPage = this.perPage < this.serverSidePerPage ? this.min : this.serverSidePerPage
      try {
        const { filename, page, perPage } = file
        self.files[i].progress = 0
        const lastResultCount = (page * perPage) - perPage
        const lastPage = Math.ceil(lastResultCount / serverSidePerPage)
        const lastServerResultCount = lastPage * serverSidePerPage
        const results = []
        const queue = new Queue()
        const hasFraction = lastServerResultCount > lastResultCount
        const serverSidePage = hasFraction ? lastPage - 1 : lastPage
        const pagesCount = Math.ceil(perPage / serverSidePerPage)
        const step = 100 / pagesCount
        for (let j = 1; j <= pagesCount; j++) {
          queue.add(() => {
            if (file.cancelled) {
              queue.next()
              return
            }
            this.callback({
              page: serverSidePage + j,
              perPage: serverSidePerPage
            }).then((res) => {
              if (!file.cancelled) {
                self.files[i].progress += step
              }
              results.push(res)
              setTimeout(() => {
                queue.next()
              }, this.delay)
            })
          })
        }
        return new Promise((resolve) => {
          queue.add(() => {
            if (file.cancelled) {
              queue.next()
              resolve()
              return
            }
            let raws = []
            results.forEach(({ nodes }) => {
              raws = this.$_.concat(raws, nodes)
            })
            if (hasFraction) {
              const fraction = serverSidePerPage - (lastServerResultCount - lastResultCount)
              raws = this.$_.slice(raws, fraction)
            }
            raws = this.$_.take(raws, perPage)
            if (this.extract) {
              raws = this.extractItems(raws)
            }
            const chunked = file.split > 0
            if (chunked) {
              raws = this.$_.chunk(raws, file.split)
            }
            this.export(raws, { filename, split: chunked })
            queue.next()
            resolve()
          })
        })
      } catch {
        this.files[i].error = true
        return Promise.resolve()
      }
    },
    loadFiles (files = []) {
      const queue = new Queue()
      const promises = []
      files.forEach((v, i) => {
        const file = v
        const index = i
        queue.add(() => {
          promises.push(
            this.loadFile(file, index)
              .then((res) => {
                setTimeout(() => {
                  queue.next()
                }, this.delay)
                return res
              })
          )
        })
      })
      return new Promise((resolve) => {
        queue.add(() => {
          queue.next()
          resolve(promises)
        })
      })
    },
    changeDialog (dialog) {
      if (dialog) {
        this.files = []
      }
    },
    close () {
      this.cancelFileAll()
      this.submitting = false
      this.setDialog(false)
    },
    setDialog (dialog = false) {
      this.dialog = dialog
    }
  }
}
</script>

<style lang="scss" scoped>
  .snt-export {
    &__content {
      .slider-perpage {
        ::v-deep {
          .v-slider__thumb-label {
            background-color: transparent !important;
            color: var(--v-primary-base);
            transform: translateY(-20%) translateY(-8px) translateX(-50%) rotate(45deg) !important;
          }
        }
      }
      .export__actions {
        position: sticky;
        bottom: -19px;
        padding: 20px 0;
        transition: opacity 300ms;
        border: 0;
        background: white;
        text-align: right;
      }
    }
  }
</style>
