"use client";

import React, { useRef, useEffect, useState, useCallback } from "react";
import gsap from "gsap";
import { useGSAP } from "@gsap/react";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import dynamic from "next/dynamic";
import type { HomePageData } from "@/utils/strapi";
import { heroTitleDisplayLines } from "@/utils/parseStrapiBrTitleLines";

// Dynamically import Player to prevent SSR issues
const Player = dynamic(
  () => import("@lottiefiles/react-lottie-player").then((mod) => mod.Player),
  { ssr: false },
);
import {
  heroFrameCache,
  heroLastPreloadedIndex,
} from "@/components/HeroFramePreloader";

import "./video-scroll.css";

// Register ScrollTrigger plugin
if (typeof window !== "undefined") {
  gsap.registerPlugin(ScrollTrigger);
}

/** Desktop hero headline + scroll-driven reveal. Set `true` to show again. */
const DESKTOP_HERO_TITLE_ENABLED = true;

interface HomeAnimatedHeroProps {
  homePageData?: HomePageData | null;
  framePath?: string;
  frameCount?: number;
  frameFormat?: string;
  framePrefix?: string;
  framePadding?: number;
  scrollDistance?: number;
  aspectRatio?: number;
}

const HomeAnimatedHero: React.FC<HomeAnimatedHeroProps> = ({
  homePageData,
  framePath = "/images/home/hero/sequence",
  frameCount = 336,
  frameFormat = "jpg",
  framePrefix = "frame_",
  framePadding = 4,
  scrollDistance = 2000,
}) => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const containerRef = useRef<HTMLDivElement>(null);
  const titleRef = useRef<HTMLHeadingElement>(null);
  const imagesRef = useRef<HTMLImageElement[]>([]);
  const currentFrameRef = useRef<number>(0);
  const canvasSizeRef = useRef<{ width: number; height: number }>({
    width: 0,
    height: 0,
  });
  const lastLoadedFrameRef = useRef<number>(0);

  const [isLoading, setIsLoading] = useState(true);
  // isReady = all frames loaded AND layout is stable for ScrollTrigger
  const [isReady, setIsReady] = useState(false);

  const hero = homePageData?.hero;
  const heroTitle = hero?.title ?? "Broaden Life Boundaries";
  const heroTitleLines = heroTitleDisplayLines(heroTitle);

  // Generate poster URL (first frame) for immediate display
  const posterUrl = `${framePath}/${framePrefix}${String(1).padStart(framePadding, "0")}.${frameFormat}`;

  // Load images in two phases:
  // 1) Initial subset for fast first interaction (uses global preload cache if available)
  // 2) Remaining frames in background
  const loadAllImages = useCallback(async (): Promise<void> => {
    setIsLoading(true);

    const images: HTMLImageElement[] = [];
    imagesRef.current = images;
    lastLoadedFrameRef.current = 0;

    // Keep first paint fast: load only a tiny startup window, stream the rest later.
    const initialFrameCount = Math.min(12, frameCount);
    const backgroundBatchSize = 6;

    const loadSingleFrame = (index: number): Promise<void> => {
      // Check global preload cache first
      const cached = heroFrameCache.get(index);
      if (cached) {
        images[index] = cached;
        imagesRef.current = images;
        if (index > lastLoadedFrameRef.current) {
          lastLoadedFrameRef.current = index;
        }
        return Promise.resolve();
      }

      // Otherwise load fresh
      const fileIndex = index + 1;
      const paddedIndex = String(fileIndex).padStart(framePadding, "0");
      const img = new Image();
      img.crossOrigin = "anonymous";

      return new Promise<void>((resolve) => {
        img.onload = () => {
          images[index] = img;
          imagesRef.current = images;
          if (index > lastLoadedFrameRef.current) {
            lastLoadedFrameRef.current = index;
          }
          resolve();
        };
        img.onerror = () => {
          console.error(`Failed to load frame ${fileIndex}`);
          resolve();
        };
        img.src = `${framePath}/${framePrefix}${paddedIndex}.${frameFormat}`;
      });
    };

    // Copy any frames already in the global preload cache
    heroFrameCache.forEach((img, index) => {
      images[index] = img;
      if (index > lastLoadedFrameRef.current) {
        lastLoadedFrameRef.current = index;
      }
    });
    imagesRef.current = images;

    // Phase 1: load initial frames in PARALLEL for speed
    const initialPromises: Promise<void>[] = [];
    for (let i = 0; i < initialFrameCount && i < frameCount; i++) {
      initialPromises.push(loadSingleFrame(i));
    }
    await Promise.all(initialPromises);

    // After initial frames are ready, mark as loaded/ready for interaction
    setIsLoading(false);

    // Wait for layout to stabilize before marking ready for ScrollTrigger
    // This ensures pin spacer calculations are correct
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        setIsReady(true);
      });
    });

    // Phase 2: load remaining frames in the background using idle time
    const scheduleBatchLoad = (startIndex: number) => {
      const loadBatch = async (from: number) => {
        const end = Math.min(from + backgroundBatchSize, frameCount);
        for (let i = from; i < end; i++) {
          if (!images[i]) {
            void loadSingleFrame(i);
          }
        }

        if (end < frameCount) {
          const idleCallback: (cb: () => void) => void =
            typeof (
              window as Window & {
                requestIdleCallback?: (cb: () => void) => void;
              }
            ).requestIdleCallback === "function"
              ? (
                  window as Window & {
                    requestIdleCallback: (cb: () => void) => void;
                  }
                ).requestIdleCallback
              : (cb: () => void) => {
                  setTimeout(cb, 150);
                };

          idleCallback(() => {
            void loadBatch(end);
          });
        }
      };

      void loadBatch(startIndex);
    };

    if (initialFrameCount < frameCount) {
      scheduleBatchLoad(initialFrameCount);
    }
  }, [frameCount, framePath, framePrefix, framePadding, frameFormat]);

  // Setup canvas dimensions
  const setupCanvas = useCallback((): void => {
    const canvas = canvasRef.current;
    const container = containerRef.current;
    if (!canvas || !container) return;

    // IMPORTANT: Clear any previous inline styles to let CSS handle display size
    // This prevents stale pixel values from overriding CSS percentages on resize
    canvas.style.width = "";
    canvas.style.height = "";

    // Force layout recalculation after clearing styles
    // This ensures getBoundingClientRect returns fresh values
    const rect = container.getBoundingClientRect();
    const dpr = window.devicePixelRatio || 1;

    // Use viewport dimensions as fallback if container reports 0
    const width = rect.width || window.innerWidth;
    const height = rect.height || window.innerHeight;

    // Set canvas internal size (for rendering resolution)
    // CSS class .canvas-layer handles display size (100% width/height)
    canvas.width = Math.round(width * dpr);
    canvas.height = Math.round(height * dpr);

    // Store logical size for drawing calculations
    canvasSizeRef.current = { width, height };

    // Scale context to account for device pixel ratio
    const ctx = canvas.getContext("2d");
    if (ctx) {
      // Reset transform before scaling
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.scale(dpr, dpr);
      ctx.imageSmoothingEnabled = true;
      ctx.imageSmoothingQuality = "high";
    }
  }, []);

  // Draw frame directly to canvas
  const drawFrame = useCallback(
    (frameIndex: number): void => {
      const canvas = canvasRef.current;
      const images = imagesRef.current;
      if (!canvas || !images[frameIndex]) return;

      const ctx = canvas.getContext("2d");
      if (!ctx) return;

      const img = images[frameIndex];
      const { width, height } = canvasSizeRef.current;

      // Ensure canvas has valid size
      if (width === 0 || height === 0) {
        setupCanvas();
        return;
      }

      // Clear canvas (use logical size since context is scaled)
      ctx.clearRect(0, 0, width, height);

      // Use "cover" strategy: scale to fill entire viewport, crop if needed
      const imgAspectRatio = img.width / img.height;
      const viewportAspectRatio = width / height;

      let drawWidth: number;
      let drawHeight: number;

      if (viewportAspectRatio > imgAspectRatio) {
        // Viewport is wider than image - fit to width, crop top/bottom
        drawWidth = width;
        drawHeight = width / imgAspectRatio;
      } else {
        // Viewport is taller than image - fit to height, crop left/right
        drawHeight = height;
        drawWidth = height * imgAspectRatio;
      }

      // Center the image (cropping equally from both sides)
      const drawX = (width - drawWidth) / 2;
      const drawY = (height - drawHeight) / 2;

      // Draw image to fill full viewport
      ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight);
    },
    [setupCanvas],
  );

  // Update frame from scroll progress
  const updateFrame = (progress: number): void => {
    // Math.round ensures last frame is reachable at progress = 1.0
    const requestedIndex = Math.round(progress * (frameCount - 1));
    // requestedIndex range: 0 .. frameCount-1 (frame_0001 .. frame_0NNNN)

    const images = imagesRef.current;

    // Determine a frame index that is actually available.
    let frameIndex = requestedIndex;
    if (!images[frameIndex]) {
      // Try to pull from global preload cache if available
      const cachedImg = heroFrameCache.get(requestedIndex);
      if (cachedImg) {
        images[requestedIndex] = cachedImg;
        imagesRef.current = images;
        frameIndex = requestedIndex;
      } else {
        // Fallback to the last loaded frame (local or global), clamped to requested index
        const lastLoaded = Math.max(
          lastLoadedFrameRef.current,
          heroLastPreloadedIndex,
        );
        if (lastLoaded >= 0) {
          frameIndex = Math.min(requestedIndex, lastLoaded);
        }
      }
    }

    if (frameIndex !== currentFrameRef.current && images[frameIndex]) {
      currentFrameRef.current = frameIndex;
      drawFrame(frameIndex); // Draw immediately, no React state

      if (DESKTOP_HERO_TITLE_ENABLED) {
        // Animate title reveal after frame 120 (smooth reveal over 30 frames)
        const titleElement = titleRef.current;
        if (titleElement) {
          const revealStartFrame = 120;
          const revealEndFrame = 240; // 30 frames for smooth reveal

          if (frameIndex < revealStartFrame) {
            // Hide title before reveal starts
            gsap.set(titleElement, { opacity: 0, y: 40 });
          } else if (
            frameIndex >= revealStartFrame &&
            frameIndex <= revealEndFrame
          ) {
            // Smooth reveal between frames 120-150
            const revealProgress =
              (frameIndex - revealStartFrame) /
              (revealEndFrame - revealStartFrame);
            // Use easing for smoother animation (ease-out curve)
            const easedProgress = 1 - Math.pow(1 - revealProgress, 3);
            gsap.set(titleElement, {
              opacity: easedProgress,
              y: 40 * (1 - easedProgress),
            });
          } else {
            // Fully visible after frame 150
            gsap.set(titleElement, { opacity: 1, y: 0 });
          }
        }
      }
    }
  };

  // Setup GSAP ScrollTrigger (only after images are loaded AND layout is ready)
  useGSAP(() => {
    const container = containerRef.current;
    // Use isReady instead of isLoading for more precise timing
    if (!container || !isReady || imagesRef.current.length === 0) return;

    // Setup canvas dimensions
    setupCanvas();

    // Initial draw after a brief delay to ensure canvas is ready
    requestAnimationFrame(() => {
      drawFrame(0);
    });

    // Create ScrollTrigger timeline with proper priority and pin settings
    const tl = gsap.timeline({
      scrollTrigger: {
        trigger: container,
        start: "top top",
        end: `+=${scrollDistance}vh`,
        scrub: 1,
        pin: true,
        pinSpacing: true, // Explicit: add spacer for pinned content
        anticipatePin: 1, // Start pin slightly early for smoother transition
        refreshPriority: 1, // Calculate FIRST among all ScrollTriggers (higher = first)
        scroller: window,
        invalidateOnRefresh: true,
        onUpdate: (self) => {
          updateFrame(self.progress);
        },
      },
    });

    // Refresh ScrollTrigger after layout is stable
    // Use requestAnimationFrame for more reliable timing than setTimeout
    requestAnimationFrame(() => {
      ScrollTrigger.refresh();

      // Emit custom event to signal hero is ready
      // Other sections can listen for this to coordinate their ScrollTriggers
      window.dispatchEvent(new CustomEvent("hero-scroll-ready"));
    });

    return () => {
      tl.scrollTrigger?.kill();
    };
  }, [isReady, setupCanvas, scrollDistance, frameCount, drawFrame]);

  // Handle window resize
  useEffect(() => {
    let timeout: NodeJS.Timeout;

    const handleResize = () => {
      clearTimeout(timeout);
      timeout = setTimeout(() => {
        setupCanvas();
        // Use the current frame from ref
        const currentFrame = currentFrameRef.current;
        if (imagesRef.current[currentFrame]) {
          drawFrame(currentFrame);
        }
        ScrollTrigger.refresh();
      }, 150);
    };

    window.addEventListener("resize", handleResize);

    return () => {
      window.removeEventListener("resize", handleResize);
      clearTimeout(timeout);
    };
  }, [setupCanvas, drawFrame]);

  // Load all images on mount
  useEffect(() => {
    loadAllImages();
  }, [loadAllImages]);

  return (
    <div ref={containerRef} className="scroll-container">
      {/* Poster layer - shows immediately while frames load */}
      <div
        className="poster-layer"
        style={{
          backgroundImage: `url(${posterUrl})`,
          opacity: isLoading ? 1 : 0,
          pointerEvents: "none",
        }}
      />

      {/* Canvas background - fades in after loading */}
      <canvas
        ref={canvasRef}
        className="canvas-layer"
        style={{
          opacity: isLoading ? 0 : 1,
        }}
      />

      {/* Loading indicator */}
      {isLoading && (
        <div className="absolute inset-0 z-20 flex items-center justify-center bg-black/50">
          <div className="w-full max-w-[500px] h-[500px] flex items-center justify-center">
            <Player
              autoplay={true}
              loop={true}
              className="w-full h-full"
              src="/animations/nre_logo_anim_short.json"
              speed={1}
              rendererSettings={{
                preserveAspectRatio: "xMidYMid meet",
              }}
            />
          </div>
        </div>
      )}

      {/* Content overlay — gated by DESKTOP_HERO_TITLE_ENABLED */}
      {DESKTOP_HERO_TITLE_ENABLED ? (
        <div className="absolute inset-0 z-10 flex items-end justify-start pointer-events-none pb-[14vh]">
          <div className="container pl-[6vw] sm:pl-[4.5vw] md:pl-[5vw] lg:pl-[6vw] xl:pl-[105px]">
            <h1
              ref={titleRef}
              className="font-times-sans text-[clamp(2rem,6.25vw,6.25rem)] font-normal leading-[0.95] mb-4 hero-title uppercase whitespace-pre-line text-white text-left"
              style={{ opacity: 0, transform: "translateY(40px)" }}
            >
              {heroTitleLines.map((line, i) => (
                <React.Fragment key={i}>
                  {line}
                  {i < heroTitleLines.length - 1 && <br />}
                </React.Fragment>
              ))}
            </h1>
          </div>
        </div>
      ) : null}
    </div>
  );
};

export default HomeAnimatedHero;
