// components.jsx — UI building blocks. Reads app context from window.PFCtx.
(function () {
  const { useContext } = React;
  const useApp = () => useContext(window.PFCtx);
  const { Icon } = window;

  // brand colors for streaming services
  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' },
  ];
  function serviceUrl(key, track) {
    const q = encodeURIComponent(`${track.artist} ${track.title}`);
    switch (key) {
      case 'spotify': return `https://open.spotify.com/search/${q}`;
      case 'apple':   return `https://music.apple.com/jp/search?term=${q}`;
      case 'yt':      return `https://music.youtube.com/search?q=${q}`;
      case 'amazon':  return `https://music.amazon.co.jp/search/${q}`;
      default: return '#';
    }
  }
  window.serviceUrl = serviceUrl;

  // deterministic 2-hue from a string
  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>
    );
  }

  // ── Album cover (generated, vinyl motif) ─────────────────────
  function Cover({ track, size = 64, radius = 12 }) {
    const app = useApp();
    if (app && app.theme && app.theme.coverStyle === 'retro') return <RetroCover track={track} size={size} radius={radius} />;
    const h = hashHue(track.id);
    const h2 = (h + 48) % 360;
    const disc = Math.round(size * 0.62);
    return (
      <div style={{
        width: size, height: size, borderRadius: radius, flexShrink: 0,
        position: 'relative', overflow: 'hidden',
        background: `linear-gradient(140deg, oklch(0.62 0.17 ${h}), oklch(0.42 0.15 ${h2}))`,
        boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.08)',
      }}>
        <div style={{ position: 'absolute', inset: 0, background:
          'radial-gradient(120% 90% at 78% 12%, rgba(255,255,255,0.28), rgba(255,255,255,0) 55%)' }} />
        <div style={{
          position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%,-50%)',
          width: disc, height: disc, borderRadius: '50%',
          background: 'radial-gradient(circle, rgba(255,255,255,0.0) 0 14%, rgba(0,0,0,0.32) 14.5% 17%, rgba(0,0,0,0.0) 17.5%), #16131a',
          boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.12), 0 1px 6px rgba(0,0,0,0.4)',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          <div style={{ width: '22%', height: '22%', borderRadius: '50%',
            background: `oklch(0.7 0.18 ${h})`, boxShadow: '0 0 0 2px rgba(0,0,0,0.35)' }} />
        </div>
      </div>
    );
  }

  // ── Equalizer (playing indicator) ────────────────────────────
  function Equalizer({ color = '#fff', size = 16, playing = true }) {
    const bars = [0, 1, 2, 3];
    return (
      <div style={{ display: 'flex', alignItems: 'flex-end', gap: 2, height: size }}>
        {bars.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>
    );
  }

  // ── Avatar ───────────────────────────────────────────────────
  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={{ position: 'relative', flexShrink: 0 }}>
        <div style={{
          width: size, height: size, borderRadius: '50%',
          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>
      </div>
    );
  }

  // ── Name line (name + badges + handle + time) ────────────────
  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={{
            display: 'inline-flex', alignItems: 'center', gap: 2, fontSize: 10.5, fontWeight: 800,
            color: accent, border: `1px solid ${app.theme.accent}55`, borderRadius: 5, padding: '1px 4px',
          }}>合格</span>
        )}
        <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>
    );
  }

  // ── Track card (cover + meta + service links + play) ─────────
  function TrackCard({ track, compact = false }) {
    const app = useApp();
    const T = app.theme;
    const playing = app.nowPlaying && app.nowPlaying.id === track.id;
    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' }}>
          <div style={{ position: 'relative' }}
               onClick={(e) => { e.stopPropagation(); app.play(track); }}>
            <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.32)', borderRadius: 12, color: '#fff', cursor: 'pointer',
            }}>
              {playing ? <Icon.pause size={22} /> : <Icon.play size={22} />}
            </div>
          </div>
          <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>
              {playing && <Equalizer color={T.accent} size={13} />}
            </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, whiteSpace: 'nowrap',
              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, lineHeight: 1,
              }}>{s.glyph || ''}</span>
              <span style={{ fontSize: 10.5, fontWeight: 700 }}>{s.label}</span>
            </a>
          ))}
        </div>
      </div>
    );
  }

  // ── Action bar (reply / repost / like / share) ───────────────
  function Actions({ post, app }) {
    const T = app.theme;
    const liked = app.isLiked(post.id);
    const btn = (icon, label, opts = {}) => (
      <button onClick={opts.onClick} style={{
        display: 'flex', alignItems: 'center', gap: 6, background: 'none', border: 'none',
        color: opts.active ? opts.activeColor : T.faint, cursor: 'pointer', padding: 0, font: 'inherit',
      }}>
        {icon}
        {label != null && <span style={{ fontSize: 13, fontWeight: 600, color: opts.active ? opts.activeColor : T.faint }}>{label}</span>}
      </button>
    );
    return (
      <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 12, maxWidth: 260 }}>
        {btn(<Icon.reply size={18} />, (post.replies || []).length || '', { onClick: () => app.go('detail', { id: post.id }) })}
        {btn(liked ? <Icon.heartFill size={18} /> : <Icon.heart size={18} />, app.likeCount(post),
          { onClick: () => app.like(post.id, 'post'), active: liked, activeColor: '#ff5a7a' })}
        {btn(<Icon.share size={17} />, null, { onClick: () => app.share(post) })}
      </div>
    );
  }

  // ── Post card ────────────────────────────────────────────────
  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: app.theme.dense ? '12px 16px' : '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} compact={app.theme.dense} />}
          <Actions post={post} app={app} />
        </div>
      </div>
    );
  }

  // ── Reply card ───────────────────────────────────────────────
  function ReplyCard({ reply, app }) {
    const T = app.theme;
    const liked = app.isLiked(reply.id);
    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>
          <button onClick={() => app.like(reply.id, 'reply')} style={{
            display: 'flex', alignItems: 'center', gap: 6, background: 'none', border: 'none',
            color: liked ? '#ff5a7a' : T.faint, cursor: 'pointer', padding: 0, marginTop: 8, font: 'inherit',
          }}>
            {liked ? <Icon.heartFill size={15} /> : <Icon.heart size={15} />}
            <span style={{ fontSize: 12.5 }}>{app.likeCount(reply)}</span>
          </button>
        </div>
      </div>
    );
  }

  // ── App header (glass) ───────────────────────────────────────
  function Header({ title, left, right, big }) {
    const app = useApp();
    const T = app.theme;
    return (
      <div style={{
        position: 'sticky', top: 0, zIndex: 5,
        paddingTop: 54, paddingBottom: 0,
        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' }}>{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: 6 }}>{right}</div>
        </div>
      </div>
    );
  }

  // ── Wordmark ─────────────────────────────────────────────────
  function Wordmark({ app, size = 20 }) {
    const T = app.theme;
    return (
      <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
        <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, letterSpacing: 0.3, fontFamily: T.display, whiteSpace: 'nowrap' }}>
          {app.copy.appName}
        </span>
      </div>
    );
  }

  // ── Mini player ──────────────────────────────────────────────
  function MiniPlayer() {
    const app = useApp();
    const T = app.theme;
    const np = app.nowPlaying;
    if (!np) return null;
    const pct = Math.min(100, (app.playPos / np._dur) * 100 || 0);
    return (
      <div style={{
        position: 'absolute', left: 8, right: 8, bottom: 74, zIndex: 40,
        background: T.surface2, borderRadius: 14, border: `1px solid ${T.border}`,
        boxShadow: '0 10px 30px rgba(0,0,0,0.5)', overflow: 'hidden',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 11, padding: 8 }}>
          <Cover track={np} size={42} radius={9} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontWeight: 800, fontSize: 13.5, color: T.text, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{np.title}</div>
            <div style={{ fontSize: 12, color: T.sub, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{np.artist}</div>
          </div>
          <Equalizer color={T.accent} size={16} playing={app.playing} />
          <button onClick={() => app.togglePlay()} style={{
            width: 38, height: 38, borderRadius: '50%', border: 'none', background: T.accent, color: '#fff',
            display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
          }}>{app.playing ? <Icon.pause size={18} /> : <Icon.play size={18} />}</button>
          <button onClick={() => app.stop()} style={{
            width: 30, height: 38, border: 'none', background: 'none', color: T.faint, cursor: 'pointer',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}><Icon.close size={18} /></button>
        </div>
        <div style={{ height: 3, background: T.border }}>
          <div style={{ height: '100%', width: pct + '%', background: T.accent, transition: 'width 0.3s linear' }} />
        </div>
      </div>
    );
  }

  // ── Bottom tab bar ───────────────────────────────────────────
  function TabBar() {
    const app = useApp();
    const T = app.theme;
    const cur = app.tab;
    const item = (key, IconOff, IconOn, label, badge) => {
      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, position: 'relative',
        }}>
          <div style={{ position: 'relative' }}>
            {active ? <IconOn size={25} /> : <IconOff size={25} />}
            {badge && <span style={{ position: 'absolute', top: -3, right: -7, width: 9, height: 9, borderRadius: '50%', background: T.accent, border: `2px solid ${T.bg}` }} />}
          </div>
          <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: 26, 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()} 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>
    );
  }

  // Album cover: 2x2 mosaic of up to 4 track covers
  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, Equalizer, Avatar, NameLine, TrackCard, PostCard, ReplyCard,
    Header, Wordmark, MiniPlayer, TabBar, SERVICES,
  });
})();
