(() => {
  "use strict";
  const projects = window.PROJECTS || [];
  const byId = id => document.getElementById(id);
  const viewer = byId("projectViewer");
  const frame = byId("viewerFrame");
  const loading = byId("viewerLoading");
  frame.setAttribute("scrolling", "yes");
  frame.setAttribute("tabindex", "0");
  const search = byId("projectSearch");
  const empty = byId("emptyResults");
  const cards = [...document.querySelectorAll(".project-card")];
  let activeFilter = "all";
  let deferredPrompt = null;
  let lastScroll = 0;
  let commandIndex = 0;
  let commandItems = projects.slice();

  const showToast = message => {
    const toast = byId("toast");
    toast.textContent = message;
    toast.classList.add("is-visible");
    clearTimeout(showToast.timer);
    showToast.timer = setTimeout(() => toast.classList.remove("is-visible"), 2600);
  };

  const getProject = id => projects.find(p => p.id === id);

  function launch(id) {
    window.APUReviewMusic?.startFromGesture?.();
    const project = getProject(id);
    if (!project) return;
    byId("viewerNumber").textContent = `P—${project.number}`;
    byId("viewerTitle").textContent = `${project.title} | ${project.subtitle}`;
    byId("viewerStandalone").href = `/projects/${project.id}/`;
    byId("viewerDownload").href = project.download;
    loading.classList.remove("is-hidden");
    frame.src = `/projects/${project.id}/`;
    try { frame.contentWindow?.scrollTo(0, 0); } catch (_) {}
    if (!viewer.open) viewer.showModal();
    document.body.classList.add("viewer-open");
    window.APUReviewMusic?.start();
    history.replaceState(null, "", `#open=${project.id}`);
  }

  function closeViewer() {
    if (viewer.open) viewer.close();
    frame.src = "about:blank";
    document.body.classList.remove("viewer-open");
    window.APUReviewMusic?.stop();
    if (location.hash.startsWith("#open=")) history.replaceState(null, "", location.pathname);
  }

  document.addEventListener("click", event => {
    const launcher = event.target.closest("[data-launch]");
    if (launcher) launch(launcher.dataset.launch);
  });
  byId("viewerClose").addEventListener("click", closeViewer);
  viewer.addEventListener("cancel", event => { event.preventDefault(); closeViewer(); });
  frame.addEventListener("load", () => loading.classList.add("is-hidden"));

  function applyFilters() {
    const q = search.value.trim().toLowerCase();
    let visible = 0;
    cards.forEach(card => {
      const matchesText = !q || `${card.dataset.title} ${card.dataset.category} ${card.textContent}`.toLowerCase().includes(q);
      const matchesFilter = activeFilter === "all" || card.dataset.category.toLowerCase().includes(activeFilter.toLowerCase()) || card.textContent.toLowerCase().includes(activeFilter.toLowerCase());
      card.hidden = !(matchesText && matchesFilter);
      if (!card.hidden) visible++;
    });
    empty.hidden = visible !== 0;
  }
  search.addEventListener("input", applyFilters);
  document.querySelectorAll("[data-filter]").forEach(button => {
    button.addEventListener("click", () => {
      document.querySelectorAll("[data-filter]").forEach(x => x.classList.remove("is-active"));
      button.classList.add("is-active");
      activeFilter = button.dataset.filter;
      applyFilters();
    });
  });

  // Pointer tilt, limited to fine-pointer devices.
  if (matchMedia("(pointer:fine)").matches) {
    cards.forEach(card => {
      card.addEventListener("mousemove", event => {
        const r = card.getBoundingClientRect();
        const x = ((event.clientX - r.left) / r.width - .5) * 7;
        const y = ((event.clientY - r.top) / r.height - .5) * -7;
        card.style.transform = `perspective(900px) rotateY(${x}deg) rotateX(${y}deg) translateZ(0)`;
      });
      card.addEventListener("mouseleave", () => card.style.transform = "");
    });
  }

  // Reveal.
  const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        entry.target.classList.add("is-visible");
        observer.unobserve(entry.target);
      }
    });
  }, {threshold:.08});
  cards.forEach(card => { card.classList.add("reveal"); observer.observe(card); });
  document.querySelectorAll(".flow-node,.belief-box,.download-grid a").forEach(el => {
    el.classList.add("reveal"); observer.observe(el);
  });

  // Header and progress.
  addEventListener("scroll", () => {
    const total = document.documentElement.scrollHeight - innerHeight;
    byId("scrollProgress").style.transform = `scaleX(${total ? scrollY / total : 0})`;
    const header = byId("siteHeader");
    header.classList.toggle("is-hidden", scrollY > lastScroll && scrollY > 180);
    lastScroll = scrollY;
  }, {passive:true});

  // Cursor.
  const dot = byId("cursorDot");
  addEventListener("pointermove", event => {
    dot.style.left = `${event.clientX}px`;
    dot.style.top = `${event.clientY}px`;
  }, {passive:true});
  document.querySelectorAll("a,button,input").forEach(el => {
    el.addEventListener("mouseenter", () => dot.classList.add("is-large"));
    el.addEventListener("mouseleave", () => dot.classList.remove("is-large"));
  });

  // Mobile menu.
  byId("menuButton").addEventListener("click", () => {
    const menu = byId("mobileMenu");
    menu.hidden = !menu.hidden;
    byId("menuButton").setAttribute("aria-expanded", String(!menu.hidden));
  });
  byId("mobileMenu").querySelectorAll("a").forEach(a => a.addEventListener("click", () => {
    byId("mobileMenu").hidden = true;
    byId("menuButton").setAttribute("aria-expanded", "false");
  }));

  // Random project.
  byId("randomProject").addEventListener("click", () => {
    launch(projects[Math.floor(Math.random() * projects.length)].id);
  });

  // Command palette.
  const command = byId("commandPalette");
  const commandInput = byId("commandInput");
  const commandResults = byId("commandResults");
  function renderCommand() {
    const q = commandInput.value.toLowerCase().trim();
    commandItems = projects.filter(p => !q || `${p.title} ${p.subtitle} ${p.category} ${p.tags.join(" ")}`.toLowerCase().includes(q));
    commandIndex = Math.min(commandIndex, Math.max(0, commandItems.length - 1));
    commandResults.innerHTML = commandItems.map((p, i) => `
      <button type="button" class="command-result ${i === commandIndex ? "is-selected" : ""}" data-command="${p.id}">
        <b>${p.number} — ${p.title}</b><span>${p.category}</span>
      </button>`).join("") || "<p>No project found.</p>";
  }
  function openCommand() {
    renderCommand();
    command.showModal();
    setTimeout(() => commandInput.focus(), 40);
  }
  byId("searchButton").addEventListener("click", openCommand);
  commandInput.addEventListener("input", () => { commandIndex = 0; renderCommand(); });
  commandResults.addEventListener("click", event => {
    const button = event.target.closest("[data-command]");
    if (!button) return;
    command.close(); launch(button.dataset.command);
  });
  command.addEventListener("keydown", event => {
    if (event.key === "ArrowDown") { event.preventDefault(); commandIndex = Math.min(commandIndex + 1, commandItems.length - 1); renderCommand(); }
    if (event.key === "ArrowUp") { event.preventDefault(); commandIndex = Math.max(commandIndex - 1, 0); renderCommand(); }
    if (event.key === "Enter" && document.activeElement === commandInput && commandItems[commandIndex]) {
      event.preventDefault(); command.close(); launch(commandItems[commandIndex].id);
    }
  });
  addEventListener("keydown", event => {
    if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k") {
      event.preventDefault(); openCommand();
    }
  });

  // PWA install prompt.
  addEventListener("beforeinstallprompt", event => {
    event.preventDefault();
    deferredPrompt = event;
    byId("installButton").hidden = false;
  });
  byId("installButton").addEventListener("click", async () => {
    if (!deferredPrompt) return showToast("Use the browser menu to install this app.");
    deferredPrompt.prompt();
    await deferredPrompt.userChoice;
    deferredPrompt = null;
    byId("installButton").hidden = true;
  });
  addEventListener("appinstalled", () => showToast("Project Universe installed successfully."));

  // Open requested project from URL hash.
  const match = location.hash.match(/^#open=([a-z0-9-]+)$/);
  if (match && getProject(match[1])) launch(match[1]);
})();
