// Gotanda Guide — main app

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

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#f5b25b",
  "accent2": "#ff5b8a",
  "lang": "ja",
  "theme": "dark",
  "cardStyle": "magazine"
}/*EDITMODE-END*/;

const ACCENT_PRESETS = {
  '#f5b25b': { name: 'Amber',  fg: '#1a1006', pair: '#ff5b8a' }, // amber + magenta
  '#ff5b8a': { name: 'Neon',   fg: '#1a050f', pair: '#5cd1ff' }, // pink + cyan
  '#5cd1ff': { name: 'Cyan',   fg: '#04141a', pair: '#f5b25b' }, // cyan + amber
  '#a8ff7a': { name: 'Lime',   fg: '#08160a', pair: '#ff5b8a' }, // lime + pink
  '#cccccc': { name: 'Mono',   fg: '#0a0a0c', pair: '#888888' }, // mono
};

function App() {
  const { SPOTS, CATEGORIES, EVENTS, NEWS, HOOD_STATS, I18N } = window.__GG;
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  const [tab, setTab] = useState('home');
  const [activeSpot, setActiveSpot] = useState(null);
  const [cat, setCat] = useState(null);
  const [order, setOrder] = useState(() => {
    try { return JSON.parse(localStorage.getItem('gg_order') || 'null'); } catch { return null; }
  });
  useEffect(() => {
    if (order) localStorage.setItem('gg_order', JSON.stringify(order));
    else localStorage.removeItem('gg_order');
  }, [order]);
  const [query, setQuery] = useState('');
  const [showImport, setShowImport] = useState(false);
  const [favs, setFavs] = useState(() => {
    try {
      return new Set(JSON.parse(localStorage.getItem('gg_favs') || '[]'));
    } catch { return new Set(); }
  });

  // De-dupe spot array by id (also fills in missing ids).
  const dedupeSpots = (arr) => {
    const seen = new Set();
    const out = [];
    for (const s of arr) {
      let id = s.id;
      if (!id || seen.has(id)) {
        let n = 2;
        const base = id || 'spot';
        do { id = `${base}-${n++}`; } while (seen.has(id));
      }
      seen.add(id);
      out.push({ ...s, id });
    }
    return out;
  };

  // Imported / custom spots
  const [customSpots, setCustomSpots] = useState(() => {
    try {
      const raw = localStorage.getItem('gg_spots');
      const parsed = raw ? JSON.parse(raw) : null;
      return parsed ? dedupeSpots(parsed) : null;
    } catch { return null; }
  });
  // Per-id image overrides (data URLs / external URLs)
  const [images, setImages] = useState(() => {
    try { return JSON.parse(localStorage.getItem('gg_images') || '{}'); }
    catch { return {}; }
  });

  useEffect(() => {
    if (customSpots) localStorage.setItem('gg_spots', JSON.stringify(customSpots));
    else localStorage.removeItem('gg_spots');
  }, [customSpots]);
  useEffect(() => {
    try { localStorage.setItem('gg_images', JSON.stringify(images)); } catch {}
  }, [images]);

  const spots = customSpots || SPOTS;
  const [categories, setCategories] = useState(() => loadCategories(CATEGORIES));
  const onCategoriesChange = (next) => { setCategories(next); saveCategories(next); };
  const [sponsors, setSponsors] = useState(() => loadSponsors());
  const onSponsorsChange = (next) => { setSponsors(next); saveSponsors(next); };

  // persist favs
  useEffect(() => {
    localStorage.setItem('gg_favs', JSON.stringify([...favs]));
  }, [favs]);

  const toggleFav = useCallback((id) => {
    setFavs((s) => {
      const n = new Set(s);
      if (n.has(id)) n.delete(id); else n.add(id);
      return n;
    });
  }, []);

  // theme & accent on root
  useEffect(() => {
    document.documentElement.setAttribute('data-theme', t.theme);
    const preset = ACCENT_PRESETS[t.accent] || ACCENT_PRESETS['#f5b25b'];
    const root = document.documentElement.style;
    root.setProperty('--accent', t.accent);
    root.setProperty('--accent-text', preset.fg);
    root.setProperty('--accent-2', t.accent2 || preset.pair);
  }, [t.theme, t.accent, t.accent2]);

  const lang = t.lang;
  const setLang = (v) => setTweak('lang', v);
  const setTheme = (v) => setTweak('theme', v);

  // counts by category
  const counts = useMemo(() => {
    const c = { all: spots.length };
    for (const s of spots) c[s.cat] = (c[s.cat] || 0) + 1;
    return c;
  }, [spots]);

  // filtered spots
  const filtered = useMemo(() => {
    const q = query.trim().toLowerCase();
    const filteredArr = cat == null ? [] : spots.filter((s) => {
      if (cat !== 'all' && s.cat !== cat) return false;
      if (q) {
        const hay = [
          s.name.ja, s.name.en, s.tag.ja, s.tag.en,
          s.desc.ja, s.desc.en,
          ...(s.tags || []),
        ].join(' ').toLowerCase();
        if (!hay.includes(q)) return false;
      }
      return true;
    });
    return filteredArr;
  }, [spots, cat, query]);

  // sections per tab
  const handleCTA = (target) => {
    setTab(target);
    setTimeout(() => {
      const id = target === 'spots' ? 'sec-spots' : target === 'map' ? 'sec-map' : null;
      if (id) {
        const el = document.getElementById(id);
        if (el) window.scrollTo({ top: el.offsetTop - 70, behavior: 'smooth' });
      } else {
        window.scrollTo({ top: 0, behavior: 'smooth' });
      }
    }, 30);
  };

  const onTab = (newTab) => {
    setTab(newTab);
    const map = {
      home: 0,
      spots: 'sec-spots',
      map: 'sec-map',
      saved: 'sec-saved',
    };
    const target = map[newTab];
    setTimeout(() => {
      if (target === 0) {
        window.scrollTo({ top: 0, behavior: 'smooth' });
      } else {
        const el = document.getElementById(target);
        if (el) window.scrollTo({ top: el.offsetTop - 70, behavior: 'smooth' });
      }
    }, 30);
  };

  // saved tab content
  const savedSpots = useMemo(
    () => spots.filter((s) => favs.has(s.id)),
    [spots, favs]
  );

  const onImport = (incoming, mode) => {
    if (mode === 'replace') setCustomSpots(dedupeSpots(incoming));
    else setCustomSpots(dedupeSpots([...(customSpots || SPOTS), ...incoming]));
  };
  const onResetSpots = () => setCustomSpots(null);

  // Refresh a single spot's Google Place Details (incl. reviews).
  const onRefreshReviews = async (spot) => {
    const apiKey = (() => { try { return localStorage.getItem('gg_google_key') || ''; } catch { return ''; } })();
    if (!apiKey) {
      alert(lang === 'ja'
        ? 'Google APIキーが未設定です。インポート→Google Placesタブでキーを保存してください。'
        : 'No Google API key. Save one in Import → Google Places.');
      return;
    }
    try {
      const updated = await refreshSpotDetails({
        apiKey, placeId: spot.id, cat: spot.cat,
      });
      // Merge: keep user's custom name/desc/image overrides if present.
      const merged = {
        ...spot,
        rating: updated.rating,
        userRatingsTotal: updated.userRatingsTotal,
        reviews: updated.reviews,
        reviewsFetchedAt: updated.reviewsFetchedAt,
        hours: updated.hours,
      };
      const base = customSpots || SPOTS;
      const next = base.map((s) => (s.id === spot.id ? merged : s));
      setCustomSpots(next);
      setActiveSpot(merged);
    } catch (e) {
      alert((lang === 'ja' ? '取得に失敗: ' : 'Refresh failed: ') + (e.message || e));
    }
  };
  const onSetImage = (id, dataUrl) => {
    setImages((m) => ({ ...m, [id]: dataUrl }));
  };
  const onEditField = (id, patch) => {
    const base = customSpots || SPOTS;
    const next = base.map((s) => (s.id === id ? { ...s, ...patch } : s));
    setCustomSpots(next);
    if (activeSpot && activeSpot.id === id) {
      setActiveSpot({ ...activeSpot, ...patch });
    }
  };

  return (
    <div className="phone-shell">
      <TopBar
        lang={lang} onLang={setLang}
        theme={t.theme} onTheme={setTheme}
        query={query} onQuery={setQuery}
        I18N={I18N}
        tab={tab} onTab={onTab}
        savedCount={savedSpots.length}
        onImport={() => setShowImport(true)}
      />
      <SponsorsSection sponsors={sponsors} lang={lang} slot="header" />
      <Hero
        lang={lang} I18N={I18N} stats={HOOD_STATS}
        onCTA={handleCTA}
      />
      <section style={{ paddingTop: 22 }}>
        <TonightStrip lang={lang} spots={spots} onOpen={setActiveSpot} />
      </section>
      <section id="sec-spots">
        <div className="sec-hd">
          <div>
            <span className="sec-hd-sub">SECTION 02 / SPOTS · {filtered.length}/{spots.length}{customSpots ? ' · IMPORTED' : ''}</span>
            <h2>{I18N.sec_spots[lang]}</h2>
          </div>
          <button className="more" onClick={() => setShowImport(true)}>
            ＋ {lang === 'ja' ? 'インポート' : 'Import'}
          </button>
        </div>
        <CategoryChips
          active={cat} onChange={setCat}
          lang={lang} CATEGORIES={categories}
          counts={counts}
        />
        <SpotList
          spots={filtered} lang={lang}
          onOpen={setActiveSpot}
          favs={favs} onFav={toggleFav}
          cardStyle={t.cardStyle}
          I18N={I18N}
          images={images}
          prompt={cat == null}
        />
      </section>
      <SponsorsSection sponsors={sponsors} lang={lang} slot="spots" />
      <section id="sec-saved">
        <div className="sec-hd">
          <div>
            <span className="sec-hd-sub">SECTION 03 / SAVED · {savedSpots.length}</span>
            <h2>{I18N.saved_lbl[lang]}</h2>
          </div>
        </div>
        {savedSpots.length === 0 ? (
          <div className="empty">
            <div className="empty-icon">♡</div>
            <div>{I18N.no_saved[lang]}</div>
          </div>
        ) : (
          <SpotList
            spots={savedSpots} lang={lang}
            onOpen={setActiveSpot}
            favs={favs} onFav={toggleFav}
            cardStyle="row"
            I18N={I18N}
            images={images}
          />
        )}
      </section>
      <div id="sec-map">
        <MapSection spots={spots} lang={lang} onOpen={setActiveSpot} I18N={I18N} />
      </div>
      <Events lang={lang} EVENTS={EVENTS} I18N={I18N} />
      <News lang={lang} NEWS={NEWS} I18N={I18N} />
      <AreaBlurb lang={lang} I18N={I18N} />
      <SponsorsSection sponsors={sponsors} lang={lang} slot="footer" />
      <div className="foot">
        <div>{I18N.brand[lang]}</div>
        <div style={{ marginTop: 6, opacity: 0.6 }}>{I18N.footer[lang]}</div>
      </div>
      <BottomNav
        tab={tab} onTab={onTab}
        lang={lang} I18N={I18N}
        savedCount={savedSpots.length}
      />
      <BackToTop lang={lang} />
      {activeSpot && (
        <SpotModal
          spot={activeSpot}
          lang={lang}
          onClose={() => setActiveSpot(null)}
          isFav={favs.has(activeSpot.id)}
          onFav={toggleFav}
          I18N={I18N}
          image={images[activeSpot.id]}
          onImage={onSetImage}
          onEditField={onEditField}
          onRefreshReviews={onRefreshReviews}
        />
      )}
      {showImport && (
        <ImportModal
          lang={lang}
          onClose={() => setShowImport(false)}
          onApply={onImport}
          onReset={onResetSpots}
          hasCustom={!!customSpots}
          currentCount={spots.length}
          categories={categories}
          onCategoriesChange={onCategoriesChange}
          sponsors={sponsors}
          onSponsorsChange={onSponsorsChange}
        />
      )}
      <TweaksPanel className={t.theme === 'dark' ? 'dark' : ''}>
        <TweakSection label={lang === 'ja' ? 'テーマ' : 'Theme'} />
        <TweakRadio
          label={lang === 'ja' ? 'モード' : 'Mode'}
          value={t.theme}
          options={[
            { value: 'dark',  label: lang === 'ja' ? 'ダーク' : 'Dark' },
            { value: 'light', label: lang === 'ja' ? 'ライト' : 'Light' },
          ]}
          onChange={(v) => setTweak('theme', v)}
        />
        <TweakColor
          label={lang === 'ja' ? 'アクセント' : 'Accent'}
          value={t.accent}
          options={Object.keys(ACCENT_PRESETS)}
          onChange={(v) => {
            const p = ACCENT_PRESETS[v];
            setTweak({ accent: v, accent2: p.pair });
          }}
        />
        <TweakSection label={lang === 'ja' ? 'コンテンツ' : 'Content'} />
        <TweakRadio
          label={lang === 'ja' ? '言語' : 'Language'}
          value={t.lang}
          options={[
            { value: 'ja', label: '日本語' },
            { value: 'en', label: 'English' },
          ]}
          onChange={(v) => setTweak('lang', v)}
        />
        <TweakRadio
          label={lang === 'ja' ? 'カード' : 'Card'}
          value={t.cardStyle}
          options={[
            { value: 'row',      label: lang === 'ja' ? 'リスト' : 'List' },
            { value: 'grid',     label: lang === 'ja' ? 'グリッド' : 'Grid' },
            { value: 'magazine', label: lang === 'ja' ? '雑誌' : 'Mag' },
          ]}
          onChange={(v) => setTweak('cardStyle', v)}
        />
      </TweaksPanel>
    </div>
  );
}

async function bootGG() {
  if (typeof window.__GG_LOAD === 'function') {
    await window.__GG_LOAD();
  }
  const root = ReactDOM.createRoot(document.getElementById('root'));
  root.render(<App />);
}
bootGG();
