---
import { Image } from "astro:assets";
import type { ImageMetadata } from "astro";
import PhotosSubNav from "@/components/PhotosSubNav.astro";
import BaseLayout from "@/layouts/BaseLayout.astro";
import type { PhotoSidecar } from "@/lib/collections";

type TagPhoto = {
  sidecar: PhotoSidecar;
  image: ImageMetadata;
  collectionSlug: string;
};

export async function getStaticPaths() {
  const sidecars = import.meta.glob<PhotoSidecar>(
    "/src/content/photos/collections/**/*.json",
    { eager: true },
  );
  const imageModules = import.meta.glob<{ default: ImageMetadata }>(
    "/src/content/photos/collections/**/*.jpg",
    { eager: true },
  );

  function toSlug(tag: string): string {
    return tag
      .toLowerCase()
      .replace(/\s+/g, "-")
      .replace(/[^a-z0-9-]/g, "");
  }

  const tagMap = new Map<string, { label: string; photos: TagPhoto[] }>();

  for (const [jsonPath, sidecar] of Object.entries(sidecars)) {
    const imgPath = jsonPath.replace(/\.json$/, ".jpg");
    const image = imageModules[imgPath]?.default;
    if (!image) continue;

    const collectionSlug = jsonPath
      .replace("/src/content/photos/collections/", "")
      .replace(/\/img\/[^/]+\.json$/, "");

    for (const tag of sidecar.tags) {
      const slug = toSlug(tag);
      if (!tagMap.has(slug)) tagMap.set(slug, { label: tag, photos: [] });
      tagMap.get(slug)?.photos.push({ sidecar, image, collectionSlug });
    }
  }

  return [...tagMap.entries()].map(([slug, { label, photos }]) => ({
    params: { slug },
    props: {
      label,
      photos: photos.sort(
        (a, b) =>
          new Date(b.sidecar.date).getTime() -
          new Date(a.sidecar.date).getTime(),
      ),
    },
  }));
}

const { label, photos } = Astro.props as { label: string; photos: TagPhoto[] };
const BATCH_SIZE = 15;
---

<BaseLayout
  title={`Tag: ${label}`}
  description={`Photos tagged with "${label}" by Adrian Altner.`}
  navDesktopHidden={true}
  footerDark={true}
>
  <div class="page">
    <main class="main">
      <PhotosSubNav />

      <header class="tag-header">
        <h1 class="tag-title">{label}</h1>
        <p class="tag-count">
          {photos.length}
          {photos.length === 1 ? "photo" : "photos"}
        </p>
      </header>

      <div class="photo-grid" id="photo-grid">
        {
          photos.map((photo, i) => {
            const ar = photo.image.width / photo.image.height;
            return (
              <div
                class="photo-item"
                data-ar={ar}
                data-batch={Math.floor(i / BATCH_SIZE)}
                data-hidden={i >= BATCH_SIZE ? "true" : undefined}
              >
                <a
                  href={`/photos/collections/${photo.collectionSlug}/${photo.sidecar.id}`}
                  class="photo-link"
                >
                  <Image
                    src={photo.image}
                    alt={photo.sidecar.alt}
                    width={800}
                    height={Math.round(800 / ar)}
                    loading="lazy"
                    class="photo-img"
                  />
                </a>
              </div>
            );
          })
        }
      </div>

      <div id="sentinel" aria-hidden="true"></div>
      <div id="load-status" class="load-status" aria-live="polite"></div>
    </main>
  </div>
</BaseLayout>

<script>
  import justifiedLayout from "justified-layout";

  const grid = document.getElementById("photo-grid");
  if (grid) {
    const BATCH_SIZE = 15;
    const TARGET_ROW_HEIGHT = 280;
    const BOX_SPACING = 8;

    const allItems = Array.from(
      grid.querySelectorAll<HTMLElement>(".photo-item"),
    );
    const sentinel = document.getElementById("sentinel");
    const status = document.getElementById("load-status");

    let currentBatch = 0;
    const totalBatches =
      Math.ceil(
        allItems.filter((el) => el.hasAttribute("data-hidden")).length /
          BATCH_SIZE,
      ) + 1;

    function applyLayout() {
      if (window.innerWidth <= 640) {
        grid!.style.height = "";
        grid!.style.position = "";
        for (const item of allItems) {
          if (!item.hasAttribute("data-hidden")) item.style.cssText = "";
        }
        return;
      }

      const visibleItems = allItems.filter(
        (el) => !el.hasAttribute("data-hidden"),
      );
      const ratios = visibleItems.map((el) => Number(el.dataset.ar));

      const result = justifiedLayout(ratios, {
        containerWidth: grid!.offsetWidth,
        targetRowHeight: TARGET_ROW_HEIGHT,
        boxSpacing: BOX_SPACING,
        containerPadding: 0,
      });

      grid!.style.position = "relative";
      grid!.style.height = `${result.containerHeight}px`;

      for (let i = 0; i < visibleItems.length; i++) {
        const box = result.boxes[i];
        const item = visibleItems[i];
        if (!box || !item) continue;
        item.style.position = "absolute";
        item.style.top = `${box.top}px`;
        item.style.left = `${box.left}px`;
        item.style.width = `${box.width}px`;
        item.style.height = `${box.height}px`;
      }
    }

    function revealNextBatch() {
      currentBatch++;
      const toReveal = allItems.filter(
        (el) =>
          el.hasAttribute("data-hidden") &&
          Number(el.dataset.batch) === currentBatch,
      );
      if (toReveal.length === 0) {
        sentinel?.remove();
        return;
      }
      if (status) status.textContent = "Loading more photos…";
      for (const el of toReveal) {
        el.removeAttribute("data-hidden");
        el.classList.add("is-visible");
      }
      applyLayout();
      if (currentBatch >= totalBatches) sentinel?.remove();
      if (status) status.textContent = "";
    }

    let resizeTimer: ReturnType<typeof setTimeout>;
    window.addEventListener("resize", () => {
      clearTimeout(resizeTimer);
      resizeTimer = setTimeout(applyLayout, 150);
    });

    applyLayout();

    if (sentinel) {
      const observer = new IntersectionObserver(
        (entries) => {
          if (entries[0]?.isIntersecting) revealNextBatch();
        },
        { rootMargin: "400px" },
      );
      observer.observe(sentinel);
    }
  }
</script>

<style>
  .page {
    display: grid;
    grid-template-rows: auto 1fr auto;
    min-height: 100dvh;
    background: #111;
    color: #f5f5f3;
  }

  .main {
    max-width: var(--width-wide);
    margin: 0 auto;
    padding: 1.5rem;
    width: 100%;
  }

  .tag-header {
    margin-bottom: 1.5rem;
  }

  .tag-title {
    font-size: 1.5rem;
    font-weight: 500;
    margin-bottom: 0.25rem;
  }

  .tag-count {
    font-size: 0.85rem;
    color: #888;
    margin: 0;
  }

  .photo-grid {
    position: relative;
  }

  .photo-item {
    border-radius: 2px;
    overflow: hidden;
  }

  .photo-item[data-hidden] {
    display: none;
  }

  .photo-item.is-visible {
    animation: fadeIn 0.4s ease forwards;
  }

  @keyframes fadeIn {
    from {
      opacity: 0;
      transform: translateY(8px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

  .photo-link {
    display: block;
    width: 100%;
    height: 100%;
    overflow: hidden;
  }

  .photo-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.4s ease;
  }

  .photo-link:hover .photo-img {
    transform: scale(1.03);
  }

  #sentinel {
    height: 1px;
    margin-top: 2rem;
  }

  .load-status {
    text-align: center;
    padding: 1rem;
    font-size: 0.85rem;
    color: #666;
    min-height: 2rem;
  }

  .page :global(.footer a) {
    color: #f5f5f3;
  }

  @media (max-width: 640px) {
    .main {
      padding: 0.25rem 0.75rem;
    }

    .photo-grid {
      display: flex;
      flex-direction: column;
      gap: 0.5rem;
      height: auto !important;
      position: static !important;
    }

    .photo-item {
      position: static !important;
      width: 100% !important;
      height: auto !important;
    }

    .photo-img {
      height: auto;
      object-fit: initial;
    }
  }
</style>
