<template>
  <el-dialog
    v-model="visible"
    :title="title"
    :width="width"
    append-to-body
    destroy-on-close
    class="file-preview-dialog"
  >
    <div class="toolbar">
      <div class="left">
        <el-select
          v-if="normalizedUrls.length > 1"
          v-model="activeUrl"
          filterable
          class="file-select"
          placeholder="选择文件"
        >
          <el-option
            v-for="(u, idx) in normalizedUrls"
            :key="u + idx"
            :label="guessNameFromUrl(u)"
            :value="u"
          />
        </el-select>
        <div v-else class="file-name" :title="activeName">
          {{ activeName }}
        </div>
        <el-tag size="small" type="info" class="file-type">{{ activeExt || 'file' }}</el-tag>
      </div>

      <div class="right">
        <el-button :disabled="!activeUrl" type="primary" plain @click="download">
          <Icon icon="ep:download" class="mr-5px" />
          下载
        </el-button>
      </div>
    </div>

    <div class="content" :style="{ height }">
      <el-empty v-if="!activeUrl" description="暂无可预览文件" />

      <template v-else>
        <el-alert
          v-if="previewHint"
          :title="previewHint"
          type="warning"
          show-icon
          :closable="false"
          class="hint"
        />

        <!-- 图片 -->
        <div v-if="activeKind === 'image'" class="stage">
          <el-image
            :src="activeUrl"
            fit="contain"
            :preview-src-list="[activeUrl]"
            preview-teleported
            class="img"
          />
        </div>

        <!-- PDF -->
        <div v-else-if="activeKind === 'pdf'" class="stage">
          <div v-if="pdfLoading" class="loading-state">
            <el-icon class="is-loading" style="font-size: 24px; color: var(--el-color-primary)">
              <Loading />
            </el-icon>
            <p style="margin-top: 10px; color: var(--el-text-color-secondary)">正在加载 PDF...</p>
          </div>
          <el-empty v-else-if="pdfError" :description="pdfError" />
          <iframe
            v-else
            class="frame"
            :src="pdfBlobUrl || pdfIframeSrc"
            title="PDF Preview"
            frameborder="0"
          ></iframe>
        </div>

        <!-- Word -->
        <div v-else-if="activeKind === 'word'" class="stage">
          <iframe
            v-if="officeIframeSrc"
            class="frame"
            :src="officeIframeSrc"
            title="Office Preview"
            frameborder="0"
          ></iframe>
          <el-empty v-else description="该文件无法直接预览" />
        </div>

        <!-- 其它 -->
        <div v-else class="stage">
          <el-empty description="暂不支持该格式预览" />
        </div>
      </template>
    </div>
  </el-dialog>
</template>

<script setup lang="ts">
import { computed, ref, watch } from 'vue'

defineOptions({ name: 'FilePreviewDialog' })

type OfficeViewer = 'microsoft'

const props = withDefaults(
  defineProps<{
    modelValue: boolean
    title?: string
    width?: string | number
    height?: string
    url?: string
    urls?: string[]
    name?: string
    useOfficeViewer?: boolean
    officeViewer?: OfficeViewer
  }>(),
  {
    title: '文件预览',
    width: '1100px',
    height: '72vh',
    url: '',
    urls: () => [],
    name: '',
    useOfficeViewer: true,
    officeViewer: 'microsoft'
  }
)

const emit = defineEmits<{
  (e: 'update:modelValue', v: boolean): void
}>()

const visible = computed({
  get: () => props.modelValue,
  set: (v: boolean) => emit('update:modelValue', v)
})

const normalizedUrls = computed(() => {
  const list = [...(props.urls || []), props.url].filter(Boolean) as string[]
  const uniq: string[] = []
  for (const u of list) {
    if (!uniq.includes(u)) uniq.push(u)
  }
  return uniq
})

const activeUrl = ref<string>('')

watch(
  () => normalizedUrls.value,
  (list) => {
    activeUrl.value = list[0] || ''
  },
  { immediate: true }
)

const activeName = computed(() => {
  if (!activeUrl.value) return ''
  if (props.name) return props.name
  return guessNameFromUrl(activeUrl.value)
})

const activeExt = computed(() => getFileExt(activeName.value || activeUrl.value))

type Kind = 'image' | 'pdf' | 'word' | 'other'
const activeKind = computed<Kind>(() => {
  const ext = activeExt.value
  if (['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg'].includes(ext)) return 'image'
  if (ext === 'pdf') return 'pdf'
  if (ext === 'doc' || ext === 'docx') return 'word'
  return 'other'
})

const isHttpUrl = (u: string) => /^https?:\/\//i.test(u)

const officeIframeSrc = computed(() => {
  if (!props.useOfficeViewer) return ''
  if (!activeUrl.value) return ''
  if (!isHttpUrl(activeUrl.value)) return ''
  if (props.officeViewer !== 'microsoft') return ''
  return `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(activeUrl.value)}`
})

const pdfIframeSrc = computed(() => {
  if (!activeUrl.value) return ''
  // Chrome 内置 PDF 预览支持 #toolbar=0
  return `${activeUrl.value}#toolbar=0&navpanes=0&scrollbar=0`
})

const pdfBlobUrl = ref<string>('')
const pdfLoading = ref(false)
const pdfError = ref<string>('')

watch(
  () => activeUrl.value,
  async (url) => {
    if (!url || activeKind.value !== 'pdf') {
      pdfBlobUrl.value = ''
      return
    }

    pdfLoading.value = true
    pdfError.value = ''

    try {
      const response = await fetch(url, {
        method: 'GET',
        mode: 'cors'
      })

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`)
      }

      const contentType = response.headers.get('content-type') || ''
      if (!contentType.includes('application/pdf')) {
        throw new Error('非 PDF 文件类型')
      }

      const blob = await response.blob()
      const blobUrl = URL.createObjectURL(blob)

      if (pdfBlobUrl.value) {
        URL.revokeObjectURL(pdfBlobUrl.value)
      }

      pdfBlobUrl.value = blobUrl
    } catch (error) {
      console.error('PDF 加载失败:', error)
      pdfError.value = error instanceof Error ? error.message : 'PDF 加载失败'
      pdfBlobUrl.value = url
    } finally {
      pdfLoading.value = false
    }
  },
  { immediate: true }
)

const previewHint = computed(() => {
  if (!activeUrl.value) return ''
  if (activeKind.value === 'word' && !officeIframeSrc.value) {
    return 'Word 预览需要可被 Office 在线服务访问的完整 URL（通常要求公网/无鉴权），否则建议后端转 PDF 再预览。'
  }
  return ''
})

const download = () => {
  if (!activeUrl.value) return
  const a = document.createElement('a')
  a.href = activeUrl.value
  a.download = activeName.value || ''
  a.target = '_blank'
  a.rel = 'noopener noreferrer'
  document.body.appendChild(a)
  a.click()
  a.remove()
}

function getFileExt(input: string) {
  const clean = input.split('?')[0].split('#')[0]
  const dot = clean.lastIndexOf('.')
  if (dot < 0) return ''
  return clean.substring(dot + 1).toLowerCase()
}

function guessNameFromUrl(u: string) {
  try {
    const url = new URL(u, window.location.origin)
    const name = url.pathname.split('/').filter(Boolean).pop() || ''
    return decodeURIComponent(name) || u
  } catch {
    const clean = u.split('?')[0].split('#')[0]
    const name = clean.split('/').filter(Boolean).pop() || clean
    try {
      return decodeURIComponent(name)
    } catch {
      return name
    }
  }
}
</script>

<style scoped lang="scss">
.file-preview-dialog {
  :deep(.el-dialog__body) {
    padding-top: 10px;
  }
}

.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 0 0 10px;
  border-bottom: 1px solid var(--el-border-color-lighter);
}

.left {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.file-select {
  width: min(520px, 48vw);
}

.file-name {
  max-width: min(520px, 48vw);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--el-text-color-primary);
  font-weight: 600;
}

.file-type {
  flex: none;
}

.right {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
}

.content {
  margin-top: 12px;
  position: relative;
}

.hint {
  margin-bottom: 10px;
}

.stage {
  height: 100%;
  border: 1px solid var(--el-border-color-lighter);
  border-radius: 10px;
  overflow: hidden;
  background: linear-gradient(180deg, rgba(0, 0, 0, 0.02), rgba(0, 0, 0, 0.01));
}

.img {
  width: 100%;
  height: 100%;
}

.frame {
  width: 100%;
  height: 100%;
  border: 0;
  background: #fff;
}
</style>
