// hub-components.jsx — building blocks for the Profil-Hub.

const { useState, useRef, useEffect } = React;

/* ---------- tier badge ---------- */
function Badge({ tier, live, override }) {
  const d = TIERS[tier];
  if (!d) return null;
  return (
    <span className={`badge ${d.cls} ${live ? 'live' : ''}`}>
      {live && <span className="dot" />}
      {override || tierLabel(tier)}
    </span>
  );
}

/* ---------- avatar ---------- */
function Avatar({ data, size = 34, cls = 'pavatar' }) {
  if (data.img) {
    return <div className={cls} style={{ width: size, height: size, backgroundImage: `url(${data.img})` }} />;
  }
  return (
    <div className={cls} style={{ width: size, height: size, background: data.bg, color: data.color }}>
      {data.mono}
    </div>
  );
}

/* ---------- clean sign-out ---------- */
// Revoke the Supabase session (the SDK clears its own persisted token), wipe the
// remaining client-side session flags, and return to the landing — where the
// anon blur+gate then shows the logged-out state. getSupabaseClient lives in
// hub-app.jsx (loaded after this module; resolved at click-time).
async function aipHubSignOut() {
  try {
    const sb = (typeof getSupabaseClient === 'function') ? getSupabaseClient() : null;
    if (sb) await sb.auth.signOut();
  } catch (_) { /* fall through to the manual wipe */ }
  try {
    localStorage.removeItem('sb-wrzzxxepdwgpwuqdbnyt-auth-token');
    localStorage.removeItem('aip_user_email');
    localStorage.removeItem('aip_username_known');
    localStorage.removeItem('aip_admin_pass');
  } catch (_) {}
  try {
    sessionStorage.removeItem('aip_greeting_shown');
    sessionStorage.removeItem('aip_last_login_pinged');
  } catch (_) {}
  window.location.href = '/';
}

/* ---------- identity header ---------- */
// Clicking the profile photo opens a small chooser: open the profile, or zoom
// the photo in a lightbox. (Sign out lives in the top-right account menu.)
// Outside-click / Escape dismiss both.
function Identity({ avatarUrl }) {
  const [menuOpen, setMenuOpen] = useState(false);
  const [zoom, setZoom] = useState(false);

  useEffect(() => {
    if (!menuOpen && !zoom) return;
    const onKey = (e) => { if (e.key === 'Escape') { setMenuOpen(false); setZoom(false); } };
    const onDocClick = () => setMenuOpen(false);
    document.addEventListener('keydown', onKey);
    document.addEventListener('click', onDocClick);
    return () => { document.removeEventListener('keydown', onKey); document.removeEventListener('click', onDocClick); };
  }, [menuOpen, zoom]);

  return (
    <div className="identity">
      <div className="avatar-wrap" onClick={(e) => e.stopPropagation()}>
        <button className="avatar-lg" aria-haspopup="menu" aria-expanded={menuOpen}
                onClick={() => setMenuOpen((o) => !o)}>
          {avatarUrl
            ? <img src={avatarUrl} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', borderRadius: '50%', display: 'block' }} />
            : 'AS'}
        </button>
        {menuOpen && (
          <div className="avatar-menu" role="menu">
            <button role="menuitem" onClick={() => { window.location.href = '/profile/showrunner-zero'; }}>{t('photo_menu_profile')}</button>
            <button role="menuitem" onClick={() => { setMenuOpen(false); setZoom(true); }}>{t('photo_menu_zoom')}</button>
          </div>
        )}
      </div>
      {zoom && ReactDOM.createPortal(
        // Portal to <body> so the fixed overlay is relative to the VIEWPORT,
        // not the backdrop-filtered .identity (which would be its containing
        // block and let the image burst out / overflow the screen).
        <div className="avatar-lightbox" role="dialog" aria-modal="true" onClick={() => setZoom(false)}>
          {avatarUrl
            ? <img src={avatarUrl} alt="" className="avatar-lightbox-img" />
            : <div className="avatar-lightbox-mono">AS</div>}
        </div>,
        document.body
      )}
      <div className="identity-main">
        <h1 className="identity-name">Akos Simonkovits</h1>
        <div className="identity-meta">
          <Badge tier="showrunner" live override={`SHOWRUNNER ZERO · ${t('live')}`} />
          <span className="handle">@showrunnerzero · {t('in_studio')}</span>
        </div>
      </div>
      <div className="identity-actions">
        <button className="ghost" onClick={() => { window.location.href = '/profile/showrunner-zero'; }}>{t('btn_profile')}</button>
        <button className="ghost" onClick={() => { window.location.href = '/studio'; }}>{t('btn_studio')}</button>
      </div>
    </div>
  );
}

/* ---------- 4 fixed sections ---------- */
// `counts` (optional) overrides a stat's number by key — used to show the
// real KÖVETÉSEK (follow) count once it's fetched.
function Sections({ active, onPick, counts }) {
  return (
    <div className="stats">
      {SECTIONS.map((s) => {
        const num = counts && counts[s.key] != null ? counts[s.key] : s.num;
        return (
          <div key={s.key}
               className={`stat ${active === s.key ? 'on' : ''}`}
               onClick={() => onPick(s.key)}>
            <div className="stat-label">{t('sec_' + s.key + '_label')}</div>
            <div className="stat-num">{num}</div>
            <div className="stat-sub">{t('sec_' + s.key + '_sub')}</div>
          </div>
        );
      })}
    </div>
  );
}

/* ---------- focus / showcase — 3 compact cards ---------- */
// Cards 1-2: the two strongest image posts from the live feed; click → scroll
// to + expand that post (onPick). Card 3: STUDIO LIVE (SIROB) — visual/inviting,
// not a working live stream.
function Focus({ posts, onPick }) {
  const imgPosts = (posts || []).filter((p) => p.media && p.media.type === 'image');
  const cards = [imgPosts[0] || null, imgPosts[1] || null];
  return (
    <div className="focus-strip">
      {cards.map((p, i) => (
        p ? (
          <button key={p.id} className="focus-card"
                  style={{ backgroundImage: `url(${p.media.src})` }}
                  onClick={() => onPick && onPick(p.id)}
                  title={p.media.title || p.name}>
            <span className="focus-card-scrim" />
            <span className="focus-card-kicker">{p.media.kicker || (p.tier === 'character' ? t('focus_scene') : t('focus_default'))}</span>
            <span className="focus-card-title">{p.media.title || p.name}</span>
          </button>
        ) : (
          <div key={`ph${i}`} className="focus-card focus-card-empty">
            <span className="focus-card-title">{t('focus_soon')}</span>
          </div>
        )
      ))}
      {/* Card 3 — STUDIO LIVE (SIROB). Deep-links to the Handbook's public
          "ask SIROB" widget (#sirobWidget on /handbook). */}
      <button className="focus-card focus-card-live" style={{ backgroundImage: 'url(/assets/hub/sirob-drop.webp)' }}
              onClick={() => { window.location.href = '/handbook#sirobWidget'; }}
              title={t('live_ask_sirob')}>
        <span className="focus-card-scrim" />
        <span className="focus-card-kicker is-live"><span className="live-dot" /> STUDIO LIVE</span>
        <span className="focus-card-title">{t('live_ask_sirob')}</span>
      </button>
    </div>
  );
}

/* ---------- clapperboard ("csapó") icon — brand-DNS, NOT a heart ---------- */
// Ported from the profile feed (src/profile/app.jsx) — slate body + 3 angled
// clap-stick stripes. stroke=currentColor → inherits the button colour (gold
// when clapped).
function ClapIcon({ size = 14 }) {
  return (
    <svg className="clap-icon" width={size} height={size} viewBox="0 0 24 24" fill="none"
         stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <rect x="3" y="9" width="18" height="11" rx="0.5" />
      <line x1="3" y1="9" x2="21" y2="9" />
      <line x1="6" y1="9" x2="9" y2="4" />
      <line x1="11" y1="9" x2="14" y2="4" />
      <line x1="16" y1="9" x2="19" y2="4" />
    </svg>
  );
}

/* ---------- post body renderer (collapsible) ---------- */
// body is pre-rendered, sanitized markdown HTML (mapApiPost → renderMarkdown).
// Long bodies clamp to a few lines with a "még több…" expander so the feed
// stays scannable; short posts are unaffected (clamp taller than their text).
// fiction-italic by tier; *emphasis* → <em> styled gold-italic in hub.css.
// `forceOpen` (from a Fókuszban-card click) shows the full text immediately.
function PostBody({ post, forceOpen }) {
  const ref = useRef(null);
  const [open, setOpen] = useState(false);
  // null = not yet measured; clamp during measurement so scrollHeight vs the
  // clamped clientHeight is meaningful, then keep the clamp ONLY if it overflows.
  const [overflow, setOverflow] = useState(null);
  const expanded = open || forceOpen;

  React.useLayoutEffect(() => {
    const el = ref.current;
    if (el) setOverflow(el.scrollHeight - el.clientHeight > 4);
  }, [post.bodyHtml]);

  // Short posts (overflow === false) render full-height with no fade/“more”.
  const clamped = !expanded && overflow !== false;

  return (
    <div className="pbody-wrap">
      <div ref={ref}
           className={`pbody ${post.fiction ? 'fiction' : ''} ${clamped ? 'clamped' : ''}`}
           dangerouslySetInnerHTML={{ __html: post.bodyHtml || '' }} />
      {overflow && !expanded && (
        <button className="pbody-more" onClick={() => setOpen(true)}>{t('more')}</button>
      )}
      {overflow && open && !forceOpen && (
        <button className="pbody-more" onClick={() => setOpen(false)}>{t('less')}</button>
      )}
    </div>
  );
}

/* ---------- post media ---------- */
function PostMedia({ media }) {
  const [zoom, setZoom] = useState(false);
  useEffect(() => {
    if (!zoom) return;
    const onKey = (e) => { if (e.key === 'Escape') setZoom(false); };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [zoom]);

  if (!media) return null;
  if (media.type === 'music') {
    return (
      <div className="pmedia" style={{ aspectRatio: 'auto' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 13, padding: '14px 16px', background: 'rgba(13,18,24,0.6)' }}>
          <div className="play" style={{ width: 38, height: 38, background: 'linear-gradient(135deg,var(--cyan),#5d97c8)', color: '#06121c', animation: 'none' }}>
            <svg width="13" height="13" viewBox="0 0 17 17"><path d="M4 2.5 L14 8.5 L4 14.5 Z" fill="currentColor"/></svg>
          </div>
          <div style={{ flex: 1 }}>
            <div style={{ fontFamily: 'var(--mono)', fontSize: 11, letterSpacing: '0.5px', color: 'var(--ink-soft)' }}>{media.label}</div>
            <div className="wave" style={{ display: 'flex', alignItems: 'center', gap: 2, marginTop: 7, height: 20 }}>
              {Array.from({ length: 38 }).map((_, i) => (
                <span key={i} style={{
                  flex: 1, borderRadius: 2,
                  height: `${6 + Math.abs(Math.sin(i * 0.7)) * 14}px`,
                  background: i < 13 ? 'var(--cyan)' : 'rgba(127,178,224,0.22)',
                }} />
              ))}
            </div>
          </div>
          <div style={{ fontFamily: 'var(--mono)', fontSize: 10, color: 'var(--ink-faint)' }}>{media.dur}</div>
        </div>
        {media.download && (
          <div className="dl-bar">
            <button className="dl-btn">
              <svg width="12" height="12" viewBox="0 0 14 14"><path d="M7 1 V9 M3.5 6 L7 9.5 L10.5 6 M2 12 H12" stroke="currentColor" strokeWidth="1.4" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
              Letöltés
              <span className="price">{media.download.price}</span>
            </button>
            <span className="dl-note">{media.download.note}</span>
          </div>
        )}
      </div>
    );
  }
  if (media.type === 'placeholder') {
    return (
      <div className={`pmedia ${media.shape || 'portrait'} ph`}>
        <div className="scrim" />
        <div className="ph-label">{media.label}</div>
        {media.title && (
          <div className="ml"><span className="t">{media.title}</span></div>
        )}
      </div>
    );
  }
  // image — click opens a 16:9 cinematic lightbox (portal to <body> so the
  // fixed overlay is viewport-relative; future video/music reuse this frame).
  return (
    <>
      <div className={`pmedia ${media.shape || 'portrait'}`}
           style={{ backgroundImage: `url(${media.src})`, cursor: 'zoom-in' }}
           role="button" tabIndex={0} title={media.title || ''}
           onClick={() => setZoom(true)}
           onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setZoom(true); } }}>
        <div className="scrim" />
        {media.kicker && <span className="mk">{media.kicker}</span>}
        {media.title && (
          <div className="ml">
            <span className="t">{media.title}</span>
          </div>
        )}
      </div>
      {zoom && ReactDOM.createPortal(
        <div className="media-lightbox" role="dialog" aria-modal="true" onClick={() => setZoom(false)}>
          {/* 16:9 cinematic frame — never 9:16. Bigger in landscape, smaller in
              portrait; viewport-fitted via min(vw, vh-derived). */}
          <div className="media-lightbox-frame" onClick={(e) => e.stopPropagation()}>
            <img src={media.src} alt={media.title || ''} className="media-lightbox-img" />
            <div className="media-lightbox-scrim" />
            {/* Burned-in caption: the post's own scene caption + the series
                title. "The Beautiful Illusion" is a brand/IP title — never
                translated, fixed on every post (one series at launch). */}
            <div className="media-lightbox-cap">
              {media.title ? media.title + ' — ' : ''}
              <span className="series">The Beautiful Illusion</span>
            </div>
          </div>
          {/* Discreet portrait-only hint (hidden in landscape via CSS). */}
          <div className="media-lightbox-hint">{t('media_rotate_hint')}</div>
        </div>,
        document.body
      )}
    </>
  );
}

/* ---------- post card ---------- */
// onClap(postId, nextLiked) → Promise<{ liked, likes_count }>. Provided by
// App: it attaches the Supabase JWT and hits POST /api/posts/:id/like. The
// card stays optimistic and reconciles against the server's truth (or reverts).
function Post({ post, onChat, onClap, forceExpand, onFollow, following, followBusy, meId }) {
  const [claps, setClaps] = useState(post.claps);
  const [clapped, setClapped] = useState(() => {
    try { return window.localStorage.getItem(`liked:${post.id}`) === '1'; } catch { return false; }
  });
  const [bursts, setBursts] = useState([]);
  const [busy, setBusy] = useState(false);
  const btnRef = useRef(null);

  const fmt = (n) => n >= 1000 ? (n / 1000).toFixed(1).replace('.0', '') + 'k' : n;

  async function clap() {
    if (busy) return;
    const wasClapped = clapped;
    const prevCount = claps;
    const next = !wasClapped;
    // optimistic
    setClapped(next);
    setClaps((c) => Math.max(0, c + (next ? 1 : -1)));
    if (next) {
      const id = Date.now();
      setBursts((b) => [...b, id]);
      setTimeout(() => setBursts((b) => b.filter((x) => x !== id)), 600);
    }
    try { window.localStorage.setItem(`liked:${post.id}`, next ? '1' : '0'); } catch { /* non-blocking */ }

    if (!onClap) return;          // no handler (shouldn't happen) → optimistic only
    setBusy(true);
    try {
      const result = await onClap(post.id, next);   // throws on failure / auth
      if (result && typeof result.likes_count === 'number') setClaps(result.likes_count);
      if (result && typeof result.liked === 'boolean') {
        setClapped(result.liked);
        try { window.localStorage.setItem(`liked:${post.id}`, result.liked ? '1' : '0'); } catch { /* non-blocking */ }
      }
    } catch (_err) {
      // revert — server rejected (e.g. not logged in)
      setClapped(wasClapped);
      setClaps(prevCount);
      try { window.localStorage.setItem(`liked:${post.id}`, wasClapped ? '1' : '0'); } catch { /* non-blocking */ }
    } finally {
      setBusy(false);
    }
  }

  return (
    <article className="post" id={`hubpost-${post.id}`}>
      {post.replyTo && (
        <div className="reply-to">{t('reply_to')} <b>{post.replyTo}</b></div>
      )}
      <div className="post-head">
        <Avatar data={post.avatar} />
        <div className="pname-block">
          <div className="pname-row">
            <span className="pname">{post.name}</span>
            <Badge tier={post.tier} override={post.badgeOverride} />
          </div>
          {post.role && <div className="prole">{post.role}</div>}
        </div>
        <span className="ptime">{relativeTime(post.publishedAt)}</span>
        {/* Per-author ACTION (follow) — market standard, in the post header.
            "ACTION" is a brand/film term → identical in all locales.
            Hidden on your own showrunner posts. Wired to /api/follows. */}
        {post.followType && !(post.followType === 'showrunner' && post.followId === meId) && (
          <button
            className={`post-action ${following && following.has(post.followId) ? 'on' : ''}`}
            disabled={followBusy === post.followId}
            onClick={() => onFollow && onFollow(post.followType, post.followId)}>
            {following && following.has(post.followId) ? 'ACTION ✓' : 'ACTION'}
          </button>
        )}
      </div>
      <PostBody post={post} forceOpen={forceExpand} />
      <PostMedia media={post.media} />
      <div className="pfoot">
        <button ref={btnRef} className={`react react-clap ${clapped ? 'clapped' : ''}`} onClick={clap} style={{ position: 'relative' }}>
          <span className="ic"><ClapIcon /></span> {fmt(claps)} {t('react_clap')}
          {bursts.map((id) => <span key={id} className="burst" style={{ left: 4, top: -2 }}><ClapIcon size={12} /></span>)}
        </button>
        <button className="react"><span className="ic">◷</span> {post.comments} {t('react_comment')}</button>
        {post.chat ? (
          <button className="react cta spacer" onClick={() => onChat(post)}>{t('cta_chat_saya')}</button>
        ) : (
          <button className="react spacer"><span className="ic">↗</span> {t('react_share')}</button>
        )}
      </div>
    </article>
  );
}

Object.assign(window, { Badge, Avatar, Identity, Sections, Focus, Post });
