// Gotanda Guide — Admin Dashboard
// Standalone management view that shares localStorage with the main site.

const { useState: useAdminState, useEffect: useAdminEffect, useMemo: useAdminMemo } = React;

// Storage keys (match main app)
const LS = {
  spots: 'gg_spots',
  images: 'gg_images',
  favs: 'gg_favs',
  cats: 'gg_categories',
  sponsors: 'gg_sponsors',
  apikey: 'gg_google_key',
};

function loadJSON(key, fallback) {
  try { const r = localStorage.getItem(key); return r ? JSON.parse(r) : fallback; }
  catch { return fallback; }
}
function saveJSON(key, val) {
  try {
    if (val == null) localStorage.removeItem(key);
    else localStorage.setItem(key, JSON.stringify(val));
  } catch {}
}

function fmtBytes(n) {
  if (n < 1024) return n + ' B';
  if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB';
  return (n / 1024 / 1024).toFixed(2) + ' MB';
}
function lsSize(key) {
  try { return new Blob([localStorage.getItem(key) || '']).size; }
  catch { return 0; }
}

// ─── Spot editor ─────────────────────────────────────────────
function SpotEditor({ spot, categories, onChange, onClose }) {
  // Helpers: tolerate either the live API shape (objects with ja/en) or
  // legacy admin shape (plain strings / `blurb`, `addr`).
  const asObj = (v) => {
    if (!v) return { ja: '', en: '' };
    if (typeof v === 'object') return { ja: '', en: '', ...v };
    return { ja: String(v), en: String(v) };
  };
  const [draft, setDraft] = useAdminState(() => ({
    id: spot?.id || `spot-${Date.now().toString(36)}`,
    name: asObj(spot?.name),
    cat: spot?.cat || categories.find((c) => c.id !== 'all')?.id || 'izakaya',
    desc: asObj(spot?.desc || spot?.blurb),
    address: asObj(spot?.address || spot?.addr),
    lat: spot?.coords?.[0] ?? spot?.lat ?? '',
    lng: spot?.coords?.[1] ?? spot?.lng ?? '',
    rating: spot?.rating ?? '',
    price: spot?.price || '',
    walk: spot?.walk ?? '',
    hours: asObj(spot?.hours),
    phone: spot?.phone || '',
    website: spot?.website || '',
    tags: (spot?.tags || []).join(', '),
    image: spot?.image || '',
  }));

  const onPickImg = (file) => {
    if (!file) return;
    const r = new FileReader();
    r.onload = () => setDraft({ ...draft, image: r.result });
    r.readAsDataURL(file);
  };

  const save = () => {
    const lat = draft.lat === '' ? undefined : Number(draft.lat);
    const lng = draft.lng === '' ? undefined : Number(draft.lng);
    const out = {
      ...draft,
      id: draft.id.trim() || `spot-${Date.now().toString(36)}`,
      lat, lng,
      coords: (typeof lat === 'number' && typeof lng === 'number') ? [lat, lng] : (spot?.coords || undefined),
      rating: draft.rating === '' ? undefined : Number(draft.rating),
      walk: draft.walk === '' ? undefined : Number(draft.walk),
      tags: draft.tags.split(',').map((t) => t.trim()).filter(Boolean),
    };
    onChange(out);
    onClose();
  };

  return (
    <div className="adm-modal-bg" onClick={onClose}>
      <div className="adm-modal" onClick={(e) => e.stopPropagation()}>
        <div className="adm-modal-hd">
          <h3>{spot ? 'スポット編集' : 'スポット新規追加'}</h3>
          <button className="adm-x" onClick={onClose}>×</button>
        </div>
        <div className="adm-grid2">
          <label className="adm-thumb-wrap">
            {draft.image ? (
              <div className="adm-thumb" style={{ backgroundImage: `url(${draft.image})` }} />
            ) : (
              <div className="adm-thumb adm-thumb-empty">画像をアップロード</div>
            )}
            <input type="file" accept="image/*" style={{ display: 'none' }}
              onChange={(e) => onPickImg(e.target.files?.[0])} />
          </label>
          <div className="adm-fields">
            <div className="adm-row2">
              <label>ID<input className="adm-input" value={draft.id} onChange={(e) => setDraft({ ...draft, id: e.target.value })} /></label>
              <label>カテゴリ
                <select className="adm-input" value={draft.cat} onChange={(e) => setDraft({ ...draft, cat: e.target.value })}>
                  {categories.filter((c) => c.id !== 'all').map((c) => (
                    <option key={c.id} value={c.id}>{c.icon} {c.label?.ja || c.id}</option>
                  ))}
                </select>
              </label>
            </div>
            <div className="adm-row2">
              <label>名称（日本語）<input className="adm-input" value={draft.name.ja}
                onChange={(e) => setDraft({ ...draft, name: { ...draft.name, ja: e.target.value } })} /></label>
              <label>名称（英語）<input className="adm-input" value={draft.name.en}
                onChange={(e) => setDraft({ ...draft, name: { ...draft.name, en: e.target.value } })} /></label>
            </div>
            <label>説明（日本語）<textarea className="adm-input" rows="2" value={draft.desc.ja}
              onChange={(e) => setDraft({ ...draft, desc: { ...draft.desc, ja: e.target.value } })} /></label>
            <label>説明（英語）<textarea className="adm-input" rows="2" value={draft.desc.en}
              onChange={(e) => setDraft({ ...draft, desc: { ...draft.desc, en: e.target.value } })} /></label>
            <label>住所<input className="adm-input" value={draft.address.ja}
              onChange={(e) => setDraft({ ...draft, address: { ja: e.target.value, en: e.target.value } })} /></label>
            <div className="adm-row3">
              <label>緯度<input className="adm-input" value={draft.lat}
                onChange={(e) => setDraft({ ...draft, lat: e.target.value })} /></label>
              <label>経度<input className="adm-input" value={draft.lng}
                onChange={(e) => setDraft({ ...draft, lng: e.target.value })} /></label>
              <label>駅から徒歩(分)<input className="adm-input" value={draft.walk}
                onChange={(e) => setDraft({ ...draft, walk: e.target.value })} /></label>
            </div>
            <div className="adm-row3">
              <label>評価<input className="adm-input" value={draft.rating}
                onChange={(e) => setDraft({ ...draft, rating: e.target.value })} /></label>
              <label>価格帯<input className="adm-input" value={draft.price} placeholder="¥¥"
                onChange={(e) => setDraft({ ...draft, price: e.target.value })} /></label>
              <label>営業時間<input className="adm-input" value={draft.hours.ja}
                onChange={(e) => setDraft({ ...draft, hours: { ja: e.target.value, en: e.target.value } })} /></label>
            </div>
            <div className="adm-row2">
              <label>電話<input className="adm-input" value={draft.phone}
                onChange={(e) => setDraft({ ...draft, phone: e.target.value })} /></label>
              <label>Webサイト<input className="adm-input" value={draft.website}
                onChange={(e) => setDraft({ ...draft, website: e.target.value })} /></label>
            </div>
            <label>タグ（カンマ区切り）<input className="adm-input" value={draft.tags}
              onChange={(e) => setDraft({ ...draft, tags: e.target.value })} /></label>
          </div>
        </div>
        <div className="adm-modal-actions">
          <button className="btn-primary" onClick={save}>保存</button>
          <button className="btn-secondary" onClick={onClose}>キャンセル</button>
        </div>
      </div>
    </div>
  );
}

// ─── Spots panel ─────────────────────────────────────────────
function AdminSpots({ spots, setSpots, categories, images, setImages }) {
  const [q, setQ] = useAdminState('');
  const [filter, setFilter] = useAdminState('all');
  const [editing, setEditing] = useAdminState(null);
  const [adding, setAdding] = useAdminState(false);
  const [showImport, setShowImport] = useAdminState(false);
  const [pubBusy, setPubBusy] = useAdminState(false);
  const [pubMsg, setPubMsg] = useAdminState('');

  // De-dupe by id, keeping the first occurrence (used for replace mode).
  const dedupeSpots = (list) => {
    const seen = new Set();
    const out = [];
    for (const s of list) {
      const id = s.id;
      if (!id || seen.has(id)) continue;
      seen.add(id);
      out.push(s);
    }
    return out;
  };

  // Append mode: merge existing + incoming by id, INCOMING wins on conflict.
  // Lets a re-import with a different/correct category overwrite stale rows.
  const mergePreferIncoming = (existing, incoming) => {
    const map = new Map();
    for (const s of existing) if (s.id) map.set(s.id, s);
    for (const s of incoming) if (s.id) map.set(s.id, s);
    return Array.from(map.values());
  };

  const onImportApply = (incoming, mode) => {
    const beforeCount = spots.length;
    const next = mode === 'replace'
      ? dedupeSpots(incoming)
      : mergePreferIncoming(spots, incoming);
    const added = Math.max(0, next.length - beforeCount);
    const updated = incoming.length - added;
    setSpots(next);
    setPubMsg(
      mode === 'replace'
        ? `✓ ${incoming.length} 件で置換しました（D1へは「公開」ボタンで反映）`
        : `✓ ${incoming.length} 件のうち 新規 ${added} / 更新 ${updated} 件（D1へは「公開」ボタンで反映）`
    );
    setShowImport(false);
  };

  const publishSpotsToD1 = async () => {
    if (!spots || !spots.length) { setPubMsg('スポットがありません'); return; }
    if (!confirm(`現在のスポット ${spots.length} 件で D1 を置換します。よろしいですか？`)) return;
    setPubBusy(true); setPubMsg('');
    try {
      const res = await fetch('/admin/api/spots-bulk', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ spots, mode: 'replace' }),
      });
      const body = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(body.message || `HTTP ${res.status}`);
      setPubMsg(`✓ スポット ${body.written} 件を D1 に書き込みました`);
    } catch (e) {
      setPubMsg('⚠ 失敗: ' + (e?.message || e));
    } finally {
      setPubBusy(false);
    }
  };

  const filtered = spots.filter((s) => {
    if (filter !== 'all' && s.cat !== filter) return false;
    if (q) {
      const hay = `${s.name?.ja || ''} ${s.name?.en || ''} ${s.id}`.toLowerCase();
      if (!hay.includes(q.toLowerCase())) return false;
    }
    return true;
  });

  const onSave = (next) => {
    const exists = spots.some((s) => s.id === next.id);
    setSpots(exists ? spots.map((s) => (s.id === next.id ? next : s)) : [...spots, next]);
    if (next.image && next.image.startsWith('data:')) {
      setImages({ ...images, [next.id]: next.image });
    }
  };

  const onDelete = (id) => {
    if (!confirm(`スポット "${id}" を削除しますか？`)) return;
    setSpots(spots.filter((s) => s.id !== id));
    const nextImg = { ...images };
    delete nextImg[id];
    setImages(nextImg);
  };

  const onMove = (idx, dir) => {
    const j = idx + dir;
    if (j < 0 || j >= spots.length) return;
    const next = spots.slice();
    [next[idx], next[j]] = [next[j], next[idx]];
    setSpots(next);
  };

  return (
    <div className="adm-panel">
      <div className="adm-toolbar">
        <input className="adm-input adm-search" placeholder="検索..." value={q} onChange={(e) => setQ(e.target.value)} />
        <select className="adm-input" value={filter} onChange={(e) => setFilter(e.target.value)}>
          <option value="all">全カテゴリ</option>
          {categories.filter((c) => c.id !== 'all').map((c) => (
            <option key={c.id} value={c.id}>{c.icon} {c.label?.ja || c.id}</option>
          ))}
        </select>
        <div className="adm-toolbar-spacer" />
        <button className="btn-secondary" onClick={() => setShowImport((v) => !v)}>
          {showImport ? '✕ インポートを閉じる' : '＋ Googleで検索してインポート'}
        </button>
        <button className="btn-primary" onClick={() => setAdding(true)}>＋ 新規追加</button>
        <button className="btn-primary" onClick={publishSpotsToD1} disabled={pubBusy}>
          {pubBusy ? '書き込み中…' : `🚀 D1へ公開 (${spots.length})`}
        </button>
      </div>

      {pubMsg && (
        <div className={pubMsg.startsWith('✓') ? 'adm-saved' : 'adm-err'} style={{ margin: '6px 4px' }}>
          {pubMsg}
        </div>
      )}

      {showImport && (
        <div className="adm-card" style={{ marginBottom: 12 }}>
          <GooglePlacesPanel
            lang="ja"
            categories={categories}
            onApply={onImportApply}
            embedded
          />
        </div>
      )}

      <div className="adm-stats">
        全 {spots.length} 件 / 表示中 {filtered.length} 件 ／ 画像登録 {Object.keys(images).length} 件
      </div>

      <table className="adm-table">
        <thead>
          <tr>
            <th style={{ width: 40 }}></th>
            <th style={{ width: 60 }}>画像</th>
            <th>名称</th>
            <th style={{ width: 100 }}>カテゴリ</th>
            <th style={{ width: 60 }}>評価</th>
            <th style={{ width: 60 }}>価格</th>
            <th style={{ width: 100 }}></th>
          </tr>
        </thead>
        <tbody>
          {filtered.map((s) => {
            const idx = spots.indexOf(s);
            const cat = categories.find((c) => c.id === s.cat);
            const img = images[s.id] || s.image;
            return (
              <tr key={s.id}>
                <td>
                  <div className="adm-mv">
                    <button className="cat-mv-btn" onClick={() => onMove(idx, -1)} disabled={idx === 0}>↑</button>
                    <button className="cat-mv-btn" onClick={() => onMove(idx, 1)} disabled={idx === spots.length - 1}>↓</button>
                  </div>
                </td>
                <td>
                  {img ? (
                    <div className="adm-tbl-thumb" style={{ backgroundImage: `url(${img})` }} />
                  ) : (
                    <div className="adm-tbl-thumb adm-tbl-thumb-empty">—</div>
                  )}
                </td>
                <td>
                  <div className="adm-tbl-name">{s.name?.ja || s.name?.en || s.id}</div>
                  <div className="adm-tbl-sub">{s.name?.en && s.name?.ja ? s.name.en : ''}</div>
                  <div className="adm-tbl-id">{s.id}</div>
                </td>
                <td>{cat ? `${cat.icon} ${cat.label?.ja || cat.id}` : s.cat}</td>
                <td>{s.rating ? `★ ${s.rating}` : '—'}</td>
                <td>{s.price || '—'}</td>
                <td>
                  <div className="adm-tbl-actions">
                    <button className="adm-btn-sm" onClick={() => setEditing(s)}>編集</button>
                    <button className="adm-btn-sm adm-btn-danger" onClick={() => onDelete(s.id)}>削除</button>
                  </div>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>

      {editing && (
        <SpotEditor spot={editing} categories={categories}
          onChange={onSave} onClose={() => setEditing(null)} />
      )}
      {adding && (
        <SpotEditor spot={null} categories={categories}
          onChange={onSave} onClose={() => setAdding(false)} />
      )}
    </div>
  );
}

// ─── Data panel ──────────────────────────────────────────────
function AdminData({ spots, setSpots, images, setImages, categories, setCategories, sponsors, setSponsors }) {
  const [pubBusy, setPubBusy] = useAdminState(false);
  const [pubMsg, setPubMsg] = useAdminState('');

  const publishToD1 = async () => {
    if (!spots || !spots.length) { setPubMsg('スポットがありません'); return; }
    if (!confirm(`現在のスポット ${spots.length} 件で D1 を置換します。よろしいですか？\n（既存の公開データは上書きされます）`)) return;
    setPubBusy(true); setPubMsg('');
    try {
      const res = await fetch('/admin/api/spots-bulk', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ spots, mode: 'replace' }),
      });
      const body = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(body.message || `HTTP ${res.status}`);
      setPubMsg(`✓ スポット ${body.written} 件を D1 に書き込みました`);
    } catch (e) {
      setPubMsg('⚠ 失敗: ' + (e?.message || e));
    } finally {
      setPubBusy(false);
    }
  };

  const [pubSpBusy, setPubSpBusy] = useAdminState(false);
  const [pubSpMsg, setPubSpMsg] = useAdminState('');
  const publishSponsorsToD1 = async () => {
    if (!confirm(`現在のパートナー ${sponsors.length} 件で D1 を置換します。よろしいですか？`)) return;
    setPubSpBusy(true); setPubSpMsg('');
    try {
      const res = await fetch('/admin/api/sponsors-bulk', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ sponsors, mode: 'replace' }),
      });
      const body = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(body.message || `HTTP ${res.status}`);
      setPubSpMsg(`✓ パートナー ${body.written} 件を D1 に書き込みました`);
    } catch (e) {
      setPubSpMsg('⚠ 失敗: ' + (e?.message || e));
    } finally {
      setPubSpBusy(false);
    }
  };

  const sizes = {
    spots: lsSize(LS.spots),
    images: lsSize(LS.images),
    cats: lsSize(LS.cats),
    sponsors: lsSize(LS.sponsors),
  };
  const total = sizes.spots + sizes.images + sizes.cats + sizes.sponsors;

  const exportAll = () => {
    const data = {
      version: 1,
      exportedAt: new Date().toISOString(),
      spots, images, categories, sponsors,
    };
    const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `gotanda-guide-${new Date().toISOString().slice(0, 10)}.json`;
    a.click();
    URL.revokeObjectURL(url);
  };

  const onImportFile = (file) => {
    if (!file) return;
    const r = new FileReader();
    r.onload = () => {
      try {
        const data = JSON.parse(r.result);
        if (data.spots) setSpots(data.spots);
        if (data.images) setImages(data.images);
        if (data.categories) setCategories(data.categories);
        if (data.sponsors) setSponsors(data.sponsors);
        alert('インポートしました');
      } catch (e) {
        alert('JSONの解析に失敗: ' + e.message);
      }
    };
    r.readAsText(file);
  };

  const clearAll = () => {
    if (!confirm('すべてのデータ（スポット・画像・カテゴリ・パートナー）を削除しますか？この操作は元に戻せません。')) return;
    Object.values(LS).forEach((k) => localStorage.removeItem(k));
    setSpots(null);
    setImages({});
    setCategories(window.__GG.CATEGORIES);
    setSponsors([]);
  };

  return (
    <div className="adm-panel">
      <div className="adm-card">
        <h3>ストレージ使用量</h3>
        <table className="adm-table-sm">
          <tbody>
            <tr><td>スポット</td><td>{spots?.length || 0} 件</td><td>{fmtBytes(sizes.spots)}</td></tr>
            <tr><td>画像（埋め込み）</td><td>{Object.keys(images).length} 件</td><td>{fmtBytes(sizes.images)}</td></tr>
            <tr><td>カテゴリ</td><td>{categories.length} 件</td><td>{fmtBytes(sizes.cats)}</td></tr>
            <tr><td>パートナー</td><td>{sponsors.length} 件</td><td>{fmtBytes(sizes.sponsors)}</td></tr>
            <tr className="adm-total"><td><b>合計</b></td><td></td><td><b>{fmtBytes(total)}</b></td></tr>
          </tbody>
        </table>
        <div className="adm-hint">localStorage の上限は通常 5–10MB です。画像が多い場合は外部ストレージへの移行を検討してください。</div>
      </div>

      <div className="adm-card">
        <h3>サーバーに公開 (D1)</h3>
        <p>現在のスポットリストを本番 D1 に書き込みます。書き込み後は <b>/api/spots</b> 経由で全訪問者に配信されます。</p>
        <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
          <button className="btn-primary" onClick={publishToD1} disabled={pubBusy}>
            {pubBusy ? '書き込み中…' : `🚀 スポットを公開 (${spots?.length || 0} 件)`}
          </button>
          {pubMsg && <span className={pubMsg.startsWith('✓') ? 'adm-saved' : 'adm-err'}>{pubMsg}</span>}
        </div>
        <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginTop: 12 }}>
          <button className="btn-primary" onClick={publishSponsorsToD1} disabled={pubSpBusy}>
            {pubSpBusy ? '書き込み中…' : `🚀 パートナーを公開 (${sponsors?.length || 0} 件)`}
          </button>
          {pubSpMsg && <span className={pubSpMsg.startsWith('✓') ? 'adm-saved' : 'adm-err'}>{pubSpMsg}</span>}
        </div>
        <div className="adm-hint">⚠ Cloudflare Access の認証が必要です（このページが見えていれば認証済み）。</div>
      </div>

      <div className="adm-card">
        <h3>バックアップ</h3>
        <p>すべての管理データを JSON ファイルでエクスポート/インポートできます。</p>
        <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
          <button className="btn-primary" onClick={exportAll}>📥 エクスポート (JSON)</button>
          <label className="btn-secondary" style={{ cursor: 'pointer' }}>
            📤 インポート (JSON)
            <input type="file" accept=".json,application/json" style={{ display: 'none' }}
              onChange={(e) => onImportFile(e.target.files?.[0])} />
          </label>
        </div>
      </div>

      <div className="adm-card adm-danger">
        <h3>危険ゾーン</h3>
        <p>すべての管理データを削除します。バックアップを取ってから実行してください。</p>
        <button className="btn-danger" onClick={clearAll}>すべてのデータを削除</button>
      </div>
    </div>
  );
}

// ─── Settings ────────────────────────────────────────────────
function AdminSettings() {
  const [apikey, setApikey] = useAdminState(() => localStorage.getItem(LS.apikey) || '');
  const [saved, setSaved] = useAdminState(false);

  const save = () => {
    if (apikey.trim()) localStorage.setItem(LS.apikey, apikey.trim());
    else localStorage.removeItem(LS.apikey);
    setSaved(true);
    setTimeout(() => setSaved(false), 1500);
  };

  return (
    <div className="adm-panel">
      <div className="adm-card">
        <h3>Google Maps API キー</h3>
        <p>Google Places の店舗検索・口コミ取得に使用されます。Google Cloud Console で <b>Places API</b> を有効化したキーを設定してください。</p>
        <input className="adm-input" type="password" placeholder="AIza..." value={apikey}
          onChange={(e) => setApikey(e.target.value)} />
        <div style={{ marginTop: 10, display: 'flex', gap: 10, alignItems: 'center' }}>
          <button className="btn-primary" onClick={save}>保存</button>
          {saved && <span className="adm-saved">✓ 保存しました</span>}
        </div>
        <div className="adm-hint">
          ⚠️ 本番運用時は Google Cloud Console で <b>HTTP リファラ制限</b> を必ず設定してください。<br />
          このキーはブラウザの localStorage にのみ保存されます。
        </div>
      </div>

      <div className="adm-card">
        <h3>サイト設定</h3>
        <p>メインサイトを別タブで開いて、最終表示を確認できます。</p>
        <a className="btn-secondary" href="/" target="_blank" rel="noopener">
          🔗 メインサイトを開く
        </a>
      </div>
    </div>
  );
}

// ─── Admin App ───────────────────────────────────────────────
function AdminApp() {
  const { SPOTS, CATEGORIES, SPONSORS } = window.__GG;
  const [tab, setTab] = useAdminState('spots');

  const [customSpots, setCustomSpots] = useAdminState(() => loadJSON(LS.spots, null));
  const spots = customSpots || SPOTS;
  const setSpots = (next) => { setCustomSpots(next); saveJSON(LS.spots, next); };

  const [images, setImages] = useAdminState(() => loadJSON(LS.images, {}));
  useAdminEffect(() => { saveJSON(LS.images, images); }, [images]);

  const [categories, setCategories] = useAdminState(() => loadJSON(LS.cats, CATEGORIES));
  useAdminEffect(() => {
    if (categories === CATEGORIES) localStorage.removeItem(LS.cats);
    else saveJSON(LS.cats, categories);
  }, [categories]);

  // Sponsors: start from whatever D1 returned via /api/sponsors, but allow
  // local edits before publishing. Local edits are kept only in React state
  // (no more localStorage write — publishing to D1 is the canonical save).
  const [sponsors, setSponsors] = useAdminState(() => SPONSORS || []);

  const TABS = [
    { id: 'spots',    label: 'スポット',     icon: '◉' },
    { id: 'cats',     label: 'カテゴリ',     icon: '⊞' },
    { id: 'sponsors', label: 'パートナー',   icon: '◇' },
    { id: 'data',     label: 'データ',       icon: '⛁' },
    { id: 'settings', label: '設定',         icon: '⚙' },
  ];

  return (
    <div className="adm-shell">
      <header className="adm-hdr">
        <div className="adm-hdr-brand">
          <span className="adm-logo">G</span>
          <div>
            <div className="adm-hdr-title">Gotanda Guide / 管理ダッシュボード</div>
            <div className="adm-hdr-sub">Admin Console · localStorage</div>
          </div>
        </div>
        <a className="adm-hdr-link" href="/" target="_blank" rel="noopener">
          サイトを表示 ↗
        </a>
      </header>

      <nav className="adm-tabs">
        {TABS.map((t) => (
          <button
            key={t.id}
            className={`adm-tab ${tab === t.id ? 'is-on' : ''}`}
            onClick={() => setTab(t.id)}
          >
            <span className="adm-tab-icon">{t.icon}</span>
            <span>{t.label}</span>
          </button>
        ))}
      </nav>

      <main className="adm-main">
        {tab === 'spots' && (
          <AdminSpots
            spots={spots} setSpots={setSpots}
            categories={categories}
            images={images} setImages={setImages}
          />
        )}
        {tab === 'cats' && (
          <div className="adm-panel">
            <CategoryManager
              lang="ja" categories={categories}
              onChange={(next) => { setCategories(next); saveCategories(next); }}
              onClose={() => {}}
            />
          </div>
        )}
        {tab === 'sponsors' && (
          <div className="adm-panel">
            <SponsorManager
              lang="ja" sponsors={sponsors}
              onChange={(next) => { setSponsors(next); saveSponsors(next); }}
              onClose={() => {}}
            />
          </div>
        )}
        {tab === 'data' && (
          <AdminData
            spots={spots} setSpots={setSpots}
            images={images} setImages={setImages}
            categories={categories} setCategories={setCategories}
            sponsors={sponsors} setSponsors={setSponsors}
          />
        )}
        {tab === 'settings' && <AdminSettings />}
      </main>

      <footer className="adm-foot">
        <span>Gotanda Guide Admin · ローカルストレージのみで動作</span>
      </footer>
    </div>
  );
}

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