/**
 * 指导文本常量集中管理
 * 所有系统提示词片段在此统一维护，避免散落在各处手动拼接
 */

// ─── 记忆系统提示 ───

export interface MemoryTypeInfo {
  key: string;
  name: string;
  description: string;
}

const BUILTIN_MEMORY_TYPES: MemoryTypeInfo[] = [
  { key: 'user', name: '用户画像', description: '偏好、角色、习惯' },
  { key: 'project', name: '项目知识', description: '进展、决策、约束' },
  { key: 'feedback', name: '行为反馈', description: '用户对你行为的纠正或确认' },
  { key: 'reference', name: '引用', description: '外部资源链接、文档地址' },
];

export function buildMemorySystemPrompt(
  types?: MemoryTypeInfo[],
  hasExistingMemory?: boolean,
): string {
  const memoryTypes = types?.length ? types : BUILTIN_MEMORY_TYPES;
  const typeLines = memoryTypes.map(t => `- ${t.key}: ${t.name}（${t.description}）`).join('\n');

  let prompt = `
## 记忆系统
你拥有长期记忆能力。使用 memory_save 工具存储重要信息，使用 memory_recall 工具检索过往记忆。使用 memory_list_types 查看所有可用记忆类型。

记忆类型：
${typeLines}

存储原则：
- 当用户透露个人偏好、角色、习惯时，存为 user 类型
- 当了解到项目进展、决策、约束时，存为 project 类型
- 当用户纠正或确认你的行为时，存为 feedback 类型
- 当提到外部资源链接时，存为 reference 类型
- 更新已有记忆而非创建重复条目
- 只存储对未来对话有价值的信息`;

  if (!hasExistingMemory) {
    prompt += `

## ⚠️ 强制执行：记忆初始化（你没有任何关于此用户的记忆）

**这是你的最高优先级任务。在回答用户问题前，你必须先显式调用 memory_recall 检查是否真的没有记忆。不可跳过。**

**第一步：显式读取记忆（必须调用工具）**
- 第一条工具调用必须是 memory_recall，参数使用 query="*" 或不传 query（获取全部记忆列表，不要用用户的问题做搜索）
- 如果 memory_recall 返回"未找到相关记忆"，才进入下一步环境初始化

**第二步：自主探索环境（仅在确认没有记忆后执行）**
- 使用 bash 工具执行系统命令，自动检测以下信息：
  - hostname（计算机名）
  - ver 或 uname -a（操作系统）
  - echo %USERPROFILE% 或 echo $HOME（用户主目录）
  - dir /b "%USERPROFILE%\\Desktop" 或 ls ~/Desktop（桌面内容概览）

**第三步：存储环境记忆**
- 将探测到的信息用 memory_save 存为 user 类型，标题固定为"用户环境信息"
- 同标题记忆会自动更新，不会重复创建

**第四步：继续执行用户任务**
- 完成记忆初始化后，继续处理用户原本的问题
- 不要只做初始化不做任务`;
  } else {
    prompt += `

## 持续优化（已有记忆时的强制规则）
你已经拥有这位用户的长期记忆。**在回答用户问题前，你必须先显式调用 memory_recall 获取全部记忆。不可跳过。**

每次对话开始时，必须遵循以下顺序：
1. 第一条工具调用必须是 memory_recall，参数使用 query="*" 或不传 query（获取全部记忆列表，不要用用户的问题做搜索）
2. 优先使用 memory_recall 返回的记忆结果执行任务
3. 不要为了同类信息再次做初始化探测
4. 只有在以下情况才允许再次用 bash 探测环境：
   - 用户明确要求你重新检查环境
   - 任务执行时发现记忆中的路径失效/不存在
   - 你检测到明显设备变化（hostname、用户名、路径不同）
5. 如果是同一类信息（如"用户环境信息"），优先 update，禁止重复 create 同标题记忆

在对话过程中：
- 默认先信任 memory_recall 返回的已有记忆
- 如果检测到的环境与记忆不一致（如不同的电脑、不同的用户名、不同的路径），说明用户可能在另一台设备上，主动更新或新增一条环境记忆，标注设备标识
- 如果用户提到新的项目、店铺、工作目录，补充存储
- 如果用户纠正了你的行为，存为 feedback 类型
- 不要重复询问已有记忆中的信息
- 一个用户可能有多台电脑，用 metadata 区分不同设备（如 { "device": "办公室PC", "hostname": "DESKTOP-XXX" }）`;
  }

  return prompt;
}

// ─── 工具使用纪律 ───

/**
 * 根据实际可用工具列表动态生成工具使用规范。
 * Crew 子 Agent 没有 clarify/patch，不应在提示词中出现这些工具。
 */
export function getToolUseEnforcement(toolNames: string[]): string {
  const rules = `# 工具使用规范

你必须通过工具采取行动 - 不要只描述你打算做什么。

## 强制规则
- 当你说"我来检查"、"让我执行"时，必须在同一回复中立即发起工具调用
- 不要以"下一步我会..."结束回复 - 现在就执行
- 持续工作直到任务真正完成，不要停在计划阶段
- 每条回复要么包含推进任务的工具调用，要么向用户交付最终结果`;

  const scenarios: string[] = [];
  if (toolNames.includes('read_file')) scenarios.push('需要读取文件内容时 → read_file（不要凭记忆猜测；大文件用 offset/limit 分段读取）');
  if (toolNames.includes('find_files')) scenarios.push('需要按文件名定位文件时 → find_files（优先于 bash/find 命令）');
  if (toolNames.includes('grep')) scenarios.push('需要搜索文件内容时 → grep（优先于 bash/grep 命令）');
  if (toolNames.includes('patch')) scenarios.push('需要修改已有文件时 → patch（局部修改）或 write_file（新建/全量重写）');
  else if (toolNames.includes('write_file')) scenarios.push('需要修改文件时 → write_file');
  if (toolNames.includes('list_dir')) scenarios.push('需要了解目录结构时 → list_dir');
  if (toolNames.includes('bash')) scenarios.push('需要执行命令时 → bash（长输出只返回尾部，必要时读取完整日志文件）');
  if (toolNames.includes('clarify')) scenarios.push('任务需求不明确时 → clarify（主动提问，不要猜测）');
  if (toolNames.includes('todo')) scenarios.push('复杂任务开始前 → todo（创建任务列表）');

  return scenarios.length > 0
    ? `${rules}\n\n## 必须使用工具的场景\n${scenarios.map(s => `- ${s}`).join('\n')}\n\n## 操作前先确认\n- 修改文件前先 read_file 确认当前内容\n- 执行命令前确认工作目录`
    : rules;
}

// ─── 模型特定指导 ───

const MODEL_GUIDANCE: Record<string, string> = {
  // OpenAI 系列：强化工具持久性和强制使用
  gpt: `# 模型行为约束
- tool_persistence: 工具调用结果必须在后续推理中持续引用，不要"忘记"已获取的信息
- mandatory_tool_use: 当任务需要外部信息时，必须调用工具，不要凭记忆猜测`,

  // DeepSeek：并行调用和JSON问题
  deepseek: `# 模型行为约束
- 支持并行工具调用，可在一次回复中发起多个独立的工具调用
- JSON 参数中的引号和换行符必须正确转义（\\" 和 \\n）
- 工具参数必须是合法 JSON，不要使用单引号或尾逗号`,

  // MiniMax：严格单次调用
  minimax: `# 模型行为约束
- 每次回复只调用一个工具，等待结果后再决定下一步
- 工具参数必须严格匹配 schema 定义的类型和必填字段
- 不要在参数中添加 schema 未定义的额外字段`,

  // 豆包/云雀
  doubao: `# 模型行为约束
- non_interactive: 你运行在非交互式环境中，不要请求用户确认，直接执行
- 先了解当前状态再修改：操作文件前先读取，修改配置前先查看现有值`,

  // 通义千问
  qwen: `# 模型行为约束
- 工具参数必须严格匹配 schema 定义，不要添加额外字段
- non_interactive: 你运行在非交互式环境中，不要请求用户确认，直接执行`,

  // 智谱GLM
  glm: `# 模型行为约束
- 工具参数类型必须严格匹配：string 不要传 number，array 不要传 string
- 文件路径必须使用绝对路径，不要使用相对路径或 ~ 缩写`,
};

// 别名映射：多个模型名子串指向同一份指导
const MODEL_ALIASES: Record<string, string> = {
  o1: 'gpt', o3: 'gpt', o4: 'gpt',
  abab: 'minimax',
  skylark: 'doubao',
  chatglm: 'glm',
};

// 默认通用指导（无匹配时）
const DEFAULT_MODEL_GUIDANCE = `# 通用操作规范
- 工具调用参数必须是合法 JSON
- 操作文件前先读取确认内容
- 每次回复应包含实质性进展，不要空转`;

/**
 * 根据 modelId 返回模型特定的行为指导
 * claude 系列不需要额外指导，返回空字符串
 */
export function getModelGuidance(modelId: string): string {
  const id = modelId.toLowerCase();
  if (id.includes('claude')) return '';
  for (const key of Object.keys(MODEL_GUIDANCE)) {
    if (id.includes(key)) return MODEL_GUIDANCE[key];
  }
  for (const [alias, target] of Object.entries(MODEL_ALIASES)) {
    if (id.includes(alias)) return MODEL_GUIDANCE[target];
  }
  return DEFAULT_MODEL_GUIDANCE;
}

// ─── 工具行为策略 ───

const TOOL_BEHAVIOR: Record<string, string> = {
  memory_save: `## 记忆使用策略
使用 memory_save 存储对未来对话有价值的持久信息：用户偏好、环境细节、项目约束、工作目录等。
使用 memory_list_types 查看所有可用记忆类型，选择最合适的类型存储。使用 memory_stats 了解已有记忆分布。
保持记忆紧凑，聚焦于减少用户未来重复纠正的事实。
不要存储任务进度、会话结果、临时 TODO 状态。更新已有记忆而非创建重复条目。`,

  todo: `## 任务规划策略
收到用户请求后，先评估任务复杂度：
**必须使用 todo：** 涉及 2 个以上步骤、修改多个文件、需要先调研再实施、用户一次提出多个需求。
**无需使用 todo：** 单步操作（查看文件、回答问题、一条命令）、简单单文件小修改。
列表顺序=优先级。同一时间只标记一个 in_progress。完成立即标记 completed。
任务列表是你的工作契约 - 它让用户看到你的计划并跟踪进度。`,

  skill_manage: `## 技能管理策略
完成复杂任务（5步以上工具调用）后，考虑用 skill_manage 将方法保存为技能以便复用。
使用技能时如发现过时或错误，立即修补，不要等用户要求。`,

  patch: `## 文件编辑策略
修改已有文件时，优先使用 patch 进行局部替换，而非 write_file 全量重写。
**用 patch：** 修改函数、修复 bug、调整配置、添加/删除代码片段。
**用 write_file：** 创建新文件、文件需要完全重写。
old_string 提供足够上下文确保唯一匹配。不需要精确匹配缩进和空白。`,

  find_files: `## 文件定位策略
需要查找文件路径、按扩展名/文件名筛选、确认目录中是否存在目标文件时，优先使用 find_files。
不要为了简单文件搜索而调用 bash。找到候选文件后，再用 read_file 读取内容。`,

  grep: `## 内容搜索策略
需要在项目文件中搜索关键字、函数名、配置项或错误文本时，优先使用 grep。
如果知道文件类型，使用 glob 限定范围。搜索到目标文件后，再用 read_file 查看上下文。`,

  read_file: `## 文件读取策略
读取文件时优先使用 read_file，不要用 bash/cat/sed 代替。
大文件会自动截断；如果结果提示继续读取，使用 offset 参数接着读取，必要时配合 limit 控制范围。
先用 find_files/grep 定位候选文件，再读取相关片段，避免一次性读取无关大文件。`,

  bash: `## 命令执行策略
使用 bash 执行构建、测试、脚本和必要的系统命令。
不要用 bash 替代 find_files/grep/read_file 做常规文件检索。
长输出会自动只保留尾部并给出完整日志路径；需要完整日志时，用 read_file 读取该路径的相关片段。`,

  clarify: `## 主动提问策略
任务需求不明确、存在多种合理解读、或缺少关键信息时，使用 clarify 向用户提问。
**应该提问：** 指令模糊有多种解读、缺少关键参数、操作有风险需确认。
**不应提问：** 任务明确可直接执行、可从上下文推断、琐碎实现细节。
提供 choices 选项让用户快速选择。一次只问一个问题。`,
};

/**
 * 根据当前可用工具列表，返回对应的行为策略提示
 * 只注入实际可用的工具策略，避免提及不存在的工具
 */
export function getToolBehaviorGuidance(
  toolNames: string[],
  toolPromptHints?: Record<string, string>
): string {
  const parts: string[] = [];
  for (const toolName of toolNames) {
    if (toolPromptHints?.[toolName]) {
      parts.push(toolPromptHints[toolName]);
      continue;
    }
    const guidance = TOOL_BEHAVIOR[toolName];
    if (guidance) parts.push(guidance);
  }
  return parts.join('\n\n');
}
