// Primus IQ — Chat screen components

// HITL approval card: the agent proposed a report plan (template + outline);
// the user picks/edits and approves, edits, or rejects before generation starts.
const ReportPlanCard = ({ plan, onDecide }) => {
  const args = plan.args || {};
  const [templates, setTemplates] = React.useState([]);
  const [template, setTemplate] = React.useState(args.template_id || 'primus_research');
  React.useEffect(() => {
    PrimusAPI.listReportTemplates()
      .then(r => setTemplates((r && r.templates) || []))
      .catch(() => setTemplates([
        { id: 'primus_research', name: 'Primus Research' },
        { id: 'default', name: 'Default Report' },
      ]));
  }, []);
  // If the proposed template_id isn't a real bundle, snap to primus_research once templates load.
  React.useEffect(() => {
    if (templates.length && !templates.some(t => t.id === template)) {
      setTemplate(templates.some(t => t.id === 'primus_research') ? 'primus_research' : templates[0].id);
    }
  }, [templates]);
  const [outlineText, setOutlineText] = React.useState((args.outline || []).join('\n'));

  const buildOutline = () => outlineText.split('\n').map(s => s.trim()).filter(Boolean);

  const approve = () => {
    const outline = buildOutline();
    const changed = template !== args.template_id ||
      outline.join('|') !== (args.outline || []).join('|');
    if (changed) {
      // Edit: replace the tool-call args entirely (must include all fields).
      // Spread the original args first so agent-set fields the card doesn't show
      // (e.g. review_mode) are preserved, then override the two the user edits.
      onDecide({
        type: 'edit',
        edited_action: {
          name: plan.action,
          args: { ...args, template_id: template, outline },
        },
      });
    } else {
      onDecide({ type: 'approve' });
    }
  };

  return (
    <div style={{
      maxWidth: 720, margin: '8px auto 16px', padding: '16px 18px',
      background: 'var(--bg-card)', border: '1px solid var(--line-2)', borderRadius: 14,
      boxShadow: 'var(--shadow-sm)',
    }}>
      <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--ink)', marginBottom: 4 }}>
        📄 Report plan — your approval needed
      </div>
      {args.title && (
        <div style={{ fontSize: 13, color: 'var(--mute)', marginBottom: 12 }}>{args.title}</div>
      )}

      <label style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--mute)' }}>Template</label>
      <select value={template} onChange={e => setTemplate(e.target.value)} style={{
        width: '100%', margin: '4px 0 12px', padding: '7px 9px', borderRadius: 8,
        border: '1px solid var(--line-2)', fontFamily: 'inherit', fontSize: 13,
        background: 'var(--bg)', color: 'var(--ink)',
      }}>
        {templates.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
      </select>

      <label style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--mute)' }}>
        Outline (one section per line)
      </label>
      <textarea value={outlineText} onChange={e => setOutlineText(e.target.value)} rows={6} style={{
        width: '100%', margin: '4px 0 12px', padding: '8px 10px', borderRadius: 8,
        border: '1px solid var(--line-2)', fontFamily: 'inherit', fontSize: 13, resize: 'vertical',
        background: 'var(--bg)', color: 'var(--ink)', lineHeight: 1.5,
      }} />

      <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
        <button onClick={() => onDecide({ type: 'reject' })} style={{
          padding: '7px 14px', borderRadius: 8, fontSize: 12.5, fontWeight: 600,
          background: 'none', color: 'var(--mute)', border: '1px solid var(--line-2)',
          cursor: 'pointer', fontFamily: 'inherit',
        }}>Reject</button>
        <button onClick={approve} style={{
          padding: '7px 16px', borderRadius: 8, fontSize: 12.5, fontWeight: 600,
          background: 'var(--maroon)', color: 'white', border: 'none',
          cursor: 'pointer', fontFamily: 'inherit',
        }}>Approve &amp; generate →</button>
      </div>
    </div>
  );
};

const ChatMsgBubble = ({ msg }) => {
  const isHuman = msg.role === 'human';
  const aiHtml = React.useMemo(() => {
    if (isHuman) return null;
    try {
      return DOMPurify.sanitize(marked.parse(msg.content || ''));
    } catch (_) {
      return msg.content || '';
    }
  }, [isHuman, msg.content]);

  return (
    <div style={{ display: 'flex', justifyContent: isHuman ? 'flex-end' : 'flex-start', marginBottom: 18 }}>
      {!isHuman && (
        <div style={{
          width: 26, height: 26, borderRadius: 13,
          background: 'var(--maroon)', color: 'white',
          display: 'grid', placeItems: 'center',
          fontSize: 10.5, fontWeight: 700, flexShrink: 0,
          marginRight: 10, marginTop: 2,
        }}>P</div>
      )}
      <div className={isHuman ? 'chat-msg-bubble-content' : 'chat-msg-bubble-content prose'} style={{
        maxWidth: isHuman ? 520 : 820,
        background: isHuman ? 'var(--maroon)' : 'transparent',
        color: isHuman ? 'white' : 'var(--ink)',
        borderRadius: isHuman ? '14px 14px 4px 14px' : '4px 14px 14px 14px',
        padding: isHuman ? '11px 15px' : '2px 0px',
        fontSize: 16.5, lineHeight: 1.7,
        border: 'none',
        boxShadow: isHuman ? '0 2px 8px rgba(122,28,28,0.18)' : 'none',
        whiteSpace: isHuman ? 'pre-wrap' : 'normal', wordBreak: 'break-word',
      }}
        {...(!isHuman ? { dangerouslySetInnerHTML: { __html: aiHtml } } : { children: msg.content })}
      />
    </div>
  );
};

const ChatThinking = ({ message }) => (
  <div style={{ display: 'flex', alignItems: 'flex-start', marginBottom: 18 }}>
    <div style={{
      width: 26, height: 26, borderRadius: 13,
      background: 'var(--maroon)', color: 'white',
      display: 'grid', placeItems: 'center',
      fontSize: 10.5, fontWeight: 700, flexShrink: 0, marginRight: 10, marginTop: 2,
    }}>P</div>
    <div style={{
      background: 'transparent', border: 'none',
      borderRadius: '4px 14px 14px 14px',
      padding: '11px 0px', display: 'flex', alignItems: 'center', gap: 9,
    }}>
      <span style={{
        width: 13, height: 13, borderRadius: 999,
        border: '2px solid var(--maroon-20)', borderTopColor: 'var(--maroon)',
        display: 'inline-block', animation: 'pi-spin 0.7s linear infinite', flexShrink: 0,
      }} />
      <span style={{ fontSize: 13, color: 'var(--mute)', fontStyle: 'italic' }}>
        {message || 'Thinking…'}
      </span>
    </div>
  </div>
);

const ChatComposer = ({ onSend, disabled, onAttach, attachedFile, onRemoveAttach, attachUploading, attachError, onClearAttachError }) => {
  const [val, setVal]         = React.useState('');
  const [focused, setFocused] = React.useState(false);
  const fileRef               = React.useRef(null);

  const submit = () => {
    const t = val.trim();
    if (!t || disabled) return;
    setVal('');
    onSend(t);
  };
  const onKD = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submit(); } };

  return (
    <div style={{ padding: '10px 20px 16px', background: 'transparent' }}>
      {/* Hidden file input */}
      <input
        ref={fileRef} type="file" style={{ display: 'none' }}
        onChange={e => { const f = e.target.files?.[0]; if (f) onAttach?.(f); e.target.value = ''; }}
      />

      {/* Attached file chip */}
      {attachedFile && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 6 }}>
          <div style={{
            display: 'inline-flex', alignItems: 'center', gap: 6,
            padding: '4px 10px', borderRadius: 8,
            background: 'var(--bg-soft)', border: '1px solid var(--line-2)',
            fontSize: 12, color: 'var(--ink-2)', maxWidth: 280,
          }}>
            <svg width="12" height="12" viewBox="0 0 16 16" fill="none">
              <path d="M4 2h6l4 4v8a1 1 0 01-1 1H3a1 1 0 01-1-1V3a1 1 0 011-1z" stroke="currentColor" strokeWidth="1.4"/>
              <path d="M10 2v4h4" stroke="currentColor" strokeWidth="1.4"/>
            </svg>
            <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
              {attachUploading ? 'Uploading…' : attachedFile.name}
            </span>
            {!attachUploading && (
              <button onClick={onRemoveAttach} style={{
                background: 'none', border: 'none', cursor: 'pointer',
                color: 'var(--mute)', padding: 0, lineHeight: 1, flexShrink: 0,
              }}>×</button>
            )}
          </div>
        </div>
      )}

      {/* Attach rejection (too big / unsupported) */}
      {attachError && (
        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8,
          marginBottom: 6, padding: '7px 11px', borderRadius: 8,
          background: 'var(--maroon-06)', border: '1px solid var(--maroon-20)',
          fontSize: 12.5, color: 'var(--maroon)',
        }}>
          <span>{attachError}</span>
          <button onClick={onClearAttachError} style={{
            background: 'none', border: 'none', cursor: 'pointer',
            color: 'var(--maroon)', padding: 0, lineHeight: 1, flexShrink: 0, fontSize: 14,
          }}>×</button>
        </div>
      )}

      <div style={{
        background: 'var(--bg-card)', borderRadius: 14,
        border: `1px solid ${focused ? 'var(--maroon-20)' : 'var(--line-2)'}`,
        boxShadow: focused ? '0 0 0 3px var(--maroon-06)' : '0 2px 6px rgba(60,30,20,0.04)',
        transition: 'all 0.18s', padding: '11px 11px 7px 15px',
      }}>
        <textarea
          value={val} onChange={e => setVal(e.target.value)}
          onFocus={() => setFocused(true)} onBlur={() => setFocused(false)}
          onKeyDown={onKD} rows={2} disabled={disabled}
          placeholder="Ask Primus IQ…"
          style={{
            width: '100%', border: 'none', resize: 'none', outline: 'none',
            fontFamily: 'inherit', fontSize: 15.5, color: 'var(--ink)',
            background: 'transparent', lineHeight: 1.5, padding: 0,
            opacity: disabled ? 0.5 : 1,
          }}
        />
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 4 }}>
          {/* Paperclip */}
          <button
            onClick={() => fileRef.current?.click()}
            disabled={disabled || attachUploading}
            title="Attach a document"
            style={{
              width: 30, height: 30, borderRadius: 7,
              background: 'transparent', border: 'none',
              cursor: disabled || attachUploading ? 'default' : 'pointer',
              color: 'var(--mute)', display: 'grid', placeItems: 'center',
              opacity: disabled || attachUploading ? 0.4 : 1,
              transition: 'all 0.15s',
            }}
          >
            <svg width="14" height="14" viewBox="0 0 16 16" fill="none">
              <path d="M13.5 7.5l-6.5 6.5a4 4 0 01-5.66-5.66l7-7a2.5 2.5 0 013.54 3.54l-7.07 7.07a1 1 0 01-1.41-1.41l6.5-6.5"
                stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/>
            </svg>
          </button>

          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <span style={{ fontSize: 11, color: 'var(--mute)' }}>{val.length} / 8,000</span>
            <button onClick={submit} disabled={!val.trim() || disabled} style={{
              width: 32, height: 32, borderRadius: 8,
              background: val.trim() && !disabled ? 'var(--maroon)' : 'var(--bg-soft)',
              color: val.trim() && !disabled ? 'white' : 'var(--mute-2)',
              border: 'none', cursor: val.trim() && !disabled ? 'pointer' : 'default',
              display: 'grid', placeItems: 'center', transition: 'all 0.15s',
            }}>
              <svg width="12" height="12" viewBox="0 0 16 16" fill="none">
                <path d="M8 12V4M4 8l4-4 4 4" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
              </svg>
            </button>
          </div>
        </div>
      </div>
      <div style={{ textAlign: 'center', marginTop: 6 }}>
        <span style={{ fontSize: 10.5, color: 'var(--mute-2)' }}>Enter to send · Shift + Enter for new line</span>
      </div>
    </div>
  );
};

const ScreenNewChat = ({ setRoute, openAddSources, currentUser, maintenance = false,
                         sessions = [], activeId = null, setActiveId, onSessionsChanged, onSessionCreated,
                         initialMessage, initialProjectId, initialDoc, onInitialConsumed, projects = [], onOpenProject }) => {
  const [messages,        setMessages]        = React.useState([]);
  const [streaming,       setStreaming]       = React.useState(false);
  const [progressMsg,     setProgressMsg]     = React.useState('');
  const [streamingText,   setStreamingText]   = React.useState('');
  const [attachedFile,    setAttachedFile]    = React.useState(null);
  const [attachUploading, setAttachUploading] = React.useState(false);
  const [attachError,     setAttachError]     = React.useState(null);
  const [pendingApproval, setPendingApproval] = React.useState(null);  // HITL report-plan card
  const abortRef       = React.useRef(null);
  const bottomRef      = React.useRef(null);
  const scrollRef      = React.useRef(null);
  const stickRef       = React.useRef(true);    // follow streaming unless the user scrolled up
  const justCreatedRef = React.useRef(false);
  const initialSentRef = React.useRef(null);   // tracks which initialMessage was auto-sent

  // Stream watchdog — if the SSE goes silent this long (e.g. the upstream model is
  // 503/timing out), stop waiting and surface a clear error instead of an endless
  // spinner. Reset on every event; cleared on any terminal event.
  const watchdogRef = React.useRef(null);
  const WATCHDOG_MS = 90000;
  const clearWatchdog = () => { if (watchdogRef.current) { clearTimeout(watchdogRef.current); watchdogRef.current = null; } };
  const armWatchdog = () => {
    clearWatchdog();
    watchdogRef.current = setTimeout(() => {
      watchdogRef.current = null;
      if (abortRef.current) { try { abortRef.current(); } catch (_) {} abortRef.current = null; }
      setStreaming(false); setStreamingText('');
      setMessages(prev => [...prev, {
        message_id: `err-${Date.now()}`, role: 'ai',
        content: 'The model is taking longer than expected and may be temporarily unavailable. Please try again in a moment.',
        created_at: new Date().toISOString(),
      }]);
    }, WATCHDOG_MS);
  };

  const hour      = new Date().getHours();
  const greeting  = hour < 12 ? 'Good morning' : hour < 18 ? 'Good afternoon' : 'Good evening';
  const firstName = currentUser?.full_name?.split(' ')?.[0] || 'there';

  React.useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages, streaming]);

  // Follow streaming tokens as they arrive — unless the user scrolled up to re-read
  // mid-stream. "Following" is tracked by onScroll (real scrolls only), not measured
  // here: by the time this runs the new token has already grown the content, which
  // would make an in-effect nearBottom check read as "far from bottom" and get stuck.
  React.useEffect(() => {
    const el = scrollRef.current;
    if (el && stickRef.current) el.scrollTop = el.scrollHeight;
  }, [streamingText]);

  React.useEffect(() => {
    if (justCreatedRef.current) { justCreatedRef.current = false; return; }
    if (abortRef.current) { abortRef.current(); abortRef.current = null; }
    clearWatchdog();
    setStreaming(false);
    if (!activeId) { setMessages([]); return; }
    PrimusAPI.getChatMessages(activeId)
      .then(data => setMessages(data || []))
      .catch(() => setMessages([]));
  }, [activeId]);

  const handleAttach = async (file) => {
    setAttachError(null);
    setAttachedFile(file);
    // If we already have a session, upload immediately.
    // If not, we upload after the session is created on first send.
    if (activeId) {
      setAttachUploading(true);
      try {
        await PrimusAPI.attachChatDocument(activeId, file);
      } catch (ex) {
        setAttachedFile(null);
        setAttachError(ex.message || 'Could not attach this file.');
      } finally {
        setAttachUploading(false);
      }
    }
  };

  const handleSend = async (text, docOverride) => {
    // docOverride lets the project-composer launch carry a file straight into the
    // first send (state set + immediate send would race the closure).
    const fileToAttach = docOverride || attachedFile;
    let sessionId = activeId;
    if (!sessionId) {
      try {
        const sess = await PrimusAPI.createChatSession(initialProjectId ? { project_id: initialProjectId } : {});
        sessionId = sess.session_id;
        justCreatedRef.current = true;
        setActiveId && setActiveId(sessionId);
        // Show it in the sidebar NOW (top, carries project_id). Don't refetch here —
        // a message-less session sorts to the bottom; the post-reply refresh reconciles
        // it to the top with its real title once last_message_at is set.
        onSessionCreated && onSessionCreated(sess);
      } catch (ex) { console.error('Failed to create session:', ex); return; }
    }

    // Show the user's message + loader immediately, so feedback starts at "send" and
    // covers the file-save phase too (not only after the AI starts).
    const humanMsg = {
      message_id: `tmp-${Date.now()}`, role: 'human',
      content: text, created_at: new Date().toISOString(),
    };
    setMessages(prev => [...prev, humanMsg]);
    setStreaming(true);
    setProgressMsg(fileToAttach ? 'Saving your file…' : 'Routing your query…');

    // If a file was attached but not yet uploaded, save it now (loader already showing).
    // If it's rejected (too big / unsupported), surface the message and abort the send —
    // summarizing a doc that didn't attach makes no sense.
    if (fileToAttach && !attachUploading) {
      try {
        setAttachUploading(true);
        await PrimusAPI.attachChatDocument(sessionId, fileToAttach);
        setAttachedFile(null);
        setProgressMsg('Routing your query…');
      } catch (ex) {
        setStreaming(false);
        setMessages(prev => prev.filter(m => m.message_id !== humanMsg.message_id));
        setAttachError(ex.message || 'Could not attach this file.');
        setAttachedFile(null);
        setAttachUploading(false);
        return;
      } finally {
        setAttachUploading(false);
      }
    }

    abortRef.current = PrimusAPI.streamChatMessage(sessionId, text, makeStreamHandler());
    armWatchdog();   // catch a total hang (no events ever arrive)
  };

  // Build a fresh SSE event handler (own tokenBuffer) — reused by both send and
  // resume so the streaming behaviour stays identical.
  const makeStreamHandler = () => {
    let tokenBuffer = '';
    return (ev) => {
      armWatchdog();   // any activity resets the silence timer
      if (ev.type === 'progress') {
        setProgressMsg(ev.message || 'Thinking…');
      } else if (ev.type === 'token') {
        tokenBuffer += ev.content || '';
        setStreamingText(tokenBuffer);
      } else if (ev.type === 'interrupt') {
        // HITL: the agent proposed a report plan — pause and show the approval card.
        clearWatchdog();   // intentional pause for user input, not a stall
        setStreaming(false);
        setStreamingText('');
        tokenBuffer = '';
        setPendingApproval({
          action: ev.action,
          args: ev.args || {},                       // { title, brief, template_id, outline }
          allowed: ev.allowed_decisions || ['approve', 'edit', 'reject'],
        });
      } else if (ev.type === 'message') {
        const content = ev.content || '';
        clearWatchdog();
        setStreaming(false);
        setStreamingText('');
        tokenBuffer = '';
        // Show immediately, then replace with authoritative DB state
        if (content) setMessages(prev => [...prev, { message_id: `ai-${Date.now()}`, role: 'ai', content, created_at: new Date().toISOString() }]);
        PrimusAPI.getChatMessages(sessionId)
          .then(data => { if (data && data.length) setMessages(data); })
          .catch(() => {});
        onSessionsChanged && onSessionsChanged();   // reconcile title/order once the first reply lands
      } else if (ev.type === 'done') {
        const finalContent = tokenBuffer;
        clearWatchdog();
        setStreamingText('');
        tokenBuffer = '';
        setStreaming(false);
        // Show immediately, then replace with authoritative DB state
        if (finalContent) setMessages(prev => [...prev, { message_id: `ai-${Date.now()}`, role: 'ai', content: finalContent, created_at: new Date().toISOString() }]);
        PrimusAPI.getChatMessages(sessionId)
          .then(data => { if (data && data.length) setMessages(data); })
          .catch(() => {});
        onSessionsChanged && onSessionsChanged();
      } else if (ev.type === 'error') {
        clearWatchdog();
        setStreaming(false);
        setStreamingText('');
        tokenBuffer = '';
        const errMsg = ev.detail || ev.message || 'Please try again.';
        setMessages(prev => [...prev, {
          message_id: `err-${Date.now()}`, role: 'ai',
          content: `Something went wrong: ${errMsg}`,
          created_at: new Date().toISOString(),
        }]);
      }
    };
  };

  // HITL: submit the user's decision on a proposed report plan and stream the rest.
  const submitApproval = (decision) => {
    setPendingApproval(null);
    setStreaming(true);
    setProgressMsg('Submitting your decision…');
    // activeId is the component-scope session id; `sessionId` only exists as a
    // local inside handleSend, so referencing it here threw a ReferenceError
    // before the fetch ever fired (no /resume request reached the server).
    abortRef.current = PrimusAPI.resumeChatMessage(activeId, decision, makeStreamHandler());
    armWatchdog();
  };

  // Auto-send the first message when arriving from a project composer. handleSend
  // creates the session with initialProjectId, so it's project-scoped end-to-end.
  React.useEffect(() => {
    if (initialMessage && initialSentRef.current !== initialMessage) {
      initialSentRef.current = initialMessage;
      handleSend(initialMessage, initialDoc);   // carry an attached doc from the project composer
      onInitialConsumed && onInitialConsumed();
    }
  }, [initialMessage]);

  const activeSession = sessions.find(s => s.session_id === activeId) || null;
  const sessionProject = activeSession?.project_id
    ? projects.find(p => p.id === activeSession.project_id)
    : null;

  if (activeId) {
    return (
      <div className="chat-surface" style={{ display: 'flex', flexDirection: 'column', height: '100%', minWidth: 0 }}>
        <div style={{
          padding: '13px 24px', borderBottom: '1px solid var(--line)',
          background: 'var(--bg-card)', flexShrink: 0, display: 'flex', alignItems: 'center', minWidth: 0,
        }}>
          {sessionProject && (
            <>
              <button
                onClick={() => onOpenProject && onOpenProject(sessionProject.id)}
                title={`Back to ${sessionProject.name}`}
                style={{
                  fontSize: 14, fontWeight: 600, color: 'var(--maroon)', background: 'none',
                  border: 'none', cursor: 'pointer', padding: 0, fontFamily: 'inherit', flexShrink: 0,
                  maxWidth: 220, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                }}
              >
                {sessionProject.name}
              </button>
              <span style={{ color: 'var(--mute)', margin: '0 8px', flexShrink: 0 }}>›</span>
            </>
          )}
          <span style={{
            fontSize: 14, fontWeight: 600, color: 'var(--ink)',
            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
          }}>
            {activeSession?.title || 'Chat'}
          </span>
        </div>
        <div ref={scrollRef}
             onScroll={(e) => { const el = e.currentTarget; stickRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 120; }}
             className="pi-thin-scroll" style={{ flex: 1, overflowY: 'auto', padding: '24px 0 0' }}>
          <div style={{ maxWidth: 880, margin: '0 auto', padding: '0 24px' }}>
            {messages.map((m, i) => <ChatMsgBubble key={m.message_id || i} msg={m} />)}
            {streaming && !streamingText && <ChatThinking message={progressMsg} />}
            {streaming && streamingText && (
              <ChatMsgBubble msg={{ role: 'ai', content: streamingText, message_id: 'streaming' }} />
            )}
            {pendingApproval && (
              <ReportPlanCard plan={pendingApproval} onDecide={submitApproval} />
            )}
            <div ref={bottomRef} />
          </div>
        </div>
        <div style={{ maxWidth: 880, margin: '0 auto', width: '100%' }}>
          <ChatComposer
            onSend={handleSend}
            disabled={streaming || maintenance}
            onAttach={handleAttach}
            attachedFile={attachedFile}
            onRemoveAttach={() => setAttachedFile(null)}
            attachUploading={attachUploading}
            attachError={attachError}
            onClearAttachError={() => setAttachError(null)}
          />
        </div>
      </div>
    );
  }

  return (
    <div className="chat-surface" style={{
      display: 'flex', flexDirection: 'column', height: '100%',
      alignItems: 'center', justifyContent: 'center',
      padding: '0 48px', minWidth: 0,
    }}>
      <h1 className="serif" style={{
        fontSize: 40, fontWeight: 400, margin: 0, marginBottom: 36,
        letterSpacing: '-0.025em', lineHeight: 1.05, textAlign: 'center',
      }}>
        <span style={{ color: 'var(--mute)' }}>{greeting},</span>{' '}
        <span className="wordmark">{firstName}</span>
      </h1>
      <div style={{ width: 680, maxWidth: '100%' }}>
        <ChatComposer
          onSend={handleSend}
          disabled={maintenance}
          onAttach={handleAttach}
          attachedFile={attachedFile}
          onRemoveAttach={() => setAttachedFile(null)}
          attachUploading={attachUploading}
          attachError={attachError}
          onClearAttachError={() => setAttachError(null)}
        />
        {maintenance && (
          <div style={{ marginTop: 8, fontSize: 12, color: 'var(--mute)', textAlign: 'center' }}>
            Chat is coming soon — temporarily unavailable.
          </div>
        )}
      </div>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, justifyContent: 'center', marginTop: 12 }}>
        {[
          { l: 'Create research',            icon: 'book'   },
          { l: 'Design a framework',         icon: 'apps'   },
          { l: 'Explore Primus expertise',   icon: 'search' },
          { l: 'Help with consulting',       icon: 'folder' },
        ].map(c => (
          <button key={c.l} onClick={() => handleSend(c.l)} style={chipBtn}>
            <PIcon name={c.icon} size={14} color="var(--maroon)" />
            {c.l}
          </button>
        ))}
      </div>
    </div>
  );
};

const composerIconBtn = {
  width: 32, height: 32, borderRadius: 8,
  background: 'transparent', border: '1px solid var(--line-2)',
  cursor: 'pointer', display: 'grid', placeItems: 'center',
  transition: 'all 0.15s',
};
const composerChip = {
  height: 32, padding: '0 10px', borderRadius: 8,
  background: 'transparent', border: '1px solid var(--line-2)',
  cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 6,
  fontSize: 13, color: 'var(--ink-2)', fontFamily: 'inherit', fontWeight: 500,
};
const chipBtn = {
  padding: '8px 14px', borderRadius: 999,
  background: 'var(--bg-card)', border: '1px solid var(--line)',
  fontSize: 13, color: 'var(--ink-2)', fontFamily: 'inherit', fontWeight: 500,
  cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 6,
  transition: 'all 0.15s',
};

Object.assign(window, {
  ChatMsgBubble,
  ChatThinking,
  ChatComposer,
  ScreenNewChat,
  composerIconBtn,
  composerChip,
  chipBtn,
});
