/**
 * LLM 调用模块
 *
 * 支持两种模式（由 cfg.mode 决定）：
 *   - 'server'（默认/推荐）：调用同源后端代理 /api/ai，由服务器持有 Key 转发给大模型。
 *     访客无需填写任何 Key，Key 只存在服务器的环境变量里，不进代码、不上传。
 *   - 'local'（兜底）：浏览器直连大模型（OpenAI 兼容协议），Key 由用户在设置里填写、
 *     仅存于其本机 localStorage。适合本地开发或后端不可用时。
 *
 * 两种模式都强制“只依据帮助中心文档回答、无关则回『无关』”。
 */

/* ---------------- 系统 prompt（local 兜底模式用；server 模式由后端持有） ---------------- */
const SYSTEM_PROMPT = `你是「GitLink 帮助中心」的智能助手，专门帮助用户解答关于 GitLink 平台（确实开源，CCF 官方开源创新服务平台）使用方法的问题。

【必须遵守的规则】
1. 你只能依据下面【帮助中心参考资料】里的内容来回答用户问题，禁止使用资料以外的知识、禁止编造。
2. 如果用户的问题与 GitLink 平台 / 帮助中心文档无关（例如闲聊、天气、其它产品、其它网站等），请只回复两个字：无关
3. 回答要简洁、准确、分点；若参考资料里涉及具体操作步骤，请保留关键步骤。
4. 回答末尾，如果参考资料里包含对应文档路径（形如 /代码库管理/仓库创建），请用一行给出：“📎 相关文档：<路径>”，方便用户点击查看。
5. 当参考资料无法支撑回答时（即便问题看起来和 GitLink 有关），也回复：无关

【帮助中心参考资料】
{CONTEXT}
`;

function buildMessages(question, contextChunks) {
  const context = contextChunks && contextChunks.length
    ? contextChunks
        .map((c, i) => `【资料${i + 1}】来源：${c.title}${c.section ? ' / ' + c.section : ''}（页面 ${c.path}）\n${c.text}`)
        .join('\n\n')
    : '（本次未检索到任何相关资料）';
  return [
    { role: 'system', content: SYSTEM_PROMPT.replace('{CONTEXT}', context) },
    { role: 'user', content: question },
  ];
}

/** 解析 SSE 流，逐 token 回调 */
async function consumeSseStream(body, onDelta) {
  const reader = body.getReader();
  const decoder = new TextDecoder('utf-8');
  let buf = '';
  let full = '';
  while (true) {
    const { value, done } = await reader.read();
    if (done) break;
    buf += decoder.decode(value, { stream: true });
    const lines = buf.split('\n');
    buf = lines.pop() || '';
    for (const line of lines) {
      const s = line.trim();
      if (!s || !s.startsWith('data:')) continue;
      const data = s.slice(5).trim();
      if (data === '[DONE]') continue;
      try {
        const json = JSON.parse(data);
        const delta = json.choices?.[0]?.delta?.content || '';
        if (delta) {
          full += delta;
          onDelta && onDelta(delta);
        }
      } catch (e) { /* 忽略非 JSON 心跳行 */ }
    }
  }
  return full;
}

/* ---------------- server 模式：调用后端代理 ---------------- */
async function askViaBackend(backendUrl, question, contextChunks, opts = {}) {
  const resp = await fetch(backendUrl || '/api/ai', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ question, context: contextChunks }),
    signal: opts.signal,
  });

  if (!resp.ok) {
    let msg = `接口请求失败（HTTP ${resp.status}）`;
    try {
      const j = await resp.json();
      if (j && j.error) msg = j.error;
    } catch (e) { /* ignore */ }
    throw new Error(msg);
  }
  return consumeSseStream(resp.body, opts.onDelta);
}

/* ---------------- local 模式：浏览器直连大模型 ---------------- */
async function askDirect(cfg, question, contextChunks, opts = {}) {
  if (!cfg.apiKey) throw new Error('尚未配置 API Key，请在“设置”里切换到“使用我自己的Key”并填写。');
  const base = (cfg.baseURL || 'https://api.deepseek.com/v1').replace(/\/+$/, '');
  const url = base.endsWith('/chat/completions') ? base : `${base}/chat/completions`;
  const messages = buildMessages(question, contextChunks);

  const resp = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${cfg.apiKey}`,
    },
    body: JSON.stringify({
      model: cfg.model || 'deepseek-chat',
      messages,
      temperature: 0.2,
      stream: true,
    }),
    signal: opts.signal,
  });

  if (!resp.ok) {
    let detail = '';
    try { detail = await resp.text(); } catch (e) { /* ignore */ }
    throw new Error(`接口请求失败（HTTP ${resp.status}）。${detail.slice(0, 200)}`);
  }
  return consumeSseStream(resp.body, opts.onDelta);
}

/**
 * 统一入口
 * @param {Object} cfg { mode, backendUrl, baseURL, apiKey, model }
 * @param {string} question
 * @param {Array} contextChunks
 * @param {Object} opts { onDelta, signal }
 */
export async function askAssistant(cfg, question, contextChunks, opts = {}) {
  if (cfg && cfg.mode === 'local') return askDirect(cfg, question, contextChunks, opts);
  return askViaBackend(cfg && cfg.backendUrl, question, contextChunks, opts);
}
