import gsap from "gsap";
import { CustomEase } from "gsap/CustomEase";
import {
  videoBannerLogoMorphAnimationConfig,
  videoBannerLogoMorphCurvedPaths as curvedPaths,
  videoBannerLogoMorphEquidistantOffsets as equidistantOffsets,
  videoBannerLogoMorphStackedCenterOffsets as stackedCenterOffsets,
  videoBannerLogoMorphStraightPaths as straightPaths,
} from "@/lib/logo/videoBannerLogoMorph";

const CFVIDEO_VISIBILITY_ATTR = "data-cfvideo-visibility";
const CFVIDEO_VISIBILITY_EVENT = "cfvideo:visibilitychange";
const VIDEO_BANNER_LOGO_MORPH_SELECTOR = "[data-video-banner-logo-morph]";
const VIDEO_BANNER_LOGO_LINE_SELECTOR = "[data-video-banner-logo-line]";
const VIDEO_BANNER_SELECTOR = ".video-banner";
const VIDEO_BANNER_DOWN_ARROW_SELECTOR = ".video-banner__down-arrow";
const VIDEO_WRAP_SELECTOR = ".hw-vid-wrap";
const HOME_RETURN_FADE_CLASS = "header-home-banner-return-fade";
const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
const LOGO_MORPH_ENTRY_DELAY_SECONDS = .5;
const LOGO_MORPH_HOME_RETURN_ENTRY_DELAY_SECONDS = 0.18;
const LOGO_MORPH_FADE_DURATION_SECONDS = 1;
const LOGO_MORPH_FIRST_BAR_INDEX = 0;
const LOGO_MORPH_FIRST_BAR_FADE_DURATION_SECONDS = 0.85;
const LOGO_MORPH_FIRST_BAR_OVERLAP_SECONDS = 0.25;
const DOWN_ARROW_PULSE_MIN_OPACITY = 0.4;
const DOWN_ARROW_PULSE_DURATION_SECONDS = 1.8;
const DOWN_ARROW_SCROLL_STOP_THRESHOLD_PX = 2;
const DEFAULT_CUBIC_BEZIER = ".36,.01,.1,1.01";

gsap.registerPlugin(CustomEase);

type VideoBannerLogoMorphController = {
  root: HTMLElement;
  cleanup: () => void;
};

type VideoBannerLogoMorphWindow = typeof window & {
  __hwVideoBannerLogoMorphBound__?: boolean;
  __hwVideoBannerLogoMorphControllers__?: VideoBannerLogoMorphController[];
  __hwVideoBannerLogoMorphInitRaf__?: number;
};

const logoMorphWindow = window as VideoBannerLogoMorphWindow;

function getControllers() {
  if (!Array.isArray(logoMorphWindow.__hwVideoBannerLogoMorphControllers__)) {
    logoMorphWindow.__hwVideoBannerLogoMorphControllers__ = [];
  }

  return logoMorphWindow.__hwVideoBannerLogoMorphControllers__;
}

function revealRoot(root: HTMLElement) {
  gsap.set(root, { autoAlpha: 1 });
}

function hideRoot(root: HTMLElement) {
  gsap.set(root, { autoAlpha: 0 });
}

function getLogoMorphFadeEase() {
  const cubicBezValue = getComputedStyle(document.documentElement)
    .getPropertyValue("--cubicBez")
    .trim();
  const controlPoints =
    cubicBezValue.match(/cubic-bezier\(([^)]+)\)/i)?.[1]?.trim() ||
    DEFAULT_CUBIC_BEZIER;

  return CustomEase.create("", controlPoints);
}

function getLogoMorphEntryDelaySeconds() {
  if (document.documentElement.classList.contains(HOME_RETURN_FADE_CLASS)) {
    return LOGO_MORPH_HOME_RETURN_ENTRY_DELAY_SECONDS;
  }

  return LOGO_MORPH_ENTRY_DELAY_SECONDS;
}

function getPageScrollY() {
  return (
    window.scrollY ||
    window.pageYOffset ||
    document.documentElement.scrollTop ||
    document.body.scrollTop ||
    0
  );
}

type LenisLike = {
  on?: (event: "scroll", handler: (event: unknown) => void) => void;
  off?: (event: "scroll", handler: (event: unknown) => void) => void;
};

function getLenisInstance(): LenisLike | null {
  const locoScroll = (window as typeof window & {
    locoScroll?: { lenisInstance?: LenisLike | null };
  }).locoScroll;

  return locoScroll?.lenisInstance ?? null;
}

function getLenisScrollY(event: unknown): number | null {
  if (!event || typeof event !== "object") return null;

  const maybeScroll = event as { animatedScroll?: unknown; scroll?: unknown };

  if (typeof maybeScroll.animatedScroll === "number") {
    return maybeScroll.animatedScroll;
  }

  if (typeof maybeScroll.scroll === "number") {
    return maybeScroll.scroll;
  }

  return null;
}

function setStaticState(
  root: HTMLElement,
  lines: SVGPathElement[],
  downArrow: HTMLElement | null,
) {
  lines.forEach((line, index) => {
    line.setAttribute("d", curvedPaths[index] ?? curvedPaths[0] ?? "");
    gsap.set(line, {
      clearProps: "filter",
      opacity: 1,
      x: 0,
    });
  });

  if (downArrow) {
    gsap.set(downArrow, { opacity: 1 });
  }

  revealRoot(root);
}

function createController(root: HTMLElement): VideoBannerLogoMorphController {
  const banner = root.closest<HTMLElement>(VIDEO_BANNER_SELECTOR);
  const downArrow =
    banner?.querySelector<HTMLElement>(VIDEO_BANNER_DOWN_ARROW_SELECTOR) ?? null;
  const videoWrap = banner?.querySelector<HTMLElement>(VIDEO_WRAP_SELECTOR) ?? null;
  const lines = Array.from(
    root.querySelectorAll<SVGPathElement>(VIDEO_BANNER_LOGO_LINE_SELECTOR),
  );

  if (
    lines.length !== curvedPaths.length ||
    window.matchMedia(REDUCED_MOTION_QUERY).matches
  ) {
    setStaticState(root, lines, downArrow);

    return {
      root,
      cleanup() {
        gsap.killTweensOf(root);
        gsap.killTweensOf(lines);
        if (downArrow) {
          gsap.killTweensOf(downArrow);
        }
      },
    };
  }

  let timeline: gsap.core.Timeline | null = null;
  let pendingPlay: gsap.core.Tween | null = null;
  let arrowPulseTween: gsap.core.Tween | null = null;
  let isVisible: boolean | null = null;
  let hasStoppedArrowPulseFromScroll = false;

  const primeLines = () => {
    lines.forEach((line, index) => {
      line.setAttribute("d", straightPaths[index]);
      gsap.set(line, {
        filter: "blur(0px)",
        opacity: 0,
        x: stackedCenterOffsets[index],
      });
    });
  };

  const stopArrowPulse = () => {
    arrowPulseTween?.kill();
    arrowPulseTween = null;

    if (downArrow) {
      gsap.set(downArrow, { opacity: 1 });
    }
  };

  const settleArrowPulseToRest = () => {
    if (!downArrow || !arrowPulseTween) return;

    const currentOpacity = Number(gsap.getProperty(downArrow, "opacity"));
    arrowPulseTween.kill();
    arrowPulseTween = null;

    if (!Number.isFinite(currentOpacity) || currentOpacity >= 0.999) {
      gsap.set(downArrow, { opacity: 1 });
      return;
    }

    const opacityDelta = 1 - DOWN_ARROW_PULSE_MIN_OPACITY;
    const remainingProgress =
      opacityDelta > 0 ? (1 - currentOpacity) / opacityDelta : 1;
    const settleDuration = Math.max(
      0.18,
      DOWN_ARROW_PULSE_DURATION_SECONDS * remainingProgress,
    );

    arrowPulseTween = gsap.to(downArrow, {
      duration: settleDuration,
      ease: "sine.out",
      opacity: 1,
      overwrite: true,
      onComplete: () => {
        arrowPulseTween = null;
      },
    });
  };

  const startArrowPulse = () => {
    if (!downArrow || hasStoppedArrowPulseFromScroll || isVisible !== true) return;

    stopArrowPulse();
    arrowPulseTween = gsap.to(downArrow, {
      duration: DOWN_ARROW_PULSE_DURATION_SECONDS,
      ease: "sine.inOut",
      opacity: DOWN_ARROW_PULSE_MIN_OPACITY,
      repeat: -1,
      yoyo: true,
    });
  };

  const stopArrowPulseFromScroll = (scrollY: number | null = null) => {
    const resolvedScrollY =
      typeof scrollY === "number" && Number.isFinite(scrollY)
        ? scrollY
        : getPageScrollY();

    if (resolvedScrollY <= DOWN_ARROW_SCROLL_STOP_THRESHOLD_PX) return;

    hasStoppedArrowPulseFromScroll = true;
    settleArrowPulseToRest();
  };

  const bindArrowPulseScrollStop = () => {
    let activeLenis: LenisLike | null = null;

    const onNativeScroll = () => {
      stopArrowPulseFromScroll(getPageScrollY());
    };
    const onLenisScroll = (event: unknown) => {
      stopArrowPulseFromScroll(getLenisScrollY(event));
    };
    const syncScrollSource = () => {
      const nextLenis = getLenisInstance();

      if (activeLenis === nextLenis) {
        stopArrowPulseFromScroll();
        return;
      }

      activeLenis?.off?.("scroll", onLenisScroll);
      activeLenis = nextLenis;
      window.removeEventListener("scroll", onNativeScroll);

      if (activeLenis?.on) {
        activeLenis.on("scroll", onLenisScroll);
      } else {
        window.addEventListener("scroll", onNativeScroll, { passive: true });
      }

      stopArrowPulseFromScroll();
    };
    const onLocoReady = () => {
      requestAnimationFrame(syncScrollSource);
    };

    requestAnimationFrame(syncScrollSource);
    document.addEventListener("loco:ready", onLocoReady);

    return () => {
      activeLenis?.off?.("scroll", onLenisScroll);
      activeLenis = null;
      window.removeEventListener("scroll", onNativeScroll);
      document.removeEventListener("loco:ready", onLocoReady);
    };
  };

  const cleanupArrowPulseScrollStop = bindArrowPulseScrollStop();

  const resetToStart = () => {
    pendingPlay?.kill();
    pendingPlay = null;
    timeline?.pause(0);
    hideRoot(root);
    stopArrowPulse();
    if (downArrow) {
      gsap.set(downArrow, { opacity: 0 });
    }
    primeLines();
  };

  const createTimeline = () => {
    timeline?.kill();
    resetToStart();

    const firstLine = lines[LOGO_MORPH_FIRST_BAR_INDEX] ?? null;
    const slideStagger = {
      amount: videoBannerLogoMorphAnimationConfig.slideStaggerAmount,
      from: "center" as const,
    };

    timeline = gsap.timeline({
      paused: true,
    });

    const firstRevealTargets = [firstLine, downArrow].filter(
      (target): target is SVGPathElement | HTMLElement => target !== null,
    );

    if (firstRevealTargets.length > 0) {
      timeline.to(firstRevealTargets, {
        duration: LOGO_MORPH_FIRST_BAR_FADE_DURATION_SECONDS,
        ease: getLogoMorphFadeEase(),
        opacity: 1,
        onComplete: startArrowPulse,
      });
    }

    timeline
      .to(
        lines,
        {
          duration: videoBannerLogoMorphAnimationConfig.slideDuration,
          ease: "power3.out",
          stagger: slideStagger,
          x: (index) => equidistantOffsets[index],
        },
        `>-${LOGO_MORPH_FIRST_BAR_OVERLAP_SECONDS}`,
      )
      .to(
        lines,
        {
          duration: LOGO_MORPH_FADE_DURATION_SECONDS,
          ease: getLogoMorphFadeEase(),
          opacity: 1,
          stagger: slideStagger,
        },
        "<",
      )
      .to(
        lines,
        {
          attr: {
            d: (index) => curvedPaths[index],
          },
          duration: videoBannerLogoMorphAnimationConfig.curveDuration,
          ease: "power2.inOut",
          stagger: videoBannerLogoMorphAnimationConfig.curveStagger,
          x: 0,
        },
        `+=${videoBannerLogoMorphAnimationConfig.pauseBetween}`,
      );
  };

  const playFromStart = () => {
    resetToStart();
    pendingPlay = gsap.delayedCall(getLogoMorphEntryDelaySeconds(), () => {
      pendingPlay = null;
      revealRoot(root);
      timeline?.play(0);
    });
  };

  const readVisibilityState = () => {
    const visibility = videoWrap?.getAttribute(CFVIDEO_VISIBILITY_ATTR);

    if (visibility === "visible") return true;
    if (visibility === "hidden") return false;
    return null;
  };

  const syncVisibility = (nextVisible: boolean, { force = false } = {}) => {
    if (!force && isVisible === nextVisible) return;

    isVisible = nextVisible;

    if (nextVisible) {
      playFromStart();
      return;
    }

    resetToStart();
  };

  const onVisibilityChange = (event: Event) => {
    const detail = (event as CustomEvent<{ isVisible?: boolean }>).detail;

    if (!detail || typeof detail.isVisible !== "boolean") return;
    syncVisibility(detail.isVisible);
  };

  createTimeline();
  videoWrap?.addEventListener(CFVIDEO_VISIBILITY_EVENT, onVisibilityChange);

  const initialVisibility = readVisibilityState();

  if (initialVisibility === true) {
    syncVisibility(true, { force: true });
  } else {
    syncVisibility(false, { force: true });
  }

  return {
    root,
    cleanup() {
      pendingPlay?.kill();
      pendingPlay = null;
      timeline?.kill();
      stopArrowPulse();
      cleanupArrowPulseScrollStop();
      videoWrap?.removeEventListener(CFVIDEO_VISIBILITY_EVENT, onVisibilityChange);
      gsap.killTweensOf(root);
      gsap.killTweensOf(lines);
      if (downArrow) {
        gsap.killTweensOf(downArrow);
      }
    },
  };
}

function cleanupControllers() {
  if (logoMorphWindow.__hwVideoBannerLogoMorphInitRaf__) {
    window.cancelAnimationFrame(logoMorphWindow.__hwVideoBannerLogoMorphInitRaf__);
    logoMorphWindow.__hwVideoBannerLogoMorphInitRaf__ = 0;
  }

  const controllers = getControllers();

  while (controllers.length) {
    controllers.pop()?.cleanup();
  }
}

function initControllers() {
  const roots = Array.from(
    document.querySelectorAll<HTMLElement>(VIDEO_BANNER_LOGO_MORPH_SELECTOR),
  );

  const controllers = getControllers();

  if (!roots.length) {
    cleanupControllers();
    return;
  }

  const controllersAlreadyMatchRoots =
    controllers.length === roots.length &&
    controllers.every(
      (controller) =>
        controller.root.isConnected && roots.includes(controller.root),
    );

  if (controllersAlreadyMatchRoots) return;

  cleanupControllers();

  const nextControllers = getControllers();
  roots.forEach((root) => {
    nextControllers.push(createController(root));
  });
}

function queueInit() {
  if (document.readyState === "loading") return;

  if (logoMorphWindow.__hwVideoBannerLogoMorphInitRaf__) {
    window.cancelAnimationFrame(logoMorphWindow.__hwVideoBannerLogoMorphInitRaf__);
  }

  logoMorphWindow.__hwVideoBannerLogoMorphInitRaf__ = window.requestAnimationFrame(
    () => {
      logoMorphWindow.__hwVideoBannerLogoMorphInitRaf__ = 0;
      initControllers();
    },
  );
}

function bindOnce() {
  if (logoMorphWindow.__hwVideoBannerLogoMorphBound__) return;

  logoMorphWindow.__hwVideoBannerLogoMorphBound__ = true;

  document.addEventListener("astro:before-swap", cleanupControllers);
  document.addEventListener("astro:page-load", queueInit);
  document.addEventListener("astro:after-swap", queueInit);
  window.addEventListener("pagehide", cleanupControllers);
  window.addEventListener("pageshow", (event) => {
    if ((event as PageTransitionEvent).persisted) {
      queueInit();
    }
  });

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", queueInit, { once: true });
  }
}

bindOnce();
queueInit();
