(function () {
  "use strict";

  const CATALOGUE = Object.freeze([
    {
      title: "Canker",
      author: "Fatima Abdolhamid",
      cover: "assets/canker.jpg",
      url: "https://www.amazon.com/dp/B0H7ZC36YD"
    },
    {
      title: "Full Moon",
      author: "Yas Is",
      cover: "assets/full-moon.jpg",
      url: "https://www.amazon.com/dp/B0H83YMRL4"
    },
    {
      title: "The Shepherd’s Palace",
      author: "Fatima Abdolhamid",
      cover: "assets/the-shepherds-palace.png",
      url: "https://www.amazon.com/dp/B0H8FKRSM3"
    },
    {
      title: "Tannic",
      author: "Fatima Abdolhamid",
      cover: "assets/tannic.png",
      url: "https://www.amazon.com/dp/B0H8ZJ5YLT"
    },
    {
      title: "The Complete Book of Cats — Vol. I",
      author: "Yas Is",
      cover: "assets/the-complete-book-of-cats.png",
      url: "https://www.amazon.com/dp/B0FLLQK7R7"
    }
  ]);

  const $ = (selector, root = document) => root.querySelector(selector);
  const $$ = (selector, root = document) => [...root.querySelectorAll(selector)];

  let books = [];
  let progressByBook = new Map();
  let coverUrls = new Map();
  let activeBookId = "";
  let searchController = null;
  let searchTimer = 0;
  let selectedSearchIndex = -1;
  let panelReturnFocus = null;
  let dialogReturnFocus = null;
  let dragDepth = 0;
  let toastTimer = 0;

  function announce(message) {
    $("#announcer").textContent = "";
    window.setTimeout(() => {
      $("#announcer").textContent = String(message || "");
    }, 20);
  }

  function showToast(message, duration = 3400) {
    const toast = $("#toast");
    clearTimeout(toastTimer);
    toast.textContent = message;
    toast.hidden = false;
    toastTimer = window.setTimeout(() => {
      toast.hidden = true;
    }, duration);
  }

  function logError(context, error) {
    console.error(`[Senliz Library] ${context}`, error);
  }

  function errorMessage(error, fallback = "Senliz Library could not complete this action.") {
    if (error instanceof globalThis.SenlizSecurity.EpubValidationError) {
      if (error.code === "unsupported-type") return "Senliz Library supports EPUB files only.";
      return "This file is not a valid EPUB.";
    }
    if (error?.name === "QuotaExceededError" || error?.name === "NS_ERROR_DOM_QUOTA_REACHED") {
      return globalThis.SenlizDB.friendlyStorageError(error);
    }
    const message = String(error?.message || "");
    if (/fixed-layout/i.test(message)) {
      return "This fixed-layout EPUB is not currently supported by Senliz Library.";
    }
    if (/no longer available|imported again/i.test(message)) {
      return "This book is no longer available locally. Import the EPUB again.";
    }
    return fallback;
  }

  function showDialog(options) {
    const dialog = $("#messageDialog");
    dialogReturnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
    $("#dialogEyebrow").textContent = options.eyebrow || "SENLIZ LIBRARY";
    $("#dialogTitle").textContent = options.title || "Message";
    $("#dialogMessage").textContent = options.message || "";
    const cancel = $('#dialogActions [value="cancel"]');
    const confirm = $('#dialogActions [value="confirm"]');
    cancel.textContent = options.cancelLabel || "Cancel";
    cancel.hidden = options.cancel === false;
    confirm.textContent = options.confirmLabel || "Continue";
    confirm.className = options.destructive ? "danger-button" : "primary-button";

    return new Promise((resolve) => {
      const onClose = () => {
        dialog.removeEventListener("close", onClose);
        dialogReturnFocus?.focus?.();
        resolve(dialog.returnValue === "confirm");
      };
      dialog.addEventListener("close", onClose);
      dialog.showModal();
      window.setTimeout(() => (cancel.hidden ? confirm : cancel).focus(), 0);
    });
  }

  function setTheme(theme, persist = true) {
    const next = theme === "dark" ? "dark" : "light";
    document.documentElement.dataset.theme = next;
    document.documentElement.style.colorScheme = next;
    $$('[data-action="toggle-theme"]').forEach((button) => {
      button.setAttribute("aria-label", next === "dark" ? "Switch to light mode" : "Switch to dark mode");
    });
    if (persist) {
      globalThis.SenlizDB.setPreference("theme", next).catch((error) => {
        logError("Theme preference could not be saved.", error);
        showToast("Theme preference was not saved.");
      });
    }
    return next;
  }

  async function toggleTheme() {
    const current = document.documentElement.dataset.theme === "dark" ? "dark" : "light";
    const next = setTheme(current === "dark" ? "light" : "dark");
    if (globalThis.SenlizReader.state().isOpen) {
      try {
        await globalThis.SenlizReader.setTheme(next);
      } catch (error) {
        logError("Reader theme could not be changed.", error);
      }
    }
  }

  function releaseCoverUrls() {
    coverUrls.forEach((url) => URL.revokeObjectURL(url));
    coverUrls.clear();
  }

  function coverFor(book) {
    if (!(book.coverBlob instanceof Blob)) return "";
    if (!coverUrls.has(book.id)) coverUrls.set(book.id, URL.createObjectURL(book.coverBlob));
    return coverUrls.get(book.id);
  }

  function percentText(progress) {
    const value = Math.round(Math.max(0, Math.min(1, Number(progress?.percentage || 0))) * 100);
    return `${value}%`;
  }

  function makeCover(book) {
    const cover = document.createElement("div");
    cover.className = "book-cover";
    const url = coverFor(book);
    if (url) {
      const image = document.createElement("img");
      image.src = url;
      image.alt = `Cover of ${book.title}`;
      image.loading = "lazy";
      image.decoding = "async";
      cover.append(image);
      return cover;
    }

    const placeholder = document.createElement("div");
    placeholder.className = "placeholder-cover";
    const label = document.createElement("span");
    label.textContent = "YOUR BOOK";
    const title = document.createElement("strong");
    title.textContent = book.title;
    const author = document.createElement("small");
    author.textContent = book.author;
    placeholder.append(label, title, author);
    cover.append(placeholder);
    return cover;
  }

  function makeImportedCard(book) {
    const article = document.createElement("article");
    article.className = "book-card";
    article.dataset.bookId = book.id;
    article.dataset.searchText = globalThis.SenlizSearch.normalizeForSearch(`${book.title} ${book.author}`);
    article.append(makeCover(book));

    const meta = document.createElement("div");
    meta.className = "book-meta";
    const title = document.createElement("h3");
    title.textContent = book.title;
    const author = document.createElement("p");
    const progress = progressByBook.get(book.id);
    author.textContent = `${book.author}${book.language ? ` · ${book.language}` : ""}`;

    const actions = document.createElement("div");
    actions.className = "book-actions";
    const read = document.createElement("button");
    read.type = "button";
    read.dataset.action = "read-book";
    read.dataset.bookId = book.id;
    read.textContent = progress?.cfi ? `Continue · ${percentText(progress)}` : "Read";
    read.setAttribute("aria-label", `${progress?.cfi ? "Continue reading" : "Read"} ${book.title}`);

    const remove = document.createElement("button");
    remove.type = "button";
    remove.className = "remove-action";
    remove.dataset.action = "remove-book";
    remove.dataset.bookId = book.id;
    remove.textContent = "Remove";
    remove.setAttribute("aria-label", `Remove ${book.title}`);

    actions.append(read, remove);
    meta.append(title, author, actions);
    article.append(meta);
    return article;
  }

  function makeAddCard() {
    const button = document.createElement("button");
    button.className = "add-book-card";
    button.type = "button";
    button.dataset.action = "choose-book";
    const icon = document.createElement("span");
    icon.setAttribute("aria-hidden", "true");
    icon.textContent = "↑";
    const title = document.createElement("strong");
    title.textContent = "Add an EPUB";
    const note = document.createElement("small");
    note.textContent = "Import an EPUB file";
    button.append(icon, title, note);
    return button;
  }

  function renderLibrary() {
    const grid = $("#libraryGrid");
    releaseCoverUrls();
    grid.replaceChildren();

    const query = globalThis.SenlizSearch.normalizeForSearch($("#librarySearch").value);
    const filtered = books.filter((book) => {
      if (!query) return true;
      return globalThis.SenlizSearch.normalizeForSearch(`${book.title} ${book.author}`).includes(query);
    });
    filtered.forEach((book) => grid.append(makeImportedCard(book)));
    if (!query) grid.append(makeAddCard());

    const count = $("#libraryResultCount");
    if (query) {
      count.textContent = `${filtered.length} local ${filtered.length === 1 ? "result" : "results"}`;
    } else {
      count.textContent = `${books.length} imported ${books.length === 1 ? "book" : "books"}`;
    }
    $("#emptyLibrary").hidden = books.length > 0 || Boolean(query);
    grid.hidden = filtered.length === 0 && Boolean(query);
    renderCatalogue(query);
    renderContinue();
  }

  function renderContinue() {
    const candidates = books
      .map((book) => ({ book, progress: progressByBook.get(book.id) }))
      .filter(({ progress }) => progress?.cfi)
      .sort((a, b) => {
        const aDate = a.progress.lastReadTimestamp || a.progress.updatedAt || "";
        const bDate = b.progress.lastReadTimestamp || b.progress.updatedAt || "";
        return bDate.localeCompare(aDate);
      });
    const section = $("#continueSection");
    if (!candidates.length || $("#librarySearch").value) {
      section.hidden = true;
      return;
    }

    const { book, progress } = candidates[0];
    const button = $("#continueBook");
    button.replaceChildren();
    button.dataset.bookId = book.id;
    const cover = coverFor(book);
    if (cover) {
      const image = document.createElement("img");
      image.src = cover;
      image.alt = "";
      button.append(image);
    } else {
      const fallback = document.createElement("span");
      fallback.className = "continue-cover-fallback";
      fallback.setAttribute("aria-hidden", "true");
      fallback.textContent = "S";
      button.append(fallback);
    }
    const copy = document.createElement("span");
    const title = document.createElement("strong");
    title.textContent = book.title;
    const detail = document.createElement("small");
    detail.textContent = `${progress.chapter || "Continue"} · ${percentText(progress)}`;
    copy.append(title, detail);
    const action = document.createElement("span");
    action.textContent = "Continue →";
    action.setAttribute("aria-hidden", "true");
    button.append(copy, action);
    button.setAttribute("aria-label", `Continue reading ${book.title} at ${percentText(progress)}`);
    section.hidden = false;
  }

  function renderCatalogue(query = "") {
    const grid = $("#catalogGrid");
    grid.replaceChildren();
    const normalizedQuery = globalThis.SenlizSearch.normalizeForSearch(query);
    const filtered = CATALOGUE.filter((book) => {
      if (!normalizedQuery) return true;
      return globalThis.SenlizSearch.normalizeForSearch(`${book.title} ${book.author}`).includes(normalizedQuery);
    });
    filtered.forEach((book) => {
      const article = document.createElement("article");
      article.className = "book-card";
      const cover = document.createElement("div");
      cover.className = "book-cover";
      const image = document.createElement("img");
      image.src = book.cover;
      image.alt = `Cover of ${book.title}`;
      image.loading = "lazy";
      cover.append(image);

      const meta = document.createElement("div");
      meta.className = "book-meta";
      const title = document.createElement("h3");
      title.textContent = book.title;
      const author = document.createElement("p");
      author.textContent = book.author;
      const actions = document.createElement("div");
      actions.className = "book-actions";
      const link = document.createElement("a");
      link.href = book.url;
      link.dataset.trustedExternal = "true";
      link.textContent = "View on Amazon";
      link.setAttribute("aria-label", `View ${book.title} on Amazon`);
      actions.append(link);
      meta.append(title, author, actions);
      article.append(cover, meta);
      grid.append(article);
    });
    grid.hidden = filtered.length === 0 && Boolean(normalizedQuery);
    $("#catalogCount").textContent = normalizedQuery
      ? `${filtered.length} catalogue ${filtered.length === 1 ? "result" : "results"}`
      : `${CATALOGUE.length} featured titles`;
    return filtered.length;
  }

  async function reloadLibrary() {
    books = (await globalThis.SenlizDB.getAllBooks()).sort((a, b) => {
      return String(b.lastOpenedDate || b.importedDate || "").localeCompare(String(a.lastOpenedDate || a.importedDate || ""));
    });
    const progress = await globalThis.SenlizDB.getAllProgress();
    progressByBook = new Map(progress.map((item) => [item.bookId, item]));
    renderLibrary();
  }

  async function importFile(file) {
    const name = String(file?.name || "Imported.epub");
    if (!/\.epub$/i.test(name) && file?.type !== globalThis.SenlizSecurity.EPUB_MIME) {
      throw new globalThis.SenlizSecurity.EpubValidationError("unsupported-type", "Unsupported file type.");
    }

    const duplicate = await globalThis.SenlizDB.findByFileName(name);
    if (duplicate) {
      throw new Error(`“${name}” is already in your library. Duplicate EPUB filenames are not imported.`);
    }

    const blob = file instanceof Blob ? file : new Blob([file], { type: globalThis.SenlizSecurity.EPUB_MIME });
    await globalThis.SenlizSecurity.validateEpub(blob);
    const metadata = await globalThis.SenlizReader.inspect(blob, name);
    const record = globalThis.SenlizDB.createBookRecord(blob, {
      ...metadata,
      fileName: name,
      size: blob.size
    });
    await globalThis.SenlizDB.putBook(record);
    return record;
  }

  async function importFiles(fileList) {
    const files = [...(fileList || [])];
    if (!files.length) return;
    let imported = 0;
    const failures = [];

    for (const file of files) {
      try {
        await importFile(file);
        imported += 1;
      } catch (error) {
        logError(`Import failed for ${file?.name || "a selected file"}.`, error);
        failures.push({ name: file?.name || "Selected file", message: errorMessage(error, error?.message || "Import failed.") });
      }
    }

    await reloadLibrary();
    if (imported) {
      const message = `${imported} ${imported === 1 ? "book" : "books"} imported and saved on this device.`;
      showToast(message);
      announce(message);
    }
    if (failures.length) {
      await showDialog({
        title: failures.length === 1 ? "Book not imported" : "Some books were not imported",
        message: failures.map((failure) => `${failure.name}\n${failure.message}`).join("\n\n"),
        cancel: false,
        confirmLabel: "OK"
      });
    }
  }

  async function nativeImportURL(url, name) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error("The selected EPUB could not be read.");
      const blob = await response.blob();
      const safeName = name || decodeURIComponent(new URL(url).pathname.split("/").pop() || "Imported.epub");
      await importFiles([new File([blob], safeName, { type: globalThis.SenlizSecurity.EPUB_MIME })]);
    } catch (error) {
      logError("Native EPUB import failed.", error);
      await showDialog({
        title: "Book not imported",
        message: errorMessage(error, "The selected EPUB could not be imported."),
        cancel: false,
        confirmLabel: "OK"
      });
    }
  }

  async function nativeImportBytes(name, bytes) {
    const data = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes || []);
    return importFiles([new File([data], name || "Imported.epub", { type: globalThis.SenlizSecurity.EPUB_MIME })]);
  }

  function chooseBooks() {
    if (globalThis.SenlizNative?.chooseEpubs) {
      globalThis.SenlizNative.chooseEpubs().then((files) => {
        (files || []).forEach((entry) => {
          if (entry.url) nativeImportURL(entry.url, entry.name);
          else nativeImportBytes(entry.name, entry.bytes);
        });
      }).catch((error) => {
        logError("The native open dialog failed.", error);
        $("#fileInput").click();
      });
      return;
    }
    $("#fileInput").click();
  }

  async function removeBook(id) {
    const book = books.find((item) => item.id === id);
    if (!book) return;
    const confirmed = await showDialog({
      eyebrow: "REMOVE BOOK",
      title: `Remove “${book.title}”?`,
      message: "The EPUB and its reading progress will be removed from this device. This cannot be undone.",
      confirmLabel: "Remove",
      destructive: true
    });
    if (!confirmed) return;
    try {
      await globalThis.SenlizDB.removeBook(id);
      await reloadLibrary();
      showToast("Book removed from this device.");
      announce(`${book.title} removed.`);
    } catch (error) {
      logError("Book removal failed.", error);
      await showDialog({
        title: "Book not removed",
        message: globalThis.SenlizDB.friendlyStorageError(error),
        cancel: false,
        confirmLabel: "OK"
      });
    }
  }

  function setReaderVisible(visible) {
    $("#libraryView").hidden = visible;
    $("#readerView").hidden = !visible;
    document.body.classList.toggle("reading", visible);
    if (!visible) closePanel();
  }

  async function openBook(id) {
    const record = books.find((book) => book.id === id) || await globalThis.SenlizDB.getBook(id);
    if (!record) {
      showToast("This book is no longer available locally.");
      return;
    }
    activeBookId = record.id;
    $("#readerTitle").textContent = record.title;
    $("#readerAuthor").textContent = record.author;
    $("#readerLoading").hidden = false;
    $("#readerError").hidden = true;
    $("#viewer").hidden = false;
    $("#chapterStatus").textContent = "Opening book…";
    $("#progressStatus").textContent = "0%";
    $("#saveStatus").textContent = "Stored on this device";
    setReaderVisible(true);
    $("#readerView").setAttribute("aria-busy", "true");

    try {
      await globalThis.SenlizReader.open(record);
    } catch (error) {
      logError(`Reader failed to open ${record.title}.`, error);
      $("#readerLoading").hidden = true;
      $("#viewer").hidden = true;
      $("#readerError").hidden = false;
      $("#readerError").textContent = errorMessage(error, "This book could not be opened. Try importing the EPUB again.");
      $("#readerView").removeAttribute("aria-busy");
      announce($("#readerError").textContent);
    }
  }

  async function closeReader() {
    try {
      await globalThis.SenlizReader.close();
    } catch (error) {
      logError("Reader cleanup failed.", error);
    }
    activeBookId = "";
    setReaderVisible(false);
    await reloadLibrary();
    $('[data-action="choose-book"]')?.focus();
  }

  function buildToc() {
    const list = $("#tocList");
    list.replaceChildren();
    const items = globalThis.SenlizReader.toc();
    if (!items.length) {
      const empty = document.createElement("p");
      empty.className = "panel-empty";
      empty.textContent = "This EPUB does not include a table of contents.";
      list.append(empty);
      return;
    }
    items.forEach((item) => {
      const button = document.createElement("button");
      button.type = "button";
      button.className = "toc-item";
      button.dataset.tocTarget = item.href;
      button.style.paddingInlineStart = `${2 + Math.min(5, Number(item.depth || 0)) * 18}px`;
      button.textContent = item.label || "Untitled section";
      list.append(button);
    });
  }

  function openPanel(panel) {
    closePanel(false);
    panelReturnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
    $("#panelBackdrop").hidden = false;
    panel.hidden = false;
    document.body.classList.add("panel-open");
    if (panel.id === "contentsPanel") buildToc();
    const focusTarget = panel.id === "searchPanel" ? $("#bookSearch") : $("button, input", panel);
    window.setTimeout(() => focusTarget?.focus(), 0);
  }

  function closePanel(returnFocus = true) {
    $("#panelBackdrop").hidden = true;
    $("#contentsPanel").hidden = true;
    $("#searchPanel").hidden = true;
    document.body.classList.remove("panel-open");
    if (returnFocus) panelReturnFocus?.focus?.();
    panelReturnFocus = null;
  }

  function renderSearchResults(results) {
    const list = $("#searchResults");
    list.replaceChildren();
    selectedSearchIndex = results.length ? 0 : -1;
    results.forEach((result, index) => {
      const item = document.createElement("li");
      const button = document.createElement("button");
      button.type = "button";
      button.className = "search-result";
      button.dataset.searchIndex = String(index);
      button.setAttribute("aria-current", index === selectedSearchIndex ? "true" : "false");
      const chapter = document.createElement("strong");
      chapter.textContent = result.label || `Section ${Number(result.spineIndex || 0) + 1}`;
      const snippet = document.createElement("span");
      snippet.textContent = result.snippet;
      button.append(chapter, snippet);
      item.append(button);
      list.append(item);
    });
    $("#searchCount").textContent = results.length
      ? `${results.length} ${results.length === 1 ? "result" : "results"}`
      : "No matches found.";
  }

  async function performBookSearch(query) {
    searchController?.abort();
    searchController = new AbortController();
    if (globalThis.SenlizSearch.normalizeForSearch(query).length < 2) {
      $("#searchResults").replaceChildren();
      $("#searchCount").textContent = "Enter at least two characters.";
      return;
    }
    $("#searchCount").textContent = "Searching…";
    try {
      const results = await globalThis.SenlizReader.search(query, searchController.signal);
      if (!searchController.signal.aborted) renderSearchResults(results);
    } catch (error) {
      if (error?.name === "AbortError") return;
      logError("Book search failed.", error);
      $("#searchCount").textContent = "Search could not be completed.";
    }
  }

  async function showSearchResult(index) {
    const selected = await globalThis.SenlizReader.showSearchResult(index);
    if (!selected) return;
    selectedSearchIndex = index;
    $$(".search-result").forEach((button, itemIndex) => {
      button.setAttribute("aria-current", itemIndex === index ? "true" : "false");
    });
    $(`.search-result[data-search-index="${index}"]`)?.scrollIntoView({ block: "nearest" });
  }

  async function stepSearch(delta) {
    const selected = await globalThis.SenlizReader.stepSearchResult(delta);
    if (!selected) return;
    selectedSearchIndex = selected.index;
    $$(".search-result").forEach((button, index) => {
      button.setAttribute("aria-current", index === selected.index ? "true" : "false");
    });
    $(`.search-result[data-search-index="${selected.index}"]`)?.scrollIntoView({ block: "nearest" });
  }

  async function confirmExternal(url) {
    let host = "external website";
    try {
      host = new URL(url).hostname.replace(/^www\./, "");
    } catch {
      return;
    }
    const confirmed = await showDialog({
      eyebrow: "EXTERNAL LINK",
      title: `Open ${host}?`,
      message: "This link will leave Senliz Library and open in your system browser.",
      confirmLabel: "Open link"
    });
    if (confirmed) globalThis.SenlizSecurity.openExternal(url);
  }

  function updateFontButtons() {
    const state = globalThis.SenlizReader.state();
    $('[data-action="decrease-font"]').disabled = state.fontSize <= globalThis.SenlizReader.constants.FONT_MIN;
    $('[data-action="increase-font"]').disabled = state.fontSize >= globalThis.SenlizReader.constants.FONT_MAX;
  }

  async function handleAction(button) {
    const action = button.dataset.action;
    if (action === "choose-book") return chooseBooks();
    if (action === "toggle-theme") return toggleTheme();
    if (action === "read-book") return openBook(button.dataset.bookId);
    if (action === "remove-book") return removeBook(button.dataset.bookId);
    if (action === "close-reader") return closeReader();
    if (action === "open-contents") return openPanel($("#contentsPanel"));
    if (action === "open-search") return openPanel($("#searchPanel"));
    if (action === "close-panel") return closePanel();
    if (action === "previous-page") return globalThis.SenlizReader.previousPage();
    if (action === "next-page") return globalThis.SenlizReader.nextPage();
    if (action === "footnote-back") return globalThis.SenlizReader.backFromNote();
    if (action === "decrease-font" || action === "increase-font") {
      try {
        await globalThis.SenlizReader.changeFont(action === "increase-font" ? 1 : -1);
        updateFontButtons();
      } catch (error) {
        logError("Font size could not be changed.", error);
        showToast("Font size preference was not saved.");
      }
      return;
    }
    if (action === "previous-result") return stepSearch(-1);
    if (action === "next-result") return stepSearch(1);
  }

  function setupEvents() {
    document.addEventListener("click", async (event) => {
      const action = event.target.closest("[data-action]");
      if (action) {
        event.preventDefault();
        await handleAction(action);
        return;
      }
      const trusted = event.target.closest("[data-trusted-external], [data-external-link]");
      if (trusted) {
        event.preventDefault();
        globalThis.SenlizSecurity.openExternal(trusted.href);
        return;
      }
      const tocTarget = event.target.closest("[data-toc-target]");
      if (tocTarget) {
        const opened = await globalThis.SenlizReader.displayTarget(tocTarget.dataset.tocTarget);
        if (opened) closePanel();
        return;
      }
      const result = event.target.closest("[data-search-index]");
      if (result) await showSearchResult(Number(result.dataset.searchIndex));
    });

    $("#continueBook").addEventListener("click", () => openBook($("#continueBook").dataset.bookId));
    $("#fileInput").addEventListener("change", async (event) => {
      await importFiles(event.target.files);
      event.target.value = "";
    });
    $("#librarySearch").addEventListener("input", renderLibrary);
    $("#bookSearch").addEventListener("input", (event) => {
      clearTimeout(searchTimer);
      searchTimer = window.setTimeout(() => performBookSearch(event.target.value), 260);
    });
    $("#panelBackdrop").addEventListener("click", () => closePanel());

    document.addEventListener("keydown", (event) => {
      const editable = event.target.matches?.("input, textarea, [contenteditable='true']");
      if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "o" && !editable) {
        event.preventDefault();
        chooseBooks();
      }
      if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "f" && !editable && !$("#readerView").hidden) {
        event.preventDefault();
        openPanel($("#searchPanel"));
      }
      if (!editable && !$("#readerView").hidden && !document.body.classList.contains("panel-open")) {
        if (event.key === "ArrowLeft" || event.key === "PageUp") {
          event.preventDefault();
          globalThis.SenlizReader.previousPage();
        }
        if (event.key === "ArrowRight" || event.key === "PageDown" || event.key === " ") {
          event.preventDefault();
          globalThis.SenlizReader.nextPage();
        }
      }
      if (event.key === "Escape" && !$("#contentsPanel").hidden || event.key === "Escape" && !$("#searchPanel").hidden) {
        event.preventDefault();
        closePanel();
      }
    });

    window.addEventListener("dragenter", (event) => {
      if (![...event.dataTransfer.types].includes("Files")) return;
      event.preventDefault();
      dragDepth += 1;
      $("#dropOverlay").hidden = false;
    });
    window.addEventListener("dragover", (event) => {
      if (![...event.dataTransfer.types].includes("Files")) return;
      event.preventDefault();
      event.dataTransfer.dropEffect = "copy";
    });
    window.addEventListener("dragleave", (event) => {
      if (![...event.dataTransfer.types].includes("Files")) return;
      dragDepth = Math.max(0, dragDepth - 1);
      if (!dragDepth) $("#dropOverlay").hidden = true;
    });
    window.addEventListener("drop", async (event) => {
      event.preventDefault();
      dragDepth = 0;
      $("#dropOverlay").hidden = true;
      await importFiles(event.dataTransfer.files);
    });

    document.addEventListener("senliz-reader-metadata", (event) => {
      $("#readerTitle").textContent = event.detail.title;
      $("#readerAuthor").textContent = event.detail.author;
      buildToc();
    });
    document.addEventListener("senliz-reader-ready", () => {
      $("#readerLoading").hidden = true;
      $("#readerError").hidden = true;
      $("#viewer").hidden = false;
      $("#readerView").removeAttribute("aria-busy");
      $("#viewer").focus();
      updateFontButtons();
      announce(`Reader opened. ${$("#readerTitle").textContent}`);
    });
    document.addEventListener("senliz-reader-error", (event) => {
      $("#readerLoading").hidden = true;
      $("#readerError").hidden = false;
      $("#readerError").textContent = event.detail.message || "This chapter could not be displayed.";
      announce($("#readerError").textContent);
    });
    document.addEventListener("senliz-location", (event) => {
      const percent = Math.round(Number(event.detail.percentage || 0) * 100);
      const pageLabel = event.detail.page && event.detail.pages
        ? ` · Page ${event.detail.page} of ${event.detail.pages}`
        : "";
      $("#chapterStatus").textContent = `${event.detail.chapter}${pageLabel}`;
      $("#progressStatus").textContent = `${percent}%`;
      $('[data-action="previous-page"]').disabled = event.detail.atStart;
      $('[data-action="next-page"]').disabled = event.detail.atEnd;
    });
    document.addEventListener("senliz-save-status", (event) => {
      $("#saveStatus").textContent = event.detail.message;
      if (event.detail.state === "error") announce(event.detail.message);
    });
    document.addEventListener("senliz-note-history", (event) => {
      $("#footnoteBack").hidden = !event.detail.available;
    });
    document.addEventListener("senliz-external-request", (event) => confirmExternal(event.detail.url));
    document.addEventListener("senliz-search-index", (event) => {
      const working = event.detail.state === "working";
      $("#searchProgress").hidden = !working;
      if (working && event.detail.total) {
        $("#searchProgress span:last-child").textContent =
          `Preparing book search… ${event.detail.completed || 0} of ${event.detail.total}`;
      }
      if (event.detail.state === "error") {
        $("#searchProgress").hidden = true;
        $("#searchCount").textContent = "Search index could not be prepared.";
      }
    });

    window.addEventListener("beforeunload", () => {
      globalThis.SenlizReader.close({ keepView: true });
      releaseCoverUrls();
    });
  }

  async function init() {
    setupEvents();
    renderCatalogue();
    try {
      const theme = await globalThis.SenlizDB.getPreference(
        "theme",
        matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
      );
      setTheme(theme, false);
      const migration = await globalThis.SenlizDB.migrateLegacyBooks();
      await reloadLibrary();
      if (migration.failures.length) {
        showToast(`${migration.failures.length} older ${migration.failures.length === 1 ? "book needs" : "books need"} to be imported again.`);
      }
    } catch (error) {
      logError("Local library startup failed.", error);
      $("#libraryGrid").replaceChildren();
      $("#emptyLibrary").hidden = false;
      $("#emptyLibrary p").textContent = "Your local library could not be opened.";
      await showDialog({
        title: "Library unavailable",
        message: "Senliz Library could not open its local database. Restart the app. Your existing files have not been intentionally removed.",
        cancel: false,
        confirmLabel: "OK"
      });
    }

    if (globalThis.SenlizNative?.onOpenFiles) {
      globalThis.SenlizNative.onOpenFiles((entries) => {
        (entries || []).forEach((entry) => {
          if (entry.url) nativeImportURL(entry.url, entry.name);
          else nativeImportBytes(entry.name, entry.bytes);
        });
      });
      globalThis.SenlizNative.ready?.();
    }
    announce("Senliz Library is ready.");
  }

  globalThis.SenlizApp = Object.freeze({
    importFiles,
    importURL: nativeImportURL,
    importBytes: nativeImportBytes,
    chooseBooks,
    openBook,
    reloadLibrary
  });

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init, { once: true });
  } else {
    init();
  }
})();
