// app.jsx — root state, routing, theme. Data layer = API (D1), not localStorage.
(function () {
  const { useState, useEffect, useRef } = React;
  window.PFCtx = React.createContext(null);

  const FONTS = {
    'Zen Kaku': "'Zen Kaku Gothic New', sans-serif",
    'Noto': "'Noto Sans JP', sans-serif",
    'Maru (丸ゴ)': "'Zen Maru Gothic', sans-serif",
    'Rounded': "'M PLUS Rounded 1c', sans-serif",
  };

  const THEMES = {
    'ダーク': { bg: '#0c0b0e', surface: '#171519', surface2: '#221f29', border: 'rgba(255,255,255,0.09)', glass: 'rgba(12,11,14,0.72)', text: '#f4f1f5', sub: '#a6a1b0', faint: '#716c7c' },
    'ピュアブラック': { bg: '#000000', surface: '#0f0f10', surface2: '#1b1b1d', border: 'rgba(255,255,255,0.10)', glass: 'rgba(0,0,0,0.74)', text: '#f6f6f7', sub: '#a3a3a8', faint: '#6e6e75' },
    'ミッドナイト': { bg: '#0a0e16', surface: '#141a26', surface2: '#1d2533', border: 'rgba(255,255,255,0.09)', glass: 'rgba(10,14,22,0.72)', text: '#eef2f8', sub: '#9fa9bd', faint: '#6b7488' },
    'コーヒー': { bg: '#100c0a', surface: '#1c1613', surface2: '#28201b', border: 'rgba(255,255,255,0.08)', glass: 'rgba(16,12,10,0.74)', text: '#f4efe9', sub: '#b0a59a', faint: '#7c7065' },
    'インク': { bg: '#0b0a14', surface: '#161420', surface2: '#211d33', border: 'rgba(255,255,255,0.09)', glass: 'rgba(11,10,20,0.74)', text: '#f0eef8', sub: '#a8a2bd', faint: '#736d88' },
    'フォレスト': { bg: '#08100c', surface: '#121b16', surface2: '#1b2620', border: 'rgba(255,255,255,0.08)', glass: 'rgba(8,16,12,0.74)', text: '#ecf3ee', sub: '#9db3a6', faint: '#6a8074' },
    'ホワイト': { light: true, bg: '#ffffff', surface: '#ffffff', surface2: '#f1eff4', border: 'rgba(0,0,0,0.10)', glass: 'rgba(255,255,255,0.82)', text: '#16131a', sub: '#5d5866', faint: '#9a94a4' },
    'ウォームペーパー': { light: true, bg: '#faf6f0', surface: '#fffdfa', surface2: '#f1eae0', border: 'rgba(60,40,20,0.10)', glass: 'rgba(250,246,240,0.84)', text: '#231d16', sub: '#6b5f50', faint: '#a89b89' },
    'ソフトグレー': { light: true, bg: '#f3f4f6', surface: '#ffffff', surface2: '#e9eaee', border: 'rgba(0,0,0,0.09)', glass: 'rgba(243,244,246,0.84)', text: '#191b21', sub: '#5b6070', faint: '#969bab' },
  };

  const RETRO = { light: true, bg: '#f0e3c6', surface: '#f8efd6', surface2: '#e8d8b4', border: 'rgba(60,42,20,0.20)', glass: 'rgba(240,227,198,0.82)', text: '#2c2316', sub: '#6a5a3f', faint: '#a8956f' };
  const RETRO_ACCENTS = { '朱赤': '#d8442f', '青緑': '#1f897a', '山吹': '#d99a1c', '紺': '#2a4a86' };

  const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
    "style": "アジカン",
    "accent": "#8b5cf6",
    "retroAccent": "朱赤",
    "bgTheme": "ダーク",
    "font": "Zen Kaku",
    "appName": "ぱぱFM",
    "tagline": "そうきくんのパパがすすめる、一日がちょっと良くなる音楽。",
    "density": "comfy"
  }/*EDITMODE-END*/;

  // deterministic hue from an id (for avatar gradients)
  function hueFromId(s) { let h = 0; for (let i = 0; i < (s || '').length; i++) h = (h * 31 + s.charCodeAt(i)) % 360; return h; }
  function relTime(epoch) {
    const d = Math.max(0, Math.floor(Date.now() / 1000) - epoch);
    if (d < 60) return 'たった今';
    if (d < 3600) return Math.floor(d / 60) + '分';
    if (d < 86400) return Math.floor(d / 3600) + '時間';
    return Math.floor(d / 86400) + '日';
  }
  function normAuthor(a) { return { ...a, passed: !!a.passed, papa: a.role === 'admin', color: hueFromId(a.id) }; }
  function normPost(p) {
    return { ...p, time: relTime(p.created_at), author: normAuthor(p.author),
      replies: (p.replies || []).map((r) => ({ ...r, time: relTime(r.created_at), author: normAuthor(r.author) })) };
  }
  function normAlbum(a) {
    return { id: a.id, owner: 'you', title: a.title, desc: a.description || '', service: a.service, playlistUrl: a.playlist_url || '', tracks: a.tracks || [] };
  }

  function App({ onLightChange }) {
    const [t, setTweak] = window.useTweaks(TWEAK_DEFAULTS);

    // ── boot / auth ─────────────────────────────────────────────
    const [boot, setBoot] = useState('loading');   // loading | auth | ready
    const [needsSetup, setNeedsSetup] = useState(false);
    const [me, setMe] = useState(null);
    const initialCode = useRef(new URLSearchParams(location.search).get('code') || '').current;

    // ── data ────────────────────────────────────────────────────
    const [posts, setPosts] = useState([]);
    const [albums, setAlbums] = useState([]);

    // ── transient UI ────────────────────────────────────────────
    const [tab, setTabState] = useState('home');
    const [detailId, setDetailId] = useState(null);
    const [composing, setComposing] = useState(false);
    const [examOpen, setExamOpen] = useState(false);
    const [shinchan, setShinchan] = useState(false);
    const [albumDetail, setAlbumDetail] = useState(null);
    const [albumEditor, setAlbumEditor] = useState(false);
    const [editingAlbum, setEditingAlbum] = useState(null);
    const [adminOpen, setAdminOpen] = useState(false);
    const [toast, setToast] = useState(null);

    // ── playback (simulated) ────────────────────────────────────
    const [nowPlaying, setNowPlaying] = useState(null);
    const [playing, setPlaying] = useState(false);
    const [playPos, setPlayPos] = useState(0);
    useEffect(() => {
      if (!playing || !nowPlaying) return;
      const iv = setInterval(() => setPlayPos((p) => { if (p + 1 >= nowPlaying._dur) { setPlaying(false); return nowPlaying._dur; } return p + 1; }), 1000);
      return () => clearInterval(iv);
    }, [playing, nowPlaying]);

    function showToast(msg) { setToast(msg); clearTimeout(showToast._t); showToast._t = setTimeout(() => setToast(null), 1900); }

    // ── loaders ─────────────────────────────────────────────────
    const loadFeed = async () => { const r = await window.PFApi.get('/feed'); setPosts(r.posts.map(normPost)); };
    const loadAlbums = async () => { const r = await window.PFApi.get('/albums'); setAlbums(r.albums.map(normAlbum)); };
    const refreshMe = async () => { const r = await window.PFApi.get('/me'); setMe(r.user); return r.user; };

    const enterApp = async (user) => {
      setMe(user);
      await Promise.all([loadFeed(), loadAlbums()]);
      setBoot('ready');
    };

    useEffect(() => {
      (async () => {
        let bs = { needs_setup: false };
        try { bs = await window.PFApi.get('/bootstrap'); } catch {}
        setNeedsSetup(bs.needs_setup);
        if (bs.needs_setup) { setBoot('auth'); return; }
        try { const r = await window.PFApi.get('/me'); await enterApp(r.user); }
        catch { setBoot('auth'); }
      })();
    }, []);

    // 401 guard for actions
    const guard = (fn) => async (...a) => {
      try { return await fn(...a); }
      catch (e) { if (e.status === 401) { setMe(null); setBoot('auth'); } else throw e; }
    };

    // ── theme ───────────────────────────────────────────────────
    const font = FONTS[t.font] || FONTS['Zen Kaku'];
    const retro = t.style === 'アジカン';
    let pal, displayFont, accent, coverStyle;
    if (retro) { pal = RETRO; displayFont = "'Zen Antique', 'Shippori Mincho', serif"; accent = RETRO_ACCENTS[t.retroAccent] || RETRO_ACCENTS['朱赤']; coverStyle = 'retro'; }
    else { pal = THEMES[t.bgTheme] || THEMES['ダーク']; displayFont = font; accent = t.accent; coverStyle = 'vinyl'; }
    const theme = { ...pal, accent, retro, coverStyle, dense: t.density === 'compact', body: font, display: displayFont };
    useEffect(() => { onLightChange && onLightChange(!!pal.light); }, [pal.light]);

    // ── auth gate / loading ─────────────────────────────────────
    if (boot === 'loading') {
      return <div style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', background: theme.bg, color: theme.faint }}>…</div>;
    }
    if (boot === 'auth') {
      return <window.AuthGate theme={theme} needsSetup={needsSetup} initialCode={initialCode} onAuthed={(u) => { history.replaceState({}, '', location.pathname); enterApp(u); }} />;
    }

    // ── me view + users map ─────────────────────────────────────
    const meView = { id: me.id, name: me.name, handle: me.handle, role: me.role, passed: !!me.passed, papa: me.role === 'admin', color: hueFromId(me.id) };
    const users = { you: meView };

    const findItem = (id) => {
      for (const p of posts) { if (p.id === id) return p; for (const r of p.replies) if (r.id === id) return r; }
      return null;
    };

    // ── actions ─────────────────────────────────────────────────
    const app = {
      theme, users, me: meView, user: me, trackById: window.PF.trackById,
      posts, albums, nowPlaying, playing, playPos, tab, detailId, editingAlbum,
      copy: { appName: t.appName, tagline: t.tagline },

      setTab(k) { setTabState(k); setDetailId(null); setComposing(false); setExamOpen(false); setShinchan(false); setAlbumDetail(null); setAlbumEditor(false); setAdminOpen(false); scrollTop(); },
      go(name, params) { if (name === 'detail') { setDetailId(params.id); scrollTop(); } },
      back() { setDetailId(null); },
      compose() { if (me.passed) setComposing(true); else setShinchan(true); },
      closeCompose() { setComposing(false); },
      startExam() { setExamOpen(true); },
      closeExam() { setExamOpen(false); },
      closeShinchan() { setShinchan(false); },
      openAdmin() { setAdminOpen(true); },
      closeAdmin() { setAdminOpen(false); },
      refreshMe,

      async logout() { try { await window.PFApi.post('/logout', {}); } catch {} setMe(null); setBoot('auth'); },

      // albums
      openAlbumDetail(id) { setAlbumDetail(id); },
      closeAlbumDetail() { setAlbumDetail(null); },
      openAlbumEditor(album) { setEditingAlbum(album || null); setAlbumEditor(true); },
      closeAlbumEditor() { setAlbumEditor(false); setEditingAlbum(null); },
      saveAlbum: guard(async (album) => {
        const payload = { title: album.title, description: album.desc, service: album.service, playlist_url: album.playlistUrl, tracks: album.tracks };
        let saved;
        if (album.id && albums.some((a) => a.id === album.id)) saved = (await window.PFApi.put('/albums/' + album.id, payload)).album;
        else saved = (await window.PFApi.post('/albums', payload)).album;
        await loadAlbums();
        setAlbumEditor(false); setEditingAlbum(null); setAlbumDetail(saved.id);
        showToast(album.id && albums.some((a) => a.id === album.id) ? 'アルバムを更新しました' : 'アルバムを作成しました 🎵');
      }),

      isLiked(id) { const it = findItem(id); return it ? !!it.liked : false; },
      likeCount(obj) { return obj.likes || 0; },
      like: guard(async (id, type = 'post') => {
        // optimistic
        setPosts((ps) => ps.map((p) => {
          if (p.id === id) return { ...p, liked: !p.liked, likes: p.likes + (p.liked ? -1 : 1) };
          return { ...p, replies: p.replies.map((r) => r.id === id ? { ...r, liked: !r.liked, likes: r.likes + (r.liked ? -1 : 1) } : r) };
        }));
        try { await window.PFApi.post('/likes', { target_id: id, target_type: type }); }
        catch (e) { await loadFeed(); throw e; }
      }),

      addReply: guard(async (postId, text) => { await window.PFApi.post('/posts/' + postId + '/replies', { text }); await loadFeed(); showToast('リプを送信しました'); }),
      addPost: guard(async (trackId, text) => { await window.PFApi.post('/posts', { track_id: trackId, text }); await loadFeed(); showToast('投稿しました 🎵'); }),
      createInvite: guard(async () => { const r = await window.PFApi.post('/invites', {}); await refreshMe(); return r; }),

      play(track) { if (nowPlaying && nowPlaying.id === track.id) { setPlaying((p) => !p); return; } setNowPlaying({ ...track, _dur: 196 }); setPlayPos(0); setPlaying(true); },
      togglePlay() { setPlaying((p) => !p); },
      stop() { setNowPlaying(null); setPlaying(false); setPlayPos(0); },
      share() { showToast('リンクをコピーしました'); },
    };

    function scrollTop() { const el = document.getElementById('pf-scroll'); if (el) el.scrollTop = 0; }

    let screen;
    if (detailId) screen = <window.DetailScreen app={app} postId={detailId} />;
    else if (tab === 'home') screen = <window.HomeScreen app={app} />;
    else if (tab === 'profile') screen = <window.ProfileScreen app={app} />;

    const overlayOpen = composing || examOpen || albumEditor || albumDetail || adminOpen;
    const showTab = !detailId && !overlayOpen;
    const showMini = !detailId && !overlayOpen && nowPlaying;

    return (
      <window.PFCtx.Provider value={app}>
        <div style={{ height: '100%', position: 'relative', overflow: 'hidden', background: theme.bg, color: theme.text, fontFamily: theme.body }}>
          <div id="pf-scroll" style={{ position: 'absolute', inset: 0, overflowY: 'auto', overflowX: 'hidden', paddingBottom: showTab ? 96 : 0 }}>
            {screen}
          </div>

          {showMini && <window.MiniPlayer />}
          {showTab && <window.TabBar />}
          {detailId && <window.ReplyBar app={app} postId={detailId} />}
          {composing && <window.ComposeScreen app={app} />}
          {examOpen && <ExamOverlay app={app} />}
          {shinchan && <window.ShinchanPrompt app={app} />}
          {albumDetail && <window.AlbumDetail app={app} albumId={albumDetail} />}
          {albumEditor && <window.AlbumEditor app={app} />}
          {adminOpen && <window.AdminScreen app={app} />}
          {theme.retro && <div className="pf-grain" />}

          {toast && (
            <div style={{ position: 'absolute', left: '50%', bottom: showTab ? 110 : 60, transform: 'translateX(-50%)', background: theme.text, color: theme.bg, fontWeight: 700, fontSize: 13.5, padding: '10px 18px', borderRadius: 22, zIndex: 95, boxShadow: '0 8px 24px rgba(0,0,0,0.4)', whiteSpace: 'nowrap', maxWidth: '90%' }}>{toast}</div>
          )}

          <Tweaks t={t} setTweak={setTweak} />
        </div>
      </window.PFCtx.Provider>
    );
  }

  function ExamOverlay({ app }) {
    const T = app.theme;
    return (
      <div style={{ position: 'absolute', inset: 0, background: T.bg, zIndex: 82, display: 'flex', flexDirection: 'column' }}>
        <div style={{ paddingTop: 54, paddingBottom: 10, borderBottom: `1px solid ${T.border}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '54px 14px 10px' }}>
          <button onClick={() => app.closeExam()} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 4, color: T.text, display: 'flex' }}><window.Icon.close size={24} /></button>
          <span style={{ fontWeight: 800, color: T.text }}>そうきくんのパパの審査</span>
          <span style={{ width: 32 }}></span>
        </div>
        <div style={{ flex: 1, overflowY: 'auto' }}><window.ExamScreen app={app} /></div>
      </div>
    );
  }

  function Tweaks({ t, setTweak }) {
    const { TweaksPanel, TweakSection, TweakColor, TweakRadio, TweakSelect, TweakText } = window;
    const retro = t.style === 'アジカン';
    return (
      <TweaksPanel>
        <TweakSection label="スタイル" />
        <TweakRadio label="ルック" value={t.style} options={['モダン', 'アジカン']} onChange={(v) => setTweak('style', v)} />
        <TweakSection label="ブランド" />
        {retro ? (
          <TweakRadio label="アクセント" value={t.retroAccent} options={['朱赤', '青緑', '山吹', '紺']} onChange={(v) => setTweak('retroAccent', v)} />
        ) : (
          <TweakColor label="アクセント" value={t.accent} options={['#8b5cf6', '#ff5a4d', '#1DB954', '#2A6FDB', '#f0a500']} onChange={(v) => setTweak('accent', v)} />
        )}
        <TweakText label="アプリ名" value={t.appName} onChange={(v) => setTweak('appName', v)} />
        <TweakText label="タグライン" value={t.tagline} onChange={(v) => setTweak('tagline', v)} />
        {!retro && (<>
          <TweakSection label="背景" />
          <TweakSelect label="背景色" value={t.bgTheme} options={['ダーク', 'ピュアブラック', 'ミッドナイト', 'コーヒー', 'インク', 'フォレスト', 'ホワイト', 'ウォームペーパー', 'ソフトグレー']} onChange={(v) => setTweak('bgTheme', v)} />
        </>)}
        <TweakSection label="タイポ" />
        <TweakRadio label="フォント" value={t.font} options={['Zen Kaku', 'Noto', 'Maru (丸ゴ)', 'Rounded']} onChange={(v) => setTweak('font', v)} />
        <TweakSection label="レイアウト" />
        <TweakRadio label="密度" value={t.density} options={['comfy', 'compact']} onChange={(v) => setTweak('density', v)} />
      </TweaksPanel>
    );
  }

  window.PFApp = App;
})();
