/* global React, Icons, TweetCard, PodcastCard, SkeletonRow, Empty, dayKey, dayLabel */
// The deck: generic Column shell with in-column filter strip, day dividers,
// and a seen-watermark divider persisted per column in localStorage.
(() => {
  const e = React.createElement;
  const { useState, useMemo, useEffect } = React;

  const SEEN_KEY = "zg:seen:v1";
  function readSeen() {
    try { return JSON.parse(localStorage.getItem(SEEN_KEY)) || {}; }
    catch { return {}; }
  }
  function useSeenWatermark(colKey, newestUnix) {
    // capture the PREVIOUS watermark once per mount, then advance it
    const [prev] = useState(() => readSeen()[colKey] || 0);
    useEffect(() => {
      if (!newestUnix) return;
      const all = readSeen();
      if ((all[colKey] || 0) < newestUnix) {
        all[colKey] = newestUnix;
        try { localStorage.setItem(SEEN_KEY, JSON.stringify(all)); } catch { /* ignore */ }
      }
    }, [colKey, newestUnix]);
    return prev;
  }

  // Incremental rendering: mount CHUNK rows, grow when the sentinel scrolls
  // into view. 500 rows x hundreds of images mounted at once stalls the
  // renderer; this keeps the initial paint to one screenful of images.
  function useChunked(items, chunk = 50) {
    const [limit, setLimit] = useState(chunk);
    useEffect(() => { setLimit(chunk); }, [items, chunk]);
    const sentinelRef = React.useCallback((node) => {
      if (!node) return;
      const io = new IntersectionObserver((entries) => {
        if (entries.some((en) => en.isIntersecting)) {
          setLimit((n) => n + chunk);
        }
      }, { root: node.closest(".col-body"), rootMargin: "600px" });
      io.observe(node);
      node._io = io;
    }, [chunk]);
    return {
      visible: items.slice(0, limit),
      hasMore: items.length > limit,
      sentinel: items.length > limit
        ? e("div", { ref: sentinelRef, className: "col-sentinel", key: "sentinel" },
            e("button", {
              className: "pod-expand",
              style: { padding: "12px 14px" },
              onClick: () => setLimit((n) => n + chunk),
            }, `Show older (${items.length - limit} more)`))
        : null,
    };
  }

  function Column({ colKey, icon, title, count, wide, synth, children, filters }) {
    const [showFilters, setShowFilters] = useState(false);
    const bodyRef = React.useRef(null);
    return e("section", {
      className: `col${wide ? " wide" : ""}${synth ? " synth" : ""}`,
      "aria-label": title,
    },
      e("header", { className: "col-head" },
        e("span", { className: "col-icon" }, icon),
        e("button", {
          className: "col-title", title: "Jump to top",
          onClick: () => bodyRef.current && bodyRef.current.scrollTo({ top: 0, behavior: "smooth" }),
        }, title),
        count != null && e("span", { className: "col-count tabnum" }, count),
        filters && e("div", { className: "col-tools" },
          e("button", {
            className: `col-tool${showFilters ? " on" : ""}`,
            title: "Filters", "aria-label": "Toggle filters",
            onClick: () => setShowFilters(!showFilters),
          }, e(Icons.Filter, { size: 13 })))),
      filters && showFilters && e("div", { className: "col-filters" }, filters),
      e("div", { className: "col-body", ref: bodyRef }, children));
  }

  // interleave day dividers + the unseen divider into a tweet list
  function withDividers(items, prevSeen) {
    const out = [];
    let lastDay = null, seenMarkPlaced = prevSeen <= 0;
    items.forEach((t) => {
      const dk = dayKey(t.created_unix);
      if (dk !== lastDay) {
        out.push(e("div", { className: "day-div", key: `d-${dk}` }, dayLabel(t.created_unix)));
        lastDay = dk;
      }
      if (!seenMarkPlaced && t.created_unix <= prevSeen) {
        out.push(e("div", { className: "unseen-div", key: "seen" }, "seen up to here"));
        seenMarkPlaced = true;
      }
      out.push(e(TweetCard, { t, key: t.id }));
    });
    return out;
  }

  function useTweetFilters() {
    const [q, setQ] = useState("");
    const [mediaOnly, setMediaOnly] = useState(false);
    const [hideReplies, setHideReplies] = useState(false);
    // stable identity per filter state; unstable identity resets useChunked
    const apply = React.useCallback((items) => items.filter((t) => {
      if (mediaOnly && !(t.media && t.media.length)) return false;
      if (hideReplies && t.is_reply) return false;
      if (q) {
        const needle = q.toLowerCase();
        const hay = `${t.text} ${t.handle} ${t.author_name || ""}`.toLowerCase();
        if (!hay.includes(needle)) return false;
      }
      return true;
    }), [q, mediaOnly, hideReplies]);
    const ui = e(React.Fragment, null,
      e("input", {
        type: "search", placeholder: "Filter...", value: q,
        onChange: (ev) => setQ(ev.target.value),
      }),
      e("button", {
        className: `fpill${mediaOnly ? " on" : ""}`,
        onClick: () => setMediaOnly(!mediaOnly),
      }, "Media"),
      e("button", {
        className: `fpill${hideReplies ? " on" : ""}`,
        onClick: () => setHideReplies(!hideReplies),
      }, "No replies"));
    return { apply, ui, active: !!(q || mediaOnly || hideReplies) };
  }

  function FirehoseColumn({ tweets, loading }) {
    const f = useTweetFilters();
    const items = useMemo(() => f.apply(tweets), [tweets, f.apply]);
    const newest = tweets.length ? tweets[0].created_unix : 0;
    const prevSeen = useSeenWatermark("firehose", newest);
    const { visible, sentinel } = useChunked(items, 50);
    return e(Column, {
      colKey: "firehose", icon: e(Icons.Tweet, { size: 15 }),
      title: "Firehose", count: items.length, filters: f.ui,
    },
      loading ? [1, 2, 3, 4, 5].map((i) => e(SkeletonRow, { key: i }))
        : items.length
          ? [...withDividers(visible, f.active ? 0 : prevSeen), sentinel]
          : e(Empty, null, "// no tweets match"));
  }

  function engagement(t) {
    return (t.likes || 0) + 2 * (t.retweets || 0) + (t.replies || 0)
      + Math.floor((t.views || 0) / 1000);
  }
  function RadarColumn({ tweets, loading }) {
    const f = useTweetFilters();
    const items = useMemo(() => {
      const ranked = [...f.apply(tweets)].sort((a, b) => engagement(b) - engagement(a));
      return ranked.slice(0, 40);
    }, [tweets, f.apply]);
    return e(Column, {
      colKey: "radar", icon: e(Icons.Zap, { size: 15 }),
      title: "Radar", count: items.length, filters: f.ui,
    },
      loading ? [1, 2, 3].map((i) => e(SkeletonRow, { key: i }))
        : items.length ? items.map((t) => e(TweetCard, { t, key: t.id }))
        : e(Empty, null, "// quiet week on the radar"));
  }

  function PodcastColumn({ podcasts, loading }) {
    const [minScore, setMinScore] = useState(0);
    const items = useMemo(
      () => podcasts.filter((p) => (p.interest_score || 0) >= minScore),
      [podcasts, minScore]);
    const filters = e(React.Fragment, null,
      [0, 60, 80].map((s) => e("button", {
        key: s, className: `fpill${minScore === s ? " on" : ""}`,
        onClick: () => setMinScore(s),
      }, s === 0 ? "All" : `${s}+`)));
    const { visible, sentinel } = useChunked(items, 30);
    return e(Column, {
      colKey: "podcasts", icon: e(Icons.Mic, { size: 15 }),
      title: "Podcasts", count: items.length, filters,
    },
      loading ? [1, 2, 3].map((i) => e(SkeletonRow, { key: i }))
        : items.length
          ? [...visible.map((p) => e(PodcastCard, { p, key: p.video_id })), sentinel]
          : e(Empty, null, "// no episodes this window"));
  }

  Object.assign(window, { Column, FirehoseColumn, RadarColumn, PodcastColumn });
})();
