// hub-app.jsx — Saya chat drawer + main App composition (NAP 7 live-data).
//
// The feed is now DB-backed: App fetches GET /api/profile/showrunner-zero/posts
// on mount, maps rows via mapApiPost, and renders them. The csapó (clap) is
// real + persistent — it hits POST /api/posts/:id/like with the Supabase
// session JWT (same pattern as src/profile/app.jsx) and the count lives in
// post_likes / profile_posts.likes_count.
//
// Still hard-coded / static (by design at launch):
//   • The Tweaks values (calm / float / regular / light-gold) — validated.
//   • The "Fókuszban" showcase (FOCUS) and the 4 section stats (SECTIONS).
//   • Saya chat replies (canned, living-fiction teaser).
//
// IVÁN-PAIR / next-chunk seams (still inert): identity badge tier-logic,
// top-nav routing + redirect + tier-gating, the follow ("action") button +
// real KÖVETÉSEK count, and the 1 € PULSE Stripe download.

const { useState: uS, useRef: uR, useEffect: uE } = React;

// ── Supabase session (JWT for the clap endpoint) ───────────────────
// Mirrors src/profile/app.jsx — the SDK (loaded in hub.html) refreshes the
// token in localStorage; we read it lazily and only to authorize /like.
const SUPABASE_URL = 'https://wrzzxxepdwgpwuqdbnyt.supabase.co';
const SUPABASE_PUBLISHABLE_KEY = 'sb_publishable_Egsk8yxTgWNhCiJ_Vht16A_pXNjM9wF';
const SUPABASE_AUTH_STORAGE_KEY = 'sb-wrzzxxepdwgpwuqdbnyt-auth-token';
const SHOWRUNNER_SLUG = 'showrunner-zero';

let _sb = null;
function getSupabaseClient() {
  if (_sb) return _sb;
  if (typeof window === 'undefined' || !window.supabase || !window.supabase.createClient) return null;
  _sb = window.supabase.createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY, {
    auth: { persistSession: true, autoRefreshToken: true, detectSessionInUrl: false, storageKey: SUPABASE_AUTH_STORAGE_KEY },
  });
  return _sb;
}
async function getStoredJWT() {
  const sb = getSupabaseClient();
  if (sb) {
    try {
      const { data } = await sb.auth.getSession();
      if (data && data.session && data.session.access_token) return data.session.access_token;
    } catch { /* fall through */ }
  }
  try {
    const raw = window.localStorage.getItem(SUPABASE_AUTH_STORAGE_KEY);
    if (!raw) return null;
    const parsed = JSON.parse(raw);
    return (parsed && parsed.access_token) || null;
  } catch { return null; }
}

// Current logged-in user (id) from the local session — no extra network.
async function getSessionUserId() {
  const sb = getSupabaseClient();
  if (!sb) return null;
  try {
    const { data } = await sb.auth.getSession();
    return (data && data.session && data.session.user && data.session.user.id) || null;
  } catch { return null; }
}

/* ---------- Saya chat drawer (canned replies) ---------- */
function ChatDrawer({ open, onClose }) {
  const [log, setLog] = uS([
    { who: 'them', text: 'Itt vagyok a sminkben. A tenger holnap. Mit szeretnél kérdezni, mielőtt lemerülök?' },
  ]);
  const [val, setVal] = uS('');
  const [typing, setTyping] = uS(false);
  const logRef = uR(null);
  const replyIdx = uR(0);

  uE(() => { if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight; }, [log, typing]);

  function send() {
    const text = val.trim();
    if (!text) return;
    setLog((l) => [...l, { who: 'me', text }]);
    setVal('');
    setTyping(true);
    setTimeout(() => {
      setTyping(false);
      const r = SAYA_REPLIES[replyIdx.current % SAYA_REPLIES.length];
      replyIdx.current += 1;
      setLog((l) => [...l, { who: 'them', text: r }]);
    }, 1100 + Math.random() * 700);
  }

  return (
    <>
      <div className={`scrim-full ${open ? 'open' : ''}`} onClick={onClose} />
      <aside className={`drawer ${open ? 'open' : ''}`} aria-hidden={!open}>
        <div className="drawer-head">
          <Avatar data={{ img: '/assets/hub/saya.png' }} size={40} cls="pavatar" />
          <div>
            <div style={{ fontFamily: 'var(--sans)', fontSize: 14, fontWeight: 600, color: '#ece8e1' }}>SAYA</div>
            <div style={{ marginTop: 3 }}><Badge tier="character" live override={`${t('live')} — LIVING FICTION`} /></div>
          </div>
          <button className="drawer-x" onClick={onClose}>×</button>
        </div>
        <div className="chat-log" ref={logRef}>
          {log.map((m, i) => <div key={i} className={`msg ${m.who}`}>{m.text}</div>)}
          {typing && <div className="msg-typing"><i/><i/><i/></div>}
        </div>
        <div className="chat-input">
          <input value={val} placeholder={t('chat_placeholder')}
                 onChange={(e) => setVal(e.target.value)}
                 onKeyDown={(e) => e.key === 'Enter' && send()} />
          <button className="send" onClick={send}>↑</button>
        </div>
      </aside>
    </>
  );
}

/* ---------- validated config (was the Tweaks panel) ---------- */
const HUB_CONFIG = { background: 'calm', cards: 'float', density: 'regular' };

/* ---------- App ---------- */
function App() {
  const [filter, setFilter] = uS('all');
  const [section, setSection] = uS(null);
  const [chat, setChat] = uS(false);
  const [posts, setPosts] = uS([]);
  const [status, setStatus] = uS('loading');   // 'loading' | 'ready' | 'error'
  const [meId, setMeId] = uS(null);             // logged-in user id (or null)
  const [authState, setAuthState] = uS('checking');  // 'checking' | 'in' | 'out' — gates anon visitors
  const [following, setFollowing] = uS(() => new Set());  // Set of followable_id I follow
  const [followCount, setFollowCount] = uS(null);         // real KÖVETÉSEK count
  const [followBusy, setFollowBusy] = uS(null);           // uuid being toggled
  const [expandId, setExpandId] = uS(null);               // post to expand (from a Fókuszban card)
  const [selfAvatarUrl, setSelfAvatarUrl] = uS(null);     // the showrunner's real photo (profile-info)
  const [lang, setLang] = uS(getHubLang());               // chrome language (shared aip_lang)
  const [acctOpen, setAcctOpen] = uS(false);              // top-right account menu (sign out)

  // Switch chrome language: persist to the shared aip_lang key + re-render.
  function changeLang(l) {
    if (!HUB_LANGS.includes(l)) return;
    try { localStorage.setItem('aip_lang', l); } catch (_) {}
    setLang(l);
  }

  // Close the top-right account menu on Escape / outside-click.
  uE(() => {
    if (!acctOpen) return;
    const onKey = (e) => { if (e.key === 'Escape') setAcctOpen(false); };
    const onDocClick = () => setAcctOpen(false);
    document.addEventListener('keydown', onKey);
    document.addEventListener('click', onDocClick);
    return () => { document.removeEventListener('keydown', onKey); document.removeEventListener('click', onDocClick); };
  }, [acctOpen]);

  // Fetch the showrunner's avatar (real photo) → header + showrunner-byline.
  // Falls back to the AS monogram if none / fetch fails.
  uE(() => {
    let alive = true;
    (async () => {
      try {
        const res = await fetch(`/api/profile/${SHOWRUNNER_SLUG}/profile-info`);
        const data = await res.json().catch(() => ({}));
        if (alive && res.ok && data.avatar_url) setSelfAvatarUrl(data.avatar_url);
      } catch { /* monogram fallback */ }
    })();
    return () => { alive = false; };
  }, []);

  // Fókuszban card → scroll to that post in the feed + expand its full text.
  function handleFocusPick(id) {
    setFilter('all');          // ensure the post isn't filtered out
    setExpandId(id);
    requestAnimationFrame(() => requestAnimationFrame(() => {
      const el = document.getElementById(`hubpost-${id}`);
      if (el && el.scrollIntoView) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }));
  }

  // Fetch the live feed on mount.
  uE(() => {
    let alive = true;
    (async () => {
      try {
        const res = await fetch(`/api/profile/${SHOWRUNNER_SLUG}/posts`);
        const data = await res.json().catch(() => ({}));
        if (!res.ok) throw new Error(data.detail || data.error || `http_${res.status}`);
        if (!alive) return;
        setPosts((data.posts || []).map(mapApiPost));
        setStatus('ready');
      } catch (_err) {
        if (alive) setStatus('error');
      }
    })();
    return () => { alive = false; };
  }, []);

  // Load session + my follows (for the follow buttons + KÖVETÉSEK count).
  uE(() => {
    let alive = true;
    (async () => {
      const id = await getSessionUserId();
      if (!alive) return;
      setMeId(id);
      setAuthState(id ? 'in' : 'out');       // resolve the anon-gate
      if (!id) return;                       // logged out → leave SECTIONS mock count
      try {
        const jwt = await getStoredJWT();
        if (!jwt) return;
        const res = await fetch('/api/follows', { headers: { Authorization: `Bearer ${jwt}` } });
        const data = await res.json().catch(() => ({}));
        if (!res.ok || !alive) return;
        setFollowing(new Set((data.following || []).map((f) => f.followable_id)));
        if (typeof data.following_count === 'number') setFollowCount(data.following_count);
      } catch { /* non-blocking */ }
    })();
    return () => { alive = false; };
  }, []);

  // Toggle follow for any followable (showrunner / agent / character / narrator).
  // Called by each post's ACTION button with the post's author target.
  async function toggleFollow(followType, followId) {
    if (!followType || !followId || followBusy === followId) return;
    const jwt = await getStoredJWT();
    if (!jwt) { window.location.href = '/auth?next=/hub'; return; }
    const wasFollowing = following.has(followId);
    // optimistic
    setFollowBusy(followId);
    setFollowing((s) => { const n = new Set(s); wasFollowing ? n.delete(followId) : n.add(followId); return n; });
    setFollowCount((c) => (c == null ? c : Math.max(0, c + (wasFollowing ? -1 : 1))));
    try {
      const res = await fetch('/api/follows', {
        method: 'POST',
        headers: { Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },
        body: JSON.stringify({ followable_type: followType, followable_id: followId }),
      });
      if (res.status === 401) { window.location.href = '/auth?next=/hub'; return; }
      const data = await res.json().catch(() => ({}));
      if (!res.ok) throw new Error(data.detail || data.error || `http_${res.status}`);
      setFollowing((s) => { const n = new Set(s); data.following ? n.add(followId) : n.delete(followId); return n; });
    } catch (_err) {
      // revert
      setFollowing((s) => { const n = new Set(s); wasFollowing ? n.add(followId) : n.delete(followId); return n; });
      setFollowCount((c) => (c == null ? c : Math.max(0, c + (wasFollowing ? 1 : -1))));
    } finally {
      setFollowBusy(null);
    }
  }

  // Real, persistent clap. Returns the server's {liked, likes_count}; throws
  // on auth/other failure so the card reverts its optimistic update.
  async function handleClap(postId, _nextLiked) {
    const jwt = await getStoredJWT();
    if (!jwt) {
      // Not logged in → send them to auth (clap is JWT-gated).
      window.location.href = '/auth?next=/hub';
      throw new Error('auth_required');
    }
    const res = await fetch(`/api/posts/${postId}/like`, {
      method: 'POST',
      headers: { Authorization: `Bearer ${jwt}` },
    });
    const data = await res.json().catch(() => ({}));
    if (res.status === 401) { window.location.href = '/auth?next=/hub'; throw new Error('auth_required'); }
    if (!res.ok) throw new Error(data.detail || data.error || `http_${res.status}`);
    return { liked: !!data.liked, likes_count: typeof data.likes_count === 'number' ? data.likes_count : undefined };
  }

  // Inject the showrunner's real photo into their own (byline) posts.
  const enriched = selfAvatarUrl
    ? posts.map((p) => (p.followType === 'showrunner' ? { ...p, avatar: { img: selfAvatarUrl } } : p))
    : posts;
  const visible = filter === 'all' ? enriched : enriched.filter((p) => p.filter === filter);

  // Make the current language visible to all child t() calls in this render.
  applyHubLang(lang);

  return (
    <div className="hub-scroll">
      {/* Phase 2: living atmospheric background (calm; respects reduced-motion). */}
      <LivingBackground intensity={HUB_CONFIG.background} motion={HUB_CONFIG.background !== 'static'} />

      {/* top bar — nav buttons inert (IVÁN-PAIR: routing + redirect + tier-gating) */}
      <header className="topbar">
        <div className="brand" role="link" tabIndex={0} title="AI Prismatik"
             onClick={() => { window.location.href = '/'; }}
             onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); window.location.href = '/'; } }}>
          <img className="brand-logo" src="/assets/hub/logo-mark.png" alt="AI Prismatik" />
          <span className="brand-name">AI PRISMATIK</span>
        </div>
        <nav className="topnav">
          <button className="on">HUB</button>
          <button onClick={() => { window.location.href = '/studio'; }}>{t('nav_studio')}</button>
          <button onClick={() => { window.location.href = '/dashboard'; }}>{t('nav_dashboard')}</button>
          <button onClick={() => { window.location.href = '/handbook'; }}>{t('nav_handbook')}</button>
          <button onClick={() => { window.location.href = '/pricing'; }}>{t('nav_pricing')}</button>
        </nav>
        <div className="top-right">
          <div className="lang">
            {HUB_LANGS.map((l) => (
              <span key={l} className={lang === l ? 'on' : ''} onClick={() => changeLang(l)}>{l.toUpperCase()}</span>
            ))}
          </div>
          <div className="acct-wrap" onClick={(e) => e.stopPropagation()}>
            <button className="avatar-sm" aria-haspopup="menu" aria-expanded={acctOpen}
                    onClick={() => setAcctOpen((o) => !o)}>AS</button>
            {acctOpen && (
              <div className="acct-menu" role="menu">
                <button role="menuitem" onClick={() => { window.location.href = '/profile/showrunner-zero'; }}>{t('photo_menu_profile')}</button>
                <button role="menuitem" className="signout" onClick={aipHubSignOut}>{t('photo_menu_signout')}</button>
              </div>
            )}
          </div>
        </div>
      </header>

      <main className="col" data-cards={HUB_CONFIG.cards} data-density={HUB_CONFIG.density}>
        <Identity avatarUrl={selfAvatarUrl} />

        <div className="divider">
          <span className="glyph">◆</span>
          <span className="label">{t('div_focus')}</span>
          <span className="rule" />
        </div>
        <Focus posts={posts} onPick={handleFocusPick} />

        {/* Stat numbers sit BELOW the Fókuszban cards (more credible than
            floating above the showcase). */}
        <Sections active={section} onPick={(k) => setSection((s) => s === k ? null : k)}
                  counts={followCount != null ? { kovetesek: followCount } : null} />

        <div className="divider">
          <span className="glyph">●</span>
          <span className="label">{t('div_feeds')}</span>
          <span className="rule" />
          <span className="count">{status === 'ready' ? `${visible.length} ${t('posts_word')}` : ''}</span>
        </div>

        <div className="filters">
          {FILTERS.map((f) => (
            <button key={f.key} className={`chip ${filter === f.key ? 'on' : ''}`}
                    onClick={() => setFilter(f.key)}>{filterLabel(f.key)}</button>
          ))}
        </div>

        {status === 'loading' && (
          <div className="feed-state">{t('state_loading')}</div>
        )}
        {status === 'error' && (
          <div className="feed-state">{t('state_error')}</div>
        )}
        {status === 'ready' && visible.map((p) => (
          <Post key={p.id} post={p} onChat={() => setChat(true)} onClap={handleClap}
                forceExpand={p.id === expandId}
                onFollow={toggleFollow} following={following} followBusy={followBusy} meId={meId} />
        ))}
        {status === 'ready' && visible.length === 0 && (
          <div className="feed-state">{t('state_empty')}</div>
        )}
      </main>

      <ChatDrawer open={chat} onClose={() => setChat(false)} />

      {/* Anon gate: the bustling feed loads behind, blurred; a centred modal
          invites sign-up. Logged-in users never see it. The feed itself is
          public curated content — this is a visual gate, not the security
          boundary (that's the server-side JWT on the protected endpoints). */}
      {authState === 'out' && (
        <div className="hub-gate" role="dialog" aria-modal="true">
          <div className="hub-gate-modal">
            <div className="hub-gate-kicker"><span className="live-dot" /> {t('live')}</div>
            <h2 className="hub-gate-title">{t('gate_title')}</h2>
            <p className="hub-gate-body">{t('gate_body')}</p>
            <button className="hub-gate-cta" onClick={() => { window.location.href = '/auth?next=/hub'; }}>
              {t('gate_cta')}
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
