// itrymo v3 — Custom search analytics variant
// v2 + a "What are you looking for?" free-text input above the tag inputs.
// Logs every settled query to localStorage so itrymo can see what users want.

// How many cards to reveal before the "Load more" button.
const PAGE_WEB = 18;
const PAGE_MOBILE = 10;

// ── Ward filter section ──────────────────────────────────────
// Ordered by tourist / visitor relevance — most place-dense wards first.
const WARD_ORDER = [
  'Shibuya','Shinjuku','Toshima / Ikebukuro','Taito / Asakusa',
  'Minato','Chuo / Ginza','Chiyoda / Marunouchi',
  'Meguro','Setagaya','Sumida / Ryogoku',
  'Koto / Toyosu','Shinagawa','Ota','Edogawa',
  'Kita','Arakawa','Adachi','Katsushika','Nerima','Nakano','Itabashi','Suginami','Bunkyo',
];

function WardSection({ wards, setWards, places, bucket }) {
  const wardCounts = React.useMemo(() => {
    const counts = new Map();
    const bucketPlaces = bucket === 'all' ? places : places.filter(p => p.bucket === bucket);
    bucketPlaces.forEach(p => {
      const areaLower = (p.area || '').toLowerCase();
      WARD_ORDER.forEach(w => {
        if ((WARD_NEIGHBORHOODS_LOWER[w] || new Set()).has(areaLower))
          counts.set(w, (counts.get(w) || 0) + 1);
      });
    });
    return counts;
  }, [places, bucket]);

  const activeWards = WARD_ORDER.filter(w => wardCounts.has(w));
  if (!activeWards.length) return null;

  const toggle = (w) => setWards(cur => cur.includes(w) ? cur.filter(x => x !== w) : [...cur, w]);

  // Reuse the same TagInput UI — feed ward names as tags, wardCounts as tagCounts Map
  return (
    <TagInput
      groupLabel="Neighborhood · Ward"
      tags={activeWards}
      selected={wards}
      onToggle={toggle}
      tagCounts={wardCounts}
      placeholder="e.g. Shibuya, Shinjuku, Minato…"
    />
  );
}

// ── Load more button ─────────────────────────────────────────
function LoadMore({ shown, total, onMore, surface }) {
  if (shown >= total) return null;
  const remaining = total - shown;
  const step = surface === 'mobile' ? PAGE_MOBILE : PAGE_WEB;
  return (
    <div style={{
      display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8,
      padding: surface === 'mobile' ? '16px 0 4px' : '28px 0 4px',
    }}>
      <button onClick={onMore} style={{
        all: 'unset', cursor: 'pointer',
        height: 44, padding: '0 22px', borderRadius: 12,
        background: '#fff', border: `1px solid ${T.selBg}`, color: T.ink,
        fontSize: 13, fontWeight: 600,
        display: 'inline-flex', alignItems: 'center', gap: 8,
      }}>
        Load {Math.min(step, remaining)} more
        <Icon name="chev" size={11} color={T.muted} />
      </button>
      <div style={{ fontSize: 11, color: T.muted }}>
        Showing {shown} of {total}
      </div>
    </div>
  );
}

// ── Slim app footer ──────────────────────────────────────────
function SiteFooter({ surface, city = 'Tokyo', onOpenPage }) {
  const links = [['About', 'about'], ['Help', 'help'], ['Privacy', 'privacy'], ['Terms', 'terms']];
  if (surface === 'mobile') {
    return (
      <div style={{
        padding: '18px 20px 26px', textAlign: 'center',
        borderTop: `1px solid ${T.border}`, marginTop: 8,
      }}>
        <div style={{ fontSize: 12, fontWeight: 700, letterSpacing: -0.3 }}>
          itrymo<span style={{ color: T.accent }}>.</span>
        </div>
        <div style={{ fontSize: 11, color: T.muted, marginTop: 4 }}>
          Currently in {city} · more cities soon
        </div>
        <div style={{
          display: 'flex', flexWrap: 'wrap', justifyContent: 'center',
          gap: '4px 14px', marginTop: 10,
        }}>
          {links.map(([l, id]) => (
            <a key={l} href="#" onClick={(e) => { e.preventDefault(); onOpenPage && onOpenPage(id); }} style={{
              fontSize: 11, color: T.faintInk, textDecoration: 'none', fontWeight: 500,
            }}>{l}</a>
          ))}
        </div>
        <div style={{ fontSize: 10, color: T.muted, marginTop: 12 }}>
          © 2026 itrymo
        </div>
      </div>
    );
  }
  return (
    <footer style={{
      background: '#fff', borderTop: `1px solid ${T.border}`,
      padding: '22px 28px',
      display: 'flex', alignItems: 'center', gap: 20, flexWrap: 'wrap',
    }}>
      <div style={{ fontSize: 15, fontWeight: 700, letterSpacing: -0.4 }}>
        itrymo<span style={{ color: T.accent }}>.</span>
      </div>
      <span style={{ fontSize: 12, color: T.muted }}>
        Currently in {city} · more cities soon
      </span>
      <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 18 }}>
        {links.map(([l, id]) => (
          <a key={l} href="#" onClick={(e) => { e.preventDefault(); onOpenPage && onOpenPage(id); }} style={{
            fontSize: 12, color: T.faintInk, textDecoration: 'none', fontWeight: 500,
          }}>{l}</a>
        ))}
        <span style={{ fontSize: 12, color: T.muted }}>© 2026 itrymo</span>
      </div>
    </footer>
  );
}

function MobileAppV3({ city = 'Tokyo', dataState = 'ready', accounts = false, onRetry }) {
  const [bucket, setBucket] = React.useState('eat');
  const [tags, setTags] = React.useState([]);
  const [wards, setWards] = React.useState([]);
  const [searchQuery, setSearchQuery] = React.useState('');
  const [sort, setSort] = React.useState('recommended');
  const [sheetOpen, setSheetOpen] = React.useState(true);
  const [foodFilters, setFoodFilters] = React.useState(DEFAULT_FOOD_FILTERS);
  const [doFilters, setDoFilters] = React.useState(DEFAULT_DO_FILTERS);
  const [stayFilters, setStayFilters] = React.useState(DEFAULT_STAY_FILTERS);
  const [visible, setVisible] = React.useState(PAGE_MOBILE);
  const [selected, setSelected] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [favOpen, setFavOpen] = React.useState(false);
  const [menuOpen, setMenuOpen] = React.useState(false);
  const [pageOpen, setPageOpen] = React.useState(null);
  const [cityOpen, setCityOpen] = React.useState(false);
  const scrollRef = React.useRef(null);
  const [scrolled, setScrolled] = React.useState(false);
  const favIds = useFavorites();

  React.useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = 0; setScrolled(false); }, [bucket]);

  React.useEffect(() => { setVisible(PAGE_MOBILE); }, [bucket, tags, wards, searchQuery, sort, foodFilters, doFilters, stayFilters]);
  React.useEffect(() => {
    if (dataState !== 'ready') return;
    setLoading(true);
    const t = setTimeout(() => setLoading(false), 420);
    return () => clearTimeout(t);
  }, [bucket, dataState]);
  const viewState = dataState === 'error' ? 'error' : (dataState === 'loading' || loading) ? 'loading' : 'ready';
  const hasFilters = tags.length > 0 || wards.length > 0 || searchQuery.length > 0;
  const clearAll = () => { setTags([]); setWards([]); setSearchQuery(''); setFoodFilters(DEFAULT_FOOD_FILTERS); setDoFilters(DEFAULT_DO_FILTERS); setStayFilters(DEFAULT_STAY_FILTERS); };

  const switchBucket = (id) => { setBucket(id); setTags([]); setWards([]); setSearchQuery(''); };
  const tagCounts = React.useMemo(() => getTagCounts(PLACES.filter(p => p.bucket === bucket)), [bucket]);

  const toggleTag = (tg) => setTags((cur) => cur.includes(tg) ? cur.filter((t) => t !== tg) : [...cur, tg]);
  const addTag = (tg) => setTags((cur) => cur.includes(tg) ? cur : [...cur, tg]);

  const groups = getGroupsFor(bucket);
  const filtered = sortPlaces(applyFilters(PLACES, { bucket, tags, wards, searchQuery, foodFilters, doFilters, stayFilters }), sort);
  const shown = filtered.slice(0, visible);
  const sortLabel = (SORTS.find((s) => s.id === sort) || {}).label || 'Must try';

  const summary = tags.length === 0
    ? 'All tags'
    : tags.slice(0, 2).join(', ') + (tags.length > 2 ? ` +${tags.length - 2}` : '');

  return (
    <div style={{
      width: '100%', height: '100%', background: T.bg,
      fontFamily: T.fontUI, color: T.ink, position: 'relative',
      display: 'flex', flexDirection: 'column', overflow: 'hidden',
    }}>
      <h1 className="sr-only">itrymo — discover where to eat, what to do, and where to stay in {city}</h1>
      <div style={{ height: 14 }} />
      <div style={{ padding: '0 20px', display: 'flex', alignItems: 'center', gap: 10 }}>
        <div style={{ fontSize: 22, fontWeight: 700, letterSpacing: -0.6, display: 'flex', alignItems: 'center', gap: 6 }}>
          itrymo<span style={{ color: T.accent }}>.</span>
          <span style={{ fontSize: 10, fontWeight: 600, color: '#B36A00', background: '#FFF3E0', border: '1px solid #F5C97A', borderRadius: 20, padding: '1px 7px', letterSpacing: 0.2 }}>Beta</span>
        </div>
        <div style={{ position: 'relative' }}>
          <button onClick={() => setCityOpen((o) => !o)} title="Change city" style={{
            all: 'unset', cursor: 'pointer',
            height: 30, padding: '0 10px', borderRadius: 999,
            background: '#fff', border: `1px solid ${T.border}`,
            display: 'inline-flex', alignItems: 'center', gap: 5,
            fontSize: 12, fontWeight: 600, color: T.faintInk,
          }}>
            <Icon name="pin" size={12} color={T.accent} />
            {city}
            <Icon name="chev" size={11} color={T.muted} style={{ transform: cityOpen ? 'rotate(180deg)' : 'none', transition: 'transform .15s' }} />
          </button>
          {cityOpen && (
            <div style={{
              position: 'absolute', top: 'calc(100% + 6px)', left: 0, zIndex: 30,
              width: 180, background: '#fff', borderRadius: 12,
              border: `1px solid ${T.border}`, boxShadow: '0 8px 24px rgba(0,0,0,0.12)', padding: 6,
            }}>
              <div style={{
                display: 'flex', alignItems: 'center', gap: 8, padding: '9px 10px', borderRadius: 8,
                background: T.selBg, color: T.selFg, fontSize: 13, fontWeight: 600,
              }}>
                <Icon name="pin" size={13} /> {city} <Icon name="check" size={13} style={{ marginLeft: 'auto' }} />
              </div>
              <div style={{ fontSize: 11.5, color: T.muted, padding: '8px 10px 4px', lineHeight: 1.45 }}>
                More cities are coming soon.
              </div>
            </div>
          )}
        </div>
        <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8 }}>
          <button title="Saved places" onClick={() => setFavOpen(true)} style={{
            all: 'unset', cursor: 'pointer', position: 'relative',
            width: 36, height: 36, borderRadius: 18, background: '#fff',
            border: `1px solid ${T.border}`,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}>
            <Icon name="heart" size={16} color={favIds.length ? '#E0533D' : T.faintInk} fill={favIds.length ? '#E0533D' : 'none'} />
            {favIds.length > 0 && (
              <span style={{
                position: 'absolute', top: -6, right: -6,
                minWidth: 16, height: 16, padding: '0 4px', borderRadius: 8,
                background: T.accent, color: '#fff', fontSize: 9, fontWeight: 700,
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                border: '2px solid #fff',
              }}>{favIds.length}</span>
            )}
          </button>
          <button title={accounts ? 'Profile' : 'Settings'} onClick={() => setMenuOpen(true)} style={{
            all: 'unset', cursor: 'pointer',
            width: 36, height: 36, borderRadius: 18,
            background: accounts ? T.accentBg : '#fff', color: accounts ? T.accent : T.faintInk,
            border: accounts ? 'none' : `1px solid ${T.border}`,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontSize: 13, fontWeight: 700,
          }}>{accounts ? 'JL' : <Icon name="gear" size={16} />}</button>
        </div>
      </div>
      <div style={{ height: 14 }} />

      <div style={{ padding: '0 20px', display: 'flex', gap: 6 }}>
        {BUCKETS.map((b) => {
          const on = b.id === bucket;
          return (
            <button key={b.id} onClick={() => switchBucket(b.id)} style={{
              all: 'unset', cursor: 'pointer',
              flex: 1, height: 46, borderRadius: 12,
              background: on ? T.selBg : '#fff',
              color: on ? T.selFg : T.faintInk,
              border: `1px solid ${on ? T.selBg : T.border}`,
              display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 3,
            }}>
              <Icon name={b.icon} size={14} />
              <div style={{ fontSize: 11, fontWeight: 600 }}>{b.label}</div>
            </button>
          );
        })}
      </div>

      <div style={{ padding: '10px 20px 0', display: 'flex', gap: 8, alignItems: 'center', position: 'relative', zIndex: 20 }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <CustomSearch
            bucket={bucket} groups={groups} tags={tags}
            onAddTag={addTag} places={PLACES} onPickPlace={setSelected}
            searchQuery={searchQuery} onSearchChange={setSearchQuery}
          />
        </div>
        <button onClick={() => setSheetOpen(true)} style={{
          all: 'unset', cursor: 'pointer', flexShrink: 0,
          height: 44, padding: '0 14px', borderRadius: 12, boxSizing: 'border-box',
          background: tags.length ? T.selBg : '#fff',
          color: tags.length ? T.selFg : T.faintInk,
          border: `1px solid ${tags.length ? T.selBg : T.border}`,
          display: 'inline-flex', alignItems: 'center', gap: 7,
          fontSize: 13, fontWeight: 600,
        }}>
          <Icon name="filter" size={14} />
          <span>Filter</span>
          {tags.length > 0 && (
            <span style={{
              minWidth: 18, height: 18, padding: '0 5px', borderRadius: 9,
              background: '#fff', color: T.selBg,
              fontSize: 11, fontWeight: 700,
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
            }}>{tags.length}</span>
          )}
        </button>
      </div>

      <div style={{ flex: 1, position: 'relative', marginTop: 12, overflow: 'hidden' }}>
        {/* Sticky results bar — slides in once you scroll into the list */}
        {bucket !== 'itinerary' && viewState === 'ready' && filtered.length > 0 && (
          <div style={{
            position: 'absolute', top: 0, left: 0, right: 0, zIndex: 8,
            display: 'flex', alignItems: 'center', gap: 10,
            padding: '10px 20px',
            background: 'rgba(250,247,242,0.94)', backdropFilter: 'blur(8px)',
            borderBottom: `1px solid ${T.borderSoft}`,
            transform: scrolled ? 'translateY(0)' : 'translateY(-100%)',
            opacity: scrolled ? 1 : 0,
            transition: 'transform .2s ease, opacity .2s ease',
            pointerEvents: scrolled ? 'auto' : 'none',
          }}>
            <span style={{ fontSize: 13, fontWeight: 700, color: T.ink }}>
              {filtered.length} {(BUCKETS.find((b) => b.id === bucket) || {}).label || 'places'}
            </span>
            <span style={{ fontSize: 12, color: T.muted, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>
              · {sortLabel}{tags.length ? ` · ${tags.length} ${tags.length === 1 ? 'tag' : 'tags'}` : ''}
            </span>
            <button onClick={() => setSheetOpen(true)} style={{
              all: 'unset', cursor: 'pointer', flexShrink: 0,
              height: 30, padding: '0 11px', borderRadius: 999,
              background: tags.length ? T.selBg : '#fff', color: tags.length ? T.selFg : T.faintInk,
              border: `1px solid ${tags.length ? T.selBg : T.border}`,
              display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 12, fontWeight: 600,
            }}><Icon name="filter" size={12} /> Filter</button>
            <button onClick={() => {
              const el = scrollRef.current; if (!el) return;
              const start = el.scrollTop, t0 = performance.now(), dur = 320;
              const step = (now) => {
                const k = Math.min(1, (now - t0) / dur);
                el.scrollTop = start * (1 - (1 - Math.pow(1 - k, 3)));
                if (k < 1) requestAnimationFrame(step);
              };
              requestAnimationFrame(step);
            }} title="Back to top" style={{
              all: 'unset', cursor: 'pointer', flexShrink: 0,
              width: 30, height: 30, borderRadius: 15, background: '#fff', border: `1px solid ${T.border}`,
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: T.faintInk,
            }}><Icon name="chev" size={12} style={{ transform: 'rotate(180deg)' }} /></button>
          </div>
        )}
        {bucket === 'itinerary' ? (
          <div style={{ height: '100%' }}>
            <ItineraryView surface="mobile" />
          </div>
        ) : (
          <div ref={scrollRef} onScroll={(e) => setScrolled(e.currentTarget.scrollTop > 160)} style={{
            height: '100%', overflowY: 'auto',
            padding: '0 12px 28px',
            opacity: sheetOpen ? 0.45 : 1, transition: 'opacity .15s',
            pointerEvents: sheetOpen ? 'none' : 'auto',
          }}>
            {viewState === 'loading' ? (
              <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, paddingTop: 12 }}>
                {Array.from({ length: 6 }).map((_, i) => <CardSkeleton key={i} variant="grid" />)}
              </div>
            ) : viewState === 'error' ? (
              <ErrorState surface="mobile" onRetry={onRetry} />
            ) : filtered.length === 0 ? (
              <EmptyState surface="mobile" hasFilters={hasFilters} onClear={clearAll} />
            ) : (
              <React.Fragment>
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, paddingTop: 12 }}>
                {shown.map((p) => (
                  <PlaceCard key={p.id} place={p} variant="grid" activeTags={tags} onTag={toggleTag} onOpen={setSelected} />
                ))}
                </div>
                <LoadMore shown={shown.length} total={filtered.length} surface="mobile" onMore={() => setVisible((v) => v + PAGE_MOBILE)} />
                {shown.length >= filtered.length && filtered.length > 0 && (
                  <div style={{ textAlign: 'center', fontSize: 11, color: T.muted, padding: '12px 0 4px' }}>
                    You've seen all {filtered.length} places · links live in your profile
                  </div>
                )}
              </React.Fragment>
            )}
          </div>
        )}

        {sheetOpen && bucket !== 'itinerary' && (
          <V3RefineSheet
            bucket={bucket} groups={groups} tags={tags}
            toggleTag={toggleTag} addTag={addTag}
            wards={wards} setWards={setWards}
            sort={sort} setSort={setSort}
            tagCounts={tagCounts}
            count={filtered.length}
            foodFilters={foodFilters} setFoodFilters={setFoodFilters}
            doFilters={doFilters} setDoFilters={setDoFilters}
            stayFilters={stayFilters} setStayFilters={setStayFilters}
            onClear={() => {
              setTags([]); setWards([]);
              setFoodFilters(DEFAULT_FOOD_FILTERS);
              setDoFilters(DEFAULT_DO_FILTERS);
              setStayFilters(DEFAULT_STAY_FILTERS);
            }}
            onClose={() => setSheetOpen(false)}
          />
        )}
      </div>
      {selected && <PlaceDetail place={selected} surface="mobile" onClose={() => setSelected(null)} />}
      {favOpen && <FavoritesView surface="mobile" onClose={() => setFavOpen(false)} onOpenPlace={(p) => { setFavOpen(false); setSelected(p); }} />}
      {menuOpen && <ProfileView surface="mobile" city={city} accounts={accounts} onClose={() => setMenuOpen(false)} onOpenFavorites={() => { setMenuOpen(false); setFavOpen(true); }} onOpenPage={(id) => { setMenuOpen(false); setPageOpen(id); }} />}
      {pageOpen && <StaticPage id={pageOpen} surface="mobile" city={city} onClose={() => setPageOpen(null)} />}
    </div>
  );
}

function V3RefineSheet({ bucket, groups, tags, toggleTag, addTag, wards, setWards, sort, setSort, tagCounts, count, foodFilters, setFoodFilters, doFilters, setDoFilters, stayFilters, setStayFilters, onClear, onClose }) {
  const bucketLabel = (BUCKETS.find((b) => b.id === bucket) || {}).label || '';
  return (
    <>
      <div onClick={onClose} style={{
        position: 'absolute', inset: 0,
        background: 'linear-gradient(180deg, rgba(0,0,0,0.04) 0%, rgba(0,0,0,0.18) 70%)',
      }} />
      <div style={{
        position: 'absolute', left: 0, right: 0, bottom: 0,
        background: T.panel, borderTopLeftRadius: 22, borderTopRightRadius: 22,
        boxShadow: '0 -12px 36px rgba(0,0,0,0.10)',
        padding: '10px 18px calc(18px + env(safe-area-inset-bottom, 0px))',
        maxHeight: '92%', display: 'flex', flexDirection: 'column',
      }}>
        <div style={{ width: 38, height: 4, borderRadius: 2, background: '#D9D2C5', margin: '4px auto 12px' }} />

        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
          <div style={{ fontSize: 15, fontWeight: 700, whiteSpace: 'nowrap' }}>
            Filters <span style={{ color: T.muted, fontWeight: 500 }}>· {bucketLabel}</span>
          </div>
          <div style={{ display: 'flex', gap: 14, alignItems: 'center' }}>
            {(tags.length > 0 || wards.length > 0) && (
              <button onClick={onClear} style={{
                all: 'unset', cursor: 'pointer', fontSize: 12, color: T.accent, fontWeight: 600,
              }}>Clear all</button>
            )}
            <button onClick={onClose} style={{
              all: 'unset', cursor: 'pointer',
              width: 28, height: 28, borderRadius: 14, background: '#F4EFE6',
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
            }}><Icon name="close" size={13} /></button>
          </div>
        </div>

        <div style={{ overflowY: 'auto', flex: 1, minHeight: 0, marginRight: -8, paddingRight: 8 }}>
          {/* Sort segmented — hidden for Itinerary (sort doesn't apply to a fixed plan) */}
          {bucket !== 'itinerary' && (
            <>
              <div style={{
                fontSize: 10, color: T.muted, letterSpacing: 0.6,
                textTransform: 'uppercase', fontWeight: 700, marginBottom: 6,
              }}>Sort</div>
              <Segmented
                options={SORTS.map((s) => ({ id: s.id, label: s.label }))}
                value={sort} onChange={setSort} size="sm"
              />
              <div style={{ fontSize: 11, color: T.muted, marginTop: 6 }}>
                {(SORTS.find((s) => s.id === sort) || {}).hint}
              </div>
            </>
          )}

          {/* Travelling With — all buckets */}
          <div style={{ marginTop: bucket === 'itinerary' ? 0 : 18 }}>
            <FoodWhoSection filters={foodFilters} setFilters={setFoodFilters} bucket={bucket} />
          </div>

          {/* Eat price filter */}
          {bucket === 'eat' && (
            <div style={{ marginTop: 18 }}>
              <EatBudgetSection filters={foodFilters} setFilters={setFoodFilters} />
            </div>
          )}

          {/* Do bucket: budget + indoor/outdoor + energy + water/land/air */}
          {bucket === 'do' && (
            <div style={{ marginTop: 18, display: 'flex', flexDirection: 'column', gap: 14 }}>
              <DoBudgetSection filters={doFilters} setFilters={setDoFilters} />
              <DoEnvSection filters={doFilters} setFilters={setDoFilters} />
              <DoEnergySection filters={doFilters} setFilters={setDoFilters} />
              <DoEnvTypesSection filters={doFilters} setFilters={setDoFilters} />
            </div>
          )}

          {/* Stay bucket: tier + proximity + must / nice */}
          {bucket === 'stay' && (
            <div style={{ marginTop: 18, display: 'flex', flexDirection: 'column', gap: 14 }}>
              <StayTierSection filters={stayFilters} setFilters={setStayFilters} />
              <StayProximitySection filters={stayFilters} setFilters={setStayFilters} />
              <StayMustHavesSection filters={stayFilters} setFilters={setStayFilters} />
              <StayNiceToHavesSection filters={stayFilters} setFilters={setStayFilters} />
            </div>
          )}

          {/* Tag inputs / chip groups (Eat only; Do/Stay/Itinerary use dedicated filters) */}
          {bucket !== 'do' && bucket !== 'stay' && bucket !== 'itinerary' && (
            <div style={{ marginTop: 18, display: 'flex', flexDirection: 'column', gap: 16 }}>
              {groups.map((g) => (
                <TagInput
                  key={g.id}
                  groupLabel={g.label}
                  tags={g.tags}
                  selected={tags.filter((t) => g.tags.includes(t))}
                  onToggle={toggleTag}
                  tagCounts={tagCounts}
                  placeholder={`e.g. ${g.tags.slice(0, 3).join(', ')}…`}
                />
              ))}
            </div>
          )}

          {/* Ward / neighborhood filter — after food profile, all buckets */}
          {bucket !== 'itinerary' && (
            <div style={{ marginTop: 18 }}>
              <WardSection wards={wards} setWards={setWards} places={PLACES} bucket={bucket} />
            </div>
          )}

          {/* Eat: Occasion + Included by default (bottom) */}
          {bucket === 'eat' && (
            <div style={{ marginTop: 18, display: 'flex', flexDirection: 'column', gap: 14 }}>
              <FoodOccasionSection filters={foodFilters} setFilters={setFoodFilters} />
              <FoodInclusionsSection filters={foodFilters} setFilters={setFoodFilters} />
            </div>
          )}
        </div>

        <button onClick={onClose} style={{
          all: 'unset', cursor: 'pointer', boxSizing: 'border-box',
          marginTop: 12, width: '100%', height: 52, borderRadius: 14,
          background: T.selBg, color: T.selFg,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          fontSize: 15, fontWeight: 700, flexShrink: 0,
          paddingBottom: 'env(safe-area-inset-bottom, 0px)',
        }}>
          {`Show ${count} ${count === 1 ? 'place' : 'places'}`}
        </button>
      </div>
    </>
  );
}

// ─────────────────────────────────────────────────────────────
// Web variant
// ─────────────────────────────────────────────────────────────
function WebAppV3({ city = 'Tokyo', dataState = 'ready', accounts = false, onRetry }) {
  const [bucket, setBucket] = React.useState('eat');
  const [tags, setTags] = React.useState([]);
  const [wards, setWards] = React.useState([]);
  const [searchQuery, setSearchQuery] = React.useState('');
  const [sort, setSort] = React.useState('recommended');
  const [foodFilters, setFoodFilters] = React.useState(DEFAULT_FOOD_FILTERS);
  const [doFilters, setDoFilters] = React.useState(DEFAULT_DO_FILTERS);
  const [stayFilters, setStayFilters] = React.useState(DEFAULT_STAY_FILTERS);
  const [visible, setVisible] = React.useState(PAGE_WEB);
  const [selected, setSelected] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [favOpen, setFavOpen] = React.useState(false);
  const [menuOpen, setMenuOpen] = React.useState(false);
  const [view, setView] = React.useState('grid');
  const [pageOpen, setPageOpen] = React.useState(null);
  const [cityOpen, setCityOpen] = React.useState(false);
  const [curOpen, setCurOpen] = React.useState(false);
  const [langOpen, setLangOpen] = React.useState(false);
  const [curId, setCurIdState] = React.useState(() => { try { return localStorage.getItem('itrymo_currency') || 'jpy'; } catch { return 'jpy'; } });
  const [langId, setLangIdState] = React.useState(() => { try { return localStorage.getItem('itrymo_lang') || 'en'; } catch { return 'en'; } });
  const setCur = (id) => { setCurIdState(id); try { localStorage.setItem('itrymo_currency', id); window.dispatchEvent(new Event('itrymo_pref_change')); } catch {} };
  const setLang = (id) => { setLangIdState(id); try { localStorage.setItem('itrymo_lang', id); window.dispatchEvent(new Event('itrymo_pref_change')); } catch {} };
  React.useEffect(() => {
    const h = () => {
      try { setCurIdState(localStorage.getItem('itrymo_currency') || 'jpy'); } catch {}
      try { setLangIdState(localStorage.getItem('itrymo_lang') || 'en'); } catch {}
    };
    window.addEventListener('itrymo_pref_change', h);
    return () => window.removeEventListener('itrymo_pref_change', h);
  }, []);
  const LANGS_LIST = window.LANGS || [{id:'en',label:'English',short:'EN'},{id:'ja',label:'日本語',short:'JA'},{id:'zh',label:'中文',short:'ZH'},{id:'ko',label:'한국어',short:'KO'}];
  const curObj = (window.CURRENCIES || [{id:'jpy',sym:'¥',short:'JPY'}]).find(c => c.id === curId) || {sym:'¥',short:'JPY'};
  const langObj = LANGS_LIST.find(l => l.id === langId) || {short:'EN'};
  const favIds = useFavorites();

  React.useEffect(() => { setVisible(PAGE_WEB); }, [bucket, tags, wards, searchQuery, sort, foodFilters, doFilters, stayFilters]);
  React.useEffect(() => {
    if (dataState !== 'ready') return;
    setLoading(true);
    const t = setTimeout(() => setLoading(false), 420);
    return () => clearTimeout(t);
  }, [bucket, dataState]);
  const viewState = dataState === 'error' ? 'error' : (dataState === 'loading' || loading) ? 'loading' : 'ready';
  const hasFilters = tags.length > 0 || wards.length > 0 || searchQuery.length > 0;
  const clearAll = () => { setTags([]); setWards([]); setSearchQuery(''); setFoodFilters(DEFAULT_FOOD_FILTERS); setDoFilters(DEFAULT_DO_FILTERS); setStayFilters(DEFAULT_STAY_FILTERS); };

  const switchBucket = (id) => { setBucket(id); setTags([]); setWards([]); setSearchQuery(''); };
  const toggleTag = (tg) => setTags((cur) => cur.includes(tg) ? cur.filter((t) => t !== tg) : [...cur, tg]);
  const addTag = (tg) => setTags((cur) => cur.includes(tg) ? cur : [...cur, tg]);

  const groups = getGroupsFor(bucket);
  const filtered = sortPlaces(applyFilters(PLACES, { bucket, tags, wards, searchQuery, foodFilters, doFilters, stayFilters }), sort);
  const shown = filtered.slice(0, visible);
  const tagCounts = React.useMemo(() => getTagCounts(PLACES.filter(p => p.bucket === bucket)), [bucket]);

  return (
    <div style={{
      width: '100%', minHeight: '100vh', background: T.bg,
      fontFamily: T.fontUI, color: T.ink,
      display: 'flex', flexDirection: 'column',
    }}>
      <h1 className="sr-only">itrymo — discover where to eat, what to do, and where to stay in {city}</h1>
      <div style={{
        height: 60, background: '#fff', borderBottom: `1px solid ${T.border}`,
        display: 'flex', alignItems: 'center', padding: '0 28px', gap: 20,
        position: 'sticky', top: 0, zIndex: 30,
      }}>
        <div style={{ fontSize: 20, fontWeight: 700, letterSpacing: -0.5, display: 'flex', alignItems: 'center', gap: 6 }}>
          itrymo<span style={{ color: T.accent }}>.</span>
          <span style={{ fontSize: 10, fontWeight: 600, color: '#B36A00', background: '#FFF3E0', border: '1px solid #F5C97A', borderRadius: 20, padding: '1px 7px', letterSpacing: 0.2 }}>Beta</span>
        </div>
        <div style={{ position: 'relative' }}>
          <button onClick={() => setCityOpen((o) => !o)} style={{
            all: 'unset', cursor: 'pointer',
            height: 36, padding: '0 12px', borderRadius: 10, border: `1px solid ${cityOpen ? T.accent : T.border}`,
            display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 13, fontWeight: 500,
          }}>
            <Icon name="pin" size={13} color={T.accent} /> {city}
            <Icon name="chev" size={11} color={T.muted} />
          </button>
          {cityOpen && (
            <div style={{
              position: 'absolute', top: 'calc(100% + 8px)', left: 0, zIndex: 50,
              background: '#fff', border: `1px solid ${T.border}`, borderRadius: 12,
              padding: 8, minWidth: 180, boxShadow: '0 8px 24px rgba(0,0,0,0.10)',
            }}>
              <div style={{ fontSize: 11, color: T.muted, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6, padding: '4px 10px 8px' }}>Available cities</div>
              {['Tokyo'].map((c) => (
                <button key={c} onClick={() => setCityOpen(false)} style={{
                  all: 'unset', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8,
                  padding: '8px 10px', borderRadius: 8, width: '100%', fontSize: 13, fontWeight: 500,
                  background: c === city ? T.bgSoft : 'transparent', boxSizing: 'border-box',
                }}>
                  <Icon name="pin" size={12} color={T.accent} /> {c}
                  {c === city && <span style={{ marginLeft: 'auto', fontSize: 11, color: T.accent, fontWeight: 700 }}>Current</span>}
                </button>
              ))}
              <div style={{ borderTop: `1px solid ${T.borderSoft}`, margin: '8px 0 4px', padding: '8px 10px 4px', fontSize: 12, color: T.muted }}>
                More cities coming soon
              </div>
            </div>
          )}
        </div>
        <div style={{ flex: 1, maxWidth: 420 }}>
          <CustomSearch
            bucket={bucket} groups={groups} tags={tags}
            onAddTag={addTag} places={PLACES} onPickPlace={setSelected}
            searchQuery={searchQuery} onSearchChange={setSearchQuery}
          />
        </div>
        <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 10 }}>
          <div style={{ position: 'relative' }}>
            <button title="Language" onClick={() => setLangOpen(o => !o)} style={navIconBtn}>
              <Icon name="globe" size={13} color={T.faintInk} />
              <span>{langObj.short}</span>
              <Icon name="chev" size={10} color={T.muted} />
            </button>
            {langOpen && (
              <div style={{
                position: 'absolute', top: 'calc(100% + 8px)', right: 0, zIndex: 50,
                background: '#fff', border: `1px solid ${T.border}`, borderRadius: 12,
                padding: 8, minWidth: 180, boxShadow: '0 8px 24px rgba(0,0,0,0.10)',
              }}>
                {LANGS_LIST.map(l => (
                  <button key={l.id} onClick={() => { setLang(l.id); setLangOpen(false); }} style={{
                    all: 'unset', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8,
                    padding: '8px 10px', borderRadius: 8, width: '100%', fontSize: 13, fontWeight: 500,
                    background: l.id === langId ? T.bgSoft : 'transparent', boxSizing: 'border-box',
                  }}>
                    {l.label}
                    {l.id === langId && <span style={{ marginLeft: 'auto', fontSize: 11, color: T.accent, fontWeight: 700 }}>✓</span>}
                    {l.id !== 'en' && <span style={{ marginLeft: l.id === langId ? 0 : 'auto', fontSize: 10, color: T.muted }}>Soon</span>}
                  </button>
                ))}
              </div>
            )}
          </div>
          <div style={{ position: 'relative' }}>
            <button title="Currency" onClick={() => setCurOpen(o => !o)} style={navIconBtn}>
              <span style={{ fontWeight: 700 }}>{curObj.sym}</span>
              <span>{curObj.short}</span>
              <Icon name="chev" size={10} color={T.muted} />
            </button>
            {curOpen && (
              <div style={{
                position: 'absolute', top: 'calc(100% + 8px)', right: 0, zIndex: 50,
                background: '#fff', border: `1px solid ${T.border}`, borderRadius: 12,
                padding: 8, minWidth: 180, boxShadow: '0 8px 24px rgba(0,0,0,0.10)',
              }}>
                {(window.CURRENCIES || [{id:'jpy',label:'Japanese Yen',sym:'¥',short:'JPY'},{id:'usd',label:'US Dollar',sym:'$',short:'USD'},{id:'eur',label:'Euro',sym:'€',short:'EUR'},{id:'gbp',label:'British Pound',sym:'£',short:'GBP'}]).map(c => (
                  <button key={c.id} onClick={() => { setCur(c.id); setCurOpen(false); }} style={{
                    all: 'unset', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8,
                    padding: '8px 10px', borderRadius: 8, width: '100%', fontSize: 13, fontWeight: 500,
                    background: c.id === curId ? T.bgSoft : 'transparent', boxSizing: 'border-box',
                  }}>
                    <span style={{ fontWeight: 700, width: 16 }}>{c.sym}</span> {c.label}
                    {c.id === curId && <span style={{ marginLeft: 'auto', fontSize: 11, color: T.accent, fontWeight: 700 }}>✓</span>}
                  </button>
                ))}
              </div>
            )}
          </div>
          <span style={{ width: 1, height: 22, background: T.borderSoft }} />
          <button title="Favorites" onClick={() => setFavOpen(true)} style={{
            all: 'unset', cursor: 'pointer', position: 'relative',
            width: 36, height: 36, borderRadius: 18,
            background: '#fff', border: `1px solid ${T.border}`,
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
            color: T.faintInk,
          }}>
            <Icon name="heart" size={16} color={favIds.length ? '#E0533D' : T.faintInk} fill={favIds.length ? '#E0533D' : 'none'} />
            {favIds.length > 0 && (
              <span style={{
                position: 'absolute', top: -10, right: -8,
                minWidth: 18, height: 18, padding: '0 5px', borderRadius: 9,
                background: T.accent, color: '#fff', fontSize: 10, fontWeight: 700,
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                border: '2px solid #fff',
              }}>{favIds.length}</span>
            )}
          </button>
          <button title={accounts ? 'Account' : 'Settings'} onClick={() => setMenuOpen(true)} style={{
            all: 'unset', cursor: 'pointer',
            width: 34, height: 34, borderRadius: 17,
            background: accounts ? T.accentBg : '#fff', color: accounts ? T.accent : T.faintInk,
            border: accounts ? 'none' : `1px solid ${T.border}`,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontSize: 12, fontWeight: 700,
          }}>{accounts ? 'JL' : <Icon name="gear" size={16} />}</button>
        </div>
      </div>

      <div style={{ background: '#fff', borderBottom: `1px solid ${T.border}`, padding: '18px 28px' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          {BUCKETS.map((b) => {
            const on = b.id === bucket;
            return (
              <button key={b.id} onClick={() => switchBucket(b.id)} style={{
                all: 'unset', cursor: 'pointer',
                height: 44, padding: '0 18px', borderRadius: 12,
                background: on ? T.selBg : T.bg,
                color: on ? T.selFg : T.faintInk,
                border: `1px solid ${on ? T.selBg : T.border}`,
                display: 'inline-flex', alignItems: 'center', gap: 8,
                fontSize: 14, fontWeight: 600,
              }}>
                <Icon name={b.icon} size={15} />
                <span>{b.label}</span>
                <span style={{ fontSize: 11, fontWeight: 500, opacity: 0.6 }}>{b.desc}</span>
              </button>
            );
          })}
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: bucket === 'itinerary' ? '1fr' : '320px 1fr', gap: 24, padding: '24px 28px 60px', alignItems: 'flex-start', flex: 1 }}>
        {bucket !== 'itinerary' && (
        <aside style={{
          position: 'sticky', top: 84,
          background: '#fff', borderRadius: 16, border: `1px solid ${T.borderSoft}`,
          padding: 18,
        }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
            <div style={{ fontSize: 14, fontWeight: 700 }}>Refine</div>
            {(tags.length > 0 || wards.length > 0) && (
              <button onClick={clearAll} style={{
                all: 'unset', cursor: 'pointer', fontSize: 12, color: T.accent, fontWeight: 600,
              }}>Clear all</button>
            )}
          </div>

          {bucket !== 'itinerary' && (
            <>
              <div style={{
                fontSize: 10, color: T.muted, letterSpacing: 0.6,
                textTransform: 'uppercase', fontWeight: 700, marginBottom: 6,
              }}>Sort</div>
              <Segmented
                options={SORTS.map((s) => ({ id: s.id, label: s.label }))}
                value={sort} onChange={setSort} size="sm"
              />
              <div style={{ fontSize: 11, color: T.muted, marginTop: 6, marginBottom: 16 }}>
                {(SORTS.find((s) => s.id === sort) || {}).hint}
              </div>
            </>
          )}

          {/* Travelling With — all buckets */}
          <div style={{ marginBottom: 16 }}>
            <FoodWhoSection filters={foodFilters} setFilters={setFoodFilters} bucket={bucket} />
          </div>

          {/* Eat price filter */}
          {bucket === 'eat' && (
            <div style={{ marginBottom: 16 }}>
              <EatBudgetSection filters={foodFilters} setFilters={setFoodFilters} />
            </div>
          )}

          {/* Do bucket: dedicated sections instead of tag inputs */}
          {bucket === 'do' && (
            <div style={{ marginBottom: 16, display: 'flex', flexDirection: 'column', gap: 14 }}>
              <DoBudgetSection filters={doFilters} setFilters={setDoFilters} />
              <DoEnvSection filters={doFilters} setFilters={setDoFilters} />
              <DoEnergySection filters={doFilters} setFilters={setDoFilters} />
              <DoEnvTypesSection filters={doFilters} setFilters={setDoFilters} />
            </div>
          )}

          {/* Stay bucket: dedicated sections instead of tag inputs */}
          {bucket === 'stay' && (
            <div style={{ marginBottom: 16, display: 'flex', flexDirection: 'column', gap: 14 }}>
              <StayTierSection filters={stayFilters} setFilters={setStayFilters} />
              <StayProximitySection filters={stayFilters} setFilters={setStayFilters} />
              <StayMustHavesSection filters={stayFilters} setFilters={setStayFilters} />
              <StayNiceToHavesSection filters={stayFilters} setFilters={setStayFilters} />
            </div>
          )}

          {bucket !== 'do' && bucket !== 'stay' && bucket !== 'itinerary' && (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 16, maxHeight: 540, overflowY: 'auto', marginRight: -6, paddingRight: 6 }}>
              {groups.map((g) => (
                <TagInput
                  key={g.id}
                  groupLabel={g.label}
                  tags={g.tags}
                  selected={tags.filter((t) => g.tags.includes(t))}
                  onToggle={toggleTag}
                  tagCounts={tagCounts}
                  placeholder={`e.g. ${g.tags.slice(0, 3).join(', ')}…`}
                />
              ))}
            </div>
          )}

          {/* Ward / neighborhood — after food profile, all buckets */}
          {bucket !== 'itinerary' && (
            <div style={{ marginTop: 16 }}>
              <WardSection wards={wards} setWards={setWards} places={PLACES} bucket={bucket} />
            </div>
          )}

          {bucket === 'eat' && (
            <div style={{ marginTop: 16, display: 'flex', flexDirection: 'column', gap: 14 }}>
              <FoodOccasionSection filters={foodFilters} setFilters={setFoodFilters} />
              <FoodInclusionsSection filters={foodFilters} setFilters={setFoodFilters} />
            </div>
          )}
        </aside>
        )}

        <main>
          {bucket === 'itinerary' ? (
            <div style={{
              height: 760, background: '#fff', borderRadius: 16,
              border: `1px solid ${T.borderSoft}`, overflow: 'hidden',
              paddingTop: 16,
            }}>
              <ItineraryView surface="web" />
            </div>
          ) : (
          <React.Fragment>
          <div style={{
            display: 'flex', alignItems: 'center', justifyContent: 'space-between',
            gap: 16, marginBottom: 16, flexWrap: 'wrap',
          }}>
            <div style={{ fontSize: 13, color: T.muted }}>
              {viewState === 'loading' ? 'Finding places…'
                : viewState === 'error' ? 'Unable to load'
                : <React.Fragment><strong style={{ color: T.ink, fontSize: 16, fontWeight: 700, marginRight: 6 }}>{filtered.length}</strong>places in {city}</React.Fragment>}
            </div>
            <div style={{ display: 'inline-flex', background: '#fff', border: `1px solid ${T.border}`, borderRadius: 10, padding: 3, gap: 2 }}>
              {[['grid', 'grip', 'Grid'], ['list', 'sort', 'List']].map(([v, ic, lbl]) => (
                <button key={v} onClick={() => setView(v)} style={{
                  all: 'unset', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 6,
                  padding: '6px 12px', borderRadius: 8, fontSize: 12, fontWeight: 600,
                  background: view === v ? T.selBg : 'transparent', color: view === v ? T.selFg : T.faintInk,
                }}>
                  <Icon name={ic} size={13} /> {lbl}
                </button>
              ))}
            </div>
          </div>

          {viewState === 'loading' ? (
            <SkeletonGrid surface="web" count={9} />
          ) : viewState === 'error' ? (
            <ErrorState surface="web" onRetry={onRetry} />
          ) : filtered.length === 0 ? (
            <EmptyState surface="web" hasFilters={hasFilters} onClear={clearAll} />
          ) : view === 'list' ? (
            <React.Fragment>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 10, width: '100%' }}>
              {shown.map((p) => (
                <PlaceCard key={p.id} place={p} variant="list" activeTags={tags} onTag={toggleTag} onOpen={setSelected} />
              ))}
            </div>
            <LoadMore shown={shown.length} total={filtered.length} surface="web" onMore={() => setVisible((v) => v + PAGE_WEB)} />
            </React.Fragment>
          ) : (
            <React.Fragment>
            <div style={{
              display: 'grid', gap: 16,
              gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))',
            }}>
              {shown.map((p) => (
                <PlaceCard key={p.id} place={p} variant="grid" activeTags={tags} onTag={toggleTag} onOpen={setSelected} />
              ))}
            </div>
            <LoadMore shown={shown.length} total={filtered.length} surface="web" onMore={() => setVisible((v) => v + PAGE_WEB)} />
            </React.Fragment>
          )}
          </React.Fragment>
          )}
        </main>
      </div>
      <SiteFooter surface="web" city={city} onOpenPage={setPageOpen} />
      {selected && <PlaceDetail place={selected} surface="web" onClose={() => setSelected(null)} />}
      {favOpen && <FavoritesView surface="web" onClose={() => setFavOpen(false)} onOpenPlace={(p) => { setFavOpen(false); setSelected(p); }} />}
      {menuOpen && <ProfileView surface="web" city={city} accounts={accounts} onClose={() => setMenuOpen(false)} onOpenFavorites={() => { setMenuOpen(false); setFavOpen(true); }} onOpenPage={(id) => { setMenuOpen(false); setPageOpen(id); }} />}
      {pageOpen && <StaticPage id={pageOpen} surface="web" city={city} onClose={() => setPageOpen(null)} />}
    </div>
  );
}

Object.assign(window, { MobileAppV3, WebAppV3, V3RefineSheet, LoadMore, SiteFooter });

const navIconBtn = {
  all: 'unset', cursor: 'pointer',
  height: 32, padding: '0 10px', borderRadius: 8,
  border: `1px solid ${T.border}`, background: '#fff',
  display: 'inline-flex', alignItems: 'center', gap: 6,
  fontSize: 12, fontWeight: 600, color: T.faintInk,
};
window.navIconBtn = navIconBtn;
