/* London Free Guide — Free Spots directory app.
   Categorised, searchable A-Z of every free London spot (window.LFG_DIRECTORY).
   Text-forward (no photos) — honest, distinct from the photo feeds, and exactly
   the answer-shaped public copy that ranks + gets LLM-cited. Reuses the shared
   Nav / Band / Footer / Brolly. */
const { useState: useDirState, useMemo: useDirMemo, useEffect: useDirEffect } = React;

const DIR = window.LFG_DIRECTORY || { categories: [], spots: [] };
const DCATS = DIR.categories;
const DSPOTS = DIR.spots;
const CAT_LABEL = Object.fromEntries(DCATS.map((c) => [c.key, c.label]));

function dirMaps(s) {
  return "https://www.google.com/maps/search/?api=1&query=" + encodeURIComponent(s.name + " " + s.area + " London");
}

/* ItemList JSON-LD across the whole directory — the GEO/SEO spine */
function DirSchema() {
  useDirEffect(() => {
    const data = {
      "@context": "https://schema.org",
      "@type": "ItemList",
      name: "Free things to do in London",
      description: "A categorised directory of free London spots — museums, parks, markets, viewpoints, galleries and more, all free to enter.",
      url: window.location.href,
      numberOfItems: DSPOTS.length,
      itemListElement: DSPOTS.map((s, i) => ({
        "@type": "ListItem",
        position: i + 1,
        item: {
          "@type": "TouristAttraction",
          name: s.name,
          description: s.desc,
          isAccessibleForFree: true,
          address: { "@type": "PostalAddress", addressLocality: s.area, addressRegion: "London", addressCountry: "GB" },
          ...(s.website ? { sameAs: s.website } : {})
        }
      }))
    };
    let el = document.getElementById("dir-jsonld");
    if (!el) { el = document.createElement("script"); el.type = "application/ld+json"; el.id = "dir-jsonld"; document.head.appendChild(el); }
    el.textContent = JSON.stringify(data, null, 2);
  }, []);
  return null;
}

function DirCard({ s, showCat }) {
  const detail = s.pillar || s.deep;
  const href = s.pillar ? ("spot-" + s.id + ".html") : s.deep ? ("Spot.html?id=" + s.id) : (s.website || dirMaps(s));
  const ext = !detail;
  const saveItem = { id: s.id, kind: "spot", title: s.name, summary: s.desc, area: s.area, tube: s.tube, cost: "free", pillar: s.pillar };
  return (
    <a className={"dir-card" + (s.img ? " has-photo" : "")} href={href} {...(ext ? { target: "_blank", rel: "noopener noreferrer" } : {})}>
      <SaveButton item={saveItem} />
      {s.img && <div className="dir-card-photo"><img src={s.img} alt={s.name} loading="lazy" /></div>}
      <div className="dir-card-top">
        <span className={"dir-free" + (s.free !== "Free" ? " soft" : "")}>{s.free}</span>
        {showCat && <span className="dir-cat-tag">{CAT_LABEL[s.cat]}</span>}
        {detail && <span className="dir-pillar-tag">Full guide</span>}
      </div>
      <h3 className="dir-name">{s.name}</h3>
      <div className="dir-loc"><span className="pin" aria-hidden="true">📍</span><span className="dir-loc-txt">{s.area}{s.tube ? " · " + s.tube : ""}</span></div>
      <p className="dir-desc">{s.desc}</p>
      {s.note && <div className="dir-note">{s.note}</div>}
      <span className="dir-go">{detail ? "Read the full guide \u2192" : (s.website ? "Visit official site \u2197" : "Open in Maps \u2197")}</span>
    </a>
  );
}

/* "London today" bar — weather on the left; rotating on the right: how many of
   the free museums/galleries we track are open right now, anything shut today, a
   late opening when there is one, then tube status as a secondary item. Open/
   closed is computed from window.LFG_HOURS vs Europe/London time. */
function DirNow() {
  const [fc, setFc] = React.useState([]);
  const [tube, setTube] = React.useState(null);
  const [rep, setRep] = React.useState(() => (window.LFGHours ? window.LFGHours.report() : null));
  React.useEffect(() => {
    let live = true;
    if (window.LFGWeather) window.LFGWeather.london().then((d) => {
      if (live && d && d.length) setFc(d.slice(0, 3).map((x) => ({ label: window.LFGNow.label(x.date), code: x.code, t: x.t })));
    }).catch(() => {});
    if (window.LFGTube) window.LFGTube.status().then((s) => { if (live) setTube(s); }).catch(() => {});
    const tmr = setInterval(() => { if (window.LFGHours) setRep(window.LFGHours.report()); }, 60000);
    return () => { live = false; clearInterval(tmr); };
  }, []);
  if (!window.NowBar) return null;
  const NB = window.NowBar;

  const items = [];
  if (rep && rep.openCount > 0) {
    items.push(<span>{NB.ic("open", "nb-go")}<b>{rep.openCount}</b> free museums open right now</span>);
  }
  if (rep && rep.lateTonight.length) {
    const l = rep.lateTonight.slice(0, 2).map((x) => x.name + " til " + x.til).join(" · ");
    items.push(<span>{NB.ic("moon")}Open late tonight: {l}</span>);
  }
  if (rep && rep.closedToday.length) {
    const n = rep.closedToday;
    const txt = n.length === 1 ? n[0]
      : n.length === 2 ? (n[0] + ", " + n[1])
      : (n[0] + ", " + n[1] + " and " + (n.length - 2) + " more");
    items.push(<span className="nb-mut">{NB.ic("shut")}Closed today: {txt}</span>);
  }
  if (tube) items.push(NB.tube(tube));

  return <NowBar forecast={fc} items={items} tickKey={(rep ? rep.openCount : 0) + "-" + (tube ? "t" : "n")} />;
}

function DirApp() {
  const [t, setTweak] = useTweaks({ accent: "#E63B2E" });
  const [q, setQ] = useDirState("");
  const [cat, setCat] = useDirState("all");

  useDirEffect(() => { document.documentElement.style.setProperty("--lfg-accent", t.accent); }, [t.accent]);

  // shuffle the stacked category order once per page load — not everyone loves
  // museums, so the bucket you land on first rotates. Filter chips stay in order.
  const catOrder = useDirMemo(() => {
    const a = DCATS.slice();
    for (let i = a.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      const tmp = a[i]; a[i] = a[j]; a[j] = tmp;
    }
    return a;
  }, []);

  const needle = q.trim().toLowerCase();
  const matches = useDirMemo(() => {
    return DSPOTS.filter((s) => {
      if (cat !== "all" && s.cat !== cat) return false;
      if (needle) {
        const hay = (s.name + " " + s.area + " " + s.tube + " " + s.desc + " " + (CAT_LABEL[s.cat] || "")).toLowerCase();
        if (!hay.includes(needle)) return false;
      }
      return true;
    });
  }, [cat, needle]);

  const grouped = cat === "all" && !needle;
  const anyActive = cat !== "all" || needle;

  return (
    <React.Fragment>
      <DirSchema />
      <Nav />
      <DirNow />

      <section className="dir-hero">
        <div className="wrap dir-hero-inner">
          <span className="dir-eyebrow">the whole free city, in one place</span>
          <h1 className="dir-title">EVERY FREE THING TO DO IN <span className="r">LONDON</span></h1>
          <p className="dir-sub">{DSPOTS.length} genuinely free London spots, sorted into {DCATS.length} categories. Free museums, parks, markets, viewpoints, galleries and the quiet hidden gems. Search it, or browse by what you fancy.</p>
        </div>
      </section>

      <div className="dir-bar">
        <div className="wrap">
          <div className="dir-search">
            <span className="mag" aria-hidden="true">⌕</span>
            <input type="search" value={q} onChange={(e) => setQ(e.target.value)} placeholder="search a place, area or vibe — try “Greenwich” or “rooftop”" aria-label="Search free spots" />
          </div>
          <div className="dir-chips" role="group" aria-label="Categories">
            <button className={"dir-chip" + (cat === "all" ? " on" : "")} onClick={() => setCat("all")}>All <span className="dir-chip-n">{DSPOTS.length}</span></button>
            {DCATS.map((c) => (
              <button key={c.key} className={"dir-chip" + (cat === c.key ? " on" : "")} onClick={() => setCat(c.key)}>{c.label} <span className="dir-chip-n">{c.count}</span></button>
            ))}
          </div>
        </div>
      </div>

      <main className="dir-main">
        <div className="wrap">
          {anyActive && (
            <div className="dir-resultline">
              <span>{matches.length} {matches.length === 1 ? "spot" : "spots"}{cat !== "all" ? " in " + CAT_LABEL[cat] : ""}{needle ? " matching “" + q.trim() + "”" : ""}</span>
              <button className="dir-clear" onClick={() => { setQ(""); setCat("all"); }}>Clear ✕</button>
            </div>
          )}

          {grouped ? (
            catOrder.map((c) => {
              const items = DSPOTS.filter((s) => s.cat === c.key);
              if (!items.length) return null;
              const PREVIEW = 6;
              const shown = items.slice(0, PREVIEW);
              const more = items.length - shown.length;
              const seeAll = () => { setCat(c.key); window.scrollTo(0, 0); };
              return (
                <section className="dir-group" id={"cat-" + c.key} key={c.key}>
                  <div className="dir-group-head">
                    <div className="dir-group-headL">
                      <h2 className="dir-group-h">{c.label}</h2>
                      <span className="dir-group-n">{items.length} free</span>
                    </div>
                    {more > 0 && (
                      <button className="dir-group-all" onClick={seeAll}>All {items.length} {c.label} <span aria-hidden="true">→</span></button>
                    )}
                  </div>
                  <div className="dir-grid">
                    {shown.map((s) => <DirCard key={s.id} s={s} />)}
                  </div>
                </section>
              );
            })
          ) : matches.length ? (
            <div className="dir-grid dir-grid-flat">
              {matches.map((s) => <DirCard key={s.id} s={s} showCat={cat === "all"} />)}
            </div>
          ) : (
            <div className="dir-empty">Nothing matched. Try a broader search, or <button className="dir-link" onClick={() => { setQ(""); setCat("all"); }}>see everything</button>.</div>
          )}
        </div>
      </main>

      <Band />
      <Footer />
      <GuideAgent />

      <TweaksPanel>
        <TweakSection label="Brand accent" />
        <TweakColor label="Accent colour" value={t.accent}
          options={["#E63B2E", "#FF6A1A", "#1F8A5B", "#2A6FDB"]}
          onChange={(v) => setTweak("accent", v)} />
      </TweaksPanel>
    </React.Fragment>
  );
}

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