/* London Free Guide — Spot detail page components.
   Renders one item from window.LFG_CONTENT, found by the ?id= URL param.
   Reuses Nav, Band, Footer, GuideAgent, Card from the shared scripts. */
const { useState: useSpotState } = React;

const SP_COST = window.LFG_COST;
const SP_KIND = window.LFG_KIND;

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

function dirUrl(item) {
  const dest = item.coords ? (item.coords[0] + "," + item.coords[1])
    : encodeURIComponent(item.title + " " + item.area + " London");
  return "https://www.google.com/maps/dir/?api=1&destination=" + dest;
}

/* back: return to the previous page (keeps feed scroll position), else home */
function goBack(e) {
  e.preventDefault();
  if (window.history.length > 1 && document.referrer) window.history.back();
  else window.location.href = "Home.html";
}

/* ---- tiny copy-to-clipboard button (text stays in the DOM, so crawlers still read it) ---- */
function CopyBtn({ text, label }) {
  const [done, setDone] = useSpotState(false);
  function copy(e) {
    e.preventDefault(); e.stopPropagation();
    if (navigator.clipboard) {
      navigator.clipboard.writeText(text).then(() => { setDone(true); setTimeout(() => setDone(false), 1400); });
    }
  }
  return (
    <button type="button" className={"copy-btn" + (done ? " done" : "")} onClick={copy} aria-label={label || "Copy"}>
      {done ? "copied ✓" : "copy"}
    </button>
  );
}

/* ---- share: native share sheet on mobile, copy-link fallback on desktop ---- */
function ShareBtn({ item }) {
  const [done, setDone] = useSpotState(false);
  async function share() {
    const url = window.location.href;
    const data = { title: item.title + " — London Free Guide", text: item.summary, url };
    if (navigator.share) { try { await navigator.share(data); } catch (e) { /* dismissed */ } }
    else if (navigator.clipboard) { await navigator.clipboard.writeText(url); setDone(true); setTimeout(() => setDone(false), 1600); }
  }
  return (
    <button type="button" className="btn btn-lg btn-ghost" onClick={share}>
      {done ? "Link copied" : "Share"}
    </button>
  );
}

/* ---- per-id page meta: canonical + OG + title (one file serves many spots) ---- */
function lfgSpotPath(id) {
  // deep spots have their own static prerendered file (the canonical home); others use the dynamic page
  return (window.LFG_DEEP_SPOTS || []).indexOf(id) !== -1 ? "spot-" + id + ".html" : "Spot.html?id=" + id;
}
function lfgSetMeta({ path, title, description }) {
  const url = "https://londonfreeguide.com/" + path;
  function set(sel, attr, val, mk) {
    let el = document.head.querySelector(sel);
    if (!el && mk) { el = document.createElement(mk.tag); Object.entries(mk.attrs).forEach(([k, v]) => el.setAttribute(k, v)); document.head.appendChild(el); }
    if (el) el.setAttribute(attr, val);
  }
  set('link[rel="canonical"]', "href", url, { tag: "link", attrs: { rel: "canonical" } });
  set('meta[property="og:url"]', "content", url, { tag: "meta", attrs: { property: "og:url" } });
  if (title) { set('meta[property="og:title"]', "content", title); set('meta[name="twitter:title"]', "content", title); document.title = title; }
  if (description) { set('meta[property="og:description"]', "content", description); set('meta[name="twitter:description"]', "content", description); set('meta[name="description"]', "content", description, { tag: "meta", attrs: { name: "description" } }); }
}

/* ---- structured data: prototype demo of the JSON-LD the BUILD should emit server-side ---- */
function SpotSchema({ item }) {
  React.useEffect(() => {
    lfgSetMeta({ path: lfgSpotPath(item.id), title: item.title + " — free in London | London Free Guide", description: item.summary });
    const data = {
      "@context": "https://schema.org",
      "@type": "TouristAttraction",
      name: item.title,
      description: item.summary,
      url: window.location.href,
      isAccessibleForFree: item.cost === "free",
      publicAccess: true,
      address: { "@type": "PostalAddress", addressLocality: item.area, addressRegion: "London", addressCountry: "GB" },
    };
    if (item.postcode) data.address.postalCode = item.postcode;
    if (item.hours) data.openingHours = item.hours;
    if (item.website) data.sameAs = [item.website];
    if (item.img) data.image = new URL(item.img, window.location.href).href;
    let el = document.getElementById("spot-jsonld");
    if (!el) { el = document.createElement("script"); el.type = "application/ld+json"; el.id = "spot-jsonld"; document.head.appendChild(el); }
    el.textContent = JSON.stringify(data, null, 2);
    // BreadcrumbList: Home › Free A–Z › title
    const crumb = {
      "@context": "https://schema.org",
      "@type": "BreadcrumbList",
      itemListElement: [
        { "@type": "ListItem", position: 1, name: "Home", item: "https://londonfreeguide.com/Home.html" },
        { "@type": "ListItem", position: 2, name: "Free A–Z", item: "https://londonfreeguide.com/Free%20Spots.html" },
        { "@type": "ListItem", position: 3, name: item.title, item: "https://londonfreeguide.com/" + lfgSpotPath(item.id).replace(/ /g, "%20") },
      ],
    };
    let cel = document.getElementById("breadcrumb-jsonld");
    if (!cel) { cel = document.createElement("script"); cel.type = "application/ld+json"; cel.id = "breadcrumb-jsonld"; document.head.appendChild(cel); }
    cel.textContent = JSON.stringify(crumb, null, 2);
  }, [item]);
  return null;
}

/* ---- breadcrumb: back · Explore › [primary tag] › title ---- */
function Crumb({ item }) {
  const tag = (window.LFG_FILTERS || []).find((f) => (item.tags || []).includes(f.key));
  return (
    <div className="wrap">
      <nav className="spot-crumb" aria-label="Breadcrumb">
        <button className="spot-back" onClick={goBack}><span aria-hidden="true">←</span> Back</button>
        <span className="sep" aria-hidden="true">·</span>
        <a href="Home.html">Explore</a>
        {tag && <React.Fragment><span className="sep">›</span><a href={"Home.html"}>{tag.label}</a></React.Fragment>}
        <span className="sep">›</span>
        <span className="cur">{item.title}</span>
      </nav>
    </div>
  );
}

/* ---- hero media: adapts to reel / carousel / single image ---- */
function HeroMedia({ item }) {
  const ig = item.igUrl || "https://www.instagram.com/londonfreeguide/";
  const type = item.media || (item.reel ? "reel" : item.kind === "guide" ? "carousel" : "image");
  const note = type === "reel" ? "Muted preview · sound on Instagram"
    : type === "carousel" ? "Swipe through on Instagram"
    : "See it on Instagram";

  if (type === "reel") {
    return (
      <div className="hero-media">
        <div className="media-frame">
          <img src={item.img} alt={item.title} />
          <div className="media-grad" aria-hidden="true"></div>
          <a className="media-play" href={ig} target="_blank" rel="noopener noreferrer" aria-label="Watch the reel on Instagram">
            <span aria-hidden="true">▶</span>
          </a>
          <div className="media-note"><span className="dot" aria-hidden="true"></span>{note}</div>
        </div>
      </div>
    );
  }

  return (
    <div className="hero-media">
      <a className={"media-frame" + (type === "carousel" ? " is-carousel" : "")} href={ig} target="_blank" rel="noopener noreferrer" aria-label="View on Instagram">
        <img src={item.img} alt={item.title} />
        <div className="media-grad" aria-hidden="true"></div>
        <span className="media-tag">{type === "carousel" ? "❏ Carousel" : <React.Fragment><ExtIcon /> Instagram</React.Fragment>}</span>
        {type === "carousel" && (
          <div className="media-dots" aria-hidden="true"><i className="on"></i><i></i><i></i><i></i></div>
        )}
        <div className="media-note"><span className="dot" aria-hidden="true"></span>{note}</div>
      </a>
    </div>
  );
}

/* ---- hero: editorial lede + the vertical reel (or a branded panel when text-forward) ---- */
function Hero({ item }) {
  const ig = item.igUrl || "https://www.instagram.com/londonfreeguide/";
  const textForward = !item.img;
  return (
    <section className={"hero" + (textForward ? " text-forward" : "")}>
      <div className="wrap hero-inner">
        <div className="hero-lede">
          {item.hook && <div className="hero-hook">{item.hook}</div>}
          <h1 className="hero-title">{item.title}</h1>
          <div className="hero-meta">
            <span className={"cost " + item.cost}>{SP_COST[item.cost]}</span>
            {item.kind === "guide" && <span className="badge guide">Guide</span>}
            {item.secret && <span className="badge ghost">Hidden gem</span>}
            {item.inbloom && <span className="badge bloom">🌸 In bloom</span>}
            {item.date && <span className="badge">{item.date}</span>}
          </div>
          <div className="hero-loc">
            <span className="pin" aria-hidden="true">📍</span>{item.area} · {item.tube}
          </div>
          <div className="hero-actions">
            <a className="btn btn-lg" href={ig} target="_blank" rel="noopener noreferrer">
              <span className="btn-ig-ico" aria-hidden="true"><ExtIcon /></span> See on Instagram
            </a>
            <a className="btn btn-lg btn-ghost" href={mapsUrl(item)} target="_blank" rel="noopener noreferrer">
              <span aria-hidden="true">📍</span> Get directions
            </a>
            <ShareBtn item={item} />
            <HeroSaveBtn item={item} />
          </div>
          {item.summary && <p className="hero-standfirst">{item.summary}</p>}
          <GetThere item={item} />
        </div>

        {textForward ? <HeroFeature item={item} /> : <HeroMedia item={item} />}
      </div>
    </section>
  );
}

/* ---- branded "always free" panel for photoless pillar pages ---- */
function HeroFeature({ item }) {
  return (
    <div className="hero-media">
      <div className="hero-feature">
        <div className="hf-glow" aria-hidden="true"></div>
        <span className="hf-kicker">{item.catLabel || "Free to visit"}</span>
        <div className="hf-free">ALWAYS<br /><span className="r">FREE</span></div>
        <div className="hf-rule" aria-hidden="true"></div>
        <div className="hf-where"><span className="pin" aria-hidden="true">📍</span>{item.area} · {item.tube}</div>
        <div className="hf-mark">LONDON <span className="r">FREE</span> GUIDE</div>
      </div>
    </div>
  );
}
function Lowdown({ item }) {
  const paras = item.body && item.body.length ? item.body : [item.summary];
  return (
    <div className="lowdown">
      <div className="divider" aria-hidden="true"></div>
      <h2 className="sec-h">The lowdown</h2>
      {paras.map((p, i) => (
        <p key={i} className={i === 0 ? "lede" : ""}>{p}</p>
      ))}
    </div>
  );
}

/* ---- getting there strip ---- */
function GetThere({ item }) {
  if (!item.getThere) return null;
  return (
    <div className="gethere">
      <span className="tube-ico" aria-hidden="true">🚇</span>
      <div className="gh-txt"><b>Getting there. </b>{item.getThere}</div>
    </div>
  );
}

/* ---- honest fare panel for day-trip spots (the train is the only real cost) ---- */
function SpotGetThere({ item }) {
  const t = item.fromLondon;
  if (!t) return null;
  return (
    <div className="spot-getthere">
      <div className="sgt-head">
        <span className="sgt-eyebrow">The catch</span>
        <h2 className="sgt-h">Getting there from London</h2>
      </div>
      <div className="sgt-rows">
        <div className="sgt-row"><span className="sgt-k">Cheapest line</span><span className="sgt-v">{t.from} → {t.to}</span></div>
        <div className="sgt-row"><span className="sgt-k">Journey</span><span className="sgt-v">{t.time}</span></div>
        <div className="sgt-row fare"><span className="sgt-k">Train fare</span><span className="sgt-v">{t.fare}</span></div>
      </div>
      {t.note && <p className="sgt-note">{t.note}</p>}
    </div>
  );
}

/* ---- insider tip gate (the one free, device-based unlock) ----
   Reads the ONE device-based join state: join free anywhere on the site and
   every insider tip is unlocked on THIS device, no repeat ask. The tip text is
   always in the DOM (crawlable + SEO/GEO friendly); only the visual blur is
   gated. Cross-device unlock is the Phase 2 server piece, not built yet. */
function Gate({ item }) {
  const member = useMember();
  if (!item.insiderTip) return null;
  const open = !!member;
  return (
    <section className={"gate" + (open ? " unlocked" : "")}>
      <div className="gate-inner">
        <span className="gate-eyebrow">{open ? "Unlocked" : "Locals only"}</span>
        <h3>The insider tip</h3>
        <p className="gate-sub">{open
          ? "You're on the list, so every insider tip is unlocked on this device. Here's this one."
          : "The bit we only tell the people on the list. Join free and every insider tip unlocks on this device."}</p>
        <div className="gate-tip">
          <span className="ico" aria-hidden="true">💡</span>
          <span className="txt">{item.insiderTip}</span>
        </div>
        {!open && (
          <MagicLink cta="Unlock free" note="One tap unlocks every insider tip on this device and gets you the weekly free-London email. No spam, leave any time." />
        )}
        {open && (
          <div className="gate-done"><span aria-hidden="true">✓</span> You're on the list — every insider tip is unlocked on this device.</div>
        )}
      </div>
    </section>
  );
}

/* ---- quick facts (left-accent panel) ---- */
function FactCard({ item }) {
  let domain = "";
  if (item.website) { try { domain = new URL(item.website).hostname.replace(/^www\./, ""); } catch (e) { domain = "Official site"; } }
  const rows = [
    { k: "Cost", v: <span className={"cost " + item.cost}>{SP_COST[item.cost]}</span> },
    item.hours && { k: "Hours", v: item.hours },
    item.bestTime && { k: "Best time", v: item.bestTime },
    { k: "Area", v: item.area },
    { k: "Tube", v: item.tube },
    item.postcode && { k: "Postcode", v: <span className="fact-copy">{item.postcode}<CopyBtn text={item.postcode} label="Copy postcode" /></span> },
    item.website && { k: "Website", v: <span className="fact-copy"><a className="fact-link" href={item.website} target="_blank" rel="noopener noreferrer">{domain}</a><CopyBtn text={item.website} label="Copy link" /></span> },
  ].filter(Boolean);
  return (
    <div className="factcard">
      <h3>Quick facts</h3>
      {rows.map((r) => (
        <div className="fact" key={r.k}>
          <div className="fact-k">{r.k}</div>
          <div className="fact-v">{r.v}</div>
        </div>
      ))}
    </div>
  );
}

/* ---- map card: real dark OSM map when the spot has coords, else stylised ---- */
function MapCard({ item }) {
  const elRef = React.useRef(null);
  const mapRef = React.useRef(null);
  const [near, setNear] = React.useState([]);
  const hasReal = !!(item.coords && typeof window !== "undefined" && window.L);
  React.useEffect(() => {
    if (!hasReal || !elRef.current || mapRef.current) return;
    const L = window.L;
    const here = item.coords;
    const map = L.map(elRef.current, { zoomControl: true, scrollWheelZoom: false, attributionControl: true }).setView(here, 15);
    L.tileLayer("https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png", {
      subdomains: "abcd", maxZoom: 19,
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> &copy; <a href="https://carto.com/attributions">CARTO</a>'
    }).addTo(map);
    // this spot — the red pin
    const mainIcon = L.divIcon({ className: "lfg-leaflet-pin", html: '<span class="lfg-tear"></span>', iconSize: [26, 26], iconAnchor: [13, 26] });
    const main = L.marker(here, { icon: mainIcon, zIndexOffset: 1000 }).addTo(map);
    main.bindPopup('<div class="lfg-pop"><strong>' + item.title + '</strong><a href="' + mapsUrl(item) + '" target="_blank" rel="noopener">View on Google Maps \u2197</a></div>');
    // other free spots within walking distance that have coords — the discovery layer
    const km = (a, b) => { const dLat = (b[0] - a[0]) * 111; const dLng = (b[1] - a[1]) * 111 * Math.cos(a[0] * Math.PI / 180); return Math.hypot(dLat, dLng); };
    const pool = (window.LFG_PILLARS || []).filter((s) => s.coords && s.id !== item.id);
    const nearList = pool.map((s) => ({ s, d: km(here, s.coords) })).filter((x) => x.d <= 1.2).sort((a, b) => a.d - b.d).slice(0, 4).map((x) => x.s);
    const nearIcon = L.divIcon({ className: "lfg-leaflet-pin near", html: '<span class="lfg-tear"></span>', iconSize: [18, 18], iconAnchor: [9, 18] });
    const bounds = [here];
    nearList.forEach((s) => {
      const m = L.marker(s.coords, { icon: nearIcon }).addTo(map);
      m.bindPopup('<div class="lfg-pop"><strong>' + s.title + '</strong><a href="' + lfgSpotPath(s.id) + '">View this spot \u2192</a></div>');
      bounds.push(s.coords);
    });
    if (nearList.length) map.fitBounds(bounds, { padding: [34, 34], maxZoom: 15 });
    mapRef.current = map;
    setNear(nearList.map((s) => ({ id: s.id, title: s.title })));
    return () => { map.remove(); mapRef.current = null; };
  }, [hasReal, item.id]);

  return (
    <div className="mapcard">
      {hasReal ? (
        <div className="mapview-real">
          <div className="mapview-canvas" ref={elRef} aria-label={"Map of " + item.title}></div>
          {near.length > 0 ? <span className="map-hint" aria-hidden="true">{near.length + " free spot" + (near.length > 1 ? "s" : "") + " nearby"}</span> : null}
        </div>
      ) : (
        <div className="mapview">
          <div className="grid" aria-hidden="true"></div>
          <div className="park" aria-hidden="true"></div>
          <div className="road r1" aria-hidden="true"></div>
          <div className="road r2" aria-hidden="true"></div>
          <div className="map-pin" aria-hidden="true"><div className="tear"></div></div>
        </div>
      )}
      <div className="map-foot">
        <div className="map-addr"><b>{item.area}</b>{item.postcode ? item.postcode + " · " : ""}near {item.tube}</div>
        <a className="map-go" href={dirUrl(item)} target="_blank" rel="noopener noreferrer">Get directions <ExtIcon /></a>
      </div>
      {near.length > 0 ? (
        <div className="map-near">
          <span className="map-near-label">Free spots a short walk away</span>
          <div className="map-near-list">
            {near.map((s) => (
              <a key={s.id} className="map-near-chip" href={lfgSpotPath(s.id)}>{s.title}</a>
            ))}
          </div>
        </div>
      ) : null}
    </div>
  );
}

/* ---- more like this — category-scored, context-fenced relevance ----
   Analysis (v0.33.0): the old version sliced the first 3 items that shared ANY
   tag from content.js alone — so it surfaced expired June events and never the
   pillars/directory. This builds a real candidate pool, drops expired + guides,
   fences London-spots vs day-trips, and ranks by category > tags > area. */

const REL_TAG_CAT = {
  views: "viewpoints-skylines", markets: "markets", walks: "walks-trails",
  outdoors: "parks-green-spaces", nature: "botanical-nature", culture: "cultural-community-spaces",
};

function relDirIndex() {
  if (relDirIndex._m) return relDirIndex._m;
  const m = {};
  ((window.LFG_DIRECTORY && window.LFG_DIRECTORY.spots) || []).forEach((s) => { m[s.id] = s; });
  return (relDirIndex._m = m);
}

function relCatOf(item) {
  const dir = relDirIndex()[item.id];
  if (dir && dir.cat) return dir.cat;
  if (item.catLabel) {
    const l = item.catLabel.toLowerCase();
    if (l.includes("museum")) return "museums";
    if (l.includes("galler")) return "art-galleries";
    if (l.includes("market")) return "markets";
    if (l.includes("view")) return "viewpoints-skylines";
    if (l.includes("park")) return "parks-green-spaces";
    if (l.includes("church") || l.includes("cathedral")) return "churches-cathedrals";
    if (l.includes("hidden")) return "hidden-gems-quirky";
  }
  for (const t of item.tags || []) if (REL_TAG_CAT[t]) return REL_TAG_CAT[t];
  return null;
}

function relExpired(i) {
  if (!i.end) return false;
  const d = new Date(i.end + "T23:59:59");
  return !isNaN(d) && d < new Date();
}

function relPool(item) {
  const dayTrip = !!item.daytrip;
  const byId = {};
  // priority 1+2: real-page sources (pillars, then content)
  (window.LFG_PILLARS || []).forEach((s) => { if (!byId[s.id]) byId[s.id] = s; });
  (window.LFG_CONTENT || []).forEach((s) => { if (!byId[s.id]) byId[s.id] = s; });
  // priority 3: directory spots (Maps-linked) fill out thin categories — London context only
  if (!dayTrip) {
    ((window.LFG_DIRECTORY && window.LFG_DIRECTORY.spots) || []).forEach((s) => {
      if (byId[s.id]) return;
      byId[s.id] = { id: s.id, kind: "spot", title: s.name, summary: s.desc, area: s.area,
        tube: s.tube, cost: "free", tags: s.tags || [], noDetail: !s.pillar, _dir: true };
    });
  }
  return Object.values(byId).filter((c) =>
    c.id !== item.id && c.kind !== "guide" && !relExpired(c) && !!c.daytrip === dayTrip
  );
}

function relScore(cand, item, sharedTagW, sameCatW) {
  let s = 0;
  const it = new Set(item.tags || []);
  const shared = (cand.tags || []).filter((t) => it.has(t)).length;
  if (item.daytrip) {
    if (cand.type && item.type && cand.type === item.type) s += sameCatW;   // coast↔coast etc
    if (cand.region && item.region && cand.region === item.region) s += 3;
    if (cand.dir && item.dir && cand.dir === item.dir) s += 1;
  } else {
    const ic = relCatOf(item), cc = relCatOf(cand);
    if (ic && cc && ic === cc) s += sameCatW;                               // same category
    if (cand.area && item.area && cand.area === item.area) s += 2;          // same neighbourhood
    if (!cand._dir) s += 1;                                                 // prefer real detail pages
  }
  s += shared * sharedTagW;
  return s;
}

function Related({ item }) {
  if (!item) return null;
  const pool = relPool(item);
  const sharedTagW = item.daytrip ? 1.5 : 2;
  const sameCatW = 6;
  const scored = pool
    .map((c) => ({ c, s: relScore(c, item, sharedTagW, sameCatW) }))
    .filter((x) => x.s > 0)
    .sort((a, b) => b.s - a.s);
  // fallback: if too few real matches, top up with same-context items so it is never empty/random
  let picks = scored.slice(0, 3).map((x) => x.c);
  if (picks.length < 3) {
    const have = new Set(picks.map((p) => p.id));
    pool.filter((c) => !have.has(c.id) && !c._dir).forEach((c) => { if (picks.length < 3) picks.push(c); });
  }
  if (!picks.length) return null;
  const heading = item.daytrip ? "More free days out" : "More like this";
  return (
    <section className="related">
      <div className="wrap">
        <div className="divider" aria-hidden="true"></div>
        <h2 className="sec-h">{heading}</h2>
        <div className="masonry">
          {picks.map((i) => <Card key={i.id} item={i} />)}
        </div>
      </div>
    </section>
  );
}

Object.assign(window, { Crumb, Hero, Lowdown, GetThere, SpotGetThere, Gate, FactCard, MapCard, Related, CopyBtn, ShareBtn, SpotSchema });
