<template>
  <div class="relative w-1/2 max-md:w-11/12 aspect-square" :style="wrapperStyle">
    <div ref="sceneRef" class="grid w-full h-full" :style="sceneStyle">
      <template v-for="(_, r) in cells" :key="`row-${r}`">
        <div
          v-for="(__, c) in cells"
          :key="`${r}-${c}`"
          class="cube relative w-full h-full aspect-square [transform-style:preserve-3d]"
          :data-row="r"
          :data-col="c"
        >
          <span class="absolute pointer-events-none -inset-9" />

          <div
            class="cube-face absolute inset-0 flex items-center justify-center"
            :style="{
              background: 'var(--cube-face-bg)',
              border: 'var(--cube-face-border)',
              boxShadow: 'var(--cube-face-shadow)',
              transform: 'translateY(-50%) rotateX(90deg)'
            }"
          />

          <div
            class="cube-face absolute inset-0 flex items-center justify-center"
            :style="{
              background: 'var(--cube-face-bg)',
              border: 'var(--cube-face-border)',
              boxShadow: 'var(--cube-face-shadow)',
              transform: 'translateY(50%) rotateX(-90deg)'
            }"
          />

          <div
            class="cube-face absolute inset-0 flex items-center justify-center"
            :style="{
              background: 'var(--cube-face-bg)',
              border: 'var(--cube-face-border)',
              boxShadow: 'var(--cube-face-shadow)',
              transform: 'translateX(-50%) rotateY(-90deg)'
            }"
          />

          <div
            class="cube-face absolute inset-0 flex items-center justify-center"
            :style="{
              background: 'var(--cube-face-bg)',
              border: 'var(--cube-face-border)',
              boxShadow: 'var(--cube-face-shadow)',
              transform: 'translateX(50%) rotateY(90deg)'
            }"
          />

          <div
            class="cube-face absolute inset-0 flex items-center justify-center"
            :style="{
              background: 'var(--cube-face-bg)',
              border: 'var(--cube-face-border)',
              boxShadow: 'var(--cube-face-shadow)',
              transform: 'rotateY(-90deg) translateX(50%) rotateY(90deg)'
            }"
          />

          <div
            class="cube-face absolute inset-0 flex items-center justify-center"
            :style="{
              background: 'var(--cube-face-bg)',
              border: 'var(--cube-face-border)',
              boxShadow: 'var(--cube-face-shadow)',
              transform: 'rotateY(90deg) translateX(-50%) rotateY(-90deg)'
            }"
          />
        </div>
      </template>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, withDefaults } from 'vue';
import gsap from 'gsap';

interface Gap {
  row: number;
  col: number;
}

interface Duration {
  enter: number;
  leave: number;
}

interface Props {
  gridSize?: number;
  cubeSize?: number;
  maxAngle?: number;
  radius?: number;
  easing?: gsap.EaseString;
  duration?: Duration;
  cellGap?: number | Gap;
  borderStyle?: string;
  faceColor?: string;
  shadow?: boolean | string;
  autoAnimate?: boolean;
  rippleOnClick?: boolean;
  rippleColor?: string;
  rippleSpeed?: number;
}

const props = withDefaults(defineProps<Props>(), {
  gridSize: 10,
  maxAngle: 45,
  radius: 3,
  easing: 'power3.out',
  duration: () => ({ enter: 0.3, leave: 0.6 }),
  borderStyle: '1px solid #fff',
  faceColor: '#0b0b0b',
  shadow: false,
  autoAnimate: true,
  rippleOnClick: true,
  rippleColor: '#fff',
  rippleSpeed: 2
});

const sceneRef = ref<HTMLDivElement | null>(null);
const rafRef = ref<number | null>(null);
const idleTimerRef = ref<number | null>(null);
const userActiveRef = ref(false);
const simPosRef = ref<{ x: number; y: number }>({ x: 0, y: 0 });
const simTargetRef = ref<{ x: number; y: number }>({ x: 0, y: 0 });
const simRAFRef = ref<number | null>(null);

const colGap = computed(() => {
  return typeof props.cellGap === 'number'
    ? `${props.cellGap}px`
    : (props.cellGap as Gap)?.col !== undefined
      ? `${(props.cellGap as Gap).col}px`
      : '5%';
});

const rowGap = computed(() => {
  return typeof props.cellGap === 'number'
    ? `${props.cellGap}px`
    : (props.cellGap as Gap)?.row !== undefined
      ? `${(props.cellGap as Gap).row}px`
      : '5%';
});

const enterDur = computed(() => props.duration.enter);
const leaveDur = computed(() => props.duration.leave);

const cells = computed(() => Array.from({ length: props.gridSize }));

const sceneStyle = computed(() => ({
  gridTemplateColumns: props.cubeSize
    ? `repeat(${props.gridSize}, ${props.cubeSize}px)`
    : `repeat(${props.gridSize}, 1fr)`,
  gridTemplateRows: props.cubeSize
    ? `repeat(${props.gridSize}, ${props.cubeSize}px)`
    : `repeat(${props.gridSize}, 1fr)`,
  columnGap: colGap.value,
  rowGap: rowGap.value,
  perspective: '99999999px',
  gridAutoRows: '1fr'
}));

const wrapperStyle = computed(() => ({
  '--cube-face-border': props.borderStyle,
  '--cube-face-bg': props.faceColor,
  '--cube-face-shadow': props.shadow === true ? '0 0 6px rgba(0,0,0,.5)' : props.shadow || 'none',
  ...(props.cubeSize
    ? {
        width: `${props.gridSize * props.cubeSize}px`,
        height: `${props.gridSize * props.cubeSize}px`
      }
    : {})
}));

const tiltAt = (rowCenter: number, colCenter: number) => {
  if (!sceneRef.value) return;

  sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach(cube => {
    const r = +cube.dataset.row!;
    const c = +cube.dataset.col!;
    const dist = Math.hypot(r - rowCenter, c - colCenter);

    if (dist <= props.radius) {
      const pct = 1 - dist / props.radius;
      const angle = pct * props.maxAngle;
      gsap.to(cube, {
        duration: enterDur.value,
        ease: props.easing,
        overwrite: true,
        rotateX: -angle,
        rotateY: angle
      });
    } else {
      gsap.to(cube, {
        duration: leaveDur.value,
        ease: 'power3.out',
        overwrite: true,
        rotateX: 0,
        rotateY: 0
      });
    }
  });
};

const onPointerMove = (e: PointerEvent) => {
  userActiveRef.value = true;
  if (idleTimerRef.value) clearTimeout(idleTimerRef.value);

  const rect = sceneRef.value!.getBoundingClientRect();
  const cellW = rect.width / props.gridSize;
  const cellH = rect.height / props.gridSize;
  const colCenter = (e.clientX - rect.left) / cellW;
  const rowCenter = (e.clientY - rect.top) / cellH;

  if (rafRef.value) cancelAnimationFrame(rafRef.value);
  rafRef.value = requestAnimationFrame(() => tiltAt(rowCenter, colCenter));

  idleTimerRef.value = setTimeout(() => {
    userActiveRef.value = false;
  }, 3000);
};

const resetAll = () => {
  if (!sceneRef.value) return;
  sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach(cube =>
    gsap.to(cube, {
      duration: leaveDur.value,
      rotateX: 0,
      rotateY: 0,
      ease: 'power3.out'
    })
  );
};

const onClick = (e: MouseEvent) => {
  if (!props.rippleOnClick || !sceneRef.value) return;

  const rect = sceneRef.value.getBoundingClientRect();
  const cellW = rect.width / props.gridSize;
  const cellH = rect.height / props.gridSize;
  const colHit = Math.floor((e.clientX - rect.left) / cellW);
  const rowHit = Math.floor((e.clientY - rect.top) / cellH);

  const baseRingDelay = 0.15;
  const baseAnimDur = 0.3;
  const baseHold = 0.6;

  const spreadDelay = baseRingDelay / props.rippleSpeed;
  const animDuration = baseAnimDur / props.rippleSpeed;
  const holdTime = baseHold / props.rippleSpeed;

  const rings: Record<number, HTMLDivElement[]> = {};
  sceneRef.value.querySelectorAll<HTMLDivElement>('.cube').forEach(cube => {
    const r = +cube.dataset.row!;
    const c = +cube.dataset.col!;
    const dist = Math.hypot(r - rowHit, c - colHit);
    const ring = Math.round(dist);
    if (!rings[ring]) rings[ring] = [];
    rings[ring].push(cube);
  });

  Object.keys(rings)
    .map(Number)
    .sort((a, b) => a - b)
    .forEach(ring => {
      const delay = ring * spreadDelay;
      const faces = rings[ring].flatMap(cube => Array.from(cube.querySelectorAll<HTMLElement>('.cube-face')));

      gsap.to(faces, {
        backgroundColor: props.rippleColor,
        duration: animDuration,
        delay,
        ease: 'power3.out'
      });
      gsap.to(faces, {
        backgroundColor: props.faceColor,
        duration: animDuration,
        delay: delay + animDuration + holdTime,
        ease: 'power3.out'
      });
    });
};

const startAutoAnimation = () => {
  if (!props.autoAnimate || !sceneRef.value) return;

  simPosRef.value = {
    x: Math.random() * props.gridSize,
    y: Math.random() * props.gridSize
  };
  simTargetRef.value = {
    x: Math.random() * props.gridSize,
    y: Math.random() * props.gridSize
  };

  const speed = 0.02;
  const loop = () => {
    if (!userActiveRef.value) {
      const pos = simPosRef.value;
      const tgt = simTargetRef.value;
      pos.x += (tgt.x - pos.x) * speed;
      pos.y += (tgt.y - pos.y) * speed;
      tiltAt(pos.y, pos.x);

      if (Math.hypot(pos.x - tgt.x, pos.y - tgt.y) < 0.1) {
        simTargetRef.value = {
          x: Math.random() * props.gridSize,
          y: Math.random() * props.gridSize
        };
      }
    }
    simRAFRef.value = requestAnimationFrame(loop);
  };
  simRAFRef.value = requestAnimationFrame(loop);
};

onMounted(() => {
  const el = sceneRef.value;
  if (!el) return;

  el.addEventListener('pointermove', onPointerMove);
  el.addEventListener('pointerleave', resetAll);
  el.addEventListener('click', onClick);

  startAutoAnimation();
});

onUnmounted(() => {
  const el = sceneRef.value;
  if (el) {
    el.removeEventListener('pointermove', onPointerMove);
    el.removeEventListener('pointerleave', resetAll);
    el.removeEventListener('click', onClick);
  }

  if (rafRef.value !== null) cancelAnimationFrame(rafRef.value);
  if (idleTimerRef.value !== null) clearTimeout(idleTimerRef.value);
  if (simRAFRef.value !== null) cancelAnimationFrame(simRAFRef.value);
});
</script>
