<template>
  <snt-loading
    :loading="loading"
    min-height="300px"
    class="printer-preview">
    <div class="d-flex flex-column">
      <slot
        :submit="submit"
        :download="download"
        :currentPage="currentPage"
        :pagesCount="pagesCount"
        :goToPrevPage="goToPrevPage"
        :goToNextPage="goToNextPage"
        name="header">
        <div class="d-flex justify-center align-center gap-6 mb-3">
          <v-btn
            :ripple="false"
            :disabled="currentPage <= 1"
            color="primary"
            icon
            @click="goToPrevPage()">
            <v-icon>mdi-chevron-left</v-icon>
          </v-btn>
          <div>{{ currentPage }} of {{ pagesCount }}</div>
          <v-btn
            :ripple="false"
            :disabled="currentPage >= pagesCount"
            color="primary"
            icon
            @click="goToNextPage()">
            <v-icon>mdi-chevron-right</v-icon>
          </v-btn>
        </div>
      </slot>
      <div
        ref="printerPreview"
        class="printer-preview__container modern-scrollbar overflow-auto pa-0 px-md-8 py-md-6">
        <div
          :style="{
            height: `${height * aspectRatio}px`,
            width: `${width * aspectRatio}px`
          }"
          class="mx-auto">
          <SntPaper
            id="printable"
            :items="pages"
            :height="height"
            :width="width"
            :inner-height="innerHeight"
            :inner-width="innerWidth"
            :aspect-ratio="aspectRatio" />
        </div>
      </div>
      <slot
        :submit="submit"
        :download="download"
        :currentPage="currentPage"
        :pagesCount="pagesCount"
        :goToPrevPage="goToPrevPage"
        :goToNextPage="goToNextPage"
        name="footer">
        <div class="d-flex gap-3 pa-4">
          <v-spacer />
          <v-btn
            class="pr-4"
            color="primary"
            depressed
            rounded
            @click="download()">
            <v-icon left>
              mdi-download
            </v-icon>
            Download
          </v-btn>
          <v-btn
            color="primary"
            depressed
            rounded
            @click="submit()">
            พิมพ์
          </v-btn>
        </div>
      </slot>
    </div>
  </snt-loading>
</template>

<script>
import Vue from 'vue'
import printJS from 'print-js'
import {
  VBtn,
  VIcon,
  VSpacer
} from 'vuetify/lib'
import VNodeElement from './components/VNodeElement.vue'
import SntPaper from './SntPaper.vue'
import { SntLoading } from '../SntLoading'
import { savePdf } from '../../utils/html-convert'

const convertToPixel = (number) => `${number}px`
export default {
  components: {
    VBtn,
    VIcon,
    VSpacer,
    SntPaper,
    SntLoading
  },
  props: {
    items: {
      type: Array,
      default: () => ([])
    },
    headers: {
      type: Array,
      default: () => ([])
    },
    footers: {
      type: Array,
      default: () => ([])
    },
    callback: {
      type: Function,
      default: () => {}
    },
    width: {
      type: Number,
      default: 0
    },
    height: {
      type: Number,
      default: 0
    },
    innerWidth: {
      type: Number,
      default: 0
    },
    innerHeight: {
      type: Number,
      default: 0
    },
    aspectRatio: {
      type: [Number],
      default: 1
    }
  },
  data  () {
    return {
      currentPage: 1,
      loading: false,
      callbackItems: []
    }
  },
  computed: {
    isSinglePage () {
      return !this.height
    },
    pages () {
      const innerPages = this.callbackItems.map((item) => this.getPages(this.items, item, true)).flat()
      if (this.headers.length) {
        innerPages.unshift({
          data: {},
          el: this.headers
        })
      }
      if (this.footers.length) {
        innerPages.push({
          data: {},
          el: this.footers
        })
      }
      if (this.isSinglePage) return [innerPages]
      const cols = this.$_.floor(this.width / this.innerWidth) || 1
      const rows = this.$_.floor(this.height / this.innerHeight) || 1
      const perPage = cols * rows
      return this.$_.chunk(innerPages, perPage)
    },
    pagesCount () {
      return this.pages.length
    }
  },
  mounted () {
    this.getCallback()
  },
  methods: {
    async getCallback (perPage = 50) {
      if (!this.callback) return new Promise()
      this.loading = true
      const { pagesCount } = await this.callback({
        page: 1,
        perPage
      })
      this.callbackItems = []
      this.$queue.beginTransition()
      this.$_.times(pagesCount, (i) => {
        const page = i + 1
        this.$queue.add(() => this.callback({
          page,
          perPage
        }).then(({ nodes }) => {
          this.callbackItems = this.$_.concat(this.callbackItems, nodes)
          this.$queue.next()
        }))
      })
      const result = new Promise((resolve) => {
        this.$queue.add(() => {
          this.loading = false
          resolve(this.callbackItems)
        })
      })
      this.$queue.endTransition()
      return result
    },
    goToPrevPage () {
      return this.goToPage(this.currentPage - 1)
    },
    goToNextPage () {
      return this.goToPage(this.currentPage + 1)
    },
    goToPage (page = 1) {
      if (page > 1) {
        this.currentPage = page > this.pagesCount ? this.pagesCount : page
      } else {
        this.currentPage = 1
      }
      const element = document.getElementById(`printer-${this.currentPage}`)
      const top = element.offsetTop
      this.$refs.printerPreview.scrollTo({ top: top - 250, behavior: 'smooth' })
    },
    setPage (page = 1) {
      if (page > 1) {
        this.currentPage = page > this.pagesCount ? this.pagesCount : page
      } else {
        this.currentPage = 1
      }
    },
    getPages (items, data, repos = false, position = 0, page = 1) {
      const canvas = this.$_.cloneDeep(items)
      const table = canvas.find(({ type }) => type === 'products')
      const currCanvas = []
      const sorted = this.$_.sortBy(canvas, [(el) => parseInt(el.properties.style.top, 10)])
      const heading = this.$_.head(sorted)
      if (table) {
        const contentSorted = sorted.filter((item) => item.type !== 'header' && item.type !== 'footer')
        const header = this.$_.find(sorted, (item) => item.type === 'header')
        const footer = this.$_.find(sorted, (item) => item.type === 'footer')
        const innerHeight = this.pxToInt(footer?.properties.style.top) || this.innerHeight
        const tableTop = parseInt(table.properties.style.top, 10)
        const tableHeight = parseInt(table.properties.style.height, 10)
        const tableBottom = (tableTop + tableHeight) || 0
        const minHeightRow = 30
        const maxRowsCount = Math.ceil(innerHeight / minHeightRow)
        const nextCanvas = []
        let currentTableNewHeight = tableHeight
        let endTable = true
        contentSorted.forEach((el) => {
          const top = parseInt(el.properties.style.top, 10)
          const height = parseInt(el.properties.style.height, 10)
          const bottom = top + height
          const isUpperTable = top < tableBottom
          const isLowerTable = (bottom < innerHeight) && endTable
          if (el.type === 'products') {
            el.properties.style.height = '0px'
            if (!el.children[0].children[1].children[0].cursor) {
              el.children[0].children[1].children[0].cursor = 0
            }
            const { newPerPage, tableNewHeight, paging } = this.getHeightTable(el, data, maxRowsCount, innerHeight)
            currentTableNewHeight = tableNewHeight
            el.properties.style.height = ''
            el.children[0].children[1].children[0].perPage = newPerPage
            if (paging) {
              const cloneEl = this.$_.cloneDeep(el)
              cloneEl.properties.style.height = tableHeight
              cloneEl.children[0].children[1].children[0].cursor += newPerPage
              cloneEl.properties.style.top = header ? (header.properties.style.top + header.properties.style.height) : heading.properties.style.top
              nextCanvas.push(cloneEl)
              endTable = false
            }
            currCanvas.push(el)
          } else if (isUpperTable) {
            currCanvas.push(el)
          } else if (isLowerTable) {
            const oldPosition = repos ? tableBottom : position
            const marginTop = this.pxToInt(el.properties.style.top) - oldPosition
            const newPosition = tableTop + currentTableNewHeight
            const newTop = newPosition + marginTop
            if ((newTop < innerHeight)) {
              el.properties.style.top = convertToPixel(newTop)
              currCanvas.push(el)
            } else {
              nextCanvas.push(el)
            }
          } else {
            nextCanvas.push(el)
          }
        })
        if (header) {
          currCanvas.push(header)
        }
        if (footer) {
          currCanvas.push(footer)
        }
        if (nextCanvas.length && !this.isSinglePage) {
          if (header) {
            nextCanvas.push(header)
          }
          if (footer) {
            nextCanvas.push(footer)
          }
          const newPosition = repos ? tableBottom : position
          const result = [{
            el: currCanvas,
            data
          }, ...this.getPages(nextCanvas, data, false, newPosition, page + 1)]
          return result.map((item, i) => ({
            el: item.el,
            data: {
              ...item.data,
              page: {
                page: i + 1,
                pagesCount: result.length
              }
            }
          }))
        }
      } else {
        sorted.forEach((el) => {
          const top = this.pxToInt(el.properties.style.top)
          el.properties.style.top = convertToPixel(top - position)
          currCanvas.push(el)
        })
      }
      return [{
        el: currCanvas,
        data: {
          ...data,
          page: {
            page: 1,
            pagesCount: 1
          }
        }
      }]
    },
    getHeightTable (el, data, perPage = 20, innerHeight) {
      const cloneEl = this.$_.cloneDeep(el)
      const tableTop = this.pxToInt(cloneEl.properties.style.top)
      cloneEl.children[0].children[1].children[0].perPage = perPage
      const doc = document.createElement('div')
      const vnode = new (Vue.extend(VNodeElement))({
        el: doc,
        propsData: {
          element: cloneEl,
          item: data
        }
      })
      const container = document.body
      container.appendChild(vnode.$el)
      const child = vnode.$el.querySelector('table')
      const tableNewHeight = child.clientHeight
      container.removeChild(vnode.$el)
      const tableNewBottom = tableTop + tableNewHeight
      if (tableNewBottom > innerHeight) {
        const newPerPage = perPage - 1
        const trying = this.getHeightTable(cloneEl, data, newPerPage, innerHeight)
        return {
          ...trying,
          paging: true
        }
      }
      return {
        newPerPage: perPage,
        tableNewHeight,
        paging: false
      }
    },
    download () {
      const items = []
      this.$_.times(this.pagesCount, (i) => {
        items.push(`printer-${i + 1}`)
      })
      savePdf(items, { width: this.width, height: this.height })
    },
    submit () {
      const items = this.pages
      const width = this.width
      const height = this.height
      const innerHeight = this.innerHeight
      const innerWidth = this.innerWidth
      const el = document.createElement('div')
      const vm = new (Vue.extend(SntPaper))({
        el,
        propsData: {
          items,
          height,
          width,
          innerHeight,
          innerWidth
        }
      })
      vm.$mount(el)
      vm.$nextTick(() => {
        const html = vm.$el.innerHTML
        const div = document.createElement('div')
        div.innerHTML = html.trim()
        const mm = 0.2645833333
        const widthPaper = this.width * mm
        const heightPaper = this.height * mm
        printJS({
          printable: div.innerHTML,
          type: 'raw-html',
          style: `
            @page {
              size: ${widthPaper}mm ${heightPaper}mm;
              margin: 0.25in;
            }
            @media print {
              html, body {
                width: ${widthPaper}mm;
                height: ${heightPaper}mm;        
              }
            }
            html {
              -webkit-print-color-adjust: exact;
            }
            body {
              width: 100%;
              height: 100%;
              margin: 0;
              padding: 0;
            }
            * {
              color: #333;
            }
          `,
          targetStyles: ['*']
        })
      })
    },
    pxToInt (px) {
      return parseInt(px, 10)
    }
  }
}
</script>

<style lang="scss" scoped>
  .printer-preview {
    font-family: sans-serif;
    line-height: initial;
    &__container {
      display: flex;
      min-height: 300px;
      height: calc(100vh - 320px);
      background: #B5B5B5;
    }
  }
</style>
