const { useState, useEffect, useRef, useMemo, useCallback } = React;

const LS_STATE = "bm_state_v2";
const LS_THEME = "bm_theme_v1";
const LS_DATAVER = "bm_dataver";

function freshState() {
  const d = window.DEFAULT_BOOKMARKS;
  return { categories: d.categories.map((c) => ({ id: c.id, name: c.name })), items: d.items };
}

// Merge newly-bundled default bookmarks into a saved state without disturbing
// the user's own edits/additions. Adds any default item whose URL isn't present,
// and adds any new default categories. Runs once per data-version bump.
function mergeNewDefaults(state) {
  const d = window.DEFAULT_BOOKMARKS;
  const have = new Set(state.items.map((it) => it.u));
  let maxId = state.items.reduce((mx, it) => Math.max(mx, it.i), 0);
  const added = [];
  d.items.forEach((it) => {
    if (!have.has(it.u)) added.push({ ...it, i: ++maxId });
  });
  const catIds = new Set(state.categories.map((c) => c.id));
  const newCats = d.categories.filter((c) => !catIds.has(c.id)).map((c) => ({ id: c.id, name: c.name }));
  if (!added.length && !newCats.length) return state;
  return { ...state, categories: [...state.categories, ...newCats], items: [...state.items, ...added] };
}

// ---------- helpers ----------
function hostOf(u) {
  try { return new URL(u).hostname.replace(/^www\./, ""); } catch (e) { return u; }
}
function prettyUrl(u) {
  try {
    const x = new URL(u);
    let s = x.hostname.replace(/^www\./, "") + (x.pathname === "/" ? "" : x.pathname);
    return s.length > 46 ? s.slice(0, 45) + "…" : s;
  } catch (e) { return u; }
}
function hashHue(s) {
  let h = 0; for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) % 360;
  return h;
}

// ---------- Favicon ----------
function Favicon({ item }) {
  const [err, setErr] = useState(false);
  const h = hostOf(item.u);
  const src = item.ic || `https://www.google.com/s2/favicons?domain=${h}&sz=64`;
  if (err || !src) {
    const hue = hashHue(h);
    return (
      <span className="fav letter" style={{ background: `hsl(${hue} 40% 28% / 0.5)`, color: `hsl(${hue} 70% 72%)` }}>
        {(h[0] || "?").toUpperCase()}
      </span>
    );
  }
  return <img className="fav" src={src} alt="" loading="lazy" onError={() => setErr(true)} />;
}

// ---------- Link row ----------
function LinkRow({ item, onEdit, onDelete, q }) {
  function highlight(text) {
    if (!q) return text;
    const i = text.toLowerCase().indexOf(q.toLowerCase());
    if (i < 0) return text;
    return (<>{text.slice(0, i)}<mark>{text.slice(i, i + q.length)}</mark>{text.slice(i + q.length)}</>);
  }
  return (
    <a className="row" href={item.u} title={item.u} target="_blank" rel="noopener noreferrer">
      <Favicon item={item} />
      <span className="row-main">
        <span className="row-title">{highlight(item.t || hostOf(item.u))}</span>
        <span className="row-host">{prettyUrl(item.u)}</span>
      </span>
      <span className="row-actions" onClick={(e) => { e.preventDefault(); e.stopPropagation(); }}>
        <button className="ico" title="Düzenle" onClick={(e) => { e.preventDefault(); e.stopPropagation(); onEdit(item); }}>edit</button>
        <button className="ico del" title="Sil" onClick={(e) => { e.preventDefault(); e.stopPropagation(); onDelete(item); }}>del</button>
      </span>
    </a>
  );
}

// ---------- Section ----------
function Section({ cat, items, onEdit, onDelete, q, onAddHere }) {
  return (
    <section className="sect">
      <div className="sect-head">
        <h2><span className="sl">//</span> {cat.name}</h2>
        <span className="sect-count">{items.length}</span>
        <button className="add-here" title="Bu kategoriye link ekle" onClick={() => onAddHere(cat.id)}>+ ekle</button>
      </div>
      <div className="grid">
        {items.map((it) => (
          <LinkRow key={it.i} item={it} onEdit={onEdit} onDelete={onDelete} q={q} />
        ))}
      </div>
    </section>
  );
}

// ---------- Edit / Add modal ----------
function EditModal({ draft, categories, onSave, onClose, onAddCategory }) {
  const [t, setT] = useState(draft.t || "");
  const [u, setU] = useState(draft.u || "");
  const [c, setC] = useState(draft.c || (categories[0] && categories[0].id));
  const firstRef = useRef(null);
  useEffect(() => { firstRef.current && firstRef.current.focus(); }, []);
  const isNew = draft.i == null;
  function save() {
    let url = u.trim();
    if (!url) return;
    if (!/^https?:\/\//i.test(url) && !/^[a-z]+:\/\//i.test(url)) url = "https://" + url;
    onSave({ ...draft, t: t.trim() || hostOf(url), u: url, c });
  }
  function onKey(e) {
    if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) save();
    if (e.key === "Escape") onClose();
  }
  function newCat() {
    const name = window.prompt("Yeni kategori adı:");
    if (name && name.trim()) { const id = onAddCategory(name.trim()); setC(id); }
  }
  return (
    <div className="overlay" onMouseDown={onClose}>
      <div className="modal" onMouseDown={(e) => e.stopPropagation()} onKeyDown={onKey}>
        <div className="modal-head">{isNew ? "yeni bookmark" : "düzenle"}</div>
        <label className="fld"><span>başlık</span>
          <input ref={firstRef} value={t} onChange={(e) => setT(e.target.value)} placeholder="ör. Perplexity" />
        </label>
        <label className="fld"><span>url</span>
          <input value={u} onChange={(e) => setU(e.target.value)} placeholder="https://..." />
        </label>
        <label className="fld"><span>kategori</span>
          <div className="cat-pick">
            <select value={c} onChange={(e) => setC(e.target.value)}>
              {categories.map((cat) => <option key={cat.id} value={cat.id}>{cat.name}</option>)}
            </select>
            <button className="mini" onClick={newCat} title="Yeni kategori">+ yeni</button>
          </div>
        </label>
        <div className="modal-foot">
          <button className="btn ghost" onClick={onClose}>iptal</button>
          <button className="btn solid" onClick={save}>{isNew ? "ekle" : "kaydet"} <kbd>⌘↵</kbd></button>
        </div>
      </div>
    </div>
  );
}

// ---------- App ----------
function App() {
  const [theme, setTheme] = useState(() => localStorage.getItem(LS_THEME) || "dark");
  const [state, setState] = useState(() => {
    const ver = window.DEFAULT_BOOKMARKS.version || 1;
    try {
      const saved = localStorage.getItem(LS_STATE);
      if (saved) {
        let st = JSON.parse(saved);
        const seenVer = parseInt(localStorage.getItem(LS_DATAVER) || "1", 10);
        if (ver > seenVer) {
          st = mergeNewDefaults(st);
          localStorage.setItem(LS_DATAVER, String(ver));
        }
        return st;
      }
    } catch (e) {}
    localStorage.setItem(LS_DATAVER, String(ver));
    return freshState();
  });
  const [q, setQ] = useState("");
  const [activeCat, setActiveCat] = useState("all");
  const [draft, setDraft] = useState(null);
  const [menuOpen, setMenuOpen] = useState(false);
  const searchRef = useRef(null);
  const fileRef = useRef(null);

  useEffect(() => { document.documentElement.setAttribute("data-theme", theme); localStorage.setItem(LS_THEME, theme); }, [theme]);
  useEffect(() => { try { localStorage.setItem(LS_STATE, JSON.stringify(state)); } catch (e) {} }, [state]);

  useEffect(() => {
    function onKey(e) {
      if (e.key === "/" && document.activeElement !== searchRef.current) { e.preventDefault(); searchRef.current && searchRef.current.focus(); }
      else if (e.key === "Escape") {
        if (draft) return;
        if (q) setQ(""); else if (document.activeElement === searchRef.current) searchRef.current.blur();
      }
    }
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [q, draft]);

  const catName = useMemo(() => Object.fromEntries(state.categories.map((c) => [c.id, c.name])), [state.categories]);

  const counts = useMemo(() => {
    const m = {}; state.items.forEach((it) => { m[it.c] = (m[it.c] || 0) + 1; }); return m;
  }, [state.items]);

  const ql = q.trim().toLowerCase();
  const filtered = useMemo(() => {
    let arr = state.items;
    if (ql) arr = arr.filter((it) => (it.t || "").toLowerCase().includes(ql) || (it.u || "").toLowerCase().includes(ql));
    if (!ql && activeCat !== "all") arr = arr.filter((it) => it.c === activeCat);
    return arr;
  }, [state.items, ql, activeCat]);

  // group filtered by category, keep category order
  const groups = useMemo(() => {
    const byCat = {};
    filtered.forEach((it) => { (byCat[it.c] = byCat[it.c] || []).push(it); });
    const order = state.categories.map((c) => c.id);
    // include any unknown cat ids at the end
    Object.keys(byCat).forEach((id) => { if (!order.includes(id)) order.push(id); });
    return order.filter((id) => byCat[id] && byCat[id].length).map((id) => ({ cat: { id, name: catName[id] || id }, items: byCat[id] }));
  }, [filtered, state.categories, catName]);

  // ----- mutations -----
  const nextId = useCallback(() => (state.items.reduce((mx, it) => Math.max(mx, it.i), 0) + 1), [state.items]);
  function saveItem(item) {
    setState((s) => {
      let items;
      if (item.i == null) items = [...s.items, { ...item, i: s.items.reduce((mx, it) => Math.max(mx, it.i), 0) + 1, d: Math.floor(Date.now() / 1000) }];
      else items = s.items.map((it) => (it.i === item.i ? { ...it, ...item } : it));
      return { ...s, items };
    });
    setDraft(null);
  }
  function deleteItem(item) {
    if (!window.confirm(`Silinsin mi?\n${item.t}`)) return;
    setState((s) => ({ ...s, items: s.items.filter((it) => it.i !== item.i) }));
  }
  function addCategory(name) {
    const id = "u" + Date.now();
    setState((s) => ({ ...s, categories: [...s.categories, { id, name }] }));
    return id;
  }
  function resetAll() {
    if (!window.confirm("Tüm değişiklikler silinip orijinal listeye dönülecek. Emin misin?")) return;
    localStorage.removeItem(LS_STATE);
    localStorage.setItem(LS_DATAVER, String(window.DEFAULT_BOOKMARKS.version || 1));
    setState(freshState());
    setMenuOpen(false);
  }
  function exportJson() {
    const blob = new Blob([JSON.stringify(state, null, 2)], { type: "application/json" });
    const a = document.createElement("a");
    a.href = URL.createObjectURL(blob); a.download = "bookmarks-backup.json"; a.click();
    setMenuOpen(false);
  }
  function importJson(e) {
    const f = e.target.files[0]; if (!f) return;
    const r = new FileReader();
    r.onload = () => { try { const d = JSON.parse(r.result); if (d.items && d.categories) { setState(d); setMenuOpen(false); } } catch (err) { alert("Geçersiz dosya"); } };
    r.readAsText(f);
  }

  const total = state.items.length;
  const shown = filtered.length;

  return (
    <div className="app">
      <header className="top">
        <div className="brand">
          <span className="prompt">~/bookmarks</span>
          <span className="caret">❯</span>
          <div className="search-wrap">
            <input
              ref={searchRef}
              className="search"
              value={q}
              onChange={(e) => setQ(e.target.value)}
              placeholder="ara…  ( / )"
              spellCheck={false}
              autoComplete="off"
            />
            {q && <button className="clear" onClick={() => setQ("")}>✕</button>}
          </div>
        </div>
        <div className="top-right">
          <span className="stat">{ql || activeCat !== "all" ? `${shown}/${total}` : `${total} link`}</span>
          <button className="tbtn" onClick={() => setDraft({})} title="Yeni bookmark">+ yeni</button>
          <button className="tbtn icon" onClick={() => setTheme(theme === "dark" ? "light" : "dark")} title="Tema">
            {theme === "dark" ? "☀" : "☾"}
          </button>
          <div className="menu-wrap">
            <button className="tbtn icon" onClick={() => setMenuOpen((m) => !m)} title="Menü">⋯</button>
            {menuOpen && (
              <div className="menu" onMouseLeave={() => setMenuOpen(false)}>
                <button onClick={exportJson}>yedek al (.json)</button>
                <button onClick={() => fileRef.current.click()}>içe aktar</button>
                <button className="danger" onClick={resetAll}>sıfırla</button>
                <input ref={fileRef} type="file" accept="application/json" style={{ display: "none" }} onChange={importJson} />
              </div>
            )}
          </div>
        </div>
      </header>

      <nav className="rail">
        <button className={"chip" + (activeCat === "all" && !ql ? " on" : "")} onClick={() => { setActiveCat("all"); setQ(""); }}>
          tümü <em>{total}</em>
        </button>
        {state.categories.filter((c) => (counts[c.id] || 0) > 0).map((c) => (
          <button key={c.id} className={"chip" + (activeCat === c.id && !ql ? " on" : "")} onClick={() => { setActiveCat(c.id); setQ(""); window.scrollTo({ top: 0 }); }}>
            {c.name} <em>{counts[c.id]}</em>
          </button>
        ))}
      </nav>

      <main className="main">
        {groups.length === 0 ? (
          <div className="empty">— sonuç yok —</div>
        ) : (
          groups.map((g) => (
            <Section key={g.cat.id} cat={g.cat} items={g.items} q={ql} onEdit={setDraft} onDelete={deleteItem} onAddHere={(cid) => setDraft({ c: cid })} />
          ))
        )}
      </main>

      <footer className="foot">
        <span>{total} bookmark · {state.categories.length} kategori</span>
        <span className="foot-hint">tarayıcıda kayıtlı · <kbd>/</kbd> ara · <kbd>esc</kbd> temizle</span>
      </footer>

      {draft && (
        <EditModal draft={draft} categories={state.categories} onSave={saveItem} onClose={() => setDraft(null)} onAddCategory={addCategory} />
      )}
    </div>
  );
}

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