// Edit PDF — full-featured page-level editor.
// Tools: Select, Text, Highlight, Blackout, Shapes, Check, Date, Time, Image, Sign, Undo.
// Page actions: insert blank, delete, rotate, reorder.
// Renders pages with pdf.js, rebuilds output via pdf-lib (overlays drawn in PDF coord space).

const EDIT_PDF_PDFJS        = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.11.174/build/pdf.min.js';
const EDIT_PDF_PDFJS_WORKER = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.11.174/build/pdf.worker.min.js';
const EDIT_PDF_PDFLIB       = 'https://cdn.jsdelivr.net/npm/pdf-lib@1.17.1/dist/pdf-lib.min.js';
const EDIT_PDF_FONTKIT      = 'https://unpkg.com/@pdf-lib/fontkit@1.1.1/dist/fontkit.umd.min.js';
// Ubuntu TTF — Latin Extended-A coverage (Turkish ç ş ğ ı ö ü İ all supported).
// pdf-lib.js.org hosts these on GitHub Pages with CORS open.
const EDIT_PDF_FONT_REG     = 'https://pdf-lib.js.org/assets/ubuntu/Ubuntu-R.ttf';
const EDIT_PDF_FONT_BOLD    = 'https://pdf-lib.js.org/assets/ubuntu/Ubuntu-B.ttf';

const EDIT_PDF_PREVIEW_SCALE = 1.4;
const EDIT_PDF_HISTORY_LIMIT = 30;

// Module-cached font bytes so a second save() doesn't re-download (~150 KB each).
let _editPdfFontReg = null;
let _editPdfFontBold = null;
async function ensureEditPdfFonts() {
  if (!window.fontkit) await window.loadScript(EDIT_PDF_FONTKIT);
  if (!_editPdfFontReg) {
    const r = await fetch(EDIT_PDF_FONT_REG);
    if (!r.ok) throw new Error('Could not load Unicode font (regular): HTTP ' + r.status);
    _editPdfFontReg = await r.arrayBuffer();
  }
  if (!_editPdfFontBold) {
    const r = await fetch(EDIT_PDF_FONT_BOLD);
    if (!r.ok) throw new Error('Could not load Unicode font (bold): HTTP ' + r.status);
    _editPdfFontBold = await r.arrayBuffer();
  }
}

function editPdfHexToRgb(hex) {
  const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex || '#000000');
  if (!m) return { r: 0, g: 0, b: 0 };
  return { r: parseInt(m[1], 16) / 255, g: parseInt(m[2], 16) / 255, b: parseInt(m[3], 16) / 255 };
}

const EDIT_PDF_TOOLS = [
  { id: 'select',    label: 'Select',    icon: 'cursor',   tip: 'Click and drag overlays you\'ve already added.' },
  { id: 'edit-text', label: 'Edit Text', icon: 'edit',     tip: 'Click any existing text on the page to change its words, size, color, or weight.' },
  { id: 'text',      label: 'Add Text',  icon: 'text',     tip: 'Click anywhere on a page to drop a new text box.' },
  { id: 'highlight', label: 'Highlight', icon: 'marker',   tip: 'Drag across a region to apply a translucent highlight (color picker in the strip).' },
  { id: 'blackout',  label: 'Blackout',  icon: 'square',   tip: 'Drag to cover a region with a solid black rectangle (visual redaction).' },
  { id: 'shape',     label: 'Shapes',    icon: 'grid',     tip: 'Drag to draw a rectangle or circle. Stroke + fill configurable.' },
  { id: 'check',     label: 'Check',     icon: 'check',    tip: 'Click to drop a green ✓ check mark.' },
  { id: 'date',      label: 'Date',      icon: 'calendar', tip: 'Click to stamp today\'s date as text.' },
  { id: 'time',      label: 'Time',      icon: 'clock',    tip: 'Click to stamp the current time as text.' },
  { id: 'image',     label: 'Image',     icon: 'image',    tip: 'Pick an image, then click on a page to place it. Drag the corner to resize.' },
  { id: 'sign',      label: 'Sign',      icon: 'sign',     tip: 'Open the signature pad — draw your signature, place it on the page.' },
  { id: 'undo',      label: 'Undo',      icon: 'undo',     tip: 'Step back through your last edits (up to 30).' },
];

function editPdfNewId() { return 'o' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6); }

window.TOOL_HANDLERS['edit-pdf'] = function EditPdfTool({ fullscreen = false, onExit } = {}) {
  const [file, setFile] = React.useState(null);
  const [pdfName, setPdfName] = React.useState('Untitled');
  const [busy, setBusy] = React.useState(false);
  const [status, setStatus] = React.useState('');
  const [err, setErr] = React.useState('');
  const [pages, setPages] = React.useState([]);
  const [activePage, setActivePage] = React.useState(0);
  const [overlays, setOverlays] = React.useState({});
  // textItems[pageIdx] = [{ str, fontName, canvas: {x, top, w, h, fontSize}, pdf: {x, baselineY, fontSize, w} }, ...]
  const [textItems, setTextItems] = React.useState({});
  // textEdits[pageIdx][itemIdx] = { str, fontSize, color, bold } — exists when user changed anything.
  const [textEdits, setTextEdits] = React.useState({});
  // Currently focused edit box, drives the floating format popover.
  const [focusedEdit, setFocusedEdit] = React.useState(null); // { page, idx } | null
  const [tool, setTool] = React.useState('select');
  const [textCfg, setTextCfg]   = React.useState({ fontSize: 16, color: '#111111' });
  const [shapeCfg, setShapeCfg] = React.useState({ shape: 'rect', stroke: '#2563eb', fill: 'transparent', strokeWidth: 2 });
  const [highlightColor, setHighlightColor] = React.useState('#fff176');
  const [pendingImage, setPendingImage] = React.useState(null);
  const [signOpen, setSignOpen] = React.useState(false);
  const [savedBlob, setSavedBlob] = React.useState(null);
  const [sidebarCollapsed, setSidebarCollapsed] = React.useState(false);
  const [history, setHistory] = React.useState([]);

  const canvasRef = React.useRef(null);
  const dragState = React.useRef(null);
  const drawState = React.useRef(null); // for rect drag-drawing of shapes/highlight/blackout
  const [drawTick, setDrawTick] = React.useState(0);
  const _drawRafPending = React.useRef(false);

  // Cleanup PDF bytes from global when component unmounts (modal close).
  React.useEffect(() => {
    return () => { window.__editPdfSourceBytes = null; };
  }, []);

  // ---------- History (snapshot before each mutation) ----------
  const snapshot = () => {
    setHistory((h) => {
      const snap = {
        pages: pages.map((p) => ({ ...p })),
        overlays: JSON.parse(JSON.stringify(overlays)),
        textEdits: JSON.parse(JSON.stringify(textEdits)),
      };
      const next = [...h, snap];
      return next.length > EDIT_PDF_HISTORY_LIMIT ? next.slice(-EDIT_PDF_HISTORY_LIMIT) : next;
    });
  };
  const undo = () => {
    setHistory((h) => {
      if (h.length === 0) return h;
      const snap = h[h.length - 1];
      setPages(snap.pages);
      setOverlays(snap.overlays);
      setTextEdits(snap.textEdits || {});
      return h.slice(0, -1);
    });
  };

  // ---------- Load PDF ----------
  const handleFile = async (f) => {
    setBusy(true); setErr(''); setSavedBlob(null);
    try {
      setStatus('Loading pdf.js…');
      await window.loadScript(EDIT_PDF_PDFJS);
      window.pdfjsLib.GlobalWorkerOptions.workerSrc = EDIT_PDF_PDFJS_WORKER;
      setStatus('Reading PDF…');
      const bytes = new Uint8Array(await f.arrayBuffer());
      const sourceBytes = bytes.slice();
      const doc = await window.pdfjsLib.getDocument({ data: bytes }).promise;
      const out = [];
      const allTexts = {};
      for (let i = 1; i <= doc.numPages; i++) {
        setStatus(`Rendering page ${i} / ${doc.numPages}…`);
        const page = await doc.getPage(i);
        const viewport = page.getViewport({ scale: EDIT_PDF_PREVIEW_SCALE });
        const canvas = document.createElement('canvas');
        canvas.width = viewport.width; canvas.height = viewport.height;
        await page.render({ canvasContext: canvas.getContext('2d'), viewport }).promise;
        const pdfV = page.getViewport({ scale: 1 });

        // Extract text content for in-place editing.
        const tc = await page.getTextContent();
        const items = [];
        for (const it of tc.items) {
          if (!it.str || !it.transform) continue;
          // PDF coordinate space (origin bottom-left).
          const fontSize_pdf = Math.hypot(it.transform[2], it.transform[3]);
          if (fontSize_pdf < 1) continue; // ignore degenerate items
          const x_pdf = it.transform[4];
          const baselineY_pdf = it.transform[5];
          const w_pdf = it.width || (it.str.length * fontSize_pdf * 0.5);
          // Canvas (origin top-left, scaled).
          const x_canvas = x_pdf * EDIT_PDF_PREVIEW_SCALE;
          const baseline_canvas = (pdfV.height - baselineY_pdf) * EDIT_PDF_PREVIEW_SCALE;
          const fontSize_canvas = fontSize_pdf * EDIT_PDF_PREVIEW_SCALE;
          const top_canvas = baseline_canvas - fontSize_canvas;
          const w_canvas = w_pdf * EDIT_PDF_PREVIEW_SCALE;
          items.push({
            str: it.str,
            fontName: it.fontName,
            canvas: { x: x_canvas, top: top_canvas, w: w_canvas, h: fontSize_canvas, fontSize: fontSize_canvas },
            pdf:    { x: x_pdf, baselineY: baselineY_pdf, fontSize: fontSize_pdf, w: w_pdf },
          });
        }
        allTexts[i - 1] = items;

        out.push({
          origIndex: i - 1,
          width: viewport.width, height: viewport.height,
          pdfWidth: pdfV.width, pdfHeight: pdfV.height,
          dataUrl: canvas.toDataURL('image/jpeg', 0.85),
          deleted: false, rotation: 0, isBlank: false,
        });
      }
      setPages(out); setActivePage(0); setOverlays({}); setTextEdits({}); setHistory([]);
      setTextItems(allTexts);
      window.__editPdfSourceBytes = sourceBytes;
      setFile(f);
      setPdfName(f.name.replace(/\.pdf$/i, '').slice(0, 60) || 'Untitled');
      setStatus('');
    } catch (e) {
      setErr(e.message || String(e));
      setStatus('');
    } finally {
      setBusy(false);
    }
  };

  // ---------- Page actions ----------
  const togglePageDeleted = (idx) => { snapshot(); setPages((arr) => arr.map((p, i) => i === idx ? { ...p, deleted: !p.deleted } : p)); };
  const rotatePage = (idx) => { snapshot(); setPages((arr) => arr.map((p, i) => i === idx ? { ...p, rotation: ((p.rotation || 0) + 90) % 360 } : p)); };
  const movePage = (idx, dir) => {
    const j = idx + dir; if (j < 0 || j >= pages.length) return;
    snapshot();
    setPages((arr) => { const n = [...arr]; [n[idx], n[j]] = [n[j], n[idx]]; return n; });
    setOverlays((m) => { const n = { ...m }; const a = n[idx], b = n[j]; n[idx] = b || []; n[j] = a || []; return n; });
    setTextItems((m) => { const n = { ...m }; const a = n[idx], b = n[j]; n[idx] = b || []; n[j] = a || []; return n; });
    setTextEdits((m) => { const n = { ...m }; const a = n[idx], b = n[j]; n[idx] = b; n[j] = a; if (!a) delete n[j]; if (!b) delete n[idx]; return n; });
    if (activePage === idx) setActivePage(j); else if (activePage === j) setActivePage(idx);
  };
  const insertBlank = (afterIdx) => {
    snapshot();
    const ref = pages[afterIdx] || pages[pages.length - 1] || { width: 612 * EDIT_PDF_PREVIEW_SCALE, height: 792 * EDIT_PDF_PREVIEW_SCALE, pdfWidth: 612, pdfHeight: 792 };
    const blankCanvas = document.createElement('canvas');
    blankCanvas.width = ref.width; blankCanvas.height = ref.height;
    const ctx = blankCanvas.getContext('2d'); ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, blankCanvas.width, blankCanvas.height);
    const blank = {
      origIndex: null, isBlank: true,
      width: ref.width, height: ref.height,
      pdfWidth: ref.pdfWidth, pdfHeight: ref.pdfHeight,
      dataUrl: blankCanvas.toDataURL('image/png'),
      deleted: false, rotation: 0,
    };
    setPages((arr) => { const n = [...arr]; n.splice(afterIdx + 1, 0, blank); return n; });
    // Shift indexed maps for indexes >= afterIdx+1.
    const shift = (m) => {
      const next = {};
      for (const [k, v] of Object.entries(m)) {
        const i = parseInt(k, 10);
        next[i >= afterIdx + 1 ? i + 1 : i] = v;
      }
      return next;
    };
    setOverlays(shift); setTextItems(shift); setTextEdits(shift);
    setActivePage(afterIdx + 1);
  };

  // ---------- Overlay placement ----------
  const onCanvasPointerDown = (e) => {
    if (tool === 'select' || tool === 'undo') return;
    const rect = e.currentTarget.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;

    if (tool === 'highlight' || tool === 'blackout' || tool === 'shape') {
      e.currentTarget.setPointerCapture(e.pointerId);
      drawState.current = { startX: x, startY: y, x, y, w: 0, h: 0 };
      return;
    }

    snapshot();
    if (tool === 'text') {
      addOverlay({ id: editPdfNewId(), type: 'text', x, y, text: 'New text', fontSize: textCfg.fontSize, color: textCfg.color });
      setTool('select');
    } else if (tool === 'check') {
      addOverlay({ id: editPdfNewId(), type: 'check', x, y, size: 24, color: '#10805a' });
      setTool('select');
    } else if (tool === 'date') {
      const today = new Date();
      const txt = today.toLocaleDateString();
      addOverlay({ id: editPdfNewId(), type: 'text', x, y, text: txt, fontSize: textCfg.fontSize, color: textCfg.color });
      setTool('select');
    } else if (tool === 'time') {
      const now = new Date();
      const txt = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
      addOverlay({ id: editPdfNewId(), type: 'text', x, y, text: txt, fontSize: textCfg.fontSize, color: textCfg.color });
      setTool('select');
    } else if (tool === 'image' && pendingImage) {
      const w = Math.min(220, pendingImage.width);
      const h = Math.round(w * (pendingImage.height / pendingImage.width));
      addOverlay({ id: editPdfNewId(), type: 'image', x: x - w / 2, y: y - h / 2, w, h, dataUrl: pendingImage.dataUrl, mime: pendingImage.mime });
      setTool('select'); setPendingImage(null);
    }
  };

  const onCanvasPointerMove = (e) => {
    if (!drawState.current) return;
    const rect = e.currentTarget.getBoundingClientRect();
    const cur = { x: e.clientX - rect.left, y: e.clientY - rect.top };
    const d = drawState.current;
    d.w = cur.x - d.startX; d.h = cur.y - d.startY;
    d.x = d.w < 0 ? cur.x : d.startX;
    d.y = d.h < 0 ? cur.y : d.startY;
    // Batch re-renders via rAF so we don't trigger 60+ React reconciliations/sec
    if (!_drawRafPending.current) {
      _drawRafPending.current = true;
      requestAnimationFrame(() => {
        _drawRafPending.current = false;
        setDrawTick((t) => t + 1);
      });
    }
  };

  const onCanvasPointerUp = (e) => {
    const d = drawState.current; if (!d) return;
    drawState.current = null;
    try { e.currentTarget.releasePointerCapture(e.pointerId); } catch {}
    const w = Math.abs(d.w), h = Math.abs(d.h);
    if (w < 6 || h < 6) return; // ignore tiny
    snapshot();
    if (tool === 'highlight') {
      addOverlay({ id: editPdfNewId(), type: 'highlight', x: d.x, y: d.y, w, h, color: highlightColor, opacity: 0.45 });
    } else if (tool === 'blackout') {
      addOverlay({ id: editPdfNewId(), type: 'blackout', x: d.x, y: d.y, w, h });
    } else if (tool === 'shape') {
      addOverlay({
        id: editPdfNewId(), type: 'shape', shape: shapeCfg.shape,
        x: d.x, y: d.y, w, h,
        stroke: shapeCfg.stroke, fill: shapeCfg.fill, strokeWidth: shapeCfg.strokeWidth,
      });
    }
    setTool('select');
  };

  const addOverlay = (ov) => setOverlays((m) => ({ ...m, [activePage]: [...(m[activePage] || []), ov] }));
  const updateOverlay = (idx, id, patch) => setOverlays((m) => ({ ...m, [idx]: (m[idx] || []).map((o) => o.id === id ? { ...o, ...patch } : o) }));
  const deleteOverlay = (idx, id) => { snapshot(); setOverlays((m) => ({ ...m, [idx]: (m[idx] || []).filter((o) => o.id !== id) })); };

  // ---------- Text edits (rewriting original PDF text) ----------
  // Each edit is an object {str, fontSize, color, bold}. Patch merges in;
  // omitted fields fall back to the original item's values.
  const setTextEdit = (pageIdx, itemIdx, patch, item) => {
    setTextEdits((m) => {
      const cur = m[pageIdx]?.[itemIdx];
      const base = cur || {
        str: item?.str ?? '',
        fontSize: item?.pdf?.fontSize ?? 12,
        color: '#111111',
        bold: false,
      };
      const merged = { ...base, ...patch };
      const next = { ...m, [pageIdx]: { ...(m[pageIdx] || {}) } };
      next[pageIdx][itemIdx] = merged;
      return next;
    });
  };
  const revertTextEdit = (pageIdx, itemIdx) => {
    snapshot();
    setTextEdits((m) => {
      const next = { ...m, [pageIdx]: { ...(m[pageIdx] || {}) } };
      delete next[pageIdx][itemIdx];
      if (Object.keys(next[pageIdx]).length === 0) delete next[pageIdx];
      return next;
    });
    setFocusedEdit(null);
  };
  const onTextEditFocus = (pageIdx, itemIdx) => {
    snapshot();
    setFocusedEdit({ page: pageIdx, idx: itemIdx });
  };
  const onTextEditBlur = (e) => {
    // Stay focused if click moves into the popover; the popover stops
    // propagation so this doesn't fire spuriously, but blur fires before
    // the popover input takes focus so we use a microtask delay.
    setTimeout(() => {
      const ae = document.activeElement;
      if (ae && ae.closest && ae.closest('.ep-textedit-format')) return;
      setFocusedEdit(null);
    }, 50);
  };

  // ---------- Drag overlay ----------
  const onOverlayPointerDown = (e, idx, ov) => {
    if (tool !== 'select') return;
    e.stopPropagation();
    e.currentTarget.setPointerCapture(e.pointerId);
    snapshot();
    dragState.current = { id: ov.id, idx, startX: e.clientX, startY: e.clientY, origX: ov.x, origY: ov.y };
  };
  const onOverlayPointerMove = (e) => {
    const d = dragState.current; if (!d) return;
    updateOverlay(d.idx, d.id, { x: d.origX + (e.clientX - d.startX), y: d.origY + (e.clientY - d.startY) });
  };
  const onOverlayPointerUp = (e) => {
    if (dragState.current) { try { e.currentTarget.releasePointerCapture(e.pointerId); } catch {} dragState.current = null; }
  };

  // ---------- Image picker ----------
  const onPickImage = (e) => {
    const f = e.target.files?.[0];
    if (!f) return;
    const reader = new FileReader();
    reader.onload = () => {
      const img = new Image();
      img.onload = () => {
        setPendingImage({ dataUrl: reader.result, mime: f.type, width: img.naturalWidth, height: img.naturalHeight });
        setTool('image');
      };
      img.src = reader.result;
    };
    reader.readAsDataURL(f);
  };

  // ---------- Toolbar click ----------
  const onToolClick = (id) => {
    if (id === 'undo') { undo(); return; }
    if (id === 'image') {
      // Trigger hidden input — handled by label wrapping
      setTool('image'); return;
    }
    if (id === 'sign') { setSignOpen(true); return; }
    setTool(id);
  };

  // ---------- Save: rebuild via pdf-lib ----------
  const save = async () => {
    if (!file && !pages.some((p) => p.isBlank)) return;
    setBusy(true); setErr(''); setSavedBlob(null);
    const t0 = performance.now();
    try {
      setStatus('Loading pdf-lib…');
      await window.loadScript(EDIT_PDF_PDFLIB);
      const { PDFDocument, rgb, degrees } = window.PDFLib;

      setStatus('Loading Unicode font…');
      await ensureEditPdfFonts();

      setStatus('Reading source…');
      const srcBytes = window.__editPdfSourceBytes;
      const src = srcBytes ? await PDFDocument.load(srcBytes) : null;
      const out = await PDFDocument.create();
      // Register fontkit so embedFont accepts custom TTF bytes (Helvetica's
      // WinAnsi encoding can't represent Turkish characters like Ş ğ ç).
      out.registerFontkit(window.fontkit);
      const helv = await out.embedFont(_editPdfFontReg, { subset: true });
      const helvBold = await out.embedFont(_editPdfFontBold, { subset: true });
      const fontFor = (bold) => bold ? helvBold : helv;

      const survivors = pages.map((p, i) => ({ ...p, currentIndex: i })).filter((p) => !p.deleted);
      // Bulk-copy non-blank pages from src in their original positions
      const origRefs = survivors.map((p) => p.origIndex).filter((v) => v != null);
      const copied = src ? await out.copyPages(src, origRefs) : [];
      const copiedByOrig = {}; let ci = 0;
      for (const o of origRefs) copiedByOrig[o] = copied[ci++];

      for (let i = 0; i < survivors.length; i++) {
        const meta = survivors[i];
        let page;
        if (meta.isBlank || meta.origIndex == null) {
          page = out.addPage([meta.pdfWidth, meta.pdfHeight]);
        } else {
          page = out.addPage(copiedByOrig[meta.origIndex]);
        }
        if (meta.rotation) page.setRotation(degrees(meta.rotation));

        // Text edits: cover original glyph area with a white rectangle, redraw
        // new content at the same baseline using per-edit fontSize/color/bold.
        // Helvetica is the only built-in font; non-Latin glyphs may fall back to '?'.
        const edits = textEdits[meta.currentIndex] || {};
        const items = textItems[meta.currentIndex] || [];
        for (const [idxStr, edit] of Object.entries(edits)) {
          const item = items[parseInt(idxStr, 10)];
          if (!item) continue;
          const fs = edit.fontSize ?? item.pdf.fontSize;
          // Cover original glyph area. Use the original fontSize for the cover
          // since that's what's actually on the page; new fontSize is for the
          // redraw. Width is the wider of original or new (rough estimate).
          const origFs = item.pdf.fontSize;
          const coverFs = Math.max(origFs, fs);
          const coverW = Math.max(item.pdf.w, fs * (edit.str || '').length * 0.6);
          page.drawRectangle({
            x: item.pdf.x - 1,
            y: item.pdf.baselineY - coverFs * 0.18,
            width: coverW + 2,
            height: coverFs * 1.18,
            color: rgb(1, 1, 1),
          });
          const c = editPdfHexToRgb(edit.color || '#111111');
          page.drawText(edit.str || '', {
            x: item.pdf.x,
            y: item.pdf.baselineY,
            size: fs,
            font: fontFor(!!edit.bold),
            color: rgb(c.r, c.g, c.b),
          });
        }

        const ovs = overlays[meta.currentIndex] || [];
        const scale = meta.pdfWidth / meta.width; // canvas-px → PDF-pt
        for (const ov of ovs) {
          if (ov.type === 'text') {
            const fontSize = ov.fontSize;
            const xPt = ov.x * scale;
            const baselinePx = ov.y + (fontSize * EDIT_PDF_PREVIEW_SCALE) * 0.78;
            const yPt = meta.pdfHeight - (baselinePx * scale);
            const c = editPdfHexToRgb(ov.color);
            page.drawText(ov.text || '', { x: xPt, y: yPt, size: fontSize, font: helv, color: rgb(c.r, c.g, c.b) });
          } else if (ov.type === 'check') {
            const c = editPdfHexToRgb(ov.color);
            const fontSize = (ov.size || 24);
            const xPt = ov.x * scale;
            const baselinePx = ov.y + (fontSize * EDIT_PDF_PREVIEW_SCALE) * 0.78;
            const yPt = meta.pdfHeight - (baselinePx * scale);
            page.drawText('✓', { x: xPt, y: yPt, size: fontSize, font: helvBold, color: rgb(c.r, c.g, c.b) });
          } else if (ov.type === 'image') {
            const isPng = (ov.mime || '').includes('png') || (ov.dataUrl || '').startsWith('data:image/png');
            const b64 = (ov.dataUrl || '').split(',')[1];
            const arr = new Uint8Array(b64.length);
            const bin = atob(b64); for (let k = 0; k < bin.length; k++) arr[k] = bin.charCodeAt(k);
            const img = isPng ? await out.embedPng(arr) : await out.embedJpg(arr);
            page.drawImage(img, {
              x: ov.x * scale,
              y: meta.pdfHeight - ((ov.y + ov.h) * scale),
              width: ov.w * scale, height: ov.h * scale,
            });
          } else if (ov.type === 'highlight') {
            const c = editPdfHexToRgb(ov.color);
            page.drawRectangle({
              x: ov.x * scale,
              y: meta.pdfHeight - ((ov.y + ov.h) * scale),
              width: ov.w * scale, height: ov.h * scale,
              color: rgb(c.r, c.g, c.b),
              opacity: ov.opacity ?? 0.45,
            });
          } else if (ov.type === 'blackout') {
            page.drawRectangle({
              x: ov.x * scale,
              y: meta.pdfHeight - ((ov.y + ov.h) * scale),
              width: ov.w * scale, height: ov.h * scale,
              color: rgb(0, 0, 0),
            });
          } else if (ov.type === 'shape') {
            const s = editPdfHexToRgb(ov.stroke);
            const xPt = ov.x * scale;
            const yPt = meta.pdfHeight - ((ov.y + ov.h) * scale);
            const wPt = ov.w * scale, hPt = ov.h * scale;
            const opts = {
              x: xPt, y: yPt, width: wPt, height: hPt,
              borderColor: rgb(s.r, s.g, s.b),
              borderWidth: (ov.strokeWidth || 2) * scale,
            };
            if (ov.fill && ov.fill !== 'transparent') {
              const f = editPdfHexToRgb(ov.fill);
              opts.color = rgb(f.r, f.g, f.b);
            }
            if (ov.shape === 'circle') {
              page.drawEllipse({
                x: xPt + wPt / 2, y: yPt + hPt / 2,
                xScale: wPt / 2, yScale: hPt / 2,
                borderColor: opts.borderColor, borderWidth: opts.borderWidth,
                color: opts.color,
              });
            } else {
              page.drawRectangle(opts);
            }
          }
        }
      }

      setStatus('Saving…');
      const data = await out.save({ useObjectStreams: true });
      const blob = new Blob([data], { type: 'application/pdf' });
      setSavedBlob(blob);
      // Trigger the download immediately so the user gets the file without
      // having to click the green action bar.
      window.downloadBlob(blob, (pdfName || 'document') + '.pdf');
      setStatus('');
      window.mmTrackComplete?.('edit-pdf', { durationMs: Math.round(performance.now() - t0), success: true });
    } catch (e) {
      setErr(e.message || String(e));
      setStatus('');
      window.mmTrackComplete?.('edit-pdf', { durationMs: Math.round(performance.now() - t0), success: false });
    } finally {
      setBusy(false);
    }
  };

  const download = () => {
    if (!savedBlob) return;
    window.downloadBlob(savedBlob, (pdfName || 'document') + '.pdf');
  };
  const reset = () => {
    setFile(null); setPages([]); setOverlays({}); setActivePage(0);
    setSavedBlob(null); setErr(''); setStatus(''); setTool('select');
    setPendingImage(null); setHistory([]); setPdfName('Untitled');
    setTextItems({}); setTextEdits({});
    window.__editPdfSourceBytes = null;
  };

  // ---------- Empty state ----------
  if (!file) return (
    <div className={fullscreen ? 'ep-fullscreen-empty' : ''}>
      {fullscreen && (
        <div className="ep-fs-topbar">
          <button className="dash-back" onClick={onExit}>
            <window.Icon name="arrow" size={14} style={{ transform: 'rotate(180deg)' }} />
            <span>MiniMagics</span>
          </button>
          <div style={{ flex: 1, textAlign: 'center', fontWeight: 700 }}>Edit PDF</div>
          <div style={{ width: 100 }} />
        </div>
      )}
      <div style={{ maxWidth: 720, margin: fullscreen ? '40px auto' : 0, padding: fullscreen ? '0 20px' : 0 }}>
        <window.Dropzone onFile={handleFile} title="Drop a PDF here" hint="rich editor: edit existing text, add overlays, reorder pages" accept="application/pdf,.pdf" />
        {busy && <div className="cmp-meta" style={{ textAlign: 'center', marginTop: 12 }}>{status}</div>}
        {err && <div style={{ color: '#c8321f', textAlign: 'center', marginTop: 12 }}>{err}</div>}
        <div className="cmp-meta" style={{ textAlign: 'center', marginTop: 14 }}>
          Everything runs in your browser. PDF stays local.
        </div>
      </div>
      <EditPdfStyles />
    </div>
  );

  const page = pages[activePage];
  const pageOverlays = overlays[activePage] || [];

  return (
    <div className={`ep-shell ${fullscreen ? 'ep-shell-fs' : ''}`}>
      {/* ---------- Top header: brand-tool name + PDF name ---------- */}
      <div className="ep-header">
        {fullscreen && (
          <button className="dash-back" onClick={onExit} title="Back to MiniMagics" style={{ marginRight: 8 }}>
            <window.Icon name="arrow" size={14} style={{ transform: 'rotate(180deg)' }} />
            <span>MiniMagics</span>
          </button>
        )}
        <div className="ep-header-title">
          <window.Icon name="edit" size={16} />
          <strong>Edit PDF</strong>
        </div>
        <div className="ep-header-name">
          <input
            className="ep-name-input"
            value={pdfName}
            onChange={(e) => setPdfName(e.target.value)}
            placeholder="Document name"
            spellCheck={false}
          />
          <window.Icon name="edit" size={12} style={{ opacity: 0.6 }} />
        </div>
        <div className="ep-header-actions">
          <button className="btn btn-secondary" onClick={reset} disabled={busy}>
            <window.Icon name="upload" size={14} /> New PDF
          </button>
          <button className="btn btn-primary" onClick={save} disabled={busy}>
            <window.Icon name="check" size={14} /> {busy ? 'Saving…' : 'Save'}
          </button>
        </div>
      </div>

      {/* ---------- Tool ribbon ---------- */}
      <div className="ep-ribbon">
        {EDIT_PDF_TOOLS.map((tw) => {
          const isActive = tool === tw.id || (tw.id === 'image' && (tool === 'image' || pendingImage));
          const tip = `${tw.label} — ${tw.tip}`;
          if (tw.id === 'image') {
            return (
              <label key={tw.id} className={`ep-tool ${isActive ? 'on' : ''}`} title={tip}>
                <window.Icon name={tw.icon} size={18} />
                <span>{tw.label}</span>
                <input type="file" accept="image/*" hidden onChange={onPickImage} />
              </label>
            );
          }
          return (
            <button key={tw.id} title={tip}
                    className={`ep-tool ${isActive ? 'on' : ''} ${tw.id === 'undo' && history.length === 0 ? 'disabled' : ''}`}
                    disabled={tw.id === 'undo' && history.length === 0}
                    onClick={() => onToolClick(tw.id)}>
              <window.Icon name={tw.icon} size={18} />
              <span>{tw.label}</span>
            </button>
          );
        })}
      </div>

      {/* ---------- Tool config strip (visible when relevant tool is active) ---------- */}
      {(tool === 'text' || tool === 'date' || tool === 'time') && (
        <div className="ep-cfg">
          <span className="cmp-meta">Text size</span>
          <input type="number" min="8" max="96" className="mini-input" style={{ width: 70 }}
                 value={textCfg.fontSize}
                 onChange={(e) => setTextCfg({ ...textCfg, fontSize: Math.max(8, Number(e.target.value) || 16) })} />
          <span className="cmp-meta">Color</span>
          <input type="color" className="ep-color" value={textCfg.color}
                 onChange={(e) => setTextCfg({ ...textCfg, color: e.target.value })} />
          <span className="cmp-meta">{tool === 'text' ? 'Click on the page to drop a text box.' : tool === 'date' ? 'Click on the page to stamp today\'s date.' : 'Click on the page to stamp current time.'}</span>
        </div>
      )}
      {tool === 'highlight' && (
        <div className="ep-cfg">
          <span className="cmp-meta">Highlight color</span>
          <input type="color" className="ep-color" value={highlightColor} onChange={(e) => setHighlightColor(e.target.value)} />
          <span className="cmp-meta">Drag on the page to highlight an area.</span>
        </div>
      )}
      {tool === 'blackout' && (
        <div className="ep-cfg">
          <span className="cmp-meta">Drag on the page to redact a region (solid black).</span>
        </div>
      )}
      {tool === 'shape' && (
        <div className="ep-cfg">
          <span className="cmp-meta">Shape</span>
          <select className="mini-input" style={{ width: 110 }} value={shapeCfg.shape}
                  onChange={(e) => setShapeCfg({ ...shapeCfg, shape: e.target.value })}>
            <option value="rect">Rectangle</option>
            <option value="circle">Circle</option>
          </select>
          <span className="cmp-meta">Stroke</span>
          <input type="color" className="ep-color" value={shapeCfg.stroke} onChange={(e) => setShapeCfg({ ...shapeCfg, stroke: e.target.value })} />
          <span className="cmp-meta">Fill</span>
          <select className="mini-input" style={{ width: 120 }} value={shapeCfg.fill}
                  onChange={(e) => setShapeCfg({ ...shapeCfg, fill: e.target.value })}>
            <option value="transparent">No fill</option>
            <option value="#fff176">Yellow</option>
            <option value="#ef9a9a">Red</option>
            <option value="#a5d6a7">Green</option>
            <option value="#90caf9">Blue</option>
            <option value="#000000">Black</option>
          </select>
          <span className="cmp-meta">Width</span>
          <input type="number" min="1" max="12" className="mini-input" style={{ width: 60 }}
                 value={shapeCfg.strokeWidth}
                 onChange={(e) => setShapeCfg({ ...shapeCfg, strokeWidth: Math.max(1, Number(e.target.value) || 2) })} />
          <span className="cmp-meta">Drag to draw.</span>
        </div>
      )}
      {tool === 'edit-text' && (
        <div className="ep-cfg">
          <span className="cmp-meta">
            Click any existing text on the page to edit it. Edits are redrawn over the original on save (Helvetica fallback).
          </span>
          <span className="cmp-meta" style={{ marginLeft: 'auto' }}>
            {Object.values(textEdits).reduce((s, m) => s + Object.keys(m || {}).length, 0)} edits
          </span>
        </div>
      )}
      {tool === 'check' && <div className="ep-cfg"><span className="cmp-meta">Click to drop a check mark.</span></div>}
      {tool === 'image' && pendingImage && <div className="ep-cfg"><span className="cmp-meta">Click on the page to place the image.</span></div>}
      {tool === 'image' && !pendingImage && <div className="ep-cfg"><span className="cmp-meta">Choose an image from the toolbar first.</span></div>}

      {err && <div style={{ marginTop: 8 }}><window.ToolError error={err} onRetry={save} /></div>}

      {/* ---------- Body: thumbnails + canvas ---------- */}
      <div className={`ep-body ${sidebarCollapsed ? 'collapsed' : ''}`}>
        <aside className="ep-thumbs">
          <div className="ep-thumbs-head">
            <strong>Pages</strong>
            <span className="cmp-meta">{activePage + 1} / {pages.length}</span>
            <button className="ep-iconbtn" onClick={() => setSidebarCollapsed((v) => !v)} title="Collapse">
              {sidebarCollapsed ? '»' : '«'}
            </button>
          </div>
          {!sidebarCollapsed && (
            <>
              <div className="ep-thumb-list">
                {pages.map((p, i) => (
                  <div key={i}
                       className={`ep-thumb ${activePage === i ? 'on' : ''} ${p.deleted ? 'deleted' : ''}`}
                       onClick={() => setActivePage(i)}>
                    <div className="ep-thumb-num">{i + 1}</div>
                    <div className="ep-thumb-img" style={{ transform: `rotate(${p.rotation || 0}deg)` }}>
                      <img src={p.dataUrl} alt="" draggable={false} />
                    </div>
                    <div className="ep-thumb-actions">
                      <button className="ep-iconbtn" onClick={(e) => { e.stopPropagation(); insertBlank(i); }} title="Insert blank page after">
                        <window.Icon name="sparkle" size={11} />
                      </button>
                      <button className="ep-iconbtn" onClick={(e) => { e.stopPropagation(); rotatePage(i); }} title="Rotate 90°">
                        <window.Icon name="rotate" size={11} />
                      </button>
                      <button className="ep-iconbtn" onClick={(e) => { e.stopPropagation(); movePage(i, -1); }} disabled={i === 0} title="Move up">↑</button>
                      <button className="ep-iconbtn" onClick={(e) => { e.stopPropagation(); movePage(i, 1); }} disabled={i === pages.length - 1} title="Move down">↓</button>
                      <button className="ep-iconbtn ep-danger" onClick={(e) => { e.stopPropagation(); togglePageDeleted(i); }} title={p.deleted ? 'Restore' : 'Delete'}>
                        <window.Icon name={p.deleted ? 'rotate' : 'x'} size={11} />
                      </button>
                    </div>
                  </div>
                ))}
              </div>
              <button className="ep-add-page" onClick={() => insertBlank(pages.length - 1)}>
                <window.Icon name="sparkle" size={12} /> Add blank page
              </button>
            </>
          )}
        </aside>

        <div className="ep-canvas-wrap" ref={canvasRef}>
          {page && (
            <div
              className="ep-canvas"
              style={{
                width: page.width, height: page.height,
                opacity: page.deleted ? 0.4 : 1,
                cursor: tool === 'select' ? 'default' : 'crosshair',
                transform: `rotate(${page.rotation || 0}deg)`,
              }}
              onPointerDown={onCanvasPointerDown}
              onPointerMove={onCanvasPointerMove}
              onPointerUp={onCanvasPointerUp}>
              <img src={page.dataUrl} alt="" draggable={false}
                   style={{ width: '100%', height: '100%', display: 'block', userSelect: 'none' }} />

              {/* Text-edit overlays. Default state is invisible — only the
                  dashed outline shows in edit mode; the input text is
                  transparent so the original PDF text shows through cleanly.
                  When the box is focused or edited, a white cover hides the
                  original and the new text is rendered in the chosen style. */}
              {(textItems[activePage] || []).map((item, idx) => {
                const edit = textEdits[activePage]?.[idx];
                const isEdited = !!edit;
                const visible = tool === 'edit-text' || isEdited;
                if (!visible) return null;
                const isFocused = focusedEdit && focusedEdit.page === activePage && focusedEdit.idx === idx;
                const value = isEdited ? edit.str : item.str;
                const showText = isFocused || isEdited;
                const fontSize_canvas = (edit?.fontSize ?? item.pdf.fontSize) * EDIT_PDF_PREVIEW_SCALE;
                const color = edit?.color || '#111111';
                const bold = !!edit?.bold;
                // Same font for both the visible input and the invisible ghost
                // span. The ghost mirrors the value so the wrapper grows with
                // content (auto-size as the user types).
                const inputFont = `${bold ? '700 ' : ''}${fontSize_canvas}px Helvetica, Arial, sans-serif`;
                return (
                  <div key={'t' + idx}
                       className={`ep-textedit-box ${isEdited ? 'edited' : ''} ${tool === 'edit-text' ? 'editmode' : ''} ${isFocused ? 'focus' : ''}`}
                       style={{
                         left: item.canvas.x,
                         top: item.canvas.top,
                         minWidth: Math.max(item.canvas.w, item.canvas.fontSize * 2),
                         height: fontSize_canvas * 1.18,
                       }}
                       onPointerDown={(e) => e.stopPropagation()}>
                    {showText && <div className="ep-textedit-cover" />}
                    <span className="ep-textedit-ghost" style={{ font: inputFont }} aria-hidden="true">
                      {(value && value.length > 0) ? value : ' '}
                    </span>
                    <input
                      className="ep-textedit-input"
                      value={value}
                      onFocus={() => onTextEditFocus(activePage, idx)}
                      onBlur={onTextEditBlur}
                      readOnly={tool !== 'edit-text'}
                      onChange={(e) => setTextEdit(activePage, idx, { str: e.target.value }, item)}
                      style={{
                        font: inputFont,
                        color: showText ? color : 'transparent',
                      }}
                    />
                    {isFocused && tool === 'edit-text' && (
                      <FormatPopover
                        edit={edit || { str: item.str, fontSize: item.pdf.fontSize, color: '#111111', bold: false }}
                        onChange={(patch) => setTextEdit(activePage, idx, patch, item)}
                        onRevert={isEdited ? () => revertTextEdit(activePage, idx) : null}
                      />
                    )}
                  </div>
                );
              })}

              {pageOverlays.map((ov) => renderOverlay(ov))}

              {/* Live preview while drawing rects */}
              {drawState.current && (tool === 'highlight' || tool === 'blackout' || tool === 'shape') && (() => {
                const d = drawState.current;
                const w = Math.abs(d.w), h = Math.abs(d.h);
                if (tool === 'highlight') return <div className="ep-preview-rect" style={{ left: d.x, top: d.y, width: w, height: h, background: highlightColor, opacity: 0.45 }} />;
                if (tool === 'blackout') return <div className="ep-preview-rect" style={{ left: d.x, top: d.y, width: w, height: h, background: '#000' }} />;
                if (tool === 'shape') {
                  if (shapeCfg.shape === 'circle') return <div className="ep-preview-rect" style={{ left: d.x, top: d.y, width: w, height: h, border: `${shapeCfg.strokeWidth}px solid ${shapeCfg.stroke}`, borderRadius: '50%', background: shapeCfg.fill === 'transparent' ? 'transparent' : shapeCfg.fill }} />;
                  return <div className="ep-preview-rect" style={{ left: d.x, top: d.y, width: w, height: h, border: `${shapeCfg.strokeWidth}px solid ${shapeCfg.stroke}`, background: shapeCfg.fill === 'transparent' ? 'transparent' : shapeCfg.fill }} />;
                }
                return null;
              })()}

              {page.deleted && (
                <div className="ep-deleted-banner"><window.Icon name="x" size={14} /> Marked for deletion</div>
              )}
            </div>
          )}
          {busy && <div className="cmp-meta" style={{ textAlign: 'center', marginTop: 12 }}>{status}</div>}
        </div>
      </div>

      {/* ---------- Save bar ---------- */}
      {savedBlob && (
        <div className="ep-savebar">
          <window.Icon name="check" size={14} />
          <span>PDF rebuilt · {window.fmtBytes(savedBlob.size)}</span>
          <span style={{ flex: 1 }} />
          <button className="btn btn-primary" onClick={download}>
            <window.Icon name="download" size={16} /> Download
          </button>
        </div>
      )}

      {signOpen && (
        <SignaturePad
          onClose={() => setSignOpen(false)}
          onSave={(dataUrl, w, h) => {
            setSignOpen(false);
            // Place the signature at center of active page
            const targetW = Math.min(220, page?.width / 2 || 220);
            const targetH = Math.round(targetW * (h / w));
            snapshot();
            addOverlay({
              id: editPdfNewId(), type: 'image',
              x: ((page?.width || targetW) - targetW) / 2,
              y: ((page?.height || targetH) - targetH) / 2,
              w: targetW, h: targetH,
              dataUrl, mime: 'image/png',
            });
          }}
        />
      )}

      <EditPdfStyles />
    </div>
  );

  // ---------- Overlay renderers (closures over state setters) ----------
  function renderOverlay(ov) {
    const commonStyle = { position: 'absolute', left: ov.x, top: ov.y, cursor: tool === 'select' ? 'move' : 'default' };
    const hover = (
      <button className="ep-iconbtn ep-overlay-x" onClick={(e) => { e.stopPropagation(); deleteOverlay(activePage, ov.id); }}>×</button>
    );
    if (ov.type === 'text') {
      return (
        <div key={ov.id} className="ep-overlay" style={commonStyle}
             onPointerDown={(e) => onOverlayPointerDown(e, activePage, ov)}
             onPointerMove={onOverlayPointerMove}
             onPointerUp={onOverlayPointerUp}>
          <input className="ep-text-input" value={ov.text}
                 onPointerDown={(e) => e.stopPropagation()}
                 onChange={(e) => updateOverlay(activePage, ov.id, { text: e.target.value })}
                 style={{ font: `${ov.fontSize * EDIT_PDF_PREVIEW_SCALE}px Helvetica, Arial, sans-serif`, color: ov.color }} />
          {hover}
        </div>
      );
    }
    if (ov.type === 'check') {
      return (
        <div key={ov.id} className="ep-overlay" style={commonStyle}
             onPointerDown={(e) => onOverlayPointerDown(e, activePage, ov)}
             onPointerMove={onOverlayPointerMove}
             onPointerUp={onOverlayPointerUp}>
          <span style={{ font: `bold ${ov.size * EDIT_PDF_PREVIEW_SCALE}px Helvetica, Arial, sans-serif`, color: ov.color, lineHeight: 1, userSelect: 'none' }}>✓</span>
          {hover}
        </div>
      );
    }
    if (ov.type === 'image') {
      return (
        <div key={ov.id} className="ep-overlay" style={commonStyle}
             onPointerDown={(e) => onOverlayPointerDown(e, activePage, ov)}
             onPointerMove={onOverlayPointerMove}
             onPointerUp={onOverlayPointerUp}>
          <div className="ep-image-overlay" style={{ width: ov.w, height: ov.h }}>
            <img src={ov.dataUrl} alt="" draggable={false} style={{ width: '100%', height: '100%', display: 'block', pointerEvents: 'none' }} />
            {hover}
            <div className="ep-resize-handle" onPointerDown={(e) => {
              e.stopPropagation();
              e.currentTarget.setPointerCapture(e.pointerId);
              const sx = e.clientX, sy = e.clientY, sw = ov.w, sh = ov.h;
              const aspect = sh / sw;
              const onMove = (ev) => {
                const w = Math.max(20, sw + (ev.clientX - sx));
                updateOverlay(activePage, ov.id, { w, h: Math.round(w * aspect) });
              };
              const onUp = () => { window.removeEventListener('pointermove', onMove); window.removeEventListener('pointerup', onUp); };
              window.addEventListener('pointermove', onMove);
              window.addEventListener('pointerup', onUp);
            }} />
          </div>
        </div>
      );
    }
    if (ov.type === 'highlight') {
      return (
        <div key={ov.id} className="ep-overlay" style={commonStyle}
             onPointerDown={(e) => onOverlayPointerDown(e, activePage, ov)}
             onPointerMove={onOverlayPointerMove}
             onPointerUp={onOverlayPointerUp}>
          <div style={{ width: ov.w, height: ov.h, background: ov.color, opacity: ov.opacity ?? 0.45, mixBlendMode: 'multiply' }} />
          {hover}
        </div>
      );
    }
    if (ov.type === 'blackout') {
      return (
        <div key={ov.id} className="ep-overlay" style={commonStyle}
             onPointerDown={(e) => onOverlayPointerDown(e, activePage, ov)}
             onPointerMove={onOverlayPointerMove}
             onPointerUp={onOverlayPointerUp}>
          <div style={{ width: ov.w, height: ov.h, background: '#000' }} />
          {hover}
        </div>
      );
    }
    if (ov.type === 'shape') {
      const inner = {
        width: ov.w, height: ov.h,
        border: `${ov.strokeWidth}px solid ${ov.stroke}`,
        background: ov.fill === 'transparent' ? 'transparent' : ov.fill,
        borderRadius: ov.shape === 'circle' ? '50%' : 0,
      };
      return (
        <div key={ov.id} className="ep-overlay" style={commonStyle}
             onPointerDown={(e) => onOverlayPointerDown(e, activePage, ov)}
             onPointerMove={onOverlayPointerMove}
             onPointerUp={onOverlayPointerUp}>
          <div style={inner} />
          {hover}
        </div>
      );
    }
    return null;
  }
};

// ---------- Inline format popover for an active text edit ----------
function FormatPopover({ edit, onChange, onRevert }) {
  return (
    <div className="ep-textedit-format" onPointerDown={(e) => e.stopPropagation()}>
      <label title="Font size">
        <window.Icon name="text" size={11} />
        <input
          type="number" min="6" max="96"
          className="mini-input"
          style={{ width: 50, padding: '2px 4px', fontSize: 11 }}
          value={Math.round(edit.fontSize)}
          onChange={(e) => {
            const v = Math.max(6, Math.min(96, Number(e.target.value) || edit.fontSize));
            onChange({ fontSize: v });
          }}
        />
      </label>
      <label title="Color">
        <input
          type="color"
          value={edit.color}
          onChange={(e) => onChange({ color: e.target.value })}
          className="ep-color"
          style={{ width: 22, height: 22, padding: 0 }}
        />
      </label>
      <button
        className={`ep-fmt-btn ${edit.bold ? 'on' : ''}`}
        onClick={() => onChange({ bold: !edit.bold })}
        title="Bold"
      ><strong>B</strong></button>
      {onRevert && (
        <button className="ep-fmt-btn" onClick={onRevert} title="Revert to original">
          <window.Icon name="rotate" size={11} />
        </button>
      )}
    </div>
  );
}

// ---------- Signature pad modal ----------
function SignaturePad({ onClose, onSave }) {
  const canvasRef = React.useRef(null);
  const drawing = React.useRef(false);
  const [hasInk, setHasInk] = React.useState(false);
  const [color, setColor] = React.useState('#000000');
  const [width, setWidth] = React.useState(3);

  React.useEffect(() => {
    const c = canvasRef.current; if (!c) return;
    const ctx = c.getContext('2d');
    ctx.lineCap = 'round'; ctx.lineJoin = 'round';
    ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, c.width, c.height);
    const onKey = (e) => e.key === 'Escape' && onClose();
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onClose]);

  const at = (e) => {
    const c = canvasRef.current; const r = c.getBoundingClientRect();
    return { x: (e.clientX - r.left) * (c.width / r.width), y: (e.clientY - r.top) * (c.height / r.height) };
  };
  const down = (e) => {
    drawing.current = true; setHasInk(true);
    const c = canvasRef.current; const ctx = c.getContext('2d');
    const p = at(e); ctx.beginPath(); ctx.moveTo(p.x, p.y);
    e.currentTarget.setPointerCapture(e.pointerId);
  };
  const move = (e) => {
    if (!drawing.current) return;
    const c = canvasRef.current; const ctx = c.getContext('2d');
    ctx.strokeStyle = color; ctx.lineWidth = width;
    const p = at(e); ctx.lineTo(p.x, p.y); ctx.stroke();
  };
  const up = () => { drawing.current = false; };

  const clear = () => {
    const c = canvasRef.current; const ctx = c.getContext('2d');
    ctx.clearRect(0, 0, c.width, c.height);
    ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, c.width, c.height);
    setHasInk(false);
  };

  const save = () => {
    if (!hasInk) return;
    const c = canvasRef.current;
    // Trim transparent margins by exporting tightly cropped image
    onSave(c.toDataURL('image/png'), c.width, c.height);
  };

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 600 }}>
        <div className="modal-head">
          <div className="mh-icon" style={{ background: 'var(--id-brand-blue-soft)', color: 'var(--id-brand-blue)' }}>
            <window.Icon name="sign" size={22} />
          </div>
          <div style={{ flex: 1 }}>
            <div className="mh-name">Draw your signature</div>
            <div className="mh-desc">Sign in the box; we'll drop it onto the active page.</div>
          </div>
          <button className="mh-close" onClick={onClose} aria-label="Close"><window.Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body">
          <canvas
            ref={canvasRef}
            width={560} height={220}
            onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={up}
            style={{ width: '100%', height: 220, border: '1px solid var(--id-border)', borderRadius: 8, background: '#fff', touchAction: 'none', cursor: 'crosshair' }}
          />
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginTop: 12 }}>
            <span className="cmp-meta">Color</span>
            <input type="color" className="ep-color" value={color} onChange={(e) => setColor(e.target.value)} />
            <span className="cmp-meta">Stroke</span>
            <input type="range" min="1" max="8" value={width} onChange={(e) => setWidth(Number(e.target.value))} />
            <span style={{ flex: 1 }} />
            <button className="btn btn-secondary" onClick={clear}>Clear</button>
            <button className="btn btn-primary" onClick={save} disabled={!hasInk}><window.Icon name="check" size={14} /> Add to page</button>
          </div>
        </div>
      </div>
    </div>
  );
}

// ---------- Stylesheet for the editor (kept inline so we don't pollute app.css) ----------
function EditPdfStyles() {
  return <style>{`
    .ep-shell { display: flex; flex-direction: column; }
    .ep-shell-fs {
      position: fixed; inset: 0; z-index: 90;
      padding: 12px; background: var(--id-bg, var(--id-surface));
      overflow: auto;
    }
    .ep-shell-fs .ep-canvas-wrap { max-height: calc(100vh - 280px); }
    .ep-shell-fs .ep-thumbs { max-height: calc(100vh - 280px); }
    .ep-fullscreen-empty {
      position: fixed; inset: 0; z-index: 90;
      background: var(--id-bg, var(--id-surface));
      overflow: auto;
    }
    .ep-fs-topbar {
      display: flex; align-items: center; gap: 12px;
      padding: 12px 20px; border-bottom: 1px solid var(--id-border);
    }

    /* Header */
    .ep-header {
      display: flex; align-items: center; gap: 14px;
      padding: 8px 12px; margin-bottom: 8px;
      background: var(--id-surface); border: 1px solid var(--id-border);
      border-radius: 10px;
    }
    .ep-header-title { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--id-text); }
    .ep-header-name {
      flex: 1; display: flex; align-items: center; gap: 6px;
      padding: 4px 10px; background: var(--id-surface-alt); border-radius: 6px;
      max-width: 360px;
    }
    .ep-name-input {
      flex: 1; background: transparent; border: 0; font: inherit;
      color: var(--id-text); padding: 2px 0; outline: none; min-width: 60px;
    }
    .ep-header-actions { display: flex; gap: 6px; }

    /* Tool ribbon */
    .ep-ribbon {
      display: flex; gap: 4px; flex-wrap: wrap;
      padding: 8px; background: var(--id-surface);
      border: 1px solid var(--id-border); border-radius: 10px;
      margin-bottom: 8px;
    }
    .ep-tool {
      display: flex; flex-direction: column; align-items: center; gap: 3px;
      padding: 8px 10px; min-width: 64px;
      background: transparent; border: 1px solid transparent; border-radius: 8px;
      color: var(--id-text-muted); font-size: 11px; font-weight: 500;
      cursor: pointer; transition: all .12s ease;
    }
    .ep-tool:hover:not(.disabled):not(:disabled) { background: var(--id-surface-alt); color: var(--id-text); }
    .ep-tool.on {
      background: var(--id-brand-blue); color: #fff; border-color: var(--id-brand-blue);
      box-shadow: 0 4px 14px -6px var(--id-brand-blue);
    }
    .ep-tool.disabled, .ep-tool:disabled { opacity: 0.4; cursor: not-allowed; }

    /* Tool config strip */
    .ep-cfg {
      display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
      padding: 8px 12px; background: var(--id-surface-alt); border-radius: 8px;
      margin-bottom: 8px; font-size: 12px;
    }
    .ep-color {
      width: 28px; height: 28px; padding: 0; border: 1px solid var(--id-border);
      border-radius: 6px; cursor: pointer; background: transparent;
    }

    /* Body grid */
    .ep-body { display: grid; grid-template-columns: 200px 1fr; gap: 10px; }
    .ep-body.collapsed { grid-template-columns: 50px 1fr; }
    @media (max-width: 720px) { .ep-body { grid-template-columns: 1fr; } }

    /* Thumbnails */
    .ep-thumbs {
      display: flex; flex-direction: column;
      background: var(--id-surface); border: 1px solid var(--id-border);
      border-radius: 10px; padding: 10px;
      max-height: 70vh; overflow: hidden;
    }
    .ep-thumbs-head {
      display: flex; align-items: center; gap: 8px;
      padding-bottom: 8px; border-bottom: 1px solid var(--id-border);
      margin-bottom: 8px; font-size: 12px;
    }
    .ep-thumbs-head .cmp-meta { font-family: var(--id-font-mono); }
    .ep-thumbs-head .ep-iconbtn { margin-left: auto; }

    .ep-thumb-list { display: flex; flex-direction: column; gap: 8px; overflow-y: auto; padding-right: 2px; }
    .ep-thumb {
      position: relative; padding: 4px; cursor: pointer;
      border: 2px solid transparent; border-radius: 8px;
      background: var(--id-surface-alt);
    }
    .ep-thumb-img { overflow: hidden; border-radius: 4px; transition: transform .2s; }
    .ep-thumb img { width: 100%; height: auto; display: block; }
    .ep-thumb.on { border-color: var(--id-brand-blue); }
    .ep-thumb.deleted { opacity: 0.4; }
    .ep-thumb.deleted .ep-thumb-img { filter: grayscale(1); }
    .ep-thumb-num {
      position: absolute; top: 6px; left: 6px;
      background: rgba(0,0,0,0.6); color: #fff;
      font-size: 9px; padding: 2px 5px; border-radius: 4px;
      font-family: var(--id-font-mono); z-index: 2;
    }
    .ep-thumb-actions { display: flex; flex-wrap: wrap; gap: 2px; justify-content: center; margin-top: 4px; }

    .ep-iconbtn {
      width: 22px; height: 22px; border-radius: 4px;
      border: 1px solid var(--id-border); background: var(--id-surface);
      font-size: 11px; cursor: pointer; display: inline-flex;
      align-items: center; justify-content: center; color: var(--id-text);
      padding: 0; flex-shrink: 0;
    }
    .ep-iconbtn:hover:not(:disabled) { background: var(--id-surface-alt); border-color: var(--id-brand-blue); color: var(--id-brand-blue); }
    .ep-iconbtn:disabled { opacity: 0.35; cursor: not-allowed; }
    .ep-iconbtn.ep-danger:hover { color: #c8321f; border-color: #c8321f; }

    .ep-add-page {
      margin-top: 8px; padding: 8px; width: 100%;
      border: 1px dashed var(--id-border); border-radius: 6px;
      background: transparent; cursor: pointer; color: var(--id-text-muted);
      font-size: 12px; display: flex; align-items: center; justify-content: center; gap: 6px;
    }
    .ep-add-page:hover { border-color: var(--id-brand-blue); color: var(--id-brand-blue); background: var(--id-surface-alt); }

    /* Canvas */
    .ep-canvas-wrap {
      background: var(--id-surface-alt); border-radius: 10px;
      padding: 16px; max-height: 75vh; overflow: auto;
      display: flex; justify-content: center; align-items: flex-start;
    }
    .ep-canvas {
      position: relative; box-shadow: var(--id-shadow-lg);
      background: #fff; border-radius: 4px; transition: transform .25s ease;
      touch-action: none;
    }

    .ep-overlay { user-select: none; }
    .ep-text-input {
      background: rgba(255,255,255,0.9);
      border: 1px dashed var(--id-brand-blue); padding: 2px 4px;
      border-radius: 3px; min-width: 80px; outline: none; line-height: 1;
    }
    .ep-text-input:focus { background: #fff; border-style: solid; }
    .ep-overlay:hover .ep-overlay-x,
    .ep-overlay:hover .ep-resize-handle { opacity: 1; }
    .ep-overlay-x {
      position: absolute; top: -10px; right: -10px;
      width: 20px; height: 20px; border-radius: 50%;
      background: #c8321f; color: #fff; border: 0; cursor: pointer;
      font-size: 12px; line-height: 1; opacity: 0; transition: opacity .15s; padding: 0;
      z-index: 3;
    }
    .ep-image-overlay { position: relative; outline: 1px dashed var(--id-brand-blue); outline-offset: 2px; }
    .ep-resize-handle {
      position: absolute; right: -6px; bottom: -6px;
      width: 14px; height: 14px; border-radius: 50%;
      background: var(--id-brand-blue); cursor: nwse-resize;
      opacity: 0; transition: opacity .15s; z-index: 3;
    }
    .ep-preview-rect { position: absolute; pointer-events: none; }

    /* Editable original-text overlays.
       The wrapper is absolutely positioned but uses content-driven width: a
       hidden ghost <span> mirrors the input value so the box auto-grows and
       auto-shrinks as the user types. Min-width keeps the box at least the
       size of the original glyph run (so the white cover hides everything). */
    .ep-textedit-box {
      position: absolute;
      pointer-events: auto;
      box-sizing: border-box;
      transition: outline-color .12s;
      /* width is determined by the ghost span (intrinsic) clamped to min-width */
    }
    .ep-textedit-box.editmode { outline: 1px dashed rgba(37, 99, 235, 0.25); }
    .ep-textedit-box.editmode:hover { outline-color: var(--id-brand-blue); }
    .ep-textedit-box.focus { outline: 2px solid var(--id-brand-blue); }
    .ep-textedit-box.edited { outline: 1px solid rgba(234, 88, 12, 0.55); }
    .ep-textedit-box.edited.focus { outline: 2px solid #ea580c; }

    .ep-textedit-cover {
      position: absolute; inset: -1px -2px;
      background: #fff; border-radius: 1px;
      pointer-events: none; z-index: 0;
    }
    .ep-textedit-ghost {
      display: inline-block;
      visibility: hidden;
      white-space: pre;
      pointer-events: none;
      padding: 0 1px;
      height: 100%;
      line-height: 1;
      box-sizing: border-box;
    }
    .ep-textedit-input {
      position: absolute; inset: 0; z-index: 1;
      width: 100%; height: 100%; box-sizing: border-box;
      background: transparent; border: 0; outline: none; padding: 0 1px;
      font-family: Helvetica, Arial, sans-serif;
      line-height: 1;
    }
    .ep-textedit-box:not(.editmode) .ep-textedit-input { pointer-events: none; }

    /* Floating format popover (size, color, bold, revert). */
    .ep-textedit-format {
      position: absolute; left: 0; top: -34px;
      display: flex; align-items: center; gap: 6px;
      padding: 4px 6px;
      background: var(--id-surface); color: var(--id-text);
      border: 1px solid var(--id-border); border-radius: 6px;
      box-shadow: var(--id-shadow-lg);
      font-size: 11px; white-space: nowrap;
      z-index: 5;
    }
    .ep-textedit-format label { display: inline-flex; align-items: center; gap: 4px; }
    .ep-fmt-btn {
      width: 24px; height: 24px; border-radius: 4px;
      background: transparent; border: 1px solid transparent;
      cursor: pointer; color: var(--id-text); padding: 0;
      display: inline-flex; align-items: center; justify-content: center;
    }
    .ep-fmt-btn:hover { background: var(--id-surface-alt); border-color: var(--id-border); }
    .ep-fmt-btn.on { background: var(--id-brand-blue); color: #fff; border-color: var(--id-brand-blue); }

    .ep-deleted-banner {
      position: absolute; inset: 0;
      display: flex; align-items: center; justify-content: center;
      gap: 6px; color: #c8321f; font-weight: 700;
      background: rgba(255, 240, 240, 0.5);
      pointer-events: none;
    }

    .ep-savebar {
      display: flex; align-items: center; gap: 10px;
      margin-top: 10px; padding: 10px 14px;
      background: #e7f4ee; color: #10805a;
      border: 1px solid #b8e2cc; border-radius: 8px;
      font-size: 13px; font-weight: 600;
    }
    [data-theme="dark"] .ep-savebar { background: rgba(16,128,90,0.18); border-color: rgba(16,128,90,0.3); color: #5fd1a4; }
  `}</style>;
}
