<script setup>
import { computed, onBeforeUnmount, ref, watch } from "vue";

const props = defineProps({
  shot: {
    type: Object,
    default: null,
  },
  playKey: {
    type: [String, Number],
    default: "",
  },
  targetRadius: {
    type: Number,
    default: 20,
  },
  targetWidth: {
    type: Number,
    default: 0,
  },
  targetHeight: {
    type: Number,
    default: 0,
  },
  hitOffsetPx: {
    type: Number,
    default: 0,
  },
});

const emit = defineEmits(["complete", "impact"]);

const phase = ref("idle");
const activePlayKey = ref("");
const animationKey = ref("");
const impactEmitted = ref(false);
const activeShot = ref(null);
let timers = [];

const isActive = computed(() => phase.value !== "idle");
const ARROW_IMPACT_MS = 340;
const COMPLETE_FALLBACK_MS = 980;

const safeTargetRadius = computed(() => {
  const radius = Number(props.targetRadius);
  return Number.isFinite(radius) && radius > 0 ? radius : 20;
});

const safeTargetSize = computed(() => {
  const width = Number(props.targetWidth);
  const height = Number(props.targetHeight);
  return {
    width: Number.isFinite(width) && width > 0 ? width : 0,
    height: Number.isFinite(height) && height > 0 ? height : 0,
  };
});

function hasShotPoint(shot) {
  const x = Number(shot?.x);
  const y = Number(shot?.y);
  return Number.isFinite(x) && Number.isFinite(y);
}

const effectiveShot = computed(() => activeShot.value || props.shot);

const shotPoint = computed(() => {
  const x = Number(effectiveShot.value?.x);
  const y = Number(effectiveShot.value?.y);
  return {
    x: Number.isFinite(x) ? x : 0,
    y: Number.isFinite(y) ? y : 0,
  };
});

const pointDirection = computed(() => {
  const point = shotPoint.value;
  const distance = Math.sqrt(point.x * point.x + point.y * point.y);
  if (distance === 0) return null;

  return {
    x: point.x / distance,
    y: point.y / distance,
  };
});

const hitOffset = computed(() => {
  const offset = Number(props.hitOffsetPx);
  const safeOffset = Number.isFinite(offset) && offset > 0 ? offset : 0;
  const direction = pointDirection.value;

  return {
    x: direction ? direction.x * safeOffset : 0,
    y: direction ? -direction.y * safeOffset : 0,
  };
});

const hitPercent = computed(() => {
  const point = shotPoint.value;
  const radius = safeTargetRadius.value;
  const diameter = radius * 2;

  return {
    left: ((point.x + radius) / diameter) * 100,
    top: ((radius - point.y) / diameter) * 100,
  };
});

const arrowAngle = computed(() => {
  const size = safeTargetSize.value;
  if (!size.width || !size.height) {
    const dx = hitPercent.value.left - 50;
    const dy = 114 - hitPercent.value.top;
    const fallbackAngle = Math.atan2(dx, dy || 1) * (180 / Math.PI);
    return Math.max(-18, Math.min(18, fallbackAngle));
  }

  const startX = size.width * 0.5;
  const startY = size.height * 1.14;
  const endX = size.width * (hitPercent.value.left / 100) + hitOffset.value.x;
  const endY = size.height * (hitPercent.value.top / 100) + hitOffset.value.y;
  const dx = endX - startX;
  const dy = startY - endY;
  const angle = Math.atan2(dx, dy || 1) * (180 / Math.PI);
  return Math.max(-18, Math.min(18, angle));
});

function formatPxOffset(value) {
  if (!value) return "";
  const operator = value > 0 ? "+" : "-";
  return ` ${operator} ${Math.abs(value)}px`;
}

function formatTargetPosition(percent, offset) {
  const pxOffset = formatPxOffset(offset);
  return pxOffset ? `calc(${percent}%${pxOffset})` : `${percent}%`;
}

const crackStyle = computed(() => ({
  left: formatTargetPosition(hitPercent.value.left, hitOffset.value.x),
  top: formatTargetPosition(hitPercent.value.top, hitOffset.value.y),
}));

function getTargetTranslate(percent) {
  const absPercent = Math.abs(percent);
  const operator = percent >= 0 ? "-" : "+";
  return `calc(${percent}vw ${operator} ${absPercent * 0.5}px)`;
}

const arrowMoveStyle = computed(() => {
  const size = safeTargetSize.value;
  let x = getTargetTranslate(hitPercent.value.left - 50);
  let y = getTargetTranslate(hitPercent.value.top - 114);

  if (size.width && size.height) {
    const startX = size.width * 0.5;
    const startY = size.height * 1.14;
    const endX = size.width * (hitPercent.value.left / 100) + hitOffset.value.x;
    const endY = size.height * (hitPercent.value.top / 100) + hitOffset.value.y;
    x = `${endX - startX}px`;
    y = `${endY - startY}px`;
  }

  return {
    "--shot-tx": x,
    "--shot-ty": y,
    "--shot-angle": `${arrowAngle.value}deg`,
  };
});

function clearTimers() {
  timers.forEach((timer) => clearTimeout(timer));
  timers = [];
}

function queueTimer(callback, delay) {
  const timer = setTimeout(callback, delay);
  timers.push(timer);
}

function emitImpactOnce(playKey) {
  if (phase.value === "idle" || activePlayKey.value !== playKey || impactEmitted.value) return;
  impactEmitted.value = true;
  emit("impact");
}

function finish(playKey) {
  if (phase.value === "idle" || activePlayKey.value !== playKey) return;
  clearTimers();
  phase.value = "idle";
  activePlayKey.value = "";
  activeShot.value = null;
  emit("complete", playKey);
}

function play() {
  if (!props.playKey || !props.shot || !props.shot.ring || !hasShotPoint(props.shot)) {
    return;
  }

  clearTimers();
  activePlayKey.value = props.playKey;
  animationKey.value = `${props.playKey}`;
  impactEmitted.value = false;
  activeShot.value = { ...props.shot };
  phase.value = "playing";

  queueTimer(() => {
    emitImpactOnce(activePlayKey.value);
  }, ARROW_IMPACT_MS);
  queueTimer(() => {
    finish(activePlayKey.value);
  }, COMPLETE_FALLBACK_MS);
}

function handleArrowAnimationEnd() {
  emitImpactOnce(activePlayKey.value);
}

function handleCrackAnimationEnd() {
  finish(activePlayKey.value);
}

watch(
  () => props.playKey,
  () => {
    play();
  },
  { immediate: true }
);

onBeforeUnmount(() => {
  clearTimers();
});
</script>

<template>
  <view
    v-show="isActive"
    :class="['shot-effect', `shot-effect--${phase}`]"
    :style="arrowMoveStyle"
  >
    <view
      :key="`arrow-${animationKey}`"
      class="shot-arrow-track"
      @animationend="handleArrowAnimationEnd"
    >
      <image
        class="shot-arrow"
        src="../static/vip/svip-jian.png"
        mode="heightFix"
      />
    </view>
    <view
      :key="`flash-${animationKey}`"
      class="shot-flash"
      :style="crackStyle"
    ></view>
    <view
      :key="`crack-anchor-${animationKey}`"
      class="shot-crack-anchor"
      :style="crackStyle"
    >
      <image
        :key="`crack-${animationKey}`"
        class="shot-crack"
        src="../static/vip/svip-lie.png"
        mode="aspectFit"
        @animationend="handleCrackAnimationEnd"
      />
    </view>
  </view>
</template>

<style scoped lang="scss">
.shot-effect {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 9999;
  pointer-events: none;
  overflow: visible;
  transform: translateZ(0);
}

.shot-arrow-track {
  position: absolute;
  left: 50%;
  top: 114%;
  width: 0;
  height: 0;
  opacity: 0;
  transform: translate3d(0, 0, 0);
  animation: none;
  backface-visibility: hidden;
  will-change: transform, opacity;
}

.shot-arrow {
  position: absolute;
  width: 248rpx;
  height: 1186rpx;
  left: 0;
  top: 0;
  opacity: 1;
  transform-origin: 44.35% 3.04%;
  transform: translate(-44.35%, -3.04%) rotate(var(--shot-angle));
  backface-visibility: hidden;
  will-change: transform;
}

.shot-effect--playing .shot-arrow-track {
  animation: shot-arrow-fly 0.38s cubic-bezier(0.68, 0, 0.9, 0.62) forwards;
}

.shot-flash,
.shot-crack-anchor {
  position: absolute;
  transform: translate(-50%, -50%);
  backface-visibility: hidden;
  will-change: transform, opacity;
}

.shot-flash {
  width: 86rpx;
  height: 86rpx;
  border-radius: 50%;
  border: 3rpx solid rgba(255, 236, 166, 0.9);
  opacity: 0;
  animation: none;
}

.shot-crack-anchor {
  width: 750rpx;
  height: 750rpx;
}

.shot-crack {
  width: 100%;
  height: 100%;
  opacity: 0;
  transform-origin: center center;
  animation: none;
  will-change: transform, opacity;
}

.shot-effect--playing .shot-flash {
  animation: shot-flash 0.42s ease-out 0.32s forwards;
}

.shot-effect--playing .shot-crack {
  animation: shot-crack-hit 0.52s ease-out 0.34s forwards;
}

@keyframes shot-arrow-fly {
  0% {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
  86% {
    opacity: 1;
    transform: translate3d(var(--shot-tx), var(--shot-ty), 0);
  }
  100% {
    opacity: 0;
    transform: translate3d(var(--shot-tx), var(--shot-ty), 0);
  }
}

@keyframes shot-flash {
  0% {
    opacity: 0.95;
    transform: translate(-50%, -50%) scale(0.2);
  }
  100% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(1.9);
  }
}

@keyframes shot-crack-hit {
  0% {
    opacity: 0;
    transform: scale(0.55);
  }
  28% {
    opacity: 1;
    transform: scale(1.08);
  }
  56% {
    opacity: 1;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(1.18);
  }
}
</style>
