// d4not — root app

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "dark",
  "view": "gallery"
}/*EDITMODE-END*/;

function App() {
  const [theme, setTheme] = useState(() => localStorage.getItem("d4not.theme") || TWEAK_DEFAULTS.theme);
  const [view, setView]   = useState(() => localStorage.getItem("d4not.view")  || TWEAK_DEFAULTS.view);
  const [sort, setSort]   = useState("recent");
  const [query, setQuery] = useState("");

  const [allProjects, setAllProjects] = useState([]);
  const [loading, setLoading] = useState(true);

  const [safetyOpen, setSafetyOpen] = useState(false);
  const [bannerShown, setBannerShown] = useState(true);

  // #submit redirects to the dedicated publish page — modal was retired.
  useEffect(() => {
    function maybeRedirect() {
      if (location.hash === "#submit") location.replace("publish.html");
    }
    maybeRedirect();
    window.addEventListener("hashchange", maybeRedirect);
    return () => window.removeEventListener("hashchange", maybeRedirect);
  }, []);

  function goPublish() { location.href = "publish.html"; }

  const [selectedSurfaces,   setSelectedSurfaces]   = useState(new Set());
  const [selectedLangs,      setSelectedLangs]      = useState(new Set());
  const [selectedFrameworks, setSelectedFrameworks] = useState(new Set());
  const [selectedTopics,     setSelectedTopics]     = useState(new Set());
  const [selectedPlatforms,  setSelectedPlatforms]  = useState(new Set());
  const [selectedOrigins,    setSelectedOrigins]    = useState(new Set());
  const [selectedTrust,      setSelectedTrust]      = useState(new Set());
  const [selectedHosts,      setSelectedHosts]      = useState(new Set());
  const [selectedSizes,      setSelectedSizes]      = useState(new Set());

  const [tweaksOn, setTweaksOn] = useState(false);

  useEffect(() => { document.documentElement.dataset.theme = theme; localStorage.setItem("d4not.theme", theme); }, [theme]);
  useEffect(() => { localStorage.setItem("d4not.view", view); }, [view]);

  // load projects from Supabase
  useEffect(() => {
    let ignore = false;
    (async () => {
      if (!window.D4API) return;
      try {
        const rows = await window.D4API.listProjects({ limit: 500 });
        if (!ignore) { setAllProjects(rows); setLoading(false); }
      } catch (e) {
        console.error("listProjects failed", e);
        if (!ignore) setLoading(false);
      }
    })();
    return () => { ignore = true; };
  }, []);

  useEffect(() => {
    function onMsg(ev) {
      const d = ev.data;
      if (!d || typeof d !== "object") return;
      if (d.type === "__activate_edit_mode")   setTweaksOn(true);
      if (d.type === "__deactivate_edit_mode") setTweaksOn(false);
    }
    window.addEventListener("message", onMsg);
    window.parent.postMessage({ type: "__edit_mode_available" }, "*");
    return () => window.removeEventListener("message", onMsg);
  }, []);

  function persistKey(k, v) {
    window.parent.postMessage({ type: "__edit_mode_set_keys", edits: { [k]: v } }, "*");
  }

  const filtered = useMemo(() => {
    let r = allProjects.slice();
    const q = query.trim().toLowerCase();
    if (q) {
      r = r.filter(p =>
        (p.title||"").toLowerCase().includes(q) ||
        (p.blurb||"").toLowerCase().includes(q) ||
        (p.author_handle||"").toLowerCase().includes(q) ||
        (p.repo_url||"").toLowerCase().includes(q) ||
        (p.stacks||[]).some(s => s.toLowerCase().includes(q)) ||
        (p.surface||"").toLowerCase().includes(q));
    }
    if (selectedSurfaces.size)   r = r.filter(p => selectedSurfaces.has(p.surface));
    if (selectedOrigins.size)    r = r.filter(p => selectedOrigins.has(p.origin));
    if (selectedTrust.size)      r = r.filter(p => selectedTrust.has(p.trust));
    if (selectedHosts.size)      r = r.filter(p => selectedHosts.has(p.host));
    if (selectedSizes.size)      r = r.filter(p => selectedSizes.has(p.size || window.projectSize(p)));
    if (selectedLangs.size)      r = r.filter(p => (p.stacks||[]).some(s => selectedLangs.has(s)));
    if (selectedFrameworks.size) r = r.filter(p => (p.stacks||[]).some(s => selectedFrameworks.has(s)));
    if (selectedTopics.size)     r = r.filter(p => (p.topics||[]).some(t => selectedTopics.has(t)));
    if (selectedPlatforms.size)  r = r.filter(p => (p.platforms||[]).some(pl => selectedPlatforms.has(pl)));
    switch (sort) {
      case "saves":  r.sort((a,b) => (b.saves||0) - (a.saves||0)); break;
      case "forks":  r.sort((a,b) => (b.forks||0) - (a.forks||0)); break;
      case "stars":  r.sort((a,b) => (b.stars||0) - (a.stars||0)); break;
      case "tokens": r.sort((a,b) => ((b.meta&&b.meta.tokens)||0) - ((a.meta&&a.meta.tokens)||0)); break;
      case "alpha":  r.sort((a,b) => a.title.localeCompare(b.title)); break;
      default:       r.sort((a,b) => new Date(b.added_at) - new Date(a.added_at));
    }
    return r;
  }, [allProjects, query, selectedSurfaces, selectedOrigins, selectedTrust, selectedHosts, selectedSizes,
      selectedLangs, selectedFrameworks, selectedTopics, selectedPlatforms, sort]);

  const counts = useMemo(() => {
    const surface = {}, origin = {}, trust = {}, host = {}, size = {};
    allProjects.forEach(p => {
      surface[p.surface] = (surface[p.surface] || 0) + 1;
      origin[p.origin]   = (origin[p.origin]   || 0) + 1;
      trust[p.trust]     = (trust[p.trust]     || 0) + 1;
      host[p.host]       = (host[p.host]       || 0) + 1;
      const sz = p.size || window.projectSize(p);
      size[sz] = (size[sz] || 0) + 1;
    });
    return { surface, origin, trust, host, size };
  }, [allProjects]);

  const totalAuthors = useMemo(() => new Set(allProjects.map(p => p.author_handle)).size, [allProjects]);

  const activeChips = [];
  selectedSurfaces  .forEach(id => { const s = window.SURFACES.find(x=>x.id===id);  activeChips.push({ key:"su:"+id, label:s.label,  kind:"surface",    id }); });
  selectedOrigins   .forEach(id => { const o = window.ORIGINS .find(x=>x.id===id);  activeChips.push({ key:"or:"+id, label:o.label,  kind:"origin",     id }); });
  selectedTrust     .forEach(id => { const t = window.TRUST   .find(x=>x.id===id);  activeChips.push({ key:"tr:"+id, label:t.label,  kind:"trust",      id }); });
  selectedHosts     .forEach(id => { const h = window.HOSTS   .find(x=>x.id===id);  activeChips.push({ key:"ho:"+id, label:h.label,  kind:"host",       id }); });
  selectedSizes     .forEach(id => { const z = window.SIZES   .find(x=>x.id===id);  activeChips.push({ key:"sz:"+id, label:z.label,  kind:"size",       id }); });
  selectedLangs     .forEach(id => activeChips.push({ key:"la:"+id, label:id, kind:"lang",      id }));
  selectedFrameworks.forEach(id => activeChips.push({ key:"fr:"+id, label:id, kind:"framework", id }));
  selectedTopics    .forEach(id => { const t = window.TOPICS   .find(x=>x.id===id); activeChips.push({ key:"to:"+id, label:t.label,  kind:"topic",      id }); });
  selectedPlatforms .forEach(id => { const p = window.PLATFORMS.find(x=>x.id===id); activeChips.push({ key:"pl:"+id, label:p.label,  kind:"platform",   id }); });

  function clearChip(c) {
    const map = {
      surface:   [selectedSurfaces,   setSelectedSurfaces],
      origin:    [selectedOrigins,     setSelectedOrigins],
      trust:     [selectedTrust,       setSelectedTrust],
      host:      [selectedHosts,       setSelectedHosts],
      size:      [selectedSizes,       setSelectedSizes],
      lang:      [selectedLangs,       setSelectedLangs],
      framework: [selectedFrameworks,  setSelectedFrameworks],
      topic:     [selectedTopics,      setSelectedTopics],
      platform:  [selectedPlatforms,   setSelectedPlatforms],
    }[c.kind];
    const n = new Set(map[0]); n.delete(c.id); map[1](n);
  }
  function clearAll() {
    setSelectedSurfaces(new Set()); setSelectedOrigins(new Set());
    setSelectedTrust(new Set()); setSelectedHosts(new Set());
    setSelectedSizes(new Set()); setSelectedLangs(new Set());
    setSelectedFrameworks(new Set()); setSelectedTopics(new Set());
    setSelectedPlatforms(new Set()); setQuery("");
  }

  return (
    <div className="shell" data-screen-label="01 Catalog">
      <SafetyStrip shown={bannerShown} onLearn={() => setSafetyOpen(true)} onClose={() => setBannerShown(false)} />
      <TopBar onUploadClick={goPublish} theme={theme} onTheme={t => { setTheme(t); persistKey("theme", t); }} />
      <div className="main">
        <FilterRail
          surfaces={window.SURFACES}
          origins={window.ORIGINS}
          trust={window.TRUST}
          selectedSurfaces={selectedSurfaces}     setSelectedSurfaces={setSelectedSurfaces}
          selectedLangs={selectedLangs}           setSelectedLangs={setSelectedLangs}
          selectedFrameworks={selectedFrameworks} setSelectedFrameworks={setSelectedFrameworks}
          selectedTopics={selectedTopics}         setSelectedTopics={setSelectedTopics}
          selectedPlatforms={selectedPlatforms}   setSelectedPlatforms={setSelectedPlatforms}
          selectedOrigins={selectedOrigins}       setSelectedOrigins={setSelectedOrigins}
          selectedTrust={selectedTrust}           setSelectedTrust={setSelectedTrust}
          selectedHosts={selectedHosts}           setSelectedHosts={setSelectedHosts}
          selectedSizes={selectedSizes}           setSelectedSizes={setSelectedSizes}
          counts={counts}
        />
        <Catalog
          results={filtered}
          sort={sort} setSort={setSort}
          view={view} setView={v => { setView(v); persistKey("view", v); }}
          activeChips={activeChips}
          onClearAll={clearAll}
          onClearChip={clearChip}
          query={query} onQuery={setQuery}
        />
      </div>
      <Colophon onSafety={() => setSafetyOpen(true)} onSubmit={goPublish} />
      <SafetyModal open={safetyOpen} onClose={() => setSafetyOpen(false)} />
      <Tweaks
        on={tweaksOn}
        theme={theme} setTheme={t => { setTheme(t); persistKey("theme", t); }}
        view={view}   setView={v => { setView(v); persistKey("view", v); }}
      />
    </div>
  );
}

function Colophon({ onSafety, onSubmit }) {
  return (
    <footer className="colophon">
      <div>
        <h5>Manifesto</h5>
        <div className="bigname">
          <span className="d">d</span><span className="four">4</span>not
        </div>
        <div className="by-d4not">
          An index by <span className="sig">@d4not</span>
        </div>
        <p>
          Software is built twice. The same auth flow, the same dashboard, the same settings page —
          burning attention, electricity and tokens on problems already solved.
        </p>
        <p>
          d4not library is a public index to break that loop. Search before you generate. If you ship
          something, leave the link behind. We don't host, we don't sell, we don't gatekeep.
        </p>
      </div>
      <div>
        <h5>Library</h5>
        <ul>
          <li><a href="index.html">Home</a></li>
          <li><a href="library.html">Catalog</a></li>
          <li><a href="manifesto.html">Manifesto</a></li>
          <li><a href="safety.html">Safety</a></li>
          <li><a href="donate.html">Donate</a></li>
        </ul>
      </div>
      <div>
        <h5>Contribute</h5>
        <ul>
          <li onClick={onSubmit} style={{ cursor: "pointer" }}>Publish a project →</li>
          <li><a href="auth.html?mode=signup">Create account</a></li>
          <li onClick={onSafety} style={{ cursor: "pointer" }}>How we review →</li>
        </ul>
      </div>
      <div>
        <h5>Contact</h5>
        <ul>
          <li><a href="mailto:contact@d4notlibrary.com">contact@d4notlibrary.com</a></li>
          <li><a href="https://github.com/d4not" target="_blank" rel="noopener">@d4not on GitHub</a></li>
        </ul>
      </div>
      <div className="signoff">
        <span>© 2026 — d4not library. Independent index. Nothing hosted here.</span>
        <span style={{ color: "var(--yellow)" }}>Pre-production beta — accounts and posts may be wiped during development.</span>
        <span>Set in Archivo Black, Archivo, JetBrains Mono.</span>
      </div>
    </footer>
  );
}

function Tweaks({ on, theme, setTheme, view, setView }) {
  return (
    <div className={"tweaks" + (on ? " on" : "")}>
      <div className="head"><span>TWEAKS</span><span style={{ opacity: 0.5 }}>d4not</span></div>
      <div className="row">
        <span>Theme</span>
        <div className="seg">
          <button className={theme === "dark" ? "on" : ""} onClick={() => setTheme("dark")}>Dark</button>
          <button className={theme === "bone" ? "on" : ""} onClick={() => setTheme("bone")}>Bone</button>
        </div>
      </div>
      <div className="row">
        <span>Layout</span>
        <div className="seg">
          <button className={view === "gallery" ? "on" : ""} onClick={() => setView("gallery")}>Gal</button>
          <button className={view === "dense"   ? "on" : ""} onClick={() => setView("dense")}>Dns</button>
          <button className={view === "list"    ? "on" : ""} onClick={() => setView("list")}>Lst</button>
          <button className={view === "masonry" ? "on" : ""} onClick={() => setView("masonry")}>Msn</button>
        </div>
      </div>
    </div>
  );
}

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