<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,
  },
  targetLeft: {
    type: Number,
    default: 0,
  },
  targetTop: {
    type: Number,
    default: 0,
  },
  hitOffsetPx: {
    type: Number,
    default: 0,
  },
  viewportMode: {
    type: Boolean,
    default: false,
  },
});

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

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

const isActive = computed(() => phase.value !== "idle");
const ARROW_IMPACT_MS = 340;
const COMPLETE_FALLBACK_MS = 980;
// 箭头尖端统一从屏幕中下区域出发，箭身自然延伸到屏幕底部之外。
const SHOT_START_VIEWPORT_Y_RATIO = 0.82;

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);
  const left = Number(props.targetLeft);
  const top = Number(props.targetTop);
  return {
    width: Number.isFinite(width) && width > 0 ? width : 0,
    height: Number.isFinite(height) && height > 0 ? height : 0,
    left: Number.isFinite(left) ? left : 0,
    top: Number.isFinite(top) ? top : 0,
  };
});

function getWindowSize() {
  try {
    const info =
      typeof uni.getWindowInfo === "function"
        ? uni.getWindowInfo()
        : uni.getSystemInfoSync();
    const width = Number(info?.windowWidth);
    const height = Number(info?.windowHeight);
    return {
      width: Number.isFinite(width) && width > 0 ? width : 0,
      height: Number.isFinite(height) && height > 0 ? height : 0,
    };
  } catch {
    return { width: 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 effectiveTargetSize = computed(
  () => activeLayout.value?.target || safeTargetSize.value
);
const effectiveWindowSize = computed(
  () => activeLayout.value?.window || getWindowSize()
);
const isViewportMode = computed(() =>
  activeLayout.value
    ? activeLayout.value.viewportMode
    : props.viewportMode === true
);

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 flightPath = computed(() => {
  const size = effectiveTargetSize.value;
  const windowSize = effectiveWindowSize.value;

  if (
    isViewportMode.value &&
    size.width > 0 &&
    size.height > 0 &&
    windowSize.width > 0 &&
    windowSize.height > 0
  ) {
    const startX = windowSize.width * 0.5;
    const endX =
      size.left +
      size.width * (hitPercent.value.left / 100) +
      hitOffset.value.x;
    const endY =
      size.top +
      size.height * (hitPercent.value.top / 100) +
      hitOffset.value.y;
    // 常规情况下从视口底部进入；靶面局部超出视口时仍保证起点在命中点下方。
    const startY = Math.max(windowSize.height + 16, endY + 80);
    const dx = endX - startX;
    const dy = endY - startY;

    return {
      startX,
      startY,
      endX,
      endY,
      translateX: dx,
      translateY: dy,
      angle: Math.atan2(dx, -dy) * (180 / Math.PI),
    };
  }

  const hasScreenCoordinates =
    size.width > 0 &&
    size.height > 0 &&
    windowSize.width > 0 &&
    windowSize.height > 0;
  const startX = hasScreenCoordinates
    ? windowSize.width * 0.5 - size.left
    : size.width * 0.5;
  const startY = hasScreenCoordinates
    ? windowSize.height * SHOT_START_VIEWPORT_Y_RATIO - size.top
    : 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 = endY - startY;

  return {
    startX,
    startY,
    endX,
    endY,
    translateX: dx,
    translateY: dy,
    angle: Math.atan2(dx, -dy) * (180 / Math.PI),
  };
});

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(() => {
  if (isViewportMode.value) {
    return {
      left: `${flightPath.value.endX}px`,
      top: `${flightPath.value.endY}px`,
    };
  }

  return {
    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 = effectiveTargetSize.value;
  const path = flightPath.value;
  let x = getTargetTranslate(hitPercent.value.left - 50);
  let y = getTargetTranslate(hitPercent.value.top - 114);

  if (isViewportMode.value || (size.width && size.height)) {
    x = `${path.translateX}px`;
    y = `${path.translateY}px`;
  }

  return {
    "--shot-start-x":
      isViewportMode.value || size.width ? `${path.startX}px` : "50%",
    "--shot-start-y":
      isViewportMode.value || size.height ? `${path.startY}px` : "114%",
    "--shot-tx": x,
    "--shot-ty": y,
    "--shot-angle": `${path.angle}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;
  activeLayout.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 };
  activeLayout.value = {
    target: { ...safeTargetSize.value },
    window: getWindowSize(),
    viewportMode: props.viewportMode === true,
  };
  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}`,
      { 'shot-effect--viewport': isViewportMode },
    ]"
    :style="arrowMoveStyle"
  >
    <view
      :key="`arrow-${animationKey}`"
      class="shot-arrow-track"
      @animationend="handleArrowAnimationEnd"
    >
      <image
        class="shot-arrow"
        src="https://static.shelingxingqiu.com/shootmini/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="https://static.shelingxingqiu.com/shootmini/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-effect--viewport {
  position: fixed;
  width: 100vw;
  height: 100vh;
}

.shot-arrow-track {
  position: absolute;
  left: var(--shot-start-x);
  top: var(--shot-start-y);
  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>
