// components.jsx — v2 UI building blocks. Reactions = know/like/curious.
(function () {
  const { useContext } = React;
  const useApp = () => useContext(window.PFCtx);
  const { Icon } = window;

  const SERVICES = [
    { key: 'spotify', label: 'Spotify', color: '#1DB954', glyph: 'S' },
    { key: 'apple', label: 'Apple', color: '#FA243C', glyph: '' },
    { key: 'yt', label: 'YT Music', color: '#FF0000', glyph: '▶' },
    { key: 'amazon', label: 'Amazon', color: '#25D1DA', glyph: 'a' },
  ];
  const REACTIONS = [{ kind: 'know', label: '知ってる' }, { kind: 'like', label: 'いいね' }, { kind: 'curious', label: '気になる' }];
  function serviceUrl(key, track) {
    const q = encodeURIComponent(`${track.artist} ${track.title}`);
    return { spotify: `https://open.spotify.com/search/${q}`, apple: `https://music.apple.com/jp/search?term=${q}`, yt: `https://music.youtube.com/search?q=${q}`, amazon: `https://music.amazon.co.jp/search/${q}` }[key] || '#';
  }
  window.serviceUrl = serviceUrl;

  function hashHue(s) { let h = 0; for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) % 360; return h; }
  function hashInt(s) { let h = 0; for (let i = 0; i < s.length; i++) h = (h * 131 + s.charCodeAt(i)) >>> 0; return h; }

  function RetroCover({ track, size = 64, radius = 12 }) {
    const n = hashInt(track.id);
    const palettes = [
      { sky: '#e7c45a', sun: '#df5b3b', ink: '#284440', cloud: '#f6efd2' }, { sky: '#8fc7bd', sun: '#e0795a', ink: '#1f5a63', cloud: '#f2efe0' },
      { sky: '#e7b34a', sun: '#d8442f', ink: '#2d2a4a', cloud: '#f6efd2' }, { sky: '#bba6d6', sun: '#e86a92', ink: '#3a2f6b', cloud: '#fbf3df' },
      { sky: '#9ec9cf', sun: '#d8442f', ink: '#1d3a57', cloud: '#eef4ec' }, { sky: '#e2a06b', sun: '#f0c14b', ink: '#5a2f3a', cloud: '#f6efd2' },
    ];
    const p = palettes[n % palettes.length]; const motif = Math.floor(n / 7) % 3; const night = motif === 1;
    const bg = night ? p.ink : p.sky; const orb = night ? p.cloud : p.sun; const cx = 32 + (n % 5) * 8; const oy = 36;
    const rays = []; for (let i = 0; i < 8; i++) { const a = i * Math.PI / 4; rays.push([cx + Math.cos(a) * 22, oy + Math.sin(a) * 22, cx + Math.cos(a) * 28, oy + Math.sin(a) * 28]); }
    const stars = [[18, 16], [70, 12], [84, 30], [30, 40], [58, 24], [12, 34]];
    return (
      <div style={{ width: size, height: size, borderRadius: radius, flexShrink: 0, overflow: 'hidden', position: 'relative', boxShadow: `inset 0 0 0 2px ${p.ink}` }}>
        <svg width={size} height={size} viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice" style={{ display: 'block' }}>
          <rect width="100" height="100" fill={bg} />
          {motif === 0 && rays.map((r, i) => <line key={i} x1={r[0]} y1={r[1]} x2={r[2]} y2={r[3]} stroke={orb} strokeWidth="2.6" strokeLinecap="round" />)}
          <circle cx={cx} cy={oy} r="17" fill={orb} />
          {night && stars.map((s, i) => <circle key={i} cx={s[0]} cy={s[1]} r="1.6" fill={p.cloud} />)}
          <path d={`M0 ${night ? 74 : 70} Q 50 ${night ? 62 : 56} 100 ${night ? 74 : 70} L100 100 L0 100 Z`} fill={p.ink} opacity={night ? 0.55 : 1} />
          {motif === 2 && <g stroke={p.ink} strokeWidth="3.2" fill="none"><rect x="22" y="22" width="56" height="56" /><line x1="50" y1="22" x2="50" y2="78" /><line x1="22" y1="50" x2="78" y2="50" /></g>}
          <g fill={p.ink} opacity="0.09">{[0, 1, 2, 3, 4].map((r) => [0, 1, 2, 3, 4].map((c) => <circle key={r + '-' + c} cx={10 + c * 20} cy={10 + r * 20} r="1.3" />))}</g>
        </svg>
      </div>
    );
  }
  function Cover({ track, size = 64, radius = 12 }) { return <RetroCover track={track} size={size} radius={radius} />; }

  function Equalizer({ color = '#fff', size = 16, playing = true }) {
    return <div style={{ display: 'flex', alignItems: 'flex-end', gap: 2, height: size }}>{[0, 1, 2, 3].map((i) => <div key={i} style={{ width: 2.5, background: color, borderRadius: 2, height: playing ? undefined : '30%', animation: playing ? `pfEq 0.9s ${i * 0.13}s ease-in-out infinite` : 'none' }} />)}</div>;
  }

  function Avatar({ user, size = 44 }) {
    const app = useApp(); const u = typeof user === 'string' ? app.users[user] : user; const init = (u.name || '?').trim().charAt(0);
    return <div style={{ width: size, height: size, borderRadius: '50%', flexShrink: 0, background: `linear-gradient(150deg, oklch(0.66 0.15 ${u.color}), oklch(0.46 0.14 ${(u.color + 40) % 360}))`, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontWeight: 800, fontSize: size * 0.42, boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.14)' }}>{init}</div>;
  }

  function NameLine({ user, time, app }) {
    const u = typeof user === 'string' ? app.users[user] : user; const accent = app.theme.accent;
    return (
      <div style={{ display: 'flex', alignItems: 'center', gap: 5, minWidth: 0, flexWrap: 'wrap' }}>
        <span style={{ fontWeight: 800, color: app.theme.text, whiteSpace: 'nowrap' }}>{u.name}</span>
        {u.papa && <span title="管理者" style={{ color: accent, display: 'inline-flex' }}><Icon.verified size={16} /></span>}
        {u.passed && !u.papa && <span title="審査合格" style={{ fontSize: 10.5, fontWeight: 800, color: accent, border: `1px solid ${accent}55`, borderRadius: 5, padding: '1px 4px' }}>合格</span>}
        {u.handle && <span style={{ color: app.theme.faint, fontSize: 13.5, whiteSpace: 'nowrap' }}>@{u.handle}</span>}
        {time && <span style={{ color: app.theme.faint, fontSize: 13.5 }}>· {time}</span>}
      </div>
    );
  }

  function TrackCard({ track, compact = false }) {
    const app = useApp(); const T = app.theme;
    return (
      <div style={{ border: `1px solid ${T.border}`, borderRadius: 16, overflow: 'hidden', background: T.surface, marginTop: 10 }}>
        <div style={{ display: 'flex', gap: 12, padding: 12, alignItems: 'center' }}>
          <a href={serviceUrl('spotify', track)} target="_blank" rel="noreferrer" onClick={(e) => e.stopPropagation()} aria-label={`${track.title} を Spotify で開く`} style={{ position: 'relative', display: 'block', flexShrink: 0 }}>
            <Cover track={track} size={compact ? 52 : 60} radius={12} />
            <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,0.26)', borderRadius: 12, color: '#fff' }}><Icon.play size={20} /></div>
          </a>
          <div style={{ minWidth: 0, flex: 1 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}><span style={{ fontWeight: 800, color: T.text, fontSize: 15, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{track.title}</span></div>
            <div style={{ color: T.sub, fontSize: 13.5, marginTop: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{track.artist} · {track.year}</div>
            <div style={{ marginTop: 3, display: 'inline-block', fontSize: 11, fontWeight: 700, color: T.sub, background: T.surface2, borderRadius: 6, padding: '1px 7px' }}>#{track.tag}</div>
          </div>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', borderTop: `1px solid ${T.border}` }}>
          {SERVICES.map((s, i) => (
            <a key={s.key} href={serviceUrl(s.key, track)} target="_blank" rel="noreferrer" onClick={(e) => e.stopPropagation()} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4, padding: '9px 4px', textDecoration: 'none', color: T.sub, borderLeft: i ? `1px solid ${T.border}` : 'none' }}>
              <span style={{ width: 22, height: 22, borderRadius: 6, background: s.color, color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 900, fontSize: 12 }}>{s.glyph || ''}</span>
              <span style={{ fontSize: 10.5, fontWeight: 700 }}>{s.label}</span>
            </a>))}
        </div>
      </div>
    );
  }

  // external shared-link card (Spotify/Apple/YT/Amazon paste or share)
  function LinkCard({ post, app }) {
    const T = app.theme;
    const svc = { spotify: { label: 'Spotify', color: '#1DB954' }, apple: { label: 'Apple Music', color: '#FA243C' }, yt: { label: 'YouTube Music', color: '#FF0000' }, amazon: { label: 'Amazon Music', color: '#25D1DA' } }[post.link_service] || { label: 'リンク', color: T.accent };
    return (
      <a href={post.link_url} target="_blank" rel="noreferrer" onClick={(e) => e.stopPropagation()} style={{ display: 'flex', gap: 12, padding: 12, marginTop: 10, border: `1px solid ${T.border}`, borderRadius: 16, background: T.surface, textDecoration: 'none', alignItems: 'center' }}>
        {post.link_image ? <img src={post.link_image} alt="" style={{ width: 60, height: 60, borderRadius: 12, objectFit: 'cover', flexShrink: 0 }} /> : <div style={{ width: 60, height: 60, borderRadius: 12, background: svc.color, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff' }}><Icon.music size={26} /></div>}
        <div style={{ minWidth: 0, flex: 1 }}>
          <div style={{ fontWeight: 800, color: T.text, fontSize: 15, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{post.link_title}</div>
          {post.link_artist && <div style={{ color: T.sub, fontSize: 13, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{post.link_artist}</div>}
          <div style={{ marginTop: 5, display: 'inline-flex', alignItems: 'center', gap: 5 }}><span style={{ width: 16, height: 16, borderRadius: 4, background: svc.color }} /><span style={{ fontSize: 11.5, color: T.faint, fontWeight: 700 }}>{svc.label}で開く ↗</span></div>
        </div>
      </a>
    );
  }

  // reaction chips (know / like / curious)
  function Reactions({ target, type, app, compact = false }) {
    const T = app.theme; const counts = app.reactions(target); const mine = app.myReactions(target);
    return (
      <div style={{ display: 'flex', gap: 6, marginTop: compact ? 6 : 10, flexWrap: 'wrap' }}>
        {REACTIONS.map((r) => {
          const on = mine.includes(r.kind); const n = counts[r.kind] || 0;
          return (
            <button key={r.kind} onClick={(e) => { e.stopPropagation(); app.react(target.id, type, r.kind); }} aria-pressed={on} aria-label={`${r.label}${on ? '（選択中）' : ''}`} style={{
              display: 'flex', alignItems: 'center', gap: 4, padding: compact ? '3px 8px' : '5px 10px', borderRadius: 999,
              border: `1px solid ${on ? T.accent : T.border}`, background: on ? `${T.accent}1a` : 'none',
              color: on ? T.accent : T.sub, cursor: 'pointer', font: 'inherit', fontSize: 12, fontWeight: 700,
            }}>{r.label}{n > 0 && <span>{n}</span>}</button>
          );
        })}
      </div>
    );
  }

  function PostCard({ post, onOpen }) {
    const app = useApp(); const T = app.theme; const track = app.trackById[post.track_id];
    return (
      <div onClick={onOpen} style={{ display: 'flex', gap: 12, padding: '16px', borderBottom: `1px solid ${T.border}`, cursor: 'pointer' }}>
        <Avatar user={post.author} size={44} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <NameLine user={post.author} time={post.time} app={app} />
          {post.text && <div style={{ color: T.text, marginTop: 3, lineHeight: 1.55, fontSize: 15, wordBreak: 'break-word' }}>{post.text}</div>}
          {track && <TrackCard track={track} />}
          {post.link_url && <LinkCard post={post} app={app} />}
          <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginTop: 10 }}>
            <button onClick={(e) => { e.stopPropagation(); app.go('detail', { id: post.id }); }} style={{ display: 'flex', alignItems: 'center', gap: 5, background: 'none', border: 'none', color: T.faint, cursor: 'pointer', font: 'inherit' }}><Icon.reply size={17} /><span style={{ fontSize: 13 }}>{(post.replies || []).length || ''}</span></button>
            <button onClick={(e) => { e.stopPropagation(); app.share(post); }} style={{ background: 'none', border: 'none', color: T.faint, cursor: 'pointer' }}><Icon.share size={16} /></button>
          </div>
          <Reactions target={post} type="post" app={app} />
        </div>
      </div>
    );
  }

  function ReplyCard({ reply, app }) {
    const T = app.theme;
    return (
      <div style={{ display: 'flex', gap: 12, padding: '14px 16px', borderBottom: `1px solid ${T.border}` }}>
        <Avatar user={reply.author} size={38} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <NameLine user={reply.author} time={reply.time} app={app} />
          <div style={{ color: T.text, marginTop: 3, lineHeight: 1.55, fontSize: 14.5, wordBreak: 'break-word' }}>{reply.text}</div>
          <Reactions target={reply} type="reply" app={app} compact />
        </div>
      </div>
    );
  }

  function Header({ title, left, right, big }) {
    const app = useApp(); const T = app.theme;
    return (
      <div style={{ position: 'sticky', top: 0, zIndex: 5, paddingTop: 'max(54px, env(safe-area-inset-top))', background: T.glass, backdropFilter: 'blur(18px) saturate(160%)', WebkitBackdropFilter: 'blur(18px) saturate(160%)', borderBottom: `1px solid ${T.border}` }}>
        <div style={{ height: 48, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '0 14px', position: 'relative' }}>
          <div style={{ position: 'absolute', left: 14, display: 'flex', alignItems: 'center', gap: 6 }}>{left}</div>
          {big ? big : <div style={{ fontWeight: 800, fontSize: 17, color: T.text }}>{title}</div>}
          <div style={{ position: 'absolute', right: 14, display: 'flex', alignItems: 'center', gap: 10 }}>{right}</div>
        </div>
      </div>
    );
  }

  function Wordmark({ app, size = 19 }) {
    const T = app.theme;
    return (
      <button onClick={() => app.openPicker()} style={{ display: 'flex', alignItems: 'center', gap: 7, background: 'none', border: 'none', cursor: 'pointer', font: 'inherit' }}>
        <div style={{ width: 24, height: 24, borderRadius: 7, background: T.accent, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff' }}><Icon.music size={15} /></div>
        <span style={{ fontWeight: 900, fontSize: size, color: T.text, fontFamily: T.display, whiteSpace: 'nowrap' }}>{app.copy.appName}</span>
        {app.groups.length > 1 && <span style={{ color: T.faint, fontSize: 12 }}>▾</span>}
      </button>
    );
  }

  function TabBar() {
    const app = useApp(); const T = app.theme; const cur = app.tab;
    const item = (key, Off, On, label) => {
      const active = cur === key;
      return <button onClick={() => app.setTab(key)} style={{ flex: 1, background: 'none', border: 'none', cursor: 'pointer', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3, padding: '8px 0 0', color: active ? T.text : T.faint }}>{active ? <On size={25} /> : <Off size={25} />}<span style={{ fontSize: 10.5, fontWeight: active ? 800 : 600 }}>{label}</span></button>;
    };
    return (
      <div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, zIndex: 45, paddingBottom: 'max(26px, env(safe-area-inset-bottom))', paddingTop: 2, background: T.glass, backdropFilter: 'blur(18px) saturate(160%)', WebkitBackdropFilter: 'blur(18px) saturate(160%)', borderTop: `1px solid ${T.border}`, display: 'flex', alignItems: 'flex-start' }}>
        {item('home', Icon.home, Icon.homeFill, 'ホーム')}
        <div style={{ flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center', paddingTop: 2 }}>
          <button onClick={() => app.compose()} aria-label="おすすめを投稿" style={{ width: 50, height: 50, borderRadius: '50%', cursor: 'pointer', border: 'none', background: T.accent, color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: `0 6px 18px ${T.accent}66` }}><Icon.plus size={26} /></button>
        </div>
        {item('profile', Icon.person, Icon.personFill, 'あなた')}
      </div>
    );
  }

  function AlbumCover({ album, size = 64, radius = 14 }) {
    const app = useApp(); const ids = (album.tracks || []).slice(0, 4); const cell = (size - 3) / 2;
    if (ids.length === 0) return <div style={{ width: size, height: size, borderRadius: radius, background: app.theme.surface2, border: `1px solid ${app.theme.border}` }} />;
    if (ids.length === 1) return <Cover track={app.trackById[ids[0]]} size={size} radius={radius} />;
    const grid = [0, 1, 2, 3].map((i) => ids[i] != null ? ids[i] : ids[i % ids.length]);
    return <div style={{ width: size, height: size, borderRadius: radius, overflow: 'hidden', flexShrink: 0, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 3, background: app.theme.border }}>{grid.map((id, i) => <Cover key={i} track={app.trackById[id]} size={cell} radius={0} />)}</div>;
  }

  Object.assign(window, { Cover, AlbumCover, Avatar, NameLine, TrackCard, LinkCard, Reactions, PostCard, ReplyCard, Header, Wordmark, TabBar, SERVICES });
})();
