// Mosaic site — FEED page (direction F3: the comparison IS the front page)
// Exports: FeedPage
// Each story links to its permanent static page at /topics/{slug} (crawlable + shareable).
const topicHref = (s) => "/topics/" + (s.slug || s.id);
// Use the real generated hero (assets/heroes/{id}.png) when present; else the procedural SArt collage.
const Art = ({ story, radius = 0, style }) => story.hero
  ? <img src={story.hero} alt="" loading="lazy" style={{ ...style, objectFit: "cover", borderRadius: radius, display: "block" }} />
  : <SArt id={story.id} seed={story.seed} radius={radius} style={style} />;

const LensColumns = ({ story, mode, bare, onOpen }) => (
  <div className={"lenses" + (bare ? " bare" : "")}>
    {story.framings.map((f, i) => (
      <div className="lens" key={i} style={{ cursor: onOpen ? "pointer" : "default" }} onClick={onOpen}>
        <div className="rule" style={{ background: framingColor(f, i, mode) }}></div>
        <h3>{f.t}</h3>
        <p>{f.b}</p>
        {f.q && (
          <blockquote>
            <div className="q">{f.q}</div>
            <div className="micro" style={{ marginTop: 3 }}>— {f.a}</div>
          </blockquote>
        )}
        {f.takes && f.takes.length > 0 && (
          <div className="ltakes">
            {f.takes.map((t, k) => (
              <div className="ltake" key={k}>
                <div className="ltake-head"><span className="ltake-voice">{t.voice}</span><span className="ltake-view">{t.view}</span></div>
                <div className="ltake-ev">“{mdInline(t.evidence)}”</div>
              </div>
            ))}
          </div>
        )}
        {f.srcs && f.srcs.length > 0 && (
          <div className="lsrc" style={{ display: "flex", alignItems: "center", gap: 9 }}>
            <SFavRow ids={f.srcs} size={22} max={5} gap={6} />
            <span className="micro">{f.n} source{f.n > 1 ? "s" : ""}</span>
          </div>
        )}
      </div>
    ))}
  </div>
);

// Accordion queue row — click to expand its framings inline; only one open at a time.
const QueueRow = ({ story, mode, density, expanded, onToggle, onOpenStory }) => {
  const total = story.srcCount || story.framings.reduce((s, f) => s + f.n, 0);
  const allSrcs = [...new Set(story.framings.flatMap((f) => f.srcs))];
  return (
  <div className={"qrow" + (expanded ? " open" : "") + (density === "compact" ? " compact" : "")}>
    <div className="qrow-head" onClick={onToggle}>
      {density !== "compact" && <div className="qrow-thumb"><Art story={story} radius={0} style={{ width: "100%", height: "100%", objectFit: "cover" }} /></div>}
      <div style={{ flex: 1, minWidth: 0 }}>
        <div className="kicker" style={{ marginBottom: 5 }}>{story.cat} · {story.upd} · {total} sources · {story.framings.length} framings</div>
        <h4><a href={topicHref(story)} className="storylink">{story.title}</a></h4>
        {density !== "compact" && <p className="sum">{story.standfirst}</p>}
        <div className="qrow-meta" style={{ display: "flex", alignItems: "center", gap: 12, marginTop: density === "compact" ? 5 : 10, flexWrap: "wrap" }}>
          <SFavRow ids={allSrcs} size={18} max={6} gap={5} />
          <STicks story={story} mode={mode} tw={5} th={11} gap={2} />
          <span className="micro" style={{ fontSize: 11.5 }}>{tickLabel(story, mode)}</span>
        </div>
      </div>
      <button type="button" className="fcard-expand" tabIndex={-1} aria-expanded={expanded}>
        {expanded ? "Hide framings" : "Compare " + story.framings.length + " framings"}
        <span className="caret">{expanded ? "▴" : "▾"}</span>
      </button>
    </div>
    {expanded && (
      <div className="qrow-frames">
        <div className="hair"></div>
        <LensColumns story={story} mode={mode} bare onOpen={() => onOpenStory(story.id)} />
        <div className="qrow-framefoot">
          <span className="micro">Columns are narratives — a source sits under the framing its coverage advances on this story, not its label.</span>
          <a className="chip" href={topicHref(story)}>Full breakdown →</a>
        </div>
      </div>
    )}
  </div>
  );
};

const FeedCard = ({ story, mode, open, onToggle, onOpenStory }) => {
  const total = story.srcCount || story.framings.reduce((s, f) => s + f.n, 0);
  return (
    <article className={"fcard" + (open ? " open" : "")}>
      <div className="fcard-head">
        <a className="fcard-banner" href={topicHref(story)}>
          <Art story={story} radius={0} style={{ position: "absolute", inset: 0, width: "100%", height: "100%" }} />
        </a>
        <div className="fcard-body">
          <div className="kicker">{story.cat} · {story.upd} · {total} sources · {story.framings.length} framings</div>
          <h2 className="fcard-title"><a href={topicHref(story)} className="storylink">{story.title}</a></h2>
          <p className="fcard-sum">{story.standfirst}</p>
          <div className="fcard-meta">
            <div className="fcard-mood">
              <STicks story={story} mode={mode} />
              <span className="micro">{tickLabel(story, mode)}</span>
              <SFavRow ids={story.framings.flatMap((f) => f.srcs)} size={16} max={5} />
            </div>
            <button className="fcard-expand" onClick={onToggle} aria-expanded={open}>
              {open ? "Hide framings" : "Compare " + story.framings.length + " framings"}
              <span className="caret">{open ? "\u25b4" : "\u25be"}</span>
            </button>
          </div>
        </div>
      </div>
      {open && (
        <div className="fcard-frames">
          <div className="hair"></div>
          <LensColumns story={story} mode={mode} bare onOpen={() => onOpenStory(story.id)} />
          <div className="fcard-framefoot">
            <span className="micro">Columns are narratives — a source sits under the framing its coverage advances on this story, not its label.</span>
            <a className="chip" href={topicHref(story)}>Full breakdown →</a>
          </div>
        </div>
      )}
    </article>
  );
};

const FeedPage = ({ mode, density, cat, layout, onOpenStory }) => {
  const list = SITE_STORIES.filter((s) => cat === "All" || s.cat === cat);
  const lead = list[0];
  const queue = list.slice(1);

  const [openSet, setOpenSet] = React.useState(() => new Set());
  React.useEffect(() => { setOpenSet(new Set(list.length ? [list[0].id] : [])); }, [cat]);
  const toggleCard = (id) => setOpenSet((prev) => { const n = new Set(prev); n.has(id) ? n.delete(id) : n.add(id); return n; });

  // Editorial queue accordion — single open row (the lead hero stays open separately)
  const [openRow, setOpenRow] = React.useState(null);
  React.useEffect(() => { setOpenRow(null); }, [cat]);

  // Cards cut — every story is a full-width card that expands to its framings inline
  if (layout === "cards") {
    return (
      <FadeIn>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", padding: "30px 2px 6px" }}>
          <span className="kicker" style={{ whiteSpace: "nowrap" }}>{cat === "All" ? "Today · " + new Date().toLocaleDateString("en-GB", { day: "numeric", month: "long" }) : cat + " · today"}</span>
          <span className="micro" style={{ whiteSpace: "nowrap" }}>{list.length} stories</span>
        </div>
        <div className="fcardlist">
          {list.map((s) => (
            <FeedCard key={s.id} story={s} mode={mode}
              open={openSet.has(s.id)} onToggle={() => toggleCard(s.id)} onOpenStory={onOpenStory} />
          ))}
        </div>
        <div className="micro" style={{ padding: "28px 2px 40px", maxWidth: 640 }}>
          <strong style={{ color: "var(--text-secondary)" }}>How Mosaic works.</strong> For every story we separate what outlets agree on
          (the established facts) from how they frame it (the divergence), label a source's lean only by what it actually published on
          that story, and flag the blind spots. Sensemaking, not consumption.
        </div>
      </FadeIn>
    );
  }

  return (
    <FadeIn>
      {/* lead story — image above the headline */}
      <a href={topicHref(lead)} style={{ display: "block" }}>
        <Art story={lead} radius={10}
          style={{ width: "100%", height: 360, display: "block", margin: "30px 0 0", cursor: "pointer" }} />
      </a>
      <div style={{ textAlign: "center", padding: "22px 0 10px" }}>
        <div className="kicker">{lead.tags} · updated {lead.upd}</div>
        <h1 className="display" style={{ fontSize: "clamp(30px, 7.5vw, 56px)", margin: "14px auto 14px", maxWidth: 780 }}><a href={topicHref(lead)} className="storylink">{lead.title}</a></h1>
        <p className="dek" style={{ maxWidth: 660, margin: "0 auto 18px", fontSize: 15.5 }}>{lead.standfirst}</p>
        <div style={{ display: "flex", justifyContent: "center", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
          <STicks story={lead} mode={mode} />
          <span className="micro">{tickLabel(lead, mode)}</span>
        </div>
      </div>

      <LensColumns story={lead} mode={mode} onOpen={() => onOpenStory(lead.id)} />
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", padding: "12px 2px 0", gap: 12, flexWrap: "wrap" }}>
        <span className="micro">Columns are narratives — a source sits under the framing its coverage advances on this story, not its label.</span>
        <a className="chip" href={topicHref(lead)}>Full breakdown →</a>
      </div>

      {/* queue */}
      <div style={{ marginTop: 38 }}>
        <div className="hairStrong"></div>
        <div style={{ display: "flex", justifyContent: "space-between", padding: "14px 2px 6px" }}>
          <span className="kicker">{cat === "All" ? "More today" : cat + " · more"}</span>
          <span className="micro">{queue.length} stories</span>
        </div>
        {queue.map((s) => (
          <QueueRow key={s.id} story={s} mode={mode} density={density}
            expanded={openRow === s.id} onToggle={() => setOpenRow(openRow === s.id ? null : s.id)} onOpenStory={onOpenStory} />
        ))}
      </div>

      <div className="micro" style={{ padding: "26px 2px 40px", maxWidth: 640 }}>
        <strong style={{ color: "var(--text-secondary)" }}>How Mosaic works.</strong> For every story we separate what outlets agree on
        (the established facts) from how they frame it (the divergence), label a source's lean only by what it actually published on
        that story, and flag the blind spots. Sensemaking, not consumption.
      </div>
    </FadeIn>
  );
};

Object.assign(window, { FeedPage, LensColumns, QueueRow, FeedCard });
