// Chat area — header with model picker + avatar menu, messages, composer
function ChatArea({
  sidebarOpen, onToggleSidebar, model, setModel, tier, onOpenPaywall, onOpenSettings,
  messages, isStreaming, onSend, onStop, onRegen, onCopy, onFeedback,
  user, onSignOut,
}) {
  const S = window.SOL;
  const [input, setInput] = React.useState('');
  const [pickerOpen, setPickerOpen] = React.useState(false);
  const pickerBtnRef = React.useRef(null);
  const [menuOpen, setMenuOpen] = React.useState(false);
  const [attachOpen, setAttachOpen] = React.useState(false);
  const [voiceOpen, setVoiceOpen] = React.useState(false);
  const [attachments, setAttachments] = React.useState([]);
  const [ctxMenu, setCtxMenu] = React.useState(null); // { x, y, msg }
  const scrollRef = React.useRef(null);
  const ta = React.useRef(null);
  const fileInputRef = React.useRef(null);

  React.useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [messages, isStreaming]);

  React.useEffect(() => {
    if (!ctxMenu) return;
    const close = () => setCtxMenu(null);
    window.addEventListener('click', close);
    window.addEventListener('scroll', close, true);
    return () => { window.removeEventListener('click', close); window.removeEventListener('scroll', close, true); };
  }, [ctxMenu]);

  const handleFileChange = (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = (ev) => {
      setAttachments(a => [...a, { dataUrl: ev.target.result, name: file.name, type: file.type }]);
    };
    reader.readAsDataURL(file);
    e.target.value = '';
  };

  const pickFile = (accept, capture) => {
    if (!fileInputRef.current) return;
    fileInputRef.current.accept = accept;
    if (capture) fileInputRef.current.setAttribute('capture', capture);
    else fileInputRef.current.removeAttribute('capture');
    fileInputRef.current.value = '';
    fileInputRef.current.click();
  };

  const submit = () => {
    const t = input.trim();
    if ((!t && attachments.length === 0) || isStreaming) return;
    onSend(t, attachments);
    setInput('');
    setAttachments([]);
    if (ta.current) ta.current.style.height = 'auto';
  };

  const cur = model === 'apollo'
    ? { name: 'Apollo 1 Max', variant: 'apollo', tier: 'PLUS' }
    : { name: 'Apollo 1', variant: 'solus', tier: 'FREE' };

  return (
    <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0, background: S.cream, position: 'relative' }}>
      <input type="file" ref={fileInputRef} style={{ display: 'none' }} onChange={handleFileChange}/>
      {/* Header */}
      <div style={{ flexShrink: 0, background: S.cream, position: 'relative' }}>
        {/* Sidebar toggle — absolutely positioned so it doesn't offset the centered rail */}
        {!sidebarOpen && (
          <button onClick={onToggleSidebar} style={{ ...iconBtn2(S), position: 'absolute', left: 8, top: '50%', transform: 'translateY(-50%)', zIndex: 1 }} title="Open sidebar">
            <window.Icon name="sidebar" size={18} color={S.ink2}/>
          </button>
        )}
        <header style={{
          width: '100%', maxWidth: 768, margin: '0 auto', boxSizing: 'border-box',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          gap: 12, padding: '8px 20px',
        }}>
          {/* Left: model selector */}
          <div style={{ position: 'relative' }}>
            <button ref={pickerBtnRef} onClick={() => setPickerOpen(!pickerOpen)} style={{
              display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer',
              height: 36, padding: '0 10px 0 8px', borderRadius: 12,
              border: `1px solid ${S.line2}`, background: S.paper,
              fontFamily: S.sans, fontSize: 13.5, fontWeight: 500, color: S.ink,
              boxShadow: '0 1px 2px rgba(0,0,0,0.05)',
            }}>
              <window.SunMark size={24} variant={cur.variant} style={{ flexShrink: 0 }}/>
              <span style={{ color: S.ink }}>{cur.name}</span>
              {cur.variant === 'apollo' && (
                <span style={{
                  display: 'inline-flex', height: 20, padding: '0 6px', borderRadius: 6, alignItems: 'center',
                  fontFamily: S.mono, fontSize: 10, fontWeight: 600, letterSpacing: 0.8,
                  textTransform: 'uppercase', background: S.cream2, color: S.ink2,
                }}>Max</span>
              )}
              <window.Icon name="chevDown" size={14} color={S.ink3}/>
            </button>
            {pickerOpen && (
              <ModelPicker current={model} tier={tier}
                anchorRef={pickerBtnRef}
                onClose={() => setPickerOpen(false)}
                onPick={(id) => {
                  if (id === 'apollo' && tier !== 'plus') { setPickerOpen(false); onOpenPaywall(); }
                  else { setModel(id); setPickerOpen(false); }
                }}/>
            )}
          </div>

          {/* Right: Plans link (free) or Go to usage (paid) */}
          {tier !== 'plus' ? (
            <a href="/pricing" style={{
              display: 'inline-flex', alignItems: 'center', gap: 6,
              height: 36, padding: '0 10px', borderRadius: 999,
              border: `1px solid ${S.line2}`, background: 'transparent',
              fontFamily: S.sans, fontSize: 12, fontWeight: 500, color: S.ink3,
              textDecoration: 'none', boxShadow: '0 1px 2px rgba(0,0,0,0.05)', whiteSpace: 'nowrap',
            }}>
              <window.Icon name="sparkle" size={14} color={S.amber}/>
              <span>Plus &amp; Studio</span>
            </a>
          ) : (
            <a href="/usage" style={{
              display: 'inline-flex', alignItems: 'center',
              height: 36, padding: '0 14px', borderRadius: 999,
              border: `1px solid ${S.line2}`, background: 'transparent',
              fontFamily: S.sans, fontSize: 12, fontWeight: 600, color: S.ink3,
              textDecoration: 'none', boxShadow: '0 1px 2px rgba(0,0,0,0.05)',
            }}>Go to usage</a>
          )}
        </header>
        <div style={{
          height: 1,
          width: '100%', maxWidth: 768, margin: '0 auto',
          padding: '0 20px', boxSizing: 'border-box',
          background: `linear-gradient(to right, transparent 0%, ${S.line2 || 'rgba(28,26,23,0.13)'} 15%, ${S.line2 || 'rgba(28,26,23,0.13)'} 80%, transparent 100%)`,
          flexShrink: 0,
        }}/>
      </div>

      {/* Messages */}
      <div ref={scrollRef} style={{ flex: 1, overflowY: 'auto', padding: '20px 0 20px' }}>
        <div style={{ maxWidth: 760, margin: '0 auto', padding: '0 24px' }}>
          {messages.length === 0 ? (
            <EmptyState model={cur.variant} onPick={(t) => { setInput(t); setTimeout(() => { onSend(t); setInput(''); }, 40); }}/>
          ) : (
            messages.map((m, i) => (
              <Message key={m.id} msg={m}
                isLast={i === messages.length - 1}
                onCopy={() => onCopy(m.id)}
                onRegen={() => onRegen(m.id)}
                onFeedback={(v) => onFeedback(m.id, v)}
                onCtxMenu={(e, msg) => { e.preventDefault(); const vw = window.innerWidth, vh = window.innerHeight; setCtxMenu({ x: Math.min(e.clientX, vw-180), y: Math.min(e.clientY, vh-120), msg }); }}/>
            ))
          )}
        </div>
      </div>

      {/* Composer — flex child so it tracks the real bottom on iOS PWA */}
      <div style={{ flexShrink: 0, position: 'relative', padding: '0 24px 22px',
        paddingBottom: 'max(22px, env(safe-area-inset-bottom))', background: S.cream }}>
        {/* fade gradient that bleeds up into the message area */}
        <div style={{ position: 'absolute', top: -56, left: 0, right: 0, height: 56,
          background: `linear-gradient(to bottom, transparent, ${S.cream})`,
          pointerEvents: 'none' }}/>
        <div style={{ maxWidth: 760, margin: '0 auto', paddingTop: 16 }}>
          <div style={{ position: 'relative' }}>
            <Composer
              value={input} setValue={setInput} onSend={submit} onStop={onStop}
              isStreaming={isStreaming}
              onAttach={() => setAttachOpen(true)}
              onVoice={() => setVoiceOpen(true)}
              taRef={ta}
              attachments={attachments}
              onRemoveAttachment={(i) => setAttachments(a => a.filter((_, idx) => idx !== i))}
            />
            {attachOpen && <AttachMenu model={model} tier={tier} onClose={() => setAttachOpen(false)} onUpgrade={onOpenPaywall} onPickFile={pickFile}/>}
            {voiceOpen && <VoiceModal onClose={() => setVoiceOpen(false)}/>}
          </div>
          <div style={{ textAlign: 'center', marginTop: 8,
            fontFamily: S.mono, fontSize: 10, color: S.ink3, letterSpacing: 0.4 }}>
            Solunce can be wrong. Read before you send.
          </div>
        </div>
      </div>

      {/* Right-click context menu */}
      {ctxMenu && (
        <div onClick={e => e.stopPropagation()} style={{
          position: 'fixed', left: ctxMenu.x, top: ctxMenu.y, zIndex: 9999,
          background: S.paper, border: `1px solid ${S.line}`,
          borderRadius: 10, boxShadow: '0 4px 20px rgba(0,0,0,0.10)', overflow: 'hidden', minWidth: 168,
        }}>
          {[
            { label: 'Copy message', icon: 'copy', action: () => { try { navigator.clipboard.writeText(ctxMenu.msg.text); } catch(e) {} setCtxMenu(null); } },
            ...(ctxMenu.msg.role === 'assistant' && !isStreaming ? [{ label: 'Regenerate', icon: 'regen', action: () => { onRegen(ctxMenu.msg.id); setCtxMenu(null); } }] : []),
          ].map(item => (
            <button key={item.label} onClick={item.action} style={{
              display: 'block', width: '100%', textAlign: 'left', padding: '9px 14px',
              background: 'transparent', border: 'none', cursor: 'pointer',
              fontFamily: S.sans, fontSize: 13.5, color: S.ink, letterSpacing: -0.1,
              transition: 'background 0.1s',
            }}
            onMouseEnter={e => e.currentTarget.style.background = S.cream2}
            onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
              {item.label}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}
function iconBtn2(S) {
  return {
    width: 36, height: 36, borderRadius: 10, border: 'none', background: 'transparent',
    display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
  };
}

function ModelPicker({ current, tier, onPick, onClose, anchorRef }) {
  const S = window.SOL;
  const [pos, setPos] = React.useState({ top: 0, left: 0 });

  React.useEffect(() => {
    if (anchorRef?.current) {
      const r = anchorRef.current.getBoundingClientRect();
      setPos({ top: r.bottom + 6, left: r.left + r.width / 2 - 148 });
    }
    const h = (e) => { if (!e.target.closest('[data-mp]')) onClose(); };
    setTimeout(() => document.addEventListener('click', h), 0);
    return () => document.removeEventListener('click', h);
  }, []);

  const isPlus = tier === 'plus';

  const ModelRow = ({ id, name, desc, badge, locked, selected, variant }) => {
    if (locked) {
      return (
        <a href="/pricing" data-mp onClick={onClose} style={{
          display: 'block', textDecoration: 'none', padding: '2px 0',
        }}>
          <div style={{
            position: 'relative', borderRadius: 10, padding: '8px 10px',
            border: `1.5px dashed ${S.line2}`, background: S.cream2,
          }}>
            <div style={{ position: 'absolute', right: 8, top: 8 }}>
              <window.Icon name="lock" size={14} color={S.ink3}/>
            </div>
            <div style={{ display: 'flex', alignItems: 'flex-start', gap: 8, paddingRight: 20 }}>
              <div style={{ marginTop: 1, opacity: 0.5, flexShrink: 0 }}>
                <window.SunMark size={28} variant={variant || 'solus'}/>
              </div>
              <div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
                  <span style={{ fontFamily: S.sans, fontSize: 13, fontWeight: 600, color: S.ink }}>{name}</span>
                  {badge && <span style={{
                    fontFamily: S.mono, fontSize: 9, letterSpacing: 0.8, padding: '2px 5px', borderRadius: 4,
                    background: S.cream, color: S.ink2, fontWeight: 600, textTransform: 'uppercase',
                  }}>{badge}</span>}
                </div>
                <div style={{ fontFamily: S.sans, fontSize: 11, color: S.ink3, marginTop: 3, lineHeight: 1.4 }}>{desc}</div>
                <div style={{ fontFamily: S.mono, fontSize: 10, color: S.ink3, marginTop: 6, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.5 }}>Plus &amp; Studio — view plans →</div>
              </div>
            </div>
          </div>
        </a>
      );
    }
    return (
      <button data-mp onClick={() => onPick(id)} style={{
        width: '100%', textAlign: 'left', cursor: 'pointer', padding: '2px 0', background: 'transparent', border: 'none',
      }}>
        <div style={{
          borderRadius: 10, padding: '8px 10px', transition: 'background 0.12s',
          border: selected ? `1px solid rgba(232,155,60,0.3)` : '1px solid transparent',
          background: selected ? S.paper : 'transparent',
        }}
        onMouseEnter={e => { if (!selected) e.currentTarget.style.background = S.cream2; }}
        onMouseLeave={e => { if (!selected) e.currentTarget.style.background = 'transparent'; }}>
          <div style={{ display: 'flex', alignItems: 'flex-start', gap: 8 }}>
            <div style={{ marginTop: 1, flexShrink: 0 }}>
              <window.SunMark size={28} variant={variant || 'solus'}/>
            </div>
            <div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
                <span style={{ fontFamily: S.sans, fontSize: 13, fontWeight: 600, color: S.ink }}>{name}</span>
                {badge && <span style={{
                  fontFamily: S.mono, fontSize: 9, letterSpacing: 0.8, padding: '2px 5px', borderRadius: 4,
                  background: S.cream2, color: S.ink2, fontWeight: 600, textTransform: 'uppercase',
                }}>{badge}</span>}
              </div>
              <div style={{ fontFamily: S.sans, fontSize: 11, color: S.ink3, marginTop: 3, lineHeight: 1.4 }}>{desc}</div>
            </div>
          </div>
        </div>
      </button>
    );
  };

  return (
    <div data-mp style={{
      position: 'fixed', top: pos.top, left: pos.left, width: 296,
      background: S.paper, border: `1px solid ${S.line2}`, borderRadius: 14,
      boxShadow: '0 20px 56px rgba(28,26,23,0.22)', padding: 8, zIndex: 9999,
      animation: 'sol-drop-in 0.18s cubic-bezier(0.16, 1, 0.3, 1)',
    }}>
      <ModelRow id="apollo" name="Apollo 1 Max" badge="Max" variant="apollo"
        desc="Flagship — deeper reasoning, more thorough answers"
        locked={!isPlus} selected={current === 'apollo'}/>
      <div style={{ height: 1, background: S.line, margin: '6px 2px' }}/>
      <ModelRow id="solus" name="Apollo 1" variant="solus"
        desc="Fast and sharp — great for everyday questions, code, and writing"
        locked={false} selected={current === 'solus'}/>
    </div>
  );
}

function AvatarMenu({ user, tier, onClose, onOpenSettings, onSignOut }) {
  const S = window.SOL;
  React.useEffect(() => {
    const h = (e) => { if (!e.target.closest('[data-am]')) onClose(); };
    setTimeout(() => document.addEventListener('click', h), 0);
    return () => document.removeEventListener('click', h);
  }, []);
  const item = (icon, label, onClick) => (
    <button onClick={onClick} style={{
      width: '100%', padding: '9px 10px', borderRadius: 10, border: 'none',
      background: 'transparent', cursor: 'pointer', textAlign: 'left',
      display: 'flex', alignItems: 'center', gap: 10, fontFamily: S.sans, fontSize: 13.5, color: S.ink,
    }}
    onMouseEnter={(e) => e.currentTarget.style.background = S.cream2}
    onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
      <window.Icon name={icon} size={16} color={S.ink2}/>{label}
    </button>
  );
  return (
    <div data-am style={{
      position: 'absolute', top: 'calc(100% + 8px)', right: 0, width: 260,
      background: S.paper, border: `1px solid ${S.line2}`, borderRadius: 14,
      boxShadow: '0 12px 40px rgba(28,26,23,0.18)', padding: 8, zIndex: 30,
      animation: 'sol-fade-in 0.15s ease',
    }}>
      <div style={{ padding: '10px 10px 12px', borderBottom: `1px solid ${S.line}` }}>
        <div style={{ fontFamily: S.sans, fontSize: 13, fontWeight: 500, color: S.ink }}>{user?.display_name || user?.email?.split('@')[0] || 'Guest'}</div>
        <div style={{ fontFamily: S.sans, fontSize: 12, color: S.ink3 }}>{user?.email || ''} · {tier === 'plus' ? 'Plus' : 'Free'}</div>
      </div>
      <div style={{ padding: 4 }}>
        {item('settings', 'Settings', onOpenSettings)}
        {item('shield', 'Privacy', () => window.open('/privacy', '_blank'))}
        {item('info', 'Help & docs', () => window.open('https://discord.gg/solunce', '_blank'))}
        <div style={{ height: 1, background: S.line, margin: '6px 4px' }}/>
        {item('logout', 'Sign out', onSignOut || (() => {}))}
      </div>
    </div>
  );
}

function EmptyState({ model, onPick }) {
  const S = window.SOL;
  const suggestions = [
    'Help me name this project',
    'Summarize this article',
    'Plan a 3-day trip to Kyoto',
  ];
  return (
    <div style={{
      minHeight: 'calc(100vh - 280px)', display: 'flex', flexDirection: 'column',
      alignItems: 'center', justifyContent: 'center', padding: '20px 0', textAlign: 'center',
      animation: 'sol-fade-in 0.35s ease',
    }}>
      <div style={{ position: 'relative', marginBottom: 20 }}>
        <div style={{ position: 'absolute', inset: -30, borderRadius: '50%',
          background: 'radial-gradient(circle, rgba(244,199,123,0.28), transparent 70%)', pointerEvents:'none' }}/>
        <window.SunMark size={64} variant={model}/>
      </div>
      <div style={{ fontFamily: S.serif, fontSize: 44, lineHeight: 1.0, letterSpacing: -1.2,
        color: S.ink, fontWeight: 400, textWrap: 'balance' }}>
        What are we <em style={{ fontStyle: 'italic', color: S.amberDeep }}>working on?</em>
      </div>
      <div style={{ fontFamily: S.sans, fontSize: 14, color: S.ink3, marginTop: 10, maxWidth: 320, lineHeight: 1.5 }}>
        Ask anything. Paste a draft. Share a file.
      </div>
      <div style={{ marginTop: 28, display: 'flex', flexDirection: 'column', gap: 8, width: '100%', maxWidth: 440 }}>
        {suggestions.map(s => (
          <button key={s} onClick={() => onPick(s)} style={{
            padding: '12px 14px', borderRadius: 14, background: S.paper, border: `1px solid ${S.line}`,
            fontFamily: S.sans, fontSize: 14, color: S.ink2, textAlign: 'left', cursor: 'pointer',
            display: 'flex', alignItems: 'center', gap: 10,
          }}
          onMouseEnter={(e)=>e.currentTarget.style.borderColor=S.line2}
          onMouseLeave={(e)=>e.currentTarget.style.borderColor=S.line}>
            <window.Icon name="sparkle" size={14} color={S.amber}/>
            <span style={{ flex: 1 }}>{s}</span>
            <window.Icon name="chevRight" size={14} color={S.ink3}/>
          </button>
        ))}
      </div>
    </div>
  );
}

function Message({ msg, isLast, onCopy, onRegen, onFeedback, onCtxMenu }) {
  const S = window.SOL;
  const [copied, setCopied] = React.useState(false);
  const [fb, setFb] = React.useState(null);

  if (msg.role === 'switch') {
    const names = { solus: 'Apollo 1', apollo: 'Apollo 1 Max' };
    const from = msg.from || 'solus', to = msg.to || 'apollo';
    return (
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, margin: '8px 0 18px', userSelect: 'none' }}>
        <div style={{ flex: 1, height: 1, background: `linear-gradient(to right, transparent, ${S.line2})` }}/>
        <div style={{
          display: 'flex', alignItems: 'center', gap: 7,
          padding: '5px 13px 5px 9px', borderRadius: 999,
          border: `1px solid ${S.line2}`, background: S.paper,
          fontFamily: S.mono, fontSize: 10.5, color: S.ink3, letterSpacing: 0.3,
          whiteSpace: 'nowrap',
        }}>
          <window.SunMark size={13} variant={from}/>
          <span>{names[from]}</span>
          <span style={{ color: S.line2, fontSize: 13 }}>→</span>
          <window.SunMark size={13} variant={to}/>
          <span style={{ color: to === 'apollo' ? S.amberDeep : S.ink2, fontWeight: 500 }}>{names[to]}</span>
        </div>
        <div style={{ flex: 1, height: 1, background: `linear-gradient(to left, transparent, ${S.line2})` }}/>
      </div>
    );
  }

  if (msg.role === 'user') {
    const imgs = (msg.attachments || []).filter(a => a.type?.startsWith('image'));
    return (
      <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 20 }} onContextMenu={e => onCtxMenu && onCtxMenu(e, msg)}>
        <div style={{ maxWidth: '78%', display: 'flex', flexDirection: 'column', gap: 6, alignItems: 'flex-end' }}>
          {imgs.length > 0 && (
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', justifyContent: 'flex-end' }}>
              {imgs.map((a, i) => (
                <img key={i} src={a.dataUrl} alt="" style={{ maxHeight: 200, maxWidth: 240, borderRadius: 12, objectFit: 'cover', border: `1px solid ${S.line}` }}/>
              ))}
            </div>
          )}
          {msg.text && (
            <div style={{
              background: S.ink, color: S.cream,
              borderRadius: '20px 20px 6px 20px', padding: '10px 14px',
              fontFamily: S.sans, fontSize: 15, lineHeight: 1.5, letterSpacing: -0.05,
              whiteSpace: 'pre-wrap', wordBreak: 'break-word',
            }}>{msg.text}</div>
          )}
        </div>
      </div>
    );
  }
  const renderInline = (str) => {
    const parts = (str || '').split(/(\*\*[^\n]+?\*\*|(?<!\*)\*(?!\*)[^\n]+?(?<!\*)\*(?!\*)|`[^`\n]+`)/g);
    return parts.map((p, i) => {
      if (p.startsWith('**') && p.endsWith('**')) return <strong key={i} style={{ fontWeight: 600 }}>{p.slice(2,-2)}</strong>;
      if (p.startsWith('*') && p.endsWith('*') && !p.startsWith('**')) return <em key={i} style={{ fontStyle: 'italic' }}>{p.slice(1,-1)}</em>;
      if (p.startsWith('`') && p.endsWith('`')) return <code key={i} style={{ fontFamily: S.mono, fontSize: '0.875em', background: S.cream2, padding: '1px 5px', borderRadius: 4 }}>{p.slice(1,-1)}</code>;
      return <React.Fragment key={i}>{p}</React.Fragment>;
    });
  };

  const renderMd = (text, trailingCursor) => {
    if (!text) return null;
    const lines = text.split('\n');
    const out = [];
    let i = 0;
    while (i < lines.length) {
      const l = lines[i];
      // fenced code block
      if (l.startsWith('```')) {
        const code = [];
        i++;
        while (i < lines.length && !lines[i].startsWith('```')) { code.push(lines[i]); i++; }
        out.push(<pre key={i} style={{ background: S.cream2, borderRadius: 8, padding: '10px 14px', fontFamily: S.mono, fontSize: 13, overflowX: 'auto', margin: '6px 0 10px', whiteSpace: 'pre', wordBreak: 'break-all' }}>{code.join('\n')}</pre>);
        i++; continue;
      }
      // headings
      if (l.startsWith('# '))   { out.push(<div key={i} style={{ fontFamily: S.serif, fontSize: 22, fontWeight: 400, margin: '14px 0 4px', color: S.ink, letterSpacing: -0.5, lineHeight: 1.2 }}>{renderInline(l.slice(2))}</div>); i++; continue; }
      if (l.startsWith('## '))  { out.push(<div key={i} style={{ fontSize: 17, fontWeight: 600, margin: '12px 0 3px', color: S.ink, letterSpacing: -0.3, lineHeight: 1.3 }}>{renderInline(l.slice(3))}</div>); i++; continue; }
      if (l.startsWith('### ')) { out.push(<div key={i} style={{ fontSize: 15, fontWeight: 600, margin: '8px 0 2px', color: S.ink }}>{renderInline(l.slice(4))}</div>); i++; continue; }
      // bullet list
      if (l.match(/^[-*]\s+/)) {
        const items = [];
        while (i < lines.length && lines[i].match(/^[-*]\s+/)) {
          items.push(<li key={i} style={{ marginBottom: 3 }}>{renderInline(lines[i].replace(/^[-*]\s+/, ''))}</li>);
          i++;
        }
        out.push(<ul key={'u'+i} style={{ paddingLeft: 22, margin: '4px 0 8px' }}>{items}</ul>);
        continue;
      }
      // numbered list
      if (l.match(/^\d+\.\s+/)) {
        const items = [];
        while (i < lines.length && lines[i].match(/^\d+\.\s+/)) {
          items.push(<li key={i} style={{ marginBottom: 3 }}>{renderInline(lines[i].replace(/^\d+\.\s+/, ''))}</li>);
          i++;
        }
        out.push(<ol key={'o'+i} style={{ paddingLeft: 22, margin: '4px 0 8px' }}>{items}</ol>);
        continue;
      }
      // blank line → small gap
      if (!l.trim()) { out.push(<div key={i} style={{ height: 6 }}/>); i++; continue; }
      // normal line
      out.push(<div key={i} style={{ lineHeight: 1.65, marginBottom: 1 }}>{renderInline(l)}</div>);
      i++;
    }
    if (trailingCursor && out.length > 0) {
      const last = out[out.length - 1];
      if (last.type === 'div') {
        const kids = last.props.children;
        const kidsArr = Array.isArray(kids) ? kids : (kids != null ? [kids] : []);
        out[out.length - 1] = React.cloneElement(last, { key: last.key }, ...kidsArr, trailingCursor);
      } else {
        out.push(<span key="cur">{trailingCursor}</span>);
      }
    }
    return out;
  };

  const copy = () => {
    try { navigator.clipboard.writeText(msg.text); } catch(e) {}
    setCopied(true); setTimeout(()=>setCopied(false), 1400);
    onCopy && onCopy();
  };

  return (
    <div style={{ marginBottom: 22 }} onContextMenu={e => onCtxMenu && onCtxMenu(e, msg)}>
      <div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
        <window.SunMark size={24} variant={msg.model || 'solus'} style={{ marginTop: 2 }}/>
        <div style={{ flex: 1 }}>
          <div style={{
            fontFamily: S.sans, fontSize: 15.5, color: S.ink, letterSpacing: -0.05,
            wordBreak: 'break-word',
          }}>
            {msg.streaming && !msg.text
              ? <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, lineHeight: 1.6 }}>
                  <span style={{ fontFamily: S.sans, fontSize: 13, color: S.ink3, letterSpacing: 0.1 }}>Thinking</span>
                  <span style={{ display: 'inline-flex', gap: 4, alignItems: 'center', paddingBottom: 1 }}>
                    {[0, 1, 2].map(i => (
                      <span key={i} style={{
                        display: 'inline-block', width: 4, height: 4, borderRadius: '50%',
                        background: S.amberDeep || S.ink3,
                        animation: `sol-thinking-dot 1.5s ease-in-out ${i * 0.2}s infinite`,
                      }}/>
                    ))}
                  </span>
                </span>
              : renderMd(msg.text, msg.streaming && msg.text
                  ? <span key="caret" style={{ display:'inline-block', width:2, height:13, background:S.ink2, verticalAlign:'-2px', marginLeft:2, opacity:0.7, animation:'sol-blink 1s steps(2) infinite' }}/>
                  : undefined)}
          </div>
          {!msg.streaming && (
            <div style={{ display: 'flex', gap: 2, marginTop: 10, marginLeft: -6 }}>
              <MsgAction icon="copy" label={copied ? 'Copied' : 'Copy'} active={copied} onClick={copy}/>
              {isLast && <MsgAction icon="refresh" label="Regenerate" onClick={onRegen}/>}
              <MsgAction icon="thumbUp" label="Good" active={fb==='up'} onClick={()=>{ setFb('up'); onFeedback('up'); }}/>
              <MsgAction icon="thumbDown" label="Bad" active={fb==='down'} onClick={()=>{ setFb('down'); onFeedback('down'); }}/>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function MsgAction({ icon, label, active, onClick }) {
  const S = window.SOL;
  const [hover, setHover] = React.useState(false);
  return (
    <button onClick={onClick}
      onMouseEnter={()=>setHover(true)} onMouseLeave={()=>setHover(false)}
      title={label}
      style={{
        display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5,
        height: 30, padding: active || hover ? '0 10px' : '0 7px',
        borderRadius: 8, border: 'none', background: active ? S.cream2 : (hover ? S.paper : 'transparent'),
        color: active ? S.amberDeep : S.ink3, cursor: 'pointer',
        fontFamily: S.sans, fontSize: 12, fontWeight: 500,
        transition: 'background 0.15s, color 0.15s, padding 0.15s',
      }}>
      <window.Icon name={icon} size={14} color="currentColor" strokeWidth={1.8}/>
      {(hover || active) && <span>{label}</span>}
    </button>
  );
}

function StreamingIndicator() {
  const S = window.SOL;
  return (
    <div style={{ padding: '4px 0', display: 'flex', alignItems: 'center', gap: 10 }}>
      <div style={{
        width: 20, height: 20, borderRadius: '50%',
        background: `radial-gradient(circle at 30% 30%, ${S.sun}, ${S.amber})`,
        animation: 'sol-pulse 1.4s ease-in-out infinite',
      }}/>
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontFamily: S.mono, fontSize: 11, color: S.ink3, letterSpacing: 0.4 }}>
        thinking
        <span style={{ display: 'inline-flex', gap: 3, alignItems: 'center' }}>
          {[0,1,2].map(i => <span key={i} style={{ display:'inline-block', width:3, height:3, borderRadius:'50%', background:S.ink3, animation:`sol-thinking-dot 1.5s ease-in-out ${i*0.2}s infinite` }}/>)}
        </span>
      </span>
    </div>
  );
}

function Composer({ value, setValue, onSend, onStop, isStreaming, onAttach, onVoice, taRef, attachments = [], onRemoveAttachment }) {
  const S = window.SOL;
  const hasContent = value.trim().length > 0 || attachments.length > 0;
  return (
    <div style={{
      background: S.paper, border: `1px solid ${S.line2}`, borderRadius: 24,
      boxShadow: '0 6px 24px rgba(28,26,23,0.06)',
    }}>
      {attachments.length > 0 && (
        <div style={{ display: 'flex', gap: 8, padding: '10px 12px 0', flexWrap: 'wrap' }}>
          {attachments.map((a, i) => (
            <div key={i} style={{ position: 'relative', flexShrink: 0 }}>
              {a.type?.startsWith('image') ? (
                <img src={a.dataUrl} alt="" style={{ width: 60, height: 60, borderRadius: 10, objectFit: 'cover', display: 'block', border: `1px solid ${S.line}` }}/>
              ) : (
                <div style={{ height: 40, maxWidth: 140, borderRadius: 10, background: S.cream2, display: 'flex', alignItems: 'center', gap: 6, padding: '0 10px', border: `1px solid ${S.line}` }}>
                  <window.Icon name="file" size={14} color={S.ink2}/>
                  <span style={{ fontFamily: S.sans, fontSize: 11, color: S.ink2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: 90 }}>{a.name || 'file'}</span>
                </div>
              )}
              <button onClick={() => onRemoveAttachment(i)} style={{ position: 'absolute', top: -5, right: -5, width: 18, height: 18, borderRadius: '50%', border: 'none', background: S.ink, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                <window.Icon name="close" size={9} color={S.cream}/>
              </button>
            </div>
          ))}
        </div>
      )}
      <div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 8px 8px 12px' }}>
        <button onClick={onAttach} title="Attach" style={composerBtn(S)}>
          <window.Icon name="paperclip" size={18} color={S.ink2}/>
        </button>
        <textarea
          ref={taRef}
          value={value}
          onChange={e => setValue(e.target.value)}
          onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); onSend(); } }}
          placeholder="Ask anything…"
          rows={1}
          style={{
            flex: 1, border: 'none', outline: 'none', background: 'transparent', resize: 'none',
            fontFamily: S.sans, fontSize: 16, lineHeight: 1.5, color: S.ink,
            minHeight: 26, maxHeight: 180, padding: '4px 4px', letterSpacing: -0.05,
          }}
        />
        <button onClick={onVoice} title="Voice" style={composerBtn(S)}>
          <window.Icon name="mic" size={18} color={S.ink2}/>
        </button>
        {isStreaming ? (
          <button onClick={onStop} title="Stop" style={{
            width: 38, height: 38, borderRadius: 12, border: 'none', background: S.ink,
            display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', flexShrink: 0,
          }}>
            <window.Icon name="stop" size={16} color={S.cream}/>
          </button>
        ) : (
          <button onClick={onSend} disabled={!hasContent} title="Send" style={{
            width: 38, height: 38, borderRadius: 12, border: 'none', cursor: hasContent ? 'pointer' : 'default',
            background: hasContent ? `radial-gradient(circle at 30% 30%, ${S.sun}, ${S.amber})` : S.cream2,
            color: hasContent ? S.ink : S.ink3,
            display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
            boxShadow: hasContent ? '0 0 0 1px rgba(196,122,34,0.2), 0 4px 12px rgba(232,155,60,0.35)' : 'none',
            transition: 'background 0.2s, box-shadow 0.2s, color 0.2s',
          }}>
            <window.Icon name="send" size={16} color="currentColor" strokeWidth={1.8}/>
          </button>
        )}
      </div>
    </div>
  );
}
function composerBtn(S) {
  return {
    width: 34, height: 34, borderRadius: 10, border: 'none', background: 'transparent',
    display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', flexShrink: 0,
  };
}

function AttachMenu({ model, tier, onClose, onUpgrade, onPickFile }) {
  const S = window.SOL;
  React.useEffect(() => {
    const h = (e) => { if (!e.target.closest('[data-at]')) onClose(); };
    setTimeout(() => document.addEventListener('click', h), 0);
    return () => document.removeEventListener('click', h);
  }, []);
  const items = [
    { icon: 'camera', label: 'Take photo', accept: 'image/*', capture: 'environment' },
    { icon: 'image', label: 'Photos', accept: 'image/*', capture: null },
    { icon: 'file', label: 'Files', accept: '.pdf,.doc,.docx,.txt,.md,.csv', capture: null },
    { icon: 'video', label: 'Video', accept: 'video/*', capture: null, locked: model !== 'apollo' },
  ];
  return (
    <div data-at style={{
      position: 'absolute', bottom: 'calc(100% + 4px)', left: 8, width: 220,
      background: S.paper, border: `1px solid ${S.line2}`, borderRadius: 14,
      boxShadow: '0 12px 40px rgba(28,26,23,0.18)', padding: 6, zIndex: 40,
      display: 'flex', flexDirection: 'column', gap: 2,
      animation: 'sol-fade-in 0.15s ease',
    }}>
      {items.map(it => (
        <button key={it.label} onClick={() => {
          if (it.locked) { onClose(); onUpgrade(); return; }
          onPickFile(it.accept, it.capture);
          onClose();
        }} style={{
          padding: '9px 10px', borderRadius: 10, border: 'none',
          background: 'transparent', cursor: 'pointer', textAlign: 'left',
          display: 'flex', alignItems: 'center', gap: 10, fontFamily: S.sans, fontSize: 13.5, color: S.ink,
          opacity: it.locked ? 0.6 : 1, width: '100%',
        }}
        onMouseEnter={(e) => e.currentTarget.style.background = S.cream2}
        onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
          <window.Icon name={it.icon} size={16} color={S.ink2}/>
          <span style={{flex:1}}>{it.label}</span>
          {it.locked && <span style={{ fontFamily: S.mono, fontSize: 9, background: S.ink, color: S.cream, padding: '2px 5px', borderRadius: 4 }}>PLUS</span>}
        </button>
      ))}
    </div>
  );
}

function VoiceModal({ onClose }) {
  const S = window.SOL;
  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(28,26,23,0.5)', zIndex: 100,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      animation: 'sol-fade-in 0.2s ease',
    }}>
      <div onClick={e=>e.stopPropagation()} style={{
        background: S.paper, borderRadius: 24, padding: 40, textAlign: 'center', minWidth: 320,
      }}>
        <div style={{
          width: 120, height: 120, borderRadius: '50%', margin: '0 auto 20px',
          background: `radial-gradient(circle at 30% 30%, ${S.sun}, ${S.amber})`,
          animation: 'sol-pulse 1.8s ease-in-out infinite',
        }}/>
        <div style={{ fontFamily: S.serif, fontSize: 28, color: S.ink, marginBottom: 6 }}>Listening…</div>
        <div style={{ fontFamily: S.sans, fontSize: 13, color: S.ink3, marginBottom: 20 }}>Speak naturally. Click anywhere to stop.</div>
        <button onClick={onClose} style={{
          padding: '10px 22px', border: `1px solid ${S.line2}`, borderRadius: 999,
          background: 'transparent', color: S.ink, fontFamily: S.sans, fontSize: 13, cursor: 'pointer',
        }}>Stop</button>
      </div>
    </div>
  );
}

window.ChatArea = ChatArea;
