// Gotanda Guide — components

const { useState, useEffect, useRef, useMemo, useCallback } = React;

const PH_GRADS = [
  'linear-gradient(135deg, #2a1208, #5a2a14)',     // izakaya warm
  'linear-gradient(135deg, #1a2026, #3a4a5e)',     // riverside cool
  'linear-gradient(135deg, #0e1428, #2a1740)',     // skyloft deep
  'linear-gradient(135deg, #1a1206, #2a1a06)',     // ramen amber
  'linear-gradient(135deg, #100c0a, #2c241c)',     // sushi neutral
  'linear-gradient(135deg, #1a1a0e, #3a3a18)',     // morning olive
  'linear-gradient(135deg, #0a0a18, #182040)',     // jazz blue
  'linear-gradient(135deg, #1a141a, #322830)',     // hotel mauve
  'linear-gradient(135deg, #161410, #2c2820)',     // shop tan
  'linear-gradient(135deg, #0a1a14, #18402a)',     // park green
  'linear-gradient(135deg, #1c1018, #401830)',     // hub magenta
  'linear-gradient(135deg, #1a0e1a, #401a3a)',     // sunset
];

const PH_CAPS = [
  'IZAKAYA · NIGHT',
  'CAFE · RIVER',
  'BAR · 28F VIEW',
  'RAMEN · LATE',
  'SUSHI · COUNTER',
  'CAFE · MORNING',
  'JAZZ · BASEMENT',
  'HOTEL · LOBBY',
  'SHOP · STATION',
  'PARK · HILLTOP',
  'COWORK · DESK',
  'ROOFTOP · DUSK',
];

function Thumb({ idx, image }) {
  if (image) {
    return <img className="thumb-img" src={image} alt="" loading="lazy" />;
  }
  const grad = PH_GRADS[idx % PH_GRADS.length];
  const cap = PH_CAPS[idx % PH_CAPS.length];
  return (
    <div className="thumb-ph" style={{ '--ph-grad': grad }}>
      <span className="ph-cap">{cap}</span>
    </div>
  );
}

function Reveal({ children, delay = 0, as: Tag = 'div', ...rest }) {
  const ref = useRef(null);
  const [shown, setShown] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) {
          setTimeout(() => setShown(true), delay);
          io.disconnect();
        }
      });
    }, { rootMargin: '-40px 0px' });
    io.observe(ref.current);
    return () => io.disconnect();
  }, [delay]);
  return (
    <Tag ref={ref} className={`reveal ${shown ? 'in' : ''} ${rest.className || ''}`} {...rest}>
      {children}
    </Tag>
  );
}

// ── Top bar ────────────────────────────────────────
function TopBar({ lang, onLang, theme, onTheme, query, onQuery, I18N, tab, onTab, savedCount, onImport }) {
  const [scrolled, setScrolled] = useState(false);
  useEffect(() => {
    const onScroll = () => setScrolled(window.scrollY > 8);
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);

  return (
    <div className={`topbar ${scrolled ? 'scrolled' : ''}`}>
      <div className="topbar-row">
        <div className="brand">
          <span className="brand-dot"></span>
          <span style={{ fontFamily: 'var(--font-sans)' }}>GOTANDA</span>
          <span className="brand-jp">／ {lang === 'ja' ? '五反田' : 'GUIDE'}</span>
        </div>
        <nav className="desktop-nav">
          {[
            { id: 'home',  label: I18N.nav.home[lang] },
            { id: 'spots', label: I18N.nav.spots[lang] },
            { id: 'map',   label: I18N.nav.map[lang] },
            { id: 'saved', label: I18N.nav.saved[lang], badge: savedCount },
          ].map((n) => (
            <button
              key={n.id}
              className={tab === n.id ? 'is-on' : ''}
              onClick={() => onTab && onTab(n.id)}
            >
              {n.label}
              {n.badge ? <span className="desktop-nav-badge">{n.badge}</span> : null}
            </button>
          ))}
        </nav>
        <div style={{ display: 'flex', gap: 8, marginLeft: 'auto' }}>
          <div className="lang-switch" role="group" aria-label="Language">
            <button className={lang === 'ja' ? 'is-on' : ''} onClick={() => onLang('ja')}>JA</button>
            <button className={lang === 'en' ? 'is-on' : ''} onClick={() => onLang('en')}>EN</button>
          </div>
          <button
            className="icon-btn"
            onClick={() => onImport && onImport()}
            aria-label="Import data"
            title={lang === 'ja' ? 'データをインポート' : 'Import data'}
          >
            ＋
          </button>
          <button
            className="icon-btn"
            onClick={() => onTheme(theme === 'dark' ? 'light' : 'dark')}
            aria-label="Toggle theme"
            title="Toggle theme"
          >
            {theme === 'dark' ? '☾' : '☀'}
          </button>
          <a
            className="icon-btn"
            href="Admin.html"
            target="_blank"
            rel="noopener"
            aria-label="Admin"
            title={lang === 'ja' ? '管理画面' : 'Admin'}
            style={{ textDecoration: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}
          >
            ⚙
          </a>
        </div>
      </div>
      <div className="search-bar">
        <span className="search-icon">⌕</span>
        <input
          value={query}
          onChange={(e) => onQuery(e.target.value)}
          placeholder={I18N.search_ph[lang]}
        />
        {query && (
          <button
            onClick={() => onQuery('')}
            style={{ background: 'none', border: 0, color: 'var(--fg-low)', cursor: 'pointer', fontSize: 16 }}
            aria-label="Clear"
          >×</button>
        )}
      </div>
    </div>
  );
}

// ── Hero ────────────────────────────────────────
function Hero({ lang, onCTA, I18N, stats }) {
  const [now, setNow] = useState(() => new Date());
  useEffect(() => {
    const t = setInterval(() => setNow(new Date()), 30_000);
    return () => clearInterval(t);
  }, []);
  const time = now.toLocaleTimeString('ja-JP', { hour: '2-digit', minute: '2-digit', hour12: false });
  const tokyoLine = lang === 'ja'
    ? `TOKYO · GOTANDA · LIVE ${time}`
    : `TOKYO · GOTANDA · LIVE ${time}`;
  const titleParts = I18N.hero_title[lang].split('\n');
  return (
    <section className="hero">
      <div className="hero-bg"></div>
      <div className="hero-cityskyline"></div>
      <div className="hero-inner">
        <div className="clock-chip">
          <span className="clock-pulse"></span>
          {tokyoLine}
        </div>
        <div className="hero-eyebrow">{I18N.tagline[lang]}</div>
        <h1>
          {titleParts[0]}
          {titleParts[1] && <><br /><em>{titleParts[1]}</em></>}
        </h1>
        <p>{I18N.hero_sub[lang]}</p>
        <div>
          <button className="hero-cta" onClick={() => onCTA('spots')}>
            {lang === 'ja' ? 'スポットを見る' : 'Browse spots'} →
          </button>
          <button className="hero-cta-ghost" onClick={() => onCTA('map')}>
            {lang === 'ja' ? 'マップ' : 'Map'}
          </button>
        </div>
        <div className="stats">
          {stats.map((s, i) => (
            <div className="stat" key={i}>
              <div className="stat-v">
                {s.value}
                <small>{s.sub[lang]}</small>
              </div>
              <div className="stat-l">{s.label[lang]}</div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

// ── Tonight strip ────────────────────────────────────────
function TonightStrip({ lang, spots, onOpen }) {
  const now = new Date();
  const h = now.getHours();
  let msg, preferCat, count;
  if (h < 6)       { msg = { ja: '深夜でも開いている店', en: 'Open late tonight' };          preferCat = 'ramen';   count = 3; }
  else if (h < 11) { msg = { ja: '朝活カフェ',           en: 'Morning cafes' };               preferCat = 'cafe';    count = 3; }
  else if (h < 17) { msg = { ja: '昼下がりの寄り道',     en: 'Afternoon detours' };           preferCat = 'sight';   count = 3; }
  else if (h < 22) { msg = { ja: '夜の本命',             en: 'Tonight\u2019s picks' };        preferCat = 'izakaya'; count = 3; }
  else             { msg = { ja: '〆ラーメン候補',       en: 'Late-night ramen' };            preferCat = 'ramen';   count = 3; }

  // Pick target spots: prefer current category, top up with top-rated others.
  const targets = React.useMemo(() => {
    if (!spots || !spots.length) return [];
    const sorted = [...spots].sort((a, b) => (b.rating || 0) - (a.rating || 0));
    const inCat = sorted.filter((s) => s.cat === preferCat);
    const others = sorted.filter((s) => s.cat !== preferCat);
    return [...inCat, ...others].slice(0, count);
  }, [spots, preferCat, count]);

  const time = now.toLocaleTimeString(lang === 'ja' ? 'ja-JP' : 'en-US', { hour: '2-digit', minute: '2-digit', hour12: false });

  return (
    <Reveal className="tonight">
      <div className="tonight-l">
        <div className="tonight-head">
          <div>
            <div className="tonight-eyebrow">{lang === 'ja' ? '今夜の五反田' : 'Tonight in Gotanda'}</div>
            <div className="tonight-msg">{msg[lang]}</div>
          </div>
          <div className="tonight-time">{time}</div>
        </div>
        {targets.length > 0 && (
          <div className="tonight-list">
            {targets.map((s) => (
              <button
                key={s.id}
                className="tonight-pick"
                onClick={(e) => { e.stopPropagation(); onOpen && onOpen(s); }}
              >
                <span className="tonight-pick-arrow">→</span>
                <span className="tonight-pick-name">{s.name[lang]}</span>
                <span className="tonight-pick-meta">★ {s.rating} · {s.price}</span>
              </button>
            ))}
          </div>
        )}
      </div>
    </Reveal>
  );
}

// ── Category chips ────────────────────────────────────────
function CategoryChips({ active, onChange, lang, CATEGORIES, counts }) {
  return (
    <div className="chips" role="tablist">
      {CATEGORIES.map((c) => (
        <button
          key={c.id}
          role="tab"
          aria-selected={active === c.id}
          className={`chip ${active === c.id ? 'is-on' : ''}`}
          onClick={() => onChange(c.id)}
        >
          <span className="chip-icon">{c.icon}</span>
          <span>{c.label[lang]}</span>
          <span className="chip-count">{counts[c.id] ?? 0}</span>
        </button>
      ))}
    </div>
  );
}

// ── Spot card ────────────────────────────────────────
function SpotCard({ spot, lang, onOpen, onFav, isFav, style, image, reorderable, onMove, isFirst, isLast }) {
  const cardClass = style === 'magazine' ? 'spot-card mag' : style === 'grid' ? 'spot-card grid-c' : 'spot-card';
  return (
    <article
      className={cardClass}
      onClick={() => onOpen(spot)}
      role="button"
      tabIndex={0}
      onKeyDown={(e) => { if (e.key === 'Enter') onOpen(spot); }}
    >
      <div className="spot-thumb">
        <Thumb idx={spot.img} image={image || spot.image} />
        <button
          className={`spot-fav ${isFav ? 'is-on' : ''}`}
          onClick={(e) => { e.stopPropagation(); onFav(spot.id); }}
          aria-label="Save"
        >
          {isFav ? '♥' : '♡'}
        </button>
        {reorderable && (
          <div className="spot-reorder" onClick={(e) => e.stopPropagation()}>
            <button
              className="spot-reorder-btn"
              onClick={(e) => { e.stopPropagation(); onMove(spot.id, -1); }}
              disabled={isFirst}
              title={lang === 'ja' ? '上へ' : 'Up'}
            >↑</button>
            <button
              className="spot-reorder-btn"
              onClick={(e) => { e.stopPropagation(); onMove(spot.id, 1); }}
              disabled={isLast}
              title={lang === 'ja' ? '下へ' : 'Down'}
            >↓</button>
          </div>
        )}
      </div>
      <div className="spot-body">
        <div>
          <div className="spot-tag">{spot.tag[lang]}</div>
          <div className="spot-name">{spot.name[lang]}</div>
        </div>
        <div className="spot-meta">
          <b>★ {spot.rating}</b>
          <span className="spot-meta-dot"></span>
          <span>{spot.price}</span>
          <span className="spot-meta-dot"></span>
          <span>{spot.walk}{lang === 'ja' ? '分' : 'm'}</span>
        </div>
      </div>
    </article>
  );
}

// ── Spot list ────────────────────────────────────────
function SpotList({ spots, lang, onOpen, favs, onFav, cardStyle, I18N, images, reorderable, onMove, prompt }) {
  if (prompt) {
    return (
      <div className="empty">
        <div className="empty-icon">◎</div>
        <div>{lang === 'ja' ? 'カテゴリを選択してください' : 'Select a category to begin'}</div>
      </div>
    );
  }
  if (!spots.length) {
    return (
      <div className="empty">
        <div className="empty-icon">⌕</div>
        <div>{I18N.no_results[lang]}</div>
      </div>
    );
  }
  if (cardStyle === 'grid') {
    return (
      <div className="spot-grid">
        {spots.map((s, i) => (
          <SpotCard
            key={s.id} spot={s} lang={lang} onOpen={onOpen}
            onFav={onFav} isFav={favs.has(s.id)} style="grid"
            image={images?.[s.id]}
            reorderable={reorderable} onMove={onMove}
            isFirst={i === 0} isLast={i === spots.length - 1}
          />
        ))}
      </div>
    );
  }
  return (
    <div className="spot-list">
      {spots.map((s, i) => (
        <SpotCard
          key={s.id} spot={s} lang={lang} onOpen={onOpen}
          onFav={onFav} isFav={favs.has(s.id)}
          style={cardStyle === 'magazine' ? 'magazine' : 'row'}
          image={images?.[s.id]}
          reorderable={reorderable} onMove={onMove}
          isFirst={i === 0} isLast={i === spots.length - 1}
        />
      ))}
    </div>
  );
}

// ── Map section ────────────────────────────────────────
// Gotanda station: 35.6262, 139.7232
const GG_MAP_CENTER = { lat: 35.6262, lng: 139.7232 };
const GG_MAP_KEY_LS = 'gg_google_key';

const GG_DARK_MAP_STYLE = [
  { elementType: 'geometry', stylers: [{ color: '#16161c' }] },
  { elementType: 'labels.text.fill', stylers: [{ color: '#8a8884' }] },
  { elementType: 'labels.text.stroke', stylers: [{ color: '#0a0a0c' }] },
  { featureType: 'administrative', elementType: 'geometry', stylers: [{ color: '#2a2a32' }] },
  { featureType: 'poi', stylers: [{ visibility: 'off' }] },
  { featureType: 'poi.park', elementType: 'geometry', stylers: [{ color: '#1a221a' }] },
  { featureType: 'road', elementType: 'geometry', stylers: [{ color: '#2a2a32' }] },
  { featureType: 'road.arterial', elementType: 'geometry', stylers: [{ color: '#34343c' }] },
  { featureType: 'road.highway', elementType: 'geometry', stylers: [{ color: '#3a3540' }] },
  { featureType: 'road', elementType: 'labels.text.fill', stylers: [{ color: '#666' }] },
  { featureType: 'transit', stylers: [{ visibility: 'off' }] },
  { featureType: 'water', elementType: 'geometry', stylers: [{ color: '#0a1418' }] },
];

function ggBuildPinIcon(google, { fill, ring, label, active }) {
  const w = active ? 38 : 32;
  const h = active ? 48 : 40;
  const svg =
    '<svg xmlns="http://www.w3.org/2000/svg" width="' + w + '" height="' + h + '" viewBox="0 0 32 40">' +
      '<path d="M16 0C7.16 0 0 7.16 0 16c0 12 16 24 16 24s16-12 16-24C32 7.16 24.84 0 16 0z" fill="' + fill + '" stroke="' + ring + '" stroke-width="2"/>' +
      '<text x="16" y="21" text-anchor="middle" font-family="-apple-system,BlinkMacSystemFont,sans-serif" font-size="13" font-weight="700" fill="' + ring + '">' + label + '</text>' +
    '</svg>';
  return {
    url: 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(svg),
    anchor: new google.maps.Point(w / 2, h),
    scaledSize: new google.maps.Size(w, h),
  };
}

function MapSection({ spots, lang, onOpen, I18N }) {
  const [active, setActive] = useState(null);
  const [apiKey, setApiKey] = useState(() => {
    try { return localStorage.getItem(GG_MAP_KEY_LS) || ''; } catch { return ''; }
  });
  const [keyDraft, setKeyDraft] = useState('');
  const [loadErr, setLoadErr] = useState(null);
  const [mapReady, setMapReady] = useState(false);
  const mapElRef = useRef(null);
  const mapRef = useRef(null);
  const markersRef = useRef({});
  const onOpenRef = useRef(onOpen);
  const setActiveRef = useRef(setActive);
  useEffect(() => { onOpenRef.current = onOpen; }, [onOpen]);
  useEffect(() => { setActiveRef.current = setActive; }, [setActive]);

  // Init Google Map once we have a key and the container is mounted
  useEffect(() => {
    if (!apiKey || !mapElRef.current || mapRef.current) return;
    if (typeof loadGoogleMaps !== 'function') {
      setLoadErr('Google Maps loader not available');
      return;
    }
    let cancelled = false;
    setLoadErr(null);
    loadGoogleMaps(apiKey).then((google) => {
      if (cancelled || !mapElRef.current) return;
      const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
      const map = new google.maps.Map(mapElRef.current, {
        center: GG_MAP_CENTER,
        zoom: 15,
        mapTypeControl: false,
        streetViewControl: false,
        fullscreenControl: false,
        zoomControl: true,
        clickableIcons: false,
        gestureHandling: 'greedy',
        styles: isDark ? GG_DARK_MAP_STYLE : [],
      });
      const themeObs = new MutationObserver(() => {
        const dark = document.documentElement.getAttribute('data-theme') === 'dark';
        map.setOptions({ styles: dark ? GG_DARK_MAP_STYLE : [] });
      });
      themeObs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
      mapRef.current = { google, map, themeObs };
      setMapReady(true);
    }).catch((e) => {
      setLoadErr(e?.message || String(e));
    });
    return () => { cancelled = true; };
  }, [apiKey]);

  useEffect(() => () => {
    const ctx = mapRef.current;
    if (ctx) {
      ctx.themeObs?.disconnect();
      Object.values(markersRef.current).forEach((m) => m.setMap(null));
      markersRef.current = {};
      mapRef.current = null;
    }
  }, []);

  // Sync markers with spots
  useEffect(() => {
    const ctx = mapRef.current;
    if (!ctx) return;
    const { google, map } = ctx;
    const cs = getComputedStyle(document.documentElement);
    const accent = (cs.getPropertyValue('--accent') || '').trim() || '#f5b25b';
    const accent2 = (cs.getPropertyValue('--accent-2') || '').trim() || '#ff5b8a';
    const fg = (cs.getPropertyValue('--accent-text') || '').trim() || '#1a1006';
    const existing = markersRef.current;
    const nextIds = new Set(spots.map((s) => s.id));

    Object.keys(existing).forEach((id) => {
      if (!nextIds.has(id)) {
        existing[id].setMap(null);
        delete existing[id];
      }
    });

    spots.forEach((s, i) => {
      if (!s.coords || s.coords.length !== 2) return;
      const isActive = active === s.id;
      const pos = { lat: s.coords[0], lng: s.coords[1] };
      const icon = ggBuildPinIcon(google, {
        fill: isActive ? accent2 : accent,
        ring: fg,
        label: String(i + 1),
        active: isActive,
      });
      const title = s.name[lang] || s.name.en || s.id;
      if (existing[s.id]) {
        existing[s.id].setPosition(pos);
        existing[s.id].setIcon(icon);
        existing[s.id].setTitle(title);
        existing[s.id].setZIndex(isActive ? 999 : i);
      } else {
        const m = new google.maps.Marker({ position: pos, map, icon, title, zIndex: isActive ? 999 : i });
        m.addListener('click', () => {
          setActiveRef.current(s.id);
          onOpenRef.current(s);
        });
        existing[s.id] = m;
      }
    });
  }, [spots, lang, active, mapReady]);

  const saveKey = () => {
    const k = keyDraft.trim();
    if (!k) return;
    try { localStorage.setItem(GG_MAP_KEY_LS, k); } catch {}
    setApiKey(k);
    setKeyDraft('');
  };

  return (
    <section>
      <div className="sec-hd">
        <div>
          <span className="sec-hd-sub">SECTION 04 / MAP</span>
          <h2>{I18N.sec_map[lang]}</h2>
        </div>
      </div>
      <div className="map-wrap">
        {apiKey ? (
          <div ref={mapElRef} className="map-iframe" role="img" aria-label="Gotanda Map"></div>
        ) : (
          <div className="map-iframe" style={{
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            flexDirection: 'column', gap: 10, padding: 20,
          }}>
            <div style={{ fontSize: 12, color: 'var(--fg-mid)', textAlign: 'center', lineHeight: 1.5, maxWidth: 320 }}>
              {lang === 'ja'
                ? 'Google Maps の API キーを入力してください。'
                : 'Enter a Google Maps API key to load the map.'}
            </div>
            <div style={{ display: 'flex', gap: 6, width: '100%', maxWidth: 360 }}>
              <input
                type="password"
                value={keyDraft}
                onChange={(e) => setKeyDraft(e.target.value)}
                placeholder={lang === 'ja' ? 'API キーを貼り付け' : 'Paste API key'}
                style={{
                  flex: 1, padding: '8px 10px', borderRadius: 8, border: '1px solid var(--line)',
                  background: 'var(--bg-elev-2)', color: 'var(--fg)',
                  fontFamily: 'var(--font-mono)', fontSize: 12,
                }}
              />
              <button
                onClick={saveKey}
                style={{
                  padding: '8px 14px', borderRadius: 8, border: 0,
                  background: 'var(--accent)', color: 'var(--accent-text)',
                  fontWeight: 600, cursor: 'pointer',
                }}
              >
                {lang === 'ja' ? '保存' : 'Save'}
              </button>
            </div>
          </div>
        )}
        {loadErr && (
          <div style={{ padding: '8px 14px', fontSize: 11, color: '#ff8a80', background: 'rgba(50,15,15,0.7)' }}>
            {loadErr}
          </div>
        )}
        <div className="map-legend">
          <span>JR · 都営浅草線 · 東急池上線</span>
          <span>{spots.length} {lang === 'ja' ? 'スポット' : 'spots'}</span>
        </div>
      </div>
      <div className="access-grid">
        <div className="access-card">
          <div className="access-card-l">{lang === 'ja' ? '東京駅から' : 'From Tokyo St.'}</div>
          <div className="access-card-v">17 <span style={{ fontSize: 13, color: 'var(--fg-low)' }}>min</span></div>
          <div className="access-card-s">{lang === 'ja' ? '山手線・直通' : 'Yamanote · direct'}</div>
        </div>
        <div className="access-card">
          <div className="access-card-l">{lang === 'ja' ? '羽田空港から' : 'From Haneda'}</div>
          <div className="access-card-v">28 <span style={{ fontSize: 13, color: 'var(--fg-low)' }}>min</span></div>
          <div className="access-card-s">{lang === 'ja' ? '京急・乗換1回' : 'Keikyu · 1 transfer'}</div>
        </div>
        <div className="access-card">
          <div className="access-card-l">{lang === 'ja' ? '新幹線品川駅' : 'Shinkansen Shinagawa'}</div>
          <div className="access-card-v">5 <span style={{ fontSize: 13, color: 'var(--fg-low)' }}>min</span></div>
          <div className="access-card-s">{lang === 'ja' ? '山手線・1駅' : 'Yamanote · 1 stop'}</div>
        </div>
        <div className="access-card">
          <div className="access-card-l">{lang === 'ja' ? '乗入路線' : 'Lines'}</div>
          <div className="access-card-v">3</div>
          <div className="access-card-s">JR · Asakusa · Ikegami</div>
        </div>
      </div>
    </section>
  );
}

// ── Events ────────────────────────────────────────
function Events({ lang, EVENTS, I18N }) {
  return (
    <section>
      <div className="sec-hd">
        <div>
          <span className="sec-hd-sub">SECTION 05 / EVENTS</span>
          <h2>{I18N.sec_events[lang]}</h2>
        </div>
      </div>
      <div className="events">
        {EVENTS.map((e, i) => (
          <div className="event-card" key={i}>
            <div className="event-tag">{e.tag[lang]}</div>
            <div className="event-date">
              <b>{e.date}</b>
              <small>{e.day[lang].toUpperCase()}</small>
            </div>
            <div className="event-name">{e.name[lang]}</div>
            <div className="event-where">⌖ {e.where[lang]}</div>
          </div>
        ))}
      </div>
    </section>
  );
}

// ── News ────────────────────────────────────────
function News({ lang, NEWS, I18N }) {
  return (
    <section>
      <div className="sec-hd">
        <div>
          <span className="sec-hd-sub">SECTION 06 / NEWS</span>
          <h2>{I18N.sec_news[lang]}</h2>
        </div>
      </div>
      <div className="news-list">
        {NEWS.map((n, i) => (
          <article className="news-item" key={i}>
            <div className="news-cat">{n.cat[lang]}</div>
            <div>
              <div className="news-title">{n.title[lang]}</div>
              <div className="news-excerpt">{n.excerpt[lang]}</div>
              <div className="news-when">— {n.when[lang]}</div>
            </div>
          </article>
        ))}
      </div>
    </section>
  );
}

// ── Area blurb ────────────────────────────────────────
function AreaBlurb({ lang, I18N }) {
  return (
    <section>
      <div className="sec-hd">
        <div>
          <span className="sec-hd-sub">SECTION 07 / AREA</span>
          <h2>{I18N.sec_area[lang]}</h2>
        </div>
      </div>
      <Reveal className="area-card">
        <div className="area-eyebrow">GOTANDA · 五反田</div>
        <p>{I18N.area_blurb[lang]}</p>
      </Reveal>
    </section>
  );
}

// ── Modal ────────────────────────────────────────
function SpotModal({ spot, lang, onClose, isFav, onFav, I18N, image, onImage, onEditField, onRefreshReviews }) {
  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    document.body.style.overflow = 'hidden';
    return () => {
      window.removeEventListener('keydown', onKey);
      document.body.style.overflow = '';
    };
  }, [onClose]);
  const [refreshing, setRefreshing] = useState(false);
  const [showAll, setShowAll] = useState(false);
  if (!spot) return null;
  const mapsUrl = `https://www.google.com/maps/search/?api=1&query=${spot.coords[0]},${spot.coords[1]}`;
  const reviews = spot.reviews || [];
  const isGoogleSpot = !!spot._googleAttrib || /^ChIJ/.test(spot.id || '');
  const handleRefresh = async () => {
    if (!onRefreshReviews) return;
    setRefreshing(true);
    try { await onRefreshReviews(spot); }
    finally { setRefreshing(false); }
  };
  const fmtAge = (ts) => {
    if (!ts) return '';
    const d = new Date(ts);
    return d.toLocaleDateString(lang === 'ja' ? 'ja-JP' : 'en-US');
  };
  return (
    <div className="modal-bd" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <div className="modal-grip"></div>
        <div className="modal-cover">
          <Thumb idx={spot.img} image={image || spot.image} />
          <button className="modal-x" onClick={onClose} aria-label="Close">×</button>
          {onImage && (
            <div style={{ position: 'absolute', left: 12, bottom: 12, zIndex: 3 }}>
              <ImagePicker lang={lang} value={image || spot.image} onChange={(d) => onImage(spot.id, d)} />
            </div>
          )}
        </div>
        <div className="modal-content">
          <div className="modal-tag">{spot.tag[lang]}</div>
          <div className="modal-title">{spot.name[lang]}</div>
          <div className="modal-meta">
            <span className="modal-pill">★ {spot.rating}{spot.userRatingsTotal ? ` (${spot.userRatingsTotal})` : ''}</span>
            <span
              className="modal-pill modal-pill-edit"
              onClick={(e) => {
                if (!onEditField) return;
                e.stopPropagation();
                const v = window.prompt(
                  lang === 'ja' ? '予算を入力 (例: ¥, ¥¥, ¥¥¥, ¥¥¥¥, Free)' : 'Enter price (e.g. ¥, ¥¥, ¥¥¥, ¥¥¥¥, Free)',
                  spot.price || ''
                );
                if (v !== null) onEditField(spot.id, { price: v.trim() });
              }}
              title={onEditField ? (lang === 'ja' ? 'クリックで編集' : 'Click to edit') : undefined}
              style={onEditField ? { cursor: 'pointer' } : undefined}
            >
              {spot.price || (lang === 'ja' ? '予算 —' : 'Price —')}
            </span>
            <span className="modal-pill">{lang === 'ja' ? `駅から${spot.walk}分` : `${spot.walk} min walk`}</span>
            {spot.tags.map((t) => <span key={t} className="modal-pill">{t}</span>)}
          </div>
          <div className="modal-desc">{spot.desc[lang]}</div>
          <div className="modal-info">
            <div className="info-cell">
              <div className="info-cell-l">{lang === 'ja' ? '営業時間' : 'Hours'}</div>
              <div className="info-cell-v">{spot.hours[lang]}</div>
            </div>
            <div className="info-cell">
              <div className="info-cell-l">{lang === 'ja' ? '住所' : 'Address'}</div>
              <div className="info-cell-v">{spot.address[lang]}</div>
            </div>
          </div>

          {isGoogleSpot && (
            <div className="reviews">
              <div className="reviews-hd">
                <div className="reviews-hd-l">
                  <div className="reviews-title">
                    {lang === 'ja' ? '口コミ' : 'Reviews'}
                    <span className="reviews-count">{reviews.length}</span>
                  </div>
                  <div className="reviews-attrib">
                    {spot.reviewsFetchedAt
                      ? (lang === 'ja' ? `${fmtAge(spot.reviewsFetchedAt)} 取得` : `Fetched ${fmtAge(spot.reviewsFetchedAt)}`)
                      : (lang === 'ja' ? '未取得' : 'Not fetched')}
                    <span className="reviews-powered">Powered by Google</span>
                  </div>
                </div>
                {onRefreshReviews && (
                  <button
                    className="reviews-refresh"
                    onClick={handleRefresh}
                    disabled={refreshing}
                  >
                    {refreshing
                      ? (lang === 'ja' ? '取得中…' : 'Loading…')
                      : (lang === 'ja' ? 'Googleから更新' : 'Refresh from Google')}
                  </button>
                )}
              </div>
              {reviews.length === 0 ? (
                <div className="reviews-empty">
                  {lang === 'ja' ? '口コミがありません。「Googleから更新」で取得できます。' : 'No reviews yet. Click refresh to fetch from Google.'}
                </div>
              ) : (
                <div className="reviews-list">
                  {(showAll ? reviews : reviews.slice(0, 2)).map((r, i) => (
                    <div key={i} className="rv">
                      <div className="rv-hd">
                        {r.avatar ? (
                          <img src={r.avatar} alt="" className="rv-avatar" referrerPolicy="no-referrer" />
                        ) : (
                          <div className="rv-avatar rv-avatar-fb">{(r.author || '?').slice(0, 1)}</div>
                        )}
                        <div className="rv-meta">
                          {r.authorUrl ? (
                            <a href={r.authorUrl} target="_blank" rel="noreferrer" className="rv-author">{r.author}</a>
                          ) : (
                            <div className="rv-author">{r.author}</div>
                          )}
                          <div className="rv-sub">
                            <span className="rv-stars">{'★'.repeat(Math.round(r.rating))}<span className="rv-stars-dim">{'★'.repeat(5 - Math.round(r.rating))}</span></span>
                            <span className="rv-time">{r.relative}</span>
                          </div>
                        </div>
                      </div>
                      <div className="rv-text">{r.text}</div>
                    </div>
                  ))}
                  {reviews.length > 2 && (
                    <button className="reviews-more" onClick={() => setShowAll((v) => !v)}>
                      {showAll
                        ? (lang === 'ja' ? '閉じる' : 'Show less')
                        : (lang === 'ja' ? `すべて表示（${reviews.length}件）` : `Show all (${reviews.length})`)}
                    </button>
                  )}
                </div>
              )}
            </div>
          )}
        </div>
        <div className="modal-actions">
          <a className="btn-primary" href={mapsUrl} target="_blank" rel="noreferrer">
            ⌖ {I18N.reserve[lang]}
          </a>
          <button className={`btn-secondary ${isFav ? 'is-on' : ''}`} onClick={() => onFav(spot.id)}>
            {isFav ? '♥' : '♡'} {isFav ? I18N.saved_lbl[lang] : I18N.save[lang]}
          </button>
        </div>
      </div>
    </div>
  );
}

// ── Back-to-top button ────────────────────────────────
function BackToTop({ lang }) {
  const [show, setShow] = React.useState(false);
  React.useEffect(() => {
    const onScroll = () => setShow(window.scrollY > 600);
    onScroll();
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  const goTop = () => window.scrollTo({ top: 0, behavior: 'smooth' });
  return (
    <button
      className={`to-top ${show ? 'is-on' : ''}`}
      onClick={goTop}
      aria-label={lang === 'ja' ? 'トップへ戻る' : 'Back to top'}
      title={lang === 'ja' ? 'トップへ戻る' : 'Back to top'}
    >
      <span className="to-top-arrow">↑</span>
      <span className="to-top-lbl">TOP</span>
    </button>
  );
}

// ── Bottom nav ────────────────────────────────────────
function BottomNav({ tab, onTab, lang, I18N, savedCount }) {
  const items = [
    { id: 'home', icon: '⌂', label: I18N.nav.home[lang] },
    { id: 'spots', icon: '◳', label: I18N.nav.spots[lang] },
    { id: 'map', icon: '⌖', label: I18N.nav.map[lang] },
    { id: 'saved', icon: '♥', label: I18N.nav.saved[lang], badge: savedCount },
  ];
  return (
    <nav className="bnav">
      {items.map((it) => (
        <button
          key={it.id}
          className={`bnav-btn ${tab === it.id ? 'is-on' : ''}`}
          onClick={() => onTab(it.id)}
        >
          <span className="bnav-icon" style={{ position: 'relative' }}>
            {it.icon}
            {it.badge ? (
              <span style={{
                position: 'absolute', top: -4, right: -10,
                background: 'var(--accent-2)', color: 'white',
                fontSize: 9, fontWeight: 700, fontFamily: 'var(--font-sans)',
                padding: '1px 5px', borderRadius: 99, lineHeight: 1.4,
              }}>{it.badge}</span>
            ) : null}
          </span>
          <span>{it.label}</span>
        </button>
      ))}
    </nav>
  );
}

Object.assign(window, {
  TopBar, Hero, TonightStrip, CategoryChips, SpotList, SpotCard,
  MapSection, Events, News, AreaBlurb, SpotModal, BottomNav, Reveal, Thumb,
  BackToTop,
});
