/**
 * GitLink 帮助中心 —— AI 智能助手（可拖拽浮动组件）
 *
 * 功能：
 *   1. 页面右下角浮动按钮，支持鼠标/触摸拖拽到任意位置（位置记忆到 localStorage）。
 *   2. 点击打开聊天面板，与用户交互；回答内容全部来自帮助中心文档（RAG）。
 *   3. 与帮助中心无关的问题，直接回复“无关”。
 *   4. 自带“设置”面板：填入 API 地址 / Key / 模型即可使用，密钥只存在本机浏览器。
 *
 * 安全说明：API Key 仅保存在当前浏览器的 localStorage，不会写入代码、不会上传到远端。
 * 如需生产环境部署，建议改为后端代理转发（见项目 README 中 AI 助手一节）。
 */
import React, { useEffect, useRef, useState, useCallback } from 'react';
import styles from './styles.module.css';
import { retrieve } from './retrieval';
import { askAssistant } from './llm';

const LS = {
  pos: 'ai_assistant_pos',
  cfg: 'ai_assistant_config',
  chat: 'ai_assistant_chat',
};

const DEFAULT_CFG = {
  mode: 'server', // 'server' = 走后端共享Key(推荐) | 'local' = 访客自带Key(兜底)
  backendUrl: '/api/ai',
  baseURL: 'https://api.deepseek.com/v1',
  apiKey: '',
  model: 'deepseek-chat',
};

const QUICK_QUESTIONS = [
  '如何创建一个代码仓库？',
  '什么是疑修（Issue）？',
  '怎样配置 DevOps 流水线？',
  '如何把 GitHub 项目导入 GitLink？',
];

/* ----------------------------- 小工具 ----------------------------- */

function loadPos() {
  try {
    const p = JSON.parse(localStorage.getItem(LS.pos));
    if (p && typeof p.x === 'number' && typeof p.y === 'number') return p;
  } catch (e) { /* ignore */ }
  return null;
}

function loadCfg() {
  try {
    const c = JSON.parse(localStorage.getItem(LS.cfg));
    return { ...DEFAULT_CFG, ...(c || {}) };
  } catch (e) { /* ignore */ }
  return { ...DEFAULT_CFG };
}

/** 把助手回复里的“📎 相关文档：/xxx”渲染成可点击链接，并保留换行 */
function renderAnswer(text) {
  const lines = text.split('\n');
  return lines.map((line, i) => {
    const m = line.match(/^(.*?)相关文档[:：]\s*(\/[^\s】】)]+)/);
    if (m) {
      return (
        <span key={i}>
          {m[1]}
          <a className={styles.docLink} href={m[2]} target="_blank" rel="noreferrer">
            {m[2]}
          </a>
        </span>
      );
    }
    return (
      <span key={i}>
        {line}
        {i < lines.length - 1 && <br />}
      </span>
    );
  });
}

/* ----------------------------- 主组件 ----------------------------- */

export default function AIAssistant() {
  const [mounted, setMounted] = useState(false);
  const [open, setOpen] = useState(false);
  const [showSettings, setShowSettings] = useState(false);

  // 浮动按钮位置（左上角坐标）
  const [pos, setPos] = useState({ x: -1, y: -1 }); // -1 表示使用默认（右下角）
  const [vw, setVw] = useState(typeof window !== 'undefined' ? window.innerWidth : 1280);

  // 聊天状态
  const [messages, setMessages] = useState([]); // {role:'user'|'assistant', content, pending?}
  const [input, setInput] = useState('');
  const [loading, setLoading] = useState(false);
  const [cfg, setCfg] = useState(DEFAULT_CFG);

  const abortRef = useRef(null);
  const scrollRef = useRef(null);
  const dragState = useRef({ dragging: false, moved: false, startX: 0, startY: 0, origX: 0, origY: 0 });

  /* ---- 初始化（仅在浏览器端） ---- */
  useEffect(() => {
    setMounted(true);
    setCfg(loadCfg());
    const p = loadPos();
    if (p) setPos(p);
    try {
      const saved = JSON.parse(localStorage.getItem(LS.chat) || '[]');
      if (Array.isArray(saved) && saved.length) setMessages(saved);
    } catch (e) { /* ignore */ }
    const onResize = () => setVw(window.innerWidth);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);

  /* ---- 消息变化时持久化 + 滚到底部 ---- */
  useEffect(() => {
    if (!mounted) return;
    try { localStorage.setItem(LS.chat, JSON.stringify(messages.slice(-30))); } catch (e) { /* ignore */ }
  }, [messages, mounted]);

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

  /* ---- 拖拽逻辑（pointer events） ---- */
  const onPointerDown = (e) => {
    // 打开状态下不拖拽（避免误触）
    if (open) return;
    dragState.current = {
      dragging: true,
      moved: false,
      startX: e.clientX,
      startY: e.clientY,
      origX: pos.x < 0 ? window.innerWidth - 72 : pos.x,
      origY: pos.y < 0 ? window.innerHeight - 72 : pos.y,
    };
    e.currentTarget.setPointerCapture(e.pointerId);
  };

  const onPointerMove = (e) => {
    const ds = dragState.current;
    if (!ds.dragging) return;
    const dx = e.clientX - ds.startX;
    const dy = e.clientY - ds.startY;
    if (Math.abs(dx) > 4 || Math.abs(dy) > 4) ds.moved = true;
    const nx = Math.max(8, Math.min(window.innerWidth - 64, ds.origX + dx));
    const ny = Math.max(8, Math.min(window.innerHeight - 64, ds.origY + dy));
    setPos({ x: nx, y: ny });
  };

  const onPointerUp = (e) => {
    const ds = dragState.current;
    ds.dragging = false;
    try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (err) { /* ignore */ }
    // 只有“没怎么动”才算点击 → 打开面板
    if (!ds.moved) {
      setOpen(true);
    } else {
      try { localStorage.setItem(LS.pos, JSON.stringify(pos)); } catch (err) { /* ignore */ }
    }
  };

  /* ---- 发送消息 ---- */
  const send = useCallback(async (text) => {
    const question = (text ?? input).trim();
    if (!question || loading) return;
    // 仅 local 模式才需要访客自己填 Key；server 模式由后端持有 Key
    if (cfg.mode === 'local' && !cfg.apiKey) {
      setOpen(true);
      setShowSettings(true);
      return;
    }

    setInput('');
    const userMsg = { role: 'user', content: question };
    setMessages((m) => [...m, userMsg, { role: 'assistant', content: '', pending: true }]);
    setLoading(true);

    // 1) 客户端检索帮助中心资料
    const { chunks, hasHit } = retrieve(question, 4);

    // 2) 完全检索不到任何相关内容 → 直接“无关”，不调用模型（省 token）
    if (!hasHit) {
      setMessages((m) => {
        const arr = m.slice();
        arr[arr.length - 1] = { role: 'assistant', content: '无关' };
        return arr;
      });
      setLoading(false);
      return;
    }

    // 3) 调用大模型，流式回填
    const controller = new AbortController();
    abortRef.current = controller;
    try {
      let acc = '';
      await askAssistant(cfg, question, chunks, {
        signal: controller.signal,
        onDelta: (d) => {
          acc += d;
          setMessages((m) => {
            const arr = m.slice();
            arr[arr.length - 1] = { role: 'assistant', content: acc, pending: false };
            return arr;
          });
        },
      });
      // 兜底：流式没回填成功时直接设置最终文本
      setMessages((m) => {
        const arr = m.slice();
        const last = arr[arr.length - 1];
        if (last && last.role === 'assistant') arr[arr.length - 1] = { ...last, content: acc || last.content, pending: false };
        return arr;
      });
    } catch (err) {
      setMessages((m) => {
        const arr = m.slice();
        arr[arr.length - 1] = {
          role: 'assistant',
          content: `⚠️ 出错了：${err.message || err}`,
          pending: false,
          error: true,
        };
        return arr;
      });
    } finally {
      setLoading(false);
      abortRef.current = null;
    }
  }, [input, loading, cfg]);

  const stop = () => {
    if (abortRef.current) abortRef.current.abort();
    setLoading(false);
    setMessages((m) => {
      const arr = m.slice();
      const last = arr[arr.length - 1];
      if (last && last.pending) arr[arr.length - 1] = { ...last, pending: false };
      return arr;
    });
  };

  const clearChat = () => {
    setMessages([]);
    try { localStorage.removeItem(LS.chat); } catch (e) { /* ignore */ }
  };

  /* ---- 设置保存 ---- */
  const saveCfg = (next) => {
    setCfg(next);
    try { localStorage.setItem(LS.cfg, JSON.stringify(next)); } catch (e) { /* ignore */ }
    setShowSettings(false);
  };

  /* ----------------------------- 渲染 ----------------------------- */
  if (!mounted) return null;

  const isMobile = vw < 600;
  const fabSize = 56;
  // 计算浮动按钮的 fixed 位置
  const fabStyle = pos.x < 0
    ? { right: 24, bottom: 24 }
    : { left: pos.x, top: pos.y };
  // 计算面板位置（桌面端，跟随按钮，并夹在视口内）
  let panelStyle = {};
  if (open && !isMobile) {
    const panelW = 384;
    const panelH = 560;
    const fabX = pos.x < 0 ? window.innerWidth - 24 - fabSize : pos.x;
    const fabY = pos.y < 0 ? window.innerHeight - 24 - fabSize : pos.y;
    let left = fabX + fabSize - panelW; // 面板右对齐到按钮
    let top = fabY - panelH - 12; // 面板在按钮上方
    if (top < 12) top = fabY + fabSize + 12; // 上方放不下就放下方
    if (left < 12) left = 12;
    if (left + panelW > vw - 12) left = vw - panelW - 12;
    panelStyle = { left, top, width: panelW, height: panelH };
  }

  return (
    <>
      {/* 浮动按钮 */}
      <button
        type="button"
        className={`${styles.fab} ${open ? styles.fabHidden : ''}`}
        style={fabStyle}
        onPointerDown={onPointerDown}
        onPointerMove={onPointerMove}
        onPointerUp={onPointerUp}
        aria-label="GitLink AI 助手"
        title="GitLink AI 助手（可拖拽）"
      >
        <span className={styles.fabIcon}>AI</span>
      </button>

      {/* 聊天面板 */}
      {open && (
        <div className={`${styles.panel} ${isMobile ? styles.panelMobile : ''}`} style={panelStyle}>
          {/* 标题栏 */}
          <div className={styles.header}>
            <div className={styles.headerTitle}>
              <span className={styles.headerBadge}>AI</span>
              GitLink 帮助助手
            </div>
            <div className={styles.headerBtns}>
              <button className={styles.iconBtn} onClick={() => setShowSettings(true)} title="设置">⚙</button>
              <button className={styles.iconBtn} onClick={clearChat} title="清空对话">↺</button>
              <button className={styles.iconBtn} onClick={() => setOpen(false)} title="关闭">✕</button>
            </div>
          </div>

          {/* 消息区 */}
          <div className={styles.body} ref={scrollRef}>
            {messages.length === 0 && (
              <div className={styles.welcome}>
                <div className={styles.welcomeTitle}>👋 你好！我是 GitLink 帮助中心 AI 助手</div>
                <div className={styles.welcomeDesc}>
                  我可以帮你解答 GitLink 平台的使用问题，回答内容均来自帮助中心文档。
                  与帮助中心无关的问题，我会回复“无关”。
                </div>
                <div className={styles.quickList}>
                  {QUICK_QUESTIONS.map((q) => (
                    <button key={q} className={styles.quickBtn} onClick={() => send(q)}>{q}</button>
                  ))}
                </div>
              </div>
            )}

            {messages.map((m, i) => (
              <div key={i} className={`${styles.msg} ${m.role === 'user' ? styles.msgUser : styles.msgBot}`}>
                <div className={`${styles.bubble} ${m.role === 'user' ? styles.bubbleUser : styles.bubbleBot} ${m.error ? styles.bubbleErr : ''}`}>
                  {m.pending && !m.content ? (
                    <span className={styles.typing}>思考中<span className={styles.dot}>·</span><span className={styles.dot}>·</span><span className={styles.dot}>·</span></span>
                  ) : (
                    renderAnswer(m.content)
                  )}
                  {m.pending && m.content ? <span className={styles.cursor}>▋</span> : null}
                </div>
              </div>
            ))}
          </div>

          {/* 输入区 */}
          <div className={styles.footer}>
            <textarea
              className={styles.textarea}
              placeholder="输入你的问题…（Enter 发送，Shift+Enter 换行）"
              value={input}
              rows={1}
              onChange={(e) => setInput(e.target.value)}
              onKeyDown={(e) => {
                if (e.key === 'Enter' && !e.shiftKey) {
                  e.preventDefault();
                  send();
                }
              }}
            />
            {loading ? (
              <button className={styles.sendBtn} onClick={stop} title="停止">■</button>
            ) : (
              <button className={styles.sendBtn} onClick={() => send()} title="发送">➤</button>
            )}
          </div>
        </div>
      )}

      {/* 设置弹层 */}
      {showSettings && (
        <SettingsModal cfg={cfg} onSave={saveCfg} onClose={() => setShowSettings(false)} />
      )}
    </>
  );
}

/* ----------------------------- 设置弹层 ----------------------------- */

function SettingsModal({ cfg, onSave, onClose }) {
  const [mode, setMode] = useState(cfg.mode || 'server');
  const [backendUrl, setBackendUrl] = useState(cfg.backendUrl || '/api/ai');
  const [baseURL, setBaseURL] = useState(cfg.baseURL);
  const [apiKey, setApiKey] = useState(cfg.apiKey);
  const [model, setModel] = useState(cfg.model);

  const presets = [
    { name: 'DeepSeek', baseURL: 'https://api.deepseek.com/v1', model: 'deepseek-chat' },
    { name: '通义千问', baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-plus' },
    { name: '智谱 GLM', baseURL: 'https://open.bigmodel.cn/api/paas/v4', model: 'glm-4-flash' },
    { name: 'Moonshot', baseURL: 'https://api.moonshot.cn/v1', model: 'moonshot-v1-8k' },
    { name: 'OpenAI', baseURL: 'https://api.openai.com/v1', model: 'gpt-4o-mini' },
  ];

  return (
    <div className={styles.modalMask} onClick={onClose}>
      <div className={styles.modal} onClick={(e) => e.stopPropagation()}>
        <div className={styles.modalHeader}>
          <span>AI 助手设置</span>
          <button className={styles.iconBtn} onClick={onClose}>✕</button>
        </div>
        <div className={styles.modalBody}>
          {/* 模式切换 */}
          <div className={styles.field}>
            <label>调用方式</label>
            <div className={styles.presetRow}>
              <button
                className={`${styles.presetBtn} ${mode === 'server' ? styles.presetActive : ''}`}
                onClick={() => setMode('server')}
              >
                服务器共享Key（推荐）
              </button>
              <button
                className={`${styles.presetBtn} ${mode === 'local' ? styles.presetActive : ''}`}
                onClick={() => setMode('local')}
              >
                使用我自己的Key
              </button>
            </div>
          </div>

          {mode === 'server' ? (
            <>
              <div className={styles.field}>
                <label>后端代理地址</label>
                <input className={styles.input} value={backendUrl} onChange={(e) => setBackendUrl(e.target.value)} placeholder="/api/ai" />
              </div>
              <div className={styles.tip}>
                ✅ 此模式下，所有访客打开即可直接提问，无需填写任何 Key。
                Key 安全地保存在服务器环境变量（/root/.ai_env）里，不进代码、不上传 git。
              </div>
            </>
          ) : (
            <>
              <div className={styles.field}>
                <label>服务厂商（快捷选择）</label>
                <div className={styles.presetRow}>
                  {presets.map((p) => (
                    <button
                      key={p.name}
                      className={`${styles.presetBtn} ${baseURL === p.baseURL ? styles.presetActive : ''}`}
                      onClick={() => { setBaseURL(p.baseURL); setModel(p.model); }}
                    >
                      {p.name}
                    </button>
                  ))}
                </div>
              </div>
              <div className={styles.field}>
                <label>API 地址（Base URL）</label>
                <input className={styles.input} value={baseURL} onChange={(e) => setBaseURL(e.target.value)} placeholder="https://api.deepseek.com/v1" />
              </div>
              <div className={styles.field}>
                <label>模型名称</label>
                <input className={styles.input} value={model} onChange={(e) => setModel(e.target.value)} placeholder="deepseek-chat" />
              </div>
              <div className={styles.field}>
                <label>API Key <span className={styles.hint}>(只保存在本机浏览器，不会上传)</span></label>
                <input className={styles.input} type="password" value={apiKey} onChange={(e) => setApiKey(e.target.value)} placeholder="sk-..." />
              </div>
              <div className={styles.tip}>
                说明：密钥仅写入当前浏览器的 localStorage，不会出现在项目代码里，也不会提交到代码仓库。
              </div>
            </>
          )}
        </div>
        <div className={styles.modalFooter}>
          <button className={styles.cancelBtn} onClick={onClose}>取消</button>
          <button
            className={styles.saveBtn}
            onClick={() => onSave({ mode, backendUrl, baseURL, apiKey, model })}
          >
            保存
          </button>
        </div>
      </div>
    </div>
  );
}
