---
import { getCollection } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro';
import { heroes } from '../lib/hero';
import { buildOgImage, buildItemList, WEBSITE, ORGANIZATION } from '../lib/seo';

const HERO_SLUG = 'alt-tab';
const INITIAL_GRID = 7;
const BATCH_SIZE = 7;

const reviews = (await getCollection('reviews')).sort(
  (a, b) => b.data.date.valueOf() - a.data.date.valueOf()
);

const tiles = reviews.map((review) => ({
  href: `/reviews/${review.id}/`,
  id: review.id,
  name: review.data.name,
  subtitle: review.data.subtitle,
  hero: heroes[review.id],
}));

const heroIndex = tiles.findIndex((t) => t.href === `/reviews/${HERO_SLUG}/`);
const hero = heroIndex >= 0 ? tiles[heroIndex] : tiles[0];
const rest = tiles.filter((_, i) => i !== (heroIndex >= 0 ? heroIndex : 0));

const lastUpdated = reviews[0]?.data.date;

const ogImage = buildOgImage(hero?.id, 'reviews', `${hero?.name ?? 'Unique'} hero image`);
const itemList = buildItemList(
  tiles.slice(0, INITIAL_GRID).map((t) => ({ url: t.href, name: t.name })),
  'Latest reviews'
);
---

<BaseLayout
  title="Unique"
  description="A small log of delightful, unique things — products, apps, phenomena, oddities."
  subheader="A small log of delightful things."
  lastUpdated={lastUpdated}
  ogImage={ogImage}
  jsonLd={[WEBSITE, ORGANIZATION, itemList]}
>
  {hero && (
    <a href={hero.href} class="hero">
      {hero.hero?.kind === 'image' && (
        <img
          class="hero-media"
          src={hero.hero.src.src}
          width={hero.hero.src.width}
          height={hero.hero.src.height}
          fetchpriority="high"
          loading="eager"
          decoding="sync"
          alt={hero.name}
        />
      )}
      {hero.hero?.kind === 'video' && (
        <video
          class="hero-media"
          src={hero.hero.src}
          autoplay
          loop
          muted
          playsinline
          preload="metadata"
          aria-label={hero.name}
        />
      )}
      <div class="hero-text">
        <span class="hero-eyebrow">Today's pick</span>
        <h1 class="hero-name">{hero.name}</h1>
        {hero.subtitle && <p class="hero-subtitle">{hero.subtitle}</p>}
      </div>
    </a>
  )}

  <ul class="grid" data-initial={INITIAL_GRID} data-batch={BATCH_SIZE}>
    {
      rest.map((tile, i) => (
        <li class:list={[{ hidden: i >= INITIAL_GRID }]} data-index={i}>
          <a href={tile.href} class:list={['tile', { 'text-only': !tile.hero }]}>
            {tile.hero?.kind === 'image' && (
              <img
                class="media"
                src={tile.hero.src.src}
                width={tile.hero.src.width}
                height={tile.hero.src.height}
                loading="lazy"
                decoding="async"
                alt={tile.name}
              />
            )}
            {tile.hero?.kind === 'video' && (
              <video
                class="media"
                src={tile.hero.src}
                autoplay
                loop
                muted
                playsinline
                preload="metadata"
                aria-label={tile.name}
              />
            )}
            <div class="text">
              <span class="name">{tile.name}</span>
              <span class="subtitle">{tile.subtitle}</span>
            </div>
          </a>
        </li>
      ))
    }
  </ul>

  {rest.length > INITIAL_GRID && (
    <div class="more-wrap">
      <button id="load-more" class="load-more" type="button">
        Load more
      </button>
    </div>
  )}

  <script>
    const grid = document.querySelector<HTMLUListElement>('.grid');
    const button = document.querySelector<HTMLButtonElement>('#load-more');
    const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    function easeInOutCubic(t: number): number {
      return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
    }

    // A long, gentle programmatic scroll. Native smooth-scroll is too quick and
    // jumps to a fixed target; we want to drift continuously as cards appear.
    function softScrollBy(distance: number, duration: number): Promise<void> {
      if (prefersReducedMotion || distance <= 0) {
        window.scrollBy(0, distance);
        return Promise.resolve();
      }
      return new Promise((resolve) => {
        const startY = window.scrollY;
        const start = performance.now();
        function step(now: number) {
          const t = Math.min(1, (now - start) / duration);
          window.scrollTo(0, startY + distance * easeInOutCubic(t));
          if (t < 1) requestAnimationFrame(step);
          else resolve();
        }
        requestAnimationFrame(step);
      });
    }

    function wait(ms: number): Promise<void> {
      return new Promise((resolve) => setTimeout(resolve, ms));
    }

    if (grid && button) {
      const batch = Number(grid.dataset.batch ?? '7');
      let busy = false;

      button.addEventListener('click', async () => {
        if (busy) return;
        busy = true;
        button.disabled = true;

        const hidden = grid.querySelectorAll<HTMLLIElement>('li.hidden');
        const toReveal = Array.from(hidden).slice(0, batch);

        // Reveal one at a time, with the page drifting smoothly to keep new
        // cards in view as they slide in.
        const REVEAL_DURATION = 520;
        const GAP = 220;
        const SCROLL_AMOUNT = 110;
        const SCROLL_DURATION = REVEAL_DURATION + GAP;

        for (let i = 0; i < toReveal.length; i++) {
          const li = toReveal[i];
          li.classList.remove('hidden');
          li.classList.add('revealing');
          li.addEventListener(
            'animationend',
            () => li.classList.remove('revealing'),
            { once: true }
          );

          // Start a soft scroll alongside each card (except the first, so the
          // user sees the initial reveal in place).
          if (i > 0 && !prefersReducedMotion) {
            softScrollBy(SCROLL_AMOUNT, SCROLL_DURATION);
          }
          await wait(GAP);
        }

        // Final settle: ensure the button (or the last card) is comfortably
        // in view.
        if (!prefersReducedMotion) {
          const target = button.getBoundingClientRect();
          const overflow = target.bottom - window.innerHeight + 32;
          if (overflow > 0) await softScrollBy(overflow, 600);
        }

        if (grid.querySelectorAll('li.hidden').length === 0) {
          button.classList.add('fading');
          button.addEventListener('transitionend', () => button.remove(), { once: true });
        } else {
          button.disabled = false;
        }
        busy = false;
      });
    }
  </script>

  <style>
    .hero {
      display: block;
      position: relative;
      border-radius: 14px;
      overflow: hidden;
      border: 1px solid var(--rule);
      background: var(--bg);
      color: inherit;
      margin-bottom: 2.5rem;
      transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
    }
    .hero:hover {
      text-decoration: none;
      transform: translateY(-2px);
      border-color: color-mix(in srgb, var(--accent) 40%, var(--rule));
      box-shadow: 0 18px 48px -18px rgba(0, 0, 0, 0.28);
    }
    .hero:hover .hero-name { color: var(--accent); }

    .hero-media {
      display: block;
      width: 100%;
      height: auto;
      max-height: 62vh;
      object-fit: cover;
      background: var(--rule);
    }
    .hero-text {
      padding: 1.6rem 1.75rem 1.75rem;
      display: flex;
      flex-direction: column;
      gap: 0.4rem;
    }
    .hero-eyebrow {
      font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
      font-size: 0.78rem;
      letter-spacing: 0.08em;
      text-transform: uppercase;
      color: var(--accent);
      font-weight: 600;
    }
    .hero-name {
      margin: 0;
      font-size: clamp(1.6rem, 3vw, 2.4rem);
      letter-spacing: -0.02em;
      transition: color 0.18s ease;
    }
    .hero-subtitle {
      margin: 0;
      color: var(--muted);
      font-size: clamp(1rem, 1.4vw, 1.15rem);
      line-height: 1.45;
    }

    .grid {
      column-count: 3;
      column-gap: 1rem;
      list-style: none;
      margin: 0;
      padding: 0;
    }
    @media (max-width: 880px) {
      .grid { column-count: 2; }
    }
    @media (max-width: 520px) {
      .grid { column-count: 1; }
    }

    .grid > li {
      break-inside: avoid;
      display: block;
      margin: 0 0 1rem;
    }
    .grid > li.hidden { display: none; }
    .grid > li.revealing {
      animation: tile-in 520ms cubic-bezier(0.22, 1, 0.36, 1) both;
      will-change: transform, opacity;
    }
    @keyframes tile-in {
      from {
        opacity: 0;
        transform: translateY(22px);
      }
      to {
        opacity: 1;
        transform: translateY(0);
      }
    }
    @media (prefers-reduced-motion: reduce) {
      .grid > li.revealing { animation: none; }
    }

    .tile {
      display: block;
      border-radius: 8px;
      overflow: hidden;
      border: 1px solid var(--rule);
      background: var(--bg);
      color: inherit;
      transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
    }
    .tile:hover {
      text-decoration: none;
      transform: translateY(-2px);
      border-color: color-mix(in srgb, var(--accent) 40%, var(--rule));
      box-shadow: 0 8px 24px -10px rgba(0, 0, 0, 0.18);
    }
    .tile:hover .name { color: var(--accent); }

    .media {
      display: block;
      width: 100%;
      height: auto;
      background: var(--rule);
    }

    .text {
      padding: 0.85rem 1rem 1rem;
      display: flex;
      flex-direction: column;
      gap: 0.2rem;
    }
    .name {
      font-family: ui-sans-serif, system-ui, -apple-system, 'Helvetica Neue', sans-serif;
      font-weight: 700;
      font-size: 1rem;
      letter-spacing: -0.01em;
      color: var(--fg);
      transition: color 0.18s ease;
    }
    .subtitle {
      color: var(--muted);
      font-size: 0.92rem;
      line-height: 1.4;
    }

    .tile.text-only {
      background: linear-gradient(155deg, var(--rule), transparent);
    }
    .tile.text-only .text {
      padding: 1.5rem 1.25rem;
    }
    .tile.text-only .name {
      font-size: 1.25rem;
      margin-bottom: 0.3rem;
    }
    .tile.text-only .subtitle {
      font-size: 1rem;
    }

    .more-wrap {
      display: flex;
      justify-content: center;
      margin: 2rem 0 0.5rem;
    }
    .load-more {
      font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
      font-size: 0.95rem;
      font-weight: 600;
      letter-spacing: 0.01em;
      color: var(--fg);
      background: var(--bg);
      border: 1px solid var(--rule);
      border-radius: 999px;
      padding: 0.7rem 1.6rem;
      cursor: pointer;
      transition:
        border-color 0.18s ease,
        color 0.18s ease,
        transform 0.18s ease,
        opacity 0.4s ease;
    }
    .load-more:hover {
      color: var(--accent);
      border-color: color-mix(in srgb, var(--accent) 50%, var(--rule));
      transform: translateY(-1px);
    }
    .load-more.fading {
      opacity: 0;
      transform: translateY(4px);
      pointer-events: none;
    }

  </style>
</BaseLayout>
