<template>
  <div class="container">
    <div class="modal-height">
      <div class="file-box">
        <p class="file-name" v-show="!renderLoading">{{ file.filename }}</p>
        <div class="view-wrapper">
          <div class="preview-wrapper" ref="wrapper" v-if="isAbleView">
            <div class="preview-inner" ref="preview-inner">
              <div class="preview-mark" ref="bg-mark" id="bg-mark"></div>
              <div class="loading-container" v-show="renderLoading">
                <div class="loading-spinner">
                  <svg viewBox="0 0 50 50" class="circular">
                    <circle cx="25" cy="25" r="20" fill="none" class="path"></circle>
                  </svg>
                </div>
              </div>
              <div v-if="isImageType" class="viewfile-image" v-show="!renderLoading">
                <slot
                  name="image"
                  :src="imageUrl"
                  :src-list="imageSrcList"
                  :index="previewIndex"
                  :file="file"
                  :preview="previewApi"
                >
                  <img
                    class="viewfile-image-inner"
                    :src="imageUrl"
                    :alt="file.filename || ''"
                    @click="openImagePreview(0)"
                  />
                </slot>

                <div v-if="previewVisible" class="viewfile-image-preview" @click.self="closeImagePreview">
                  <button class="viewfile-image-preview-close" type="button" @click="closeImagePreview">×</button>
                  <button
                    v-if="imageSrcList.length > 1"
                    class="viewfile-image-preview-prev"
                    type="button"
                    @click.stop="prevImage"
                  >
                    ‹
                  </button>
                  <img class="viewfile-image-preview-img" :src="imageSrcList[previewIndex]" alt="" />
                  <button
                    v-if="imageSrcList.length > 1"
                    class="viewfile-image-preview-next"
                    type="button"
                    @click.stop="nextImage"
                  >
                    ›
                  </button>
                </div>
              </div>
              <div v-else class="output" v-show="!renderLoading" ref="output"></div>
            </div>
          </div>
          <view-other-component
            v-else-if="file.type && !isAbleView"
            :file="file"
          />
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, watch, nextTick, onBeforeUnmount, onMounted } from "vue"
import { readDataURL, render } from "./util"
import ViewOtherComponent from "./vendors/other/index.vue"


const props = defineProps({
  file: {
    type: Object,
    default: () => {
      return {}
    },
  },
})

const renderLoading = ref(false)
const output = ref(null)
const imageUrl = ref("")
const previewVisible = ref(false)
const previewIndex = ref(0)

// 可在在线预览文件类型
const isAbleView = computed(() => {
  const lowType = (props.file.type || "").toLowerCase()
  const typeObject = {
    doc: ["docx"],
    xlsx: ["xlsx"],
    pdf: ["pdf"],
    ppt: ["pptx"],
    image: ["gif", "jpg", "jpeg", "bmp", "tiff", "tif", "png", "svg"],
    text: [
      "txt",
      "json",
      "js",
      "css",
      "java",
      "py",
      "html",
      "jsx",
      "ts",
      "tsx",
      "xml",
      "md",
      "log",
    ],
  }
  const ablePreviewTypes = Object.values(typeObject).flat()
  return ablePreviewTypes.includes(lowType)
})

const isImageType = computed(() => {
  const lowType = (props.file.type || "").toLowerCase()
  return ["gif", "jpg", "jpeg", "bmp", "tiff", "tif", "png", "svg"].includes(lowType)
})

const imageSrcList = computed(() => {
  const src = String(imageUrl.value || "")
  return src ? [src] : []
})

const previewApi = computed(() => {
  return {
    visible: previewVisible.value,
    open: openImagePreview,
    close: closeImagePreview,
    next: nextImage,
    prev: prevImage,
  }
})

function openImagePreview(index = 0) {
  if (!imageSrcList.value.length) return
  previewIndex.value = Math.max(0, Math.min(imageSrcList.value.length - 1, Number(index) || 0))
  previewVisible.value = true
}

function closeImagePreview() {
  previewVisible.value = false
}

function nextImage() {
  const list = imageSrcList.value
  if (list.length <= 1) return
  previewIndex.value = (previewIndex.value + 1) % list.length
}

function prevImage() {
  const list = imageSrcList.value
  if (list.length <= 1) return
  previewIndex.value = (previewIndex.value - 1 + list.length) % list.length
}

function onKeydown(e) {
  if (!previewVisible.value) return
  if (e.key === "Escape") closeImagePreview()
  if (e.key === "ArrowRight") nextImage()
  if (e.key === "ArrowLeft") prevImage()
}

onMounted(() => {
  window.addEventListener("keydown", onKeydown)
})

onBeforeUnmount(() => {
  window.removeEventListener("keydown", onKeydown)
})

/**
 * @{params} buffer 文件字节流
 * @{params} extend 文件扩展名
 */
const renderResult = (buffer, extend) => {
  if (!output.value) {
    return Promise.reject(new Error("Output element not found"))
  }
  output.value.innerHTML = ""
  const node = document.createElement("div")
  const child = output.value.appendChild(node)
  return new Promise((resolve, reject) =>
    render(buffer, extend, child).then(resolve).catch(reject)
  )
}

watch(
  () => props.file,
  (newFile) => {
    if (newFile && newFile.type) {
      try {
        renderLoading.value = true
        nextTick(() => {
          if (isImageType.value) {
            imageUrl.value = ""
            previewVisible.value = false
            previewIndex.value = 0
            if (output.value) output.value.innerHTML = ""
            readDataURL(newFile.fileBuffer)
              .then((url) => {
                imageUrl.value = String(url || "")
              })
              .finally(() => {
                renderLoading.value = false
              })
            return
          }

          imageUrl.value = ""
          previewVisible.value = false
          previewIndex.value = 0
          renderResult(newFile.fileBuffer, newFile.type).finally(() => {
            renderLoading.value = false
          })
        })
      } catch (error) {
        console.warn(error)
      }
    }
  },
  { immediate: true, deep: true }
)
</script>

<style lang="less">
.container {
  width: 100%;
  height: 100%;
  .docx-wrapper {
    background: unset !important;
    padding: 0 !important;

    & > section.docx {
      background: unset !important;
    }
  }
}

.viewfile-image {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.viewfile-image-inner {
  max-width: 100%;
  max-height: 100%;
  display: block;
  object-fit: contain;
}

.viewfile-image-preview {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
}

.viewfile-image-preview-img {
  max-width: calc(100vw - 120px);
  max-height: calc(100vh - 120px);
  object-fit: contain;
  display: block;
}

.viewfile-image-preview-close,
.viewfile-image-preview-prev,
.viewfile-image-preview-next {
  position: absolute;
  border: 0;
  background: rgba(0, 0, 0, 0.36);
  color: #fff;
  cursor: pointer;
  line-height: 1;
}

.viewfile-image-preview-close {
  right: 16px;
  top: 16px;
  width: 40px;
  height: 40px;
  border-radius: 20px;
  font-size: 28px;
}

.viewfile-image-preview-prev,
.viewfile-image-preview-next {
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  border-radius: 22px;
  font-size: 28px;
}

.viewfile-image-preview-prev {
  left: 16px;
}

.viewfile-image-preview-next {
  right: 16px;
}
</style>
<style lang="less" scoped>
@import url("./index.less");
:deep(.header-rewrite) {
  margin-bottom: 0 !important;
}

.modal-height {
  display: flex;
  justify-content: space-between;
  height: 100%;
  .file-box {
    flex: 1;
    padding: 15px 40px;
    overflow: scroll;
    text-align: center;

    .file-name {
      line-height: 36px;
      text-align: center;
      font-size: 16px;
      font-weight: 500;
    }

    .view-wrapper {
      position: relative;
      width: 100%;
      height: calc(100% - 36px);
      overflow: hidden;
      .preview-inner {
        position: relative;
        width: 100%;
        height: 100%;
        
        .loading-container {
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
          z-index: 999;
          display: flex;
          flex-direction: column;
          align-items: center;
          
          .loading-spinner {
            width: 50px;
            height: 50px;
            
            .circular {
              animation: rotate 2s linear infinite;
              height: 100%;
              transform-origin: center center;
              width: 100%;
              position: absolute;
              top: 0;
              bottom: 0;
              left: 0;
              right: 0;
              margin: auto;
            }
            
            .path {
              stroke-dasharray: 90, 150;
              stroke-dashoffset: 0;
              stroke-width: 4;
              stroke-miterlimit: 10;
              stroke: #409eff;
              stroke-linecap: round;
              animation: dash 1.5s ease-in-out infinite;
            }
          }
          
          .loading-text {
            margin-top: 15px;
            color: #606266;
            font-size: 14px;
          }
        }
      }
    }
  }
}

@keyframes rotate {
  100% {
    transform: rotate(360deg);
  }
}

@keyframes dash {
  0% {
    stroke-dasharray: 1, 150;
    stroke-dashoffset: 0;
  }
  50% {
    stroke-dasharray: 90, 150;
    stroke-dashoffset: -35;
  }
  100% {
    stroke-dasharray: 90, 150;
    stroke-dashoffset: -124;
  }
}
</style>
