type BeltImage = {
  src: string;
  w?: number;
  h?: number;
  alt?: string | null;
};

type BeltModelClient = {
  modelKey: string;
  href: string;
  firstName: string;
  images: BeltImage[];
};

type BeltItem = {
  href: string;
  src: string;
  alt: string;
  w?: number;
  h?: number;
  modelKey: string;
  name: string;
};

function randInt(maxExclusive: number): number {
  if (maxExclusive <= 0) return 0;

  if (typeof crypto !== "undefined" && "getRandomValues" in crypto) {
    const buf = new Uint32Array(1);
    crypto.getRandomValues(buf);
    return buf[0] % maxExclusive;
  }

  return Math.floor(Math.random() * maxExclusive);
}

function shuffleInPlace<T>(arr: T[]): T[] {
  for (let i = arr.length - 1; i > 0; i -= 1) {
    const j = randInt(i + 1);
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }
  return arr;
}

function rotate<T>(arr: T[], offset: number): T[] {
  const n = arr.length;
  if (n === 0) return [];
  const o = ((offset % n) + n) % n;
  if (o === 0) return arr.slice();
  return arr.slice(o).concat(arr.slice(0, o));
}

function uniqueModelsByKey(models: BeltModelClient[]): BeltModelClient[] {
  const map = new Map<string, BeltModelClient>();

  for (const m of models) {
    if (!m || !m.modelKey || !m.href || !m.firstName) continue;
    if (!Array.isArray(m.images) || m.images.length === 0) continue;
    if (map.has(m.modelKey)) continue;
    map.set(m.modelKey, m);
  }

  return Array.from(map.values());
}

function chooseImage(m: BeltModelClient): BeltImage | null {
  const imgs = (m.images ?? []).filter(
    (x) => x && typeof x.src === "string" && x.src.trim().length
  );
  if (!imgs.length) return null;
  return imgs[randInt(imgs.length)];
}

function buildBase(models: BeltModelClient[], targetCount: number): BeltItem[] {
  const pool = uniqueModelsByKey(models).slice();
  if (!pool.length) return [];

  shuffleInPlace(pool);

  const count = Math.min(pool.length, Math.max(2, targetCount));
  const picked = pool.slice(0, count);

  const base: BeltItem[] = [];
  for (const m of picked) {
    const img = chooseImage(m);
    if (!img) continue;

    base.push({
      href: m.href,
      src: img.src,
      alt: img.alt?.trim() || `Sedcard Foto von ${m.firstName}`,
      w: img.w,
      h: img.h,
      modelKey: m.modelKey,
      name: m.firstName,
    });
  }

  return base;
}

function getAnimationDurationSeconds(el: Element): number {
  const dur = getComputedStyle(el).animationDuration;
  const n = parseFloat(dur);
  if (!Number.isFinite(n) || n <= 0) return 0;
  return dur.includes("ms") ? n / 1000 : n;
}

function buildTrack(track: HTMLElement, items: BeltItem[]): void {
  const frag = document.createDocumentFragment();

  for (const it of items) {
    const a = document.createElement("a");
    a.className = "belt__item";
    a.href = it.href;
    a.setAttribute("aria-label", it.alt);
    a.dataset.model = it.modelKey;
    a.tabIndex = -1;

    const img = document.createElement("img");
    img.src = it.src;
    img.alt = it.alt;
    img.loading = "lazy";
    img.decoding = "async";

    if (typeof it.w === "number") img.width = it.w;
    if (typeof it.h === "number") img.height = it.h;

    a.appendChild(img);

    const credit = document.createElement("span");
    credit.className = "belt__credit";
    credit.textContent = it.name;
    a.appendChild(credit);

    frag.appendChild(a);
  }

  track.replaceChildren(frag);
}

function bindHoverPause(root: HTMLElement, track: HTMLElement): void {
  if (root.dataset.pmBeltHoverBound === "1") return;
  root.dataset.pmBeltHoverBound = "1";

  root.addEventListener("mouseover", (e) => {
    const t = e.target as Element | null;
    if (!t) return;
    if (t.closest(".belt__item")) track.style.animationPlayState = "paused";
  });

  root.addEventListener("mouseout", (e) => {
    const t = e.target as Element | null;
    if (!t) return;

    const fromItem = t.closest(".belt__item");
    if (!fromItem) return;

    const to = (e as MouseEvent).relatedTarget as Element | null;
    if (to && to.closest(".belt__item")) return;

    track.style.animationPlayState = "running";
  });
}

function initBelt(): void {
  const root = document.querySelector<HTMLElement>("[data-belt]");
  if (!root) return;

  const track = root.querySelector<HTMLElement>(".belt__track");
  if (!track) return;

  const dataEl = root.querySelector<HTMLScriptElement>("#belt-data");
  if (!dataEl) return;

  let models: BeltModelClient[] = [];
  try {
    models = JSON.parse(dataEl.textContent ?? "[]") as BeltModelClient[];
  } catch {
    return;
  }

  if (!Array.isArray(models) || models.length === 0) return;

  const unique = uniqueModelsByKey(models);
  if (unique.length < 2) return;

  bindHoverPause(root, track);

  const target = Math.min(
    unique.length,
    Math.max(24, Math.floor(window.innerWidth / 55) + 18)
  );

  const baseRaw = buildBase(unique, target);
  if (baseRaw.length < 2) return;

  const base = rotate(baseRaw, randInt(baseRaw.length));

  const loop = base.concat(base);
  buildTrack(track, loop);

  // CSS-Animation neu starten, damit Safari translate3d(-50%) auf der
  // neuen Track-Breite korrekt berechnet (WebKit-Bug bei max-content + %-Transform)
  track.style.animation = "none";
  void track.offsetHeight;
  track.style.animation = "";

  const durSec = getAnimationDurationSeconds(track);
  if (durSec > 0) {
    const offset = (randInt(1_000_000) / 1_000_000) * durSec;
    track.style.animationDelay = `${-offset}s`;
  }
}

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

const g = globalThis as unknown as { __pmBeltListenerAdded?: boolean };
if (!g.__pmBeltListenerAdded) {
  g.__pmBeltListenerAdded = true;
  document.addEventListener("astro:page-load", initBelt);
}