/* ============================================================
   Peter Walters / links — app
   Content lives in content.json; admin at /admin.html
   ============================================================ */

const { useState, useEffect, useRef } = React;
const LIVE = (typeof window !== 'undefined' && window.PWLive) || null;
const vis = (it) => LIVE ? LIVE.isVisible(it) : (it && it.enabled !== false);

/* ── Icons ──────────────────────────────────────────────── */
const Icon = {
  Instagram: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <rect x="3" y="3" width="18" height="18" rx="5"/>
      <circle cx="12" cy="12" r="4"/>
      <circle cx="17.5" cy="6.5" r="0.8" fill="currentColor" stroke="none"/>
    </svg>
  ),
  Spotify: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <circle cx="12" cy="12" r="9.2"/>
      <path d="M7.5 9.5c3.2-1 7-0.8 10.2 1"/>
      <path d="M8 13c2.8-0.8 6-0.6 8.5 1"/>
      <path d="M8.5 16c2.2-0.6 4.6-0.5 6.6 0.7"/>
    </svg>
  ),
  Substack: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <rect x="5" y="5" width="14" height="2" fill="currentColor"/>
      <rect x="5" y="10" width="14" height="2" fill="currentColor"/>
      <path d="M5 15h14v5l-7-3.5L5 20v-5z" fill="currentColor"/>
    </svg>
  ),
  LinkedIn: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <rect x="3" y="3" width="18" height="18" rx="2"/>
      <path d="M7 10v7" /><circle cx="7" cy="7.2" r="0.9" fill="currentColor" stroke="none"/>
      <path d="M11 17v-4.5c0-1.4 1-2.5 2.4-2.5s2.6 1.1 2.6 2.5V17"/>
      <path d="M11 17v-7"/>
    </svg>
  ),
  Mail: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <rect x="3" y="5" width="18" height="14" rx="2"/>
      <path d="M3.5 6.5l8.5 6.5 8.5-6.5"/>
    </svg>
  ),
  Globe: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <circle cx="12" cy="12" r="9"/>
      <path d="M3 12h18"/>
      <path d="M12 3c2.8 3 2.8 15 0 18M12 3c-2.8 3-2.8 15 0 18"/>
    </svg>
  ),
  Arrow: () => (
    <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M4 12L12 4"/>
      <path d="M6 4h6v6"/>
    </svg>
  ),
  Chev: () => (
    <svg viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M3 5l4 4 4-4"/>
    </svg>
  ),
  Play: () => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <circle cx="12" cy="12" r="9"/>
      <path d="M10 8.5l5 3.5-5 3.5z" fill="currentColor"/>
    </svg>
  ),
};

const SOCIAL_ICONS = {
  ig: Icon.Instagram, sp: Icon.Spotify, sub: Icon.Substack,
  li: Icon.LinkedIn,  mail: Icon.Mail,   web: Icon.Globe,
};

/* ── Components ─────────────────────────────────────────── */

function Header({ profile, socials }) {
  return (
    <header className="hdr">
      <div className="portrait">
        <img src="assets/portrait.jpg" alt={profile.name} width="192" height="192" fetchpriority="high" />
      </div>
      <div className="handle">{profile.location}</div>
      <h1 className="name">{profile.name}</h1>
      <p className="bio">
        {profile.bio_line1}
        <br/>
        {profile.bio_line2}
      </p>
      <nav className="socials" aria-label="Social links">
        {socials.filter(vis).map(({ id, label, href }) => {
          const Comp = SOCIAL_ICONS[id] || Icon.Globe;
          return (
            <a key={id} href={href} aria-label={label} title={label} target="_blank" rel="noopener noreferrer">
              <Comp />
            </a>
          );
        })}
      </nav>
    </header>
  );
}

function Tabs({ tab, setTab }) {
  return (
    <div className="tabs" role="tablist" aria-label="Sections">
      <button role="tab" aria-selected={tab === "yoga"} className="tab" onClick={() => setTab("yoga")}>
        <span className="num">01 / Practice</span>
        Yoga
      </button>
      <button role="tab" aria-selected={tab === "build"} className="tab" onClick={() => setTab("build")}>
        <span className="num">02 / Make</span>
        Building
      </button>
      <div className="tabs-rail" aria-hidden="true" />
      <div className={"tabs-ind" + (tab === "build" ? " is-right" : "")} aria-hidden="true" />
    </div>
  );
}

/* Live ribbon — auto-computes next chronological event */
function NextUp({ content }) {
  const [ev, setEv] = useState(() => (LIVE ? LIVE.next(content) : null));
  useEffect(() => {
    if (!LIVE) return;
    setEv(LIVE.next(content));
    const id = setInterval(() => setEv(LIVE.next(content)), 60_000);
    return () => clearInterval(id);
  }, [content]);
  if (!ev) return null;
  return (
    <a className="nextup rise" style={stagger(0)} href={ev.studio?.href || (content.monthly_class && content.monthly_class.href) || '#'} target="_blank" rel="noopener noreferrer">
      <span className="nextup-dot" aria-hidden="true" />
      <span className="nextup-kicker">Next</span>
      <span className="nextup-when">{ev.dayLabel} · {ev.time}</span>
      <span className="nextup-sep">/</span>
      <span className="nextup-where">{ev.label}{ev.city ? <span className="nextup-city"> · {ev.city}</span> : null}</span>
    </a>
  );
}

/* Compact sticky nav that slides in on scroll */
function StickyNav({ tab, setTab, profile, visible }) {
  return (
    <div className={"sticky-nav" + (visible ? " is-visible" : "")} aria-hidden={!visible}>
      <div className="sticky-inner">
        <div className="sticky-id">
          <img src="assets/portrait.jpg" alt="" width="56" height="56" />
          <span>{profile.name.split(' ')[0]}</span>
        </div>
        <div className="sticky-tabs" role="tablist">
          <button role="tab" aria-selected={tab === 'yoga'}  onClick={() => setTab('yoga')}>Yoga</button>
          <button role="tab" aria-selected={tab === 'build'} onClick={() => setTab('build')}>Building</button>
          <span className={"sticky-ind" + (tab === 'build' ? ' is-right' : '')} aria-hidden="true" />
        </div>
      </div>
    </div>
  );
}

function stagger(i) {
  return { animationDelay: `${60 + i * 55}ms` };
}

function Row({ i, href, title, sub, tag }) {
  return (
    <a className="row rise" style={stagger(i)} href={href} target="_blank" rel="noopener noreferrer">
      <div className="meta">
        <span className="title">{title}</span>
        {sub && <span className="sub">{sub}</span>}
      </div>
      {tag && <span className="tag">{tag}</span>}
      <span className="arrow"><Icon.Arrow /></span>
    </a>
  );
}

function Hero({ i, hero }) {
  return (
    <a className="hero rise" style={stagger(i)} href={hero.href} target="_blank" rel="noopener noreferrer">
      <div className="hero-kicker">
        <span className="dot" /> {hero.kicker}
      </div>
      <h2 className="hero-title">{hero.title}</h2>
      <p className="hero-sub">{hero.subtitle}</p>
      <div className="hero-foot">
        <span>{hero.cta_label}</span>
        <span>{hero.url_label} &nbsp;↗</span>
      </div>
    </a>
  );
}

function Podcast({ i, data }) {
  // Convert show URL → embed URL: open.spotify.com/show/X → open.spotify.com/embed/show/X
  const embedSrc = (data.href || '').replace('open.spotify.com/show/', 'open.spotify.com/embed/show/');
  return (
    <div className="podcast rise" style={stagger(i)}>
      <iframe
        title={data.title}
        src={embedSrc + (embedSrc.includes('?') ? '&' : '?') + 'utm_source=generator&theme=0'}
        loading="lazy"
        frameBorder="0"
        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
        allowFullScreen
      />
      <a className="podcast-out" href={data.href} target="_blank" rel="noopener noreferrer">
        Open in Spotify <Icon.Arrow />
      </a>
    </div>
  );
}

function MonthlyClass({ i, data }) {
  return (
    <a className="monthly rise" style={stagger(i)} href={data.href} target="_blank" rel="noopener noreferrer">
      <div className="monthly-cal" aria-hidden="true">
        <span className="dot" />
        <span className="label">Next</span>
      </div>
      <div className="monthly-body">
        <div className="monthly-head">
          <span className="title">{data.title}</span>
          {data.tag && <span className="tag">{data.tag}</span>}
        </div>
        <span className="date">{data.date}</span>
        <p className="desc">{data.description}</p>
      </div>
      <span className="arrow"><Icon.Arrow /></span>
    </a>
  );
}

/* Count-up on the retreat months-out numeral. Parses the
   leading integer out of whatever PWLive returns ("5MO OUT")
   and animates 0→n once. Reduced-motion / no-number → static. */
function CountUp({ text }) {
  const ref = useRef(null);
  useEffect(() => {
    const num = String(text || "").match(/\d+/);
    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    const node = ref.current && ref.current.querySelector(".cu-n");
    if (!num || reduce || !node) return;
    const target = parseInt(num[0], 10);
    node.textContent = "0";
    let startT = null;
    const dur = Math.min(900, 260 + target * 55);
    let raf;
    const tick = (t) => {
      if (startT == null) startT = t;
      const p = Math.min(1, (t - startT) / dur);
      const eased = 1 - Math.pow(1 - p, 3);
      node.textContent = String(Math.round(eased * target));
      if (p < 1) raf = requestAnimationFrame(tick);
      else node.textContent = String(target);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [text]);
  const parts = String(text || "").match(/^(\D*)(\d+)(.*)$/);
  if (!parts) return <span className="date-countdown">{text}</span>;
  return (
    <span className="date-countdown" ref={ref}>
      {parts[1]}<span className="cu-n">{parts[2]}</span>{parts[3]}
    </span>
  );
}

function Retreat({ i, r }) {
  const hasImage = !!r.image;
  const start = LIVE ? LIVE.parseRetreat(r.date) : null;
  const countdown = (start && LIVE) ? LIVE.monthsBetween(new Date(), start) : null;
  return (
    <a className="greece rise" style={stagger(i)} href={r.href} target="_blank" rel="noopener noreferrer">
      <div className={"greece-img" + (hasImage ? "" : " is-placeholder")}>
        {hasImage ? (
          <img src={r.image} alt={r.title} loading="lazy" decoding="async"
               onError={(e) => { e.currentTarget.style.display = 'none'; e.currentTarget.parentElement.classList.add('is-placeholder'); }} />
        ) : (
          <div className="placeholder-pattern" aria-hidden="true">
            <span>{r.title}</span>
          </div>
        )}
        <div className="greece-overlay">
          <div className="top">
            <span>{r.kicker}</span>
            <span className="date">
              {r.date}
              {countdown && <CountUp text={countdown} />}
            </span>
          </div>
          <div className="bot">
            <h3>{r.title}</h3>
            <p>{r.description}</p>
          </div>
        </div>
      </div>
      {r.marquee && r.marquee.length > 0 && (
        <div className="marquee" aria-hidden="true">
          <div className="marquee-track">
            {Array.from({length: 4}).map((_, k) => (
              <React.Fragment key={k}>
                {r.marquee.map((t, j) => (
                  <span key={j}>{t}</span>
                ))}
              </React.Fragment>
            ))}
          </div>
        </div>
      )}
    </a>
  );
}

function SectionLabel({ children }) {
  return (
    <div className="section-label">
      <span>{children}</span>
      <span className="line" />
    </div>
  );
}

function Studios({ i, studios, nextEv }) {
  return (
    <div className="studio rise" style={stagger(i)}>
      {studios.map((s) => {
        // Is this studio's next slot the chronological next event?
        const isNext = nextEv && nextEv.type === 'studio' &&
          (nextEv.studio.id === s.id || nextEv.studio.name === s.name);
        return (
          <a key={s.id || s.name} className={"studio-row" + (isNext ? ' is-next' : '')} href={s.href} target="_blank" rel="noopener noreferrer">
            <span className="place">
              {s.name}
              {isNext && <span className="next-pill">{nextEv.dayLabel}</span>}
            </span>
            <span className="meta">
              <span className="when">{s.when}</span>
              <span className="city">{s.city}</span>
            </span>
            <span className="arrow"><Icon.Arrow /></span>
          </a>
        );
      })}
    </div>
  );
}

function Expand({ i, title, count, children }) {
  const [open, setOpen] = useState(false);
  return (
    <div className="expand rise" data-open={open} style={stagger(i)}>
      <button className="expand-head" onClick={() => setOpen(o => !o)} aria-expanded={open}>
        <span className="title">{title}</span>
        <span className="count">{count}</span>
        <span className="chev"><Icon.Chev /></span>
      </button>
      <div className="expand-body">
        <div className="inner">
          <div className="expand-list">{children}</div>
        </div>
      </div>
    </div>
  );
}

function ExpandLink({ title, ext, code, href }) {
  return (
    <a href={href} target="_blank" rel="noopener noreferrer">
      <span className="ttl">{title}</span>
      {code && <span className="code">CODE · {code}</span>}
      <span className="ext">{ext}&nbsp;↗</span>
    </a>
  );
}

function YogaPanel({ content, nextEv }) {
  let i = 0;
  const retreats = (content.retreats || []).filter(vis);
  const studios  = (content.studios || []).filter(vis);
  const affiliates = (content.affiliates || []).filter(vis);
  return (
    <section className="panel" role="tabpanel" aria-label="Yoga">
      {vis(content.hero) && <Hero i={i++} hero={content.hero} />}

      {retreats.map((r) => (
        <Retreat key={r.id} i={i++} r={r} />
      ))}

      {vis(content.monthly_class) && (
        <>
          <SectionLabel>Monthly Online Class</SectionLabel>
          <MonthlyClass i={i++} data={content.monthly_class} />
        </>
      )}

      {vis(content.podcast) && (
        <>
          <SectionLabel>Listen</SectionLabel>
          <Podcast i={i++} data={content.podcast} />
        </>
      )}

      {studios.length > 0 && (
        <>
          <SectionLabel>Studio Classes</SectionLabel>
          <Studios i={i++} studios={studios} nextEv={nextEv} />
        </>
      )}

      {vis(content.membership) && (
        <>
          <SectionLabel>Membership</SectionLabel>
          <Row i={i++}
            title={content.membership.title}
            sub={content.membership.subtitle}
            tag={content.membership.tag}
            href={content.membership.href}
          />
        </>
      )}

      {affiliates.length > 0 && (
        <>
          <SectionLabel>More</SectionLabel>
          <Expand i={i++} title="Things I love" count={`${affiliates.length} items`}>
            {affiliates.map((a) => <ExpandLink key={a.id || a.title} {...a} />)}
          </Expand>
        </>
      )}
    </section>
  );
}

function BuildPanel({ content }) {
  const { cards, contact_label, contact_email } = content.building;
  const visCards = (cards || []).filter(vis);
  return (
    <section className="panel" role="tabpanel" aria-label="Building">
      {visCards.map((c, idx) => (
        <a key={c.id || c.title} className="build-card rise" style={stagger(idx)} href={c.href} target="_blank" rel="noopener noreferrer">
          <div className="kicker">
            <span>{c.kicker}</span>
            <span className="yr">{c.year}</span>
          </div>
          <h3>{c.title}</h3>
          <p>{c.description}</p>
          <div className="foot">
            <span>{c.footer_left}</span>
            <span className="go">{c.footer_right} <Icon.Arrow /></span>
          </div>
        </a>
      ))}

      <div className="build-contact rise" style={stagger(visCards.length)}>
        <span>{contact_label}</span>
        <a href={`mailto:${contact_email}`}>{contact_email}</a>
      </div>
    </section>
  );
}

/* ── App ────────────────────────────────────────────────── */

function App() {
  const [content, setContent] = useState(null);
  const [error, setError] = useState(null);
  const [scrolled, setScrolled] = useState(false);
  const [tab, setTabState] = useState(() => {
    if (typeof window !== "undefined") {
      if (window.location.hash === "#building") return "build";
      if (window.location.hash === "#yoga") return "yoga";
      try {
        const saved = localStorage.getItem("pw_tab");
        if (saved === "build" || saved === "yoga") return saved;
      } catch (e) { /* noop */ }
    }
    return "yoga";
  });

  // View Transitions API for buttery tab swap (Chrome/Edge/Safari TP)
  const setTab = (next) => {
    if (next === tab) return;
    try { localStorage.setItem("pw_tab", next); } catch (e) {}
    if (typeof document !== "undefined" && document.startViewTransition) {
      document.startViewTransition(() => setTabState(next));
    } else {
      setTabState(next);
    }
  };

  useEffect(() => {
    // Allow ?preview reading a draft from localStorage (admin preview)
    const params = new URLSearchParams(window.location.search);
    if (params.get("preview") === "1") {
      try {
        const draft = localStorage.getItem("pw_content_draft");
        if (draft) {
          setContent(JSON.parse(draft));
          return;
        }
      } catch (e) { /* fall through */ }
    }
    // bust cache so edits to content.json show up quickly
    fetch("content.json?v=" + Date.now())
      .then(r => r.json())
      .then(setContent)
      .catch(e => setError(e.message));
  }, []);

  useEffect(() => {
    const newHash = tab === "build" ? "#building" : "#yoga";
    if (window.location.hash !== newHash) {
      history.replaceState(null, "", newHash + window.location.search);
    }
    // Theme switches with tab — yoga = light brand, build = dark
    document.documentElement.setAttribute("data-theme", tab === "build" ? "build" : "yoga");
    document.querySelector('meta[name="theme-color"]')?.setAttribute(
      "content", tab === "build" ? "#2a2722" : "#F7EFDA"
    );
  }, [tab]);

  // Sticky-nav threshold
  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 320);
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  // Magnetic pull on link rows (desktop, non-reduced-motion).
  // Event-delegated so it survives panel remounts on tab switch.
  useEffect(() => {
    const fine = window.matchMedia("(hover: hover) and (pointer: fine)").matches;
    const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (!fine || reduce) return;
    const SEL = ".row,.hero,.greece,.monthly,.build-card,.nextup,.studio-row,.expand-head";
    const MAX = 6;
    let cur = null;
    const clear = (el) => { if (el) { el.style.setProperty("--mx", "0px"); el.style.setProperty("--my", "0px"); } };
    const move = (e) => {
      const el = e.target.closest ? e.target.closest(SEL) : null;
      if (cur && cur !== el) clear(cur);
      cur = el;
      if (!el) return;
      const r = el.getBoundingClientRect();
      const dx = Math.max(-1, Math.min(1, (e.clientX - (r.left + r.width / 2)) / (r.width / 2)));
      const dy = Math.max(-1, Math.min(1, (e.clientY - (r.top + r.height / 2)) / (r.height / 2)));
      el.style.setProperty("--mx", (dx * MAX).toFixed(1) + "px");
      el.style.setProperty("--my", (dy * MAX).toFixed(1) + "px");
    };
    const leave = () => { clear(cur); cur = null; };
    document.addEventListener("pointermove", move, { passive: true });
    document.addEventListener("pointerleave", leave, true);
    window.addEventListener("blur", leave);
    return () => {
      document.removeEventListener("pointermove", move);
      document.removeEventListener("pointerleave", leave, true);
      window.removeEventListener("blur", leave);
    };
  }, []);

  if (error) {
    return <main className="app"><div className="loading">Failed to load content.json — {error}</div></main>;
  }
  if (!content) {
    return <main className="app"><div className="loading"><span className="loading-dot"/></div></main>;
  }

  const nextEv = LIVE ? LIVE.next(content) : null;

  return (
    <>
      <StickyNav tab={tab} setTab={setTab} profile={content.profile} visible={scrolled} />
      <main className="app">
        <Header profile={content.profile} socials={content.socials} />
        <NextUp content={content} />
        <Tabs tab={tab} setTab={setTab} />
        <div key={tab} className="panel-wrap">
          {tab === "yoga"
            ? <YogaPanel content={content} nextEv={nextEv} />
            : <BuildPanel content={content} />}
        </div>
        <footer className="foot">
          {content.footer}
        </footer>
      </main>
    </>
  );
}

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