/* London Free Guide — Save / bookmark feature.
   Principle: save instantly to localStorage (no login, no friction). Nudge a
   passwordless magic-link sign-up only to SYNC across devices once a few are
   saved. The save action is never gated; only the cross-device sync is.

   Three presentation DIRECTIONS, switchable live from the Tweaks panel:
     heart — quiet Instagram-style bookmark, review in a slide-over panel
     tray  — a planning "shortlist" that docks at the bottom as you browse
     map   — a collection you review as pins on a stylised London map

   The save state + persistence is shared; only the affordance, the nav entry
   and the review surface change per direction. This file self-mounts its own
   layer (tray + panel + toast) so any page that loads it gets the feature.
   Exports SaveButton, SavedNavButton, HeroSaveBtn for the shared Nav / cards. */

const { useState: useSv, useEffect: useEf, useMemo: useMemoSv } = React;

const SAVED_KEY   = "lfg-saved";
const PATTERN_KEY = "lfg-save-pattern";
const SYNC_KEY    = "lfg-sync";
const NUDGE_AT    = 3;                 /* show the sync nudge after this many */

const SAVED_COST    = window.LFG_COST || {};
const SAVED_CONTENT = (window.LFG_CONTENT || []).concat(window.LFG_PILLARS || []);
const SAVED_BY_ID   = {};
SAVED_CONTENT.forEach((i) => { SAVED_BY_ID[i.id] = i; });

/* fold in the whole A–Z directory so ANY directory spot can be saved and still
   resolve on the Saved page (which has no other knowledge of it). Normalise to
   the shared item shape; feed + pillars win on id collisions. Every page that
   loads saved.jsx must also load directory-data.js BEFORE it, or these ids
   silently drop on that page. */
((window.LFG_DIRECTORY && window.LFG_DIRECTORY.spots) || []).forEach((s) => {
  if (!SAVED_BY_ID[s.id]) {
    SAVED_BY_ID[s.id] = {
      id: s.id, kind: "spot", title: s.name, summary: s.desc,
      area: s.area, tube: s.tube, cost: "free", pillar: !!s.pillar,
      img: s.img || null, noDetail: !s.pillar
    };
  } else if (s.img && !SAVED_BY_ID[s.id].img) {
    /* feed/pillar object won this id but the A–Z record carries the photo —
       fold the directory image onto a COPY so the Saved card shows it without
       mutating the shared feed/pillar object other pages rely on */
    SAVED_BY_ID[s.id] = Object.assign({}, SAVED_BY_ID[s.id], { img: s.img });
  }
});

/* ---- per-direction copy + glyphs ---- */
const PATTERN = {
  heart: { corner: "\u2661", cornerOn: "\u2665", nav: "\u2665",
           verb: "Save", saved: "Saved", noun: "Saved",
           panelTitle: "Saved spots",
           empty: "Nothing saved yet. Tap the heart on any spot to keep it here.",
           toast: "Saved" },
  tray:  { corner: "+", cornerOn: "\u2713", nav: "\u2630",
           verb: "Add", saved: "On your list", noun: "List",
           panelTitle: "Your London list",
           empty: "Your list is empty. Add a few spots and plan a free day out.",
           toast: "Added to your list" },
  map:   { corner: "\uD83D\uDCCD", cornerOn: "\uD83D\uDCCD", nav: "\uD83D\uDCCD",
           verb: "Pin", saved: "Pinned", noun: "Map",
           panelTitle: "Your map",
           empty: "No pins yet. Pin spots and see them gathered on your map.",
           toast: "Pinned to your map" }
};
function cfgOf(p) { return PATTERN[p] || PATTERN.heart; }

/* clean stroke heart (replaces the colourful ♥ emoji) — outline when empty,
   filled with currentColor when saved. Used by the heart save pattern only;
   tray (+/✓) and map (📍) keep their own glyphs. */
function HeartIco({ filled }) {
  return (
    <svg className="hico" viewBox="0 0 24 24" fill={filled ? "currentColor" : "none"}
      stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M20.8 5.1a5.2 5.2 0 0 0-7.4 0L12 6.5l-1.4-1.4a5.2 5.2 0 1 0-7.4 7.4l1.4 1.4L12 21.3l7.4-7.4 1.4-1.4a5.2 5.2 0 0 0 0-7.4z" />
    </svg>
  );
}

/* ---- storage helpers + cross-component sync via window events ---- */
function readSaved() {
  try { return JSON.parse(localStorage.getItem(SAVED_KEY) || "[]"); } catch (e) { return []; }
}
function writeSaved(a) {
  try { localStorage.setItem(SAVED_KEY, JSON.stringify(a)); } catch (e) {}
  window.dispatchEvent(new CustomEvent("lfg-saved-change", { detail: a.slice() }));
}
function toggleSavedId(id) {
  const a = readSaved();
  const i = a.indexOf(id);
  let added;
  if (i >= 0) { a.splice(i, 1); added = false; } else { a.unshift(id); added = true; }
  writeSaved(a);
  return added;
}
function readPattern() {
  return document.documentElement.getAttribute("data-save-pattern") || "heart";
}
function savedItems(ids) { return ids.map((id) => SAVED_BY_ID[id]).filter(Boolean); }
function fireToast(item, pattern) {
  window.dispatchEvent(new CustomEvent("lfg-save-toast", { detail: { item: item, pattern: pattern } }));
}
/* the dedicated review surface is now a real page/route */
const SAVED_HREF = "Saved.html";
const LFG_SITE = "londonfreeguide.com";
function mapsDirAll(items) {
  if (!items.length) return "#";
  const pts = items.map((i) => encodeURIComponent(i.title + " " + i.area + " London"));
  if (pts.length === 1) {
    return "https://www.google.com/maps/search/?api=1&query=" + pts[0];
  }
  const capped = pts.slice(0, 10);          /* Maps allows ~9 waypoints + a destination */
  const destination = capped[capped.length - 1];
  const waypoints = capped.slice(0, -1).join("%7C");
  return "https://www.google.com/maps/dir/?api=1&destination=" + destination +
    (waypoints ? "&waypoints=" + waypoints : "");
}

/* a plain-text version of the list to copy / WhatsApp / email — brand + link
   baked in so every shared list points a friend back to LFG */
function buildShareText(items) {
  const lines = items.map((it, i) =>
    (i + 1) + ". " + it.title + " \u2014 " + it.area + " \u00b7 " + (SAVED_COST[it.cost] || "Free"));
  let txt = "My free London list \u00b7 via LONDON FREE GUIDE \u2614\n\n" + lines.join("\n");
  const route = mapsDirAll(items);
  if (route && route !== "#") txt += "\n\n\uD83D\uDDFA The route: " + route;
  txt += "\n\nMore free London \u2192 " + LFG_SITE;
  return txt;
}

/* ---- shared hooks ---- */
function useSavedIds() {
  const [ids, setIds] = useSv(readSaved);
  useEf(() => {
    const h = (e) => setIds(((e && e.detail) || readSaved()).slice());
    window.addEventListener("lfg-saved-change", h);
    return () => window.removeEventListener("lfg-saved-change", h);
  }, []);
  return ids;
}
function usePattern() {
  const [p, setP] = useSv(readPattern);
  useEf(() => {
    const h = () => setP(readPattern());
    window.addEventListener("lfg-pattern-change", h);
    return () => window.removeEventListener("lfg-pattern-change", h);
  }, []);
  return p;
}

/* ============================================================= *
 *  Affordances used inside the shared cards / nav / hero
 * ============================================================= */

/* corner button sitting over a post image (cards + rail) */
function SaveButton({ item }) {
  const ids = useSavedIds();
  const pattern = usePattern();
  const cfg = cfgOf(pattern);
  const saved = ids.indexOf(item.id) >= 0;
  function click(e) {
    e.preventDefault();
    e.stopPropagation();
    if (toggleSavedId(item.id)) fireToast(item, pattern);
  }
  return (
    <button
      type="button"
      className={"save-btn pat-" + pattern + (saved ? " on" : "")}
      aria-pressed={saved}
      aria-label={(saved ? "Remove " : "Save ") + item.title}
      title={saved ? cfg.saved : cfg.verb}
      onClick={click}
    >
      <span className="save-ico" aria-hidden="true">{pattern === "heart" ? <HeartIco filled={saved} /> : (saved ? cfg.cornerOn : cfg.corner)}</span>
    </button>
  );
}

/* nav entry with a live count; links through to the dedicated Saved page */
function SavedNavButton() {
  const ids = useSavedIds();
  const pattern = usePattern();
  const cfg = cfgOf(pattern);
  return (
    <a
      href={SAVED_HREF}
      className={"nav-saved pat-" + pattern + (ids.length ? " has" : "")}
      aria-label={"Open " + cfg.noun + ", " + ids.length + " saved"}
      title={cfg.panelTitle}
    >
      <span className="nav-saved-ico" aria-hidden="true">{pattern === "heart" ? <HeartIco filled={ids.length > 0} /> : cfg.nav}</span>
      {ids.length > 0 && <span className="nav-saved-count">{ids.length}</span>}
    </a>
  );
}

/* big button for the spot-page hero actions */
function HeroSaveBtn({ item }) {
  const ids = useSavedIds();
  const pattern = usePattern();
  const cfg = cfgOf(pattern);
  const saved = ids.indexOf(item.id) >= 0;
  function click() {
    if (toggleSavedId(item.id)) fireToast(item, pattern);
  }
  return (
    <button type="button" className={"btn btn-lg btn-ghost hero-save" + (saved ? " on" : "")} onClick={click}>
      <span className="hero-save-ico" aria-hidden="true">{pattern === "heart" ? <HeartIco filled={saved} /> : (saved ? cfg.cornerOn : cfg.corner)}</span>
      {saved ? cfg.saved : cfg.verb}
    </button>
  );
}

/* ============================================================= *
 *  Review surfaces (self-mounted layer)
 * ============================================================= */

/* the saved-page join nudge — honest, device-based. Saved spots live on this
   device; joining the free list unlocks insider tips here + the weekly email. */
function SyncNudge() {
  const member = useMember();
  if (member) {
    return (
      <div className="sync done">
        <span className="sync-tick" aria-hidden="true">{"\u2713"}</span>
        <span>You're on the list — insider tips are unlocked on this device and the weekly email's on its way. Your saved spots are kept on this device.</span>
      </div>
    );
  }
  return (
    <div className="sync">
      <div className="sync-copy">
        <span className="sync-hand">while you're here</span>
        <p>Your saved spots live on this device. Join the free list for the weekly roundup and to unlock every spot's insider tip on this device.</p>
      </div>
      <MagicLink cta="Join free" note="No spam. One tap to leave. We never sell your email." />
    </div>
  );
}

/* a stylised "your map" view — reuses the brand teardrop pin */
const PIN_POS = [
  { l: 24, t: 30 }, { l: 62, t: 22 }, { l: 44, t: 52 }, { l: 76, t: 58 },
  { l: 18, t: 64 }, { l: 54, t: 78 }, { l: 86, t: 38 }, { l: 34, t: 84 }
];
function SavedMap({ items }) {
  return (
    <div className="savedmap" role="img" aria-label={items.length + " saved spots on a map"}>
      <div className="grid"></div>
      <span className="road r1"></span>
      <span className="road r2"></span>
      <span className="park"></span>
      <span className="river"></span>
      {items.slice(0, PIN_POS.length).map((it, i) => (
        <span className="smap-pin" key={it.id} style={{ left: PIN_POS[i].l + "%", top: PIN_POS[i].t + "%" }}>
          <span className="tear"><b>{i + 1}</b></span>
        </span>
      ))}
    </div>
  );
}

/* small line-icon set for the share buttons (no emoji slop) */
function ShareIco({ name }) {
  const p = {
    copy: <React.Fragment><rect x="8.5" y="8.5" width="11" height="11" rx="2.5" /><path d="M5.5 15.5V5.5a2 2 0 0 1 2-2h8" /></React.Fragment>,
    chat: <path d="M4 5.5h16v10H9l-4 3.5v-3.5H4z" />,
    mail: <React.Fragment><rect x="3.5" y="5.5" width="17" height="13" rx="2" /><path d="M4 6.5l8 6 8-6" /></React.Fragment>
  };
  return (
    <svg className="shbtn-ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      {p[name]}
    </svg>
  );
}

/* share the saved list off the device — copy, WhatsApp, email.
   On phones the system share sheet is offered first (covers Messages, AirDrop,
   every app); the explicit three always show as a reliable fallback. */
function ShareRow({ items }) {
  const [copied, setCopied] = useSv(false);
  const text = buildShareText(items);
  const subject = "My free London list \u2614";
  const wa = "https://wa.me/?text=" + encodeURIComponent(text);
  const mail = "mailto:?subject=" + encodeURIComponent(subject) + "&body=" + encodeURIComponent(text);
  const canNative = typeof navigator !== "undefined" && typeof navigator.share === "function";

  function copy() {
    try {
      navigator.clipboard.writeText(text).then(function () {
        setCopied(true);
        setTimeout(function () { setCopied(false); }, 1800);
      });
    } catch (e) {}
  }
  function native() {
    try { navigator.share({ title: subject, text: text }).catch(function () {}); } catch (e) {}
  }

  return (
    <div className="savedpg-share">
      <span className="savedpg-share-label">Send it to whoever you're dragging along</span>
      <div className="savedpg-share-btns">
        {canNative && (
          <button type="button" className="shbtn shbtn-share" onClick={native}>Share</button>
        )}
        <button type="button" className="shbtn" onClick={copy}>
          <ShareIco name="copy" />{copied ? "Copied \u2713" : "Copy"}
        </button>
        <a className="shbtn" href={wa} target="_blank" rel="noopener noreferrer">
          <ShareIco name="chat" />WhatsApp
        </a>
        <a className="shbtn" href={mail}>
          <ShareIco name="mail" />Email
        </a>
      </div>
    </div>
  );
}

/* ---- the dedicated Saved page body (rendered inside Saved.html) ---- */

/* per-direction page header copy */
const PAGE_HEAD = {
  heart: { eyebrow: "your little black book of free London",
           a: "Saved", accent: "spots",
           line: "Kept for whenever you fancy them. Tap a heart again to let one go." },
  tray:  { eyebrow: "a free day, building",
           a: "Your London", accent: "list",
           line: "Lined up in order. Open each for the lowdown or send the lot to Maps as one route." },
  map:   { eyebrow: "gathered across town",
           a: "Your", accent: "map",
           line: "Everywhere you've pinned, in one place. Pick what's nearest and go." }
};

/* a roomy page row (tray + map directions) */
function SavedPageRow({ item, idx, numbered }) {
  const maps = "https://www.google.com/maps/search/?api=1&query=" +
    encodeURIComponent(item.title + " " + item.area + " London");
  const href = item.noDetail ? maps
    : (item.kind === "guide" ? "Guide.html?id=" : "Spot.html?id=") + item.id;
  const ext = item.noDetail ? { target: "_blank", rel: "noopener noreferrer" } : {};
  return (
    <div className="prow">
      {numbered && <span className="prow-num">{idx + 1}</span>}
      <a className={"prow-thumb" + (item.img ? "" : " noimg")} href={href} {...ext}>{item.img ? <img src={item.img} alt="" loading="lazy" /> : <span className="prow-thumb-free">Free</span>}</a>
      <div className="prow-main">
        <a className="prow-title" href={href} {...ext}>{item.title}</a>
        <p className="prow-sum">{item.summary}</p>
        <div className="prow-meta">
          <span className={"cost " + item.cost}>{SAVED_COST[item.cost]}</span>
          <span className="prow-loc"><span aria-hidden="true">{"\uD83D\uDCCD"}</span> {item.area} · {item.tube}</span>
          {item.hours && <span className="prow-hours"><span aria-hidden="true">{"\uD83D\uDD50"}</span> {item.hours}</span>}
        </div>
      </div>
      <div className="prow-actions">
        <a className="prow-open" href={href} {...ext}>Open <span aria-hidden="true">{"\u2192"}</span></a>
        <a className="prow-map" href={maps} target="_blank" rel="noopener noreferrer" aria-label={"Directions to " + item.title}><span aria-hidden="true">{"\u2197"}</span> Maps</a>
        <button className="prow-remove" onClick={() => toggleSavedId(item.id)} aria-label={"Remove " + item.title}>{"\u00d7"}</button>
      </div>
    </div>
  );
}

function SavedPage() {
  const ids = useSavedIds();
  const pattern = usePattern();
  const cfg = cfgOf(pattern);
  const head = PAGE_HEAD[pattern] || PAGE_HEAD.heart;
  const items = useMemoSv(() => savedItems(ids), [ids]);
  const hasItems = items.length > 0;
  const showNudge = items.length >= NUDGE_AT;
  const numbered = pattern === "tray" || pattern === "map";

  return (
    <section className="savedpg">
      <div className="wrap">
        <header className="savedpg-head">
          <span className="eyebrow">{head.eyebrow}</span>
          <h1 className="savedpg-title">{head.a} <span className="r">{head.accent}</span></h1>
          <div className="savedpg-subrow">
            <span className="savedpg-count">{items.length} {items.length === 1 ? "spot" : "spots"}</span>
            <p className="savedpg-line">{head.line}</p>
          </div>
        </header>

        {hasItems && (
          <div className="savedpg-actions">
            <a className="sbtn sbtn-primary" href={mapsDirAll(items)} target="_blank" rel="noopener noreferrer">
              <span aria-hidden="true">{"\uD83D\uDDFA"}</span> Send {items.length > 1 ? "all to Maps" : "to Maps"}
            </a>
            <button className="sbtn sbtn-ghost" onClick={() => writeSaved([])}>Clear all</button>
          </div>
        )}

        {hasItems && <ShareRow items={items} />}
      </div>

      <div className="wrap">
        {!hasItems ? (
          <div className="savedpg-empty">
            <span className="savedpg-empty-ico" aria-hidden="true">{cfg.nav}</span>
            <p className="savedpg-empty-hand">nothing here yet</p>
            <p>{cfg.empty}</p>
            <a className="sbtn sbtn-primary" href="Home.html">Browse free spots</a>
          </div>
        ) : (
          <React.Fragment>
            {pattern === "map" && <SavedMap items={items} />}
            {pattern === "heart" ? (
              <div className="masonry savedpg-grid">
                {items.map((it) => <Card key={it.id} item={it} />)}
              </div>
            ) : (
              <div className="savedpg-rows">
                {items.map((it, i) => <SavedPageRow key={it.id} item={it} idx={i} numbered={numbered} />)}
              </div>
            )}
            {showNudge && <div className="savedpg-sync"><SyncNudge /></div>}
          </React.Fragment>
        )}
      </div>
    </section>
  );
}

/* the docked planning tray (tray direction only) — links to the Saved page */
function ShortlistTray({ ids }) {
  const items = savedItems(ids);
  if (!items.length) return null;
  return (
    <div className="tray">
      <a className="tray-inner" href={SAVED_HREF} aria-label={"Open your list, " + items.length + " spots"}>
        <span className="tray-thumbs">
          {items.slice(0, 3).map((i) => (i.img ? <img key={i.id} src={i.img} alt="" /> : <span key={i.id} className="tray-thumb-noimg" aria-hidden="true">F</span>))}
          {items.length > 3 && <span className="tray-more">+{items.length - 3}</span>}
        </span>
        <span className="tray-label"><b>{items.length} {items.length === 1 ? "spot" : "spots"}</b> on your list</span>
        <span className="tray-cta">{"Plan the day \u2192"}</span>
      </a>
    </div>
  );
}

/* transient confirmation toast */
function SaveToast({ toast }) {
  if (!toast) return null;
  const cfg = cfgOf(toast.pattern);
  return (
    <div className="save-toast" key={toast.key} role="status">
      <span className="save-toast-txt"><b>{cfg.toast}</b><span className="save-toast-name"> · {toast.item.title}</span></span>
      <a className="save-toast-view" href={SAVED_HREF}>View {cfg.noun.toLowerCase()}</a>
    </div>
  );
}

/* the self-mounted layer: tray + toast, wired to the window events.
   Suppressed on the Saved page itself (it already shows everything). */
function SavedLayer() {
  const ids = useSavedIds();
  const pattern = usePattern();
  const [toast, setToast] = useSv(null);
  const onSavedPage = /Saved\.html$/i.test(location.pathname);

  /* lift Brolly above the tray on small screens while the tray is docked */
  useEf(() => {
    document.body.classList.toggle("has-tray", !onSavedPage && pattern === "tray" && ids.length > 0);
    return () => document.body.classList.remove("has-tray");
  }, [pattern, ids.length, onSavedPage]);

  useEf(() => {
    let timer;
    const h = (e) => {
      setToast(Object.assign({ key: Date.now() }, e.detail));
      clearTimeout(timer);
      timer = setTimeout(() => setToast(null), 2800);
    };
    window.addEventListener("lfg-save-toast", h);
    return () => { window.removeEventListener("lfg-save-toast", h); clearTimeout(timer); };
  }, []);

  if (onSavedPage) return null;

  return (
    <React.Fragment>
      {pattern === "tray" && <ShortlistTray ids={ids} />}
      <SaveToast toast={toast} />
    </React.Fragment>
  );
}

Object.assign(window, { SaveButton, SavedNavButton, HeroSaveBtn, SavedPage });

/* self-mount the saved layer once, independent of each page's app root */
(function () {
  if (document.getElementById("lfg-saved-root")) return;
  var el = document.createElement("div");
  el.id = "lfg-saved-root";
  document.body.appendChild(el);
  ReactDOM.createRoot(el).render(<SavedLayer />);
})();
