'use client'

import clsx from "clsx";
import Image from "next/image";
import { useCallback, useEffect, useRef, useState } from "react"

interface WobblingImageInterface {
  images: {
    idle: string;
    aware?: string;
    poked?: string;
    weird?: string;
  }
}

interface ImageClipInterface {
  name: string;
  src: string;
  size: number;
  visible?: boolean;
}

const ImageClip: React.FC<ImageClipInterface> = ({
  name, src, size, visible
}) => (
  <Image
    className={clsx("absolute left-1/2 -translate-x-1/2 top-0 pointer-events-none", visible ? "opacity-100" : "opacity-0")}
    alt={name}
    src={src}
    width={size}
    height={size}
    draggable={false}
  />
)

function WobblingImage ({
  images
}: WobblingImageInterface) {
  const size = 400;
  const [isPoked, setPoked] = useState(false);
  const [isAware, setAware] = useState(false);
  const pokeTimeoutRef = useRef<NodeJS.Timeout | null>(null);

  const [isAnimating, setIsAnimating] = useState(false);
  
  const audioPath = "/sound/poke.wav";
  const audioContextRef = useRef<AudioContext | null>(null);
  const [audioBuffer, setAudioBuffer] = useState<AudioBuffer | null>(null);
  const [audioLoaded, setAudioLoaded] = useState(false);
  
  const [dummyAudio, setDummyAudio] = useState<HTMLAudioElement | null>(null);

  useEffect(() => {
    if (typeof Audio !== undefined) setDummyAudio(new Audio(audioPath))
    return () => {
      if (dummyAudio) dummyAudio.pause();
    }
  }, [])

  const initializeAudioContext = useCallback((): AudioContext => {
    if (!audioContextRef.current) {
      audioContextRef.current = new (window.AudioContext || (window as any).webkitAudioContext)();
    }
    return audioContextRef.current;
  }, []);

  const loadAudio = useCallback(async (url: string): Promise<AudioBuffer> => {
    const response = await fetch(url);
    const arrayBuffer = await response.arrayBuffer();
    const context = initializeAudioContext();
    return await context.decodeAudioData(arrayBuffer);
  }, [initializeAudioContext]);

  const initializeAudio = useCallback(async (): Promise<void> => {
    if (audioBuffer) return;

    setAudioLoaded(false);
    try {
      const buffer = await loadAudio(audioPath);
      setAudioBuffer(buffer);
    } catch (error) {
      console.error('Failed to load audio:', error);
    }
    finally {
      setAudioLoaded(true);
    }
  }, [audioBuffer, loadAudio]);

  const playRandomPitch = useCallback(async (): Promise<void> => {
    await initializeAudio();

    if (!audioBuffer || !audioContextRef.current) return;

    const context = audioContextRef.current;
    const changeToLowPitch = Math.floor(Math.random() * 66);
    const randomPitch = Math.random() * 0.38 + (changeToLowPitch == 1 ? 0.25 : 0.75);

    const source = context.createBufferSource();
    source.buffer = audioBuffer;
    source.playbackRate.value = randomPitch;
    source.connect(context.destination);
    source.start(0);

    source.onended = () => {
      source.disconnect();
    };
  }, [audioBuffer, initializeAudio]);


  function handleClick() {
    const pokedDuration = 700 + Math.floor(Math.random() * 100)
    if (!isAware) setAware(true);

    setIsAnimating(false);
    requestAnimationFrame(() => setIsAnimating(true));
    
    playRandomPitch();
    if (dummyAudio && !audioLoaded) {
      dummyAudio.currentTime = 0;
      dummyAudio.play();
    }

    if (pokeTimeoutRef.current) {
      clearTimeout(pokeTimeoutRef.current);
    }
    setPoked(true);
    pokeTimeoutRef.current = setTimeout(() => {
      setPoked(false);
    }, pokedDuration);
  }

  function handleAnimationEnd() {
    setIsAnimating(false);
  }

  return (
    <div className="">
      <div
        className={clsx(
          "relative mx-auto cursor-grab active:cursor-grabbing h-[400px] select-none",
          isAnimating && "animate-[click-bounce_230ms_ease-out]"
        )}
        onClick={handleClick}
        onAnimationEnd={handleAnimationEnd}
        role="button"
        tabIndex={0}
        onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleClick(); } }}
        onMouseDown={(e) => e.preventDefault()}
        onPointerDown={(e) => e.preventDefault()}
      >
        <ImageClip
          name="idle"
          src={images.idle}
          size={size}
          visible={!isPoked && !isAware}
        />
        <ImageClip
          name="aware"
          src={images.aware || images.idle}
          size={size}
          visible={!isPoked && isAware}
        />
        <ImageClip
          name="poked"
          src={images.poked || images.idle}
          size={size}
          visible={isPoked}
        />
      </div>
    </div>
  )
}

export default WobblingImage;