/**
 * FileSystem 插件
 * 提供文件系统操作的常用工具
 */

const { Plugin } = require('../src/core/plugin-base')
const { NodeHtmlMarkdown } = require('node-html-markdown')
const { Readable } = require('stream')
const { pipeline } = require('stream/promises')
class FileSystemPlugin extends Plugin {
  constructor(config = {}) {
    super()
    this.name = 'file-system'
    this.version = '1.0.0'
    this.description = '文件系统工具插件'
    this.priority = 5
    this.system = true 
  }

  install(framework) {
    const { z } = require('zod')
    const fs = require('fs')
    const path = require('path')
    const { exec } = require('child_process')

    // 路径安全验证：防止路径穿越攻击
    const validatePath = (filePath, allowOutsideCwd = false) => {
      const resolved = path.resolve(filePath)
      const cwd = process.cwd()

      // 允许绝对路径且在允许列表中的路径（如果有的话）
      if (allowOutsideCwd) {
        return { valid: true, resolved }
      }

      // 检查是否在 cwd 目录下
      if (!resolved.startsWith(cwd)) {
        return { valid: false, error: `路径不允许访问: ${filePath}` }
      }

      return { valid: true, resolved }
    }

    // 读取目录
    framework.registerTool({
      name: 'read_directory',
      description: '读取目录内容，列出目录中的文件和子目录',
      inputSchema: z.object({
        path: z.string().optional().describe('目录路径'),
        dirPath: z.string().optional().describe('目录路径(同path)'),
        recursive: z.boolean().optional().describe('是否递归')
      }),
      execute: async (args, framework) => {
        const dirPath = args.path || args.dirPath || '.'
        const recursive = args.recursive || false
        try {
          const items = []
          const readDir = (currentPath, depth = 0) => {
            if (depth > 3 && recursive) return
            const entries = fs.readdirSync(currentPath, { withFileTypes: true })
            for (const entry of entries) {
              const fullPath = path.join(currentPath, entry.name)
              const relativePath = path.relative(process.cwd(), fullPath)
              if (relativePath.includes('node_modules') || relativePath.includes('.git')) {
                continue
              }
              items.push({
                name: entry.name,
                path: relativePath,
                type: entry.isDirectory() ? 'directory' : 'file',
                size: entry.isFile() ? fs.statSync(fullPath).size : null
              })
              if (entry.isDirectory() && recursive && depth < 3) {
                readDir(fullPath, depth + 1)
              }
            }
          }
          readDir(dirPath)
          return { success: true, dirPath, items: items.slice(0, 100), total: items.length }
        } catch (error) {
          return { success: false, error: error.message }
        }
      }
    })

    // 创建目录
    framework.registerTool({
      name: 'create_directory',
      description: '创建新目录',
      inputSchema: z.object({
        path: z.string().optional().describe('目录路径'),
        dirPath: z.string().optional().describe('目录路径(同path)')
      }),
      execute: async (args, framework) => {
        const dirPath = args.path || args.dirPath
        try {
          fs.mkdirSync(dirPath, { recursive: true })
          return { success: true, message: `目录已创建: ${dirPath}` }
        } catch (error) {
          return { success: false, error: error.message }
        }
      }
    })

    // 读取文件
    framework.registerTool({
      name: 'read_file',
      description: '读取文件内容。path 是必填参数。',
      inputSchema: z.object({
        path: z.string().describe('文件路径（必须）'),
        lines: z.number().optional().describe('只读取前 N 行')
      }),
      execute: async (args, framework) => {
        const { path: filePath, lines } = args
        if (!filePath) {
          return { success: false, error: 'path 是必填参数' }
        }

        // 路径安全验证
        const pathCheck = validatePath(filePath)
        if (!pathCheck.valid) {
          return { success: false, error: pathCheck.error }
        }

        try {
          if (!fs.existsSync(pathCheck.resolved)) {
            return { success: false, error: '文件不存在' }
          }
          const stat = fs.statSync(pathCheck.resolved)
          if (stat.size > 1024 * 1024) {
            return { success: false, error: '文件太大，超过 1MB' }
          }
          let content
          if (lines) {
            const fileContent = fs.readFileSync(pathCheck.resolved, 'utf8')
            const allLines = fileContent.split('\n')
            content = allLines.slice(0, lines).join('\n')
          } else {
            content = fs.readFileSync(pathCheck.resolved, 'utf8')
          }
          return {
            success: true,
            filePath: pathCheck.resolved,
            content,
            size: stat.size,
            lines: lines ? null : content.split('\n').length
          }
        } catch (error) {
          return { success: false, error: error.message }
        }
      }
    })

    // 写入文件
    framework.registerTool({
      name: 'write_file',
      description: `创建或写入文件内容（仅适用于小文件，建议小于10KB）。

重要：大文件必须使用 append_to_file 分多次写入，每次追加一部分内容。
禁止将大段内容作为 content 参数传入，这会导致工具调用失败。`,
      inputSchema: z.object({
        path: z.string().describe('文件路径'),
        content: z.string().describe('文件内容'),
        mode: z.enum(['write', 'append']).default('write').describe('写入模式：write=覆盖写入，append=追加内容')
      }),
      execute: async (args, framework) => {
        const { path: filePath, content, mode = 'write' } = args
        
        const pathCheck = validatePath(filePath)
        if (!pathCheck.valid) {
          return { success: false, error: pathCheck.error }
        }

        try {
          const dir = path.dirname(pathCheck.resolved)
          if (!fs.existsSync(dir)) {
            fs.mkdirSync(dir, { recursive: true })
          }
          
          const flag = mode === 'append' ? 'a' : 'w'
          const writeStream = fs.createWriteStream(pathCheck.resolved, { 
            encoding: 'utf8',
            flags: flag,
            highWaterMark: 256 * 1024 // 256KB 缓冲区
          })
          
          // 分块写入，避免背压问题
          const CHUNK_SIZE = 1024 * 1024 // 1MB 块
          let offset = 0
          
          const writeChunk = async () => {
            while (offset < content.length) {
              const end = Math.min(offset + CHUNK_SIZE, content.length)
              const chunk = content.slice(offset, end)
              
              const canContinue = writeStream.write(chunk)
              offset = end
              
              // 如果缓冲区满了，等待 drain 事件
              if (!canContinue && offset < content.length) {
                await new Promise(resolve => writeStream.once('drain', resolve))
              }
            }
          }
          
          await writeChunk()
          
          return new Promise((resolve, reject) => {
            writeStream.end((err) => {
              if (err) reject(err)
              else resolve({
                success: true,
                message: `文件已${mode === 'append' ? '追加' : '写入'}: ${pathCheck.resolved}`,
                filePath: pathCheck.resolved,
                size: content.length,
                mode
              })
            })
          })
          
        } catch (error) {
          return { success: false, error: error.message }
        }
      }
    })

    // 追加文件内容（用于大文件分段写入）
    framework.registerTool({
      name: 'append_to_file',
      description: `追加内容到文件末尾。适用于大文件写入，每次最多追加约64KB内容。

使用方式：
1. 先用 write_file 创建新文件（或清空现有文件）
2. 多次调用 append_to_file 追加内容，每次 content 不超过 64KB
3. 追加顺序很重要，content 应该按文件顺序排列`,
      inputSchema: z.object({
        path: z.string().describe('文件路径'),
        content: z.string().describe('要追加的内容（建议不超过64KB）')
      }),
      execute: async (args, framework) => {
        const { path: filePath, content } = args

        const pathCheck = validatePath(filePath)
        if (!pathCheck.valid) {
          return { success: false, error: pathCheck.error }
        }

        try {
          // 如果文件不存在，返回错误（应该先用 write_file 创建）
          if (!fs.existsSync(pathCheck.resolved)) {
            return { success: false, error: '文件不存在，请先用 write_file 创建文件' }
          }

          fs.appendFileSync(pathCheck.resolved, content, 'utf8')
          return {
            success: true,
            message: `已追加内容到: ${pathCheck.resolved}`,
            filePath: pathCheck.resolved,
            appendedSize: content.length
          }
        } catch (error) {
          return { success: false, error: error.message }
        }
      }
    })

    // 删除文件
    framework.registerTool({
      name: 'delete_file',
      description: '删除文件或目录',
      inputSchema: z.object({
        path: z.string().optional().describe('文件或目录路径'),
        targetPath: z.string().optional().describe('文件或目录路径(同path)'),
        recursive: z.boolean().optional().describe('递归删除目录')
      }),
      execute: async (args, framework) => {
        const targetPath = args.path || args.targetPath
        const recursive = args.recursive || false

        // 路径安全验证
        const pathCheck = validatePath(targetPath)
        if (!pathCheck.valid) {
          return { success: false, error: pathCheck.error }
        }

        try {
          if (!fs.existsSync(pathCheck.resolved)) {
            return { success: false, error: '目标不存在' }
          }
          const stat = fs.statSync(pathCheck.resolved)
          if (stat.isDirectory()) {
            if (recursive) {
              fs.rmSync(pathCheck.resolved, { recursive: true, force: true })
            } else {
              fs.rmdirSync(pathCheck.resolved)
            }
          } else {
            fs.unlinkSync(pathCheck.resolved)
          }
          return { success: true, message: `已删除: ${pathCheck.resolved}` }
        } catch (error) {
          return { success: false, error: error.message }
        }
      }
    })

    // 修改文件（替换文本）
        framework.registerTool({
          name: 'modify_file',
          description: '修改文件内容（替换文本），支持精确匹配和正则',
          inputSchema: z.object({
            filePath: z.string().describe('文件路径（必须）'),
            find: z.string().min(1).describe('要查找的文本（必填，不能为空）'),
            replace: z.string().describe('替换后的文本'),
            replaceAll: z.boolean().optional().describe('是否替换所有匹配，默认 true'),
            useRegex: z.boolean().optional().describe('是否使用正则表达式匹配，默认 false'),
            backup: z.boolean().default(false).describe('修改前是否创建备份，默认 false')
          }),
          execute: async (args, framework) => {
            const filePath = args.filePath || args.path
            const find = args.find
            const replace = args.replace
            const replaceAll = args.replaceAll !== false // 默认 true
            const useRegex = args.useRegex === true
            const backup = args.backup !== false // 默认 true

            // 校验 filePath
            if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
              return { success: false, error: 'filePath 是必填参数，不能为空' }
            }

            // 校验 find
            if (!find || typeof find !== 'string' || find.trim() === '') {
              return { success: false, error: 'find 是必填参数，不能为空字符串' }
            }

            // 路径安全验证
            const pathCheck = validatePath(filePath)
            if (!pathCheck.valid) {
              return { success: false, error: pathCheck.error }
            }

            try {
              if (!fs.existsSync(pathCheck.resolved)) {
                return { success: false, error: '文件不存在' }
              }

              // 检查是否是二进制文件
              const stats = fs.statSync(pathCheck.resolved)
              if (stats.size > 10 * 1024 * 1024) {
                return { success: false, error: '文件超过 10MB，不支持修改' }
              }

              let content = fs.readFileSync(pathCheck.resolved, 'utf8')
              let count = 0
              const matches = []

              if (useRegex) {
                // 正则表达式模式
                const flags = replaceAll ? 'g' : ''
                const regex = new RegExp(find, flags)

                let match
                while ((match = regex.exec(content)) !== null) {
                  matches.push({
                    index: match.index,
                    length: match[0].length,
                    text: match[0]
                  })
                  if (!replaceAll) break
                }

                if (matches.length > 0) {
                  count = replaceAll ? matches.length : 1
                  content = content.replace(regex, replace)
                }
              } else {
                // 精确字符串匹配
                const escapedFind = find.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
                const flags = replaceAll ? 'g' : ''
                const regex = new RegExp(escapedFind, flags)

                let match
                while ((match = regex.exec(content)) !== null) {
                  matches.push({
                    index: match.index,
                    length: match[0].length,
                    text: match[0]
                  })
                  if (!replaceAll) break
                }

                if (matches.length > 0) {
                  count = replaceAll ? matches.length : 1
                  content = content.replace(regex, replace)
                }
              }

              if (count === 0) {
                return {
                  success: false,
                  error: `未找到匹配文本: "${find.substring(0, 50)}${find.length > 50 ? '...' : ''}"`,
                  filePath
                }
              }

              // 创建备份
              if (backup) {
                const backupPath = pathCheck.resolved + '.bak'
                fs.writeFileSync(backupPath, fs.readFileSync(pathCheck.resolved), 'utf8')
              }

              // 原子写入：先写临时文件，再 rename
              const tempPath = pathCheck.resolved + '.tmp.' + Date.now()
              fs.writeFileSync(tempPath, content, 'utf8')

              // 验证写入内容
              const verifyContent = fs.readFileSync(tempPath, 'utf8')
              if (verifyContent !== content) {
                fs.unlinkSync(tempPath)
                return { success: false, error: '写入验证失败，内容不匹配' }
              }

              fs.renameSync(tempPath, pathCheck.resolved)

              return {
                success: true,
                message: `文件已修改: ${pathCheck.resolved}`,
                filePath: pathCheck.resolved,
                replacements: count,
                matches: matches.slice(0, 5), // 最多返回5个匹配位置
                backupCreated: backup
              }
            } catch (error) {
              return { success: false, error: error.message }
            }
          }
        })

    // 搜索文件
    framework.registerTool({
      name: 'search_file',
      description: '在文件或目录中搜索文本，支持精确匹配和正则表达式',
      inputSchema: z.object({
        pattern: z.string().describe('搜索模式（关键词或正则表达式）'),
        path: z.string().optional().describe('搜索目录路径'),
        file: z.string().optional().describe('搜索指定文件（与 path 二选一）'),
        fileType: z.string().optional().describe('文件类型过滤，如 .js、.py'),
        maxResults: z.number().optional().describe('最大结果数，默认 100'),
        maxResultsPerFile: z.number().optional().describe('每个文件最大结果数，默认 50'),
        contextLines: z.number().optional().describe('匹配行的上下文行数，默认 0'),
        caseSensitive: z.boolean().optional().describe('是否大小写敏感，默认 false'),
        useRegex: z.boolean().optional().describe('是否使用正则表达式，默认 false'),
        excludeDirs: z.array(z.string()).optional().describe('排除的目录名，默认 ["node_modules", ".git", "dist", "build"]')
      }),
      execute: async (args, framework) => {
        const pattern = args.pattern
        const dirPath = args.path || process.cwd()
        const targetFile = args.file
        const fileType = args.fileType
        const maxResults = args.maxResults || 100
        const maxResultsPerFile = args.maxResultsPerFile || 50
        const contextLines = args.contextLines || 0
        const caseSensitive = args.caseSensitive === true
        const useRegex = args.useRegex === true
        const excludeDirs = args.excludeDirs || ['node_modules', '.git', 'dist', 'build', '.claude']

        if (!pattern || typeof pattern !== 'string' || pattern.trim() === '') {
          return { success: false, error: 'pattern 是必填参数，不能为空' }
        }

        try {
          const results = []
          let regex

          if (useRegex) {
            // 直接使用正则表达式
            try {
              regex = new RegExp(pattern, caseSensitive ? 'g' : 'gi')
            } catch (e) {
              return { success: false, error: `无效的正则表达式: ${e.message}` }
            }
          } else {
            // 转义特殊字符作为精确匹配
            const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
            regex = new RegExp(escaped, caseSensitive ? 'g' : 'gi')
          }

          const binaryExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.ico', '.exe', '.dll', '.so', '.zip', '.tar', '.gz', '.rar', '.7z', '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.mp3', '.mp4', '.avi', '.mkv', '.wav', '.flac']

          const searchInContent = (content, filePath) => {
            const matches = []
            const lines = content.split('\n')
            let fileResultsCount = 0

            for (let i = 0; i < lines.length; i++) {
              if (fileResultsCount >= maxResultsPerFile) break
              const line = lines[i]
              regex.lastIndex = 0 // 重置 regex 状态

              if (regex.test(line)) {
                // 找到所有匹配的位置
                const lineMatches = []
                regex.lastIndex = 0
                let match
                while ((match = regex.exec(line)) !== null) {
                  lineMatches.push({
                    column: match.index,
                    length: match[0].length,
                    text: match[0]
                  })
                  if (!regex.global) break
                }

                const matchInfo = {
                  file: filePath,
                  line: i + 1,
                  content: line.substring(0, 300),
                  matches: lineMatches
                }

                if (contextLines > 0) {
                  const start = Math.max(0, i - contextLines)
                  const end = Math.min(lines.length, i + contextLines + 1)
                  matchInfo.context = lines.slice(start, end).map((l, idx) => ({
                    line: start + idx + 1,
                    content: l
                  }))
                }

                matches.push(matchInfo)
                fileResultsCount++
              }
            }

            return matches
          }

          // 搜索单个文件
          if (targetFile) {
            const fullPath = path.resolve(targetFile)
            if (!fs.existsSync(fullPath)) {
              return { success: false, error: `文件不存在: ${targetFile}` }
            }

            const ext = path.extname(fullPath).toLowerCase()
            if (binaryExtensions.includes(ext)) {
              return { success: false, error: `不支持搜索二进制文件: ${ext}` }
            }

            try {
              const content = fs.readFileSync(fullPath, 'utf8')
              const fileMatches = searchInContent(content, targetFile)
              results.push(...fileMatches)
            } catch (e) {
              return { success: false, error: `读取文件失败: ${e.message}` }
            }
          } else {
            // 搜索目录
            const searchDir = (currentPath, depth = 0) => {
              if (depth > 10 || results.length >= maxResults) return

              let entries
              try {
                entries = fs.readdirSync(currentPath, { withFileTypes: true })
              } catch (e) {
                return // 跳过无法读取的目录
              }

              for (const entry of entries) {
                if (results.length >= maxResults) break

                const fullPath = path.join(currentPath, entry.name)
                const relativePath = path.relative(process.cwd(), fullPath)

                // 检查是否在排除目录中
                const shouldExclude = excludeDirs.some(exclude =>
                  relativePath.includes(exclude)
                )
                if (shouldExclude) continue

                if (entry.isDirectory()) {
                  searchDir(fullPath, depth + 1)
                } else if (entry.isFile()) {
                  // 文件类型过滤
                  if (fileType && !entry.name.endsWith(fileType)) continue

                  const ext = path.extname(entry.name).toLowerCase()
                  if (binaryExtensions.includes(ext)) continue

                  try {
                    const content = fs.readFileSync(fullPath, 'utf8')
                    const fileMatches = searchInContent(content, relativePath)

                    for (const match of fileMatches) {
                      if (results.length >= maxResults) break
                      results.push(match)
                    }
                  } catch (e) {
                    // 跳过无法读取的文件
                  }
                }
              }
            }

            searchDir(dirPath)
          }

          // 计算统计信息
          const filesWithMatches = new Set(results.map(r => r.file)).size
          const totalMatches = results.reduce((sum, r) => sum + (r.matches?.length || 1), 0)

          return {
            success: true,
            pattern,
            results: results.slice(0, maxResults),
            total: results.length,
            stats: {
              filesWithMatches,
              totalMatches,
              searchPath: targetFile || dirPath
            }
          }
        } catch (error) {
          return { success: false, error: error.message }
        }
      }
    })

    // 执行命令
    framework.registerTool({
      name: 'execute_command',
      description: '执行终端命令',
      inputSchema: z.object({
        cmd: z.string().optional().describe('要执行的命令'),
        command: z.string().optional().describe('要执行的命令(同cmd)'),
        run: z.string().optional().describe('要执行的命令(同cmd)'),
        cwd: z.string().optional().describe('工作目录'),
        timeout: z.number().optional().describe('超时时间(ms)')
      }),
      execute: async (args, framework) => {
        const command = args.cmd || args.command || args.run
        const cwd = args.cwd || process.cwd()
        const timeout = Math.min(args.timeout || 30000, 120000) // 最多 2 分钟

        // 验证命令：检查危险的 shell 模式
        const dangerousPatterns = [
          /;\s*rm\s+/i,           // ; rm -rf
          /\|\s*rm\s+/i,          // | rm -rf
          /&&\s*rm\s+/i,          // && rm -rf
          /;\s*del\s+/i,          // ; del
          /\|\s*del\s+/i,         // | del
          /&\s*rm\s+/i,           // & rm -rf
          /;\s*format\s+/i,       // ; format
          /&\s*format\s+/i,       // & format
          /\$\(/i,                // $(command substitution)
          /`[^`]+`/i,             // `command substitution`
          />\s*\/dev\//i,         // redirect to /dev/
          /<\s*\/dev\//i,         // read from /dev/
        ]

        for (const pattern of dangerousPatterns) {
          if (pattern.test(command)) {
            return {
              success: false,
              error: `命令包含可疑模式，已拒绝执行: ${pattern.toString()}`
            }
          }
        }

        // 验证 cwd 路径
        const resolvedCwd = path.resolve(cwd)
        if (!fs.existsSync(resolvedCwd)) {
          return { success: false, error: `工作目录不存在: ${resolvedCwd}` }
        }

        return new Promise((resolve) => {
          const startTime = Date.now()
          exec(command, { cwd: resolvedCwd, timeout, shell: true }, (error, stdout, stderr) => {
            const duration = Date.now() - startTime
            if (error) {
              resolve({ success: false, command, error: error.message, stderr: stderr.substring(0, 1000), duration })
            } else {
              resolve({
                success: true,
                command,
                stdout: stdout.substring(0, 10000),
                stderr: stderr.substring(0, 1000),
                duration
              })
            }
          })
        })
      }
    })

    // 执行 Bash 命令
    framework.registerTool({
      name: 'bash',
      description: '执行 Bash 命令（支持 Linux/macOS/Windows Git Bash）',
      inputSchema: z.object({
        cmd: z.string().optional().describe('要执行的 bash 命令'),
        command: z.string().optional().describe('要执行的 bash 命令(同cmd)'),
        cwd: z.string().optional().describe('工作目录'),
        timeout: z.number().optional().describe('超时时间(ms)')
      }),
      execute: async (args, framework) => {
        const command = args.cmd || args.command
        const cwd = args.cwd || process.cwd()
        const timeout = Math.min(args.timeout || 30000, 120000)

        if (!command) {
          return { success: false, error: 'cmd/command 是必填参数' }
        }

        // 验证命令：检查危险的 shell 模式
        const dangerousPatterns = [
          /;\s*rm\s+-rf/i,
          /\|\s*rm\s+/i,
          /&&\s*rm\s+/i,
          /;\s*del\s+/i,
          /\$\(/i,
          /`[^`]+`/i,
          />\s*\/dev\//i,
          /<\s*\/dev\//i,
        ]

        for (const pattern of dangerousPatterns) {
          if (pattern.test(command)) {
            return {
              success: false,
              error: `命令包含可疑模式，已拒绝执行: ${pattern.toString()}`
            }
          }
        }

        // 验证 cwd 路径
        const resolvedCwd = path.resolve(cwd)
        if (!fs.existsSync(resolvedCwd)) {
          return { success: false, error: `工作目录不存在: ${resolvedCwd}` }
        }

        // 确定 bash 路径
        let bashPath = 'bash'
        const isWindows = process.platform === 'win32'

        if (isWindows) {
          // Windows 下尝试查找 Git Bash
          const possiblePaths = [
            'C:\\Program Files\\Git\\bin\\bash.exe',
            'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
            process.env.GIT_BASH_PATH || 'bash'
          ]
          for (const p of possiblePaths) {
            if (fs.existsSync(p)) {
              bashPath = p
              break
            }
          }
        }

        return new Promise((resolve) => {
          const startTime = Date.now()
          exec(
            `"${bashPath}" -c ${JSON.stringify(command)}`,
            { cwd: resolvedCwd, timeout, shell: false },
            (error, stdout, stderr) => {
              const duration = Date.now() - startTime
              if (error) {
                resolve({
                  success: false,
                  command,
                  error: error.message,
                  stderr: stderr.substring(0, 2000),
                  stdout: stdout.substring(0, 2000),
                  duration
                })
              } else {
                resolve({
                  success: true,
                  command,
                  stdout: stdout.substring(0, 10000),
                  stderr: stderr.substring(0, 1000),
                  duration
                })
              }
            }
          )
        })
      }
    })

    // 获取北京时间
    framework.registerTool({
      name: 'get_time',
      description: '获取当前的时间，需要获取日期和时间的时候需要调用该接口',
      inputSchema: z.object({}),
      execute: async (args, framework) => {
        const now = new Date()
        const beijingTime = new Date(now.getTime() + (8 * 60 * 60 * 1000))
        return {
          success: true,
          beijingTime: beijingTime.toISOString().replace('T', ' ').substring(0, 19),
          timestamp: now.getTime(),
          timezone: 'Asia/Shanghai (UTC+8)',
          formatted: {
            year: beijingTime.getUTCFullYear(),
            month: String(beijingTime.getUTCMonth() + 1).padStart(2, '0'),
            day: String(beijingTime.getUTCDate()).padStart(2, '0'),
            hour: String(beijingTime.getUTCHours()).padStart(2, '0'),
            minute: String(beijingTime.getUTCMinutes()).padStart(2, '0'),
            second: String(beijingTime.getUTCMinutes()).padStart(2, '0')
          }
        }
      }
    })

    // HTTP 请求
    framework.registerTool({
      name: 'fetch',
      description: '发送 HTTP 请求获取远程数据。HTML 内容会保存到临时文件并返回路径；非 HTML 内容可选择转换为 Markdown。注意：如果访问失败或超时，LLM 应自动使用 proxy: true 重试。',
      inputSchema: z.object({
        url: z.string().describe('请求 URL'),
        method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).optional().describe('请求方法，默认 GET'),
        headers: z.record(z.string()).optional().describe('请求头'),
        body: z.string().optional().describe('请求体（POST/PUT/PATCH 时使用）'),
        timeout: z.number().optional().describe('超时时间(ms)，默认 30000'),
        proxy: z.boolean().optional().describe('是否使用代理，默认 false。访问失败时可自动设为 true 重试'),
        toMarkdown: z.boolean().default(true).describe('是否将 HTML 响应转换为 Markdown 格式，默认 true'),
        maxLength: z.number().optional().describe('最大返回长度（字符数），超过则截断')
      }),
      execute: async (args, framework) => {
        const { url, method = 'GET', headers = {}, body, timeout = 30000, proxy = false, toMarkdown = true, maxLength } = args
        const fs = require('fs')
        const path = require('path')
        const os = require('os')

        try {
          const controller = new AbortController()
          const timeoutId = setTimeout(() => controller.abort(), timeout)
          const fetchUrl = proxy ? `https://fcdn.foliko.com?url=${encodeURIComponent(url)}` : url

          const response = await fetch(fetchUrl, {
            method,
            headers,
            body: body || undefined,
            signal: controller.signal
          })

          clearTimeout(timeoutId)

          const text = await response.text()
          let data
          try {
            data = JSON.parse(text)
          } catch {
            data = text
          }

          // 检测是否为 HTML 内容
          const isHtml = typeof data === 'string' && (/<[a-z][\s\S]*>/i.test(data) || data.trim().startsWith('<'))

          // HTML 内容统一保存到临时文件
          if (isHtml) {
            const tmpDir = os.tmpdir()
            const filename = `fetch_${Date.now()}_${Math.random().toString(36).slice(2, 8)}.html`
            const filePath = path.join(tmpDir, filename)
            fs.writeFileSync(filePath, data, 'utf-8')

            // toMarkdown 为 true 时返回 markdown
            if (toMarkdown) {
              try {
                const markdown = NodeHtmlMarkdown.translate(data)
                return {
                  success: true,
                  status: response.status,
                  statusText: response.statusText,
                  headers: Object.fromEntries(response.headers.entries()),
                  usedProxy: proxy,
                  filePath: filePath,
                  markdown: markdown,
                  content: markdown
                }
              } catch (e) {
                // 转换失败时只返回路径
                return {
                  success: true,
                  status: response.status,
                  statusText: response.statusText,
                  headers: Object.fromEntries(response.headers.entries()),
                  usedProxy: proxy,
                  filePath: filePath,
                  content: `[HTML已保存到文件: ${filePath}]`
                }
              }
            }

            return {
              success: true,
              status: response.status,
              statusText: response.statusText,
              headers: Object.fromEntries(response.headers.entries()),
              usedProxy: proxy,
              filePath: filePath,
              content: `[HTML已保存到文件: ${filePath}]`
            }
          }

          // 非 HTML 内容
          if (toMarkdown && typeof data === 'string') {
            try {
              const markdown = NodeHtmlMarkdown.translate(data)
              const truncatedMarkdown = maxLength && markdown.length > maxLength
                ? markdown.substring(0, maxLength) + '\n\n...(truncated)'
                : markdown
              return {
                success: true,
                status: response.status,
                statusText: response.statusText,
                headers: Object.fromEntries(response.headers.entries()),
                usedProxy: proxy,
                markdown: truncatedMarkdown,
                content: truncatedMarkdown,
                originalLength: markdown.length,
                truncated: maxLength && markdown.length > maxLength
              }
            } catch (e) {
              // 转换失败时返回原始 body
            }
          }

          // 返回原始内容
          const truncatedData = maxLength && typeof data === 'string' && data.length > maxLength
            ? data.substring(0, maxLength) + '\n\n...(truncated)'
            : data
          return {
            success: true,
            status: response.status,
            statusText: response.statusText,
            headers: Object.fromEntries(response.headers.entries()),
            usedProxy: proxy,
            body: truncatedData,
            originalLength: typeof data === 'string' ? data.length : null,
            truncated: maxLength && typeof data === 'string' && data.length > maxLength
          }
        } catch (error) {
          return {
            success: false,
            error: error.message,
            url,
            method,
            hint: '如果访问失败，可尝试设置 proxy: true'
          }
        }
      }
    })

    // 发送通知
    framework.registerTool({
      name: 'notification_send',
      description: '发送系统通知，仅发送给当前聊天会话，通知会显示给用户或在下次对话时呈现',
      inputSchema: z.object({
        title: z.string().describe('通知标题'),
        message: z.string().describe('通知内容'),
        source: z.string().optional().describe('通知来源标识，默认 系统消息')
      }),
      execute: async (args, framework) => {
        const { title, message, source = '系统消息' } = args
        try {
          // 获取当前执行上下文中的 sessionId，只发送到当前会话
          const ctx = framework.getExecutionContext()
          const sessionId = ctx?.sessionId || null

          framework.emit('notification', {
            title,
            message,
            source,
            sessionId,
            timestamp: new Date().toISOString()
          })
          return { success: true, message: '通知已发送' }
        } catch (error) {
          return { success: false, error: error.message }
        }
      }
    })

    return this
  }
}

module.exports = FileSystemPlugin
