import {
  useCallback,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import { useNavigate } from "react-router";
import { gsap } from "gsap";
import { ProductTransitionContext } from "../transitions/ProductTransitionContext";
import "./ProductTransition.css";

const supportsProductTransition = () => {
  if (typeof window === "undefined") return false;

  return !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
};

const rectToObject = (rect) => ({
  top: rect.top,
  left: rect.left,
  width: rect.width,
  height: rect.height,
});

const isPlainNavigationClick = (event) =>
  event.button === 0 &&
  !event.metaKey &&
  !event.altKey &&
  !event.ctrlKey &&
  !event.shiftKey;

const getStageRect = (sourceRect) => {
  const viewportWidth = window.innerWidth;
  const viewportHeight = window.innerHeight;
  const sourceRatio = sourceRect.height / sourceRect.width || 1;
  const stageWidth = Math.min(
    Math.max(sourceRect.width * 1.16, 260),
    viewportWidth * 0.56,
    620
  );
  const stageHeight = Math.min(stageWidth * sourceRatio, viewportHeight * 0.72);
  const normalizedWidth = stageHeight / sourceRatio;

  return {
    left: (viewportWidth - normalizedWidth) / 2,
    top: (viewportHeight - stageHeight) / 2,
    width: normalizedWidth,
    height: stageHeight,
  };
};

const getTransformForRect = (rect, sourceRect) => ({
  x: rect.left,
  y: rect.top,
  scaleX: rect.width / sourceRect.width,
  scaleY: rect.height / sourceRect.height,
});

export function ProductTransitionProvider({ children }) {
  const navigate = useNavigate();
  const overlayRef = useRef(null);
  const imageRef = useRef(null);
  const washRef = useRef(null);
  const timelineRef = useRef(null);
  const [transition, setTransition] = useState(null);

  const clearTransition = useCallback(() => {
    timelineRef.current?.kill();
    timelineRef.current = null;
    document.body.classList.remove("product-transition-active");
    setTransition(null);
  }, []);

  const startProductTransition = useCallback(
    (event, perfume) => {
      if (!perfume?.slug || !isPlainNavigationClick(event)) {
        return false;
      }

      if (!supportsProductTransition()) {
        return false;
      }

      const card = event.currentTarget;
      const image = card.querySelector("[data-product-transition-source]");

      if (!image) {
        return false;
      }

      const sourceRect = image.getBoundingClientRect();

      if (sourceRect.width <= 0 || sourceRect.height <= 0) {
        return false;
      }

      event.preventDefault();
      timelineRef.current?.kill();
      document.body.classList.add("product-transition-active");

      setTransition({
        id: `${perfume.slug}-${Date.now()}`,
        slug: perfume.slug,
        to: `/duft/${perfume.slug}`,
        image: image.currentSrc || image.src || perfume.image,
        alt: perfume.name,
        sourceRect: rectToObject(sourceRect),
        phase: "leaving",
      });

      return true;
    },
    []
  );

  useLayoutEffect(() => {
    if (transition?.phase !== "leaving") {
      return undefined;
    }

    const overlay = overlayRef.current;
    const image = imageRef.current;
    const wash = washRef.current;

    if (!overlay || !image || !wash) {
      return undefined;
    }

    const routeContent = document.querySelector("[data-route-content]");
    const sourceRect = transition.sourceRect;
    const stageRect = getStageRect(sourceRect);
    let completed = false;

    gsap.set(overlay, { autoAlpha: 1, pointerEvents: "auto" });
    gsap.set(wash, { autoAlpha: 0 });
    gsap.set(image, {
      x: sourceRect.left,
      y: sourceRect.top,
      width: sourceRect.width,
      height: sourceRect.height,
      scaleX: 1,
      scaleY: 1,
      autoAlpha: 1,
      transformOrigin: "0 0",
      force3D: true,
    });

    timelineRef.current = gsap.timeline({
      defaults: { ease: "power4.inOut" },
      onComplete: () => {
        completed = true;

        navigate(transition.to, {
          state: {
            productTransition: true,
            transitionId: transition.id,
          },
        });

        setTransition((current) =>
          current?.id === transition.id
            ? { ...current, phase: "entering" }
            : current
        );
      },
    });

    timelineRef.current
      .to(wash, { autoAlpha: 1, duration: 0.42, ease: "power2.out" }, 0)
      .to(
        routeContent,
        {
          autoAlpha: 0.16,
          filter: "blur(10px)",
          scale: 0.985,
          duration: 0.62,
          ease: "power3.out",
        },
        0
      )
      .to(
        image,
        {
          ...getTransformForRect(stageRect, sourceRect),
          duration: 0.78,
        },
        0.03
      );

    return () => {
      if (!completed) {
        timelineRef.current?.kill();
      }
    };
  }, [navigate, transition]);

  useLayoutEffect(() => {
    if (transition?.phase !== "entering") {
      return undefined;
    }

    const overlay = overlayRef.current;
    const image = imageRef.current;
    const wash = washRef.current;

    if (!overlay || !image || !wash) {
      return undefined;
    }

    let frame = 0;
    let cancelled = false;
    let completed = false;
    const sourceRect = transition.sourceRect;

    const runEnterAnimation = () => {
      if (cancelled) return;

      window.scrollTo({ top: 0, left: 0, behavior: "instant" });

      const target = document.querySelector(
        `[data-product-transition-target="${transition.slug}"]`
      );

      if (!target && frame < 16) {
        frame += 1;
        window.requestAnimationFrame(runEnterAnimation);
        return;
      }

      const routeContent = document.querySelector("[data-route-content]");
      gsap.set(routeContent, {
        autoAlpha: 1,
        filter: "none",
        scale: 1,
        clearProps: "opacity,visibility,filter,transform",
      });

      if (!target) {
        gsap.to(overlay, {
          autoAlpha: 0,
          duration: 0.3,
          ease: "power2.out",
          onComplete: clearTransition,
        });
        return;
      }

      const targetRect = rectToObject(target.getBoundingClientRect());
      const revealItems = gsap.utils.toArray("[data-product-transition-reveal]");

      gsap.set(target, { autoAlpha: 0 });
      gsap.set(revealItems, { y: 28, autoAlpha: 0, force3D: true });

      timelineRef.current?.kill();
      timelineRef.current = gsap.timeline({
        defaults: { ease: "power4.out" },
        onComplete: () => {
          completed = true;
          clearTransition();
        },
      });

      timelineRef.current
        .to(image, {
          ...getTransformForRect(targetRect, sourceRect),
          duration: 0.72,
        })
        .set(target, { autoAlpha: 1 }, ">-0.08")
        .to(image, { autoAlpha: 0, duration: 0.16, ease: "power2.out" }, "<")
        .to(wash, { autoAlpha: 0, duration: 0.5, ease: "power2.out" }, "<")
        .to(
          revealItems,
          {
            y: 0,
            autoAlpha: 1,
            duration: 0.82,
            stagger: 0.07,
            ease: "power4.out",
            clearProps: "transform,opacity,visibility",
          },
          ">-0.05"
        );
    };

    window.requestAnimationFrame(runEnterAnimation);

    return () => {
      cancelled = true;
      if (!completed) {
        timelineRef.current?.kill();
      }
    };
  }, [clearTransition, transition]);

  const value = useMemo(
    () => ({
      activeSlug: transition?.slug || null,
      phase: transition?.phase || "idle",
      startProductTransition,
    }),
    [startProductTransition, transition]
  );

  return (
    <ProductTransitionContext.Provider value={value}>
      {children}

      {transition && (
        <div
          className={`product-transition product-transition--${transition.phase}`}
          ref={overlayRef}
          aria-hidden="true"
        >
          <div className="product-transition__wash" ref={washRef} />
          <img
            className="product-transition__image"
            src={transition.image}
            alt={transition.alt}
            ref={imageRef}
            decoding="async"
          />
        </div>
      )}
    </ProductTransitionContext.Provider>
  );
}
