<template>
  <div v-if="visible" class="captcha-overlay">
    <div class="captcha-modal">
      <div class="captcha-header">
        <h3 class="captcha-title">安全验证</h3>
        <button class="close-btn" @click="handleClose">×</button>
      </div>
      <div class="captcha-content">
        <canvas ref="canvasRef" :width="canvasWidth" :height="canvasHeight" class="captcha-canvas"
          @click="handleCanvasClick"></canvas>
        <div class="captcha-instruction">
          <p>
            请依次点击 <span class="target-list">【{{ targetChars.join('、') }}】</span>
          </p>
        </div>
        <div class="captcha-status">
          <p v-if="errorMessage" class="error-message">{{ errorMessage }}</p>
          <p v-else-if="successMessage" class="success-message">{{ successMessage }}</p>
          <p v-else class="status-text">点击图片中的目标文字</p>
        </div>
      </div>
      <div class="captcha-footer">
        <button class="refresh-btn" @click="refreshCaptcha" :disabled="isRefreshing">
          {{ isRefreshing ? '刷新中...' : '刷新验证' }}
        </button>
        <button class="confirm-btn" :disabled="clickedList.length < 3 || !!successMessage" @click="handleConfirm">
          确认
        </button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, watch, nextTick } from 'vue'

const props = defineProps({
  visible: {
    type: Boolean,
    default: false,
  },
})
const emit = defineEmits(['success', 'close'])

const canvasRef = ref(null)
const canvasWidth = 300
const canvasHeight = 180
const bgImgUrl = '/image/clickCaptcha.jpg' // 可替换为任意背景图

const allChars = ['大', '数', '据', '一', '查', '风', '险', '报', '告']
const targetChars = ref(['一', '查', '查']) // 目标点击顺序固定
const charPositions = ref([]) // [{char, x, y, w, h}]
const clickedIndex = ref(0)
const errorMessage = ref('')
const successMessage = ref('')
const isRefreshing = ref(false)
const clickedList = ref([]) // [{char, idx, ...}]
let currentChars = [] // 当前乱序后的字顺序

function randomChars(count, except = []) {
  const pool = allChars.filter(c => !except.includes(c))
  const arr = []
  while (arr.length < count) {
    const c = pool[Math.floor(Math.random() * pool.length)]
    if (!arr.includes(c)) arr.push(c)
  }
  return arr
}

function randomColor() {
  return (
    '#' +
    Math.floor(Math.random() * 0xffffff)
      .toString(16)
      .padStart(6, '0')
  )
}

function drawCaptcha() {
  const canvas = canvasRef.value
  if (!canvas) return
  const ctx = canvas.getContext('2d')
  ctx.clearRect(0, 0, canvasWidth, canvasHeight)

  // 绘制背景图
  const bg = new window.Image()
  bg.src = bgImgUrl
  bg.onload = () => {
    ctx.drawImage(bg, 0, 0, canvasWidth, canvasHeight)
    // 绘制乱序文字
    charPositions.value.forEach(pos => {
      ctx.save()
      ctx.translate(pos.x + pos.w / 2, pos.y + pos.h / 2)
      ctx.rotate(pos.angle)
      ctx.font = 'bold 28px sans-serif'
      ctx.fillStyle = pos.color
      ctx.shadowColor = '#333'
      ctx.shadowBlur = 4
      ctx.fillText(pos.char, -pos.w / 2, pos.h / 2 - 8)
      ctx.restore()
    })
    // 绘制点击顺序标签
    clickedList.value.forEach((item, i) => {
      const pos = charPositions.value[item.idx]
      if (!pos) return
      ctx.save()
      ctx.beginPath()
      ctx.arc(pos.x + pos.w / 2, pos.y + 6, 12, 0, 2 * Math.PI)
      ctx.fillStyle = '#8CC6F7'
      ctx.fill()
      ctx.font = 'bold 16px sans-serif'
      ctx.fillStyle = '#fff'
      ctx.textAlign = 'center'
      ctx.textBaseline = 'middle'
      ctx.fillText((i + 1).toString(), pos.x + pos.w / 2, pos.y + 6)
      ctx.restore()
    })
  }
}

function refreshCaptcha() {
  isRefreshing.value = true
  setTimeout(() => {
    generateCaptcha()
    isRefreshing.value = false
  }, 500)
}

function generateCaptcha() {
  // 乱序排列7个字
  const chars = [...allChars]
  for (let i = chars.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1))
      ;[chars[i], chars[j]] = [chars[j], chars[i]]
  }
  currentChars = chars
  targetChars.value = ['一', '查', '查']
  clickedIndex.value = 0
  errorMessage.value = ''
  successMessage.value = ''
  clickedList.value = []
  // 生成每个字的坐标、角度、颜色
  charPositions.value = []
  const used = []
  chars.forEach((char, idx) => {
    let x,
      y,
      w = 36,
      h = 36,
      tryCount = 0
    do {
      x = Math.random() * (canvasWidth - w - 10) + 5
      y = Math.random() * (canvasHeight - h - 10) + 5
      tryCount++
    } while (used.some(pos => Math.abs(pos.x - x) < w && Math.abs(pos.y - y) < h) && tryCount < 20)
    used.push({ x, y })
    const angle = (Math.random() - 0.5) * 0.7
    const color = randomColor()
    charPositions.value.push({ char, x, y, w, h, idx, angle, color })
  })
  nextTick(drawCaptcha)
}

function handleCanvasClick(e) {
  if (successMessage.value) return
  // 适配缩放
  const rect = canvasRef.value.getBoundingClientRect()
  const scaleX = canvasWidth / rect.width
  const scaleY = canvasHeight / rect.height
  const x = (e.clientX - rect.left) * scaleX
  const y = (e.clientY - rect.top) * scaleY
  // 找到被点中的字
  const posIdx = charPositions.value.findIndex(
    pos => x >= pos.x && x <= pos.x + pos.w && y >= pos.y && y <= pos.y + pos.h
  )
  if (posIdx === -1) {
    errorMessage.value = '请点击目标文字'
    setTimeout(() => (errorMessage.value = ''), 1200)
    return
  }
  // 已经点过不能重复点
  if (clickedList.value.some(item => item.idx === posIdx)) return
  if (clickedList.value.length >= charPositions.value.length) return
  clickedList.value.push({ char: charPositions.value[posIdx].char, idx: posIdx })
  drawCaptcha()
}

function handleConfirm() {
  if (clickedList.value.length < 3) {
    errorMessage.value = '请依次点击3个字'
    setTimeout(() => (errorMessage.value = ''), 1200)
    return
  }
  const userSeq = clickedList.value
    .slice(0, 3)
    .map(item => item.char)
    .join('')
  if (userSeq === '真爱查') {
    successMessage.value = '验证成功！'
    setTimeout(() => emit('success'), 600)
  } else {
    errorMessage.value = '校验错误，请重试'
    setTimeout(() => {
      errorMessage.value = ''
      clickedList.value = []
      // 失败时重新打乱
      generateCaptcha()
    }, 1200)
  }
}

function handleClose() {
  emit('close')
}

onMounted(() => {
  if (props.visible) generateCaptcha()
})
watch(
  () => props.visible,
  v => {
    if (v) generateCaptcha()
  }
)
</script>

<style scoped>
.captcha-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
  padding: 1rem;
  backdrop-filter: blur(4px);
  animation: fadeIn 0.2s ease-out;
}

@keyframes fadeIn {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

.captcha-modal {
  background: #fff;
  border-radius: 1rem;
  width: 100%;
  max-width: 360px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05);
  overflow: hidden;
  animation: slideUp 0.3s ease-out;
}

@keyframes slideUp {
  from {
    transform: translateY(20px);
    opacity: 0;
  }

  to {
    transform: translateY(0);
    opacity: 1;
  }
}

.captcha-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.25rem 1.5rem;
  border-bottom: 1px solid var(--color-border-primary, #ebedf0);
  background: linear-gradient(135deg, var(--color-primary-light, rgba(140, 198, 247, 0.1)), #ffffff);
}

.captcha-title {
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--color-text-primary, #323233);
  margin: 0;
  background: linear-gradient(135deg, var(--color-primary, #8CC6F7), var(--color-primary-600, #709ec6));
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.close-btn {
  background: rgba(0, 0, 0, 0.05);
  border: none;
  font-size: 1.5rem;
  color: var(--color-text-secondary, #646566);
  cursor: pointer;
  padding: 0.375rem;
  width: 32px;
  height: 32px;
  border-radius: 0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s ease;
  line-height: 1;
}

.close-btn:hover {
  color: var(--color-text-primary, #323233);
  background: rgba(0, 0, 0, 0.1);
  transform: rotate(90deg);
}

.captcha-content {
  padding: 1.5rem 1.5rem 1rem 1.5rem;
  background: #ffffff;
}

.captcha-canvas {
  width: 100%;
  border-radius: 0.75rem;
  background: var(--color-bg-tertiary, #f8f8f8);
  display: block;
  margin: 0 auto;
  border: 2px solid var(--color-border-primary, #ebedf0);
  transition: all 0.2s ease;
  cursor: pointer;
}

.captcha-canvas:hover {
  border-color: var(--color-primary, #8CC6F7);
  box-shadow: 0 0 0 3px var(--color-primary-light, rgba(140, 198, 247, 0.1));
}

.captcha-instruction {
  margin: 1.25rem 0 0.75rem 0;
  text-align: center;
}

.captcha-instruction p {
  font-size: 0.95rem;
  color: var(--color-text-secondary, #646566);
  margin: 0;
  line-height: 1.5;
}

.target-list {
  color: var(--color-primary, #8CC6F7);
  font-weight: 600;
  font-size: 1.05rem;
  padding: 0.25rem 0.5rem;
  background: var(--color-primary-light, rgba(140, 198, 247, 0.1));
  border-radius: 0.375rem;
  display: inline-block;
}

.captcha-status {
  text-align: center;
  min-height: 1.75rem;
  margin-top: 0.5rem;
}

.error-message {
  color: var(--color-danger, #ee0a24);
  font-size: 0.875rem;
  margin: 0;
  padding: 0.5rem;
  background: rgba(238, 10, 36, 0.1);
  border-radius: 0.5rem;
  animation: shake 0.3s ease-in-out;
}

@keyframes shake {

  0%,
  100% {
    transform: translateX(0);
  }

  25% {
    transform: translateX(-5px);
  }

  75% {
    transform: translateX(5px);
  }
}

.success-message {
  color: var(--color-success, #07c160);
  font-size: 0.875rem;
  margin: 0;
  padding: 0.5rem;
  background: rgba(7, 193, 96, 0.1);
  border-radius: 0.5rem;
  animation: bounce 0.4s ease-out;
}

@keyframes bounce {

  0%,
  100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(-3px);
  }
}

.status-text {
  color: var(--color-text-tertiary, #969799);
  font-size: 0.875rem;
  margin: 0;
}

.captcha-footer {
  padding: 1.25rem 1.5rem;
  border-top: 1px solid var(--color-border-primary, #ebedf0);
  background: var(--color-bg-secondary, #fafafa);
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.refresh-btn {
  width: 100%;
  padding: 0.875rem;
  background: var(--color-primary, #8CC6F7);
  color: white;
  border: none;
  border-radius: 0.625rem;
  font-size: 0.95rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
  box-shadow: 0 2px 8px rgba(140, 198, 247, 0.3);
}

.refresh-btn:hover:not(:disabled) {
  background: var(--color-primary-dark, rgba(140, 198, 247, 0.8));
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(140, 198, 247, 0.4);
}

.refresh-btn:active:not(:disabled) {
  transform: translateY(0);
}

.refresh-btn:disabled {
  background: var(--color-text-disabled, #c8c9cc);
  cursor: not-allowed;
  box-shadow: none;
}

.confirm-btn {
  width: 100%;
  padding: 0.875rem;
  background: var(--color-success, #07c160);
  color: white;
  border: none;
  border-radius: 0.625rem;
  font-size: 0.95rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
  box-shadow: 0 2px 8px rgba(7, 193, 96, 0.3);
}

.confirm-btn:hover:not(:disabled) {
  background: rgba(7, 193, 96, 0.9);
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(7, 193, 96, 0.4);
}

.confirm-btn:active:not(:disabled) {
  transform: translateY(0);
}

.confirm-btn:disabled {
  background: var(--color-text-disabled, #c8c9cc);
  cursor: not-allowed;
  box-shadow: none;
}

@media (max-width: 480px) {
  .captcha-overlay {
    padding: 0.75rem;
  }

  .captcha-modal {
    max-width: 100%;
    border-radius: 0.875rem;
  }

  .captcha-header {
    padding: 1rem 1.25rem;
  }

  .captcha-content {
    padding: 1.25rem 1.25rem 0.875rem 1.25rem;
  }

  .captcha-canvas {
    min-height: 140px;
  }

  .captcha-footer {
    padding: 1rem 1.25rem;
  }

  .captcha-instruction p {
    font-size: 0.875rem;
  }
}
</style>
