// Gonçalo Pena – site behaviour. All content is already in the HTML (rendered by build.jl);
// this script only adds interactivity: dark mode, header on scroll, canvases, Poisson widgets,
// publication theme filter, BibTeX/DOI/code copy, code tabs, KaTeX.
(function () {
  "use strict";
  function $(s) { return document.querySelector(s); }
  function $$(s) { return Array.prototype.slice.call(document.querySelectorAll(s)); }
  var reduce = !!(window.matchMedia && matchMedia("(prefers-reduced-motion: reduce)").matches);

  // ---------- dark mode: an explicit choice stamps data-theme; no choice follows the OS ----------
  function store(k, v) {
    try { if (v === undefined) return localStorage.getItem(k); localStorage.setItem(k, v); } catch (e) { return null; }
  }
  function currentTheme() {
    var t = document.documentElement.getAttribute("data-theme");
    if (t) return t;
    return window.matchMedia && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
  }
  var toggles = $$("[data-theme-toggle]");
  function syncToggles() {
    var dark = currentTheme() === "dark";
    toggles.forEach(function (b) {
      b.setAttribute("aria-pressed", String(dark));
      b.setAttribute("aria-label", dark ? "Switch to light mode" : "Switch to dark mode");
      b.dataset.mode = dark ? "dark" : "light";
    });
  }
  toggles.forEach(function (b) {
    b.addEventListener("click", function () {
      var next = currentTheme() === "dark" ? "light" : "dark";
      document.documentElement.setAttribute("data-theme", next);
      store("gp-theme", next);
      syncToggles();
    });
  });
  syncToggles();

  // ---------- phones: the side column becomes a top bar with a menu button ----------
  var side = $("#side"), menuBtn = $("#menubtn");
  function setMenu(open) { side.classList.toggle("open", open); menuBtn.setAttribute("aria-expanded", String(open)); menuBtn.textContent = open ? "Close" : "Menu"; }
  menuBtn.addEventListener("click", function () { setMenu(!side.classList.contains("open")); });
  $$("#nav a").forEach(function (a) { a.addEventListener("click", function () { setMenu(false); }); });
  document.addEventListener("keydown", function (e) { if (e.key === "Escape" && side.classList.contains("open")) { setMenu(false); menuBtn.focus(); } });

  // ---------- maths (software page) ----------
  if (window.renderMathInElement) {
    try { renderMathInElement($("#main"), { delimiters: [{ left: "$$", right: "$$", display: true }, { left: "$", right: "$", display: false }], throwOnError: false }); } catch (e) {}
  }

  // ---------- profile photo: fall back to the monogram if the image cannot load ----------
  var photo = $("#photo"), img = photo && photo.querySelector("img");
  function monogram() { photo.classList.remove("has-photo"); photo.textContent = "GP"; }
  if (img) { if (img.complete && !img.naturalWidth) monogram(); else img.addEventListener("error", monogram); }

  // ---------- canvas helpers ----------
  var STOPS = [[11, 46, 79], [3, 105, 161], [48, 156, 222], [159, 211, 243], [240, 249, 255]];
  function blues(t) {
    t = Math.max(0, Math.min(1, t));
    var p = t * (STOPS.length - 1), i = Math.min(Math.floor(p), STOPS.length - 2), f = p - i, a = STOPS[i], b = STOPS[i + 1];
    return "rgb(" + Math.round(a[0] + (b[0] - a[0]) * f) + "," + Math.round(a[1] + (b[1] - a[1]) * f) + "," + Math.round(a[2] + (b[2] - a[2]) * f) + ")";
  }
  function fit(cv, w, h) {
    var d = Math.min(window.devicePixelRatio || 1, 2);
    cv.width = Math.round(w * d); cv.height = Math.round(h * d);
    var c = cv.getContext("2d"); c.setTransform(d, 0, 0, d, 0, 0); return c;
  }
  function rng(seed) { var s = seed % 2147483647; if (s <= 0) s += 2147483646; return function () { s = s * 16807 % 2147483647; return (s - 1) / 2147483646; }; }
  function bg(c, w, h, a, b) { var g = c.createLinearGradient(0, 0, w, h); g.addColorStop(0, a); g.addColorStop(1, b); c.fillStyle = g; c.fillRect(0, 0, w, h); }
  function path(c, w, step, fy) { c.beginPath(); for (var x = 0; x <= w; x += step) { if (x) c.lineTo(x, fy(x)); else c.moveTo(x, fy(x)); } c.stroke(); }

  // Hero: nodal values of uₕ drawn as cells centred on the real, nonuniform node positions.
  // On the home page the mesh is refined into place on every load (5 → 10 → 20 → 40); other pages show it static.
  var heroCv = $("#hero-cv");
  if (heroCv && window.BramblePoisson) {
    var hero = BramblePoisson.solve(40, true, "sine"), mn = Infinity, mx = -Infinity, k;
    for (k = 0; k < hero.u.length; k++) { if (hero.u[k] < mn) mn = hero.u[k]; if (hero.u[k] > mx) mx = hero.u[k]; }
    var rg = mx - mn || 1, from = null, to = hero, fade = 1;
    var paint = function (c, w, h, S, alpha) {
      var N = S.N, X = S.X, Y = S.Y, u = S.u, i, j;
      function mid(Z, k) { return k === 0 ? 0 : k > N ? 1 : 0.5 * (Z[k - 1] + Z[k]); }
      c.globalAlpha = alpha;
      for (j = 0; j <= N; j++) {
        var y0 = mid(Y, j), y1 = mid(Y, j + 1);
        for (i = 0; i <= N; i++) {
          var x0 = mid(X, i), x1 = mid(X, i + 1);
          c.fillStyle = blues(0.04 + 0.76 * (u[j * (N + 1) + i] - mn) / rg);
          c.fillRect(x0 * w, (1 - y1) * h, (x1 - x0) * w + 0.7, (y1 - y0) * h + 0.7);
        }
      }
      c.strokeStyle = "rgba(255,255,255," + (N < 40 ? 0.22 : 0.14) + ")"; c.lineWidth = 0.7; c.beginPath();
      for (i = 0; i <= N; i++) { c.moveTo(X[i] * w, 0); c.lineTo(X[i] * w, h); }
      for (j = 0; j <= N; j++) { c.moveTo(0, (1 - Y[j]) * h); c.lineTo(w, (1 - Y[j]) * h); }
      c.stroke();
    };
    var drawHero = function () {
      var w = heroCv.clientWidth, h = heroCv.clientHeight;
      if (!w || !h) return;
      var c = fit(heroCv, w, h);
      if (from) paint(c, w, h, from, 1);
      paint(c, w, h, to, fade);
    };
    var home = document.body.dataset.page === "home";
    if (!reduce && home && window.requestAnimationFrame) {
      var levels = [5, 10, 20].map(function (n) { return BramblePoisson.solve(n, true, "sine"); }).concat([hero]);
      var step = 0, t0 = null, DUR = 320;
      to = levels[0]; fade = 0;
      var tick = function (t) {
        if (t0 === null) t0 = t;
        var e = Math.min(1, (t - t0) / DUR);
        fade = 1 - Math.pow(1 - e, 2);
        drawHero();
        if (e < 1) return requestAnimationFrame(tick);
        if (++step < levels.length) { from = to; to = levels[step]; fade = 0; t0 = null; requestAnimationFrame(tick); }
        else from = null;
      };
      requestAnimationFrame(tick);
    }
    if (window.ResizeObserver) new ResizeObserver(drawHero).observe(heroCv); else window.addEventListener("resize", drawHero);
    drawHero();
  }

  // One generated motif per research theme; also used on the publication cards.
  var MOTIF = {
    drug: function (c, w, h, r) {
      bg(c, w, h, "#0b2e4f", "#0369A1");
      c.globalCompositeOperation = "lighter";
      for (var k = 0; k < 7; k++) {
        var x = r() * w, y = r() * h, R = (0.18 + r() * 0.3) * w, g = c.createRadialGradient(x, y, 0, x, y, R);
        g.addColorStop(0, "rgba(159,211,243," + (0.22 + r() * 0.22).toFixed(2) + ")");
        g.addColorStop(0.5, "rgba(48,156,222,.1)"); g.addColorStop(1, "rgba(48,156,222,0)");
        c.fillStyle = g; c.fillRect(0, 0, w, h);
      }
      c.globalCompositeOperation = "source-over";
      var cx = w * (0.3 + r() * 0.4), cy = h * (0.3 + r() * 0.3);
      c.strokeStyle = "rgba(240,249,255,.22)"; c.lineWidth = 1;
      for (var q = 1; q < 10; q++) { c.beginPath(); c.arc(cx, cy, q * q * 3.6 + 6, 0, 6.2832); c.stroke(); }
      c.fillStyle = "#CEB070"; c.beginPath(); c.arc(cx, cy, 4, 0, 6.2832); c.fill();
    },
    conv: function (c, w, h, r) {
      bg(c, w, h, "#0b2e4f", "#0c3f66");
      function axis(n, L) { var a = [0, L]; for (var i = 1; i < n; i++) a.push(Math.pow(r(), 1.5) * L); return a.sort(function (p, q) { return p - q; }); }
      var xs = axis(16, w), ys = axis(10, h), i, j;
      c.strokeStyle = "rgba(159,211,243,.42)"; c.lineWidth = 1; c.beginPath();
      for (i = 0; i < xs.length; i++) { c.moveTo(xs[i], 0); c.lineTo(xs[i], h); }
      for (j = 0; j < ys.length; j++) { c.moveTo(0, ys[j]); c.lineTo(w, ys[j]); }
      c.stroke();
      c.fillStyle = "rgba(240,249,255,.75)";
      for (i = 0; i < xs.length; i++) for (j = 0; j < ys.length; j++) { c.beginPath(); c.arc(xs[i], ys[j], 1.6, 0, 6.2832); c.fill(); }
      var si = 4 + Math.floor(r() * (xs.length - 8)), sj = 3 + Math.floor(r() * (ys.length - 6));
      c.strokeStyle = "#CEB070"; c.lineWidth = 2.2; c.beginPath();
      c.moveTo(xs[si - 1], ys[sj]); c.lineTo(xs[si + 1], ys[sj]); c.moveTo(xs[si], ys[sj - 1]); c.lineTo(xs[si], ys[sj + 1]); c.stroke();
      c.fillStyle = "#CEB070";
      [[0, 0], [-1, 0], [1, 0], [0, -1], [0, 1]].forEach(function (d) { c.beginPath(); c.arc(xs[si + d[0]], ys[sj + d[1]], 3.4, 0, 6.2832); c.fill(); });
    },
    fsi: function (c, w, h, r) {
      bg(c, w, h, "#0369A1", "#0b2e4f");
      var cx = w * (0.4 + r() * 0.2), s = w * 0.17, bul = h * (0.09 + r() * 0.06), n = 16, k;
      function bump(x) { return bul * Math.exp(-Math.pow((x - cx) / s, 2)); }
      function top(x) { return h * 0.2 - bump(x); }
      function bot(x) { return h * 0.8 + bump(x); }
      c.fillStyle = "rgba(0,0,0,.28)"; c.fillRect(0, 0, w, h * 0.2); c.fillRect(0, h * 0.8, w, h * 0.2);
      c.lineWidth = 1.1;
      for (k = 0; k < n; k++) {
        var f = (k + 0.5) / n, ph = r() * 6.28, amp = Math.sin(Math.PI * f);
        c.strokeStyle = "rgba(240,249,255," + (0.16 + 0.5 * amp).toFixed(2) + ")";
        path(c, w, 4, function (x) { return top(x) + (bot(x) - top(x)) * f + Math.sin(x / w * 11 + ph) * 1.4 * amp; });
      }
      c.strokeStyle = "#1C999B"; c.lineWidth = 4; path(c, w, 4, top); path(c, w, 4, bot);
    },
    nonfick: function (c, w, h, r) {
      bg(c, w, h, "#0b2e4f", "#10466f");
      var mid = h * 0.5, A = h * 0.36, lam = 2.2 + r(), k;
      c.strokeStyle = "rgba(159,211,243,.25)"; c.lineWidth = 1; path(c, w, w, function () { return mid; });
      c.setLineDash([5, 5]); c.strokeStyle = "rgba(206,176,112,.85)"; c.lineWidth = 1.2;
      path(c, w, 4, function (x) { return mid - A * Math.exp(-lam * x / w); });
      path(c, w, 4, function (x) { return mid + A * Math.exp(-lam * x / w); });
      c.setLineDash([]);
      for (k = 3; k >= 0; k--) {
        var om = 16 + k * 8 + r() * 4, ph = r() * 6.28;
        c.strokeStyle = k === 0 ? "#f0f9ff" : "rgba(159,211,243," + (0.6 - k * 0.13).toFixed(2) + ")";
        c.lineWidth = k === 0 ? 2.2 : 1.2;
        path(c, w, 2, function (x) { return mid - A * Math.exp(-lam * x / w) * Math.cos(om * x / w + ph); });
      }
    }
  };
  $$("canvas[data-motif]").forEach(function (cv) {
    var m = MOTIF[cv.dataset.motif];
    if (m) m(fit(cv, 480, 300), 480, 300, rng(+cv.dataset.seed || 11));
  });

  // ---------- Poisson widgets ----------
  if (window.BramblePoisson) {
    $$("[data-bp]").forEach(function (el) {
      BramblePoisson.mount(el, { compact: el.dataset.bp === "compact", N: 24, colormap: "blues" });
    });
  }

  // ---------- publications timeline: fill the rail to the reading line, light the dots passed ----------
  var tl = $(".tl"), tlQueued = false;
  function drawTl() {
    tlQueued = false;
    var line = window.innerHeight * 0.4, r = tl.getBoundingClientRect(), now = null;
    tl.style.setProperty("--p", Math.max(0, Math.min(1, (line - r.top - 36) / (r.height - 36))).toFixed(4));
    $$(".tl .pub").forEach(function (li) { li.classList.toggle("is-past", !li.hidden && li.getBoundingClientRect().top + 22 <= line); });
    $$(".tl .yg").forEach(function (g) { if (!g.hidden && g.getBoundingClientRect().top <= line) now = g; g.classList.remove("is-now"); });
    now = now || $(".tl .yg:not([hidden])");
    if (now) now.classList.add("is-now");
    $$("#yx a").forEach(function (a) {
      var g = document.getElementById("y" + a.dataset.year), on = g === now;
      a.hidden = !g || g.hidden;
      if (on !== (a.getAttribute("aria-current") === "true")) {
        if (on) { a.setAttribute("aria-current", "true"); a.parentNode.scrollLeft = a.offsetLeft - a.parentNode.clientWidth / 2; }
        else a.removeAttribute("aria-current");
      }
    });
  }
  function queueTl() { if (tl && !tlQueued) { tlQueued = true; requestAnimationFrame(drawTl); } }
  if (tl) {
    tl.classList.add("tl-on");
    window.addEventListener("scroll", queueTl, { passive: true });
    window.addEventListener("resize", queueTl);
  }

  // ---------- publications: filter the pre-rendered list by theme (hash = theme key) ----------
  var count = $("#count"), items = $$(".pub[data-themes]");
  var labels = {};
  try { labels = JSON.parse(count ? count.dataset.labels : "{}"); } catch (e) {}
  function applyTheme(k) {
    if (!count) return;
    if (!labels[k]) k = "all";
    var n = 0;
    items.forEach(function (li) {
      var on = k === "all" || (" " + li.dataset.themes + " ").indexOf(" " + k + " ") >= 0;
      li.hidden = !on;
      if (on) n++;
    });
    $$(".yg").forEach(function (g) { g.hidden = !g.querySelector(".pub:not([hidden])"); });
    $("#empty").hidden = n > 0;
    queueTl();
    count.innerHTML = k === "all" ? "<strong>" + n + "</strong> publications" :
      "<strong>" + n + "</strong> of " + items.length + " publications on " + labels[k].replace(/&/g, "&amp;").replace(/</g, "&lt;");
    $$(".tf").forEach(function (b) { b.setAttribute("aria-pressed", String(b.dataset.setTheme === k)); });
  }
  function themeFromHash() { applyTheme(decodeURIComponent(location.hash.replace("#", ""))); }
  if (count) {
    themeFromHash();
    window.addEventListener("hashchange", themeFromHash);
  }

  // ---------- copying ----------
  function copy(text, btn, done) {
    // Icon-only buttons keep their icons (CSS swaps them) and announce through the label instead.
    var icon = btn.classList.contains("btn-ic"), label = icon ? btn.getAttribute("aria-label") : btn.textContent;
    function set(v) { if (icon) { btn.setAttribute("aria-label", v); btn.title = v; } else btn.textContent = v; }
    function ok() { set(done || "Copied"); btn.classList.add("is-copied"); setTimeout(function () { set(label); btn.classList.remove("is-copied"); }, 1600); }
    function fallback() {
      var ta = document.createElement("textarea");
      ta.value = text; ta.setAttribute("readonly", ""); ta.style.position = "fixed"; ta.style.opacity = "0";
      document.body.appendChild(ta); ta.select();
      try { document.execCommand("copy"); ok(); } catch (e) { set("Select and copy"); }
      document.body.removeChild(ta);
    }
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(ok, fallback);
      else fallback();
    } catch (e) { fallback(); }
  }

  // ---------- delegated clicks ----------
  document.addEventListener("click", function (e) {
    var t = e.target, b;
    if ((b = t.closest("#yx a"))) {
      // Jump to the year without touching the hash, which holds the theme filter.
      e.preventDefault();
      var g = document.getElementById("y" + b.dataset.year);
      if (g) { g.scrollIntoView({ behavior: reduce ? "auto" : "smooth", block: "start" }); g.querySelector(".yg-y").focus({ preventScroll: true }); }
    } else if ((b = t.closest("[data-set-theme]"))) {
      // The hash holds the theme, so a filtered view can be linked; "all" clears it.
      var k = b.dataset.setTheme;
      if (history.replaceState) history.replaceState(null, "", k === "all" ? location.pathname + location.search : "#" + k);
      applyTheme(k);
      // A tag inside a paper: bring the filter bar into view so the change is visible.
      if (!b.closest("#tbar")) $("#tbar").scrollIntoView({ behavior: reduce ? "auto" : "smooth", block: "center" });
    } else if ((b = t.closest("[data-toggle-bib]"))) {
      var box = document.getElementById("bib-" + b.dataset.toggleBib);
      if (box) { box.hidden = !box.hidden; b.setAttribute("aria-expanded", String(!box.hidden)); }
    } else if ((b = t.closest("[data-copy-bib]"))) {
      // Clicking the BibTeX itself copies it, unless the reader is selecting part of it.
      if (b.tagName === "PRE" && String(window.getSelection && getSelection())) return;
      if (b.tagName === "PRE") b = b.parentNode.querySelector("button[data-copy-bib]");
      var pre = document.querySelector("#bib-" + b.dataset.copyBib + " pre");
      if (pre) copy(pre.textContent, b, "Copied");
    } else if ((b = t.closest("[data-copy-doi]"))) {
      copy("https://doi.org/" + b.dataset.copyDoi, b, "DOI copied");
    } else if ((b = t.closest("[data-copy-target]"))) {
      var src = document.getElementById(b.dataset.copyTarget);
      if (src) copy(src.textContent, b, "Copied");
    } else if ((b = t.closest("[data-contact]")) && $("#contact")) {
      // Already on the home page: scroll smoothly and move focus to the heading.
      e.preventDefault();
      if (history.replaceState) history.replaceState(null, "", "#contact");
      $("#contact").scrollIntoView({ behavior: reduce ? "auto" : "smooth", block: "start" });
      $("#h-contact").focus({ preventScroll: true });
    }
  });

  // ---------- code / output tabs ----------
  var tabs = [$("#tab-code"), $("#tab-out")].filter(Boolean);
  function selectTab(t) {
    tabs.forEach(function (b) {
      var on = b === t; b.setAttribute("aria-selected", String(on)); b.tabIndex = on ? 0 : -1;
      document.getElementById(b.getAttribute("aria-controls")).hidden = !on;
    });
  }
  tabs.forEach(function (b, i) {
    b.addEventListener("click", function () { selectTab(b); });
    b.addEventListener("keydown", function (e) {
      if (e.key === "ArrowRight" || e.key === "ArrowLeft") { var n = tabs[(i + 1) % tabs.length]; selectTab(n); n.focus(); e.preventDefault(); }
    });
  });
})();
