"use client";

import AOS from "aos";

/**
 * Isotope + images shift page height after first paint. If `AOS.refresh()` runs
 * only once on `layoutComplete`, offsets for content below the grid are often
 * wrong (elements stay invisible until something triggers another refresh, e.g. filter).
 * Run refresh after paint and on short delays so layout has settled.
 */
export function refreshAosAfterMasonryLayout() {
  const refresh = () => {
    AOS.refresh();
  };

  requestAnimationFrame(() => {
    refresh();
    requestAnimationFrame(() => {
      refresh();
    });
  });

  window.setTimeout(refresh, 100);
  window.setTimeout(refresh, 350);
  window.setTimeout(refresh, 700);
}

/**
 * When masonry height changes (images, Isotope layout), re-measure AOS element positions.
 */
type IsotopeLayoutable = { layout: () => void };

/**
 * Isotope masonry often measures before `next/image` `<img>` nodes have final
 * dimensions (client navigation, lazy decode). Relayout when images load and
 * on deferred ticks / grid resize so items do not stack until a full reload.
 */
export function bindMasonryGridRelayout(
  grid: HTMLElement,
  getIso: () => IsotopeLayoutable | null | undefined,
  isCancelled: () => boolean,
): () => void {
  let debounceId: number | undefined;

  const relayout = () => {
    if (isCancelled()) return;
    getIso()?.layout();
  };

  const debouncedRelayout = () => {
    if (debounceId !== undefined) window.clearTimeout(debounceId);
    debounceId = window.setTimeout(() => {
      debounceId = undefined;
      relayout();
    }, 40);
  };

  const imgs = Array.from(grid.querySelectorAll("img"));
  const onImgLoad = () => debouncedRelayout();
  for (const img of imgs) {
    img.addEventListener("load", onImgLoad);
  }

  queueMicrotask(debouncedRelayout);
  requestAnimationFrame(debouncedRelayout);
  requestAnimationFrame(() => {
    requestAnimationFrame(debouncedRelayout);
  });

  const deferredTimeouts = [0, 120, 320, 650].map((ms) =>
    window.setTimeout(debouncedRelayout, ms),
  );

  const ro = new ResizeObserver(() => debouncedRelayout());
  ro.observe(grid);

  return () => {
    for (const img of imgs) {
      img.removeEventListener("load", onImgLoad);
    }
    ro.disconnect();
    if (debounceId !== undefined) window.clearTimeout(debounceId);
    for (const id of deferredTimeouts) {
      window.clearTimeout(id);
    }
  };
}

export function attachDebouncedAosRefreshOnResize(
  el: HTMLElement,
  isCancelled: () => boolean,
): () => void {
  let t: number | undefined;
  const ro = new ResizeObserver(() => {
    if (isCancelled()) return;
    if (t !== undefined) window.clearTimeout(t);
    t = window.setTimeout(() => {
      if (!isCancelled()) AOS.refresh();
    }, 100);
  });
  ro.observe(el);
  return () => {
    if (t !== undefined) window.clearTimeout(t);
    ro.disconnect();
  };
}
