/* Emily Stanford — Selected Writing
   Interactive editorial tile grid (Acquired-inspired).
   Two directions toggled via Tweaks: "Editorial Grid" and "Magazine Mosaic". */

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

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "direction": "Editorial grid",
  "headline": "grotesk",
  "color": "category",
  "accent": "#C2362A",
  "density": "regular",
  "photoBright": 0.82,
  "dark": false,
  "showSearch": true,
  "navStyle": "underline"
}/*EDITMODE-END*/;

const CATS = window.CATEGORIES;
const ARTICLES = window.ARTICLES;
const MONO_INK = "#1F1D1A";
const isEditable = () => !!(window.omelette && window.omelette.writeFile);

function catColor(cat, colorMode, idx) {
  if (colorMode === "mono") {
    // tasteful stepped neutrals so adjacent tiles still read apart
    const inks = ["#1C1B18", "#26241F", "#322F28", "#1C1B18", "#2C2A24"];
    return inks[idx % inks.length];
  }
  return (CATS[cat] && CATS[cat].color) || "#333";
}

/* ---------- Icons ---------- */
const SearchIcon = () => (
  <svg className="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"
       strokeLinecap="round" strokeLinejoin="round">
    <circle cx="11" cy="11" r="7" /><path d="m21 21-4.3-4.3" />
  </svg>
);
const Arrow = () => (
  <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor"
       strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
    <path d="M7 17 17 7M9 7h8v8" />
  </svg>
);

/* ---------- Tile ---------- */
function Tile({ a, colorMode, idx, editable }) {
  const ref = useRef(null);
  const [filled, setFilled] = useState(false);

  // observe the image-slot's data-filled attribute so the scrim/texture react
  useEffect(() => {
    const tile = ref.current;
    if (!tile) return;
    const slot = tile.querySelector("image-slot");
    if (!slot) return;
    const sync = () => {
      const f = slot.hasAttribute("data-filled");
      tile.setAttribute("data-filled", f ? "true" : "false");
      setFilled(f);
    };
    sync();
    const mo = new MutationObserver(sync);
    mo.observe(slot, { attributes: true, attributeFilter: ["data-filled"] });
    return () => mo.disconnect();
  }, []);

  const openArticle = () => window.open(a.url, "_blank", "noopener");
  const pickImage = () => {
    const slot = ref.current && ref.current.querySelector("image-slot");
    const input = slot && slot.shadowRoot && slot.shadowRoot.querySelector("input");
    if (input) input.click();
  };
  const stop = (e) => { e.preventDefault(); e.stopPropagation(); };

  // In edit mode the tile body is for authoring images (click empty tile to
  // upload, or drag-drop). A dedicated ↗ button previews the article.
  // Published viewers (read-only) click the tile to read.
  const onTileClick = () => {
    if (editable || isEditable()) { if (!filled) pickImage(); }
    else openArticle();
  };
  const openLink = (e) => { stop(e); openArticle(); };

  const color = catColor(a.category, colorMode, idx);

  return (
    <article ref={ref} className="tile" data-size={a.size || ""} data-filled="false"
             data-hasquote={a.quote ? "true" : "false"} data-editable={editable ? "true" : "false"}
             style={{ "--cat": color }} onClick={onTileClick}>
      <image-slot id={"slot-" + a.id} shape="rect" fit="cover"
                  placeholder={a.title}></image-slot>
      <div className="texture"></div>
      <div className="scrim"></div>
      <div className="hover-scrim"></div>
      {editable ? (
        <button className="open-link" title="Preview article" aria-label="Preview article"
                onMouseDown={stop} onClick={openLink}><Arrow /></button>
      ) : null}
      <div className="overlay">
        <div className="overlay-top">
          <span className="cat-chip">{a.category}</span>
          {a.stat ? (
            <span className={"stat-badge" + (typeof a.stat === "string" ? " stat-phrase" : "")}>
              <span className="stat-star">★</span>
              <span className="stat-text">
                <span className="stat-val">{typeof a.stat === "string" ? a.stat : a.stat.value}</span>
                {typeof a.stat === "object" && a.stat.note ? <span className="stat-note">{a.stat.note}</span> : null}
              </span>
            </span>
          ) : null}
        </div>
        <div className="overlay-bottom">
          <div className="textstack">
            <h3 className="tile-title">{a.title}</h3>
            {a.quote ? <blockquote className="pullquote">{a.quote}</blockquote> : null}
          </div>
          <div className="tile-foot">
            {editable && !filled
              ? <span className="add-hint">+ Click or drop image</span>
              : <span className="read-tag">{(a.url && a.url.includes("notablecap.com")) ? "Notable Capital" : "First Round"} <Arrow /></span>}
          </div>
        </div>
      </div>
    </article>
  );
}

/* ---------- Media (Podcast & Video) ---------- */
const MEDIA = window.MEDIA || [];
const MEDIA_CATS = window.MEDIA_CATEGORIES || {};
const ytThumb = (id) => "https://i.ytimg.com/vi/" + id + "/hqdefault.jpg";

const PlayIcon = () => (
  <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
    <path d="M8 5v14l11-7z" />
  </svg>
);

function MediaTile({ m }) {
  const hasArt = !!(m.videoId || m.thumb);
  const thumb = m.videoId ? ytThumb(m.videoId) : (m.thumb || "");
  const foot = m.show || "";
  const color = (MEDIA_CATS[m.cat] && MEDIA_CATS[m.cat].color) || "#1A1916";
  const dest = /youtube\.com|youtu\.be/.test(m.url) ? "YouTube"
    : (/x\.com|twitter\.com/.test(m.url) ? "X" : "Listen");
  return (
    <a className="tile media" data-filled={hasArt ? "true" : "false"} style={{ "--cat": color }}
       href={m.url} target="_blank" rel="noopener">
      <image-slot id={m.id} shape="rect" fit="cover" src={thumb} placeholder=""></image-slot>
      {!hasArt ? <div className="texture"></div> : null}
      <div className="scrim"></div>
      <div className="play" aria-hidden="true"><PlayIcon /></div>
      <div className="overlay">
        <div className="overlay-top">
          <span className="cat-chip">{m.type === "podcast" ? "Podcast" : "Video"}</span>
        </div>
        <div className="overlay-bottom">
          {m.title ? <h3 className="tile-title">{m.title}</h3> : null}
          <div className="tile-foot">
            <span>{foot || (m.type === "podcast" ? "Listen" : "Watch")}</span>
            <span className="read-tag">{dest} <Arrow /></span>
          </div>
        </div>
      </div>
    </a>
  );
}

/* ---------- App ---------- */
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [query, setQuery] = useState("");
  const HAS_FEATURED = ARTICLES.some(a => a.featured);
  const PAGE = 24;
  const [active, setActive] = useState(HAS_FEATURED ? "Featured" : "All");
  const [limit, setLimit] = useState(PAGE);
  const [mode, setMode] = useState("writing");
  const [mediaActive, setMediaActive] = useState("All");
  const [editable, setEditable] = useState(isEditable());

  // The host runtime may inject window.omelette AFTER this script runs, so
  // poll briefly until it appears rather than reading the flag once at load.
  useEffect(() => {
    if (editable) { document.body.setAttribute("data-editable", "true"); return; }
    let n = 0;
    const iv = setInterval(() => {
      n++;
      if (isEditable()) {
        setEditable(true);
        document.body.setAttribute("data-editable", "true");
        clearInterval(iv);
      } else if (n > 40) {
        clearInterval(iv);
      }
    }, 150);
    return () => clearInterval(iv);
  }, [editable]);

  // theme + accent + headline on <html>
  useEffect(() => {
    const r = document.documentElement;
    r.setAttribute("data-theme", t.dark ? "dark" : "light");
    r.setAttribute("data-headline", t.headline === "serif" ? "serif" : "grotesk");
    r.style.setProperty("--accent", t.accent);
    const min = t.density === "compact" ? "212px" : t.density === "comfy" ? "320px" : "262px";
    r.style.setProperty("--tile-min", min);
    const cols = t.density === "compact" ? 5 : t.density === "comfy" ? 3 : 4;
    r.style.setProperty("--cols", cols);
    r.style.setProperty("--photo-bright", t.photoBright);
  }, [t.dark, t.accent, t.headline, t.density, t.photoBright]);

  const cats = useMemo(() => {
    const used = Object.keys(CATS).filter(c => ARTICLES.some(a => a.category === c)).sort((x, y) => x.localeCompare(y));
    return [...(HAS_FEATURED ? ["Featured"] : []), "All", ...used];
  }, []);

  const filtered = useMemo(() => {
    const q = query.trim().toLowerCase();
    const out = ARTICLES.filter(a => {
      if (active === "Featured") { if (!a.featured) return false; }
      else if (active !== "All" && a.category !== active) return false;
      if (!q) return true;
      return (a.title + " " + a.category + " " + (a.blurb || "")).toLowerCase().includes(q);
    });
    const key = (s) => (s || "").toLowerCase().replace(/^[“"'’]+/, "").replace(/^(the|a|an)\s+/, "");
    return out.sort((a, b) => key(a.title).localeCompare(key(b.title), undefined, { numeric: true }));
  }, [query, active]);

  // reset pagination whenever the filter, query, or mode changes
  useEffect(() => { setLimit(PAGE); }, [query, active, mode, mediaActive]);

  const mediaCats = useMemo(() => {
    const used = Object.keys(MEDIA_CATS).filter(c => MEDIA.some(m => m.cat === c)).sort((x, y) => x.localeCompare(y));
    return ["All", ...used];
  }, []);

  const mediaFiltered = useMemo(() => {
    const q = query.trim().toLowerCase();
    return MEDIA.filter(m => {
      if (mediaActive !== "All" && m.cat !== mediaActive) return false;
      if (!q) return true;
      return (m.title + " " + (m.show || "") + " " + (m.cat || "") + " " + m.type).toLowerCase().includes(q);
    });
  }, [query, mediaActive]);

  const isMedia = mode === "media";
  const list = isMedia ? mediaFiltered : filtered;
  const visible = list.slice(0, limit);
  const remaining = list.length - visible.length;

  const mosaic = t.direction === "Magazine mosaic" && !isMedia;

  return (
    <div>
      <header className="topbar">
        <div className="brand">
          <span className="name">Emily Stanford</span>
        </div>
        {t.showSearch && (
          <div className="search">
            <input value={query} onChange={(e) => setQuery(e.target.value)}
                   placeholder={isMedia ? "Search podcasts & videos" : "Search writing"}
                   aria-label="Search" />
            <SearchIcon />
          </div>
        )}
        <div className="topbar-right">
          <a className="pill-btn" href="About.html">About</a>
        </div>
      </header>

      <div className="modebar" data-style={t.navStyle}>
        <div className="seg" role="tablist">
          <button className="seg-btn" data-active={!isMedia} onClick={() => { setMode("writing"); setQuery(""); }}>Writing</button>
          <button className="seg-btn" data-active={isMedia} onClick={() => { setMode("media"); setQuery(""); }}>Podcast &amp; Video</button>
        </div>
      </div>

      {isMedia ? (
        <p className="media-note">I don’t appear on screen — but I scripted, produced, filmed, edited, or wrote the questions for every episode and video here.</p>
      ) : null}

      {!isMedia ? (
        <p className="media-note">Longform essays, ghostwritten investor perspectives, and deep-diving founder interviews exploring how the very best companies actually get built and keep their edge.</p>
      ) : null}

      {!isMedia ? (
        <nav className="filters">
          {cats.map(c => (
            <button key={c} className="chip" data-active={active === c}
                    onClick={() => setActive(c)}>
              {c !== "All" && c !== "Featured" && <span className="dot" style={{ background: CATS[c].color }}></span>}
              {c === "Featured" && <span className="star">★</span>}
              {c}
            </button>
          ))}
          <span className="count-label">
            {list.length} {list.length === 1 ? "piece" : "pieces"}
          </span>
        </nav>
      ) : (
        <nav className="filters">
          {mediaCats.map(c => (
            <button key={c} className="chip" data-active={mediaActive === c}
                    onClick={() => setMediaActive(c)}>
              {c !== "All" && MEDIA_CATS[c] && <span className="dot" style={{ background: MEDIA_CATS[c].color }}></span>}
              {c}
            </button>
          ))}
          <span className="count-label">
            {list.length} {list.length === 1 ? "piece" : "pieces"}
          </span>
        </nav>
      )}

      <main className="grid-wrap">
        {list.length === 0 ? (
          <div className="empty-state">No {isMedia ? "media" : "pieces"} match “{query}”.</div>
        ) : (
          <div className={mosaic ? "grid-mosaic" : "grid-editorial"}>
            {visible.map((item, i) => (
              isMedia
                ? <MediaTile key={item.id} m={item} />
                : <Tile key={item.id} a={item} idx={i} colorMode={t.color} editable={editable} />
            ))}
          </div>
        )}
        {remaining > 0 && (
          <div className="load-more-wrap">
            <button className="load-more" onClick={() => setLimit(l => l + PAGE)}>
              Load more <span className="lm-count">{remaining} more</span>
            </button>
          </div>
        )}
      </main>

      {editable && (
        <div className="drop-hint">{isMedia
          ? "Tiles are color-branded by show · drag cover art onto any tile to add it (YouTube art loads automatically)"
          : "Click or drop a photo onto any tile to add it · ↗ to preview the article"}</div>
      )}

      <TweaksPanel>
        <TweakSection label="Layout" />
        <TweakRadio label="Direction" value={t.direction}
                    options={["Editorial grid", "Magazine mosaic"]}
                    onChange={(v) => setTweak("direction", v)} />
        <TweakRadio label="Density" value={t.density}
                    options={["compact", "regular", "comfy"]}
                    onChange={(v) => setTweak("density", v)} />
        <TweakSlider label="Photo brightness" value={t.photoBright} min={0.4} max={1} step={0.02}
                     onChange={(v) => setTweak("photoBright", v)} />
        <TweakToggle label="Search bar" value={t.showSearch}
                     onChange={(v) => setTweak("showSearch", v)} />
        <TweakSelect label="Section toggle" value={t.navStyle}
                     options={["underline", "minimal", "squared", "pill"]}
                     onChange={(v) => setTweak("navStyle", v)} />

        <TweakSection label="Type & color" />
        <TweakRadio label="Headlines" value={t.headline}
                    options={["grotesk", "serif"]}
                    onChange={(v) => setTweak("headline", v)} />
        <TweakRadio label="Tiles" value={t.color}
                    options={["category", "mono"]}
                    onChange={(v) => setTweak("color", v)} />
        <TweakColor label="Accent" value={t.accent}
                    options={["#C2362A", "#2456C9", "#1F7A54", "#6A47C2", "#1A1916"]}
                    onChange={(v) => setTweak("accent", v)} />
        <TweakToggle label="Dark mode" value={t.dark}
                     onChange={(v) => setTweak("dark", v)} />
      </TweaksPanel>
    </div>
  );
}

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