<script setup lang="ts">
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { fetchAdminFileBlob, fetchFileBlobByURL } from '@/shared/api/files'
import { isAdminPath } from '@/shared/utils/adminPath'

const props = withDefaults(
  defineProps<{
    source: string
    alt?: string
    admin?: boolean
    imageClass?: string
    fallbackClass?: string
    loading?: 'eager' | 'lazy'
    decoding?: 'async' | 'sync' | 'auto'
    previewSrcList?: string[]
    previewTeleported?: boolean
    previewInitialIndex?: number
    fit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down'
    imageStyle?: any
  }>(),
  {
    alt: '',
    admin: false,
    imageClass: '',
    fallbackClass: '',
    loading: 'lazy',
    decoding: 'async',
    previewTeleported: true,
    previewInitialIndex: 0,
  }
)

const imageURL = ref('')
const previewURLs = ref<string[]>([])
const failed = ref(false)
const createdURLs: string[] = []

const usePreview = computed(() => !!props.previewSrcList?.length)
const effectiveAdmin = computed(() => props.admin || isAdminPath(window.location.pathname))
const fallbackText = computed(() => (failed.value ? '图片加载失败' : '图片加载中'))
const imageStyleValue = computed(() => {
  if (!props.fit) return props.imageStyle
  return [props.imageStyle, { objectFit: props.fit }]
})

function extractObjectKey(value: string) {
  try {
    const parsed = new URL(value, window.location.origin)
    return parsed.searchParams.get('key') || ''
  } catch {
    return ''
  }
}

function shouldFetchWithAuth(value: string) {
  return value.includes('/api/files/object') || value.includes('/api/admin/files/object')
}

function shouldFetchAsAdmin(value: string) {
  return effectiveAdmin.value || value.includes('/api/admin/files/object')
}

async function resolveURL(url: string): Promise<string> {
  if (!url || !shouldFetchWithAuth(url)) return url
  const key = extractObjectKey(url)
  const blob =
    shouldFetchAsAdmin(url) && key ? await fetchAdminFileBlob(key) : await fetchFileBlobByURL(url)
  const blobURL = URL.createObjectURL(blob)
  createdURLs.push(blobURL)
  return blobURL
}

function cleanup() {
  createdURLs.forEach(u => URL.revokeObjectURL(u))
  createdURLs.length = 0
}

async function loadImage() {
  cleanup()
  imageURL.value = ''
  previewURLs.value = []
  failed.value = false

  if (!props.source) {
    failed.value = true
    return
  }

  try {
    imageURL.value = await resolveURL(props.source)
    if (props.previewSrcList?.length) {
      previewURLs.value = await Promise.all(props.previewSrcList.map(resolveURL))
    }
  } catch {
    failed.value = true
  }
}

watch(() => [props.source, effectiveAdmin.value, props.previewSrcList] as const, loadImage, {
  immediate: true,
  deep: true,
})

onBeforeUnmount(cleanup)
</script>

<template>
  <el-image
    v-if="imageURL && usePreview"
    :class="imageClass"
    :src="imageURL"
    :alt="alt"
    :fit="fit"
    :style="imageStyle"
    :preview-src-list="previewURLs"
    :preview-teleported="previewTeleported"
    :initial-index="previewInitialIndex"
  />
  <img
    v-else-if="imageURL"
    :class="imageClass"
    :src="imageURL"
    :style="imageStyleValue"
    :alt="alt"
    :loading="loading"
    :decoding="decoding"
  />
  <span v-else :class="['auth-image-fallback', fallbackClass]">{{ fallbackText }}</span>
</template>

<style scoped>
.auth-image-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 120px;
  border: 1px dashed #d8dee8;
  border-radius: 12px;
  background: #f8fafc;
  color: #94a3b8;
  font-size: 13px;
  font-weight: 700;
}
</style>
