(() => {
  const app = document.querySelector("[data-note-app]");
  if (!app) {
    return;
  }

  const noteList = app.querySelector("[data-note-list]");
  const newNoteForm = app.querySelector("[data-new-note-form]");
  const newNoteTitle = app.querySelector("[data-new-note-title]");
  const noteTitleInput = app.querySelector("[data-note-title]");
  const noteTitleMirror = app.querySelector("[data-note-title-mirror]");
  const editor = app.querySelector("[data-content-editor]");
  const canvasPane = app.querySelector(".zn-canvas-pane");
  const paper = app.querySelector("[data-paper]");
  const canvas = app.querySelector("[data-canvas]");
  const ctx = canvas.getContext("2d");
  const inkPreviewCanvas = app.querySelector("[data-ink-preview]");
  const inkPreviewCtx = inkPreviewCanvas.getContext("2d");
  const shapePreviewCanvas = app.querySelector("[data-shape-preview]");
  const shapePreviewCtx = shapePreviewCanvas.getContext("2d");
  const selectionCanvas = app.querySelector("[data-selection-canvas]");
  const selectionCtx = selectionCanvas?.getContext("2d");
  const selectionToolbar = app.querySelector("[data-selection-toolbar]");
  const textLayer = app.querySelector("[data-text-layer]");
  const saveState = app.querySelector("[data-save-state]");
  const topbar = app.querySelector(".zn-topbar");
  const fullscreenButton = app.querySelector("[data-fullscreen-toggle]");
  const fullscreenIcon = app.querySelector("[data-fullscreen-icon]");
  const toolbar = app.querySelector(".zn-toolbar");
  const typographyMenuButton = app.querySelector("[data-typography-menu-button]");
  const typographyPopover = app.querySelector("[data-typography-popover]");
  const fontSizeDropdown = app.querySelector("[data-font-size]");
  const pdfButton = document.getElementById("znPdfButton");
  const pdfInput = document.getElementById("pdfFileInput");
  const pdfIcon = document.getElementById("pdfBtnIcon");
  /*  const pdfInput = app.querySelector("[data-pdf-input]");
   const pdfButton = app.querySelector(".zn-pdf-button"); */
  const clearButton = app.querySelector("[data-clear]");
  const pointerModeButton = app.querySelector("[data-pointer-mode]");
  const toolButtons = app.querySelectorAll("[data-tool]");
  const shapeMenuButton = app.querySelector("[data-shape-menu-button]");
  const shapePopover = app.querySelector("[data-shape-popover]");
  const shapeOptions = app.querySelectorAll("[data-shape-tool]");
  const penToolButtons = app.querySelectorAll("[data-pen-tool-button]");
  const penMenuButton = app.querySelector("[data-pen-menu-button]");
  const penPopover = app.querySelector("[data-pen-popover]");
  const activePenLabel = app.querySelector("[data-active-pen-label]");
  const penGalleryLabel = app.querySelector("[data-pen-gallery-label]");
  const penThicknessButtons = app.querySelectorAll("[data-pen-thickness] [data-size]");
  const penColorButtons = app.querySelectorAll("[data-pen-colors] [data-color]");
  const penTypeButtons = app.querySelectorAll("[data-pen-types] [data-type]");
  const eraserButton = app.querySelector("[data-eraser-button]");
  const eraserMenuButton = app.querySelector("[data-eraser-menu-button]");
  const eraserPopover = app.querySelector("[data-eraser-popover]");
  const eraserOptions = app.querySelectorAll("[data-eraser-mode]");
  const eraserWidthInput = app.querySelector("[data-eraser-width]");
  const ocrMenuButton = app.querySelector("[data-ocr-menu-button]");
  const ocrPopover = app.querySelector("[data-ocr-popover]");
  const ocrFullCanvasButton = app.querySelector("[data-ocr-full-canvas]");
  const ocrSelectAreaButton = app.querySelector("[data-ocr-select-area]");
  const ocrModal = app.querySelector("[data-ocr-modal]");
  const ocrLoading = app.querySelector("[data-ocr-loading]");
  const ocrResult = app.querySelector("[data-ocr-result]");
  const ocrProgress = app.querySelector("[data-ocr-progress]");
  const ocrProgressLabel = app.querySelector("[data-ocr-progress-label]");
  const ocrPreview = app.querySelector("[data-ocr-preview]");
  const ocrText = app.querySelector("[data-ocr-text]");
  const ocrFontSize = app.querySelector("[data-ocr-font-size]");
  const ocrInsertEditorButton = app.querySelector("[data-ocr-insert-editor]");
  const ocrInsertFloatingButton = app.querySelector("[data-ocr-insert-floating]");
  const ocrCopyButton = app.querySelector("[data-ocr-copy]");
  const ocrRetryButton = app.querySelector("[data-ocr-retry]");
  const ocrCancelButton = ocrModal?.querySelector(".cancel");
  const recoveryModal = app.querySelector("[data-recovery-modal]");
  const recoveryRestoreButton = app.querySelector("[data-recovery-restore]");
  const recoveryDiscardButton = app.querySelector("[data-recovery-discard]");
  const commandButtons = app.querySelectorAll("[data-command]");
  const toggleLinesButton = app.querySelector("[data-toggle-lines]");
  const lineSpacingMenuButton = app.querySelector("[data-line-spacing-menu-button]");
  const lineSpacingPopover = app.querySelector("[data-line-spacing-popover]");
  const lineSpacingOptions = app.querySelectorAll("[data-line-spacing]");
  const layoutToggle = app.querySelector("[data-layout-toggle]");
  const expandCanvasButton = app.querySelector("[data-expand-canvas]");
  const textViewButton = app.querySelector("[data-text-view]");
  const editorShell = app.querySelector(".zn-editor");
  const paneResizer = app.querySelector("[data-pane-resizer]");
  const pdfPane = app.querySelector(".zn-pdf-pane");
  const pdfPages = app.querySelector("[data-pdf-pages]");
  const pdfZoomInButton = app.querySelector("[data-pdf-zoom-in]");
  const pdfZoomOutButton = app.querySelector("[data-pdf-zoom-out]");
  const pdfZoomLabel = app.querySelector("[data-pdf-zoom-label]");
  const expandPdfButton = app.querySelector("[data-expand-pdf]");
  const toggleNotesPanelButton = app.querySelector("[data-toggle-notes-panel]");
  const appScope = String(app.dataset.appScope || "CLIENT").toUpperCase() === "SERVER" ? "SERVER" : "CLIENT";
  let pdfPageStates = [];
  let visiblePdfPages = new Set();
  let pdfPageObserver = null;
  let paperExpansionFrame = null;
  let paperRestoreExpansionFrame = null;
  let paneResizeState = null;
  let isPdfLoaded = false;
  const CANVAS_EXPAND_MARGIN = 160;
  const CANVAS_EXPAND_AMOUNT = 500;
  const RESTORE_CONTENT_PADDING = 200;
  const DEFAULT_PAPER_LINE_HEIGHT = 40;
  const PAPER_LINE_HEIGHTS = new Set([32, 40, 48]);
  const state = {
    notes: [],
    activeNote: null,
    tool: "pen",
    drawing: false,
    isPanning: false,
    panStartX: 0,
    panStartY: 0,
    panScrollLeft: 0,
    panScrollTop: 0,
    panScrollElement: null,
    activePointerId: null,
    lastPoint: null,
    lastMidPoint: null,
    currentStroke: null,
    currentStrokeFrame: null,
    drawingSurface: "paper",
    pen: {
      color: "#000000",
      size: 0.5,
      type: "pen"
    },
    penProfiles: {
      pen: { color: "#000000", size: 0.5 },
      highlighter: { color: "#e9ff32", size: 4 }
    },
    eraserMode: "soft",
    eraserWidth: 26,
    inkDataUrl: null,
    inkStrokes: [],
    pdfInkDataUrl: null,
    pdfInkStrokes: [],
    activePdfPageNumber: null,
    clearInkDataUrl: false,
    clearPdfInkDataUrl: false,
    showPaperLines: true,
    paperLineHeight: DEFAULT_PAPER_LINE_HEIGHT,
    lastSaved: null,
    saveTimer: null,
    loading: false,
    recognizing: false,
    ocrSelection: null,
    ocrLastRequest: null,
    ocrResult: null,
    ocrFontSize: "16px",
    pdfZoom: 1,
    pdfDocument: null,
    activeTextRoot: null,
    savedTextRange: null,
    isOnline: navigator.onLine,
    syncing: false,
    hasUnsyncedChanges: false,
    lastSyncedAt: null,
    pendingRecovery: null,
    selectedStrokeIds: new Set(),
  };
  const toolPalettePopups = new Map();
  let selectionManager = null;
  let selectionModulePromise = null;

  const penSettingsKey = "zennote.pen.profiles";
  const legacyPenSettingsKey = "zennote.pen.settings";
  const paneWidthKey = "zennote.layout.canvasPaneWidth";
  const penTypes = ["pen", "highlighter"];
  const defaultPenProfiles = () => ({
    pen: { color: "#000000", size: 0.5 },
    highlighter: { color: "#e9ff32", size: 4 }
  });
  const penSizeToPixels = new Map([
    [0.25, 1.5],
    [0.35, 2],
    [0.5, 3],
    [0.7, 4],
    [1, 6],
    [2, 10],
    [3, 14],
    [4, 18]
  ]);
  const penColorNames = new Map([
    ["#000000", "Black"],
    ["#f44336", "Red"],
    ["#ff9800", "Orange"],
    ["#ffc107", "Amber"],
    ["#4caf50", "Green"],
    ["#3f51b5", "Indigo"],
    ["#9c27b0", "Purple"],
    ["#795548", "Brown"],
    ["#e9ff32", "Yellow"],
    ["#8cff32", "Green"]
  ]);
  const apiBase = app.dataset.apiNotesUrl || new URL("api/notes", document.baseURI).toString();
  const apiFetch = (url, options = {}) => fetch(url, {
    credentials: "same-origin",
    ...options,
    headers: {
      ...(options.headers || {})
    }
  });
  const apiError = async (response, fallbackMessage) => {
    const details = `${response.status} ${response.statusText}`.trim();
    let body = "";

    try {
      body = (await response.text()).trim();
    } catch {
      body = "";
    }

    const message = body
      ? `${fallbackMessage} (${details}): ${body.slice(0, 200)}`
      : `${fallbackMessage} (${details})`;

    return new Error(message);
  };

  const api = {
    async list() {
      //const response = await fetch("/api/notes");
      const response = await apiFetch(apiBase);
      if (!response.ok) {
        throw new Error("Unable to load notes");
      }
      return response.json();
    },
    async get(id) {
      //const response = await fetch(`/api/notes/${id}`);
      const response = await apiFetch(`${apiBase}/${id}`);
      if (!response.ok) {
        throw new Error("Unable to load note");
      }
      return response.json();
    },
    async create(title) {
      //const response = await fetch("/api/notes", {
      const response = await apiFetch(apiBase, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ title })
      });
      if (!response.ok) {
        throw new Error("Unable to create note");
      }
      return response.json();
    },
    
    async update(note) {
      //const response = await fetch(`/api/notes/${note.id}`, {
     const response = await apiFetch(`${apiBase}/${note.id}`, {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(note)
      });
      if (!response.ok) {
        throw await apiError(response, "Unable to save note");
      }
      return response.json();
    },
    async delete(id) {
      //const response = await fetch(`/api/notes/${id}`, {
      const response = await apiFetch(`${apiBase}/${id}`, {
        method: "DELETE"
      });
      if (!response.ok) {
        throw new Error("Unable to delete note");
      }
    }
  };
 
  const offlineStore = (() => {
    const draftKey = "zennote.offline.drafts";
    const queueKey = "zennote.offline.pendingUpdates";
    const ocrKey = "zennote.offline.ocrText";
    const dbName = "ZenNoteOffline";
    const dbVersion = 1;
    const drawingStoreName = "drawings";
    let dbPromise = null;

    const readJson = (key, fallback) => {
      try {
        const value = window.localStorage.getItem(key);
        return value ? JSON.parse(value) : fallback;
      } catch {
        return fallback;
      }
    };

    const writeJson = (key, value) => {
      try {
        window.localStorage.setItem(key, JSON.stringify(value));
      } catch {
        // Local saves are best-effort; IndexedDB still protects larger drawing data.
      }
    };

    const openDb = () => {
      if (!("indexedDB" in window)) {
        return Promise.resolve(null);
      }

      if (!dbPromise) {
        dbPromise = new Promise((resolve, reject) => {
          const request = window.indexedDB.open(dbName, dbVersion);
          request.onupgradeneeded = () => {
            const db = request.result;
            if (!db.objectStoreNames.contains(drawingStoreName)) {
              db.createObjectStore(drawingStoreName, { keyPath: "noteId" });
            }
          };
          request.onsuccess = () => resolve(request.result);
          request.onerror = () => reject(request.error);
        }).catch(() => null);
      }

      return dbPromise;
    };

    const withDrawingStore = async (mode, action) => {
      const db = await openDb();
      if (!db) {
        return null;
      }

      return new Promise((resolve, reject) => {
        const transaction = db.transaction(drawingStoreName, mode);
        const store = transaction.objectStore(drawingStoreName);
        const request = action(store);
        request.onsuccess = () => resolve(request.result ?? null);
        request.onerror = () => reject(request.error);
      }).catch(() => null);
    };

    const getDrafts = () => readJson(draftKey, {});
    const setDrafts = (drafts) => writeJson(draftKey, drafts);
    const getQueue = () => readJson(queueKey, []);
    const setQueue = (queue) => writeJson(queueKey, queue);

    const saveDrawing = async (noteId, payload, timestamp) => {
      await withDrawingStore("readwrite", (store) => store.put({
        noteId,
        timestamp,
        inkStrokesJson: payload.inkStrokesJson ?? null,
        pdfInkStrokesJson: payload.pdfInkStrokesJson ?? null
      }));
    };

    const getDrawing = async (noteId) => {
      return await withDrawingStore("readonly", (store) => store.get(noteId));
    };

    const removeDrawing = async (noteId) => {
      await withDrawingStore("readwrite", (store) => store.delete(noteId));
    };

    return {
      async saveDraft(noteId, payload, timestamp = Date.now()) {
        const drafts = getDrafts();
        drafts[noteId] = {
          noteId,
          timestamp,
          title: payload.title,
          contentHtml: payload.contentHtml,
          textLayerHtml: payload.textLayerHtml,
          showPaperLines: payload.showPaperLines,
          paperLineHeight: payload.paperLineHeight
        };
        setDrafts(drafts);
        await saveDrawing(noteId, payload, timestamp);
        return drafts[noteId];
      },
      async getDraft(noteId) {
        const draft = getDrafts()[noteId];
        if (!draft) {
          return null;
        }

        const drawing = await getDrawing(noteId);
        return {
          ...draft,
          inkStrokesJson: drawing?.inkStrokesJson ?? null,
          pdfInkStrokesJson: drawing?.pdfInkStrokesJson ?? null
        };
      },
      listDrafts() {
        return Object.values(getDrafts());
      },
      async removeDraft(noteId) {
        const drafts = getDrafts();
        delete drafts[noteId];
        setDrafts(drafts);
        await removeDrawing(noteId);
      },
      async enqueue(noteId, payload, timestamp = Date.now()) {
        const {
          inkStrokesJson,
          pdfInkStrokesJson,
          ...lightPayload
        } = payload;
        const queue = getQueue().filter((item) => item.noteId !== noteId);
        queue.push({ noteId, timestamp, payload: lightPayload });
        queue.sort((a, b) => a.timestamp - b.timestamp);
        setQueue(queue);
        await saveDrawing(noteId, { inkStrokesJson, pdfInkStrokesJson }, timestamp);
      },
      getQueue,
      setQueue,
      async getQueuedPayload(item) {
        const drawing = await getDrawing(item.noteId);
        return {
          ...item.payload,
          ...(drawing ? {
            inkStrokesJson: drawing.inkStrokesJson ?? null,
            pdfInkStrokesJson: drawing.pdfInkStrokesJson ?? null
          } : {})
        };
      },
      async removeQueueItem(noteId) {
        setQueue(getQueue().filter((item) => item.noteId !== noteId));
      },
      async clearNote(noteId) {
        await this.removeDraft(noteId);
        await this.removeQueueItem(noteId);
      },
      hasUnsyncedChanges(noteId = null) {
        if (noteId === null || noteId === undefined) {
          return getQueue().length > 0 || Object.keys(getDrafts()).length > 0;
        }

        const key = String(noteId);
        return getQueue().some((item) => String(item.noteId) === key)
          || Object.prototype.hasOwnProperty.call(getDrafts(), key);
      },
      setOcrText(text) {
        if (text) {
          window.localStorage.setItem(ocrKey, text);
        } else {
          window.localStorage.removeItem(ocrKey);
        }
      },
      getOcrText() {
        return window.localStorage.getItem(ocrKey) || "";
      },
      clearOcrText() {
        window.localStorage.removeItem(ocrKey);
      }
    };
  })();
  const expandPaperIfNeeded = (event) => {
    if (paperExpansionFrame) {
      return false;
    }

    let expanded = false;

    const rect = paper.getBoundingClientRect();
    const currentHeight = paper.scrollHeight;
    const currentWidth = paper.scrollWidth;
    const scrollLeft = canvasPane.scrollLeft;
    const scrollTop = canvasPane.scrollTop;

    if (event.clientY > rect.bottom - CANVAS_EXPAND_MARGIN) {
      paper.style.minHeight = `${currentHeight + CANVAS_EXPAND_AMOUNT}px`;
      expanded = true;
    }

    if (event.clientX > rect.right - CANVAS_EXPAND_MARGIN) {
      paper.style.width = `${currentWidth + CANVAS_EXPAND_AMOUNT}px`;
      expanded = true;
    }

    if (expanded) {
      resizeCanvas();
      canvasPane.scrollLeft = scrollLeft;
      canvasPane.scrollTop = scrollTop;
      paperExpansionFrame = window.requestAnimationFrame(() => {
        canvasPane.scrollLeft = scrollLeft;
        canvasPane.scrollTop = scrollTop;
        paperExpansionFrame = null;
      });
    }

    return expanded;
  };
  const getSaveStateIconClass = (syncState) => {
    if (syncState === "syncing") {
      return "fas fa-sync fa-spin";
    }
    if (syncState === "saved") {
      return "fas fa-save";
    }
    return "fas fa-exclamation-circle";
  };

  const createCloudSaveIcon = () => {
    const wrapper = document.createElement("span");
    wrapper.className = "zn-cloud-status";
    wrapper.setAttribute("aria-hidden", "true");

    const cloud = document.createElement("i");
    cloud.className = "fas fa-cloud";
    wrapper.appendChild(cloud);

    const badge = document.createElement("span");
    badge.className = "status-badge";
    const check = document.createElement("i");
    check.className = "fas fa-check";
    badge.appendChild(check);
    wrapper.appendChild(badge);

    return wrapper;
  };

  const setSaveState = (message, syncState = "saved") => {
    saveState.replaceChildren();
    const icon = syncState === "saved" && appScope === "SERVER"
      ? createCloudSaveIcon()
      : document.createElement("i");
    if (icon.tagName === "I") {
      icon.className = getSaveStateIconClass(syncState);
      icon.setAttribute("aria-hidden", "true");
    }
    saveState.appendChild(icon);

    const text = document.createElement("span");
    text.className = "zn-save-state-text";
    text.textContent = message;
    saveState.appendChild(text);

    saveState.dataset.syncState = syncState;
    saveState.setAttribute("title", message);
    saveState.setAttribute("aria-label", message);
  };

  const updateFullscreenUi = () => {
    if (!fullscreenButton || !fullscreenIcon) {
      return;
    }

    const isFullscreen = Boolean(document.fullscreenElement);
    fullscreenButton.classList.toggle("active", isFullscreen);
    fullscreenButton.setAttribute("aria-label", isFullscreen ? "Exit fullscreen" : "Enter fullscreen");
    fullscreenButton.setAttribute("title", isFullscreen ? "Exit fullscreen" : "Enter fullscreen");
    fullscreenIcon.className = isFullscreen ? "fa-solid fa-compress" : "fa-solid fa-maximize";
  };

  const toggleFullscreen = async () => {
    if (!document.fullscreenEnabled && !document.fullscreenElement) {
      return;
    }

    try {
      if (document.fullscreenElement) {
        await document.exitFullscreen();
      } else {
        await document.documentElement.requestFullscreen();
      }
    } finally {
      updateFullscreenUi();
    }
  };

  const formatRelativeTime = (date) => {
    const seconds = Math.max(0, Math.round((Date.now() - date.getTime()) / 1000));
    if (seconds < 60) {
      return "just now";
    }
    const minutes = Math.round(seconds / 60);
    if (minutes < 60) {
      return `${minutes} min ago`;
    }
    const hours = Math.round(minutes / 60);
    return `${hours} hr ago`;
  };

  const refreshUnsyncedState = () => {
    state.hasUnsyncedChanges = offlineStore.hasUnsyncedChanges(state.activeNote?.id);
    return state.hasUnsyncedChanges;
  };

  const updateSaveIndicator = () => {
    refreshUnsyncedState();
    if (state.syncing) {
      setSaveState("Syncing notes...", "syncing");
    } else if (!state.isOnline || state.hasUnsyncedChanges) {
      setSaveState("Saved locally - waiting to sync", "offline");
    } else {
      setSaveState("Saved", "saved");
    }
    return;

    refreshUnsyncedState();
    if (state.syncing) {
      setSaveState("🟡 Syncing notes…", "syncing");
    } else if (!state.isOnline || state.hasUnsyncedChanges) {
      setSaveState("🔴 Offline • Saving locally", "offline");
    } else {
      setSaveState("🟢 Saved", "saved");
    }
  };

  const loadScript = (src) => new Promise((resolve, reject) => {
    const existing = document.querySelector(`script[src="${src}"]`);
    if (existing) {
      existing.addEventListener("load", resolve, { once: true });
      existing.addEventListener("error", reject, { once: true });
      if (existing.dataset.loaded === "true") {
        resolve();
      }
      return;
    }

    const script = document.createElement("script");
    script.src = src;
    script.async = true;
    script.onload = () => {
      script.dataset.loaded = "true";
      resolve();
    };
    script.onerror = reject;
    document.head.appendChild(script);
  });

  const ensurePdfJs = async () => {
    /* const pdfScript = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js";
    const workerScript = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js"; */
const pdfScript = "js/pdf.min.js";
    const workerScript = "js/pdf.worker.min.js";
    if (!window.pdfjsLib) {
      setSaveState("Loading PDF engine...");
      await loadScript(pdfScript);
    }

    if (!window.pdfjsLib) {
      throw new Error("PDF engine unavailable");
    }

    window.pdfjsLib.GlobalWorkerOptions.workerSrc = workerScript;
    return window.pdfjsLib;
  };

  const openToolbar = () => app.classList.add("toolbar-open");
  const closeToolbar = () => {
    if (!typographyPopover.classList.contains("open") && !shapePopover.classList.contains("open") && !penPopover.classList.contains("open") && !eraserPopover.classList.contains("open") && !ocrPopover?.classList.contains("open")) {
      app.classList.remove("toolbar-open");
    }
  };
  const syncToolbarOpen = () => {
    app.classList.toggle(
      "toolbar-open",
      typographyPopover.classList.contains("open") || shapePopover.classList.contains("open") || penPopover.classList.contains("open") || eraserPopover.classList.contains("open") || ocrPopover?.classList.contains("open"));
  };

  const updateShapeUi = () => {
    const active = isShapeTool(state.tool);
    shapeMenuButton.classList.toggle("active", active || shapePopover.classList.contains("open"));
    shapeMenuButton.classList.toggle("violet", active || shapePopover.classList.contains("open"));
    shapeOptions.forEach((button) => {
      button.classList.toggle("active", button.dataset.shapeTool === state.tool);
    });
  };

  const clamp = (value, min, max) => Math.min(Math.max(value, min), max);

  const isSplitPaneResizable = () => editorShell.classList.contains("pdf-mode") && !editorShell.classList.contains("pdf-only");

  const applyStoredPaneWidth = () => {
    let width = 0;
    try {
      width = Number(window.localStorage.getItem(paneWidthKey));
    } catch {
      width = 0;
    }

    if (Number.isFinite(width) && width > 0) {
      editorShell.style.setProperty("--zn-canvas-pane-width", `${width}px`);
    }
  };

  const redrawAfterPaneResize = () => {
    window.requestAnimationFrame(() => {
      resizeCanvas();
      renderPdfInk();
      applyPdfZoom();
    });
  };

  const setCanvasPaneWidth = (width) => {
    const editorRect = editorShell.getBoundingClientRect();
    const resizerWidth = paneResizer?.offsetWidth || 8;
    const minPaneWidth = 320;
    const maxPaneWidth = Math.max(minPaneWidth, editorRect.width - resizerWidth - minPaneWidth);
    const nextWidth = Math.round(clamp(width, minPaneWidth, maxPaneWidth));

    editorShell.style.setProperty("--zn-canvas-pane-width", `${nextWidth}px`);
    try {
      window.localStorage.setItem(paneWidthKey, String(nextWidth));
    } catch {
      // Layout preference persistence is best-effort.
    }
    redrawAfterPaneResize();
  };

  const startPaneResize = (event) => {
    if (!isSplitPaneResizable()) {
      return;
    }

    event.preventDefault();
    paneResizeState = {
      pointerId: event.pointerId,
      startX: event.clientX,
      startWidth: canvasPane.getBoundingClientRect().width
    };
    editorShell.classList.add("resizing-panes");
    paneResizer.setPointerCapture(event.pointerId);
  };

  const continuePaneResize = (event) => {
    if (!paneResizeState || event.pointerId !== paneResizeState.pointerId) {
      return;
    }

    event.preventDefault();
    setCanvasPaneWidth(paneResizeState.startWidth + event.clientX - paneResizeState.startX);
  };

  const stopPaneResize = (event) => {
    if (!paneResizeState || event.pointerId !== paneResizeState.pointerId) {
      return;
    }

    if (paneResizer.hasPointerCapture(event.pointerId)) {
      paneResizer.releasePointerCapture(event.pointerId);
    }
    paneResizeState = null;
    editorShell.classList.remove("resizing-panes");
    redrawAfterPaneResize();
  };

  const clearToolPopoverPosition = (popover) => {
    if (!popover) {
      return;
    }

    popover.style.left = "";
    popover.style.top = "";
  };

  const closeToolPopover = (popover) => {
    if (!popover) {
      return;
    }

    popover.classList.remove("open");
    clearToolPopoverPosition(popover);
  };

  const createToolPalettePopup = ({ name, trigger, popup, onShow, onHide }) => {
    if (!window.jQuery || !trigger || !popup) {
      return null;
    }

    const getPalettePosition = () => {
      const triggerRect = trigger.getBoundingClientRect();
      const spaceBelow = window.innerHeight - triggerRect.bottom;
      const spaceAbove = triggerRect.top;
      const desiredHeight = Math.min(popup.scrollHeight || 360, window.innerHeight - 24);

      return spaceBelow >= desiredHeight || spaceBelow > spaceAbove
        ? "bottom center"
        : "top center";
    };

    const fitPaletteToViewport = () => {
      popup.style.maxWidth = `${Math.max(220, window.innerWidth - 24)}px`;
      popup.style.maxHeight = `${Math.max(220, window.innerHeight - 76)}px`;
      if (window.innerWidth < 700) {
        popup.style.width = `${Math.max(280, window.innerWidth - 24)}px`;
      } else {
        popup.style.width = "";
      }
    };

    const api = {
      show() {
        fitPaletteToViewport();
        $(trigger).popup("setting", "position", getPalettePosition());
        $(trigger).popup("show");
      },
      hide() {
        $(trigger).popup("hide");
      },
      forceHide() {
        $(trigger).popup("hide");
        popup.classList.remove("open", "visible", "animating", "transition");
        popup.style.display = "";
        trigger.setAttribute("aria-expanded", "false");
        onHide?.();
      },
      toggle() {
        if (popup.classList.contains("open")) {
          api.hide();
        } else {
          api.show();
        }
      },
      refresh() {
        fitPaletteToViewport();
        $(trigger).popup("setting", "position", getPalettePosition());
        $(trigger).popup("refresh");
      }
    };

    $(trigger).popup({
      popup: $(popup),
      on: "manual",
      position: getPalettePosition(),
      context: "body",
      boundary: window,
      inline: false,
      movePopup: true,
      preserve: true,
      hoverable: true,
      closable: true,
      exclusive: true,
      distanceAway: 8,
      transition: "fade down",
      duration: 140,
      onShow() {
        popup.classList.add("open");
        trigger.setAttribute("aria-expanded", "true");
        onShow?.();
      },
      onHide() {
        popup.classList.remove("open");
        trigger.setAttribute("aria-expanded", "false");
        onHide?.();
      }
    });

    toolPalettePopups.set(name, api);
    return api;
  };

  const hideToolPalettePopup = (name) => {
    const popup = toolPalettePopups.get(name);
    if (popup) {
      popup.forceHide ? popup.forceHide() : popup.hide();
      return true;
    }
    return false;
  };

  const portalToolPopovers = () => {
    [typographyPopover, shapePopover, penPopover, eraserPopover, lineSpacingPopover, ocrPopover].filter(Boolean).forEach((popover) => {
      if (popover.parentElement !== document.body) {
        document.body.appendChild(popover);
      }
    });
  };

  const normalizeToolbarTooltips = () => {
    toolbar.querySelectorAll(".button[data-tooltip]").forEach((button) => {
      const tooltip = button.getAttribute("data-tooltip");

      if (!tooltip) {
        return;
      }

      button.setAttribute("data-content", tooltip);
      if (!button.getAttribute("aria-label")) {
        button.setAttribute("aria-label", tooltip);
      }
      button.removeAttribute("data-tooltip");
    });
  };


  const positionToolPopover = (button, popover) => {
    if (!button || !popover || !popover.classList.contains("open")) {
      return;
    }

    window.requestAnimationFrame(() => {
      if (!popover.classList.contains("open")) {
        return;
      }

      const gap = 10;
      const margin = 12;
      const buttonRect = button.getBoundingClientRect();
      const popoverRect = popover.getBoundingClientRect();
      const maxLeft = Math.max(margin, window.innerWidth - popoverRect.width - margin);
      const left = clamp(buttonRect.left + (buttonRect.width / 2) - (popoverRect.width / 2), margin, maxLeft);
      let top = buttonRect.top - popoverRect.height - gap;

      if (top < margin) {
        top = clamp(buttonRect.bottom + gap, margin, window.innerHeight - popoverRect.height - margin);
      }

      popover.style.left = `${left}px`;
      popover.style.top = `${top}px`;
    });
  };

  const positionOpenToolPopover = () => {
    if (typographyPopover.classList.contains("open")) {
      positionToolPopover(typographyMenuButton, typographyPopover);
    }
    if (shapePopover.classList.contains("open")) {
      positionToolPopover(shapeMenuButton, shapePopover);
    }
    if (penPopover.classList.contains("open")) {
      const popup = toolPalettePopups.get("pen");
      if (popup) {
        popup.refresh();
      } else {
        positionToolPopover(penMenuButton, penPopover);
      }
    }
    if (eraserPopover.classList.contains("open")) {
      positionToolPopover(eraserMenuButton, eraserPopover);
    }
    if (lineSpacingPopover.classList.contains("open")) {
      positionToolPopover(lineSpacingMenuButton, lineSpacingPopover);
    }
    if (ocrPopover?.classList.contains("open")) {
      positionToolPopover(ocrMenuButton, ocrPopover);
    }
  };

  const setTypographyPopoverOpen = (open) => {
    typographyPopover.classList.toggle("open", open);
    typographyMenuButton.classList.toggle("active", open);
    typographyMenuButton.classList.toggle("violet", open);
    if (open) {
      setShapePopoverOpen(false);
      hideToolPalettePopup("pen") || closeToolPopover(penPopover);
      updatePenUi();
      closeToolPopover(eraserPopover);
      closeToolPopover(lineSpacingPopover);
      lineSpacingMenuButton.setAttribute("aria-expanded", "false");
      setOcrPopoverOpen(false);
      positionToolPopover(typographyMenuButton, typographyPopover);
    } else {
      clearToolPopoverPosition(typographyPopover);
    }
    syncToolbarOpen();
    updateEraserUi();
  };

  const setShapePopoverOpen = (open) => {
    shapePopover.classList.toggle("open", open);
    if (open) {
      closeToolPopover(typographyPopover);
      typographyMenuButton.classList.remove("active", "violet");
      hideToolPalettePopup("pen") || closeToolPopover(penPopover);
      updatePenUi();
      closeToolPopover(eraserPopover);
      closeToolPopover(lineSpacingPopover);
      lineSpacingMenuButton.setAttribute("aria-expanded", "false");
      setOcrPopoverOpen(false);
      positionToolPopover(shapeMenuButton, shapePopover);
    } else {
      clearToolPopoverPosition(shapePopover);
    }
    syncToolbarOpen();
    updateShapeUi();
    updateEraserUi();
  };

  const setPenPopoverOpen = (open) => {
    if (open) {
      setTypographyPopoverOpen(false);
      setShapePopoverOpen(false);
      setEraserPopoverOpen(false);
      setLineSpacingPopoverOpen(false);
      setOcrPopoverOpen(false);
      const popup = toolPalettePopups.get("pen");
      if (popup) {
        popup.show();
      } else {
        penPopover.classList.add("open");
        positionToolPopover(penMenuButton, penPopover);
      }
    } else {
      hideToolPalettePopup("pen") || closeToolPopover(penPopover);
    }
    syncToolbarOpen();
    updatePenUi();
  };

  const setEraserPopoverOpen = (open) => {
    eraserPopover.classList.toggle("open", open);
    if (open) {
      setTypographyPopoverOpen(false);
      setShapePopoverOpen(false);
      hideToolPalettePopup("pen") || closeToolPopover(penPopover);
      updatePenUi();
      closeToolPopover(lineSpacingPopover);
      lineSpacingMenuButton.setAttribute("aria-expanded", "false");
      setOcrPopoverOpen(false);
      positionToolPopover(eraserMenuButton, eraserPopover);
    } else {
      clearToolPopoverPosition(eraserPopover);
    }
    syncToolbarOpen();
    updateEraserUi();
  };

  const setLineSpacingPopoverOpen = (open) => {
    lineSpacingPopover.classList.toggle("open", open);
    lineSpacingMenuButton.classList.toggle("active", open);
    lineSpacingMenuButton.classList.toggle("violet", open);
    lineSpacingMenuButton.setAttribute("aria-expanded", String(open));
    if (open) {
      setTypographyPopoverOpen(false);
      setShapePopoverOpen(false);
      hideToolPalettePopup("pen") || closeToolPopover(penPopover);
      updatePenUi();
      closeToolPopover(eraserPopover);
      setOcrPopoverOpen(false);
      positionToolPopover(lineSpacingMenuButton, lineSpacingPopover);
    } else {
      clearToolPopoverPosition(lineSpacingPopover);
    }
    syncToolbarOpen();
    updateEraserUi();
  };

  const setOcrPopoverOpen = (open) => {
    if (!ocrPopover || !ocrMenuButton) {
      return;
    }

    ocrPopover.classList.toggle("open", open);
    ocrMenuButton.classList.toggle("active", open || state.tool === "ocr-select");
    ocrMenuButton.classList.toggle("violet", open || state.tool === "ocr-select");
    if (open) {
      setTypographyPopoverOpen(false);
      setShapePopoverOpen(false);
      hideToolPalettePopup("pen") || closeToolPopover(penPopover);
      updatePenUi();
      closeToolPopover(eraserPopover);
      closeToolPopover(lineSpacingPopover);
      lineSpacingMenuButton.setAttribute("aria-expanded", "false");
      positionToolPopover(ocrMenuButton, ocrPopover);
    } else {
      clearToolPopoverPosition(ocrPopover);
    }
    syncToolbarOpen();
    updateEraserUi();
  };

  const updateDrawingCursor = () => {
    app.classList.toggle("pan-cursor", state.tool === "pan");
    app.classList.toggle("grabbing", state.isPanning);
    app.classList.toggle("eraser-cursor", state.tool === "eraser");
    app.classList.toggle("text-cursor", state.tool === "text");
    app.classList.toggle("highlighter-cursor", state.tool === "pen" && state.pen.type === "highlighter");
    app.classList.toggle("ocr-select-cursor", state.tool === "ocr-select");
    app.classList.toggle("lasso-cursor", state.tool === "lasso");
    app.classList.toggle("selection-move-cursor", state.tool === "lasso" && selectionManager?.hasSelection?.());
    app.classList.toggle("pen-cursor", state.tool !== "pan" && state.tool !== "eraser" && state.tool !== "text" && state.tool !== "ocr-select" && state.tool !== "lasso" && state.pen.type !== "highlighter");
  };

  const syncPenViewFromProfiles = () => {
    const profile = getActivePenProfile(state.pen.type);
    state.pen = { type: state.pen.type, color: profile.color, size: profile.size };
  };

  const normalizePenProfile = (profile, fallback) => {
    const fallbackProfile = fallback || { color: "#000000", size: 0.5 };
    const size = Number(profile?.size);
    const color = typeof profile?.color === "string" ? profile.color.toLowerCase() : fallbackProfile.color;

    return {
      color: /^#[0-9a-f]{6}$/i.test(color) ? color : fallbackProfile.color,
      size: penSizeToPixels.has(size) ? size : fallbackProfile.size
    };
  };

  const normalizePenProfiles = (profiles) => {
    const defaults = defaultPenProfiles();
    const source = profiles && typeof profiles === "object" ? profiles : {};

    return {
      pen: normalizePenProfile(source.pen, defaults.pen),
      highlighter: normalizePenProfile(source.highlighter, defaults.highlighter)
    };
  };

  const formatPenSize = (size) => {
    const value = Number(size);
    return `${value.toFixed(2).replace(/\.?0+$/, "")} mm`;
  };

  const getPenColorName = (color) => penColorNames.get(String(color || "").toLowerCase()) || "Custom";

  const getPenTypeLabel = (type) => {
    if (type === "highlighter") {
      return "Highlighter";
    }
    return "Pen";
  };

  const getActivePenProfile = (type = state.pen.type) => state.penProfiles[type] || state.penProfiles.pen;

  const getActivePenLabel = () => {
    const type = state.pen.type;
    const profile = getActivePenProfile(type);
    if (type === "highlighter") {
      return `${getPenColorName(profile.color)} ${getPenTypeLabel(type)}`;
    }
    return `${getPenColorName(profile.color)} ${getPenTypeLabel(type)}, ${formatPenSize(profile.size)}`;
  };

  const savePenSettings = () => {
    try {
      window.localStorage.setItem(penSettingsKey, JSON.stringify(state.penProfiles));
    } catch {
      // Pen settings persistence is best-effort and should never block drawing.
    }
  };

  const restorePenSettings = () => {
    try {
      const stored = window.localStorage.getItem(penSettingsKey);
      if (stored) {
        state.penProfiles = normalizePenProfiles(JSON.parse(stored));
        syncPenViewFromProfiles();
        return;
      }
      const legacy = window.localStorage.getItem(legacyPenSettingsKey);
      if (legacy) {
        const parsed = JSON.parse(legacy);
        const legacyType = penTypes.includes(parsed?.type) ? parsed.type : "pen";
        const migrated = defaultPenProfiles();
        migrated[legacyType] = normalizePenProfile(parsed, migrated[legacyType]);
        state.penProfiles = normalizePenProfiles(migrated);
        try {
          window.localStorage.setItem(penSettingsKey, JSON.stringify(state.penProfiles));
          window.localStorage.removeItem(legacyPenSettingsKey);
        } catch {
          // best-effort migration
        }
        syncPenViewFromProfiles();
        return;
      }
      state.penProfiles = normalizePenProfiles(state.penProfiles);
      syncPenViewFromProfiles();
    } catch {
      state.penProfiles = normalizePenProfiles(state.penProfiles);
      syncPenViewFromProfiles();
    }
  };

  const updatePenUi = () => {
    const label = getActivePenLabel();
    if (activePenLabel) {
      activePenLabel.textContent = label;
    }
    if (penGalleryLabel) {
      penGalleryLabel.textContent = label;
    }

    const activeProfile = getActivePenProfile(state.pen.type);
    const activePenIcon = app.querySelector("[data-active-pen-icon]");
    if (activePenIcon) {
      const iconClass = state.pen.type === "highlighter" ? "fa-highlighter" : "fa-pen";
      activePenIcon.className = `fa-solid ${iconClass} zn-active-pen-icon zn-active-pen-icon-${state.pen.type}`;
      activePenIcon.style.color = activeProfile.color;
    }
    penToolButtons.forEach((button) => {
      const active = state.tool === "pen";
      button.classList.toggle("active", active);
      button.classList.toggle("violet", active);
      button.setAttribute("aria-label", `Activate ${label}`);
      button.setAttribute("data-content", label);
    });
    penMenuButton.classList.toggle("active", penPopover.classList.contains("open"));
    penMenuButton.classList.toggle("violet", penPopover.classList.contains("open"));
    penThicknessButtons.forEach((button) => {
      button.classList.toggle("active", state.pen.type === "pen" && Number(button.dataset.size) === activeProfile.size);
    });
    penColorButtons.forEach((button) => {
      button.classList.toggle("active", state.pen.type === "pen" && button.dataset.color.toLowerCase() === activeProfile.color);
    });
    penTypeButtons.forEach((button) => {
      const matches = button.dataset.type === state.pen.type
        && button.dataset.color.toLowerCase() === activeProfile.color;
      button.classList.toggle("active", matches);
      button.setAttribute("aria-pressed", String(matches));
    });
  };

  const updateEraserUi = () => {
    const active = state.tool === "eraser";
    eraserButton.classList.toggle("active", active);
    eraserButton.classList.toggle("violet", active);
    eraserMenuButton.classList.toggle("active", eraserPopover.classList.contains("open"));
    eraserMenuButton.classList.toggle("violet", eraserPopover.classList.contains("open"));
    eraserOptions.forEach((button) => {
      button.classList.toggle("active", button.dataset.eraserMode === state.eraserMode);
    });
  };

  const setPdfHasPages = (hasPages) => {
    pdfPane.classList.toggle("has-pdf", hasPages);
  };

  const resetPdfSession = () => {
    if (pdfPageObserver) {
      pdfPageObserver.disconnect();
    }
    pdfPageObserver = null;
    pdfPageStates = [];
    visiblePdfPages = new Set();
    state.activePdfPageNumber = null;
    state.pdfDocument = null;
    pdfPages.innerHTML = "";
    setPdfHasPages(false);
  };

  const setupPdfPageObserver = () => {
    if (pdfPageObserver) {
      pdfPageObserver.disconnect();
    }

    pdfPageObserver = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        const pageNumber = Number(entry.target.dataset.pageNumber);
        const pageState = getPdfPageState(pageNumber);
        if (entry.isIntersecting) {
          visiblePdfPages.add(pageNumber);
          renderPdfPage(pageNumber);
          schedulePdfPageRender(pageNumber);
        } else {
          visiblePdfPages.delete(pageNumber);
          if (pageState) {
            releasePdfPage(pageState);
          }
        }
      });
    }, {
      root: pdfPane,
      rootMargin: "600px 0px",
      threshold: 0.01
    });

    pdfPageStates.forEach((pageState) => pdfPageObserver.observe(pageState.wrapper));
  };

  const ensurePdfPagesVisible = () => {
    if (pdfPages.children.length > 0) {
      setPdfHasPages(true);
    }
  };


  const resizeCanvasToElement = (targetCanvas, targetContext, element) => {
    const rect = element.getBoundingClientRect();
    const ratio = window.devicePixelRatio || 1;
    const width = Math.max(1, rect.width, element.offsetWidth);
    const height = Math.max(1, rect.height, element.offsetHeight);
    targetCanvas.width = Math.max(1, Math.round(width * ratio));
    targetCanvas.height = Math.max(1, Math.round(height * ratio));
    targetCanvas.style.width = `${width}px`;
    targetCanvas.style.height = `${height}px`;
    targetContext.setTransform(ratio, 0, 0, ratio, 0, 0);
  };

  const restoreCanvasImage = (targetContext, targetCanvas, dataUrl, width, height, afterRestore) => {
    targetContext.clearRect(0, 0, targetCanvas.width, targetCanvas.height);
    if (!dataUrl) {
      afterRestore?.();
      return;
    }

    const image = new Image();
    image.onload = () => {
      targetContext.drawImage(image, 0, 0, width, height);
      afterRestore?.();
    };
    image.src = dataUrl;
  };

  const pointPressure = (point, fallback = 0.5) => Number.isFinite(point?.pressure) ? clamp(point.pressure, 0, 1) : fallback;
  const pointTime = (point) => Number.isFinite(point?.time) ? point.time : Number.isFinite(point?.timestamp) ? point.timestamp : 0;
  const pointVelocity = (point) => Number.isFinite(point?.velocity) ? point.velocity : 0;

  const isInkStroke = (stroke) => stroke && !isShapeTool(stroke.tool) && stroke.tool !== "eraser";

  const normalizeStrokePoint = (stroke, point, index) => ({
    x: point.x,
    y: point.y,
    pressure: Number.isFinite(point.pressure)
      ? point.pressure
      : Array.isArray(stroke.pressure) && Number.isFinite(stroke.pressure[index])
        ? stroke.pressure[index]
        : 0.5,
    time: Number.isFinite(point.time)
      ? point.time
      : Array.isArray(stroke.timestamps) && Number.isFinite(stroke.timestamps[index])
        ? stroke.timestamps[index]
        : 0,
    tiltX: Number.isFinite(point.tiltX)
      ? point.tiltX
      : Array.isArray(stroke.tiltX) && Number.isFinite(stroke.tiltX[index])
        ? stroke.tiltX[index]
        : 0,
    tiltY: Number.isFinite(point.tiltY)
      ? point.tiltY
      : Array.isArray(stroke.tiltY) && Number.isFinite(stroke.tiltY[index])
        ? stroke.tiltY[index]
        : 0,
    pointerType: point.pointerType || stroke.pointerType || "mouse",
    velocity: Number.isFinite(point.velocity) ? point.velocity : 0,
    predicted: point.predicted === true
  });

  const getStrokePoints = (stroke) => Array.isArray(stroke?.points)
    ? stroke.points.map((point, index) => normalizeStrokePoint(stroke, point, index))
    : [];

  const createInkStroke = (point, pageNumber = null) => {
    const stroke = {
      id: `stroke-${Date.now()}-${Math.random().toString(36).slice(2)}`,
      tool: state.tool,
      pen: { ...state.pen },
      brushType: state.pen.type,
      color: state.pen.color,
      brushColor: state.pen.color,
      input: point.pointerType || "mouse",
      points: [],
      pressure: [],
      timestamps: [],
      tiltX: [],
      tiltY: []
    };

    if (pageNumber) {
      stroke.pageNumber = pageNumber;
    }

    addPointToStroke(stroke, point);
    return stroke;
  };

  const addPointToStroke = (stroke, point) => {
    const previous = stroke.points[stroke.points.length - 1] || null;
    const time = pointTime(point) || performance.now();
    const pressure = pointPressure(point);
    const velocity = previous
      ? Math.hypot(point.x - previous.x, point.y - previous.y) / Math.max(1, time - pointTime(previous))
      : 0;
    const storedPoint = {
      x: point.x,
      y: point.y,
      pressure,
      time,
      velocity,
      tiltX: Number.isFinite(point.tiltX) ? point.tiltX : 0,
      tiltY: Number.isFinite(point.tiltY) ? point.tiltY : 0,
      pointerType: point.pointerType || stroke.input || "mouse"
    };

    stroke.points.push(storedPoint);
    stroke.pressure.push(storedPoint.pressure);
    stroke.timestamps.push(storedPoint.time);
    stroke.tiltX.push(storedPoint.tiltX);
    stroke.tiltY.push(storedPoint.tiltY);
    return storedPoint;
  };

  const parseStrokes = (json) => {
    if (!json) {
      return [];
    }

    try {
      const strokes = JSON.parse(json);
      return Array.isArray(strokes) ? strokes : [];
    } catch {
      return [];
    }
  };

  const serializeStrokes = (strokes) => strokes.length > 0 ? JSON.stringify(strokes) : null;

  const normalizePaperLineHeight = (value) => {
    const numericValue = Number(value);
    return PAPER_LINE_HEIGHTS.has(numericValue) ? numericValue : DEFAULT_PAPER_LINE_HEIGHT;
  };

  const currentSnapshot = () => ({
    title: noteTitleInput.value || "Untitled note",
    contentHtml: editor.innerHTML,
    textLayerHtml: textLayer.innerHTML,
    inkStrokesJson: serializeStrokes(state.inkStrokes),
    pdfInkStrokesJson: serializeStrokes(state.pdfInkStrokes),
    showPaperLines: state.showPaperLines,
    paperLineHeight: state.paperLineHeight
  });

  const snapshotFromNote = (note) => ({
    title: note.title,
    contentHtml: note.contentHtml || "",
    textLayerHtml: note.textLayerHtml || "",
    inkStrokesJson: serializeStrokes(parseStrokes(note.inkStrokesJson)),
    pdfInkStrokesJson: serializeStrokes(parseStrokes(note.pdfInkStrokesJson)),
    showPaperLines: note.showPaperLines !== false,
    paperLineHeight: normalizePaperLineHeight(note.paperLineHeight)
  });

  const setTitleFields = (title) => {
    noteTitleInput.value = title || "Untitled note";
    noteTitleMirror.value = noteTitleInput.value;
  };

  const syncTitleFields = (source) => {
    const value = source.value;
    noteTitleInput.value = value;
    noteTitleMirror.value = value;
  };

  const canAnnotatePdf = () => state.tool !== "pan" && state.tool !== "text" && state.tool !== "ocr-select" && state.tool !== "lasso";

  const setViewMode = (mode) => {
    editorShell.classList.remove("drawing-only", "text-only", "pdf-mode", "pdf-only", "single");
    expandCanvasButton.classList.remove("active", "violet");
    textViewButton.classList.remove("active", "violet");
    layoutToggle.classList.remove("active", "violet");
    expandPdfButton.classList.remove("active", "violet");

    if (mode === "drawing") {
      editorShell.classList.add("drawing-only");
      expandCanvasButton.classList.add("active", "violet");
    } else if (mode === "text") {
      textViewButton.classList.add("active", "violet");
    } else if (mode === "pdf") {
      editorShell.classList.add("pdf-mode");
      ensurePdfPagesVisible();
    } else if (mode === "pdf-only") {
      editorShell.classList.add("pdf-mode", "pdf-only");
      expandPdfButton.classList.add("active", "violet");
      ensurePdfPagesVisible();
    } else {
      layoutToggle.classList.add("active", "violet");
      editorShell.classList.add("pdf-mode");
      ensurePdfPagesVisible();
    }

    pdfPane.classList.toggle("annotating", editorShell.classList.contains("pdf-mode") && canAnnotatePdf());
    paneResizer?.setAttribute("aria-hidden", String(!isSplitPaneResizable()));
    resizeCanvas();
    renderPdfInk();
  };

  const applyPaperLines = () => {
    paper.classList.toggle("hide-lines", !state.showPaperLines);
    toggleLinesButton.classList.toggle("active", state.showPaperLines);
    toggleLinesButton.classList.toggle("violet", state.showPaperLines);
    paper.style.setProperty("--zn-paper-line-height", `${state.paperLineHeight}px`);
    lineSpacingOptions.forEach((option) => {
      option.classList.toggle("active", Number(option.dataset.lineSpacing) === state.paperLineHeight);
    });
  };

  const setPaperLineHeight = (value) => {
    state.paperLineHeight = normalizePaperLineHeight(value);
    applyPaperLines();
  };

  const updateNoteListAfterSync = (updated) => {
    state.notes = state.notes.map((note) => note.id === updated.id
      ? { id: updated.id, title: updated.title, updatedAt: updated.updatedAt }
      : note);
    state.notes.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt) || b.id - a.id);
    renderNoteList();
  };

  const applyDraftToEditor = (draft) => {
    setTitleFields(draft.title);
    editor.innerHTML = draft.contentHtml || "";
    textLayer.innerHTML = draft.textLayerHtml || "";
    enableFloatingTextLayerMovement();
    state.inkDataUrl = null;
    state.inkStrokes = parseStrokes(draft.inkStrokesJson);
    state.pdfInkDataUrl = null;
    state.pdfInkStrokes = parseStrokes(draft.pdfInkStrokesJson).map((stroke) => ({
      ...stroke,
      pageNumber: stroke.pageNumber || 1
    }));
    state.showPaperLines = draft.showPaperLines !== false;
    state.paperLineHeight = normalizePaperLineHeight(draft.paperLineHeight);
    applyPaperLines();
    renderInk(false);
    renderPdfInk();
    schedulePaperRestoreExpansion();
  };

  const snapshotsDiffer = (left, right) => {
    if (!left || !right) {
      return Boolean(left || right);
    }

    return ["title", "contentHtml", "textLayerHtml", "inkStrokesJson", "pdfInkStrokesJson", "showPaperLines", "paperLineHeight"]
      .some((key) => (left[key] ?? null) !== (right[key] ?? null));
  };

  const processSyncQueue = async () => {
    if (state.syncing || !state.isOnline) {
      updateSaveIndicator();
      return;
    }

    let queue = offlineStore.getQueue();
    if (queue.length === 0) {
      updateSaveIndicator();
      return;
    }

    state.syncing = true;
    updateSaveIndicator();

    try {
      while (state.isOnline && queue.length > 0) {
        const item = queue[0];
        const payload = await offlineStore.getQueuedPayload(item);
        const updated = await api.update(payload);
        await offlineStore.removeQueueItem(item.noteId);

        const draft = await offlineStore.getDraft(item.noteId);
        if (!draft || draft.timestamp <= item.timestamp) {
          await offlineStore.removeDraft(item.noteId);
        }

        if (state.activeNote?.id === updated.id && (!draft || draft.timestamp <= item.timestamp)) {
          state.activeNote = updated;
          state.lastSaved = snapshotFromNote(updated);
          setTitleFields(updated.title);
        }

        updateNoteListAfterSync(updated);
        state.lastSyncedAt = new Date();
        queue = offlineStore.getQueue();
      }
    } catch {
      // Keep the failed item queued; the next online event or edit will retry.
    } finally {
      state.syncing = false;
      updateSaveIndicator();
    }
  };

  const queueLocalSnapshot = async (snapshot, payload, timestamp) => {
    const noteId = state.activeNote.id;
    await offlineStore.saveDraft(noteId, snapshot, timestamp);
    await offlineStore.enqueue(noteId, payload, timestamp);
    refreshUnsyncedState();
  };

  const handleRecoveryChoice = async (restore) => {
    const recovery = state.pendingRecovery;
    state.pendingRecovery = null;
    if (!recovery) {
      return;
    }

    if (restore) {
      applyDraftToEditor(recovery.draft);
      await saveActiveNote();
    } else {
      await offlineStore.clearNote(recovery.noteId);
      if (state.activeNote?.id === recovery.noteId && state.activeNote.fromLocalDraft) {
        state.notes = state.notes.filter((note) => note.id !== recovery.noteId);
        state.activeNote = null;
        clearPaperState();
        renderNoteList();
      }
      updateSaveIndicator();
    }

    if (window.jQuery) {
      $(recoveryModal).modal("hide");
    }
  };

  const promptDraftRecovery = async (note) => {
    const draft = await offlineStore.getDraft(note.id);
    if (!draft) {
      return false;
    }

    if (!note.fromLocalDraft && !snapshotsDiffer(draft, snapshotFromNote(note))) {
      await offlineStore.clearNote(note.id);
      refreshUnsyncedState();
      return false;
    }

    state.pendingRecovery = { noteId: note.id, draft };
    if (window.jQuery) {
      $(recoveryModal).modal({
        autofocus: false,
        closable: false
      }).modal("show");
    } else if (window.confirm("Recover unsaved work?")) {
      await handleRecoveryChoice(true);
    }
    updateSaveIndicator();
    return true;
  };

  const debounceSave = (delay = 550) => {
    if (state.loading || !state.activeNote) {
      return;
    }

    setSaveState("Saving locally...", "syncing");
    window.clearTimeout(state.saveTimer);
    state.saveTimer = window.setTimeout(() => {
      state.saveTimer = null;
      if (state.drawing) {
        debounceSave(delay);
        return;
      }
      saveActiveNote();
    }, delay);
  };

  const flushPendingSave = async () => {
    if (!state.saveTimer) {
      return;
    }

    window.clearTimeout(state.saveTimer);
    state.saveTimer = null;
    await saveActiveNote();
  };

  const editableParent = (node) => {
    const element = node?.nodeType === Node.TEXT_NODE ? node.parentElement : node;
    if (!(element instanceof Element)) {
      return null;
    }

    return element.closest("[data-content-editor], .zn-floating-text");
  };

  const rememberTextSelection = () => {
    const selection = window.getSelection();
    if (!selection || selection.rangeCount === 0) {
      return;
    }

    const root = editableParent(selection.anchorNode);
    if (!root || !root.contains(editableParent(selection.focusNode))) {
      return;
    }

    state.activeTextRoot = root;
    state.savedTextRange = selection.getRangeAt(0).cloneRange();
  };

  const restoreTextSelection = () => {
    if (!state.savedTextRange || !state.activeTextRoot?.isConnected) {
      return false;
    }

    const selection = window.getSelection();
    selection.removeAllRanges();
    selection.addRange(state.savedTextRange);
    state.activeTextRoot.focus();
    return true;
  };

  const applyFontSize = (fontSize) => {
    const root = state.activeTextRoot?.isConnected ? state.activeTextRoot : editor;
    state.activeTextRoot = root;
    restoreTextSelection();

    const selection = window.getSelection();
    if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
      root.style.fontSize = fontSize;
      debounceSave();
      return;
    }

    const range = selection.getRangeAt(0);
    const span = document.createElement("span");
    span.style.fontSize = fontSize;

    try {
      range.surroundContents(span);
      selection.removeAllRanges();
      selection.selectAllChildren(span);
      selection.collapseToEnd();
    } catch {
      span.appendChild(range.extractContents());
      range.insertNode(span);
      selection.removeAllRanges();
      selection.selectAllChildren(span);
      selection.collapseToEnd();
    }

    state.savedTextRange = selection.rangeCount ? selection.getRangeAt(0).cloneRange() : null;
    debounceSave();
  };

  const initializedFloatingText = new WeakSet();

  const enableFloatingTextMovement = (element) => {
    if (initializedFloatingText.has(element)) {
      return;
    }

    initializedFloatingText.add(element);
    element.contentEditable = "true";

    let dragState = null;

    element.addEventListener("pointerdown", (event) => {
      if (!event.ctrlKey) {
        return;
      }

      event.preventDefault();
      event.stopPropagation();

      const startLeft = Number.parseFloat(element.style.left) || element.offsetLeft;
      const startTop = Number.parseFloat(element.style.top) || element.offsetTop;

      dragState = {
        pointerId: event.pointerId,
        startX: event.clientX,
        startY: event.clientY,
        startLeft,
        startTop
      };

      element.classList.add("dragging");
      element.setPointerCapture(event.pointerId);
    });

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

      event.preventDefault();
      element.style.left = `${dragState.startLeft + event.clientX - dragState.startX}px`;
      element.style.top = `${dragState.startTop + event.clientY - dragState.startY}px`;
    });

    const stopDragging = (event) => {
      if (!dragState || event.pointerId !== dragState.pointerId) {
        return;
      }

      if (element.hasPointerCapture(event.pointerId)) {
        element.releasePointerCapture(event.pointerId);
      }

      dragState = null;
      element.classList.remove("dragging");
      debounceSave();
    };

    element.addEventListener("pointerup", stopDragging);
    element.addEventListener("pointercancel", stopDragging);

    element.addEventListener("keydown", (event) => {
      const wantsDelete = event.key === "Delete" || event.key === "Backspace";
      const isEmpty = element.textContent.trim().length === 0;

      if (!wantsDelete || (!event.ctrlKey && !isEmpty)) {
        return;
      }

      event.preventDefault();
      element.remove();
      debounceSave();
    });
  };

  const enableFloatingTextLayerMovement = () => {
    textLayer.querySelectorAll(".zn-floating-text").forEach(enableFloatingTextMovement);
  };

  const renderNoteList = () => {
    noteList.innerHTML = "";

    state.notes.forEach((note) => {
      const item = document.createElement("div");
      item.className = "zn-note-item";
      item.dataset.noteId = note.id;
      item.classList.toggle("active", state.activeNote?.id === note.id);
      item.addEventListener("click", () => loadNote(note.id));

      const name = document.createElement("span");
      name.className = "zn-note-name";
      name.textContent = note.title;
      item.appendChild(name);

      const deleteButton = document.createElement("button");
      deleteButton.type = "button";
      deleteButton.className = "zn-note-delete";
      deleteButton.setAttribute("aria-label", `Delete ${note.title}`);
      deleteButton.innerHTML = '<i class="trash alternate outline icon"></i>';
      deleteButton.addEventListener("click", (event) => {
        event.stopPropagation();
        if (confirm("Are you sure do you want to delete this Note?"))
          deleteNote(note.id);
      });
      item.appendChild(deleteButton);

      noteList.appendChild(item);
    });
  };

  const isShapeTool = (tool) => tool === "line" || tool === "rectangle" || tool === "square";

  const getShapeBounds = (tool, start, end) => {
    const dx = end.x - start.x;
    const dy = end.y - start.y;

    if (tool !== "square") {
      return {
        x: start.x,
        y: start.y,
        width: dx,
        height: dy
      };
    }

    const size = Math.max(Math.abs(dx), Math.abs(dy));
    return {
      x: start.x,
      y: start.y,
      width: dx < 0 ? -size : size,
      height: dy < 0 ? -size : size
    };
  };

  const pointFromShape = (stroke, xKey, yKey, fallback) => ({
    x: Number.isFinite(stroke[xKey]) ? stroke[xKey] : fallback?.x ?? 0,
    y: Number.isFinite(stroke[yKey]) ? stroke[yKey] : fallback?.y ?? 0,
    pressure: fallback?.pressure || 0.5
  });

  const getStrokeBounds = (stroke) => {
    if (!stroke) {
      return null;
    }

    if (isShapeTool(stroke.tool)) {
      const start = pointFromShape(stroke, "startX", "startY", stroke.points?.[0]);
      const end = pointFromShape(stroke, "endX", "endY", stroke.points?.[1]);

      if (stroke.tool === "line") {
        return {
          maxX: Math.max(start.x, end.x),
          maxY: Math.max(start.y, end.y)
        };
      }

      const bounds = Number.isFinite(stroke.x)
        && Number.isFinite(stroke.y)
        && Number.isFinite(stroke.width)
        && Number.isFinite(stroke.height)
        ? stroke
        : getShapeBounds(stroke.tool, start, end);

      return {
        maxX: Math.max(bounds.x, bounds.x + bounds.width),
        maxY: Math.max(bounds.y, bounds.y + bounds.height)
      };
    }

    if (!Array.isArray(stroke.points) || stroke.points.length === 0) {
      return null;
    }

    return stroke.points.reduce((bounds, point) => ({
      maxX: Math.max(bounds.maxX, point.x || 0),
      maxY: Math.max(bounds.maxY, point.y || 0)
    }), { maxX: 0, maxY: 0 });
  };

  const expandPaperToContent = () => {
    let maxX = 0;
    let maxY = 0;

    state.inkStrokes.forEach((stroke) => {
      const bounds = getStrokeBounds(stroke);
      if (!bounds) {
        return;
      }

      maxX = Math.max(maxX, bounds.maxX);
      maxY = Math.max(maxY, bounds.maxY);
    });

    textLayer.querySelectorAll(".zn-floating-text").forEach((element) => {
      maxX = Math.max(maxX, element.offsetLeft + element.offsetWidth);
      maxY = Math.max(maxY, element.offsetTop + element.offsetHeight);
    });

    const targetWidth = Math.ceil(maxX + RESTORE_CONTENT_PADDING);
    const targetHeight = Math.ceil(maxY + RESTORE_CONTENT_PADDING);
    const nextWidth = Math.max(paper.scrollWidth, targetWidth);
    const nextHeight = Math.max(paper.scrollHeight, targetHeight);
    const needsResize = nextWidth > paper.scrollWidth || nextHeight > paper.scrollHeight;

    if (!needsResize) {
      return;
    }

    paper.style.width = `${nextWidth}px`;
    paper.style.minHeight = `${nextHeight}px`;
    resizeCanvas();
  };

  const schedulePaperRestoreExpansion = (noteId = state.activeNote?.id) => {
    if (paperRestoreExpansionFrame) {
      window.cancelAnimationFrame(paperRestoreExpansionFrame);
    }

    paperRestoreExpansionFrame = window.requestAnimationFrame(() => {
      paperRestoreExpansionFrame = null;
      if (noteId === state.activeNote?.id) {
        expandPaperToContent();
      }
    });
  };

  const createShapeStroke = (tool, start, end, pageNumber = null) => {
    const stroke = {
      tool,
      pen: { ...state.pen },
      brushType: state.pen.type,
      color: state.pen.color,
      brushColor: state.pen.color,
      points: [start, end],
      startX: start.x,
      startY: start.y,
      endX: end.x,
      endY: end.y
    };

    if (tool === "rectangle" || tool === "square") {
      Object.assign(stroke, getShapeBounds(tool, start, end));
    }

    if (pageNumber) {
      stroke.pageNumber = pageNumber;
    }

    return stroke;
  };

  const shapeHasSize = (stroke) => {
    const start = stroke.points?.[0];
    const end = stroke.points?.[1];
    if (!start || !end) {
      return false;
    }

    return Math.hypot(end.x - start.x, end.y - start.y) > 1;
  };

  const clearShapePreview = (targetContext = shapePreviewCtx, targetCanvas = shapePreviewCanvas) => {
    targetContext.clearRect(0, 0, targetCanvas.width, targetCanvas.height);
  };

  const renderShapePreview = (stroke, targetContext = shapePreviewCtx, targetCanvas = shapePreviewCanvas) => {
    clearShapePreview(targetContext, targetCanvas);
    if (stroke) {
      drawStoredStroke(stroke, targetContext);
    }
  };

  const restoreInk = (dataUrl, afterRestore) => {
    const rect = paper.getBoundingClientRect();
    restoreCanvasImage(ctx, canvas, dataUrl, rect.width, paper.scrollHeight, afterRestore);
  };

  const renderCurrentStroke = () => {
    if (state.drawingSurface !== "paper" || !state.currentStroke || isShapeTool(state.currentStroke.tool)) {
      return;
    }

    drawStoredStroke(state.currentStroke, ctx);
  };

  const resizeCanvas = () => {
    resizeCanvasToElement(canvas, ctx, paper);
    resizeCanvasToElement(shapePreviewCanvas, shapePreviewCtx, paper);
    selectionManager?.resizeToElement(paper);
    clearShapePreview();
    renderInk(true, renderCurrentStroke, false);
  };

  const resetPaperSize = () => {
    if (paperExpansionFrame) {
      window.cancelAnimationFrame(paperExpansionFrame);
      paperExpansionFrame = null;
    }
    if (paperRestoreExpansionFrame) {
      window.cancelAnimationFrame(paperRestoreExpansionFrame);
      paperRestoreExpansionFrame = null;
    }

    paper.style.removeProperty("width");
    paper.style.removeProperty("min-height");
    [canvas, inkPreviewCanvas, shapePreviewCanvas, selectionCanvas].filter(Boolean).forEach((targetCanvas) => {
      targetCanvas.width = 1;
      targetCanvas.height = 1;
      targetCanvas.style.removeProperty("width");
      targetCanvas.style.removeProperty("height");
    });
    canvasPane.scrollLeft = 0;
    canvasPane.scrollTop = 0;
    resizeCanvas();
    window.requestAnimationFrame(resizeCanvas);
  };

  const disableContextMenu = (element) => {
    element?.addEventListener("contextmenu", (event) => {
      event.preventDefault();
    });
  };

  const renderInk = (allowFallback = true, afterRender, persistSnapshot = false) => {
    if (state.inkStrokes.length === 0) {
      if (allowFallback) {
        restoreInk(state.inkDataUrl, afterRender);
      } else {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        afterRender?.();
      }
      return;
    }

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    state.inkStrokes.forEach((stroke) => drawStoredStroke(stroke, ctx));
    if (persistSnapshot) {
      state.inkDataUrl = canvas.toDataURL("image/png");
    }
    afterRender?.();
  };

  const getPdfPageState = (pageNumber) => {
    return pdfPageStates.find((pageState) => pageState.pageNumber === pageNumber) || null;
  };

  const getStrokesForPdfPage = (pageNumber) => {
    return state.pdfInkStrokes.filter((stroke) => (stroke.pageNumber || 1) === pageNumber);
  };

  const resizePdfPageInkCanvas = (pageState) => {
    const ratio = window.devicePixelRatio || 1;
    pageState.inkCanvas.width = Math.max(1, Math.round(pageState.width * ratio));
    pageState.inkCanvas.height = Math.max(1, Math.round(pageState.height * ratio));
    pageState.inkCanvas.style.width = `${pageState.width}px`;
    pageState.inkCanvas.style.height = `${pageState.height}px`;
    pageState.inkCtx.setTransform(ratio, 0, 0, ratio, 0, 0);

    if (pageState.previewCanvas && pageState.previewCtx) {
      pageState.previewCanvas.width = pageState.inkCanvas.width;
      pageState.previewCanvas.height = pageState.inkCanvas.height;
      pageState.previewCanvas.style.width = pageState.inkCanvas.style.width;
      pageState.previewCanvas.style.height = pageState.inkCanvas.style.height;
      pageState.previewCtx.setTransform(ratio, 0, 0, ratio, 0, 0);
      clearShapePreview(pageState.previewCtx, pageState.previewCanvas);
    }
  };

  const renderPdfPageInk = (pageNumber) => {
    const pageState = getPdfPageState(pageNumber);
    if (!pageState) {
      return;
    }

    pageState.raf = null;
    pageState.inkCtx.clearRect(0, 0, pageState.width, pageState.height);
    getStrokesForPdfPage(pageNumber).forEach((stroke) => drawStoredStroke(stroke, pageState.inkCtx));
  };

  const schedulePdfPageRender = (pageNumber, force = false) => {
    const pageState = getPdfPageState(pageNumber);
    if (!pageState || pageState.raf || (!force && !visiblePdfPages.has(pageNumber))) {
      return;
    }

    pageState.raf = window.requestAnimationFrame(() => renderPdfPageInk(pageNumber));
  };

  const renderPdfInk = () => {
    pdfPageStates.forEach((pageState) => schedulePdfPageRender(pageState.pageNumber));
  };

  const renderPdfPage = async (pageNumber, force = false) => {
    const pageState = getPdfPageState(pageNumber);
    if (!pageState || pageState.rendering || (pageState.rendered && !force) || !state.pdfDocument) {
      return;
    }

    pageState.rendering = true;
    pageState.pageCanvas.width = Math.floor(pageState.width);
    pageState.pageCanvas.height = Math.floor(pageState.height);
    resizePdfPageInkCanvas(pageState);

    try {
      const page = await state.pdfDocument.getPage(pageNumber);
      await page.render({
        canvasContext: pageState.pageCtx,
        viewport: pageState.viewport
      }).promise;
      pageState.rendered = true;
      schedulePdfPageRender(pageNumber, true);
    } finally {
      pageState.rendering = false;
    }
  };

  const releasePdfPage = (pageState) => {
    if (pageState.rendering || !pageState.rendered) {
      return;
    }

    pageState.pageCanvas.width = 1;
    pageState.pageCanvas.height = 1;
    pageState.inkCanvas.width = 1;
    pageState.inkCanvas.height = 1;
    pageState.inkCtx.setTransform(1, 0, 0, 1, 0, 0);
    pageState.rendered = false;
  };

  const clearPaperState = () => {
    selectionManager?.clear();
    setTitleFields("");
    editor.innerHTML = "";
    textLayer.innerHTML = "";
    state.inkDataUrl = null;
    state.inkStrokes = [];
    state.pdfInkDataUrl = null;
    state.pdfInkStrokes = [];
    state.lastSaved = null;
    state.showPaperLines = true;
    state.paperLineHeight = DEFAULT_PAPER_LINE_HEIGHT;
    paper.classList.remove("has-pdf");
    paper.style.backgroundImage = "";
    applyPaperLines();
    resetPdfSession();
    resetPaperSize();
  };

  const loadNote = async (id) => {
    if (state.activeNote?.id !== id) {
      await flushPendingSave();
    }

    state.loading = true;
    setSaveState("Loading...");
    clearPaperState();

    let note = null;
    try {
      note = await api.get(id);
    } catch {
      const draft = await offlineStore.getDraft(id);
      if (!draft) {
        state.loading = false;
        updateSaveIndicator();
        throw new Error("Unable to load note");
      }

      note = {
        id,
        title: draft.title,
        contentHtml: draft.contentHtml || "",
        textLayerHtml: draft.textLayerHtml || "",
        inkDataUrl: null,
        inkStrokesJson: draft.inkStrokesJson,
        pdfInkDataUrl: null,
        pdfInkStrokesJson: draft.pdfInkStrokesJson,
        showPaperLines: draft.showPaperLines,
        paperLineHeight: draft.paperLineHeight,
        updatedAt: new Date(draft.timestamp).toISOString(),
        fromLocalDraft: true
      };
    }
    state.activeNote = note;
    setTitleFields(note.title);
    editor.innerHTML = note.contentHtml || "";
    textLayer.innerHTML = note.textLayerHtml || "";
    enableFloatingTextLayerMovement();
    state.inkDataUrl = note.inkDataUrl;
    state.inkStrokes = parseStrokes(note.inkStrokesJson);
    selectionManager?.invalidateIndex();
    state.selectedStrokeIds = new Set();
    state.pdfInkDataUrl = null;
    state.pdfInkStrokes = parseStrokes(note.pdfInkStrokesJson).map((stroke) => ({
      ...stroke,
      pageNumber: stroke.pageNumber || 1
    }));
    state.showPaperLines = note.showPaperLines !== false;
    state.paperLineHeight = normalizePaperLineHeight(note.paperLineHeight);
    state.lastSaved = snapshotFromNote(note);
    applyPaperLines();
    resetPdfSession();

    renderInk(false);
    schedulePaperRestoreExpansion();
    renderNoteList();
    state.loading = false;
    await promptDraftRecovery(note);
    updateSaveIndicator();
  };

  const saveActiveNote = async () => {
    if (!state.activeNote) {
      return;
    }

    const snapshot = currentSnapshot();
    const payload = {
      id: state.activeNote.id,
      ...snapshot
    };

    if (state.clearInkDataUrl) {
      payload.clearInkDataUrl = true;
      payload.inkStrokesJson = null;
      snapshot.inkStrokesJson = null;
    }

    if (state.clearPdfInkDataUrl) {
      payload.clearPdfInkDataUrl = true;
      payload.pdfInkStrokesJson = null;
      snapshot.pdfInkStrokesJson = null;
    }

    const hasChanged = snapshotsDiffer(snapshot, state.lastSaved)
      || state.clearInkDataUrl
      || state.clearPdfInkDataUrl;

    if (!hasChanged) {
      updateSaveIndicator();
      return;
    }

    let updated = null;
    try {
      updated = await api.update(payload);
    } catch (error) {
      const timestamp = Date.now();
      await queueLocalSnapshot(snapshot, payload, timestamp);
      state.isOnline = navigator.onLine;
      updateSaveIndicator();
      if (state.isOnline) {
        window.setTimeout(processSyncQueue, 8000);
      }
      return;
    }

    await offlineStore.clearNote(state.activeNote.id);
    state.clearInkDataUrl = false;
    state.clearPdfInkDataUrl = false;
    state.activeNote = updated;
    state.lastSaved = snapshotFromNote(updated);

    state.notes = state.notes.map((note) => note.id === state.activeNote.id
      ? { id: updated.id, title: updated.title, updatedAt: updated.updatedAt }
      : note);
    state.notes.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt) || b.id - a.id);
    renderNoteList();
    updateSaveIndicator();
  };

  const deleteNote = async (id) => {
    await api.delete(id);
    await offlineStore.clearNote(id);
    state.notes = state.notes.filter((note) => note.id !== id);

    if (state.activeNote?.id === id) {
      state.activeNote = null;
      clearPaperState();
      if (state.notes.length > 0) {
        await loadNote(state.notes[0].id);
      }
    }

    renderNoteList();
    updateSaveIndicator();
  };

  const getPoint = (event, targetCanvas = canvas) => {
    const rect = targetCanvas.getBoundingClientRect();
    const baseWidth = Number(targetCanvas.dataset.baseWidth);
    const baseHeight = Number(targetCanvas.dataset.baseHeight);
    const pressure = event.pointerType === "pen" && Number.isFinite(event.pressure) && event.pressure > 0
      ? event.pressure
      : 0.5;
    const tiltX = Number.isFinite(event.tiltX) ? event.tiltX : 0;
    const tiltY = Number.isFinite(event.tiltY) ? event.tiltY : 0;
    const common = {
      pressure,
      time: Number.isFinite(event.timeStamp) ? event.timeStamp : performance.now(),
      tiltX,
      tiltY,
      pointerType: event.pointerType || "mouse",
      buttons: event.buttons || 0,
      button: Number.isFinite(event.button) ? event.button : 0
    };

    if (baseWidth > 0 && baseHeight > 0) {
      return {
        x: (event.clientX - rect.left) * (baseWidth / Math.max(1, rect.width)),
        y: (event.clientY - rect.top) * (baseHeight / Math.max(1, rect.height)),
        ...common
      };
    }

    return {
      x: event.clientX - rect.left,
      y: event.clientY - rect.top,
      ...common
    };
  };

  const getStrokePen = (stroke) => {
    if (stroke?.pen) {
      const type = penTypes.includes(stroke.pen.type)
        ? stroke.pen.type
        : stroke.brushType === "highlighter" || stroke.tool === "highlighter"
          ? "highlighter"
          : stroke.brushType === "marker"
            ? "marker"
            : "pen";

      return {
        ...normalizePenProfile(stroke.pen),
        type
      };
    }

    const legacyType = stroke?.brushType === "highlighter" || stroke?.tool === "highlighter"
      ? "highlighter"
      : stroke?.brushType === "marker"
        ? "marker"
        : "pen";
    const legacyColor = stroke?.color || stroke?.brushColor || "#000000";
    const legacySize = legacyType === "highlighter" ? 3 : legacyType === "marker" ? 2 : 0.7;

    return {
      ...normalizePenProfile({
      color: legacyColor,
      size: legacySize
      }),
      type: legacyType
    };
  };

  const getPenPixelWidth = (pen) => penSizeToPixels.get(Number(pen.size)) || 3;

  const lineWidthForStroke = (stroke, point) => {
    if (stroke.tool === "eraser") {
      return stroke.width || state.eraserWidth;
    }

    const pen = getStrokePen(stroke);
    const baseWidth = getPenPixelWidth(pen);
    if (pen.type === "highlighter") {
      return baseWidth;
    }

    const pressure = pointPressure(point);
    const pressureFactor = pressure > 0 ? 0.5 + pressure : 1;
    const velocity = pointVelocity(point);
    const velocityFactor = velocity > 0 ? clamp(1.16 - velocity * 0.32, 0.72, 1.18) : 1;
    const brushFactor = pen.type === "marker" ? 1.08 : pen.type === "highlighter" ? 1.18 : pen.type === "pencil" ? 0.82 : 1;
    return baseWidth * pressureFactor * velocityFactor * brushFactor;
  };

  const lineWidth = (point) => lineWidthForStroke(state, point);

  const applyStrokeStyle = (targetContext, point, stroke = state) => {
    targetContext.save();
    const pen = getStrokePen(stroke);
    const isHighlighter = pen.type === "highlighter";
    const isPencil = pen.type === "pencil";
    targetContext.lineCap = isHighlighter ? "butt" : "round";
    targetContext.lineJoin = isHighlighter ? "miter" : "round";
    targetContext.lineWidth = lineWidthForStroke(stroke, point);

    if (stroke.tool === "eraser") {
      targetContext.globalCompositeOperation = "destination-out";
      targetContext.strokeStyle = "#000";
    } else {
      targetContext.globalCompositeOperation = "source-over";
      targetContext.strokeStyle = pen.color;
      targetContext.globalAlpha = isHighlighter ? 0.2 : pen.type === "marker" ? 0.68 : isPencil ? 0.72 : 1;
    }
  };

  const midpoint = (from, to) => ({
    x: (from.x + to.x) / 2,
    y: (from.y + to.y) / 2,
    pressure: (from.pressure + to.pressure) / 2
  });

  const drawLine = (from, to, targetContext = ctx) => {
    applyStrokeStyle(targetContext, to);

    targetContext.beginPath();
    targetContext.moveTo(from.x, from.y);
    targetContext.lineTo(to.x, to.y);
    targetContext.stroke();
    targetContext.restore();
  };

  const drawSmoothLine = (from, to, targetContext = ctx) => {
    const nextMidPoint = midpoint(from, to);
    const start = state.lastMidPoint || from;

    applyStrokeStyle(targetContext, nextMidPoint);
    targetContext.beginPath();
    targetContext.moveTo(start.x, start.y);
    targetContext.quadraticCurveTo(from.x, from.y, nextMidPoint.x, nextMidPoint.y);
    targetContext.stroke();
    targetContext.restore();

    state.lastMidPoint = nextMidPoint;
  };

  const drawStoredShape = (stroke, targetContext = ctx) => {
    const start = pointFromShape(stroke, "startX", "startY", stroke.points?.[0]);
    const end = pointFromShape(stroke, "endX", "endY", stroke.points?.[1]);
    const stylePoint = end.pressure ? end : start;

    applyStrokeStyle(targetContext, stylePoint, stroke);

    if (stroke.tool === "line") {
      targetContext.beginPath();
      targetContext.moveTo(start.x, start.y);
      targetContext.lineTo(end.x, end.y);
      targetContext.stroke();
      targetContext.restore();
      return;
    }

    const bounds = Number.isFinite(stroke.x)
      && Number.isFinite(stroke.y)
      && Number.isFinite(stroke.width)
      && Number.isFinite(stroke.height)
      ? stroke
      : getShapeBounds(stroke.tool, start, end);

    targetContext.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
    targetContext.restore();
  };

  const drawStoredStroke = (stroke, targetContext = ctx) => {
    if (isShapeTool(stroke.tool)) {
      drawStoredShape(stroke, targetContext);
      return;
    }

    if (!stroke.points || stroke.points.length < 2) {
      return;
    }

    let lastPoint = stroke.points[0];
    let lastMidPoint = null;

    for (let index = 1; index < stroke.points.length; index += 1) {
      const point = stroke.points[index];
      const nextMidPoint = midpoint(lastPoint, point);
      const start = lastMidPoint || lastPoint;

      applyStrokeStyle(targetContext, nextMidPoint, stroke);
      targetContext.beginPath();
      targetContext.moveTo(start.x, start.y);
      targetContext.quadraticCurveTo(lastPoint.x, lastPoint.y, nextMidPoint.x, nextMidPoint.y);
      targetContext.stroke();
      targetContext.restore();

      lastPoint = point;
      lastMidPoint = nextMidPoint;
    }

    if (lastMidPoint) {
      applyStrokeStyle(targetContext, lastPoint, stroke);
      targetContext.beginPath();
      targetContext.moveTo(lastMidPoint.x, lastMidPoint.y);
      targetContext.lineTo(lastPoint.x, lastPoint.y);
      targetContext.stroke();
      targetContext.restore();
    }
  };

  const distanceToSegment = (point, from, to) => {
    const dx = to.x - from.x;
    const dy = to.y - from.y;

    if (dx === 0 && dy === 0) {
      return Math.hypot(point.x - from.x, point.y - from.y);
    }

    const amount = Math.max(0, Math.min(1, ((point.x - from.x) * dx + (point.y - from.y) * dy) / (dx * dx + dy * dy)));
    return Math.hypot(point.x - (from.x + amount * dx), point.y - (from.y + amount * dy));
  };

  const shapeContainsPoint = (stroke, point) => {
    const start = pointFromShape(stroke, "startX", "startY", stroke.points?.[0]);
    const end = pointFromShape(stroke, "endX", "endY", stroke.points?.[1]);
    const tolerance = lineWidthForStroke(stroke, end) / 2 + 8;

    if (stroke.tool === "line") {
      return distanceToSegment(point, start, end) <= tolerance;
    }

    const bounds = Number.isFinite(stroke.x)
      && Number.isFinite(stroke.y)
      && Number.isFinite(stroke.width)
      && Number.isFinite(stroke.height)
      ? stroke
      : getShapeBounds(stroke.tool, start, end);
    const left = Math.min(bounds.x, bounds.x + bounds.width);
    const right = Math.max(bounds.x, bounds.x + bounds.width);
    const top = Math.min(bounds.y, bounds.y + bounds.height);
    const bottom = Math.max(bounds.y, bounds.y + bounds.height);
    const nearVerticalEdge = Math.abs(point.x - left) <= tolerance || Math.abs(point.x - right) <= tolerance;
    const nearHorizontalEdge = Math.abs(point.y - top) <= tolerance || Math.abs(point.y - bottom) <= tolerance;
    const withinY = point.y >= top - tolerance && point.y <= bottom + tolerance;
    const withinX = point.x >= left - tolerance && point.x <= right + tolerance;

    return (nearVerticalEdge && withinY) || (nearHorizontalEdge && withinX);
  };

  const strokeContainsPoint = (stroke, point) => {
    if (isShapeTool(stroke.tool)) {
      return shapeContainsPoint(stroke, point);
    }

    if (!stroke.points || stroke.points.length < 2) {
      return false;
    }

    for (let index = 1; index < stroke.points.length; index += 1) {
      const from = stroke.points[index - 1];
      const to = stroke.points[index];
      const width = lineWidthForStroke(stroke, to);
      if (distanceToSegment(point, from, to) <= width / 2 + 8) {
        return true;
      }
    }

    return false;
  };

  const commitInkSurface = () => {
    if (state.drawingSurface === "pdf") {
      state.pdfInkDataUrl = null;
    } else {
      state.inkDataUrl = null;
      selectionManager?.invalidateIndex();
    }
  };

  const softEraseStrokeAtPoint = (point) => {
    const strokes = state.drawingSurface === "pdf"
      ? state.pdfInkStrokes.filter((stroke) => (stroke.pageNumber || 1) === state.activePdfPageNumber)
      : state.inkStrokes;
    let strokeIndex = -1;
    let matchedStroke = null;

    for (let index = strokes.length - 1; index >= 0; index -= 1) {
      if (strokes[index].tool !== "eraser" && strokeContainsPoint(strokes[index], point)) {
        strokeIndex = index;
        matchedStroke = strokes[index];
        break;
      }
    }

    if (strokeIndex < 0) {
      return false;
    }

    if (state.drawingSurface === "pdf") {
      state.pdfInkStrokes = state.pdfInkStrokes.filter((stroke) => stroke !== matchedStroke);
      schedulePdfPageRender(state.activePdfPageNumber, true);
    } else {
      strokes.splice(strokeIndex, 1);
      renderInk(false);
    }

    commitInkSurface();
    return true;
  };

  const createNormalEraserStroke = (point) => ({
    tool: "eraser",
    brushType: "normal-eraser",
    color: "#000000",
    brushColor: "#000000",
    width: state.eraserWidth,
    points: [point]
  });

  const addFloatingText = (event) => {
    const rect = paper.getBoundingClientRect();
    const note = document.createElement("div");
    note.className = "zn-floating-text";
    note.contentEditable = "true";
    note.style.left = `${event.clientX - rect.left}px`;
    note.style.top = `${event.clientY - rect.top}px`;

    //note.textContent = "Write here";
    note.dataset.placeholder = "Write here...";
    note.title = "Ctrl + Drag to move";
    enableFloatingTextMovement(note);
    textLayer.appendChild(note);
    note.focus();
    debounceSave(1800);
  };

  const setToolbarDisabled = (disabled) => {
    state.recognizing = disabled;
    toolbar.querySelectorAll("button, input, .ui.dropdown").forEach((element) => {
      if (element.closest("[data-ocr-modal]")) {
        return;
      }

      if ("disabled" in element) {
        element.disabled = disabled;
      }
      element.classList.toggle("disabled", disabled);
    });
    toolbar.classList.toggle("zn-toolbar-disabled", disabled);
  };

  const setOcrProgress = (message, amount = 0) => {
    if (ocrProgressLabel) {
      ocrProgressLabel.textContent = message;
    }
    if (ocrProgress) {
      const percent = Math.round(Math.max(0, Math.min(1, amount)) * 100);
      ocrProgress.querySelector(".bar").style.width = `${percent}%`;
    }
  };

  const setOcrActionsEnabled = (enabled) => {
    [ocrInsertEditorButton, ocrInsertFloatingButton, ocrCopyButton, ocrRetryButton].forEach((button) => {
      button.disabled = !enabled;
      button.classList.toggle("disabled", !enabled);
    });
  };

  const openOcrModalLoading = (imageDataUrl) => {
    state.ocrResult = null;
    if (ocrPreview) {
      ocrPreview.src = imageDataUrl;
    }
    if (ocrText) {
      ocrText.value = "";
    }
    ocrLoading.hidden = false;
    ocrResult.hidden = true;
    setOcrActionsEnabled(false);
    setOcrProgress("Recognizing handwriting...", 0);
    if (window.jQuery) {
      $(ocrModal).modal({
        autofocus: false,
        closable: false
      }).modal("show");
    }
  };

  const showOcrResult = (result) => {
    state.ocrResult = result;
    ocrLoading.hidden = true;
    ocrResult.hidden = false;
    if (ocrPreview) {
      ocrPreview.src = result.imageDataUrl;
    }
    if (ocrText) {
      ocrText.value = result.text || "";
      offlineStore.setOcrText(ocrText.value);
      ocrText.focus();
      ocrText.select();
    }
    setOcrActionsEnabled(true);
  };

  const getPaperVisibleRect = () => {
    const paneRect = canvasPane.getBoundingClientRect();
    const paperRect = paper.getBoundingClientRect();
    const left = Math.max(paneRect.left, paperRect.left);
    const top = Math.max(paneRect.top, paperRect.top);
    const right = Math.min(paneRect.right, paperRect.right);
    const bottom = Math.min(paneRect.bottom, paperRect.bottom);
    const width = Math.max(1, right - left);
    const height = Math.max(1, bottom - top);

    return {
      x: Math.max(0, left - paperRect.left),
      y: Math.max(0, top - paperRect.top),
      width,
      height
    };
  };

  const normalizeRect = (start, end) => ({
    x: Math.max(0, Math.min(start.x, end.x)),
    y: Math.max(0, Math.min(start.y, end.y)),
    width: Math.abs(end.x - start.x),
    height: Math.abs(end.y - start.y)
  });

  const drawFloatingTextForOcr = (targetContext, rect, scale) => {
    textLayer.querySelectorAll(".zn-floating-text").forEach((element) => {
      const left = element.offsetLeft;
      const top = element.offsetTop;
      const right = left + element.offsetWidth;
      const bottom = top + element.offsetHeight;
      if (right < rect.x || bottom < rect.y || left > rect.x + rect.width || top > rect.y + rect.height) {
        return;
      }

      const computed = window.getComputedStyle(element);
      const fontSize = Number.parseFloat(computed.fontSize) || 16;
      const lineHeight = Number.parseFloat(computed.lineHeight) || fontSize * 1.3;
      targetContext.save();
      targetContext.scale(scale, scale);
      targetContext.fillStyle = computed.color || "#111";
      targetContext.font = `${computed.fontWeight || "400"} ${fontSize}px ${computed.fontFamily || "Arial"}`;
      const x = left - rect.x + 10;
      let y = top - rect.y + 10 + fontSize;
      element.innerText.split("\n").forEach((line) => {
        targetContext.fillText(line, x, y);
        y += lineHeight;
      });
      targetContext.restore();
    });
  };

  const captureOcrImage = (rect) => {
    const safeRect = {
      x: Math.max(0, Math.floor(rect.x)),
      y: Math.max(0, Math.floor(rect.y)),
      width: Math.max(1, Math.ceil(rect.width)),
      height: Math.max(1, Math.ceil(rect.height))
    };
    const maxSide = 1800;
    const scale = Math.min(2, maxSide / Math.max(safeRect.width, safeRect.height));
    const outputCanvas = document.createElement("canvas");
    const outputContext = outputCanvas.getContext("2d");
    const sourceScaleX = canvas.width / Math.max(1, canvas.getBoundingClientRect().width);
    const sourceScaleY = canvas.height / Math.max(1, canvas.getBoundingClientRect().height);

    outputCanvas.width = Math.max(1, Math.round(safeRect.width * scale));
    outputCanvas.height = Math.max(1, Math.round(safeRect.height * scale));
    outputContext.fillStyle = "#fff";
    outputContext.fillRect(0, 0, outputCanvas.width, outputCanvas.height);
    outputContext.imageSmoothingEnabled = true;
    outputContext.imageSmoothingQuality = "high";
    outputContext.drawImage(
      canvas,
      safeRect.x * sourceScaleX,
      safeRect.y * sourceScaleY,
      safeRect.width * sourceScaleX,
      safeRect.height * sourceScaleY,
      0,
      0,
      outputCanvas.width,
      outputCanvas.height);
    drawFloatingTextForOcr(outputContext, safeRect, scale);

    return {
      rect: safeRect,
      imageDataUrl: outputCanvas.toDataURL("image/png")
    };
  };

  const tesseractOcrProvider = {
    async recognize(imageDataUrl, onProgress) {
      //const script = "https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js";
      const script = "/js/tesseract.min.js";
      if (!window.Tesseract) {
        onProgress?.("Loading handwriting recognizer...", 0.02);
        await loadScript(script);
      }
      if (!window.Tesseract) {
        throw new Error("OCR engine unavailable");
      }

      const result = await window.Tesseract.recognize(imageDataUrl, "eng", {
        logger(message) {
          if (message.status === "recognizing text") {
            onProgress?.("Recognizing handwriting...", message.progress || 0);
          } else if (message.status) {
            onProgress?.(`${message.status}...`, message.progress || 0.08);
          }
        }
      });

      return result?.data?.text?.trim() || "";
    }
  };

  const cloudOcrProvider = null;

  const selectOcrProvider = () => {
    if (!state.isOnline || !cloudOcrProvider) {
      return tesseractOcrProvider;
    }

    return cloudOcrProvider;
  };

  const runOcr = async (request) => {
    if (!state.activeNote || state.recognizing) {
      return;
    }

    state.ocrLastRequest = request;
    const capture = captureOcrImage(request.rect);
    state.ocrLastRequest.imageDataUrl = capture.imageDataUrl;
    openOcrModalLoading(capture.imageDataUrl);
    setToolbarDisabled(true);

    try {
      const text = await selectOcrProvider().recognize(capture.imageDataUrl, setOcrProgress);
      showOcrResult({
        ...request,
        rect: capture.rect,
        imageDataUrl: capture.imageDataUrl,
        text
      });
      setOcrProgress("Recognition complete", 1);
    } catch (error) {
      showOcrResult({
        ...request,
        rect: capture.rect,
        imageDataUrl: capture.imageDataUrl,
        text: ""
      });
      setSaveState(error?.message ? `OCR failed: ${error.message}` : "OCR failed");
    } finally {
      setToolbarDisabled(false);
    }
  };

  const renderOcrSelectionPreview = (selection) => {
    clearShapePreview();
    if (!selection) {
      return;
    }

    const rect = normalizeRect(selection.start, selection.end);
    shapePreviewCtx.save();
    shapePreviewCtx.fillStyle = "rgba(17, 24, 39, 0.24)";
    shapePreviewCtx.fillRect(0, 0, paper.scrollWidth, paper.scrollHeight);
    shapePreviewCtx.clearRect(rect.x, rect.y, rect.width, rect.height);
    shapePreviewCtx.strokeStyle = "#8b15c9";
    shapePreviewCtx.lineWidth = 2;
    shapePreviewCtx.setLineDash([8, 5]);
    shapePreviewCtx.strokeRect(rect.x + 0.5, rect.y + 0.5, rect.width, rect.height);
    shapePreviewCtx.setLineDash([]);
    shapePreviewCtx.fillStyle = "rgba(255, 255, 255, 0.92)";
    shapePreviewCtx.strokeStyle = "rgba(139, 21, 201, 0.45)";
    shapePreviewCtx.lineWidth = 1;
    const labelWidth = 142;
    const labelHeight = 50;
    const labelX = Math.min(rect.x + 10, Math.max(0, paper.scrollWidth - labelWidth - 10));
    const labelY = Math.max(8, rect.y + 10);
    shapePreviewCtx.fillRect(labelX, labelY, labelWidth, labelHeight);
    shapePreviewCtx.strokeRect(labelX, labelY, labelWidth, labelHeight);
    shapePreviewCtx.fillStyle = "#30263a";
    shapePreviewCtx.font = "700 13px Arial, Helvetica, sans-serif";
    shapePreviewCtx.fillText("Handwriting", labelX + 12, labelY + 21);
    shapePreviewCtx.fillText("recognition", labelX + 12, labelY + 39);
    shapePreviewCtx.restore();
  };

  const setOcrSelectTool = () => {
    state.tool = "ocr-select";
    state.ocrSelection = null;
    pointerModeButton.classList.remove("active", "violet");
    toolButtons.forEach((button) => button.classList.remove("active", "violet"));
    ocrMenuButton?.classList.add("active", "violet");
    setShapePopoverOpen(false);
    setPenPopoverOpen(false);
    setEraserPopoverOpen(false);
    setOcrPopoverOpen(false);
    pdfPane.classList.remove("annotating");
    updateDrawingCursor();
  };

  const startOcrSelection = (event) => {
    if (!state.activeNote || state.recognizing) {
      return;
    }
    event.preventDefault();
    canvas.setPointerCapture(event.pointerId);
    const point = getPoint(event, canvas);
    state.drawing = true;
    state.drawingSurface = "paper";
    state.ocrSelection = {
      pointerId: event.pointerId,
      start: point,
      end: point
    };
    renderOcrSelectionPreview(state.ocrSelection);
  };

  const continueOcrSelection = (event) => {
    if (!state.drawing || !state.ocrSelection || event.pointerId !== state.ocrSelection.pointerId) {
      return;
    }
    event.preventDefault();
    state.ocrSelection.end = getPoint(event, canvas);
    renderOcrSelectionPreview(state.ocrSelection);
  };

  const stopOcrSelection = (event) => {
    if (!state.drawing || !state.ocrSelection || event.pointerId !== state.ocrSelection.pointerId) {
      return;
    }
    event.preventDefault();
    const selection = state.ocrSelection;
    selection.end = getPoint(event, canvas);
    state.drawing = false;
    state.ocrSelection = null;
    clearShapePreview();

    const rect = normalizeRect(selection.start, selection.end);
    if (rect.width < 8 || rect.height < 8) {
      setTool("pen");
      return;
    }

    setTool("pen");
    runOcr({ mode: "selection", rect });
  };

  const insertOcrIntoEditor = () => {
    const text = ocrText.value.trim();
    if (!text) {
      return;
    }
    const fragment = document.createDocumentFragment();
    text.split(/\n{2,}/).forEach((paragraph) => {
      const element = document.createElement("p");
      element.style.fontSize = state.ocrFontSize;
      element.textContent = paragraph;
      fragment.appendChild(element);
    });
    editor.appendChild(fragment);
    offlineStore.clearOcrText();
    debounceSave();
    if (window.jQuery) {
      $(ocrModal).modal("hide");
    }
  };

  const insertOcrAsFloatingText = () => {
    const text = ocrText.value.trim();
    if (!text || !state.ocrResult) {
      return;
    }
    const note = document.createElement("div");
    note.className = "zn-floating-text";
    note.contentEditable = "true";
    note.style.left = `${state.ocrResult.rect.x}px`;
    note.style.top = `${state.ocrResult.rect.y + state.ocrResult.rect.height + 12}px`;
    note.style.fontSize = state.ocrFontSize;
    note.textContent = text;
    note.title = "Ctrl + Drag to move";
    enableFloatingTextMovement(note);
    textLayer.appendChild(note);
    note.focus();
    offlineStore.clearOcrText();
    debounceSave();
    if (window.jQuery) {
      $(ocrModal).modal("hide");
    }
  };

  const startPanning = (event) => {
    if (state.tool !== "pan") {
      return false;
    }

    const scrollElement = event.target.closest(".zn-pdf-pane") || canvasPane;
    event.preventDefault();
    scrollElement.setPointerCapture(event.pointerId);
    state.isPanning = true;
    state.activePointerId = event.pointerId;
    state.panStartX = event.clientX;
    state.panStartY = event.clientY;
    state.panScrollLeft = scrollElement.scrollLeft;
    state.panScrollTop = scrollElement.scrollTop;
    state.panScrollElement = scrollElement;
    updateDrawingCursor();
    return true;
  };

  const continuePanning = (event) => {
    if (!state.isPanning || state.tool !== "pan" || state.activePointerId !== event.pointerId) {
      return false;
    }

    event.preventDefault();
    const scrollElement = state.panScrollElement || canvasPane;
    const dx = event.clientX - state.panStartX;
    const dy = event.clientY - state.panStartY;
    scrollElement.scrollLeft = state.panScrollLeft - dx;
    scrollElement.scrollTop = state.panScrollTop - dy;
    return true;
  };

  const stopPanning = (event) => {
    if (!state.isPanning || state.activePointerId !== event.pointerId) {
      return false;
    }

    state.isPanning = false;
    state.activePointerId = null;
    state.panScrollElement = null;
    updateDrawingCursor();
    return true;
  };

  /* const startDrawing = (event) => {
    if (!state.activeNote) {
      return;
    }

    if (state.tool === "ocr-select") {
      startOcrSelection(event);
      return;
    }

    if (state.tool === "text") {
      addFloatingText(event);
      return;
    }

    canvas.setPointerCapture(event.pointerId);
    state.drawing = true;
    state.drawingSurface = "paper";
    state.lastPoint = getPoint(event, canvas);
    state.lastMidPoint = null;
    state.currentStroke = null;

    if (isShapeTool(state.tool)) {
      state.currentStroke = createShapeStroke(state.tool, state.lastPoint, state.lastPoint);
      clearShapePreview();
      return;
    }

    if (state.tool === "eraser") {
      if (state.eraserMode === "soft") {
        softEraseStrokeAtPoint(state.lastPoint);
        return;
      }

      state.currentStroke = createNormalEraserStroke(state.lastPoint);
      drawLine(state.lastPoint, { ...state.lastPoint, x: state.lastPoint.x + 0.1 }, ctx);
      return;
    }

    state.currentStroke = {
      tool: state.tool,
      pen: { ...state.pen },
      brushType: state.pen.type,
      color: state.pen.color,
      brushColor: state.pen.color,
      points: [state.lastPoint]
    };
  }; */
  const startDrawing = (event) => {
    if (event.button === 2) {
      event.preventDefault();
      return;
    }

    if (!state.activeNote) {
      return;
    }

    // ==========================================================
    // 1. CINTIQ ERASER OVERRIDE:
    // Store the actual intended tool so we can reference it mid-stroke
    // if the physical back eraser tip is hitting the glass screen.
    // ==========================================================
    let activeDrawingTool = state.tool;
    if (event.pointerType === "eraser") {
      activeDrawingTool = "eraser";
    }

    if (activeDrawingTool === "pan") {
      startPanning(event);
      return;
    }

    if (activeDrawingTool === "ocr-select") {
      startOcrSelection(event);
      return;
    }

    if (activeDrawingTool === "lasso") {
      selectionManager?.startPointer(event);
      updateDrawingCursor();
      return;
    }

    if (activeDrawingTool === "text") {
      addFloatingText(event);
      return;
    }

    canvas.setPointerCapture(event.pointerId);
    state.drawing = true;
    state.drawingSurface = "paper";
    state.lastPoint = getPoint(event, canvas);
    state.lastMidPoint = null;
    state.currentStroke = null;

    if (isShapeTool(activeDrawingTool)) {
      state.currentStroke = createShapeStroke(activeDrawingTool, state.lastPoint, state.lastPoint);
      clearShapePreview();
      return;
    }

    if (activeDrawingTool === "eraser") {
      if (state.eraserMode === "soft") {
        softEraseStrokeAtPoint(state.lastPoint);
        return;
      }

      state.currentStroke = createNormalEraserStroke(state.lastPoint);
      // Explicitly mark this stroke object as an eraser so the renderer doesn't use pen colors
      state.currentStroke.tool = "eraser";

      drawLine(state.lastPoint, { ...state.lastPoint, x: state.lastPoint.x + 0.1 }, ctx);
      return;
    }

    state.currentStroke = {
      tool: activeDrawingTool,
      pen: { ...state.pen },
      brushType: state.pen.type,
      color: state.pen.color,
      brushColor: state.pen.color,
      points: [state.lastPoint]
    };
  };
  /* const continueDrawing = (event) => {
    if (state.tool === "ocr-select") {
      continueOcrSelection(event);
      return;
    }

    if (!state.drawing || !state.lastPoint) {
      return;
    }

    const activePdfPage = state.drawingSurface === "pdf" ? getPdfPageState(state.activePdfPageNumber) : null;
    const activeCanvas = activePdfPage?.inkCanvas || canvas;
    const activeContext = activePdfPage?.inkCtx || ctx;
    const pointerEvents = typeof event.getCoalescedEvents === "function"
      ? event.getCoalescedEvents()
      : [event];

    pointerEvents.forEach((pointerEvent) => {
      if (state.drawingSurface === "paper") {
        expandPaperIfNeeded(pointerEvent);
      }

      const point = getPoint(pointerEvent, activeCanvas);

      if (isShapeTool(state.tool)) {
        state.currentStroke = createShapeStroke(
          state.tool,
          state.currentStroke?.points?.[0] || state.lastPoint,
          point,
          state.drawingSurface === "pdf" ? state.activePdfPageNumber : null);

        const previewCanvas = activePdfPage?.previewCanvas || shapePreviewCanvas;
        const previewContext = activePdfPage?.previewCtx || shapePreviewCtx;
        renderShapePreview(state.currentStroke, previewContext, previewCanvas);
        state.lastPoint = point;
        return;
      }

      if (state.tool === "eraser") {
        if (state.eraserMode === "soft") {
          softEraseStrokeAtPoint(point);
        } else {
          drawSmoothLine(state.lastPoint, point, activeContext);
          state.currentStroke?.points.push(point);
        }
        state.lastPoint = point;
        return;
      }

      drawSmoothLine(state.lastPoint, point, activeContext);
      state.currentStroke?.points.push(point);
      state.lastPoint = point;
    });
  }; */
  const continueDrawing = (event) => {
    if (continuePanning(event)) {
      return;
    }

    // ==========================================================
    // CINTIQ ERASER OVERRIDE FOR MAIN HOOK:
    // ==========================================================
    let mainActiveTool = state.tool;
    if (event.pointerType === "eraser") {
      mainActiveTool = "eraser";
    }

    if (mainActiveTool === "ocr-select") {
      continueOcrSelection(event);
      return;
    }

    if (mainActiveTool === "lasso") {
      selectionManager?.continuePointer(event);
      return;
    }

    if (!state.drawing || !state.lastPoint) {
      return;
    }

    const activePdfPage = state.drawingSurface === "pdf" ? getPdfPageState(state.activePdfPageNumber) : null;
    const activeCanvas = activePdfPage?.inkCanvas || canvas;
    const activeContext = activePdfPage?.inkCtx || ctx;
    const pointerEvents = typeof event.getCoalescedEvents === "function"
      ? event.getCoalescedEvents()
      : [event];

    pointerEvents.forEach((pointerEvent) => {
      // ==========================================================
      // CINTIQ ERASER OVERRIDE FOR COALESCED HIGH-HZ POINTER LOOPS:
      // ==========================================================
      let activeDrawingTool = state.tool;
      if (pointerEvent.pointerType === "eraser") {
        activeDrawingTool = "eraser";
      }

      if (state.drawingSurface === "paper") {
        expandPaperIfNeeded(pointerEvent);
      }

      const point = getPoint(pointerEvent, activeCanvas);

      if (isShapeTool(activeDrawingTool)) {
        state.currentStroke = createShapeStroke(
          activeDrawingTool,
          state.currentStroke?.points?.[0] || state.lastPoint,
          point,
          state.drawingSurface === "pdf" ? state.activePdfPageNumber : null);

        const previewCanvas = activePdfPage?.previewCanvas || shapePreviewCanvas;
        const previewContext = activePdfPage?.previewCtx || shapePreviewCtx;
        renderShapePreview(state.currentStroke, previewContext, previewCanvas);
        state.lastPoint = point;
        return;
      }

      if (activeDrawingTool === "eraser") {
        if (state.eraserMode === "soft") {
          softEraseStrokeAtPoint(point);
        } else {
          drawSmoothLine(state.lastPoint, point, activeContext);
          state.currentStroke?.points.push(point);
        }
        state.lastPoint = point;
        return;
      }

      drawSmoothLine(state.lastPoint, point, activeContext);
      state.currentStroke?.points.push(point);
      state.lastPoint = point;
    });
  };
  const stopDrawing = (event) => {
    if (stopPanning(event)) {
      return;
    }

    if (state.tool === "ocr-select") {
      stopOcrSelection(event);
      return;
    }

    if (state.tool === "lasso") {
      selectionManager?.stopPointer(event);
      updateDrawingCursor();
      return;
    }

    if (!state.drawing) {
      return;
    }

    state.drawing = false;
    if (isShapeTool(state.tool)) {
      const activePdfPage = state.drawingSurface === "pdf" ? getPdfPageState(state.activePdfPageNumber) : null;
      const activeCanvas = activePdfPage?.inkCanvas || canvas;
      const activeContext = activePdfPage?.inkCtx || ctx;
      const previewCanvas = activePdfPage?.previewCanvas || shapePreviewCanvas;
      const previewContext = activePdfPage?.previewCtx || shapePreviewCtx;

      if (event && state.currentStroke) {
        const point = getPoint(event, activeCanvas);
        state.currentStroke = createShapeStroke(
          state.tool,
          state.currentStroke.points?.[0] || state.lastPoint,
          point,
          state.drawingSurface === "pdf" ? state.activePdfPageNumber : null);
      }

      clearShapePreview(previewContext, previewCanvas);

      if (state.currentStroke && shapeHasSize(state.currentStroke)) {
        drawStoredStroke(state.currentStroke, activeContext);
        if (state.drawingSurface === "pdf") {
          state.currentStroke.pageNumber = state.activePdfPageNumber;
          state.pdfInkStrokes.push(state.currentStroke);
        } else {
          state.inkStrokes.push(state.currentStroke);
        }
      }

      state.lastPoint = null;
      state.lastMidPoint = null;
      state.currentStroke = null;
      state.activePdfPageNumber = null;
      commitInkSurface();
      debounceSave(1800);
      return;
    }

    if (!(state.tool === "eraser" && state.eraserMode === "soft") && state.lastMidPoint && state.lastPoint) {
      const activeContext = state.drawingSurface === "pdf"
        ? getPdfPageState(state.activePdfPageNumber)?.inkCtx
        : ctx;
      if (!activeContext) {
        return;
      }
      drawLine(state.lastMidPoint, state.lastPoint, activeContext);
    }
    if (state.tool === "eraser" && state.eraserMode === "normal" && state.currentStroke?.points.length === 1) {
      const [point] = state.currentStroke.points;
      state.currentStroke.points.push({ ...point, x: point.x + 0.1 });
    }
    if (state.currentStroke?.points.length > 1) {
      if (state.drawingSurface === "pdf") {
        state.currentStroke.pageNumber = state.activePdfPageNumber;
        state.pdfInkStrokes.push(state.currentStroke);
      } else {
        state.inkStrokes.push(state.currentStroke);
      }
    }
    state.lastPoint = null;
    state.lastMidPoint = null;
    state.currentStroke = null;
    state.activePdfPageNumber = null;
    commitInkSurface();
    debounceSave(1800);
  };

  const setTool = (tool) => {
    state.tool = tool;
    if (tool !== "lasso") {
      selectionManager?.clear();
      state.selectedStrokeIds = selectionManager?.selectedStrokeIds || new Set();
    }
    setShapePopoverOpen(false);
    setPenPopoverOpen(false);
    setEraserPopoverOpen(false);
    setOcrPopoverOpen(false);
    pointerModeButton.classList.remove("active", "violet");
    ocrMenuButton?.classList.remove("active", "violet");
    toolButtons.forEach((button) => {
      const active = button.dataset.tool === tool;
      button.classList.toggle("active", active);
      button.classList.toggle("violet", active);
    });
    pdfPane.classList.toggle("annotating", editorShell.classList.contains("pdf-mode") && canAnnotatePdf());
    updatePenUi();
    updateDrawingCursor();
    updateShapeUi();
    updateEraserUi();
  };

  const setTextTool = () => {
    state.tool = "text";
    selectionManager?.clear();
    pointerModeButton.classList.add("active", "violet");
    toolButtons.forEach((button) => {
      button.classList.remove("active", "violet");
    });
    setShapePopoverOpen(false);
    setPenPopoverOpen(false);
    setEraserPopoverOpen(false);
    setOcrPopoverOpen(false);
    pdfPane.classList.remove("annotating");
    ocrMenuButton?.classList.remove("active", "violet");
    updateDrawingCursor();
    updateShapeUi();
  };

  const setEraserMode = (mode) => {
    state.tool = "eraser";
    selectionManager?.clear();
    state.eraserMode = mode;
    pointerModeButton.classList.remove("active", "violet");
    ocrMenuButton?.classList.remove("active", "violet");
    toolButtons.forEach((button) => {
      button.classList.remove("active", "violet");
    });
    pdfPane.classList.toggle("annotating", editorShell.classList.contains("pdf-mode") && canAnnotatePdf());
    updateDrawingCursor();
    updateShapeUi();
    updateEraserUi();
  };

  const clearInk = () => {
  if(!confirm('This will remove all drawings from the current board. Continue?'))
    return;
    const shouldClearPdf = pdfPageStates.length > 0
      && editorShell.classList.contains("pdf-mode")
      && (editorShell.classList.contains("pdf-only") || state.drawingSurface === "pdf");

    if (shouldClearPdf) {
      state.pdfInkDataUrl = null;
      state.pdfInkStrokes = [];
      state.clearPdfInkDataUrl = true;
      pdfPageStates.forEach((pageState) => schedulePdfPageRender(pageState.pageNumber, true));
    } else {
      state.inkDataUrl = null;
      state.inkStrokes = [];
      state.clearInkDataUrl = true;
      selectionManager?.clear();
      selectionManager?.invalidateIndex();
      renderInk(false);
    }
    window.clearTimeout(state.saveTimer);
    saveActiveNote();
  };
  const setPen = (penPatch, { close = false } = {}) => {
    const patchWithoutType = penPatch ? { ...penPatch } : {};
    const nextType = penTypes.includes(patchWithoutType.type)
      ? patchWithoutType.type
      : state.pen.type;
    delete patchWithoutType.type;
    if (nextType === "highlighter") {
      patchWithoutType.size = 4;
    }
    const currentProfile = getActivePenProfile(nextType);
    const mergedProfile = normalizePenProfile({ ...currentProfile, ...patchWithoutType }, currentProfile);
    state.penProfiles = {
      ...state.penProfiles,
      [nextType]: mergedProfile
    };
    state.pen = { type: nextType, color: mergedProfile.color, size: mergedProfile.size };
    state.tool = "pen";
    selectionManager?.clear();
    pointerModeButton.classList.remove("active", "violet");
    setShapePopoverOpen(false);
    setEraserPopoverOpen(false);
    setOcrPopoverOpen(false);
    ocrMenuButton?.classList.remove("active", "violet");
    toolButtons.forEach((button) => {
      const active = button.dataset.tool === "pen";
      button.classList.toggle("active", active);
      button.classList.toggle("violet", active);
    });
    pdfPane.classList.toggle("annotating", editorShell.classList.contains("pdf-mode") && canAnnotatePdf());
    ensurePdfPagesVisible();
    if (close) {
      setPenPopoverOpen(false);
    }
    savePenSettings();
    updatePenUi();
    updateDrawingCursor();
    updateShapeUi();
    updateEraserUi();
  };

  const startPdfDrawing = (event) => {
    if (event.button === 2) {
      event.preventDefault();
      return;
    }

    if (!state.activeNote) {
      return;
    }

    const pageNumber = Number(event.currentTarget.dataset.pageNumber);
    const pageState = getPdfPageState(pageNumber);
    if (!pageState) {
      return;
    }

    pageState.inkCanvas.setPointerCapture(event.pointerId);
    state.drawing = true;
    state.drawingSurface = "pdf";
    state.activePdfPageNumber = pageNumber;
    state.lastPoint = getPoint(event, pageState.inkCanvas);
    state.lastMidPoint = null;
    state.currentStroke = null;

    if (isShapeTool(state.tool)) {
      state.currentStroke = createShapeStroke(state.tool, state.lastPoint, state.lastPoint, pageNumber);
      clearShapePreview(pageState.previewCtx, pageState.previewCanvas);
      return;
    }

    if (state.tool === "eraser") {
      if (state.eraserMode === "soft") {
        softEraseStrokeAtPoint(state.lastPoint);
        return;
      }

      state.currentStroke = createNormalEraserStroke(state.lastPoint);
      state.currentStroke.pageNumber = pageNumber;
      drawLine(state.lastPoint, { ...state.lastPoint, x: state.lastPoint.x + 0.1 }, pageState.inkCtx);
      return;
    }

    state.currentStroke = {
      pageNumber,
      tool: state.tool,
      pen: { ...state.pen },
      brushType: state.pen.type,
      color: state.pen.color,
      brushColor: state.pen.color,
      points: [state.lastPoint]
    };
  };
  const applyPdfZoom = () => {
    pdfPages.style.transformOrigin = "top left";
    pdfPages.style.transform = `scale(${state.pdfZoom})`;

    pdfPages.style.width = `${100 / state.pdfZoom}%`;

    if (pdfZoomLabel) {
      pdfZoomLabel.textContent = `${Math.round(state.pdfZoom * 100)}%`;
    }
  };

  const ensureSelectionManager = async () => {
    if (selectionManager || !selectionCanvas || !selectionCtx) {
      return selectionManager;
    }

    selectionModulePromise ||= import("./selection/index.js");
    const { SelectionManager, LassoSelectionMode } = await selectionModulePromise;

    selectionManager = new SelectionManager({
      canvas: selectionCanvas,
      inputCanvas: canvas,
      context: selectionCtx,
      toolbar: selectionToolbar,
      getPoint,
      getStrokes: () => state.inkStrokes,
      setStrokes: (strokes) => {
        state.inkStrokes = strokes;
        state.selectedStrokeIds = selectionManager?.selectedStrokeIds || new Set();
      },
      drawStroke: drawStoredStroke,
      renderInk: () => renderInk(false),
      requestSave: () => debounceSave(600),
      idFactory: () => `stroke-${Date.now()}-${Math.random().toString(36).slice(2)}`,
      onSelectionChange: (ids) => {
        state.selectedStrokeIds = ids;
        updateDrawingCursor();
      },
      mode: LassoSelectionMode.ANY_POINT
    });

    selectionManager.bindToolbar();
    selectionManager.resizeToElement(paper);
    return selectionManager;
  };

  const renderPdf = async (file) => {
    const pdfjsLib = await ensurePdfJs();

    setSaveState("Reading PDF...");
    const buffer = await file.arrayBuffer();

    const documentProxy = await pdfjsLib.getDocument({
      data: new Uint8Array(buffer)
    }).promise;

    resetPdfSession();
    state.pdfDocument = documentProxy;
    setPdfHasPages(true);
    setViewMode("pdf");

    for (let pageNumber = 1; pageNumber <= documentProxy.numPages; pageNumber += 1) {
      setSaveState(`Rendering PDF page ${pageNumber} of ${documentProxy.numPages}...`);

      const page = await documentProxy.getPage(pageNumber);
      const viewport = page.getViewport({ scale: 1.25 });

      const pageWrapper = document.createElement("div");
      pageWrapper.className = "zn-pdf-page-wrap";
      pageWrapper.dataset.pageNumber = String(pageNumber);
      pageWrapper.style.width = `${viewport.width}px`;
      pageWrapper.style.height = `${viewport.height}px`;

      const pageCanvas = document.createElement("canvas");
      const pageContext = pageCanvas.getContext("2d");
      const inkCanvas = document.createElement("canvas");
      const inkContext = inkCanvas.getContext("2d");
      const previewCanvas = document.createElement("canvas");
      const previewContext = previewCanvas.getContext("2d");

      pageCanvas.className = "zn-pdf-page";
      pageCanvas.width = 1;
      pageCanvas.height = 1;
      pageCanvas.style.width = `${viewport.width}px`;
      pageCanvas.style.height = `${viewport.height}px`;

      inkCanvas.className = "zn-pdf-page-ink-canvas";
      inkCanvas.dataset.pageNumber = String(pageNumber);
      inkCanvas.dataset.baseWidth = String(viewport.width);
      inkCanvas.dataset.baseHeight = String(viewport.height);
      inkCanvas.setAttribute("aria-label", `PDF page ${pageNumber} annotations`);

      previewCanvas.className = "zn-pdf-page-preview-canvas";
      previewCanvas.setAttribute("aria-hidden", "true");

      pageWrapper.appendChild(pageCanvas);
      pageWrapper.appendChild(inkCanvas);
      pageWrapper.appendChild(previewCanvas);
      pdfPages.appendChild(pageWrapper);

      const pageState = {
        pageNumber,
        wrapper: pageWrapper,
        pageCanvas,
        pageCtx: pageContext,
        inkCanvas,
        inkCtx: inkContext,
        previewCanvas,
        previewCtx: previewContext,
        viewport,
        width: viewport.width,
        height: viewport.height,
        raf: null,
        rendered: false,
        rendering: false
      };
      pdfPageStates.push(pageState);
      resizePdfPageInkCanvas(pageState);

      inkCanvas.addEventListener("pointerdown", startPdfDrawing);
      inkCanvas.addEventListener("pointermove", continueDrawing);
      inkCanvas.addEventListener("pointerup", stopDrawing);
      inkCanvas.addEventListener("pointercancel", stopDrawing);
      disableContextMenu(inkCanvas);

      await new Promise(resolve => requestAnimationFrame(resolve));
    }

    setupPdfPageObserver();
    renderPdfPage(1, true);
    schedulePdfPageRender(1, true);
    applyPdfZoom();

    setSaveState("PDF loaded");
  };
  const initialize = async () => {
    restorePenSettings();
    updatePenUi();
    portalToolPopovers();

    if (window.jQuery) {
      normalizeToolbarTooltips();
      $(".ui.dropdown").dropdown();
      createToolPalettePopup({
        name: "pen",
        trigger: penMenuButton,
        popup: penPopover,
        onShow() {
          openToolbar();
          updatePenUi();
          syncToolbarOpen();
        },
        onHide() {
          updatePenUi();
          syncToolbarOpen();
        }
      });
      $(".zn-toolbar .button").not("[data-pen-menu-button]").popup({
        position: "top center",
        context: "body",
        boundary: window,
        inline: false,
        movePopup: true,
        distanceAway: 10,
        offset: 0,
        transition: "fade up",
        duration: 170,
        variation: "zn-toolbar-popup",
        exclusive: true
      });
    }

    updateDrawingCursor();
    updatePenUi();
    await ensureSelectionManager();
    applyStoredPaneWidth();
    resizeCanvas();
    try {
      state.notes = await api.list();
    } catch {
      state.isOnline = navigator.onLine;
      state.notes = offlineStore.listDrafts()
        .map((draft) => ({
          id: draft.noteId,
          title: draft.title || "Untitled note",
          updatedAt: new Date(draft.timestamp).toISOString(),
          fromLocalDraft: true
        }))
        .sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt) || b.id - a.id);
    }
    renderNoteList();

    if (state.notes.length > 0) {
      await loadNote(state.notes[0].id);
    }

    updateSaveIndicator();
    setViewMode("split");
  };
  pdfZoomInButton.addEventListener("click", () => {
    state.pdfZoom = Math.min(2.5, state.pdfZoom + 0.1);
    applyPdfZoom();
  });
  expandPdfButton.addEventListener("click", () => {
    const nextMode = editorShell.classList.contains("pdf-only") ? "pdf" : "pdf-only";

    setViewMode(nextMode);

    window.requestAnimationFrame(() => {
      setPdfHasPages(pdfPages.children.length > 0);
      renderPdfInk();
      applyPdfZoom();
    });
  });
  pdfZoomOutButton.addEventListener("click", () => {
    state.pdfZoom = Math.max(0.5, state.pdfZoom - 0.1);
    applyPdfZoom();
  });
  toggleNotesPanelButton.addEventListener("click", () => {
    const collapsed = app.classList.toggle("notes-panel-collapsed");
    toggleNotesPanelButton.setAttribute("aria-expanded", String(!collapsed));
  });

  recoveryRestoreButton.addEventListener("click", () => {
    handleRecoveryChoice(true);
  });

  recoveryDiscardButton.addEventListener("click", () => {
    handleRecoveryChoice(false);
  });

  window.addEventListener("online", () => {
    state.isOnline = true;
    updateSaveIndicator();
    processSyncQueue();
  });

  window.addEventListener("offline", () => {
    state.isOnline = false;
    updateSaveIndicator();
  });

  window.addEventListener("beforeunload", (event) => {
    if (!state.saveTimer) {
      return;
    }

    event.preventDefault();
    event.returnValue = "A local save is still pending.";
  });
  fullscreenButton?.addEventListener("click", toggleFullscreen);
  document.addEventListener("fullscreenchange", updateFullscreenUi);
  updateFullscreenUi();

  newNoteForm.addEventListener("submit", async (event) => {
    event.preventDefault();
    const title = newNoteTitle.value || "Untitled note";
    const note = await api.create(title);
    newNoteTitle.value = "";
    state.notes = [{ id: note.id, title: note.title, updatedAt: note.updatedAt }, ...state.notes];
    await loadNote(note.id);
  });

  editor.addEventListener("input", debounceSave);
  editor.addEventListener("focusin", () => {
    state.activeTextRoot = editor;
    rememberTextSelection();
  });
  editor.addEventListener("keyup", rememberTextSelection);
  editor.addEventListener("mouseup", rememberTextSelection);
  textLayer.addEventListener("input", debounceSave);
  textLayer.addEventListener("focusin", (event) => {
    const floatingText = event.target.closest(".zn-floating-text");
    if (floatingText) {
      state.activeTextRoot = floatingText;
      rememberTextSelection();
    }
  });
  textLayer.addEventListener("keyup", rememberTextSelection);
  textLayer.addEventListener("mouseup", rememberTextSelection);
  document.addEventListener("selectionchange", rememberTextSelection);
  noteTitleInput.addEventListener("input", () => {
    syncTitleFields(noteTitleInput);
    debounceSave();
  });
  noteTitleMirror.addEventListener("input", () => {
    syncTitleFields(noteTitleMirror);
    debounceSave();
  });
  clearButton.addEventListener("click", clearInk);
  pointerModeButton.addEventListener("click", setTextTool);
  topbar.addEventListener("mouseenter", openToolbar);
  toolbar.addEventListener("mouseenter", openToolbar);
  toolbar.addEventListener("mouseleave", closeToolbar);
  toolbar.addEventListener("scroll", positionOpenToolPopover);
  window.addEventListener("resize", () => {
    if (isSplitPaneResizable()) {
      setCanvasPaneWidth(canvasPane.getBoundingClientRect().width);
    }
    resizeCanvas();
    renderPdfInk();
    positionOpenToolPopover();
  });

  paneResizer?.addEventListener("pointerdown", startPaneResize);
  paneResizer?.addEventListener("pointermove", continuePaneResize);
  paneResizer?.addEventListener("pointerup", stopPaneResize);
  paneResizer?.addEventListener("pointercancel", stopPaneResize);

  canvas.addEventListener("pointerdown", startDrawing);
  canvas.addEventListener("pointermove", continueDrawing);
  canvas.addEventListener("pointerup", stopDrawing);
  canvas.addEventListener("pointercancel", stopDrawing);
  [paper, canvas, inkPreviewCanvas, shapePreviewCanvas, selectionCanvas].forEach(disableContextMenu);
  [canvasPane, pdfPane].forEach((panSurface) => {
    panSurface.addEventListener("pointerdown", (event) => {
      if (startPanning(event)) {
        event.stopPropagation();
      }
    }, true);
    panSurface.addEventListener("pointermove", (event) => {
      if (continuePanning(event)) {
        event.stopPropagation();
      }
    });
    panSurface.addEventListener("pointerup", stopPanning);
    panSurface.addEventListener("pointercancel", stopPanning);
  });

  toolButtons.forEach((button) => {
    button.addEventListener("click", () => setTool(button.dataset.tool));
  });

  typographyMenuButton.addEventListener("click", (event) => {
    event.stopPropagation();
    setTypographyPopoverOpen(!typographyPopover.classList.contains("open"));
  });

  typographyPopover.addEventListener("click", (event) => {
    event.stopPropagation();
  });

  shapeMenuButton.addEventListener("click", (event) => {
    event.stopPropagation();
    setShapePopoverOpen(!shapePopover.classList.contains("open"));
  });

  shapePopover.addEventListener("click", (event) => {
    event.stopPropagation();
  });

  shapeOptions.forEach((button) => {
    button.addEventListener("click", () => {
      setTool(button.dataset.shapeTool);
      setShapePopoverOpen(false);
    });
  });

  penToolButtons.forEach((button) => {
    button.addEventListener("click", (event) => {
      event.stopPropagation();
      setPen({ type: "pen" });
      setPenPopoverOpen(false);
    });
  });

  penMenuButton.addEventListener("click", (event) => {
    event.stopPropagation();
    setPenPopoverOpen(!penPopover.classList.contains("open"));
  });

  penPopover.addEventListener("click", (event) => {
    event.stopPropagation();
  });

  eraserButton.addEventListener("click", (event) => {
    event.stopPropagation();
    setEraserMode("soft");
    setEraserPopoverOpen(false);
  });

  eraserMenuButton.addEventListener("click", (event) => {
    event.stopPropagation();
    setEraserPopoverOpen(!eraserPopover.classList.contains("open"));
  });

  eraserPopover.addEventListener("click", (event) => {
    event.stopPropagation();
  });

  eraserOptions.forEach((button) => {
    button.addEventListener("click", () => {
      setEraserMode(button.dataset.eraserMode);
      setEraserPopoverOpen(false);
    });
  });

  eraserWidthInput.addEventListener("input", () => {
    state.eraserWidth = Number(eraserWidthInput.value) || 26;
  });

  ocrMenuButton?.addEventListener("click", (event) => {
    event.stopPropagation();
    setOcrPopoverOpen(!ocrPopover?.classList.contains("open"));
  });

  ocrPopover?.addEventListener("click", (event) => {
    event.stopPropagation();
  });

  ocrFullCanvasButton?.addEventListener("click", () => {
    setOcrPopoverOpen(false);
    runOcr({
      mode: "full",
      rect: getPaperVisibleRect()
    });
  });

  ocrSelectAreaButton?.addEventListener("click", () => {
    setOcrSelectTool();
  });

  ocrFontSize?.querySelectorAll("button").forEach((button) => {
    button.addEventListener("click", () => {
      state.ocrFontSize = button.dataset.value || "16px";
      ocrFontSize.querySelectorAll("button").forEach((item) => {
        const active = item === button;
        item.classList.toggle("active", active);
        item.classList.toggle("violet", active);
      });
    });
  });

  ocrInsertEditorButton?.addEventListener("click", insertOcrIntoEditor);
  ocrInsertFloatingButton?.addEventListener("click", insertOcrAsFloatingText);
  ocrText?.addEventListener("input", () => {
    offlineStore.setOcrText(ocrText.value);
  });
  ocrCancelButton?.addEventListener("click", () => {
    offlineStore.clearOcrText();
  });
  ocrRetryButton?.addEventListener("click", () => {
    if (state.ocrLastRequest) {
      runOcr(state.ocrLastRequest);
    }
  });
  ocrCopyButton?.addEventListener("click", async () => {
    const text = ocrText?.value;
    if (!text) {
      return;
    }

    try {
      await navigator.clipboard.writeText(text);
      setSaveState("Recognized text copied");
    } catch {
      ocrText?.focus();
      ocrText?.select();
      document.execCommand("copy");
      setSaveState("Recognized text copied");
    }
  });

  penThicknessButtons.forEach((button) => {
    button.addEventListener("click", () => {
      setPen({ type: "pen", size: Number(button.dataset.size) });
    });
  });

  penColorButtons.forEach((button) => {
    button.addEventListener("click", () => {
      setPen({ type: "pen", color: button.dataset.color });
    });
  });

  penTypeButtons.forEach((button) => {
    button.addEventListener("click", () => {
      setPen({
        type: button.dataset.type,
        color: button.dataset.color,
        size: Number(button.dataset.size)
      });
      if (button.dataset.type === "highlighter" && pdfPageStates.length > 0) {
        setViewMode("pdf");
      }
    });
  });

  fontSizeDropdown?.querySelectorAll(".item").forEach((item) => {
    item.addEventListener("mousedown", (event) => {
      event.preventDefault();
    });

    item.addEventListener("click", () => {
      const fontSize = item.dataset.value;
      if (!fontSize) {
        return;
      }

      const input = fontSizeDropdown.querySelector("input");
      const label = fontSizeDropdown.querySelector(".text");
      if (input) {
        input.value = fontSize;
      }
      if (label) {
        label.textContent = fontSize;
      }

      applyFontSize(fontSize);
    });
  });

  document.addEventListener("click", (event) => {
    if (!typographyPopover.contains(event.target) && !typographyMenuButton.contains(event.target)) {
      setTypographyPopoverOpen(false);
    }
    if (!shapePopover.contains(event.target) && !shapeMenuButton.contains(event.target)) {
      setShapePopoverOpen(false);
    }
    if (!penPopover.contains(event.target) && !penMenuButton.contains(event.target) && !Array.from(penToolButtons).some((button) => button.contains(event.target))) {
      setPenPopoverOpen(false);
    }
    if (!eraserPopover.contains(event.target) && !eraserButton.contains(event.target) && !eraserMenuButton.contains(event.target)) {
      setEraserPopoverOpen(false);
    }
    if (!lineSpacingPopover.contains(event.target) && !lineSpacingMenuButton.contains(event.target)) {
      setLineSpacingPopoverOpen(false);
    }
    if (ocrPopover && ocrMenuButton && !ocrPopover.contains(event.target) && !ocrMenuButton.contains(event.target)) {
      setOcrPopoverOpen(false);
    }
  });

  document.addEventListener("keydown", (event) => {
    if (selectionManager?.handleKeydown(event)) {
      return;
    }

    if (event.key !== "Escape") {
      return;
    }

    if (selectionManager?.hasSelection?.()) {
      selectionManager.clear();
      updateDrawingCursor();
      return;
    }

    if (penPopover.classList.contains("open")) {
      setPenPopoverOpen(false);
      penMenuButton.focus();
    }
    if (lineSpacingPopover.classList.contains("open")) {
      setLineSpacingPopoverOpen(false);
      lineSpacingMenuButton.focus();
    }
  });

  commandButtons.forEach((button) => {
    button.addEventListener("mousedown", (event) => {
      event.preventDefault();
    });

    button.addEventListener("click", () => {
      restoreTextSelection() || editor.focus();
      document.execCommand(button.dataset.command, false, button.dataset.value || null);
      if (button.dataset.command === "foreColor") {
        typographyPopover.querySelectorAll("[data-command='foreColor']").forEach((item) => {
          item.classList.toggle("active", item === button);
        });
      }
      if (button.dataset.command?.startsWith("justify")) {
        typographyPopover.querySelectorAll("[data-command^='justify']").forEach((item) => {
          item.classList.toggle("active", item === button);
        });
      }
      debounceSave();
    });
  });

  toggleLinesButton.addEventListener("click", () => {
    state.showPaperLines = !state.showPaperLines;
    applyPaperLines();
    debounceSave();
  });
  lineSpacingMenuButton.addEventListener("click", (event) => {
    event.stopPropagation();
    setLineSpacingPopoverOpen(!lineSpacingPopover.classList.contains("open"));
  });

  lineSpacingPopover.addEventListener("click", (event) => {
    event.stopPropagation();
  });

  lineSpacingOptions.forEach((button) => {
    button.addEventListener("click", () => {
      setPaperLineHeight(button.dataset.lineSpacing);
      setLineSpacingPopoverOpen(false);
      debounceSave();
    });
  });
  pdfInput.addEventListener("change", (event) => {
    const [file] = event.target.files;
    if (file) {
      renderPdf(file)
        .then(() => {
          // PDF loaded successfully
          isPdfLoaded = true;
          // Swap icon to a clear icon (e.g., trash or close times icon)
          pdfIcon.className = "times circle icon";
          pdfButton.querySelector("span").textContent = "Clear PDF";
        })
        .catch((error) => {
          setSaveState(error?.message ? `PDF failed: ${error.message}` : "PDF import failed");
        });
    }
  });

  // 2. Control button click behavior dynamically
  pdfButton.addEventListener("click", () => {
    if (isPdfLoaded) {
      // ---- ACTION: CLEAR PDF ----
      isPdfLoaded = false;
      pdfInput.value = ""; // Reset the input element

      // Call whatever existing routine you have to wipe canvas / layout settings
      // For example, if you switch views back or clear layout:
      setViewMode("split");

      // Clear the PDF container visually (adjust selector based on your code)
      const pdfPagesContainer = document.querySelector(".zn-pdf-pages");
      if (pdfPagesContainer) pdfPagesContainer.innerHTML = "";
      document.querySelector(".zn-editor").classList.remove("pdf-mode");

      // Restore the original PDF selection icon state
      pdfIcon.className = "file pdf icon";
      pdfButton.querySelector("span").textContent = "PDF";
    } else {
      // ---- ACTION: OPEN FILE DIALOG ----
      pdfInput.click(); // Programmatically open the file explorer selection
    }
  });
  /* pdfInput.addEventListener("change", (event) => {
    const [file] = event.target.files;
    if (file) {
      renderPdf(file).catch((error) => {
        setSaveState(error?.message ? `PDF failed: ${error.message}` : "PDF import failed");
      });
    } else if (pdfPageStates.length > 0) {
      setViewMode("pdf");
    }
  });

  pdfButton.addEventListener("click", () => {
    setViewMode("pdf");
  }); */

  layoutToggle.addEventListener("click", () => {
    setViewMode("split");
  });

  expandCanvasButton.addEventListener("click", () => {
    setViewMode(editorShell.classList.contains("drawing-only") ? "split" : "drawing");
  });

  textViewButton.addEventListener("click", () => {
    const isTextSplit = !editorShell.classList.contains("drawing-only")
      && !editorShell.classList.contains("text-only")
      && !editorShell.classList.contains("pdf-mode")
      && !editorShell.classList.contains("pdf-only");

    setViewMode(isTextSplit ? "split" : "text");
  });

  initialize().catch(() => setSaveState("Load failed"));
})();
