<script setup lang="ts">
import { CircleStencil, Cropper, RectangleStencil } from 'vue-advanced-cropper'
import 'vue-advanced-cropper/dist/style.css'

const props = defineProps<{
  file: File | null
  variant: 'avatar' | 'headerIcon'
}>()

const emit = defineEmits<{
  confirm: [file: File]
}>()

const open = defineModel<boolean>('open', { default: false })

const imageSrc = ref<string | null>(null)
const cropperRef = ref<{ getResult: () => { canvas?: HTMLCanvasElement } } | null>(null)
const confirming = ref(false)

const title = computed(() =>
  props.variant === 'avatar' ? '裁剪头像' : '裁剪公开主页顶栏图标',
)

const stencilComponent = computed(() =>
  props.variant === 'avatar' ? CircleStencil : RectangleStencil,
)

const stencilProps = { aspectRatio: 1 }

const outputMaxSide = computed(() => (props.variant === 'avatar' ? 512 : 512))

watch(
  () => ({ isOpen: open.value, f: props.file }),
  ({ isOpen, f }) => {
    if (imageSrc.value) {
      URL.revokeObjectURL(imageSrc.value)
      imageSrc.value = null
    }
    if (isOpen && f) {
      imageSrc.value = URL.createObjectURL(f)
    }
  },
  { immediate: true },
)

onBeforeUnmount(() => {
  if (imageSrc.value) {
    URL.revokeObjectURL(imageSrc.value)
  }
})

function canvasToOutputFile(
  source: HTMLCanvasElement,
  mime: string,
  maxSide: number,
  filename: string,
): Promise<File> {
  let w = source.width
  let h = source.height
  let canvas = source
  if (w > maxSide || h > maxSide) {
    const scale = maxSide / Math.max(w, h)
    w = Math.round(w * scale)
    h = Math.round(h * scale)
    const c = document.createElement('canvas')
    c.width = w
    c.height = h
    const ctx = c.getContext('2d')
    if (!ctx) {
      throw new Error('无法创建画布上下文')
    }
    ctx.drawImage(source, 0, 0, w, h)
    canvas = c
  }
  const quality = mime === 'image/jpeg' ? 0.92 : mime === 'image/webp' ? 0.9 : undefined
  return new Promise((resolve, reject) => {
    canvas.toBlob(
      (blob) => {
        if (!blob) {
          reject(new Error('导出图片失败'))
          return
        }
        resolve(new File([blob], filename, { type: blob.type }))
      },
      mime,
      quality,
    )
  })
}

function pickOutputMime(original: File | null): { mime: string; ext: string } {
  const t = original?.type
  if (t === 'image/png') {
    return { mime: 'image/png', ext: 'png' }
  }
  if (t === 'image/webp') {
    return { mime: 'image/webp', ext: 'webp' }
  }
  return { mime: 'image/jpeg', ext: 'jpg' }
}

async function onConfirm() {
  const cropper = cropperRef.value
  if (!cropper || !props.file) {
    return
  }
  confirming.value = true
  try {
    const { canvas } = cropper.getResult()
    if (!canvas) {
      throw new Error('无法生成裁剪图')
    }
    const { mime, ext } = pickOutputMime(props.file)
    const base = props.file.name.replace(/\.[^.]+$/, '') || 'image'
    const out = await canvasToOutputFile(canvas, mime, outputMaxSide.value, `${base}-cropped.${ext}`)
    emit('confirm', out)
    open.value = false
  } finally {
    confirming.value = false
  }
}
</script>

<template>
  <UModal
    v-model:open="open"
    :title="title"
    description="拖动、缩放图片以选取区域；确认后将上传裁剪后的图片。"
    class="w-[calc(100vw-2rem)] sm:max-w-lg"
  >
    <template #body>
      <div v-if="imageSrc" class="space-y-3">
        <Cropper
          ref="cropperRef"
          class="min-h-[280px] h-[min(52vh,420px)] rounded-lg bg-elevated"
          :src="imageSrc"
          :stencil-component="stencilComponent"
          :stencil-props="stencilProps"
        />
        <p class="text-xs text-muted">
          {{ variant === 'avatar' ? '头像将显示为圆形，建议把主体放在圆内。' : '顶栏图标为方形显示。' }}
        </p>
      </div>
    </template>
    <template #footer="{ close }">
      <div class="flex justify-end gap-2">
        <UButton type="button" variant="outline" color="neutral" @click="close">
          取消
        </UButton>
        <UButton type="button" :loading="confirming" :disabled="!imageSrc" @click="onConfirm">
          确认并上传
        </UButton>
      </div>
    </template>
  </UModal>
</template>
