import { useRef, useEffect, useCallback, useState } from 'react';
import { Topbar } from '../../components/layout/Topbar';
import { Send, Download, Zap, Paperclip, X, FileText, AlertCircle } from 'lucide-react';
import { usePageState } from '../../contexts';
import type { RagCitation } from '../../contexts';
import { useLanguage } from '../../contexts/LanguageContext';
import { agenticChat } from '../../api/rag';
import type { SSEMessage } from '../../api/index';

const TOKEN_KEY = 'auth_token';
function authHeader(): Record<string, string> {
  const t = localStorage.getItem(TOKEN_KEY);
  return t ? { Authorization: `Bearer ${t}` } : {};
}

// ── Document context state ─────────────────────────────────────────────────────

interface DocContext {
  filename: string;
  text: string;
  charCount: number;
  truncated: boolean;
  /** 'extracting' while the backend is parsing; 'ready' when text is available; 'error' on failure */
  status: 'extracting' | 'ready' | 'error';
  errorMsg?: string;
}

// ── Agentic-mode types ────────────────────────────────────────────────────────

interface ThinkingStep {
  id: string;
  step: string;
  status: 'running' | 'done';
  intent_type?: string;
  reason?: string;
  requires_decomposition?: boolean;
  sub_queries?: string[];
  query?: string;
  index?: number;
  total?: number;
  found?: number;
  sufficient?: boolean;
  confidence?: number;
  retry?: boolean;
}

const STEP_ICONS: Record<string, string> = {
  intent_analysis: '🔍',
  query_planning: '📋',
  retrieving: '📚',
  grounding_check: '🔗',
};

// ── Helpers ───────────────────────────────────────────────────────────────────

// Map a raw source doc from the backend "retrieved" event to our Citation shape.
function mapSource(s: Record<string, unknown>, idx: number): RagCitation {
  const rawScore = typeof s.score === 'number' ? s.score : 0;
  const displayScore = rawScore <= 1 ? Math.round(rawScore * 100) : Math.round(rawScore);
  return {
    index: idx,
    score: displayScore,
    name: String(s.doc_name ?? ''),
    clause: String(s.clause ?? s.section_title ?? ''),
    snippet: String(s.preview ?? s.text ?? ''),
    docId: s.doc_id ? String(s.doc_id) : undefined,
  };
}

// Parse message text and replace [N] with clickable <button class="cite-ref"> elements.
function renderWithCitations(
  text: string,
  onCiteClick: (n: number) => void,
): React.ReactNode[] {
  const parts = text.split(/(\[\d+\])/g);
  return parts.map((part, i) => {
    const m = part.match(/^\[(\d+)\]$/);
    if (m) {
      const n = parseInt(m[1], 10);
      return (
        <button
          key={i}
          className="cite-ref"
          onClick={() => onCiteClick(n)}
          title={`Jump to source [${n}]`}
        >
          {n}
        </button>
      );
    }
    return part;
  });
}

const MOCK_QUICK = [
  'What does EU AI Act Art. 9 require for risk management?',
  'Which documents need CSMS certification?',
  'Summarize MIIT training data rules',
  'What are high-risk AI categories under Annex III?',
];

export function RagChatPage() {
  // All persistent state lives in PageStateContext — survives route changes
  const { ragState, setRagState, ragStreamingRef, ragAbortRef } = usePageState();
  const { t } = useLanguage();
  const { messages, citations, sessionId, inputDraft } = ragState;

  // Local-only UI state: highlighted citation and streaming indicator
  // These are fine to reset on navigation since they're transient UI feedback
  const [highlightedCit, setHighlightedCit] = useState<number | null>(null);
  const [streaming, setStreaming] = useState(ragStreamingRef.current);
  const [quickPrompts, setQuickPrompts] = useState<string[]>(MOCK_QUICK);

  // P0-1 Agentic mode state
  const [agenticMode, setAgenticMode] = useState(false);
  const [thinkingSteps, setThinkingSteps] = useState<ThinkingStep[]>([]);
  const [thinkingExpanded, setThinkingExpanded] = useState(true);

  // ── Document context state ─────────────────────────────────────────────────
  // Holds the extracted text from the attached file; sent to the backend as
  // conversation context on every message while it is set.
  const [docContext, setDocContext] = useState<DocContext | null>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);

  const bottomRef = useRef<HTMLDivElement>(null);
  const citRailRef = useRef<HTMLDivElement>(null);
  const citItemRefs = useRef<Record<number, HTMLDivElement | null>>({});

  // ── Document context helpers ───────────────────────────────────────────────

  /** Upload file to /rag/upload-context, extract its text, store as context. */
  async function handleFileAttach(file: File) {
    setDocContext({ filename: file.name, text: '', charCount: 0, truncated: false, status: 'extracting' });

    const fd = new FormData();
    fd.append('file', file);

    try {
      const res = await fetch('/api/v1/rag/upload-context', {
        method: 'POST',
        headers: authHeader(),
        body: fd,
      });
      if (!res.ok) {
        const errText = await res.text().catch(() => t.ragchat.attachErrorMsg);
        setDocContext(prev => prev ? { ...prev, status: 'error', errorMsg: errText.slice(0, 120) } : null);
        return;
      }
      const data = await res.json();
      setDocContext({
        filename: data.filename ?? file.name,
        text: data.text ?? '',
        charCount: data.char_count ?? 0,
        truncated: data.truncated ?? false,
        status: 'ready',
      });
    } catch (err) {
      setDocContext(prev => prev
        ? { ...prev, status: 'error', errorMsg: String(err).slice(0, 120) }
        : null
      );
    }
  }

  function handleFileInputChange(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (file) void handleFileAttach(file);
    // Reset so the same file can be re-selected
    e.target.value = '';
  }

  function handleFileDrop(e: React.DragEvent<HTMLDivElement>) {
    e.preventDefault();
    const file = Array.from(e.dataTransfer.files).find(f =>
      /\.(pdf|docx?|txt|md)$/i.test(f.name)
    );
    if (file) void handleFileAttach(file);
  }

  // Fetch quick questions from backend on mount (only once per session)
  useEffect(() => {
    fetch('/api/v1/rag/quick-questions', { headers: authHeader() })
      .then(r => r.json())
      .then(d => {
        if (Array.isArray(d?.questions) && d.questions.length > 0) {
          setQuickPrompts(d.questions.map((q: { question: string }) => q.question));
        }
      })
      .catch(() => { /* keep mock */ });
  }, []);

  // Auto-scroll to latest message
  useEffect(() => {
    bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);

  // Jump to citation N in the rail and highlight it
  const jumpToCitation = useCallback((n: number) => {
    setHighlightedCit(n);
    const el = citItemRefs.current[n];
    if (el) {
      el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }
    setTimeout(() => setHighlightedCit(h => h === n ? null : h), 3000);
  }, []);

  async function send(text?: string) {
    const q = (text ?? inputDraft).trim();
    // Block send while a document is still being extracted
    if (!q || ragStreamingRef.current || docContext?.status === 'extracting') return;

    setRagState(s => ({ ...s, inputDraft: '' }));

    // Show document context badge in user message bubble when active
    const docPrefix = docContext?.status === 'ready'
      ? `📄 ${docContext.filename}\n`
      : '';
    const displayQuery = docPrefix + q;

    const userMsgId = Date.now().toString();
    const assistantId = (Date.now() + 1).toString();

    setRagState(s => ({
      ...s,
      messages: [
        ...s.messages,
        { id: userMsgId, role: 'user', text: displayQuery },
        { id: assistantId, role: 'assistant', text: '' },
      ],
      citations: [],
    }));

    ragStreamingRef.current = true;
    setStreaming(true);
    setHighlightedCit(null);

    // P0-1: reset thinking panel for new query
    if (agenticMode) {
      setThinkingSteps([]);
      setThinkingExpanded(true);
    }

    const ctrl = new AbortController();
    ragAbortRef.current = ctrl;

    if (agenticMode) {
      // ── Agentic path ────────────────────────────────────────────────────
      const newCitations: RagCitation[] = [];

      const handleMessage = (msg: SSEMessage) => {
        if (msg.type === 'session') {
          if (msg.session_id) setRagState(s => ({ ...s, sessionId: msg.session_id! }));

        } else if (msg.type === 'thinking') {
          // Build a stable step id so we can upsert running→done transitions.
          const stepId = `${msg.step}-${msg.retry ? 'retry' : (msg.index ?? 0)}`;
          setThinkingSteps(prev => {
            const idx = prev.findIndex(s => s.id === stepId);
            const stepObj: ThinkingStep = {
              id: stepId,
              step: msg.step ?? '',
              status: (msg.status as 'running' | 'done') ?? 'running',
              intent_type: msg.intent_type,
              reason: msg.reason,
              sub_queries: msg.sub_queries,
              query: msg.query,
              index: msg.index,
              total: msg.total,
              found: msg.found,
              sufficient: msg.sufficient,
              confidence: msg.confidence,
              retry: msg.retry,
            };
            if (idx >= 0) {
              const updated = [...prev];
              updated[idx] = stepObj;
              return updated;
            }
            return [...prev, stepObj];
          });

        } else if (msg.type === 'retrieved' && Array.isArray(msg.docs)) {
          const mapped = (msg.docs as unknown as Record<string, unknown>[]).map((d, i) => mapSource(d, i + 1));
          newCitations.push(...mapped);
          setRagState(s => ({ ...s, citations: [...mapped] }));

        } else if (msg.type === 'chunk' && msg.text) {
          setRagState(s => ({
            ...s,
            messages: s.messages.map(m =>
              m.id === assistantId ? { ...m, text: m.text + msg.text! } : m
            ),
          }));

        } else if (msg.type === 'done') {
          setThinkingExpanded(false);
          setRagState(s => ({
            ...s,
            messages: s.messages.map(m => {
              if (m.id !== assistantId) return m;
              const refs = [...new Set(
                [...m.text.matchAll(/\[(\d+)\]/g)].map(r => parseInt(r[1], 10))
              )].filter(n => n >= 1 && n <= newCitations.length);
              return { ...m, citationRefs: refs };
            }),
          }));

        } else if (msg.type === 'error') {
          setRagState(s => ({
            ...s,
            messages: s.messages.map(m =>
              m.id === assistantId ? { ...m, text: `Error: ${msg.text ?? 'Unknown error'}` } : m
            ),
          }));
        }
      };

      try {
        await agenticChat(
          q, 5, handleMessage,
          (err) => {
            setRagState(s => ({
              ...s,
              messages: s.messages.map(m =>
                m.id === assistantId ? { ...m, text: t.ragchat.apiError } : m
              ),
            }));
            console.error('agenticChat error:', err);
          },
          undefined,
          undefined,
          sessionId ?? undefined,
          ctrl.signal,
          // Pass document context to agentic pipeline
          docContext?.status === 'ready' ? docContext.text : undefined,
          docContext?.status === 'ready' ? docContext.filename : undefined,
        );
      } finally {
        ragStreamingRef.current = false;
        setStreaming(false);
      }

    } else {
      // ── Standard RAG path (unchanged) ───────────────────────────────────
      try {
        const body: Record<string, unknown> = { query: q, top_k: 5 };
        if (sessionId) body.session_id = sessionId;
        // Inject document text as conversation context when a file is attached
        if (docContext?.status === 'ready') {
          body.context_text = docContext.text;
          body.context_filename = docContext.filename;
        }

        const res = await fetch('/api/v1/rag/chat', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', ...authHeader() },
          body: JSON.stringify(body),
          signal: ctrl.signal,
        });

        if (!res.body) throw new Error('No stream');
        const reader = res.body.getReader();
        const dec = new TextDecoder();
        let buffer = '';
        const newCitations: RagCitation[] = [];

        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          buffer += dec.decode(value, { stream: true });

          const blocks = buffer.split('\n\n');
          buffer = blocks.pop() ?? '';

          for (const block of blocks) {
            const dataLine = block.split('\n').find(l => l.startsWith('data: '));
            if (!dataLine) continue;
            const raw = dataLine.slice(6).trim();
            if (!raw) continue;
            try {
              const j = JSON.parse(raw);

              if (j.type === 'session') {
                if (j.session_id) setRagState(s => ({ ...s, sessionId: j.session_id }));

              } else if (j.type === 'retrieved' && Array.isArray(j.docs)) {
                const mapped = j.docs.map((d: Record<string, unknown>, i: number) => mapSource(d, i + 1));
                newCitations.push(...mapped);
                setRagState(s => ({ ...s, citations: [...mapped] }));

              } else if (j.type === 'chunk' && j.text) {
                setRagState(s => ({
                  ...s,
                  messages: s.messages.map(msg =>
                    msg.id === assistantId
                      ? { ...msg, text: msg.text + (j.text as string) }
                      : msg
                  ),
                }));

              } else if (j.type === 'done') {
                setRagState(s => ({
                  ...s,
                  messages: s.messages.map(msg => {
                    if (msg.id !== assistantId) return msg;
                    const refs = [...new Set(
                      [...msg.text.matchAll(/\[(\d+)\]/g)].map(r => parseInt(r[1], 10))
                    )].filter(n => n >= 1 && n <= newCitations.length);
                    return { ...msg, citationRefs: refs };
                  }),
                }));
                break;

              } else if (j.type === 'error') {
                setRagState(s => ({
                  ...s,
                  messages: s.messages.map(msg =>
                    msg.id === assistantId
                      ? { ...msg, text: `Error: ${j.text ?? 'Unknown error'}` }
                      : msg
                  ),
                }));
              }
            } catch { /* malformed JSON chunk, skip */ }
          }
        }
      } catch (e: unknown) {
        if (e instanceof Error && e.name !== 'AbortError') {
          setRagState(s => ({
            ...s,
            messages: s.messages.map(msg =>
              msg.id === assistantId
                ? { ...msg, text: t.ragchat.apiError }
                : msg
            ),
          }));
        }
      } finally {
        ragStreamingRef.current = false;
        setStreaming(false);
      }
    }
  }

  const lastAssistantId = [...messages].reverse().find(m => m.role === 'assistant')?.id;

  return (
    <div className="chat-page">
      <Topbar
        title={t.ragchat.topbarTitle}
        actions={
          <button
            className="btn sm"
            onClick={() => {
              const text = messages.map(m => `${m.role === 'user' ? 'Q' : 'A'}: ${m.text}`).join('\n\n');
              const blob = new Blob([text], { type: 'text/plain' });
              const url = URL.createObjectURL(blob);
              const a = document.createElement('a'); a.href = url; a.download = 'chat-export.txt'; a.click();
              URL.revokeObjectURL(url);
            }}
          >
            <Download size={13} />{t.ragchat.exportBtn}
          </button>
        }
      />

      <div className="chat-body">
        {/* ── History pane ── */}
        <div className="history-pane">
          <div className="history-header">{t.ragchat.quickPromptsHeader}</div>
          {quickPrompts.map(q => (
            <button key={q} className="quick-item" onClick={() => send(q)}>
              {q}
            </button>
          ))}
        </div>

        {/* ── Chat main ── */}
        <div className="chat-main">
          {/* P0-1: Agentic Thinking Panel — shown when agentic mode is active */}
          {agenticMode && thinkingSteps.length > 0 && (
            <div style={{
              margin: '0 0 4px 0',
              border: '1px solid var(--border)',
              borderRadius: 8,
              background: streaming ? 'var(--surface)' : 'var(--surface-2, var(--surface))',
              overflow: 'hidden',
              transition: 'max-height 0.4s ease',
              flexShrink: 0,
            }}>
              {/* Panel header — clickable to collapse/expand */}
              <button
                onClick={() => setThinkingExpanded(x => !x)}
                style={{
                  width: '100%',
                  display: 'flex',
                  alignItems: 'center',
                  gap: 6,
                  padding: '6px 12px',
                  background: 'none',
                  border: 'none',
                  cursor: 'pointer',
                  fontSize: 12,
                  color: streaming ? 'var(--accent, #6366f1)' : 'var(--success-fg, #16a34a)',
                  textAlign: 'left',
                }}
              >
                <span>{streaming ? '⚙' : '✓'}</span>
                <span style={{ fontWeight: 600 }}>
                  {streaming
                    ? t.ragchat.agentThinking
                    : `${t.ragchat.agentDone} · ${thinkingSteps.filter(s => s.status === 'done').length} ${t.ragchat.stepSuffix}`
                  }
                </span>
                <span style={{ marginLeft: 'auto', fontSize: 10 }}>{thinkingExpanded ? '▲' : '▼'}</span>
              </button>
              {/* Step list */}
              {thinkingExpanded && (
                <div style={{ padding: '0 12px 8px' }}>
                  {thinkingSteps.map(step => {
                    const stepLabels: Record<string, string> = {
                      intent_analysis: t.ragchat.stepIntentAnalysis,
                      query_planning:  t.ragchat.stepQueryPlanning,
                      retrieving:      t.ragchat.stepRetrieving,
                      grounding_check: t.ragchat.stepGrounding,
                    };
                    const intentLabels: Record<string, string> = {
                      simple_qa:  t.ragchat.intentSimpleQa,
                      compare:    t.ragchat.intentCompare,
                      multi_hop:  t.ragchat.intentMultiHop,
                      ambiguous:  t.ragchat.intentAmbiguous,
                    };
                    return (
                      <div key={step.id} style={{
                        display: 'flex',
                        alignItems: 'flex-start',
                        gap: 6,
                        fontSize: 12,
                        padding: '3px 0',
                        color: step.status === 'done' ? 'var(--fg)' : 'var(--muted)',
                      }}>
                        <span style={{ width: 16, textAlign: 'center', flexShrink: 0 }}>
                          {step.status === 'running'
                            ? <span style={{ animation: 'spin 1s linear infinite', display: 'inline-block' }}>⟳</span>
                            : (STEP_ICONS[step.step] ?? '·')
                          }
                        </span>
                        <span>
                          <strong>{stepLabels[step.step] ?? step.step}</strong>
                          {/* Intent analysis detail */}
                          {step.step === 'intent_analysis' && step.status === 'done' && step.intent_type && (
                            <span style={{ marginLeft: 6, color: 'var(--muted)' }}>
                              → {intentLabels[step.intent_type] ?? step.intent_type}
                              {step.requires_decomposition && ` · ${t.ragchat.intentNeedsDecomposition}`}
                            </span>
                          )}
                          {/* Query planning detail */}
                          {step.step === 'query_planning' && step.status === 'done' && step.sub_queries && (
                            <span style={{ marginLeft: 6, color: 'var(--muted)' }}>
                              → {step.sub_queries.length} {t.ragchat.subQueriesCountSuffix}
                            </span>
                          )}
                          {/* Retrieval detail */}
                          {step.step === 'retrieving' && (
                            <span style={{ marginLeft: 6, color: 'var(--muted)', wordBreak: 'break-all' }}>
                              {step.total && step.total > 1 && `[${step.index}/${step.total}] `}
                              {step.retry && t.ragchat.retryLabel}
                              {step.query && step.query.length > 50
                                ? step.query.slice(0, 50) + '…'
                                : step.query}
                              {step.status === 'done' && step.found !== undefined && (
                                <span style={{ color: step.found > 0 ? 'var(--success-fg, #16a34a)' : 'var(--warning, #ca8a04)' }}>
                                  {' '}· {step.found} {t.ragchat.chunksFoundSuffix}
                                </span>
                              )}
                            </span>
                          )}
                          {/* Grounding check detail */}
                          {step.step === 'grounding_check' && step.status === 'done' && (
                            <span style={{ marginLeft: 6, color: step.sufficient ? 'var(--success-fg, #16a34a)' : 'var(--warning, #ca8a04)' }}>
                              → {step.sufficient ? t.ragchat.groundingSufficient : t.ragchat.groundingInsufficient}
                              {step.confidence !== undefined && ` (${Math.round(step.confidence * 100)}%)`}
                            </span>
                          )}
                        </span>
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
          )}

          {/* Messages area — accepts drag-and-drop document context attachment */}
          <div
            className="messages"
            onDragOver={e => { e.preventDefault(); e.dataTransfer.dropEffect = 'copy'; }}
            onDrop={handleFileDrop}
          >
            {messages.map(msg => (
              <div key={msg.id} className={`message msg-${msg.role}`}>
                {msg.role === 'assistant' && <div className="msg-avatar">AI</div>}
                <div className="msg-bubble">
                  {msg.role === 'assistant'
                    ? renderWithCitations(msg.text, jumpToCitation)
                    : msg.text
                  }
                  {streaming && msg.id === lastAssistantId && (
                    <span className="blink-cursor">▋</span>
                  )}
                </div>
                {msg.role === 'user' && <div className="msg-avatar user-av">You</div>}
              </div>
            ))}
            <div ref={bottomRef} />
          </div>

          <div className="composer">
            <div className="quick-chips">
              {quickPrompts.slice(0, 3).map(q => (
                <button key={q} className="chip" onClick={() => send(q)}>
                  {q.length > 42 ? q.slice(0, 42) + '…' : q}
                </button>
              ))}
            </div>
            {/* P0-1: Agentic mode toggle */}
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
              <label style={{
                display: 'flex', alignItems: 'center', gap: 5,
                fontSize: 12, color: agenticMode ? 'var(--accent, #6366f1)' : 'var(--muted)',
                cursor: 'pointer', userSelect: 'none',
              }}>
                <input
                  type="checkbox"
                  checked={agenticMode}
                  onChange={e => {
                    setAgenticMode(e.target.checked);
                    setThinkingSteps([]);
                  }}
                  style={{ cursor: 'pointer', accentColor: 'var(--accent, #6366f1)' }}
                />
                <Zap size={11} />
                <span>{t.ragchat.agenticMode}</span>
              </label>
              {agenticMode && (
                <span style={{ fontSize: 11, color: 'var(--muted)' }}>
                  {t.ragchat.agenticModeHint}
                </span>
              )}
            </div>

            {/* ── Document context badge ── */}
            {docContext && (
              <div style={{
                display: 'flex', alignItems: 'center', gap: 8,
                padding: '6px 10px', marginBottom: 6,
                background: docContext.status === 'error'
                  ? 'rgba(220,38,38,0.06)'
                  : docContext.status === 'ready'
                  ? 'rgba(34,197,94,0.06)'
                  : 'rgba(99,102,241,0.06)',
                border: `1px solid ${
                  docContext.status === 'error' ? 'rgba(220,38,38,0.3)'
                  : docContext.status === 'ready' ? 'rgba(34,197,94,0.3)'
                  : 'rgba(99,102,241,0.3)'
                }`,
                borderRadius: 8, fontSize: 12,
              }}>
                {docContext.status === 'extracting' && (
                  <span style={{ animation: 'spin 1s linear infinite', display: 'inline-block', color: 'var(--accent,#6366f1)' }}>⟳</span>
                )}
                {docContext.status === 'ready' && <FileText size={13} color="#16a34a" />}
                {docContext.status === 'error' && <AlertCircle size={13} color="#dc2626" />}

                <span style={{
                  fontWeight: 600, fontSize: 11,
                  color: docContext.status === 'error' ? '#dc2626'
                    : docContext.status === 'ready' ? '#16a34a'
                    : 'var(--accent,#6366f1)',
                  flexShrink: 0,
                }}>
                  {t.ragchat.attachContextBadge}
                </span>

                <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'var(--fg)' }}
                  title={docContext.filename}>
                  {docContext.filename}
                </span>

                {docContext.status === 'ready' && (
                  <span style={{ fontSize: 10, color: 'var(--muted)', flexShrink: 0 }}>
                    {(docContext.charCount / 1000).toFixed(1)}k chars
                    {docContext.truncated ? ` · ${t.ragchat.attachTruncated}` : ''}
                  </span>
                )}
                {docContext.status === 'extracting' && (
                  <span style={{ fontSize: 11, color: 'var(--accent,#6366f1)', flexShrink: 0 }}>
                    {t.ragchat.attachExtracting}
                  </span>
                )}
                {docContext.status === 'error' && (
                  <span style={{ fontSize: 11, color: '#dc2626', flexShrink: 0 }} title={docContext.errorMsg}>
                    {t.ragchat.attachError}
                  </span>
                )}

                {/* Clear button */}
                <button
                  onClick={() => setDocContext(null)}
                  style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px 4px', color: 'var(--muted)', display: 'flex', alignItems: 'center', gap: 2, fontSize: 11, flexShrink: 0 }}
                  title={t.ragchat.attachClearLabel}
                >
                  <X size={11} /> {t.ragchat.attachClearLabel}
                </button>
              </div>
            )}

            {/* Hidden file input */}
            <input
              ref={fileInputRef}
              type="file"
              accept={t.ragchat.attachAccept}
              style={{ display: 'none' }}
              onChange={handleFileInputChange}
            />

            <div className="composer-row">
              {/* Paperclip button — replaces attached doc when clicked again */}
              <button
                className="btn icon-btn"
                onClick={() => fileInputRef.current?.click()}
                disabled={streaming || docContext?.status === 'extracting'}
                title={t.ragchat.attachBtn}
                style={{ flexShrink: 0, padding: '8px', color: docContext?.status === 'ready' ? 'var(--accent, #6366f1)' : undefined }}
              >
                <Paperclip size={15} />
              </button>
              <textarea
                className="composer-input"
                placeholder={t.ragchat.inputPlaceholder}
                value={inputDraft}
                onChange={e => setRagState(s => ({ ...s, inputDraft: e.target.value }))}
                onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); void send(); } }}
                rows={2}
              />
              <button
                className="btn primary"
                onClick={() => void send()}
                disabled={!inputDraft.trim() || streaming || docContext?.status === 'extracting'}
              >
                <Send size={14} />
              </button>
            </div>
          </div>
        </div>

        {/* ── Citation rail ── */}
        <div className="citation-rail" ref={citRailRef}>
          <div className="citation-header">
            {t.ragchat.citationsHeader}{citations.length > 0 && ` (${citations.length})`}
          </div>
          {citations.length === 0 && (
            <p style={{ padding: '12px 16px', fontSize: 12, color: 'var(--muted)', lineHeight: 1.5 }}>
              {t.ragchat.citationsEmpty}
            </p>
          )}
          {citations.map(c => (
            <div
              key={c.index}
              ref={el => { citItemRefs.current[c.index] = el; }}
              className={`citation-item${highlightedCit === c.index ? ' highlighted' : ''}`}
            >
              <div className="cit-index">[{c.index}]</div>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 4, marginBottom: 3 }}>
                  <div className="cit-name">{c.name}</div>
                  {c.clause && <span className="cit-clause">{c.clause}</span>}
                  <span className="cit-score" style={{ marginLeft: 'auto' }}>{c.score}%</span>
                </div>
                <div className="cit-snippet">{c.snippet}</div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}
