<template>
  <section
    ref="promoSectionRef"
    :class="{ 'is-promo-ready': isPromoReady }"
    class="promo-section relative h-[580px] sm:h-[640px] md:h-[720px] xl:h-[960px] w-screen overflow-hidden bg-white"
    @mousemove="handleMouseMove"
  >
    <div class="relative h-full w-full overflow-hidden">
      <div
        class="pointer-events-none absolute left-1/2 top-1/2 z-30 w-[min(88vw,760px)] -translate-x-1/2 -translate-y-1/2 text-center"
      >
        <h1
          class="hero-title relative -top-28 m-0 text-[clamp(34px,10vw,48px)] font-thin leading-[0.9] text-dark md:-top-24 md:text-[clamp(44px,7vw,70px)]"
          v-html="section.title"
        />
      </div>

      <div class="promo-plates-layer pointer-events-none absolute inset-0 z-10" aria-hidden="true">
        <picture
          v-for="(plate, index) in section.plates"
          :key="`${plate.src}-${index}`"
        >
          <source
            :srcset="plate.src.replace(/\.(png|jpg|jpeg)$/i, '.avif')"
            type="image/avif"
          />
          <source
            :srcset="plate.src.replace(/\.(png|jpg|jpeg)$/i, '.webp')"
            type="image/webp"
          />
          <img
            :src="plate.src"
            class="promo-plate absolute left-1/2 top-1/2 h-auto select-none object-contain"
            :class="`promo-plate-${index + 1}`"
            draggable="false"
            :alt="plate.alt ?? ''"
            width="1024"
            height="1024"
            decoding="async"
            fetchpriority="high"
            aria-hidden="true"
          />
        </picture>
      </div>
    </div>
  </section>
</template>

<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, nextTick } from "vue";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";

gsap.registerPlugin(ScrollTrigger);

type SectionButtonIconName =
  | "mail"
  | "document"
  | "code"
  | "external"
  | "arrowRight"
  | "arrowUp"
  | "none";

type SectionButtonIconPosition = "left" | "right" | "center";

interface SectionButton {
  enabled: boolean;
  label: string;
  url: string;
  external: boolean;
  color?: string | null;
  icon?: {
    name?: SectionButtonIconName;
    position?: SectionButtonIconPosition;
  };
}

interface PromoPlate {
  src: string;
  alt?: string;
}

interface SectionProps {
  title: string;
  description: string;
  button: SectionButton;
  plates: PromoPlate[];
}

interface PlateLayout {
  x: number;
  y: number;
  size: number;
  r: number;
  fromX: number;
  fromY: number;
  sx: number;
  sy: number;
  sr: number;
}

type Range = "mobileSmall" | "mobileLarge" | "tablet" | "desktop";

defineProps<{
  section: SectionProps;
}>();

const promoSectionRef = ref<HTMLElement | null>(null);
const isPromoReady = ref(false);

let ctx: gsap.Context | undefined;
let resizeTimer: number | undefined;
let mouseMoveFrame: number | undefined;
let latestMouseEvent: MouseEvent | null = null;

const getRange = (): Range => {
  const w = window.innerWidth;

  if (w <= 425) return "mobileSmall";
  if (w <= 768) return "mobileLarge";
  if (w <= 1140) return "tablet";

  return "desktop";
};

const isTouchLayout = (): boolean =>
  getRange() === "mobileSmall" || getRange() === "mobileLarge";

const getPlateElements = (): HTMLElement[] => {
  if (!promoSectionRef.value) return [];

  return gsap.utils.toArray<HTMLElement>(
    promoSectionRef.value.querySelectorAll(".promo-plate"),
  );
};

const applyMouseMove = (e: MouseEvent): void => {
  if (isTouchLayout()) return;
  if (!promoSectionRef.value) return;

  const rect = promoSectionRef.value.getBoundingClientRect();

  const x = (e.clientX - rect.left) / rect.width - 0.5;
  const y = (e.clientY - rect.top) / rect.height - 0.5;

  const plateEls = getPlateElements();

  gsap.to(plateEls, {
    "--tilt-x": `${x * 6}deg`,
    "--tilt-y": `${y * -6}deg`,
    "--tilt-move-x": `${x * 2}vw`,
    "--tilt-move-y": `${y * 2}vw`,
    duration: 0.1,
    ease: "power2.out",
    overwrite: "auto",
  });
};

const handleMouseMove = (e: MouseEvent): void => {
  if (isTouchLayout()) return;

  latestMouseEvent = e;

  if (mouseMoveFrame) return;

  mouseMoveFrame = window.requestAnimationFrame(() => {
    mouseMoveFrame = undefined;

    if (!latestMouseEvent) return;

    applyMouseMove(latestMouseEvent);
  });
};

const desktopLayout: PlateLayout[] = [
  {
    x: -43,
    y: -16,
    size: 20,
    r: -13,
    fromX: -90,
    fromY: -22,
    sx: -3.5,
    sy: 2.5,
    sr: -1.5,
  },
  {
    x: -34,
    y: 25,
    size: 20,
    r: 10,
    fromX: -58,
    fromY: 62,
    sx: 2.5,
    sy: -2,
    sr: 1.2,
  },
  {
    x: 6,
    y: 20,
    size: 30,
    r: -8,
    fromX: 18,
    fromY: 68,
    sx: -2.5,
    sy: -2,
    sr: -1.2,
  },
  {
    x: 44,
    y: -10,
    size: 20,
    r: 12,
    fromX: 92,
    fromY: -24,
    sx: 3.5,
    sy: 2.5,
    sr: 1.5,
  },
  {
    x: -47,
    y: 11,
    size: 25,
    r: 9,
    fromX: -92,
    fromY: 8,
    sx: 3,
    sy: -2,
    sr: 1.2,
  },
  {
    x: -18,
    y: 10,
    size: 22,
    r: -11,
    fromX: -42,
    fromY: 42,
    sx: -3,
    sy: -2.5,
    sr: -1.2,
  },
  {
    x: 18,
    y: 6,
    size: 17,
    r: 13,
    fromX: 42,
    fromY: 30,
    sx: 3,
    sy: -2,
    sr: 1.2,
  },
  {
    x: 47,
    y: 13,
    size: 14,
    r: -9,
    fromX: 92,
    fromY: 12,
    sx: -3,
    sy: -2,
    sr: -1.2,
  },
  {
    x: -35,
    y: -5,
    size: 13,
    r: 14,
    fromX: -52,
    fromY: 40,
    sx: 2,
    sy: -3,
    sr: 1.2,
  },
  {
    x: 32,
    y: 20,
    size: 21,
    r: -12,
    fromX: 54,
    fromY: 62,
    sx: -2,
    sy: -3,
    sr: -1.2,
  },
];

const tabletLayout: PlateLayout[] = [
  {
    x: -41,
    y: -15,
    size: 18,
    r: -13,
    fromX: -78,
    fromY: -22,
    sx: -2.2,
    sy: 1.5,
    sr: -0.9,
  },
  {
    x: -34,
    y: 23,
    size: 18,
    r: 10,
    fromX: -56,
    fromY: 54,
    sx: 1.6,
    sy: -1.3,
    sr: 0.8,
  },
  {
    x: 4,
    y: 22,
    size: 24,
    r: -8,
    fromX: 16,
    fromY: 56,
    sx: -1.6,
    sy: -1.3,
    sr: -0.8,
  },
  {
    x: 41,
    y: -10,
    size: 18,
    r: 12,
    fromX: 78,
    fromY: -22,
    sx: 2.2,
    sy: 1.5,
    sr: 0.9,
  },
  {
    x: -44,
    y: 7,
    size: 21,
    r: 9,
    fromX: -78,
    fromY: 8,
    sx: 1.8,
    sy: -1.2,
    sr: 0.8,
  },
  {
    x: -18,
    y: 8,
    size: 19,
    r: -11,
    fromX: -40,
    fromY: 38,
    sx: -1.8,
    sy: -1.4,
    sr: -0.8,
  },
  {
    x: 18,
    y: 4,
    size: 16,
    r: 13,
    fromX: 40,
    fromY: 30,
    sx: 1.8,
    sy: -1.2,
    sr: 0.8,
  },
  {
    x: 43,
    y: 10,
    size: 14,
    r: -9,
    fromX: 78,
    fromY: 12,
    sx: -1.8,
    sy: -1.2,
    sr: -0.8,
  },
  {
    x: -33,
    y: -4,
    size: 13,
    r: 14,
    fromX: -50,
    fromY: 36,
    sx: 1.2,
    sy: -1.8,
    sr: 0.8,
  },
  {
    x: 31,
    y: 20,
    size: 18,
    r: -12,
    fromX: 50,
    fromY: 52,
    sx: -1.2,
    sy: -1.8,
    sr: -0.8,
  },
];

const mobileLargeLayout: PlateLayout[] = [
  {
    x: -40,
    y: -25,
    size: 35,
    r: -13,
    fromX: -70,
    fromY: -24,
    sx: -1,
    sy: 0.7,
    sr: -0.5,
  },
  {
    x: -30,
    y: 30,
    size: 25,
    r: 10,
    fromX: -62,
    fromY: 48,
    sx: 0.9,
    sy: -0.7,
    sr: 0.5,
  },
  {
    x: -2,
    y: 33,
    size: 32,
    r: -8,
    fromX: 12,
    fromY: 50,
    sx: -0.9,
    sy: -0.7,
    sr: -0.5,
  },
  {
    x: 40,
    y: -20,
    size: 34,
    r: 12,
    fromX: 70,
    fromY: -24,
    sx: 1,
    sy: 0.7,
    sr: 0.5,
  },
  {
    x: -44,
    y: 6,
    size: 32,
    r: 9,
    fromX: -72,
    fromY: 6,
    sx: 0.8,
    sy: -0.6,
    sr: 0.5,
  },
  {
    x: -10,
    y: 6,
    size: 33,
    r: -11,
    fromX: -38,
    fromY: 36,
    sx: -0.8,
    sy: -0.6,
    sr: -0.5,
  },
  {
    x: 19,
    y: 5,
    size: 28,
    r: 13,
    fromX: 38,
    fromY: 30,
    sx: 0.8,
    sy: -0.6,
    sr: 0.5,
  },
  {
    x: 44,
    y: 8,
    size: 20,
    r: -9,
    fromX: 72,
    fromY: 10,
    sx: -0.8,
    sy: -0.6,
    sr: -0.5,
  },
  {
    x: -28,
    y: -5,
    size: 22,
    r: 14,
    fromX: -50,
    fromY: 32,
    sx: 0.7,
    sy: -0.8,
    sr: 0.5,
  },
  {
    x: 32,
    y: 30,
    size: 34,
    r: -12,
    fromX: 52,
    fromY: 48,
    sx: -0.7,
    sy: -0.8,
    sr: -0.5,
  },
];

const mobileSmallLayout: PlateLayout[] = [
  {
    x: -38,
    y: -30,
    size: 35,
    r: -13,
    fromX: -68,
    fromY: -24,
    sx: -0.8,
    sy: 0.5,
    sr: -0.4,
  },
  {
    x: -34,
    y: 43,
    size: 25,
    r: 10,
    fromX: -62,
    fromY: 48,
    sx: 0.7,
    sy: -0.5,
    sr: 0.4,
  },
  {
    x: 0,
    y: 40,
    size: 35,
    r: -8,
    fromX: 12,
    fromY: 52,
    sx: -0.7,
    sy: -0.5,
    sr: -0.4,
  },
  {
    x: 38,
    y: -20,
    size: 30,
    r: 12,
    fromX: 68,
    fromY: -24,
    sx: 0.8,
    sy: 0.5,
    sr: 0.4,
  },
  {
    x: -43,
    y: 18,
    size: 30,
    r: 9,
    fromX: -70,
    fromY: 6,
    sx: 0.6,
    sy: -0.4,
    sr: 0.4,
  },
  {
    x: -2,
    y: 3,
    size: 40,
    r: -11,
    fromX: -38,
    fromY: 36,
    sx: -0.6,
    sy: -0.4,
    sr: -0.4,
  },
  {
    x: 23,
    y: 20,
    size: 25,
    r: 13,
    fromX: 38,
    fromY: 30,
    sx: 0.6,
    sy: -0.4,
    sr: 0.4,
  },
  {
    x: 45,
    y: 5,
    size: 19,
    r: -9,
    fromX: 70,
    fromY: 10,
    sx: -0.6,
    sy: -0.4,
    sr: -0.4,
  },
  {
    x: -33,
    y: -5,
    size: 23,
    r: 14,
    fromX: -48,
    fromY: 32,
    sx: 0.5,
    sy: -0.6,
    sr: 0.4,
  },
  {
    x: 40,
    y: 40,
    size: 28,
    r: -12,
    fromX: 52,
    fromY: 48,
    sx: -0.5,
    sy: -0.6,
    sr: -0.4,
  },
];

const getLayout = (): PlateLayout[] => {
  const range = getRange();

  if (range === "mobileSmall") return mobileSmallLayout;
  if (range === "mobileLarge") return mobileLargeLayout;
  if (range === "tablet") return tabletLayout;

  return desktopLayout;
};

const getLayoutOffsetY = (): number => {
  const range = getRange();

  if (range === "mobileSmall") return 110;
  if (range === "mobileLarge") return 98;
  if (range === "tablet") return 86;

  return 70;
};

const getPlateWidth = (p: PlateLayout): string => {
  const range = getRange();

  if (range === "mobileSmall") {
    return `clamp(${p.size * 4.1}px, ${p.size}vw, ${p.size * 5.4}px)`;
  }

  if (range === "mobileLarge") {
    return `clamp(${p.size * 4.6}px, ${p.size}vw, ${p.size * 7}px)`;
  }

  if (range === "tablet") {
    return `clamp(${p.size * 6.2}px, ${p.size}vw, ${p.size * 10.5}px)`;
  }

  return `clamp(${p.size * 8}px, ${p.size}vw, ${p.size * 18}px)`;
};

const waitForPromoImages = async (): Promise<void> => {
  if (!promoSectionRef.value) return;

  const images = Array.from(
    promoSectionRef.value.querySelectorAll<HTMLImageElement>(".promo-plate"),
  );

  await Promise.all(
    images.map(async (img) => {
      if (!img.complete || img.naturalWidth === 0) {
        await new Promise<void>((resolve) => {
          img.addEventListener("load", () => resolve(), { once: true });
          img.addEventListener("error", () => resolve(), { once: true });
        });
      }

      try {
        await img.decode();
      } catch {}
    }),
  );
};

const initHero = (): void => {
  isPromoReady.value = false;

  ctx?.revert();

  if (!promoSectionRef.value) return;

  const section = promoSectionRef.value;

  ctx = gsap.context(() => {
    const layout = getLayout();
    const layoutOffsetY = getLayoutOffsetY();

    const plateEls = gsap.utils.toArray<HTMLElement>(
      section.querySelectorAll(".promo-plate"),
    );

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

    plateEls.forEach((plate, i) => {
      const p = layout[i];

      if (!p) {
        gsap.set(plate, {
          visibility: "visible",
          opacity: 0,
        });
        return;
      }

      gsap.set(plate, {
        opacity: 1,
        display: "block",
        width: getPlateWidth(p),
        "--layout-offset-y": `${layoutOffsetY}px`,
        "--base-x": `${p.x}vw`,
        "--base-y": `${p.y}vw`,
        "--base-r": `${p.r}deg`,
        "--intro-x": `${p.fromX - p.x}vw`,
        "--intro-y": `${p.fromY - p.y}vw`,
        "--intro-r": `${p.r * 0.4}deg`,
        "--parallax-x": "0vw",
        "--parallax-y": "0vw",
        "--parallax-r": "0deg",
        "--tilt-x": "0deg",
        "--tilt-y": "0deg",
        "--tilt-move-x": "0vw",
        "--tilt-move-y": "0vw",
        "--plate-scale": 0.9,
      });

      introTl.to(
        plate,
        {
          "--intro-x": "0vw",
          "--intro-y": "0vw",
          "--intro-r": "0deg",
          "--plate-scale": 1,
          opacity: 1,
          duration: 1,
          ease: "power3.out",
        },
        i * 0.045,
      );
    });

    requestAnimationFrame(() => {
      isPromoReady.value = true;

      requestAnimationFrame(() => {
        introTl.play(0);
      });
    });

    const scrollTl = gsap.timeline({
      scrollTrigger: {
        trigger: section,
        start: "top 85%",
        end: "bottom 20%",
        scrub: 1.1,
        invalidateOnRefresh: true,
      },
    });

    plateEls.forEach((plate, i) => {
      const p = layout[i];
      if (!p) return;

      scrollTl.to(
        plate,
        {
          "--parallax-x": `${p.sx}vw`,
          "--parallax-y": `${p.sy}vw`,
          "--parallax-r": `${p.sr}deg`,
          ease: "none",
        },
        0,
      );
    });
  }, section);
};

const handleResize = (): void => {
  if (resizeTimer) {
    window.clearTimeout(resizeTimer);
  }

  resizeTimer = window.setTimeout(() => {
    initHero();
  }, 180);
};

onMounted(async () => {
  await nextTick();
  await waitForPromoImages();

  initHero();

  window.addEventListener("resize", handleResize, { passive: true });
});

onBeforeUnmount(() => {
  window.removeEventListener("resize", handleResize);

  if (resizeTimer) {
    window.clearTimeout(resizeTimer);
  }

  if (mouseMoveFrame) {
    window.cancelAnimationFrame(mouseMoveFrame);
  }

  ctx?.revert();
});
</script>

<style scoped>
.promo-plates-layer {
  opacity: 0;
  visibility: hidden;
}

.is-promo-ready .promo-plates-layer {
  opacity: 1;
  visibility: visible;
}

.promo-plate {
  transform: translate(-50%, -50%)
    translate(var(--base-x), calc(var(--base-y) + var(--layout-offset-y)))
    translate(var(--intro-x), var(--intro-y))
    translate(var(--parallax-x), var(--parallax-y))
    translate(var(--tilt-move-x), var(--tilt-move-y))
    rotate(calc(var(--base-r) + var(--intro-r) + var(--parallax-r)))
    rotateX(var(--tilt-y)) rotateY(var(--tilt-x)) scale(var(--plate-scale));

  transform-style: preserve-3d;
  will-change: transform, opacity;
  contain: layout paint;
}

@media (max-width: 768px) {
  .promo-plate {
    transform: translate(-50%, -50%)
      translate(var(--base-x), calc(var(--base-y) + var(--layout-offset-y)))
      translate(var(--intro-x), var(--intro-y))
      translate(var(--parallax-x), var(--parallax-y))
      rotate(calc(var(--base-r) + var(--intro-r) + var(--parallax-r)))
      scale(var(--plate-scale));
  }
}
</style>
