// chat.jsx
// Chat surface: message thread (rich-text bubbles), input bar (model locked to
// DeepSeek V4 Pro), the 02 workshop stage chips, and the inline topic selector.

(function () {
  const { useState, useRef, useEffect } = React;
  const { IconArrowUp, IconPaperclip, IconMic, IconLock, IconCheck, IconCompass } = window.AppIcons;
  const { MODEL_LABEL } = window.CanAIAgents;

  function AiAvatar({ agent }) {
    const Icn = (agent && agent.Icon) || null;
    return (
      <div className="ai-avatar" aria-hidden>
        {Icn
          ? <Icn size={17} />
          : <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
              <path d="M18.5 7.5 A 7 7 0 1 0 18.5 16.5" stroke="white" strokeWidth="2.8" strokeLinecap="round" fill="none" />
            </svg>}
      </div>
    );
  }

  function MessageBubble({ m, agent, isStreaming, isLast }) {
    const showCaret = m.role === 'assistant' && isStreaming && isLast && m.content;
    const showThinking = m.role === 'assistant' && isStreaming && isLast && !m.content;
    if (m.role === 'user') {
      return (
        <div className="msg-row user">
          <div className="bubble user">{m.content}</div>
        </div>
      );
    }
    return (
      <div className="msg-row assistant">
        <AiAvatar agent={agent} />
        <div className={`bubble assistant ${m.error ? 'error' : ''}`}>
          {showThinking
            ? <span className="thinking-dots"><span /><span /><span /></span>
            : <>{window.renderMarkdown(m.content)}{showCaret ? <span className="caret" /> : null}</>}
        </div>
      </div>
    );
  }

  function ChatThread({ messages, agent, isStreaming, trailing }) {
    const endRef = useRef(null);
    useEffect(() => {
      if (endRef.current) endRef.current.scrollIntoView({ block: 'end' });
    }, [messages, trailing]);
    return (
      <div className="chat-thread scroll-area">
        {messages.map((m, idx) => (
          <MessageBubble
            key={m.id}
            m={m}
            agent={agent}
            isStreaming={isStreaming}
            isLast={idx === messages.length - 1}
          />
        ))}
        {trailing ? <div className="thread-trailing">{trailing}</div> : null}
        <div ref={endRef} />
      </div>
    );
  }

  // 01 — questionnaire launcher card (shown in welcome before diagnosis)
  function QuestionnaireLauncher({ onStart }) {
    return (
      <div className="launch-card">
        <div className="launch-icon"><IconCompass size={20} /></div>
        <div className="launch-body">
          <div className="launch-title">开始定位诊断</div>
          <div className="launch-sub">19 道题，约 3–5 分钟，全程在网页内完成</div>
        </div>
        <button className="launch-btn" onClick={onStart}>开始</button>
      </div>
    );
  }

  // 02 — staged workflow chips (生成选题 → 选定选题 → 生成文案 → 分镜脚本)
  const STAGES = [
    { id: 'topics', label: '① 生成选题' },
    { id: 'select', label: '② 选定选题' },
    { id: 'copy', label: '③ 生成文案' },
    { id: 'script', label: '④ 分镜脚本' },
  ];
  function WorkshopStageBar({ onStage, disabled, stageState }) {
    return (
      <div className="stage-bar">
        <span className="stage-bar-label">流程</span>
        {STAGES.map((s) => (
          <button
            key={s.id}
            className={`stage-chip ${stageState && stageState[s.id] ? 'done' : ''}`}
            disabled={disabled}
            onClick={() => onStage(s.id)}
          >
            {stageState && stageState[s.id] ? <IconCheck size={13} /> : null}
            {s.label}
          </button>
        ))}
      </div>
    );
  }

  // 02 — inline topic selector (pick up to 4)
  function TopicSelector({ topics, onConfirm, onCancel }) {
    const [picked, setPicked] = useState([]);
    const toggle = (t) => {
      setPicked((p) => p.includes(t) ? p.filter((x) => x !== t) : (p.length >= 4 ? p : [...p, t]));
    };
    return (
      <div className="selector-card">
        <div className="selector-head">
          <span>选定选题 <span className="selector-count">{picked.length}/4</span></span>
          <button className="q-cancel" onClick={onCancel}>取消</button>
        </div>
        <div className="selector-list">
          {topics.map((t, i) => (
            <button key={i} className={`selector-item ${picked.includes(t) ? 'sel' : ''}`} onClick={() => toggle(t)}>
              <span className={`selector-box ${picked.includes(t) ? 'sel' : ''}`}>{picked.includes(t) ? <IconCheck size={12} /> : null}</span>
              <span className="selector-text">{t}</span>
            </button>
          ))}
        </div>
        <button className="q-btn primary" disabled={picked.length === 0} onClick={() => onConfirm(picked)}>
          确定，用这 {picked.length} 条写文案
        </button>
      </div>
    );
  }

  // STT backend endpoint (Aliyun via Worker proxy) — only reachable on canai.help
  const STT_ENDPOINT = 'https://api.canai.help/api/stt';

  // Detect WeChat / QQ / other in-app browsers where Web Speech API is broken
  const IS_WECHAT_BROWSER = /MicroMessenger|WeChat|QQ\//i.test(navigator.userAgent);
  // Detect if Web Speech API actually exists AND we're not in a broken env
  const HAS_REAL_SPEECH = !IS_WECHAT_BROWSER && !!(window.SpeechRecognition || window.webkitSpeechRecognition);

  function InputBar({ accent, onSend, disabled, placeholder }) {
    const [value, setValue] = useState('');
    const [focused, setFocused] = useState(false);
    const [isRecording, setIsRecording] = useState(false);
    const [micStatus, setMicStatus] = useState(''); // '', 'listening', 'processing', 'error'
    const taRef = useRef(null);
    const recognitionRef = useRef(null);
    const recorderRef = useRef(null);
    const timersRef = useRef([]);        // all timeouts, cleaned on stop
    const gotResultRef = useRef(false);  // did Web Speech produce a result?

    const autoresize = () => {
      const el = taRef.current;
      if (!el) return;
      el.style.height = 'auto';
      el.style.height = Math.min(el.scrollHeight, 160) + 'px';
    };
    useEffect(autoresize, [value]);
    useEffect(() => () => { stopAll(); }, []);

    function addTimer(fn, ms) {
      const id = setTimeout(fn, ms);
      timersRef.current.push(id);
      return id;
    }
    function clearTimers() {
      timersRef.current.forEach((id) => clearTimeout(id));
      timersRef.current = [];
    }

    function stopAll() {
      clearTimers();
      if (recognitionRef.current) { try { recognitionRef.current.abort(); } catch (_) {} recognitionRef.current = null; }
      if (recorderRef.current) {
        try {
          if (recorderRef.current.recorder.state === 'recording') recorderRef.current.recorder.stop();
          recorderRef.current.stream.getTracks().forEach((t) => t.stop());
        } catch (_) {}
        recorderRef.current = null;
      }
      setIsRecording(false);
      setMicStatus('');
      gotResultRef.current = false;
    }

    // ---- Web Speech API (Chrome/Safari/Edge on desktop & Android Chrome) ----
    function startWebSpeech() {
      if (!HAS_REAL_SPEECH) return false;
      const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
      try {
        const rec = new SR();
        rec.lang = 'zh-CN';
        rec.continuous = false;
        rec.interimResults = false;
        rec.maxAlternatives = 1;
        recognitionRef.current = rec;
        gotResultRef.current = false;
        setIsRecording(true);
        setMicStatus('listening');

        rec.onresult = (e) => {
          gotResultRef.current = true;
          const text = e.results[0][0].transcript;
          if (text) setValue((v) => v ? v + text : text);
          setMicStatus('');
        };
        rec.onerror = (e) => {
          console.warn('SpeechRecognition error:', e.error);
          if (e.error === 'not-allowed') {
            setMicStatus('error');
            addTimer(() => setMicStatus(''), 3000);
          }
          stopAll();
          // If it errored out (not permission), try MediaRecorder as fallback
          if (e.error !== 'not-allowed' && e.error !== 'aborted') {
            startMediaRecording();
          }
        };
        rec.onend = () => {
          recognitionRef.current = null;
          if (!gotResultRef.current) {
            // Ended without result — Web Speech silently failed, don't leave user hanging
            setIsRecording(false);
            setMicStatus('');
          } else {
            setIsRecording(false);
            setMicStatus('');
          }
        };

        rec.start();

        // Safety timeout: if no result within 8 seconds, Web Speech is probably broken
        // → abort and fall back to MediaRecorder
        addTimer(() => {
          if (recognitionRef.current && !gotResultRef.current) {
            console.warn('Web Speech timeout — falling back to MediaRecorder');
            try { recognitionRef.current.abort(); } catch (_) {}
            recognitionRef.current = null;
            setIsRecording(false);
            startMediaRecording();
          }
        }, 8000);

        // Hard stop at 60s
        addTimer(() => { if (recognitionRef.current) recognitionRef.current.stop(); }, 60000);
        return true;
      } catch (_) { return false; }
    }

    // ---- MediaRecorder + Aliyun Worker STT (for WeChat & fallback) ----
    async function startMediaRecording() {
      // Check HTTPS (mic requires secure context)
      if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
        setMicStatus('error');
        addTimer(() => setMicStatus(''), 3000);
        return;
      }
      if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
        setMicStatus('error');
        addTimer(() => setMicStatus(''), 3000);
        return;
      }
      try {
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
        const mime = MediaRecorder.isTypeSupported('audio/webm') ? 'audio/webm'
          : MediaRecorder.isTypeSupported('audio/mp4') ? 'audio/mp4'
          : MediaRecorder.isTypeSupported('audio/ogg') ? 'audio/ogg' : '';
        const recorder = new MediaRecorder(stream, mime ? { mimeType: mime } : undefined);
        const chunks = [];
        recorderRef.current = { recorder, stream };
        setIsRecording(true);
        setMicStatus('listening');

        recorder.ondataavailable = (e) => { if (e.data.size > 0) chunks.push(e.data); };
        recorder.onstop = async () => {
          stream.getTracks().forEach((t) => t.stop());
          recorderRef.current = null;
          setMicStatus('processing');

          const blob = new Blob(chunks, { type: recorder.mimeType || 'audio/webm' });
          if (blob.size < 500) {
            // Too short / empty recording
            setIsRecording(false);
            setMicStatus('');
            return;
          }

          const fd = new FormData();
          fd.append('audio', blob, 'voice.' + (mime.includes('mp4') ? 'mp4' : mime.includes('ogg') ? 'ogg' : 'webm'));
          try {
            const resp = await fetch(STT_ENDPOINT, { method: 'POST', body: fd });
            if (!resp.ok) throw new Error('HTTP ' + resp.status);
            const data = await resp.json();
            if (data.text) {
              setValue((v) => v ? v + data.text : data.text);
              setMicStatus('');
            } else if (data.error) {
              console.warn('STT error:', data.error);
              setMicStatus('error');
              addTimer(() => setMicStatus(''), 3000);
            } else {
              setMicStatus('');
            }
          } catch (err) {
            console.warn('STT fetch error:', err);
            setMicStatus('error');
            addTimer(() => setMicStatus(''), 3000);
          }
          setIsRecording(false);
        };

        recorder.start();
        // Auto-stop at 60s
        addTimer(() => { if (recorder.state === 'recording') recorder.stop(); }, 60000);
      } catch (err) {
        if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
          setMicStatus('error');
          addTimer(() => setMicStatus(''), 3000);
        } else {
          console.warn('Mic error:', err);
          setMicStatus('error');
          addTimer(() => setMicStatus(''), 3000);
        }
        setIsRecording(false);
      }
    }

    function handleMicClick() {
      if (disabled) return;
      if (isRecording) { stopAll(); return; } // click again = stop
      // WeChat / broken env → go straight to MediaRecorder
      // Desktop Chrome/Safari → try Web Speech first (8s timeout auto-fallback)
      if (HAS_REAL_SPEECH) {
        startWebSpeech();
      } else {
        startMediaRecording();
      }
    }

    const micHint = micStatus === 'listening' ? '正在听…点击停止'
      : micStatus === 'processing' ? '识别中…'
      : micStatus === 'error' ? (IS_WECHAT_BROWSER ? '请在浏览器中打开使用语音' : '语音不可用，请检查麦克风权限')
      : '';

    const hasText = value.trim().length > 0;
    const canSend = hasText && !disabled;
    const send = () => {
      if (!canSend) return;
      const text = value.trim();
      setValue('');
      onSend(text);
    };

    let sendBg;
    if (disabled) sendBg = 'linear-gradient(135deg, #fb7185 0%, #ef4444 100%)';
    else if (hasText) sendBg = `linear-gradient(135deg, ${accent.from} 0%, ${accent.to} 100%)`;
    else sendBg = 'linear-gradient(135deg, #bfdbfe 0%, #93c5fd 100%)';

    return (
      <div>
        <div className={`input-shell ${focused ? 'focused' : ''} ${disabled ? 'loading' : ''}`} style={{ padding: '14px 16px 10px' }}>
          <textarea
            ref={taRef}
            className="q-input"
            placeholder={micStatus === 'listening' ? '🎙 正在听你说…点麦克风停止'
              : micStatus === 'processing' ? '⏳ 语音识别中…'
              : micStatus === 'error' ? '语音暂不可用'
              : (placeholder || '输入你的问题，向 Can AI 提问')}
            value={value}
            onChange={(e) => setValue(e.target.value)}
            onFocus={() => setFocused(true)}
            onBlur={() => setFocused(false)}
            onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }}
            rows={1}
          />
          <div className="input-row" style={{ display: 'flex', alignItems: 'center', marginTop: 10, gap: 2, minWidth: 0 }}>
            <button className="icon-btn icon-btn-secondary" title="附件"><IconPaperclip size={18} /></button>
            <button
              className={`icon-btn mic-btn ${isRecording ? 'recording' : ''}`}
              title={isRecording ? '点击停止录音' : '语音输入'}
              onClick={handleMicClick}
            >
              <IconMic size={18} />
            </button>
            <div style={{ flex: 1, minWidth: 4 }} />
            <div className="model-pill model-locked" title="已锁定主模型">
              <span className="model-dot" style={{ background: accent.to }} />
              <span className="model-pill-label">{MODEL_LABEL}</span>
              <IconLock size={12} />
            </div>
            <button
              className="send-btn"
              style={{ marginLeft: 6, background: sendBg, flexShrink: 0 }}
              disabled={!canSend && !disabled}
              onClick={send}
              title="发送"
            >
              {disabled
                ? <span style={{ display: 'inline-flex', gap: 3 }}>
                    <span className="typing-dot" style={{ width: 4, height: 4, borderRadius: 999, background: 'white' }} />
                    <span className="typing-dot" style={{ width: 4, height: 4, borderRadius: 999, background: 'white' }} />
                    <span className="typing-dot" style={{ width: 4, height: 4, borderRadius: 999, background: 'white' }} />
                  </span>
                : <IconArrowUp size={18} stroke={2.2} />}
            </button>
          </div>
        </div>
        <div className="footer-disclaimer">
          Can AI 是一款 AI 工具，其回答未必正确无误，最终决策请由人来综合评估完成。
        </div>
      </div>
    );
  }

  window.CanAIChat = {
    ChatThread, MessageBubble, AiAvatar,
    QuestionnaireLauncher, WorkshopStageBar, TopicSelector, InputBar,
    STAGES,
  };
})();
