---
import BaseLayout from "../../layouts/BaseLayout.astro";
import { SITE, authors } from "../../lib/blog-data.js";
import IndexRow from "../../components/IndexRow.astro";
import { getProjects, getCategories } from "../../lib/projects.js";

const author = authors[0];

// Both come from cja_projects at build time. Filters are derived from what the
// projects actually carry, so a category can never render as an empty chip —
// tag a project "Legal" in the database and the filter appears on its own.
const projects = await getProjects();
const categories = await getCategories();
---

<BaseLayout
  title={`Projects — ${SITE.name}`}
  description="Autonomous software, AI agents, and intelligent business systems currently in development or production."
  canonical="/projects"
>
  <section class="mx-auto w-full max-w-6xl px-5 pt-20 pb-10 md:pt-28">
    <p class="font-mono text-xs tracking-[0.18em] text-muted-foreground uppercase">
      {author.jobTitle}
    </p>
    <h1
      class="mt-6 max-w-[20ch] font-serif text-[clamp(2rem,5vw,3.1rem)] leading-tight tracking-tight text-balance"
    >
      Projects
    </h1>
    <p class="mt-6 max-w-[58ch] text-base leading-relaxed text-muted-foreground">
      Autonomous software, AI agents, and intelligent business systems — some running in
      production for years, some still being built.
    </p>
  </section>

  <section class="mx-auto w-full max-w-6xl px-5 pb-24">
    <!-- Filters are links, not buttons: each is a real URL that can be shared,
         bookmarked, and opened in a new tab. JS upgrades them to filter in
         place; without it they still work as anchors. -->
    <nav class="ca-filters" aria-label="Filter projects">
      <a href="#all" class="ca-filter" data-filter="all" aria-current="true">
        All
        <span class="ca-filter-count">{projects.length}</span>
      </a>
      {
        categories.map((c) => (
          <a href={`#${c.slug}`} class="ca-filter" data-filter={c.slug}>
            {c.name}
            <span class="ca-filter-count">{c.count}</span>
          </a>
        ))
      }
    </nav>

    <div class="mt-8 flex flex-col" data-project-list>
      {
        projects.map((project, i) => (
          <div
            data-project
            data-categories={(project.categories ?? [])
              .map((c) => c.toLowerCase())
              .join(" ")}
            style={`view-transition-name: proj-${project.slug}`}
          >
            <IndexRow
              href={`/projects/${project.slug}`}
              title={project.title}
              meta={project.period}
              summary={project.summary}
              tags={project.categories ?? project.tags}
              first={i === 0}
            />
          </div>
        ))
      }
    </div>

    <p class="ca-filter-empty" data-empty hidden>
      Nothing in that category yet.
      <a href="#all" class="ca-quiet" data-filter="all">Show everything</a>
    </p>
  </section>
</BaseLayout>

<script>
  // Plain DOM filtering rather than a React island. Every project is already in
  // the server-rendered HTML — good for crawlers and instant on first paint —
  // so moving the list into a client component would take that away to solve a
  // problem that is a few lines of classList work.
  //
  // The animation uses the View Transitions API. Each row carries a stable
  // view-transition-name, so the browser tweens the rows that stay put as well
  // as the ones leaving and arriving. Doing this by hand would mean a FLIP
  // implementation measuring every row before and after; the platform already
  // does it, and does it better.
  const list = document.querySelector("[data-project-list]");
  const filters = [...document.querySelectorAll("[data-filter]")];
  const items = [...document.querySelectorAll("[data-project]")];
  const empty = document.querySelector("[data-empty]");
  const root = document.documentElement;

  const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  const canTransition =
    typeof document.startViewTransition === "function" && !reduce;

  const mutate = (active) => {
    let shown = 0;

    for (const item of items) {
      const cats = (item.dataset.categories || "").split(" ").filter(Boolean);
      const match = active === "all" || cats.includes(active);
      item.hidden = !match;
      if (match) shown++;
    }

    // The top hairline follows the first VISIBLE row, not the first row —
    // otherwise filtering leaves the list with no rule or two.
    for (const item of items) {
      item.querySelector(".ca-entry")?.classList.remove("border-t", "border-border");
    }
    items
      .find((i) => !i.hidden)
      ?.querySelector(".ca-entry")
      ?.classList.add("border-t", "border-border");

    for (const f of filters) {
      if (f.dataset.filter === active) f.setAttribute("aria-current", "true");
      else f.removeAttribute("aria-current");
    }

    if (empty) empty.hidden = shown > 0;
  };

  const apply = (raw) => {
    const active = (raw || "all").replace(/^#/, "").toLowerCase();

    if (!canTransition) {
      mutate(active);
      return;
    }

    // Scope the transition CSS to this attribute so it cannot collide with the
    // theme toggle, which drives its own ::view-transition rules.
    root.setAttribute("data-ca-filtering", "");
    const vt = document.startViewTransition(() => mutate(active));
    vt.finished.finally(() => root.removeAttribute("data-ca-filtering"));
  };

  for (const f of filters) {
    f.addEventListener("click", (e) => {
      e.preventDefault();
      const next = f.dataset.filter || "all";
      history.replaceState(null, "", next === "all" ? location.pathname : `#${next}`);
      apply(next);
    });
  }

  window.addEventListener("hashchange", () => apply(location.hash));
  mutate((location.hash || "all").replace(/^#/, "").toLowerCase());
</script>
