"use client";

import {
  MutableRefObject,
  useEffect,
  useRef,
  useState,
} from "react";

type AppleFrameSequenceProps = {
  progressRef: MutableRefObject<number>;
  frameCount?: number;
  mobileFrameCount?: number;
  onReady?: () => void;
};

function clamp(value: number, min: number, max: number) {
  return Math.min(max, Math.max(min, value));
}

function framePath(frame: number, mobile: boolean) {
  const padded = String(frame).padStart(4, "0");

  return mobile
    ? `/frames/lark/mobile/frame-${padded}.webp`
    : `/frames/lark/desktop/frame-${padded}.webp`;
}

export default function AppleFrameSequence({
  progressRef,
  frameCount = 180,
  mobileFrameCount = 100,
  onReady,
}: AppleFrameSequenceProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const imagesRef = useRef<HTMLImageElement[]>([]);
  const loadedRef = useRef(0);
  const lastFrameRef = useRef(-1);

  const [mobile, setMobile] = useState(false);
  const [ready, setReady] = useState(false);

  useEffect(() => {
    const updateMobile = () => {
      setMobile(window.innerWidth < 768);
    };

    updateMobile();

    window.addEventListener("resize", updateMobile);

    return () => {
      window.removeEventListener("resize", updateMobile);
    };
  }, []);

  useEffect(() => {
    let cancelled = false;

    const total = mobile ? mobileFrameCount : frameCount;

    imagesRef.current = [];
    loadedRef.current = 0;
    lastFrameRef.current = -1;
    setReady(false);

    const drawImage = (
      context: CanvasRenderingContext2D,
      image: HTMLImageElement,
      canvas: HTMLCanvasElement,
    ) => {
      const canvasRatio = canvas.width / canvas.height;
      const imageRatio = image.naturalWidth / image.naturalHeight;

      let drawWidth = canvas.width;
      let drawHeight = canvas.height;
      let drawX = 0;
      let drawY = 0;

      if (imageRatio > canvasRatio) {
        drawHeight = canvas.height;
        drawWidth = drawHeight * imageRatio;
        drawX = (canvas.width - drawWidth) / 2;
      } else {
        drawWidth = canvas.width;
        drawHeight = drawWidth / imageRatio;
        drawY = (canvas.height - drawHeight) / 2;
      }

      context.clearRect(0, 0, canvas.width, canvas.height);
      context.drawImage(
        image,
        drawX,
        drawY,
        drawWidth,
        drawHeight,
      );
    };

    const resizeCanvas = () => {
      const canvas = canvasRef.current;
      if (!canvas) return;

      const dpr = Math.min(window.devicePixelRatio || 1, 2);

      canvas.width = Math.round(window.innerWidth * dpr);
      canvas.height = Math.round(window.innerHeight * dpr);
      canvas.style.width = `${window.innerWidth}px`;
      canvas.style.height = `${window.innerHeight}px`;

      lastFrameRef.current = -1;
    };

    resizeCanvas();

    const priorityFrames = [1, Math.round(total * 0.25), Math.round(total * 0.5)];

    const loadFrame = (frame: number) => {
      const image = new Image();

      image.decoding = "async";
      image.src = framePath(frame, mobile);

      image.onload = () => {
        if (cancelled) return;

        loadedRef.current += 1;

        if (frame === 1) {
          const canvas = canvasRef.current;
          const context = canvas?.getContext("2d");

          if (canvas && context) {
            drawImage(context, image, canvas);
          }

          setReady(true);
          onReady?.();
        }
      };

      imagesRef.current[frame - 1] = image;
    };

    priorityFrames.forEach(loadFrame);

    for (let frame = 1; frame <= total; frame += 1) {
      if (!priorityFrames.includes(frame)) {
        loadFrame(frame);
      }
    }

    let animationFrame = 0;

    const render = () => {
      const canvas = canvasRef.current;
      const context = canvas?.getContext("2d");

      if (canvas && context) {
        const targetFrame = clamp(
          Math.round(progressRef.current * (total - 1)),
          0,
          total - 1,
        );

        if (targetFrame !== lastFrameRef.current) {
          const exactImage = imagesRef.current[targetFrame];

          let image = exactImage;

          if (!image?.complete || !image.naturalWidth) {
            for (let offset = 1; offset < 10; offset += 1) {
              const previous = imagesRef.current[targetFrame - offset];
              const next = imagesRef.current[targetFrame + offset];

              if (previous?.complete && previous.naturalWidth) {
                image = previous;
                break;
              }

              if (next?.complete && next.naturalWidth) {
                image = next;
                break;
              }
            }
          }

          if (image?.complete && image.naturalWidth) {
            drawImage(context, image, canvas);
            lastFrameRef.current = targetFrame;
          }
        }
      }

      animationFrame = requestAnimationFrame(render);
    };

    render();

    window.addEventListener("resize", resizeCanvas);

    return () => {
      cancelled = true;
      cancelAnimationFrame(animationFrame);
      window.removeEventListener("resize", resizeCanvas);
    };
  }, [
    frameCount,
    mobile,
    mobileFrameCount,
    onReady,
    progressRef,
  ]);

  return (
    <div className="absolute inset-0">
      <canvas
        ref={canvasRef}
        className="absolute inset-0 h-full w-full"
        aria-hidden="true"
      />

      <div
        className="absolute inset-0 bg-[#f5f5f7] transition-opacity duration-700"
        style={{
          opacity: ready ? 0 : 1,
          pointerEvents: ready ? "none" : "auto",
        }}
      />
    </div>
  );
}
