const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");

function getScrollBehavior() {
  return reducedMotion.matches ? "auto" : "smooth";
}

let activeFilter = null;
let activeSort = { col: "stars", order: "desc" };
const searchInput = document.querySelector(".search");
const filterBar = document.querySelector(".filter-bar");
const filterValue = document.querySelector(".filter-value");
const filterClear = document.querySelector(".filter-clear");
const noResults = document.querySelector(".no-results");
const rows = document.querySelectorAll(".table tbody tr.row");
const tags = document.querySelectorAll(".tag");
const tbody = document.querySelector(".table tbody");

function initRevealSections() {
  const sections = document.querySelectorAll("[data-reveal]");
  if (!sections.length) return;

  if (!("IntersectionObserver" in window)) {
    sections.forEach(function (section) {
      section.classList.add("is-visible");
    });
    return;
  }

  const observer = new IntersectionObserver(
    function (entries) {
      entries.forEach(function (entry) {
        if (!entry.isIntersecting) return;
        entry.target.classList.add("is-visible");
        observer.unobserve(entry.target);
      });
    },
    {
      threshold: 0.12,
      rootMargin: "0px 0px -8% 0px",
    },
  );

  sections.forEach(function (section, index) {
    section.classList.add("will-reveal");
    section.style.transitionDelay = Math.min(index * 70, 180) + "ms";
    observer.observe(section);
  });
}

initRevealSections();

// Smooth scroll without hash in URL
document.querySelectorAll("[data-scroll-to]").forEach(function (link) {
  link.addEventListener("click", function (e) {
    const el = document.getElementById(link.dataset.scrollTo);
    if (!el) return;
    e.preventDefault();
    el.scrollIntoView({ behavior: getScrollBehavior() });
  });
});

// Land at #library-index without leaving the hash in the URL
if (window.location.hash === "#library-index") {
  const target = document.getElementById("library-index");
  if (target) {
    target.scrollIntoView();
  }
  history.replaceState(
    null,
    "",
    window.location.pathname + window.location.search,
  );
}

// Pause hero animations when scrolled out of view
(function () {
  const hero = document.querySelector(".hero");
  if (!hero || !("IntersectionObserver" in window)) return;
  const observer = new IntersectionObserver(function (entries) {
    hero.classList.toggle("offscreen", !entries[0].isIntersecting);
  });
  observer.observe(hero);
})();

function relativeTime(isoStr) {
  const date = new Date(isoStr);
  const now = new Date();
  const diffMs = now - date;
  const diffHours = Math.floor(diffMs / 3600000);
  const diffDays = Math.floor(diffMs / 86400000);
  if (diffHours < 1) return "just now";
  if (diffHours < 24)
    return diffHours === 1 ? "1 hour ago" : diffHours + " hours ago";
  if (diffDays === 1) return "yesterday";
  if (diffDays < 30) return diffDays + " days ago";
  const diffMonths = Math.floor(diffDays / 30);
  if (diffMonths < 12)
    return diffMonths === 1 ? "1 month ago" : diffMonths + " months ago";
  const diffYears = Math.floor(diffDays / 365);
  return diffYears === 1 ? "1 year ago" : diffYears + " years ago";
}

document.querySelectorAll(".col-commit[data-commit]").forEach(function (td) {
  const time = td.querySelector("time");
  if (time) time.textContent = relativeTime(td.dataset.commit);
});

document
  .querySelectorAll(".expand-commit time[datetime]")
  .forEach(function (time) {
    time.textContent = relativeTime(time.getAttribute("datetime"));
  });

rows.forEach(function (row, i) {
  row._origIndex = i;
  let next = row.nextElementSibling;
  if (next && next.classList.contains("desc-row")) {
    row._descRow = next;
    next = next.nextElementSibling;
  }
  row._expandRow = next;
});

function collapseAll() {
  if (!tbody) return;
  const openRows = tbody.querySelectorAll("tr.row.open");
  openRows.forEach(function (row) {
    row.classList.remove("open");
    row.setAttribute("aria-expanded", "false");
  });
}

function applyFilters() {
  const query = searchInput ? searchInput.value.toLowerCase().trim() : "";
  const descRowsVisible = !isIndexDocument || activeFilter !== null;
  let visibleCount = 0;

  collapseAll();

  rows.forEach(function (row) {
    let show = true;

    if (activeFilter) {
      const rowTags = row.dataset.tags;
      show = rowTags ? rowTags.split("||").includes(activeFilter) : false;
    }

    if (show && query) {
      if (!row._searchText) {
        let text = row.textContent.toLowerCase();
        if (row._descRow) {
          text += " " + row._descRow.textContent.toLowerCase();
        }
        if (row._expandRow) {
          text += " " + row._expandRow.textContent.toLowerCase();
        }
        row._searchText = text;
      }
      show = row._searchText.includes(query);
    }

    if (row.hidden !== !show) row.hidden = !show;
    if (row._descRow) {
      const descHidden = !show || !descRowsVisible;
      if (row._descRow.hidden !== descHidden) {
        row._descRow.hidden = descHidden;
      }
    }

    if (show) {
      visibleCount++;
      const numCell = row.cells[0];
      if (numCell.textContent !== String(visibleCount)) {
        numCell.textContent = String(visibleCount);
      }
    }
  });

  if (noResults) noResults.hidden = visibleCount > 0;

  tags.forEach(function (tag) {
    tag.classList.toggle("active", activeFilter === tag.dataset.value);
  });

  if (filterBar) {
    if (activeFilter) {
      filterBar.classList.add("visible");
      if (filterValue) filterValue.textContent = activeFilter;
    } else {
      filterBar.classList.remove("visible");
    }
  }

  updateURL();
}

const filterUrlsScript = document.getElementById("filter-urls");
const filterToUrl = filterUrlsScript
  ? JSON.parse(filterUrlsScript.textContent)
  : {};

const isIndexDocument =
  location.pathname === "/" || location.pathname === "/index.html";

const urlToFilter = {};
Object.keys(filterToUrl).forEach(function (k) {
  urlToFilter[filterToUrl[k]] = k;
});

function buildQueryString() {
  const params = new URLSearchParams();
  const query = searchInput ? searchInput.value.trim() : "";
  if (query) params.set("q", query);
  if (activeSort.col !== "stars" || activeSort.order !== "desc") {
    params.set("sort", activeSort.col);
    params.set("order", activeSort.order);
  }
  const qs = params.toString();
  return qs ? "?" + qs : "";
}

function updateURL() {
  if (!isIndexDocument) return;
  const path =
    activeFilter && filterToUrl[activeFilter] ? filterToUrl[activeFilter] : "/";
  history.replaceState(null, "", path + buildQueryString());
}

function getSortValue(row, col) {
  if (col === "name") {
    return row.querySelector(".col-name a").textContent.trim().toLowerCase();
  }
  if (col === "stars") {
    const text = row
      .querySelector(".col-stars")
      .textContent.trim()
      .replace(/,/g, "");
    const num = parseInt(text, 10);
    return isNaN(num) ? -1 : num;
  }
  if (col === "commit-time") {
    const attr = row.querySelector(".col-commit").getAttribute("data-commit");
    return attr ? new Date(attr).getTime() : 0;
  }
  return 0;
}

function sortRows() {
  if (!tbody) return;

  const arr = Array.prototype.slice.call(rows);
  const col = activeSort.col;
  const order = activeSort.order;

  // Cache sort values once to avoid DOM queries per comparison
  arr.forEach(function (row) {
    row._sortVal = getSortValue(row, col);
  });

  arr.sort(function (a, b) {
    const aVal = a._sortVal;
    const bVal = b._sortVal;
    if (col === "name") {
      const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
      if (cmp === 0) return a._origIndex - b._origIndex;
      return order === "desc" ? -cmp : cmp;
    }
    if (aVal <= 0 && bVal <= 0) return a._origIndex - b._origIndex;
    if (aVal <= 0) return 1;
    if (bVal <= 0) return -1;
    const cmp = aVal - bVal;
    if (cmp === 0) return a._origIndex - b._origIndex;
    return order === "desc" ? -cmp : cmp;
  });

  const frag = document.createDocumentFragment();
  arr.forEach(function (row) {
    frag.appendChild(row);
    if (row._descRow) frag.appendChild(row._descRow);
    if (row._expandRow) frag.appendChild(row._expandRow);
  });
  tbody.appendChild(frag);
  applyFilters();
}

const sortHeaders = document.querySelectorAll("th[data-sort]");

function updateSortIndicators() {
  sortHeaders.forEach(function (th) {
    th.classList.remove("sort-asc", "sort-desc");
    if (th.dataset.sort === activeSort.col) {
      th.classList.add("sort-" + activeSort.order);
      th.setAttribute(
        "aria-sort",
        activeSort.order === "asc" ? "ascending" : "descending",
      );
    } else {
      th.removeAttribute("aria-sort");
    }
  });
}

// Expand/collapse: event delegation on tbody
if (tbody) {
  tbody.addEventListener("click", function (e) {
    // Don't toggle if clicking a link or tag button
    if (e.target.closest("a") || e.target.closest(".tag")) return;

    let row = e.target.closest("tr.row");
    if (!row) {
      const descRow = e.target.closest("tr.desc-row");
      if (descRow) row = descRow.previousElementSibling;
    }
    if (!row) return;

    const isOpen = row.classList.contains("open");
    if (isOpen) {
      row.classList.remove("open");
      row.setAttribute("aria-expanded", "false");
    } else {
      row.classList.add("open");
      row.setAttribute("aria-expanded", "true");
    }
  });

  // Keyboard: Enter or Space on focused .row toggles expand
  tbody.addEventListener("keydown", function (e) {
    if (e.key !== "Enter" && e.key !== " ") return;
    const row = e.target.closest("tr.row");
    if (!row) return;
    e.preventDefault();
    row.click();
  });
}

tags.forEach(function (tag) {
  tag.addEventListener("click", function (e) {
    e.preventDefault();
    const value = tag.dataset.value;
    const url = tag.dataset.url;
    if (isIndexDocument) {
      activeFilter = activeFilter === value ? null : value;
      if (activeFilter && url) {
        history.pushState(null, "", url + buildQueryString());
      } else {
        history.pushState(null, "", "/" + buildQueryString());
      }
      applyFilters();
    } else if (url) {
      window.location.href = url + "#library-index";
    }
  });
});

if (filterClear) {
  filterClear.addEventListener("click", function () {
    if (!isIndexDocument) {
      window.location.href = "/#library-index";
      return;
    }
    activeFilter = null;
    applyFilters();
  });
}

const noResultsClear = document.querySelector(".no-results-clear");
if (noResultsClear) {
  noResultsClear.addEventListener("click", function () {
    if (!isIndexDocument) {
      window.location.href = "/";
      return;
    }
    if (searchInput) searchInput.value = "";
    activeFilter = null;
    applyFilters();
  });
}

sortHeaders.forEach(function (th) {
  th.addEventListener("click", function () {
    const col = th.dataset.sort;
    const defaultOrder = col === "name" ? "asc" : "desc";
    const altOrder = defaultOrder === "asc" ? "desc" : "asc";
    if (activeSort.col === col) {
      if (activeSort.order === defaultOrder)
        activeSort = { col: col, order: altOrder };
      else activeSort = { col: "stars", order: "desc" };
    } else {
      activeSort = { col: col, order: defaultOrder };
    }
    sortRows();
    updateSortIndicators();
  });
});

if (searchInput) {
  let searchTimer;
  searchInput.addEventListener("input", function () {
    clearTimeout(searchTimer);
    searchTimer = setTimeout(applyFilters, 150);
  });

  document.addEventListener("keydown", function (e) {
    if (
      e.key === "/" &&
      !["INPUT", "TEXTAREA", "SELECT"].includes(
        document.activeElement.tagName,
      ) &&
      !e.ctrlKey &&
      !e.metaKey
    ) {
      e.preventDefault();
      searchInput.focus();
    }
    if (e.key === "Escape" && document.activeElement === searchInput) {
      searchInput.value = "";
      activeFilter = null;
      applyFilters();
      searchInput.blur();
    }
  });
}

const backToTop = document.querySelector(".back-to-top");
const resultsSection = document.querySelector("#library-index");
const tableWrap = document.querySelector(".table-wrap");
const stickyHeaderCell = backToTop ? backToTop.closest("th") : null;

function updateBackToTopVisibility() {
  if (!backToTop || !tableWrap || !stickyHeaderCell) return;

  const tableRect = tableWrap.getBoundingClientRect();
  const headRect = stickyHeaderCell.getBoundingClientRect();
  const hasPassedHeader = tableRect.top <= 0 && headRect.bottom > 0;

  backToTop.classList.toggle("visible", hasPassedHeader);
}

if (backToTop) {
  let scrollTicking = false;
  window.addEventListener("scroll", function () {
    if (!scrollTicking) {
      requestAnimationFrame(function () {
        updateBackToTopVisibility();
        scrollTicking = false;
      });
      scrollTicking = true;
    }
  });

  window.addEventListener("resize", updateBackToTopVisibility);

  backToTop.addEventListener("click", function () {
    const target = searchInput || resultsSection;
    if (!target) return;
    target.scrollIntoView({ behavior: getScrollBehavior(), block: "center" });
    if (searchInput) searchInput.focus();
  });

  updateBackToTopVisibility();
}

(function () {
  const params = new URLSearchParams(location.search);
  const q = params.get("q");
  const sort = params.get("sort");
  const order = params.get("order");
  if (q && searchInput) searchInput.value = q;
  if (
    (sort === "name" || sort === "stars" || sort === "commit-time") &&
    (order === "desc" || order === "asc")
  ) {
    activeSort = { col: sort, order: order };
  }
  const matched = urlToFilter[location.pathname];
  if (matched) activeFilter = matched;
  if (q || activeFilter || sort) {
    sortRows();
  }
  if (activeFilter) {
    applyFilters();
  }
  updateSortIndicators();
})();

window.addEventListener("popstate", function () {
  if (!isIndexDocument) return;
  const matched = urlToFilter[location.pathname];
  activeFilter = matched || null;
  applyFilters();
});
