// Main app: file-tree library, markdown rendering, pointer-driven annotations
// (no native text-selection popup), S Pen drawing, manual finger-scroll.
const { useState, useEffect, useRef, useCallback, useMemo } = React;
const A = window.Annotate;

const HL_COLORS = [
  { id: "amber", label: "Sarı", solid: "#e8b53d" },
  { id: "green", label: "Yeşil", solid: "#5bbf8a" },
  { id: "sky", label: "Mavi", solid: "#5aa6e0" },
  { id: "rose", label: "Pembe", solid: "#e07a93" },
  { id: "violet", label: "Mor", solid: "#a98bdc" },
];
const PEN_COLORS = [
  { id: "accent", label: "Teal", solid: "#5ec9b7" },
  { id: "amber", label: "Sarı", solid: "#e8b53d" },
  { id: "rose", label: "Pembe", solid: "#e07a93" },
  { id: "ink", label: "Beyaz", solid: "#e9e7e2" },
];
const HINTS = {
  read: "Parmakla gez · kelimeye dokunarak işaretle",
  highlight: "Bir cümleye dokun · kalemle sürükle (kelime kelime)",
  note: "Yapışkan not için bir cümleye dokun",
  comment: "Kenar yorumu için bir cümleye dokun",
  pen: "Kalemle çiz · parmakla kaydır",
  eraser: "Silmek için bir çizgiye dokun",
};

function load(key, init) {
  try { const v = localStorage.getItem(key); return v ? JSON.parse(v) : init; } catch { return init; }
}
function save(key, val) { try { localStorage.setItem(key, JSON.stringify(val)); } catch {} }
const uid = () => Math.random().toString(36).slice(2, 9);

// Shown when there are no notes yet (empty data.js) so the app renders instead of crashing.
const EMPTY_NOTE = {
  id: "__empty", name: "", title: "", tags: [], minutes: 0,
  content: "# Henüz not yok\n\nNotlarını `data.js` içindeki `tree` alanına ekle; burada görünecekler.",
};

// ---- cross-device merge (used by sync; union by id, strokes by content) ----
const annoKey = (x) => x.id || JSON.stringify(x);
function mergeAnnoStores(a, b) {
  const out = { ...(a || {}) };
  for (const noteId in (b || {})) {
    const cur = out[noteId] || {}, inc = b[noteId] || {}, merged = { ...cur };
    for (const type of ["highlights", "stickies", "comments", "strokes"]) {
      const byKey = new Map();
      (cur[type] || []).forEach((x) => byKey.set(annoKey(x), x));
      (inc[type] || []).forEach((x) => byKey.set(annoKey(x), x));
      merged[type] = [...byKey.values()];
    }
    out[noteId] = merged;
  }
  return out;
}
const mergeProgress = (a, b) => {
  const out = { ...(a || {}) };
  for (const k in (b || {})) out[k] = Math.max(out[k] || 0, b[k] || 0);
  return out;
};
const mergeById = (a, b) => {
  const m = new Map();
  (a || []).forEach((x) => m.set(x.id, x));
  (b || []).forEach((x) => m.set(x.id, x));
  return [...m.values()];
};

// tree helpers
function ancestorFolders(nodes, id, acc) {
  for (const n of nodes) {
    if (n.type === "file") { if (n.id === id) return acc; }
    else { const r = ancestorFolders(n.children, id, [...acc, n.id]); if (r) return r; }
  }
  return null;
}
function pruneTree(nodes, pred) {
  const out = [];
  for (const n of nodes) {
    if (n.type === "file") { if (pred(n)) out.push(n); }
    else { const ch = pruneTree(n.children, pred); if (ch.length) out.push({ ...n, children: ch }); }
  }
  return out;
}
function collectFolderIds(nodes, acc) {
  nodes.forEach((n) => { if (n.type === "folder") { acc.push(n.id); collectFolderIds(n.children, acc); } });
  return acc;
}

function App() {
  const data = window.STUDY_DATA;
  const allFiles = window.STUDY_FILES;
  const [theme, setTheme] = useState(() => load("sc_theme", "dark"));
  const [search, setSearch] = useState("");
  const [activeTags, setActiveTags] = useState([]);
  const [activeNoteId, setActiveNoteId] = useState(() => load("sc_active", allFiles[0]?.id));
  const [tool, setTool] = useState("read");
  const [hlColor, setHlColor] = useState("amber");
  const [penColor, setPenColor] = useState("accent");
  const [penWidth, setPenWidth] = useState(4);
  const [rightTab, setRightTab] = useState("outline");
  const [focus, setFocus] = useState(false);
  const [leftOpen, setLeftOpen] = useState(true);
  const [rightOpen, setRightOpen] = useState(true);
  const [hint, setHint] = useState(null);
  const [customHl, setCustomHl] = useState(() => load("sc_custom_hl", []));
  const [customPen, setCustomPen] = useState(() => load("sc_custom_pen", []));

  const [annoStore, setAnnoStore] = useState(() => load("sc_anno_v2", {}));
  const [progress, setProgress] = useState(() => load("sc_progress_v2", {}));
  const [outline, setOutline] = useState([]);
  const [markers, setMarkers] = useState([]);
  const [editing, setEditing] = useState(null);
  const [activeHl, setActiveHl] = useState(null);
  const [expanded, setExpanded] = useState(() => {
    const a = ancestorFolders(data.tree, load("sc_active", allFiles[0]?.id), []) || [];
    return new Set(a.length ? a : ["f_bankacilik", "f_temeller"]);
  });

  const scrollRef = useRef(null);
  const contentRef = useRef(null);
  const pageRef = useRef(null);
  const canvasRef = useRef(null);
  const strokeRef = useRef(null);
  const dragRef = useRef(null);
  const touchRef = useRef(null);

  const note = allFiles.find((n) => n.id === activeNoteId) || allFiles[0] || EMPTY_NOTE;
  const anno = annoStore[activeNoteId] || { highlights: [], stickies: [], comments: [], strokes: [] };
  const annoRef = useRef(annoStore); annoRef.current = annoStore;
  // Always read the current note id from a ref so canvas callbacks never go stale.
  const activeRef = useRef(activeNoteId); activeRef.current = activeNoteId;
  // Remembers panel state when entering focus mode so it can be restored on exit.
  const prevPanels = useRef(null);
  // Cross-device sync bookkeeping.
  const pushTimer = useRef(null);
  const syncReady = useRef(false);

  // Built-in palette merged with the user's saved custom colors.
  const hlAll = useMemo(() => [...HL_COLORS, ...customHl], [customHl]);
  const penAll = useMemo(() => [...PEN_COLORS, ...customPen], [customPen]);
  // Color id -> css color. Hex ids (custom colors) resolve directly; named ids look up the palette.
  const hlSolid = (id) => (id && id[0] === "#" ? id : (hlAll.find((x) => x.id === id) || HL_COLORS[0]).solid);
  const penSolid = (id) => (id && id[0] === "#" ? id : (penAll.find((x) => x.id === id) || PEN_COLORS[0]).solid);

  useEffect(() => { save("sc_theme", theme); document.documentElement.dataset.theme = theme; }, [theme]);
  useEffect(() => { save("sc_active", activeNoteId); }, [activeNoteId]);
  useEffect(() => { save("sc_anno_v2", annoStore); }, [annoStore]);
  useEffect(() => { save("sc_progress_v2", progress); }, [progress]);
  useEffect(() => { save("sc_custom_hl", customHl); }, [customHl]);
  useEffect(() => { save("sc_custom_pen", customPen); }, [customPen]);

  // ---------- cross-device sync (Cloudflare Access + KV via /api/state) ----------
  // On load: pull the server copy and merge it into local state.
  useEffect(() => {
    let cancelled = false;
    fetch("/api/state", { headers: { accept: "application/json" } })
      .then((r) => (r.ok ? r.json() : null))
      .then((srv) => {
        if (cancelled || !srv) return;
        if (srv.anno) setAnnoStore((l) => mergeAnnoStores(l, srv.anno));
        if (srv.progress) setProgress((l) => mergeProgress(l, srv.progress));
        if (srv.customHl) setCustomHl((l) => mergeById(l, srv.customHl));
        if (srv.customPen) setCustomPen((l) => mergeById(l, srv.customPen));
      })
      .catch(() => {});
    return () => { cancelled = true; };
  }, []);
  // On change: debounce-push the merged state to the server (offline-safe; localStorage stays the cache).
  useEffect(() => {
    if (!syncReady.current) { syncReady.current = true; return; }
    clearTimeout(pushTimer.current);
    pushTimer.current = setTimeout(() => {
      fetch("/api/state", {
        method: "PUT",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ anno: annoStore, progress, customHl, customPen }),
      }).catch(() => {});
    }, 1500);
    return () => clearTimeout(pushTimer.current);
  }, [annoStore, progress, customHl, customPen]);
  useEffect(() => {
    setHint(HINTS[tool]);
    const t = setTimeout(() => setHint(null), 2600);
    return () => clearTimeout(t);
  }, [tool]);

  const updateAnno = useCallback((noteId, fn) => {
    setAnnoStore((prev) => {
      const cur = prev[noteId] || { highlights: [], stickies: [], comments: [], strokes: [] };
      return { ...prev, [noteId]: fn({ ...cur }) };
    });
  }, []);

  // ---------- tree filtering ----------
  const allTags = useMemo(() => {
    const s = new Set(); allFiles.forEach((f) => f.tags.forEach((t) => s.add(t))); return [...s];
  }, [allFiles]);

  const filtering = search.trim() !== "" || activeTags.length > 0;
  const pred = useCallback((f) => {
    // OR semantics: a note matches if it carries ANY of the selected tags.
    if (activeTags.length && !activeTags.some((t) => f.tags.includes(t))) return false;
    if (search.trim()) {
      const q = search.toLowerCase();
      return f.title.toLowerCase().includes(q) || f.name.toLowerCase().includes(q) ||
        f.content.toLowerCase().includes(q) || f.tags.some((t) => t.includes(q));
    }
    return true;
  }, [search, activeTags]);

  const viewTree = useMemo(() => filtering ? pruneTree(data.tree, pred) : data.tree, [filtering, pred, data.tree]);
  const viewExpanded = useMemo(() => filtering ? new Set(collectFolderIds(viewTree, [])) : expanded, [filtering, viewTree, expanded]);

  const toggleExpand = (id) => setExpanded((p) => { const n = new Set(p); n.has(id) ? n.delete(id) : n.add(id); return n; });
  const toggleTag = (t) => setActiveTags((p) => p.includes(t) ? p.filter((x) => x !== t) : [...p, t]);
  const clearTags = () => setActiveTags([]);

  // Add a custom color (hex id) to the palette and select it.
  const addCustomColor = (which, hex) => {
    hex = (hex || "").toLowerCase();
    if (!/^#[0-9a-f]{6}$/.test(hex)) return;
    if (which === "hl") {
      setHlColor(hex);
      setCustomHl((p) => (p.some((c) => c.id === hex) || HL_COLORS.some((c) => c.solid.toLowerCase() === hex)) ? p : [...p, { id: hex, label: hex, solid: hex }]);
    } else {
      setPenColor(hex);
      setCustomPen((p) => (p.some((c) => c.id === hex) || PEN_COLORS.some((c) => c.solid.toLowerCase() === hex)) ? p : [...p, { id: hex, label: hex, solid: hex }]);
    }
  };

  // Focus mode collapses both panels but keeps the toolbar toggles working,
  // so the sidebar can still be reopened on demand; exiting restores prior state.
  const toggleFocus = () => {
    setFocus((f) => {
      if (!f) { prevPanels.current = { l: leftOpen, r: rightOpen }; setLeftOpen(false); setRightOpen(false); }
      else { const p = prevPanels.current; setLeftOpen(p ? p.l : true); setRightOpen(p ? p.r : true); }
      return !f;
    });
  };
  const progressOf = (id) => progress[id] || 0;
  const annoCountOf = (id) => {
    const a = annoStore[id]; if (!a) return 0;
    return (a.highlights?.length || 0) + (a.stickies?.length || 0) + (a.comments?.length || 0);
  };

  // ---------- render ----------
  const applyAnnotations = useCallback((el) => {
    const a = annoRef.current[activeNoteId] || {};
    const colorOf = hlSolid;
    const paint = (list, cls) => (list || []).forEach((it) => {
      const c = colorOf(it.color);
      for (let i = it.s; i <= it.e; i++) {
        const w = A.wordEl(el, i); if (!w) continue;
        w.classList.add("anno-hl"); if (cls) w.classList.add(cls);
        w.dataset.anno = it.id;
        if (cls !== "anno-comment") { w.style.background = `color-mix(in oklab, ${c} 30%, transparent)`; w.style.setProperty("--hl", c); }
      }
    });
    paint(a.highlights, null);
    paint(a.stickies, "anno-sticky");
    paint(a.comments, "anno-comment");
  }, [activeNoteId]);

  const recomputeMarkers = useCallback(() => {
    const page = pageRef.current, el = contentRef.current;
    if (!page || !el) return;
    const pr = page.getBoundingClientRect();
    const a = annoRef.current[activeNoteId] || {};
    const out = [];
    const add = (list, type) => (list || []).forEach((it) => {
      const w = A.wordEl(el, it.s); if (!w) return;
      const r = w.getBoundingClientRect();
      out.push({ id: it.id, type, top: r.top - pr.top, color: it.color, text: it.text, quote: it.quote });
    });
    add(a.stickies, "stickies"); add(a.comments, "comments");
    out.sort((x, y) => x.top - y.top);
    let last = -100; out.forEach((m) => { if (m.top < last + 34) m.top = last + 34; last = m.top; });
    setMarkers(out);
  }, [activeNoteId]);

  const renderNote = useCallback(() => {
    const el = contentRef.current;
    if (!el || !window.marked) return;
    el.innerHTML = window.marked.parse(note.content, { gfm: true, breaks: false });
    // External links (e.g. in "Kaynaklar") open in a new tab; internal .md links
    // stay in-app (handled by onContentClick).
    el.querySelectorAll("a[href]").forEach((a) => {
      const href = a.getAttribute("href") || "";
      if (/^https?:|^mailto:/i.test(href)) { a.target = "_blank"; a.rel = "noopener noreferrer"; }
    });
    el.querySelectorAll("img").forEach((img) => {
      const ph = document.createElement("div");
      ph.className = "img-ph";
      ph.innerHTML = `<span class="img-ph-label">${img.getAttribute("alt") || "görsel"}</span>`;
      img.replaceWith(ph);
    });
    el.querySelectorAll("pre code").forEach((c) => {
      if (/language-jsx|language-tsx/.test(c.className)) c.className = "language-javascript";
      try { window.hljs && window.hljs.highlightElement(c); } catch {}
    });
    try {
      window.renderMathInElement && window.renderMathInElement(el, {
        delimiters: [{ left: "$$", right: "$$", display: true }, { left: "$", right: "$", display: false }],
        throwOnError: false,
      });
    } catch {}
    setOutline(A.extractOutline(el));
    A.wrapWords(el);
    applyAnnotations(el);
    recomputeMarkers();
  }, [note, applyAnnotations, recomputeMarkers]);

  useEffect(() => { renderNote(); }, [activeNoteId, theme]);
  // On annotation change, only re-apply highlight/note classes — do NOT re-parse
  // the markdown (that full re-render froze the canvas on every stroke/highlight).
  useEffect(() => {
    const el = contentRef.current;
    if (!el || !el.querySelector(".w")) return;
    el.querySelectorAll(".w").forEach((w) => {
      w.classList.remove("anno-hl", "anno-sticky", "anno-comment");
      w.style.background = ""; w.style.removeProperty("--hl"); delete w.dataset.anno;
    });
    applyAnnotations(el);
    recomputeMarkers();
  }, [annoStore]);
  useEffect(() => {
    const onResize = () => { resizeCanvas(); recomputeMarkers(); };
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, [recomputeMarkers]);

  // ---------- annotation CRUD ----------
  const createAnno = (type, fields) => {
    const id = uid();
    updateAnno(activeNoteId, (a) => ({ ...a, [type]: [...(a[type] || []), { id, ...fields }] }));
    return id;
  };
  const deleteAnno = (type, id) => {
    updateAnno(activeNoteId, (a) => ({ ...a, [type]: (a[type] || []).filter((x) => x.id !== id) }));
    setEditing(null); setActiveHl(null);
  };
  const setAnnoText = (type, id, text) =>
    updateAnno(activeNoteId, (a) => ({ ...a, [type]: (a[type] || []).map((x) => x.id === id ? { ...x, text } : x) }));
  const setAnnoColor = (type, id, color) =>
    updateAnno(activeNoteId, (a) => ({ ...a, [type]: (a[type] || []).map((x) => x.id === id ? { ...x, color } : x) }));

  const openEditor = (type, id, rect) => {
    const x = Math.max(16, Math.min(rect.left, window.innerWidth - 290));
    const y = Math.max(70, Math.min((rect.bottom || rect.top) + 8, window.innerHeight - 220));
    setEditing({ type, id, x, y });
  };
  const openEditorAtWord = (type, id, wi) => {
    const w = A.wordEl(contentRef.current, wi);
    openEditor(type, id, w ? w.getBoundingClientRect() : { left: window.innerWidth / 2, top: 200 });
  };

  // ---------- pointer-driven selection ----------
  const wiAt = (x, y) => {
    const el = document.elementFromPoint(x, y);
    const w = el && el.closest && el.closest(".w");
    return w ? Number(w.dataset.wi) : null;
  };
  const previewRange = (s, e) => {
    const c = contentRef.current; if (!c) return;
    c.querySelectorAll(".w.sel-live").forEach((x) => x.classList.remove("sel-live"));
    if (s == null) return;
    const lo = Math.min(s, e), hi = Math.max(s, e);
    for (let i = lo; i <= hi; i++) { const el = A.wordEl(c, i); if (el) el.classList.add("sel-live"); }
  };

  const commit = (s, e) => {
    const quote = A.wordsText(contentRef.current, s, e);
    if (!quote) return;
    if (tool === "highlight") createAnno("highlights", { s, e, quote, color: hlColor });
    else if (tool === "note") { const id = createAnno("stickies", { s, e, quote, color: hlColor, text: "" }); openEditorAtWord("stickies", id, s); }
    else if (tool === "comment") { const id = createAnno("comments", { s, e, quote, text: "" }); openEditorAtWord("comments", id, s); }
  };

  const commitTap = (wi) => {
    if (tool === "highlight") {
      const ex = (anno.highlights || []).find((h) => wi >= h.s && wi <= h.e);
      if (ex) { deleteAnno("highlights", ex.id); return; }
    }
    const [s, e] = A.sentenceBounds(contentRef.current, wi);
    commit(s, e);
  };

  const annotMode = tool === "highlight" || tool === "note" || tool === "comment";

  const onPointerDown = (e) => {
    if (!annotMode) return;
    const wi = wiAt(e.clientX, e.clientY);
    if (e.pointerType === "touch") {
      // finger: tap to annotate, drag to scroll (manual, since touch-action is none)
      touchRef.current = { wi, x: e.clientX, y: e.clientY, top: scrollRef.current.scrollTop, moved: false };
      try { contentRef.current.setPointerCapture(e.pointerId); } catch {}
      return;
    }
    if (wi == null) return;
    e.preventDefault();
    try { contentRef.current.setPointerCapture(e.pointerId); } catch {}
    dragRef.current = { start: wi, last: wi, moved: false };
    previewRange(wi, wi);
  };
  const onPointerMove = (e) => {
    if (dragRef.current) {
      const wi = wiAt(e.clientX, e.clientY);
      if (wi != null && wi !== dragRef.current.last) { dragRef.current.last = wi; dragRef.current.moved = true; previewRange(dragRef.current.start, wi); }
      return;
    }
    if (touchRef.current) {
      const t = touchRef.current;
      const dy = e.clientY - t.y;
      if (Math.abs(dy) > 5 || Math.abs(e.clientX - t.x) > 5) t.moved = true;
      if (t.moved) scrollRef.current.scrollTop = t.top - dy;
    }
  };
  const onPointerUp = (e) => {
    if (dragRef.current) {
      const { start, last, moved } = dragRef.current; dragRef.current = null; previewRange(null);
      if (moved) commit(Math.min(start, last), Math.max(start, last));
      else if (start != null) commitTap(start);
      return;
    }
    if (touchRef.current) {
      const t = touchRef.current; touchRef.current = null;
      if (!t.moved && t.wi != null) commitTap(t.wi);
    }
  };

  // tap an existing annotation (read mode) to recolor / edit
  const onContentClick = (e) => {
    if (tool !== "read") return;
    // Internal note links ([x](x.md)) navigate within the app instead of the browser.
    const a = e.target.closest && e.target.closest("a[href]");
    if (a) {
      const href = a.getAttribute("href") || "";
      if (!/^https?:|^mailto:|^#/.test(href)) {
        const base = href.split(/[#?]/)[0].replace(/\.md$/i, "").split("/").pop();
        const target = allFiles.find((f) => f.id === base || f.name === base + ".md");
        if (target) { e.preventDefault(); openNote(target.id); return; }
      }
      return; // external link: let it behave normally
    }
    const sp = e.target.closest && e.target.closest(".anno-hl");
    if (!sp || !sp.dataset.anno) { setActiveHl(null); return; }
    const id = sp.dataset.anno;
    let type = "highlights";
    if (sp.classList.contains("anno-sticky")) type = "stickies";
    else if (sp.classList.contains("anno-comment")) type = "comments";
    const r = sp.getBoundingClientRect();
    if (type === "highlights") setActiveHl({ id, x: r.left, y: r.bottom + 6 });
    else openEditor(type, id, r);
  };

  // ---------- drawing ----------
  const resizeCanvas = useCallback(() => {
    const cv = canvasRef.current, page = pageRef.current;
    if (!cv || !page) return;
    const dpr = window.devicePixelRatio || 1;
    const w = page.offsetWidth, h = page.offsetHeight;
    cv.width = w * dpr; cv.height = h * dpr; cv.style.width = w + "px"; cv.style.height = h + "px";
    cv.getContext("2d").setTransform(dpr, 0, 0, dpr, 0, 0);
    redraw();
  }, []);
  // Stable across renders; reads the current note via activeRef so it never
  // paints a stale page's strokes after a note switch.
  const redraw = useCallback(() => {
    const cv = canvasRef.current; if (!cv) return;
    const ctx = cv.getContext("2d"); const dpr = window.devicePixelRatio || 1;
    ctx.clearRect(0, 0, cv.width / dpr, cv.height / dpr);
    ((annoRef.current[activeRef.current]?.strokes) || []).forEach((st) => drawStroke(ctx, st));
  }, []);
  // Stroke points are stored normalized to page width (0..1) so drawings stay
  // aligned across orientation/size changes; scaled back up by the live width here.
  const drawStroke = (ctx, st) => {
    if (!st.pts || !st.pts.length) return;
    const W = (canvasRef.current && canvasRef.current.getBoundingClientRect().width) || 1;
    ctx.lineJoin = "round"; ctx.lineCap = "round"; ctx.strokeStyle = penSolid(st.color); ctx.lineWidth = st.width;
    ctx.beginPath(); ctx.moveTo(st.pts[0][0] * W, st.pts[0][1] * W);
    for (let i = 1; i < st.pts.length; i++) ctx.lineTo(st.pts[i][0] * W, st.pts[i][1] * W);
    ctx.stroke();
  };
  useEffect(() => { resizeCanvas(); }, [activeNoteId, note, outline.length]);
  useEffect(() => { redraw(); }, [annoStore, activeNoteId]);

  const ptFromEvent = (e) => { const r = canvasRef.current.getBoundingClientRect(); const W = r.width || 1; return [(e.clientX - r.left) / W, (e.clientY - r.top) / W]; };
  const onCanvasDown = (e) => {
    if (tool !== "pen" && tool !== "eraser") return;
    if (e.pointerType === "touch") { // finger scrolls even over the canvas
      touchRef.current = { y: e.clientY, top: scrollRef.current.scrollTop, scroll: true, moved: false };
      try { canvasRef.current.setPointerCapture(e.pointerId); } catch {}
      return;
    }
    canvasRef.current.setPointerCapture(e.pointerId);
    const p = ptFromEvent(e);
    if (tool === "eraser") { eraseAt(p); strokeRef.current = { erasing: true }; return; }
    strokeRef.current = { color: penColor, width: penWidth, pts: [p] };
  };
  const onCanvasMove = (e) => {
    if (touchRef.current && touchRef.current.scroll) { scrollRef.current.scrollTop = touchRef.current.top - (e.clientY - touchRef.current.y); return; }
    if (!strokeRef.current) return;
    const p = ptFromEvent(e);
    if (strokeRef.current.erasing) { eraseAt(p); return; }
    strokeRef.current.pts.push(p); drawStroke(canvasRef.current.getContext("2d"), strokeRef.current);
  };
  const onCanvasUp = () => {
    if (touchRef.current && touchRef.current.scroll) { touchRef.current = null; return; }
    const st = strokeRef.current; strokeRef.current = null;
    if (!st || st.erasing || st.pts.length < 1) return;
    st.id = uid();
    updateAnno(activeNoteId, (a) => ({ ...a, strokes: [...(a.strokes || []), st] }));
  };
  const eraseAt = (p) => {
    const W = (canvasRef.current && canvasRef.current.getBoundingClientRect().width) || 1;
    updateAnno(activeNoteId, (a) => ({
      ...a, strokes: (a.strokes || []).filter((st) => !st.pts.some(([x, y]) => Math.hypot((x - p[0]) * W, (y - p[1]) * W) < 14 + st.width)),
    }));
  };
  const undoStroke = () => updateAnno(activeNoteId, (a) => ({ ...a, strokes: (a.strokes || []).slice(0, -1) }));
  const clearPage = () => {
    if (!confirm("Bu sayfadaki tüm vurgu, not ve çizimleri temizle?")) return;
    updateAnno(activeNoteId, () => ({ highlights: [], stickies: [], comments: [], strokes: [] }));
  };

  // ---------- progress / nav ----------
  const onScroll = useCallback(() => {
    const s = scrollRef.current; if (!s) return;
    const pct = s.scrollHeight <= s.clientHeight ? 100 : (s.scrollTop / (s.scrollHeight - s.clientHeight)) * 100;
    const v = pct >= 97 ? 100 : pct;
    setProgress((prev) => ({ ...prev, [activeNoteId]: Math.max(prev[activeNoteId] || 0, v) }));
    setActiveHl(null);
  }, [activeNoteId]);

  // ---------- pull-to-refresh (read mode, at top) ----------
  const pullRef = useRef(null);
  const pull = useRef({ y0: 0, active: false, dist: 0 });
  const PULL_THRESH = 70;
  const setPullUI = (dist, ready) => {
    const el = pullRef.current; if (!el) return;
    el.style.opacity = dist <= 0 ? "0" : String(Math.min(1, dist / PULL_THRESH));
    el.style.transform = `translateX(-50%) translateY(${Math.max(0, dist)}px) rotate(${dist * 2.4}deg)`;
    el.classList.toggle("ready", !!ready);
  };
  const onTouchStartScroll = (e) => {
    if (tool !== "read" || e.touches.length !== 1 || !scrollRef.current || scrollRef.current.scrollTop > 0) return;
    pull.current = { y0: e.touches[0].clientY, active: true, dist: 0 };
  };
  const onTouchMoveScroll = (e) => {
    const p = pull.current; if (!p.active) return;
    if (scrollRef.current.scrollTop > 0) { p.active = false; p.dist = 0; setPullUI(0); return; }
    const dy = e.touches[0].clientY - p.y0;
    if (dy <= 0) { p.dist = 0; setPullUI(0); return; }
    p.dist = Math.min(dy * 0.5, 110); // damped
    setPullUI(p.dist, p.dist >= PULL_THRESH);
  };
  const onTouchEndScroll = () => {
    const p = pull.current; if (!p.active) return;
    p.active = false;
    if (p.dist >= PULL_THRESH) { setPullUI(PULL_THRESH, true); window.location.reload(); }
    else setPullUI(0);
  };

  const jumpTo = (id) => { const h = document.getElementById(id); if (h && scrollRef.current) scrollRef.current.scrollTo({ top: h.offsetTop - 24, behavior: "smooth" }); };
  const jumpToAnno = (id) => {
    const sp = contentRef.current?.querySelector(`[data-anno="${id}"]`);
    if (sp && scrollRef.current) {
      scrollRef.current.scrollTo({ top: sp.offsetTop - 90, behavior: "smooth" });
      sp.classList.add("flash"); setTimeout(() => sp.classList.remove("flash"), 1200);
    }
  };
  const openNote = (id) => {
    setActiveNoteId(id); setActiveHl(null); setEditing(null);
    const a = ancestorFolders(data.tree, id, []); if (a) setExpanded((p) => new Set([...p, ...a]));
    if (scrollRef.current) scrollRef.current.scrollTop = 0;
  };

  const drawMode = tool === "pen" || tool === "eraser";
  const path = window.findFilePath(activeNoteId);
  // Clickable breadcrumb: folder ids paired with their display names.
  const crumbIds = ancestorFolders(data.tree, activeNoteId, []) || [];
  const crumbs = crumbIds.map((id, i) => ({ id, name: path[i] }));
  // Reveal a folder crumb in the tree (open the sidebar + expand its chain).
  const revealCrumb = (i) => { setLeftOpen(true); setExpanded((p) => new Set([...p, ...crumbIds.slice(0, i + 1)])); };

  return (
    <div className={"app" + (focus ? " focus" : "")}>
      {leftOpen && (
        <Sidebar tree={viewTree} rootName={data.rootName} activeId={activeNoteId} openNote={openNote}
          search={search} setSearch={setSearch} allTags={allTags} activeTags={activeTags} toggleTag={toggleTag}
          clearTags={clearTags} expanded={viewExpanded} toggle={toggleExpand} progressOf={progressOf} annoCountOf={annoCountOf}
          fileCount={allFiles.length} />
      )}

      <main className="main">
        <TopBar note={note} crumbs={crumbs} onCrumb={revealCrumb} progress={progressOf(activeNoteId)}
          theme={theme} toggleTheme={() => setTheme((t) => t === "dark" ? "light" : "dark")}
          focus={focus} toggleFocus={toggleFocus}
          leftOpen={leftOpen} toggleLeft={() => setLeftOpen((v) => !v)}
          rightOpen={rightOpen} toggleRight={() => setRightOpen((v) => !v)} />

        <div className={"canvas" + (drawMode ? " draw-mode tool-" + tool : "") + (annotMode ? " annot-mode" : "")}>
          <div className="pull-ind" ref={pullRef}><Icons.refresh size={20} /></div>
          <div className="canvas-scroll" ref={scrollRef} onScroll={onScroll}
            onTouchStart={onTouchStartScroll} onTouchMove={onTouchMoveScroll}
            onTouchEnd={onTouchEndScroll} onTouchCancel={onTouchEndScroll}>
            <div className="page-wrap" ref={pageRef}>
              <div className="page">
                <div className="note-head">
                  <span className="nh-folder">{path[path.length - 1] || data.rootName}</span>
                  <div className="nh-tags">{note.tags.map((t) => <span key={t} className="mini-tag">{t}</span>)}</div>
                </div>
                <article className={"md" + (annotMode ? " annot" : "")} ref={contentRef}
                  onPointerDown={onPointerDown} onPointerMove={onPointerMove}
                  onPointerUp={onPointerUp} onPointerCancel={onPointerUp} onClick={onContentClick} />
              </div>
              <canvas className="draw-layer" ref={canvasRef}
                style={{ pointerEvents: drawMode ? "auto" : "none", touchAction: drawMode ? "none" : "auto" }}
                onPointerDown={onCanvasDown} onPointerMove={onCanvasMove} onPointerUp={onCanvasUp} onPointerCancel={onCanvasUp} />
              <div className="markers-layer">
                {markers.map((m) => (
                  <button key={m.id} className={"marker " + m.type} style={{ top: m.top, "--mc": hlSolid(m.color) }}
                    title={m.text || m.quote}
                    onClick={() => openEditorAtWord(m.type, m.id, (anno[m.type].find((x) => x.id === m.id) || {}).s)}>
                    {m.type === "stickies" ? <Icons.note size={15} /> : <Icons.comment size={15} />}
                  </button>
                ))}
              </div>
            </div>
          </div>

          <Toolbar tool={tool} setTool={setTool} hlColors={hlAll} hlColor={hlColor} setHlColor={setHlColor}
            penColors={penAll} penColor={penColor} setPenColor={setPenColor}
            onAddHlColor={(hex) => addCustomColor("hl", hex)} onAddPenColor={(hex) => addCustomColor("pen", hex)}
            penWidth={penWidth} setPenWidth={setPenWidth} onUndoStroke={undoStroke} onClear={clearPage} hint={hint} />
        </div>
      </main>

      {rightOpen && (
        <RightPanel tab={rightTab} setTab={setRightTab} outline={outline} jumpTo={jumpTo}
          annotations={anno} hlColors={hlAll} jumpToAnno={jumpToAnno} deleteAnno={deleteAnno} note={note} />
      )}

      {activeHl && (
        <div className="hl-pop" style={{ left: activeHl.x, top: activeHl.y }}>
          {hlAll.map((c) => (
            <button key={c.id} className="swatch sm" style={{ "--sw": c.solid }}
              onClick={() => { setAnnoColor("highlights", activeHl.id, c.id); setActiveHl(null); }} />
          ))}
          <CustomColorAdd sm onAdd={(hex) => { hex = hex.toLowerCase(); addCustomColor("hl", hex); setAnnoColor("highlights", activeHl.id, hex); setActiveHl(null); }} />
          <div className="sel-pop-divider" />
          <button className="sel-pop-btn" onClick={() => deleteAnno("highlights", activeHl.id)}><Icons.trash size={16} /></button>
        </div>
      )}

      {editing && (() => {
        const item = (anno[editing.type] || []).find((x) => x.id === editing.id);
        if (!item) return null;
        return (
          <div className="editor-card" style={{ left: editing.x, top: editing.y }}>
            <div className="ec-head">
              <span className={"ec-type " + editing.type}>{editing.type === "stickies" ? "Yapışkan not" : "Kenar yorumu"}</span>
              <button className="ec-x" onClick={() => setEditing(null)}><Icons.close size={15} /></button>
            </div>
            <div className="ec-quote">“{item.quote}”</div>
            <textarea autoFocus value={item.text || ""} placeholder="Notunu yaz…"
              onChange={(e) => setAnnoText(editing.type, editing.id, e.target.value)} />
            <div className="ec-foot">
              <button className="ec-del" onClick={() => deleteAnno(editing.type, editing.id)}><Icons.trash size={14} /> Sil</button>
              <button className="ec-done" onClick={() => setEditing(null)}>Tamam</button>
            </div>
          </div>
        );
      })()}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
