(() => {
  const $ = (selector, root = document) => root.querySelector(selector);

  const els = {
    img: $("#viewer"),
    prev: $("#prev"),
    play: $("#play"),
    next: $("#next"),
    note: $("#note"),
    hudLeft: $("#hudLeft"),
    hudRight: $("#hudRight"),
    hudLoadTrack: $("#hudLoadTrack"),
    hudLoadFill: $("#hudLoadFill"),
    hudLoadMeta: $("#hudLoadMeta"),
    uiCount: $("#uiCount"),
    uiLoad: $("#uiLoad"),
    folderSelect: $("#folderSelect"),
    thumbs: $("#thumbs"),
    topLoad: $("#topLoad"),
    topLoadBar: $("#topLoadBar"),
    topLoadPct: $("#topLoadPct"),
  };

  const state = {
    folders: [],
    folder: "",
    images: [],
    index: 0,
    folderToken: 0,
    navToken: 0,
    playing: false,
    timer: null,
    loadToken: 0,
    busy: false,
    preloadToken: 0,
    preloadTotal: 0,
    preloadDone: 0,
    preloadRaf: 0,
    thumbNodes: [],
    activeThumbIndex: -1,
  };

  const preloadCache = new Set();

  const appBase = (() => {
    const raw = document.documentElement && document.documentElement.dataset
      ? String(document.documentElement.dataset.base || "")
      : "";
    if (!raw || raw === "/") {
      return "";
    }

    return raw.endsWith("/") ? raw.slice(0, -1) : raw;
  })();

  const withBase = (path) => {
    const clean = String(path || "").replace(/^\/+/, "");
    return appBase ? `${appBase}/${clean}` : `/${clean}`;
  };

  const picsRootLabel = () => withBase("pics/");

  const storage = {
    get(key, fallback = "") {
      try {
        const value = window.localStorage.getItem(key);
        return value == null ? fallback : value;
      } catch (_err) {
        return fallback;
      }
    },
    set(key, value) {
      try {
        window.localStorage.setItem(key, String(value));
      } catch (_err) {
        // Ignore storage failures.
      }
    },
  };

  const gesture = { lockUntil: 0 };
  const lockClicks = (ms = 450) => {
    gesture.lockUntil = Date.now() + ms;
  };
  const clickLocked = () => Date.now() < gesture.lockUntil;

  const attachSwipe = (el, onNext, onPrev) => {
    if (!el) {
      return;
    }

    let sx = 0;
    let sy = 0;
    let tracking = false;
    let moved = false;
    let pointerId = -1;

    const start = (x, y) => {
      sx = x;
      sy = y;
      tracking = true;
      moved = false;
    };

    const move = (x, y, event) => {
      if (!tracking) {
        return;
      }

      const dx = x - sx;
      const dy = y - sy;
      if (!moved && (Math.abs(dx) > 8 || Math.abs(dy) > 8)) {
        moved = true;
      }

      if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 8 && event && event.cancelable) {
        event.preventDefault();
      }
    };

    const end = (x, y, event) => {
      if (!tracking) {
        return;
      }

      tracking = false;
      const dx = x - sx;
      const dy = y - sy;

      if (moved) {
        lockClicks();
      }

      if (Math.abs(dx) < 40 || Math.abs(dx) < Math.abs(dy)) {
        return;
      }

      if (dx < 0) {
        onNext();
      } else {
        onPrev();
      }

      if (event && event.cancelable) {
        event.preventDefault();
      }
    };

    if (window.PointerEvent) {
      el.addEventListener("pointerdown", (event) => {
        if (event.pointerType !== "touch" && event.pointerType !== "pen") {
          return;
        }

        pointerId = event.pointerId;
        start(event.clientX, event.clientY);
      }, { passive: true });

      el.addEventListener("pointermove", (event) => {
        if (!tracking || pointerId !== event.pointerId) {
          return;
        }

        move(event.clientX, event.clientY, event);
      }, { passive: false });

      el.addEventListener("pointerup", (event) => {
        if (!tracking || pointerId !== event.pointerId) {
          return;
        }

        end(event.clientX, event.clientY, event);
        pointerId = -1;
      }, { passive: false });

      el.addEventListener("pointercancel", () => {
        tracking = false;
        pointerId = -1;
      }, { passive: true });

      return;
    }

    el.addEventListener("touchstart", (event) => {
      if (!event.touches || event.touches.length !== 1) {
        return;
      }

      const touch = event.touches[0];
      start(touch.clientX, touch.clientY);
    }, { passive: true });

    el.addEventListener("touchmove", (event) => {
      if (!tracking || !event.touches || event.touches.length !== 1) {
        return;
      }

      const touch = event.touches[0];
      move(touch.clientX, touch.clientY, event);
    }, { passive: false });

    el.addEventListener("touchend", (event) => {
      if (!tracking || !event.changedTouches || event.changedTouches.length < 1) {
        return;
      }

      const touch = event.changedTouches[0];
      end(touch.clientX, touch.clientY, event);
    }, { passive: false });

    el.addEventListener("touchcancel", () => {
      tracking = false;
    }, { passive: true });
  };

  const api = async (params) => {
    const url = new URL(withBase("system/api.php"), window.location.origin);
    Object.entries(params || {}).forEach(([key, value]) => {
      url.searchParams.set(key, String(value));
    });

    const response = await fetch(url.toString(), { cache: "no-store" });
    if (!response.ok) {
      throw new Error("API " + response.status);
    }

    return response.json();
  };

  const normalizeImageItem = (entry) => {
    if (typeof entry === "string") {
      return { name: "", url: entry, thumb: entry };
    }

    if (!entry || typeof entry !== "object") {
      return null;
    }

    const url = typeof entry.url === "string" && entry.url !== ""
      ? entry.url
      : (typeof entry.src === "string" ? entry.src : "");

    if (url === "") {
      return null;
    }

    const thumb = typeof entry.thumb === "string" && entry.thumb !== "" ? entry.thumb : url;
    const name = typeof entry.name === "string" ? entry.name : "";

    return { name, url, thumb };
  };

  const normalizeImages = (list) => {
    if (!Array.isArray(list)) {
      return [];
    }

    const result = [];
    list.forEach((entry) => {
      const normalized = normalizeImageItem(entry);
      if (normalized) {
        result.push(normalized);
      }
    });

    return result;
  };

  const getThumbSource = (item) => {
    if (!item || typeof item !== "object") {
      return "";
    }

    if (typeof item.thumb === "string" && item.thumb !== "") {
      return item.thumb;
    }

    if (typeof item.url === "string" && item.url !== "") {
      return item.url;
    }

    return "";
  };

  const getViewerCandidates = (item) => {
    const result = [];

    if (!item || typeof item !== "object") {
      return result;
    }

    const push = (value) => {
      if (typeof value !== "string" || value === "") {
        return;
      }
      if (!result.includes(value)) {
        result.push(value);
      }
    };

    push(item.url);
    push(item.src);
    push(item.thumb);

    const snapshot = result.slice();
    snapshot.forEach((candidate) => {
      const encoded = encodeURI(candidate);
      if (encoded !== candidate) {
        push(encoded);
      }
    });

    return result;
  };

  const setNote = (text = "") => {
    if (els.note) {
      els.note.textContent = text;
    }
  };

  const setHud = (text = "") => {
    if (els.hudRight) {
      els.hudRight.textContent = text;
    }
  };

  const scheduleLoadUI = () => {
    if (state.preloadRaf) {
      return;
    }

    state.preloadRaf = requestAnimationFrame(() => {
      state.preloadRaf = 0;

      const totalLoad = state.preloadTotal || 0;
      const doneLoad = totalLoad > 0 ? Math.min(state.preloadDone, totalLoad) : 0;
      const pct = totalLoad > 0 ? Math.floor((doneLoad / totalLoad) * 100) : 0;
      const loading = totalLoad > 0 && doneLoad < totalLoad;

      if (els.uiLoad) {
        els.uiLoad.textContent = `${pct}%`;
      }

      if (els.hudLoadFill) {
        els.hudLoadFill.style.width = `${pct}%`;
      }

      if (els.hudLoadTrack) {
        els.hudLoadTrack.setAttribute("aria-valuenow", String(pct));
      }

      if (els.hudLoadMeta) {
        els.hudLoadMeta.textContent = totalLoad > 0
          ? `Loaded ${doneLoad}/${totalLoad} • ${pct}%`
          : `Loaded 0/0 • ${pct}%`;
      }

      if (els.hudLeft) {
        const complete = totalLoad > 0 && doneLoad >= totalLoad;
        els.hudLeft.classList.toggle("is-complete", complete);
      }

      if (els.topLoad) {
        els.topLoad.classList.toggle("is-visible", loading);
        els.topLoad.setAttribute("aria-hidden", loading ? "false" : "true");
      }

      if (els.topLoadBar) {
        els.topLoadBar.style.width = `${pct}%`;
      }

      if (els.topLoadPct) {
        els.topLoadPct.textContent = `${pct}%`;
      }

      const total = state.images.length;
      const current = total > 0 ? state.index + 1 : 0;
      if (els.hudRight) {
        els.hudRight.textContent = total > 0 ? `${current}/${total}${state.playing ? " • PLAY" : ""}` : "0/0";
      }
    });
  };

  const resetLoadUI = () => {
    state.preloadTotal = 0;
    state.preloadDone = 0;
    scheduleLoadUI();
  };

  const mapWithConcurrency = async (items, limit, mapper) => {
    if (!Array.isArray(items) || items.length === 0) {
      return;
    }

    const workerCount = Math.max(1, Math.min(limit, items.length));
    let cursor = 0;

    const workers = Array.from({ length: workerCount }, async () => {
      while (true) {
        const index = cursor;
        cursor += 1;

        if (index >= items.length) {
          return;
        }

        try {
          await mapper(items[index], index);
        } catch (_err) {
          // Continue even if one image fails to preload.
        }
      }
    });

    await Promise.all(workers);
  };

  const preloadImage = (source) => new Promise((resolve) => {
    if (typeof source !== "string" || source === "") {
      resolve();
      return;
    }

    if (preloadCache.has(source)) {
      resolve();
      return;
    }

    const im = new Image();
    const done = () => {
      im.onload = null;
      im.onerror = null;
      preloadCache.add(source);
      resolve();
    };

    im.onload = done;
    im.onerror = done;
    im.decoding = "async";
    im.loading = "eager";
    im.src = source;
  });

  const startPreload = (items) => {
    const sources = [];
    if (Array.isArray(items)) {
      items.forEach((item) => {
        const source = getThumbSource(item);
        if (source !== "") {
          sources.push(source);
        }
      });
    }

    const list = Array.from(new Set(sources));
    const token = ++state.preloadToken;
    state.preloadTotal = list.length;
    state.preloadDone = 0;
    scheduleLoadUI();

    if (list.length === 0) {
      return;
    }

    const cpu = navigator.hardwareConcurrency ? Math.floor(navigator.hardwareConcurrency / 2) : 4;
    const limit = Math.max(2, Math.min(8, cpu));

    mapWithConcurrency(list, limit, async (source) => {
      await preloadImage(source);
      if (token !== state.preloadToken) {
        return;
      }

      state.preloadDone += 1;
      scheduleLoadUI();
    }).catch((_err) => {
      // Ignore preload failures.
    });
  };

  const lb = {
    wrap: null,
    img: null,
    close: null,
    prev: null,
    next: null,
    open: false,
  };

  const setButtons = () => {
    const has = state.images.length > 0;
    const locked = state.busy;

    if (els.prev) {
      els.prev.disabled = !has || locked;
    }

    if (els.next) {
      els.next.disabled = !has || locked;
    }

    if (els.play) {
      els.play.disabled = !has || locked;
      els.play.textContent = state.playing ? "Pause" : "Play";
    }

    if (lb.prev) {
      lb.prev.disabled = !has || state.images.length < 2 || locked;
    }

    if (lb.next) {
      lb.next.disabled = !has || state.images.length < 2 || locked;
    }
  };

  const showCounts = () => {
    const total = state.images.length;
    const current = total > 0 ? state.index + 1 : 0;

    if (els.uiCount) {
      els.uiCount.textContent = `${current}/${total}`;
    }

    scheduleLoadUI();
    setHud(total > 0 ? `${current}/${total}${state.playing ? " • PLAY" : ""}` : "0/0");
  };

  const stopPlay = () => {
    state.playing = false;
    if (state.timer) {
      clearInterval(state.timer);
    }
    state.timer = null;
    setButtons();
    showCounts();
  };

  const startPlay = () => {
    if (!state.images.length) {
      return;
    }

    state.playing = true;
    setButtons();
    showCounts();

    if (state.timer) {
      clearInterval(state.timer);
    }

    state.timer = setInterval(() => {
      if (state.busy) {
        return;
      }

      setIndex(state.index + 1);
    }, 2500);
  };

  const togglePlay = () => {
    if (state.playing) {
      stopPlay();
      return;
    }

    startPlay();
  };

  const highlightThumb = () => {
    const prev = state.thumbNodes[state.activeThumbIndex];
    if (prev) {
      prev.classList.remove("is-active");
    }

    const next = state.thumbNodes[state.index];
    if (next) {
      next.classList.add("is-active");
      next.scrollIntoView({ block: "nearest", inline: "nearest", behavior: "smooth" });
    }

    state.activeThumbIndex = state.index;
  };

  const navNext = () => {
    if (!state.images.length || state.busy) {
      return;
    }

    stopPlay();
    setIndex(state.index + 1);
  };

  const navPrev = () => {
    if (!state.images.length || state.busy) {
      return;
    }

    stopPlay();
    setIndex(state.index - 1);
  };

  const ensureLightbox = () => {
    if (lb.wrap) {
      return;
    }

    const wrap = document.createElement("div");
    wrap.id = "lightbox";
    wrap.className = "lightbox";
    wrap.hidden = true;
    wrap.setAttribute("aria-hidden", "true");

    const close = document.createElement("button");
    close.type = "button";
    close.className = "lb-close";
    close.setAttribute("aria-label", "Close");
    close.textContent = "×";

    const prev = document.createElement("button");
    prev.type = "button";
    prev.className = "lb-nav lb-prev";
    prev.setAttribute("aria-label", "Previous image");
    prev.textContent = "‹";

    const next = document.createElement("button");
    next.type = "button";
    next.className = "lb-nav lb-next";
    next.setAttribute("aria-label", "Next image");
    next.textContent = "›";

    const im = document.createElement("img");
    im.alt = "Full screen image";
    im.decoding = "async";

    wrap.appendChild(close);
    wrap.appendChild(prev);
    wrap.appendChild(next);
    wrap.appendChild(im);
    document.body.appendChild(wrap);

    close.addEventListener("click", (event) => {
      event.preventDefault();
      event.stopPropagation();
      closeLightbox();
    });

    prev.addEventListener("click", (event) => {
      event.preventDefault();
      event.stopPropagation();
      navPrev();
    });

    next.addEventListener("click", (event) => {
      event.preventDefault();
      event.stopPropagation();
      navNext();
    });

    wrap.addEventListener("click", (event) => {
      if (clickLocked()) {
        return;
      }

      if (event.target === wrap) {
        closeLightbox();
      }
    });

    im.addEventListener("dblclick", () => {
      wrap.classList.toggle("cover");
    });

    lb.wrap = wrap;
    lb.img = im;
    lb.close = close;
    lb.prev = prev;
    lb.next = next;

    attachSwipe(wrap, navNext, navPrev);
  };

  const closeLightbox = () => {
    if (!lb.wrap) {
      return;
    }

    lb.open = false;
    lb.wrap.hidden = true;
    lb.wrap.classList.remove("cover");
    lb.wrap.setAttribute("aria-hidden", "true");
    document.body.classList.remove("no-scroll");
  };

  const syncLightbox = (url) => {
    if (!lb.open || !lb.img || typeof url !== "string" || url === "") {
      return;
    }

    lb.img.src = url;
  };

  const openLightbox = () => {
    if (!state.images.length) {
      return;
    }

    ensureLightbox();
    lb.open = true;
    lb.wrap.hidden = false;
    lb.wrap.setAttribute("aria-hidden", "false");
    document.body.classList.add("no-scroll");

    const current = state.images[state.index];
    const fallback = getViewerCandidates(current)[0] || "";
    const currentSrc = els.img && typeof els.img.src === "string" ? els.img.src : "";
    lb.img.src = currentSrc || fallback;
    setButtons();
  };

  const showImage = async (item) => {
    if (!item || typeof item !== "object") {
      return false;
    }

    state.busy = true;
    setButtons();
    if (els.img) {
      els.img.classList.remove("ready");
    }

    const token = ++state.loadToken;
    const candidates = getViewerCandidates(item);

    const probe = (url) => new Promise((resolve) => {
      const im = new Image();
      const done = (ok) => {
        im.onload = null;
        im.onerror = null;
        resolve(ok);
      };

      im.onload = () => done(true);
      im.onerror = () => done(false);
      im.decoding = "async";
      im.loading = "eager";
      im.src = url;
    });

    try {
      if (candidates.length === 0) {
        return false;
      }

      let finalUrl = "";
      for (let i = 0; i < candidates.length; i += 1) {
        const candidate = candidates[i];
        const ok = await probe(candidate);

        if (token !== state.loadToken) {
          return false;
        }

        if (ok) {
          finalUrl = candidate;
          break;
        }
      }

      if (token !== state.loadToken || finalUrl === "") {
        return false;
      }

      if (els.img) {
        els.img.src = finalUrl;
        requestAnimationFrame(() => {
          if (token === state.loadToken && els.img) {
            els.img.classList.add("ready");
          }
        });
      }

      syncLightbox(finalUrl);
      return true;
    } finally {
      if (token === state.loadToken) {
        state.busy = false;
        setButtons();
      }
    }
  };

  const renderThumbs = () => {
    if (!els.thumbs) {
      return;
    }

    els.thumbs.textContent = "";
    state.thumbNodes = [];
    state.activeThumbIndex = -1;

    if (!state.images.length) {
      const empty = document.createElement("div");
      empty.className = "mono";
      empty.style.gridColumn = "1/-1";
      empty.style.opacity = ".75";
      empty.textContent = "No images in this folder.";
      els.thumbs.appendChild(empty);
      return;
    }

    const frag = document.createDocumentFragment();
    for (let i = 0; i < state.images.length; i += 1) {
      const item = state.images[i];
      const d = document.createElement("div");
      d.className = "thumb";
      d.dataset.i = String(i);

      const im = document.createElement("img");
      im.loading = "lazy";
      im.decoding = "async";
      im.fetchPriority = "low";
      im.src = getThumbSource(item);
      im.alt = item.name || state.folder;

      d.appendChild(im);
      state.thumbNodes.push(d);
      frag.appendChild(d);
    }

    els.thumbs.appendChild(frag);
    highlightThumb();
  };

  const setIndex = async (nextIndex) => {
    if (!state.images.length) {
      return;
    }

    const total = state.images.length;
    const folderToken = state.folderToken;
    const navToken = ++state.navToken;
    const start = ((nextIndex % total) + total) % total;
    state.index = start;

    showCounts();
    highlightThumb();
    setNote("");

    let ok = await showImage(state.images[state.index]);
    if (folderToken !== state.folderToken || navToken !== state.navToken) {
      return;
    }

    if (!ok && total > 1) {
      for (let step = 1; step < total; step += 1) {
        if (folderToken !== state.folderToken || navToken !== state.navToken) {
          return;
        }

        state.index = (start + step) % total;
        showCounts();
        highlightThumb();
        ok = await showImage(state.images[state.index]);
        if (folderToken !== state.folderToken || navToken !== state.navToken) {
          return;
        }

        if (ok) {
          setNote("Skipped a broken image.");
          break;
        }
      }
    }

    if (folderToken !== state.folderToken || navToken !== state.navToken) {
      return;
    }

    if (!ok) {
      setNote("Could not load this image.");
      if (els.img) {
        els.img.removeAttribute("src");
        els.img.classList.remove("ready");
      }
    }

    showCounts();
  };

  const loadFolder = async (folder) => {
    const folderToken = ++state.folderToken;
    state.navToken += 1;
    state.loadToken += 1;
    state.busy = false;
    stopPlay();
    closeLightbox();

    state.folder = folder;
    state.images = [];
    state.index = 0;

    if (els.img) {
      els.img.removeAttribute("src");
      els.img.classList.remove("ready");
    }

    setNote("");
    resetLoadUI();
    showCounts();
    setButtons();

    const data = await api({ action: "list", folder });
    if (folderToken !== state.folderToken) {
      return;
    }

    if (!data.ok) {
      throw new Error(data.error || "Could not read the folder");
    }

    state.images = normalizeImages(data.images);
    storage.set("pics_folder", folder);

    startPreload(state.images);
    showCounts();
    renderThumbs();
    setButtons();

    if (!state.images.length) {
      setNote("This folder has no images.");
      setHud("0/0");
      return;
    }

    await setIndex(0);
  };

  const init = async () => {
    try {
      setHud("0/0");
      resetLoadUI();

      const data = await api({ action: "init" });
      if (!data.ok) {
        throw new Error(data.error || "Init failed");
      }

      state.folders = Array.isArray(data.folders) ? data.folders : [];
      if (els.folderSelect) {
        els.folderSelect.textContent = "";
      }

      if (!state.folders.length) {
        setHud("0/0");
        setNote(`Create e.g. ${picsRootLabel()}test/ and put some .jpg/.png files there.`);
        setButtons();
        return;
      }

      state.folders.forEach((folder) => {
        if (!els.folderSelect) {
          return;
        }

        const opt = document.createElement("option");
        opt.value = folder.name;
        opt.textContent = `${folder.name} (${folder.count})`;
        els.folderSelect.appendChild(opt);
      });

      const saved = storage.get("pics_folder", "");
      const first = String(state.folders[0].name || "");
      const pick = state.folders.some((folder) => folder.name === saved) ? saved : first;

      if (els.folderSelect) {
        els.folderSelect.value = pick;
      }

      await loadFolder(pick);
    } catch (err) {
      stopPlay();
      setHud("0/0");
      setNote(String(err && err.message ? err.message : err));
      setButtons();
    }
  };

  if (els.prev) {
    els.prev.addEventListener("click", () => {
      navPrev();
    });
  }

  if (els.next) {
    els.next.addEventListener("click", () => {
      navNext();
    });
  }

  if (els.play) {
    els.play.addEventListener("click", () => {
      togglePlay();
    });
  }

  if (els.folderSelect) {
    els.folderSelect.addEventListener("change", async () => {
      try {
        await loadFolder(els.folderSelect.value);
      } catch (err) {
        setNote(String(err && err.message ? err.message : err));
      }
    });
  }

  if (els.thumbs) {
    els.thumbs.addEventListener("click", async (event) => {
      const d = event.target.closest(".thumb");
      if (!d) {
        return;
      }

      const i = Number(d.dataset.i);
      if (!Number.isFinite(i)) {
        return;
      }

      stopPlay();
      await setIndex(i);
    });
  }

  if (els.img) {
    els.img.addEventListener("click", () => {
      if (clickLocked()) {
        return;
      }

      openLightbox();
    });
  }

  attachSwipe(els.img, navNext, navPrev);

  window.addEventListener("keydown", (event) => {
    const tagName = event.target && event.target.tagName ? event.target.tagName : "";
    if (tagName === "INPUT" || tagName === "SELECT" || tagName === "TEXTAREA") {
      return;
    }

    if (lb.open) {
      if (event.key === "Escape") {
        event.preventDefault();
        closeLightbox();
        return;
      }

      if (event.key === "ArrowLeft") {
        event.preventDefault();
        navPrev();
        return;
      }

      if (event.key === "ArrowRight") {
        event.preventDefault();
        navNext();
        return;
      }
    }

    if (event.key === "ArrowLeft") {
      event.preventDefault();
      navPrev();
    }

    if (event.key === "ArrowRight") {
      event.preventDefault();
      navNext();
    }

    if (event.key === " " || event.key === "Spacebar") {
      event.preventDefault();
      togglePlay();
    }
  });

  window.addEventListener("visibilitychange", () => {
    if (document.hidden) {
      stopPlay();
    }
  });

  init();
})();
