
const { useState, useRef, useEffect, useCallback } = React;

const CHAT_API_BASE = 'https://transfer-lens.onrender.com';

const QUICK_PROMPTS = [
  'What should I take next semester?',
  'Can I finish in 2 years?',
  'Is my schedule too heavy?',
  'Which Cal-GETC areas am I missing?',
];

// Assistant replies are rendered as one bubble per paragraph (split on blank
// lines), so a multi-part answer appears as a sequence of bubbles.
function splitChunks(text) {
  return (text || '').split(/\n{2,}/).map(s => s.trim()).filter(Boolean);
}

// The counselor disclaimer is shown as a single static line pinned at the bottom
// of the chat (below), so strip it out when it arrives as its own message chunk.
const COUNSELOR_NOTE = 'Confirm details with your counselor before making changes to your schedule.';
const COUNSELOR_NOTE_KEY = 'confirm details with your counselor';
function messageChunks(text) {
  return splitChunks(text).filter(c => !c.toLowerCase().startsWith(COUNSELOR_NOTE_KEY));
}

function AiAvatar() {
  return (
    <div className="chat-msg-avatar">
      <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><circle cx="7" cy="5.5" r="2.5" stroke="currentColor" strokeWidth="1.4"/><path d="M1.5 13c0-2.9 2.4-3.5 5.5-3.5s5.5.6 5.5 3.5" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"/></svg>
    </div>
  );
}

function MarkdownText({ text }) {
  const parts = text.split(/(\*\*[^*]+\*\*|`[^`]+`|\n)/g);
  return (
    <span>
      {parts.map((p, i) => {
        if (p === '\n') return <br key={i} />;
        if (p.startsWith('**') && p.endsWith('**')) return <strong key={i}>{p.slice(2, -2)}</strong>;
        if (p.startsWith('`') && p.endsWith('`')) return <code key={i} className="chat-code">{p.slice(1, -1)}</code>;
        return <span key={i}>{p}</span>;
      })}
    </span>
  );
}

function ChatPanel({ open, onClose, profile }) {
  const [messages, setMessages] = useState(() => {
    try { return JSON.parse(localStorage.getItem('tl_chat') || '[]'); } catch { return []; }
  });
  const [input, setInput] = useState('');
  const [loading, setLoading] = useState(false);
  const [sending, setSending] = useState(false);
  // Staggered reveal of the newest assistant reply: revealCount is how many of
  // its chunks are visible so far. It's driven directly when the reply lands
  // (below) so the first frame already shows just chunk 0, then more every
  // 300ms — never a flash of all chunks at once.
  const [revealCount, setRevealCount] = useState(0);
  const bottomRef = useRef(null);
  const inputRef = useRef(null);
  const staggerTimers = useRef([]);

  const clearStaggerTimers = () => {
    staggerTimers.current.forEach(clearTimeout);
    staggerTimers.current = [];
  };
  useEffect(() => () => clearStaggerTimers(), []);   // clear on unmount

  useEffect(() => {
    if (open) setTimeout(() => inputRef.current?.focus(), 100);
  }, [open]);

  useEffect(() => {
    if (bottomRef.current) {
      bottomRef.current.parentElement.scrollTop = bottomRef.current.offsetTop;
    }
  }, [messages, loading, revealCount]);

  // Append an assistant reply and reveal its chunks one at a time: chunk 0 is
  // shown immediately (in the same render the message is added, so there's no
  // all-at-once flash), then one more chunk every 300ms. Each setTimeout runs
  // in its own task, so each setRevealCount triggers its own paint.
  const revealReply = (content) => {
    const count = messageChunks(content).length;
    clearStaggerTimers();
    setRevealCount(1);
    setMessages(prev => [...prev, { role: 'assistant', content }]);
    for (let i = 1; i < count; i++) {
      staggerTimers.current.push(setTimeout(() => setRevealCount(i + 1), i * 300));
    }
  };

  useEffect(() => {
    localStorage.setItem('tl_chat', JSON.stringify(messages.slice(-40)));
  }, [messages]);

  // Build the structured plan the advisor endpoint grounds its answers in. The
  // backend turns this into the system prompt — we just send the student's
  // actual plan state.
  const buildPlan = () => {
    const placed = new Set(Object.values(profile.semesters || {}).flat());
    Object.keys(profile.courseStatuses || {}).forEach(k => placed.add(k));

    const recCourses = profile.recommendedCourses || [];
    const recBanked = recCourses.filter(rc => !placed.has(rc.key)).map(rc => rc.key);

    const allKeys = new Set([
      ...Object.values(profile.semesters || {}).flat(),
      ...(profile.requiredUnplaced || []),
      ...recBanked,
    ]);
    const course_titles = {}, course_units = {};
    allKeys.forEach(k => {
      const c = (window.MOCK_COURSES || {})[k] || {};
      const rc = recCourses.find(r => r.key === k);
      course_titles[k] = c.title || (rc && rc.title) || '';
      course_units[k] = c.units || (rc && rc.units) || 0;
    });

    // Cal-GETC: an area is satisfied if the student picked enough for it OR a
    // plan course already covers it (mirrors the Cal-GETC tab's grounding).
    const sel = profile.calgetcSelections || {};
    const areaOk = {};
    for (const area of (window.MOCK_CALGETC || [])) {
      const need = area.id === '5C' ? 1 : (area.rule === 'two_disciplines' ? 2 : (area.required || 1));
      const picks = sel[area.id] || [];
      const pool = (window.PLANNER_GE_AREAS && window.PLANNER_GE_AREAS[area.id])
        || (area.courses || []).map(c => c.key);
      areaOk[area.id] = picks.length >= need || pool.some(k => placed.has(k));
    }
    // Area 5C (Laboratory Activity) is satisfied implicitly whenever 5A or 5B is:
    // IVC's lab-bearing science courses (PHYS 4A, CHEM 1A, BIO courses) satisfy
    // the lecture area AND the lab area at the same time.
    if (areaOk['5A'] || areaOk['5B']) areaOk['5C'] = true;
    const calgetc_satisfied = [], calgetc_pending = [];
    for (const area of (window.MOCK_CALGETC || [])) {
      (areaOk[area.id] ? calgetc_satisfied : calgetc_pending).push(area.name);
    }

    return {
      schools: (profile.selections || []).map(s => ({
        abbr: s.school?.abbr || s.school?.name || '?',
        major: s.major || 'Unknown Major',
      })),
      transfer_term: profile.transferTerm || '',
      use_summer: !!profile.useSummer,
      summer_load: profile.summerLoad || 'light',
      semesters: profile.semesters || {},
      course_titles,
      course_units,
      required_unplaced: profile.requiredUnplaced || [],
      recommended_banked: recBanked,
      calgetc_satisfied,
      calgetc_pending,
    };
  };

  const send = useCallback(async (text) => {
    const trimmed = text.trim();
    if (!trimmed || loading) return;
    setSending(true);
    setTimeout(() => setSending(false), 400);
    const history = messages.slice(-8);   // prior turns, before this message
    setMessages(prev => [...prev, { role: 'user', content: trimmed }]);
    setInput('');
    setLoading(true);

    try {
      const res = await fetch(`${CHAT_API_BASE}/api/advisor`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message: trimmed, history, plan: buildPlan() }),
      });

      if (res.status === 429) {
        const data = await res.json().catch(() => ({}));
        setLoading(false);
        revealReply(data.message || "You've reached the session limit for Lumen. Speak with an IVC counselor for personalized guidance.");
        return;
      }
      if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);

      // Read the full streamed reply (the typing indicator stays up meanwhile),
      // then commit it once — revealReply splits it into staggered chunks.
      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let acc = '';
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        acc += decoder.decode(value, { stream: true });
      }
      const content = acc.trim() || "I'm having trouble connecting right now. Please try again in a moment.";
      setLoading(false);
      revealReply(content);
    } catch (e) {
      setLoading(false);
      revealReply("I'm having trouble connecting right now. Please try again in a moment.");
    }
  }, [messages, loading, profile]);

  const handleKey = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(input); }
  };

  return (
    <div className={`chat-panel ${open ? 'chat-open' : ''}`}>
      <div className="chat-header">
        <div className="chat-header-left">
          <div className="chat-avatar-sm">
            <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="6" r="3" stroke="currentColor" strokeWidth="1.5"/><path d="M2 14c0-3.3 2.7-4 6-4s6 .7 6 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
          </div>
          <div>
            <div className="chat-header-title">Lumen</div>
            <div className="chat-header-sub">AI-powered · answers based on your plan</div>
          </div>
        </div>
        <div className="chat-header-actions">
          {messages.length > 0 && (
            <button className="chat-clear" onClick={() => { setMessages([]); localStorage.removeItem('tl_chat'); }}>Clear</button>
          )}
          <button className="chat-close" onClick={onClose}>
            <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></svg>
          </button>
        </div>
      </div>

      <div className="chat-messages">
        {messages.length === 0 && (
          <div className="chat-welcome">
            <div className="chat-welcome-icon">
              <svg width="32" height="32" viewBox="0 0 32 32" fill="none"><circle cx="16" cy="13" r="6" stroke="var(--accent)" strokeWidth="2"/><path d="M4 30c0-6.6 5.4-8 12-8s12 1.4 12 8" stroke="var(--accent)" strokeWidth="2" strokeLinecap="round"/></svg>
            </div>
            <div className="chat-welcome-title">Hi! I'm Lumen, your transfer advisor.</div>
            <div className="chat-welcome-sub">Ask me anything about your plan, courses, deadlines, or what to take next.</div>
            <div className="chat-quick-prompts">
              {QUICK_PROMPTS.map(q => (
                <button key={q} className="chat-quick-btn" onClick={() => send(q)}>{q}</button>
              ))}
            </div>
          </div>
        )}

        {messages.map((m, i) => {
          if (m.role === 'user') {
            return (
              <div key={i} className="chat-msg chat-msg-user">
                <div className="chat-msg-bubble"><MarkdownText text={m.content} /></div>
              </div>
            );
          }
          // Assistant: one bubble per chunk. The newest reply (the last message)
          // reveals its chunks gradually via revealCount; earlier replies show
          // all chunks at once.
          const chunks = messageChunks(m.content);
          const revealing = i === messages.length - 1 && revealCount > 0;
          const shown = revealing ? chunks.slice(0, revealCount) : chunks;
          const moreComing = revealing && revealCount < chunks.length;
          return (
            <React.Fragment key={i}>
              {shown.map((chunk, ci) => (
                <div key={ci} className="chat-msg chat-msg-ai">
                  <AiAvatar />
                  <div className="chat-msg-bubble"><MarkdownText text={chunk} /></div>
                </div>
              ))}
              {moreComing && (
                <div className="chat-msg chat-msg-ai">
                  <AiAvatar />
                  <div className="chat-msg-bubble chat-typing"><span></span><span></span><span></span></div>
                </div>
              )}
            </React.Fragment>
          );
        })}

        {loading && (
          <div className="chat-msg chat-msg-ai">
            <AiAvatar />
            <div className="chat-msg-bubble chat-typing">
              <span></span><span></span><span></span>
            </div>
          </div>
        )}

        <div ref={bottomRef}></div>
      </div>

      <div className="chat-disclaimer" style={{ padding: '6px 16px', fontSize: '0.68rem', lineHeight: 1.4, color: 'var(--text-3)', textAlign: 'center' }}>
        {COUNSELOR_NOTE}
      </div>

      <div className="chat-input-row">
        <textarea
          ref={inputRef}
          className="chat-input"
          placeholder="Ask about your transfer plan…"
          value={input}
          onChange={e => setInput(e.target.value)}
          onKeyDown={handleKey}
          rows={1}
        />
        <button className={`chat-send ${sending ? 'sending' : ''}`} disabled={!input.trim() || loading} onClick={() => send(input)}>
          <svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M13 3L3 8l4 2 2 4 4-11z" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round"/></svg>
        </button>
      </div>
    </div>
  );
}

Object.assign(window, { ChatPanel });
