"""
CLI 交互界面 - 使用 prompt_toolkit 实现命令补全
"""

import asyncio
from typing import Optional, List
from pathlib import Path
from datetime import datetime

from dotenv import load_dotenv
from prompt_toolkit import PromptSession
from prompt_toolkit.completion import Completer, Completion
from prompt_toolkit.history import FileHistory
from prompt_toolkit.styles import Style

from ..config import ScraperConfig, LLMConfig
from ..agent import NSFCAgent
from ..memory import get_memory
from ..tasks import (
    NSFCDirectScraper,
    NSFCSearchTask,
    SearchResults,
    DataExporter, 
    DataAnalyzer,
    LLMFilter,
    ReviewGenerator,
)


def _sanitize_filename(name: str, max_length: int = 50) -> str:
    """
    将字符串转换为安全的文件名
    - 移除或替换特殊字符
    - 限制长度
    """
    import re
    # 替换常见的特殊符号为下划线
    safe_name = re.sub(r'[\\/:*?"<>|+\s]+', '_', name)
    # 移除其他非法字符，保留中文、字母、数字、下划线、连字符
    safe_name = re.sub(r'[^\w\u4e00-\u9fff\-]', '', safe_name)
    # 合并连续下划线
    safe_name = re.sub(r'_+', '_', safe_name)
    # 去除首尾下划线
    safe_name = safe_name.strip('_')
    # 限制长度
    if len(safe_name) > max_length:
        safe_name = safe_name[:max_length].rstrip('_')
    return safe_name or "untitled"


def _load_env():
    """加载环境变量"""
    paths = [
        Path.cwd() / ".env",
        Path.cwd() / "config" / ".env",
        Path(__file__).parent.parent.parent.parent / ".env",
        Path(__file__).parent.parent.parent.parent / "config" / ".env",
    ]
    for p in paths:
        if p.exists():
            load_dotenv(p)
            return str(p)
    load_dotenv()
    return None

_load_env()


# 命令定义
COMMANDS = {
    '/scrape': '直接爬取(推荐)',
    '/search': 'AI搜索(实验性)',
    '/batch': '批量搜索多关键词(直接爬取)',
    '/load': '加载数据文件',
    '/export': '导出 excel/csv/json',
    '/analyze': '数据统计分析',
    '/filter': 'LLM 筛选项目',
    '/review': '生成研究综述',
    '/pipeline': '搜索-筛选-综述',
    '/memory': '查看/添加经验',
    '/config': '配置 LLM',
    '/status': '查看状态',
    '/help': '显示帮助',
    '/exit': '退出',
}


class NSFCCompleter(Completer):
    """命令补全器"""
    
    def __init__(self, output_dir: str = "./output"):
        self.output_dir = Path(output_dir)
    
    def get_completions(self, document, complete_event):
        text = document.text_before_cursor
        
        # 命令补全
        if text.startswith('/') and ' ' not in text:
            for cmd, desc in COMMANDS.items():
                if cmd.startswith(text):
                    yield Completion(cmd, -len(text), display_meta=desc)
        
        # /load 文件补全
        elif text.startswith('/load '):
            yield from self._complete_files(text[6:])
        
        # /export 格式补全
        elif text.startswith('/export '):
            for fmt in ['excel', 'csv', 'json']:
                if fmt.startswith(text[8:]):
                    yield Completion(fmt, -len(text[8:]))
    
    def _complete_files(self, prefix: str):
        """文件补全 - 按创建时间排序"""
        if not self.output_dir.exists():
            return
        
        files = list(self.output_dir.glob("*.json"))
        # 按修改时间降序排序
        files.sort(key=lambda f: f.stat().st_mtime, reverse=True)
        
        for f in files:
            name = f.name
            if name.startswith(prefix):
                mtime = datetime.fromtimestamp(f.stat().st_mtime)
                meta = mtime.strftime("%m-%d %H:%M")
                yield Completion(name, -len(prefix), display_meta=meta)


class NSFCCLI:
    """国自然爬虫 CLI"""
    
    def __init__(self, skip_wizard: bool = False):
        self.config: Optional[ScraperConfig] = None
        self.agent: Optional[NSFCAgent] = None
        self.current_data: Optional[SearchResults] = None
        self.running = True
        self.playwright_available = True
        self.llm_available = False
        
        # 首次运行检查
        if not skip_wizard:
            self._run_first_time_setup()
        
        self._auto_configure()
    
    def _run_first_time_setup(self):
        """首次运行设置向导"""
        from ..setup_wizard import check_playwright, check_llm_config, run_setup_wizard
        
        # 检查是否需要运行向导
        playwright_ok, _ = check_playwright()
        llm_ok, _ = check_llm_config()
        
        # 如果都已配置，跳过向导
        if playwright_ok and llm_ok:
            return
        
        # 运行向导
        self.playwright_available = run_setup_wizard()
    
    def _auto_configure(self):
        """自动加载配置"""
        try:
            self.config = ScraperConfig.from_env()
            if self.config.llm.api_key:
                self.agent = NSFCAgent(self.config)
                self.llm_available = True
                print("✓ LLM 配置已加载")
            else:
                print("ℹ️  LLM 未配置，高级功能不可用（/filter, /review, /pipeline）")
                print("   运行 /config 进行配置")
        except Exception as e:
            print(f"⚠ 配置加载失败: {e}")
    
    def print_help(self):
        """打印帮助"""
        help_text = """
╔══════════════════════════════════════════════════════════════╗
║                   国自然项目爬虫 CLI 帮助                      ║
╚══════════════════════════════════════════════════════════════╝

【爬取命令】
/scrape <关键词> [-p 页数|all] [-y 年份]  ⭐推荐！直接爬取，自动获取详情摘要
/search <关键词> [-p 页数|all]  AI搜索（实验性，不稳定）
/batch <词1>,<词2> [-p 页数|all] [-y 年份] 批量搜索，默认直接爬取
                                  加 --ai 切换为AI搜索模式

【数据管理】
  /load [文件名]               加载已保存的JSON数据（支持多选合并）
  /export [excel|csv|json]     导出数据（json格式可被load加载）

【分析功能】
  /analyze                     统计分析：年度分布、单位排名、关键词等
  /filter <条件>               LLM智能筛选（需配置API Key）
  /review <主题> [| 自定义要求] 生成研究综述，保存为docx（需配置API Key）

【一键流程】
  /pipeline <搜索> | <筛选> | <综述主题> [| 自定义要求]
      一次完成：爬取 → LLM筛选 → 生成综述

【系统命令】
  /memory                      查看/添加AI经验记忆
  /config                      配置LLM（API Key、模型等）
  /status                      查看当前状态
  /help                        显示本帮助
  /exit                        退出程序

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【使用示例】

1. 直接爬取（推荐）：
   /scrape 轨道交通 -p 3      爬取3页，自动获取详情和摘要
   /scrape 轨道交通 -p all    爬取全部页面

2. 按年份爬取（获取更全数据）：
   /scrape 轨道交通 -y 5 -p 2       最近5年，每年爬取2页
   /scrape 轨道交通 -y 2020-2024    指定年份范围，每年1页
   /scrape 轨道交通 -y 2023,2024 -p all  指定年份，每年全部页面

3. 批量搜索：
   /batch 轨道,铁路,高铁 -p 2    搜索3个关键词，每个2页
   /batch 轨道,铁路,高铁 -p all  搜索3个关键词，每个全部页面
   /batch 轨道,铁路 -y 2022-2024 -p 2  按年份批量搜索
   /batch 轨道,铁路 --ai -p 2    使用AI模式批量搜索

4. 加载数据：
   /load                       显示文件列表，交互式选择
   /load nsfc_轨道             模糊匹配文件名
   多选: 输入序号用逗号分隔，如 1,2,3 会合并加载并去重

5. LLM筛选：
   /filter 与风险评估相关的项目

6. 生成综述：
   /review 轨道交通安全风险研究综述
   /review 人工智能综述 | 重点分析深度学习，字数3000字以上

7. 一键流程：
   /pipeline 轨道交通 -p 5 | 风险管理相关 | 轨道交通风险研究

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【提示】
  • 输入 / 后按Tab可自动补全命令
  • 搜索会打开浏览器窗口，请勿手动操作
  • LLM功能需先运行 /config 配置API Key
"""
        print(help_text)
    
    def print_status(self):
        """打印状态"""
        print("\n【当前状态】")
        if not self.config:
            print("  ⚠ 未配置，请运行 /config")
            return
        
        # LLM 配置
        if self.config.llm.api_key:
            print(f"  LLM: {self.config.llm.provider}/{self.config.llm.model} ✓")
            if self.config.llm.base_url:
                print(f"  API: {self.config.llm.base_url}")
            print(f"  配置文件: config/.env 或 ~/.nsfc_spider/.env")
            print(f"  重新配置: 运行 /config")
        else:
            print("  LLM: 未配置")
            print("  配置方法: 运行 /config")
        
        # Playwright 状态
        if self.playwright_available:
            print(f"  Playwright: 已安装 ✓")
        else:
            print(f"  Playwright: 未安装")
            print(f"  安装方法: playwright install chromium")
        
        # 数据状态
        if self.current_data and self.current_data.projects:
            print(f"  数据: {len(self.current_data.projects)} 条")
            print(f"  关键词: {self.current_data.keyword}")
        else:
            print("  数据: 无（请先搜索或加载）")
    
    def configure(self):
        """交互式配置 - 支持查看和修改现有配置"""
        from ..setup_wizard import save_config, check_llm_config
        
        # 检查是否已有配置
        has_config, current_config = check_llm_config()
        
        if has_config:
            print("\n" + "=" * 60)
            print("  当前 LLM 配置")
            print("=" * 60)
            print(f"  提供商: {current_config['provider']}")
            print(f"  模型: {current_config['model']}")
            print(f"  API Key: {current_config['api_key']}")
            print("\n是否重新配置？(y/n)")
            choice = input("  > ").strip().lower()
            
            if choice != 'y':
                print("\n已取消配置。")
                return
        
        print("\n" + "=" * 60)
        print("  LLM 配置")
        print("=" * 60)
        
        # 选择提供商
        print("\n1. 选择 LLM 提供商：")
        print("  1) OpenAI (gpt-4o, gpt-4o-mini)")
        print("  2) Anthropic (claude-sonnet-4)")
        print("  3) DeepSeek (deepseek-chat)")
        print("  4) Ollama (本地模型)")
        print("  5) OpenAI-Compatible (自定义 API)")
        
        provider_choice = input("\n请选择 [1-5，默认 1]: ").strip() or "1"
        
        provider_map = {
            "1": ("openai", "gpt-4o", "https://api.openai.com/v1"),
            "2": ("anthropic", "claude-sonnet-4", None),
            "3": ("openai", "deepseek-chat", "https://api.deepseek.com/v1"),
            "4": ("ollama", "llama2", "http://localhost:11434"),
            "5": ("openai", "", ""),  # 自定义
        }
        
        provider, default_model, default_base_url = provider_map.get(provider_choice, provider_map["1"])
        
        # 输入模型
        if provider_choice == "5":
            print(f"\n2. 模型名称:")
            print("  例如：gpt-4o, gpt-3.5-turbo, qwen-plus 等")
            model = input("  > ").strip()
            if not model:
                print("\n✗ 模型名称不能为空，配置取消。")
                return
        else:
            print(f"\n2. 模型名称 [默认: {default_model}]:")
            model = input("  > ").strip() or default_model
        
        # 输入 API Key
        if provider != "ollama":
            print("\n3. API Key:")
            print("  获取地址：")
            if provider == "openai":
                print("    https://platform.openai.com/api-keys")
            elif provider == "anthropic":
                print("    https://console.anthropic.com/")
            elif provider == "deepseek":
                print("    https://platform.deepseek.com/")
            
            api_key = input("\n  请输入 API Key: ").strip()
            
            if not api_key:
                print("\n✗ API Key 不能为空，配置取消。")
                return
        else:
            api_key = ""
            print("\n  Ollama 本地模型无需 API Key")
        
        # 输入 Base URL
        if provider_choice == "5":
            print(f"\n4. Base URL:")
            print("  例如：https://api.openai-proxy.com/v1")
            print("  例如：http://localhost:8000/v1")
            base_url = input("  > ").strip()
            if not base_url:
                print("\n✗ Base URL 不能为空，配置取消。")
                return
        elif default_base_url:
            print(f"\n4. Base URL [默认: {default_base_url}]:")
            print("  (直接回车使用默认值)")
            base_url = input("  > ").strip() or default_base_url
        else:
            base_url = None
        
        # 保存配置
        config_dict = {
            "provider": provider,
            "model": model,
            "api_key": api_key,
            "base_url": base_url
        }
        
        # 显示配置预览
        print("\n" + "-" * 60)
        print("  配置预览")
        print("-" * 60)
        print(f"  提供商: {provider}")
        print(f"  模型: {model}")
        if api_key:
            print(f"  API Key: {api_key[:10]}...")
        if base_url:
            print(f"  Base URL: {base_url}")
        
        print("\n是否保存配置？(y/n)")
        save_choice = input("  > ").strip().lower()
        
        if save_choice == 'y':
            save_config(config_dict)
            
            # 重新加载配置
            self.config = ScraperConfig(
                llm=LLMConfig(provider=provider, model=model, api_key=api_key, base_url=base_url)
            )
            if api_key:
                self.agent = NSFCAgent(self.config)
                self.llm_available = True
            
            print("\n✓ 配置已保存并生效！")
            print("  配置文件位置: config/.env 或 ~/.nsfc_spider/.env")
            print("\n现在可以使用以下 LLM 功能：")
            print("  • /search - AI 智能搜索")
            print("  • /filter - LLM 智能筛选")
            print("  • /review - 生成研究综述")
            print("  • /pipeline - 一键流程\n")
        else:
            print("\n配置未保存。")
    
    def _parse_args(self, args: str) -> tuple[str, int, bool, Optional[List[int]]]:
        """解析参数: 关键词 [-p 页数|all] [-y 年份范围] [-a 获取摘要]
        
        年份参数格式:
          -y 5        最近5年（自动计算）
          -y 2024     单个年份
          -y 2020-2024  年份范围
          -y 2020,2021,2022  指定多个年份
        """
        include_abstract = "-a" in args
        args = args.replace("-a", "").strip()
        
        # 解析年份参数
        years: Optional[List[int]] = None
        if "-y" in args:
            parts = args.split("-y")
            before_y = parts[0].strip()
            if len(parts) > 1:
                after_y = parts[1]
                # 检查 -y 后面是否有 -p
                if "-p" in after_y:
                    y_p_parts = after_y.split("-p")
                    year_arg = y_p_parts[0].strip()
                    # 保留 -p 部分到 args
                    args = before_y + " -p" + y_p_parts[1]
                else:
                    year_arg = after_y.strip()
                    args = before_y
                years = self._parse_years(year_arg)
        
        # 解析页数
        parts = args.split("-p")
        keyword = parts[0].strip()
        
        if len(parts) > 1:
            page_arg = parts[1].strip().lower()
            if page_arg == "all":
                pages = 0
            else:
                pages = int(page_arg)
        else:
            pages = 1
        
        return keyword, pages, include_abstract, years
    
    def _parse_years(self, year_arg: str) -> List[int]:
        """解析年份参数"""
        current_year = datetime.now().year
        year_arg = year_arg.strip()
        
        # 格式1: 单个数字 <= 10 表示最近N年
        if year_arg.isdigit() and int(year_arg) <= 10:
            n = int(year_arg)
            return list(range(current_year, current_year - n, -1))
        
        # 格式2: 年份范围 2020-2024
        if "-" in year_arg and not year_arg.startswith("-"):
            start, end = year_arg.split("-")
            start_year = int(start.strip())
            end_year = int(end.strip())
            return list(range(end_year, start_year - 1, -1))
        
        # 格式3: 逗号分隔 2020,2021,2022
        if "," in year_arg:
            return [int(y.strip()) for y in year_arg.split(",") if y.strip()]
        
        # 格式4: 单个年份
        if year_arg.isdigit():
            return [int(year_arg)]
        
        return []
    
    def _save_results(self, keyword: str) -> str:
        """保存搜索结果到 JSON 文件"""
        import json
        if not self.current_data:
            return ""
        
        output_dir = Path(self.config.output_dir) if self.config else Path("./output")
        output_dir.mkdir(parents=True, exist_ok=True)
        
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        safe_keyword = keyword.replace(" ", "_")[:20]
        filename = f"nsfc_{safe_keyword}_{timestamp}.json"
        filepath = output_dir / filename
        
        data = self.current_data.model_dump()
        with open(filepath, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        
        return str(filepath)
    
    async def do_scrape(self, args: str):
        """直接爬取 - 使用 Playwright 精确控制（推荐）"""
        if not args:
            print("用法: /scrape <关键词> [-p 页数] [-y 年份]")
            print("示例: /scrape 轨道交通 -p 3")
            print("示例: /scrape 轨道交通 -y 5 -p 2    # 最近5年，每年2页")
            print("示例: /scrape 轨道交通 -y 2020-2024  # 指定年份范围")
            return
        
        keyword, pages, _, years = self._parse_args(args)
        
        if years:
            years_str = ",".join(str(y) for y in years)
            print(f"🔍 按年份爬取: {keyword}")
            print(f"   年份: {years_str}, 每年 {pages if pages > 0 else '全部'} 页")
        else:
            print(f"🔍 直接爬取: {keyword}, {pages if pages > 0 else '全部'} 页")
        print("  (使用 Playwright 精确控制，自动获取详情)")
        
        try:
            scraper = NSFCDirectScraper(headless=False)
            self.current_data = await scraper.search(keyword, pages=pages, years=years)
            await scraper.close()
            
            print(f"\n✓ 获取 {len(self.current_data.projects)} 条项目")
            
            # 统计有摘要的数量
            with_abstract = sum(1 for p in self.current_data.projects if p.abstract_cn)
            print(f"  其中 {with_abstract} 条有摘要信息")
            
            if self.current_data.projects:
                output_file = self._save_results(keyword)
                print(f"  已保存: {output_file}")
        except Exception as e:
            print(f"❌ 爬取失败: {e}")
    
    async def do_search(self, args: str):
        """搜索项目 - 使用 AI 模式"""
        if not args:
            print("用法: /search <关键词> [-p 页数] [-a 获取摘要]")
            return
        
        if not self.config or not self.config.llm.api_key:
            print("⚠ 未配置 LLM API Key，请先运行 /config")
            return
        
        keyword, pages, include_abstract, _ = self._parse_args(args)
        print(f"🤖 AI搜索: {keyword}, {pages} 页" + ("，含摘要" if include_abstract else ""))
        print("  (AI 会自动操作浏览器，请勿手动干预)")
        
        try:
            agent = NSFCAgent(self.config)
            task = NSFCSearchTask(agent)
            self.current_data = await task.search(keyword, pages=pages, include_abstract=include_abstract)
            
            print(f"✓ 获取 {len(self.current_data.projects)} 条项目")
            
            if self.current_data.projects:
                output_file = self._save_results(keyword)
                print(f"  已保存: {output_file}")
        except Exception as e:
            print(f"❌ 搜索失败: {e}")
    
    async def do_batch(self, args: str):
        """批量搜索 - 默认使用直接爬取，--ai 切换为AI模式"""
        if not args:
            print("用法: /batch <关键词1>,<关键词2>,... [-p 页数|all] [-y 年份]")
            print("      /batch <关键词1>,<关键词2>,... --ai [-p 页数]  (AI模式)")
            print("示例: /batch 轨道,铁路,高铁 -p 2")
            print("示例: /batch 轨道,铁路 -y 2022-2024 -p all")
            return
        
        # 检测 --ai 标志
        use_ai = "--ai" in args
        args = args.replace("--ai", "").strip()
        
        if use_ai and (not self.config or not self.config.llm.api_key):
            print("⚠ AI模式需要配置 LLM API Key，请先运行 /config")
            print("  或去掉 --ai 使用直接爬取模式（推荐）")
            return
        
        # 分离关键词和其他参数：关键词在第一个 -p 或 -y 之前
        # 先找到参数部分的起始位置
        param_start = len(args)
        for flag in [' -p', ' -y']:
            idx = args.find(flag)
            if idx != -1 and idx < param_start:
                param_start = idx
        
        keywords_str = args[:param_start].strip()
        param_str = args[param_start:].strip()
        
        keywords = [k.strip() for k in keywords_str.split(",") if k.strip()]
        if not keywords:
            print("⚠ 请提供至少一个关键词")
            return
        
        # 用一个虚拟关键词拼接参数，复用 _parse_args 解析 -p 和 -y
        dummy_args = f"dummy {param_str}"
        _, pages, _, years = self._parse_args(dummy_args)
        
        pages_text = "全部页面" if pages == 0 else f"{pages} 页"
        mode_text = "🤖 AI" if use_ai else "🔍 直接"
        print(f"{mode_text}批量搜索 {len(keywords)} 个关键词, 每个 {pages_text}")
        if years:
            print(f"   年份: {','.join(str(y) for y in years)}")
        
        all_projects = []
        scraper = None
        
        try:
            if use_ai:
                # AI 模式：使用 NSFCSearchTask + browser-use
                agent = NSFCAgent(self.config)
                task = NSFCSearchTask(agent)
                for kw in keywords:
                    print(f"\n  🤖 AI搜索: {kw}...")
                    result = await task.search(kw, pages=pages)
                    all_projects.extend(result.projects)
                    print(f"    ✓ 获取 {len(result.projects)} 条")
            else:
                # 直接爬取模式：使用 NSFCDirectScraper（推荐）
                scraper = NSFCDirectScraper(headless=False)
                for kw in keywords:
                    print(f"\n  🔍 直接爬取: {kw}...")
                    result = await scraper.search(kw, pages=pages, years=years)
                    all_projects.extend(result.projects)
                    print(f"    ✓ 获取 {len(result.projects)} 条")
            
            # 去重
            seen = set()
            unique = []
            for p in all_projects:
                key = p.approval_no or p.project_name
                if key not in seen:
                    seen.add(key)
                    unique.append(p)
            
            self.current_data = SearchResults(
                keyword=",".join(keywords),
                projects=unique,
                total_count=len(unique)
            )
            print(f"\n✓ 共获取 {len(unique)} 条项目（去重前 {len(all_projects)} 条）")
            
            # 统计有摘要的数量
            with_abstract = sum(1 for p in unique if p.abstract_cn)
            if with_abstract:
                print(f"  其中 {with_abstract} 条有摘要信息")
            
            if unique:
                output_file = self._save_results("batch_" + "_".join(keywords[:3]))
                print(f"  已保存: {output_file}")
        except Exception as e:
            print(f"❌ 批量搜索失败: {e}")
        finally:
            if scraper:
                await scraper.close()
    
    def do_load(self, args: str):
        """加载数据文件（支持多选）"""
        import json
        output_dir = Path(self.config.output_dir) if self.config else Path("./output")
        
        if not output_dir.exists():
            print(f"⚠ 输出目录不存在: {output_dir}")
            return
        
        files = list(output_dir.glob("*.json"))
        if not files:
            print("⚠ 没有可加载的文件")
            return
        
        # 按修改时间排序
        files.sort(key=lambda f: f.stat().st_mtime, reverse=True)
        
        args = args.strip()
        
        # 检查是否是序号格式 (如 "1" 或 "1,2,3")
        if args and all(x.strip().isdigit() or x.strip() == ',' for x in args.replace(',', ' ').split()):
            # 先显示文件列表
            print("\n可用文件 (按时间排序):")
            for i, f in enumerate(files[:20], 1):
                mtime = datetime.fromtimestamp(f.stat().st_mtime)
                size = f.stat().st_size / 1024
                print(f"  {i:2}. {f.name} ({size:.1f}KB, {mtime.strftime('%m-%d %H:%M')})")
            
            # 解析序号
            indices = [int(x.strip()) - 1 for x in args.split(",") if x.strip().isdigit()]
            selected = [files[i] for i in indices if 0 <= i < len(files)]
            
            if selected:
                self._load_selected_files(selected)
            else:
                print("⚠ 无效序号")
            return
        
        # 如果指定了文件名
        if args:
            target = output_dir / args
            if target.exists():
                self._load_file(target)
                return
            # 模糊匹配
            matches = [f for f in files if args in f.name]
            if len(matches) == 1:
                self._load_file(matches[0])
                return
            elif len(matches) > 1:
                print(f"找到 {len(matches)} 个匹配文件，请选择:")
                files = matches
            else:
                print(f"⚠ 未找到文件: {args}")
                return
        
        # 显示文件列表供选择
        print("\n可用文件 (按时间排序):")
        for i, f in enumerate(files[:20], 1):
            mtime = datetime.fromtimestamp(f.stat().st_mtime)
            size = f.stat().st_size / 1024
            print(f"  {i:2}. {f.name} ({size:.1f}KB, {mtime.strftime('%m-%d %H:%M')})")
        
        print("\n输入序号加载 (多选用逗号分隔, 如: 1,2,3):")
        choice = input("> ").strip()
        
        if not choice:
            return
        
        # 解析选择
        indices = [int(x.strip()) - 1 for x in choice.split(",") if x.strip().isdigit()]
        selected = [files[i] for i in indices if 0 <= i < len(files)]
        
        if not selected:
            print("⚠ 无效选择")
            return
        
        self._load_selected_files(selected)
    
    def _load_selected_files(self, selected):
        """加载选中的文件（单个或多个）"""
        if len(selected) == 1:
            # 单文件直接加载
            self._load_file(selected[0])
        else:
            # 多文件合并
            all_projects = []
            keywords = []
            for f in selected:
                data = self._load_file(f, merge=True)
                if data:
                    all_projects.extend(data.projects)
                    keywords.append(data.keyword)
            
            # 去重
            seen = set()
            unique = []
            for p in all_projects:
                key = p.approval_no or p.project_name
                if key not in seen:
                    seen.add(key)
                    unique.append(p)
            
            self.current_data = SearchResults(
                keyword="+".join(keywords),
                projects=unique,
                total_count=len(unique)
            )
            print(f"✓ 合并 {len(selected)} 个文件, 共 {len(unique)} 条项目")
    
    def _load_file(self, filepath: Path, merge: bool = False) -> Optional[SearchResults]:
        """加载单个文件"""
        import json
        try:
            with open(filepath, "r", encoding="utf-8") as f:
                data = json.load(f)
            
            result = SearchResults(**data)
            
            if not merge:
                self.current_data = result
                print(f"✓ 已加载: {filepath.name} ({len(result.projects)} 条)")
            
            return result
        except Exception as e:
            print(f"⚠ 加载失败: {e}")
            return None
    
    def do_export(self, args: str):
        """导出数据为 Excel、CSV 或 JSON"""
        if not self.current_data or not self.current_data.projects:
            print("⚠ 没有数据可导出，请先搜索或加载")
            return
        
        fmt = args.strip().lower() if args.strip() else "excel"
        if fmt not in ["excel", "csv", "json"]:
            print("用法: /export [excel|csv|json]")
            print("  excel - 导出为 Excel 文件")
            print("  csv   - 导出为 CSV 文件")
            print("  json  - 导出为 JSON 文件（可被 /load 加载）")
            return
        
        output_dir = Path(self.config.output_dir) if self.config else Path("./output")
        output_dir.mkdir(parents=True, exist_ok=True)
        
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        
        # 检查是否经过筛选（keyword 中包含 [筛选:...]）
        keyword = self.current_data.keyword
        is_filtered = "[筛选:" in keyword
        
        if is_filtered:
            # 提取原始关键词和筛选条件
            import re
            match = re.match(r'^(.+?)\[筛选:(.+?)\]$', keyword)
            if match:
                original_keyword = match.group(1).strip()
                filter_criteria = match.group(2).strip()
            else:
                original_keyword = keyword
                filter_criteria = ""
            
            safe_keyword = _sanitize_filename(original_keyword, max_length=20)
            safe_filter = _sanitize_filename(filter_criteria, max_length=20)
            base_name = f"nsfc_{safe_keyword}_filtered_{safe_filter}_{timestamp}"
        else:
            safe_keyword = _sanitize_filename(keyword, max_length=30)
            base_name = f"nsfc_{safe_keyword}_{timestamp}"
        
        if fmt == "excel":
            filepath = output_dir / f"{base_name}.xlsx"
            DataExporter.to_excel(self.current_data, str(filepath))
        elif fmt == "csv":
            filepath = output_dir / f"{base_name}.csv"
            DataExporter.to_csv(self.current_data, str(filepath))
        else:  # json
            filepath = output_dir / f"{base_name}.json"
            self._export_json(filepath)
        
        print(f"✓ 已导出: {filepath}")
    
    def _export_json(self, filepath: Path):
        """导出为 JSON 文件（与 scrape 输出格式一致，可被 load 加载）"""
        import json
        assert self.current_data is not None  # 调用前已检查
        data = self.current_data.model_dump()
        with open(filepath, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
    
    def do_analyze(self, args: str):
        """数据统计分析"""
        if not self.current_data or not self.current_data.projects:
            print("⚠ 没有数据可分析，请先搜索或加载")
            return
        
        analysis = DataAnalyzer.analyze(self.current_data)
        DataAnalyzer.print_analysis(analysis)
    
    async def do_filter(self, args: str):
        """LLM 智能筛选项目"""
        if not self.current_data or not self.current_data.projects:
            print("⚠ 没有数据可筛选，请先搜索或加载")
            return
        
        if not args.strip():
            print("用法: /filter <筛选条件>")
            print("示例: /filter 与风险管理相关的项目")
            return
        
        if not self.config or not self.config.llm.api_key:
            print("⚠ 未配置 LLM，请先运行 /config")
            return
        
        criteria = args.strip()
        print(f"筛选条件: {criteria}")
        print(f"待筛选: {len(self.current_data.projects)} 条项目...")
        
        llm_filter = LLMFilter(self.config.llm)
        filtered = await llm_filter.filter_projects(
            self.current_data.projects, 
            criteria
        )
        
        if filtered:
            self.current_data = SearchResults(
                keyword=f"{self.current_data.keyword}[筛选:{criteria[:20]}]",
                projects=filtered,
                total_count=len(filtered)
            )
            print(f"✓ 筛选完成: {len(filtered)} 条相关项目")
        else:
            print("⚠ 未找到符合条件的项目")
    
    async def do_review(self, args: str):
        """生成研究综述"""
        if not self.current_data or not self.current_data.projects:
            print("⚠ 没有数据，请先搜索或加载")
            return
        
        if not args.strip():
            print("用法: /review <研究主题> [| 自定义要求]")
            print("示例: /review 轨道交通风险管理研究综述")
            print("示例: /review 人工智能研究 | 重点分析深度学习")
            return
        
        if not self.config or not self.config.llm.api_key:
            print("⚠ 未配置 LLM，请先运行 /config")
            return
        
        # 解析主题和自定义要求
        parts = [p.strip() for p in args.split("|", 1)]
        topic = parts[0]
        custom_requirements = parts[1] if len(parts) > 1 else None
        
        print(f"主题: {topic}")
        if custom_requirements:
            print(f"自定义要求: {custom_requirements}")
        print(f"基于 {len(self.current_data.projects)} 条项目生成综述...")
        
        output_dir = Path(self.config.output_dir) if self.config else Path("./output")
        output_dir.mkdir(parents=True, exist_ok=True)
        
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        safe_topic = _sanitize_filename(topic, max_length=30)
        
        output_path = output_dir / f"review_{safe_topic}_{timestamp}.docx"
        
        generator = ReviewGenerator(self.config.llm)
        review = await generator.generate_review(
            self.current_data.projects,
            topic,
            str(output_path),
            stream=False,
            custom_requirements=custom_requirements
        )
        
        print(f"\n✓ 综述已保存: {output_path}")
    
    async def do_pipeline(self, args: str):
        """一键流程: 爬取 -> 筛选 -> 综述"""
        if not args.strip():
            print("用法: /pipeline <关键词> [-p 页数] | <筛选条件> | <综述主题> [| 自定义要求]")
            print("示例: /pipeline 轨道交通 -p 5 | 风险管理相关 | 轨道交通风险研究")
            return
        
        parts = [p.strip() for p in args.split("|")]
        if len(parts) < 3:
            print("⚠ 参数不完整，需要: 关键词 | 筛选条件 | 综述主题 [| 自定义要求]")
            return
        
        search_args = parts[0]
        filter_criteria = parts[1]
        review_topic = parts[2]
        custom_requirements = parts[3] if len(parts) > 3 else None
        
        # Step 1: 爬取
        print("\n[1/3] 爬取项目...")
        await self.do_scrape(search_args)
        if not self.current_data or not self.current_data.projects:
            print("⚠ 爬取无结果，流程终止")
            return
        
        # Step 2: 筛选
        print("\n[2/3] LLM 筛选...")
        await self.do_filter(filter_criteria)
        if not self.current_data or not self.current_data.projects:
            print("⚠ 筛选无结果，流程终止")
            return
        
        # Step 3: 综述
        print("\n[3/3] 生成综述...")
        review_args = review_topic
        if custom_requirements:
            review_args = f"{review_args} | {custom_requirements}"
        await self.do_review(review_args)
        
        print("\n✓ Pipeline 完成!")
    
    def do_memory(self, args: str):
        """查看或添加经验记忆"""
        memory_dir = str(self.config.output_dir).replace("output", "memory") if self.config else "./memory"
        memory = get_memory(memory_dir)
        
        args = args.strip()
        
        # 无参数：显示所有经验
        if not args:
            memory.print_all()
            return
        
        # 添加经验
        if args.startswith("add "):
            content = args[4:].strip()
            if not content:
                print("用法: /memory add <经验内容>")
                return
            
            # 询问分类
            print("\n选择经验类型:")
            print("  1. 通用经验")
            print("  2. 网站经验 (kd.nsfc.cn)")
            print("  3. 任务经验")
            choice = input("> ").strip()
            
            if choice == "1":
                memory.add_general_experience(content)
                print("✓ 已添加通用经验")
            elif choice == "2":
                cat = input("分类 (页面结构/搜索操作/数据提取/注意事项): ").strip() or "注意事项"
                memory.add_website_experience("kd.nsfc.cn", content, cat)
                print(f"✓ 已添加网站经验 [{cat}]")
            elif choice == "3":
                task_type = input("任务类型 (搜索/详情提取): ").strip() or "搜索"
                memory.add_task_experience(task_type, content)
                print(f"✓ 已添加任务经验 [{task_type}]")
            else:
                print("⚠ 无效选择")
            return
        
        print("用法:")
        print("  /memory          查看所有经验")
        print("  /memory add <内容>  添加新经验")
    
    def _check_llm_required(self, cmd: str) -> bool:
        """检查命令是否需要 LLM，如果需要但未配置则提示"""
        llm_required_cmds = ["/search", "/filter", "/review", "/pipeline"]
        
        if cmd in llm_required_cmds and not self.llm_available:
            print(f"\n⚠️  命令 {cmd} 需要配置 LLM 才能使用")
            print("\nLLM 用于以下高级功能：")
            print("  • /search - AI 智能搜索")
            print("  • /filter - LLM 智能筛选")
            print("  • /review - 生成研究综述")
            print("  • /pipeline - 一键流程")
            print("\n请运行 /config 配置 LLM，或使用以下基础功能：")
            print("  • /scrape - 直接爬取（推荐，无需 LLM）")
            print("  • /batch  - 批量爬取（推荐，无需 LLM）")
            print("  • /load - 加载数据")
            print("  • /export - 导出数据")
            print("  • /analyze - 数据分析\n")
            return False
        
        return True
    
    def _check_playwright_required(self, cmd: str) -> bool:
        """检查命令是否需要 Playwright，如果需要但未安装则提示"""
        playwright_required_cmds = ["/scrape", "/search", "/batch", "/pipeline"]
        
        if cmd in playwright_required_cmds and not self.playwright_available:
            print(f"\n⚠️  命令 {cmd} 需要安装 Playwright 浏览器")
            print("\n请运行以下命令安装：")
            print("  playwright install chromium")
            print("\n或：")
            print("  python -m playwright install chromium\n")
            return False
        
        return True
    
    async def process_command(self, cmd_line: str):
        """处理命令"""
        cmd_line = cmd_line.strip()
        if not cmd_line:
            return
        
        # 解析命令和参数
        parts = cmd_line.split(maxsplit=1)
        cmd = parts[0].lower()
        args = parts[1] if len(parts) > 1 else ""
        
        # 检查依赖
        if not self._check_playwright_required(cmd):
            return
        
        if not self._check_llm_required(cmd):
            return
        
        # 命令分发
        if cmd == "/help":
            self.print_help()
        elif cmd == "/status":
            self.print_status()
        elif cmd == "/config":
            self.configure()
        elif cmd == "/exit":
            self.running = False
            print("再见!")
            # 强制终止进程，避免 asyncio 和浏览器资源卡住
            import os
            os._exit(0)
        elif cmd == "/scrape":
            await self.do_scrape(args)
        elif cmd == "/search":
            await self.do_search(args)
        elif cmd == "/batch":
            await self.do_batch(args)
        elif cmd == "/load":
            self.do_load(args)
        elif cmd == "/export":
            self.do_export(args)
        elif cmd == "/analyze":
            self.do_analyze(args)
        elif cmd == "/filter":
            await self.do_filter(args)
        elif cmd == "/review":
            await self.do_review(args)
        elif cmd == "/pipeline":
            await self.do_pipeline(args)
        elif cmd == "/memory":
            self.do_memory(args)
        else:
            print(f"未知命令: {cmd}")
            print("输入 /help 查看可用命令")
    
    async def run_loop(self):
        """主循环"""
        # 设置 prompt_toolkit 样式
        style = Style.from_dict({
            'prompt': '#00aa00 bold',
        })
        
        # 创建历史文件目录
        history_dir = Path.home() / ".nsfc_spider"
        history_dir.mkdir(exist_ok=True)
        history_file = history_dir / "history"
        
        # 创建 PromptSession
        output_dir = str(self.config.output_dir) if self.config else "./output"
        session = PromptSession(
            history=FileHistory(str(history_file)),
            completer=NSFCCompleter(output_dir=output_dir),
            style=style,
        )
        
        print("\n" + "=" * 50)
        print("  国自然项目爬虫 CLI")
        print("  输入 /help 查看命令, /exit 退出")
        print("=" * 50 + "\n")
        
        while self.running:
            try:
                cmd_line = await session.prompt_async("nsfc> ")
                await self.process_command(cmd_line)
            except KeyboardInterrupt:
                print("\n使用 /exit 退出")
            except EOFError:
                break
            except Exception as e:
                print(f"错误: {e}")


def main():
    """CLI 入口"""
    cli = NSFCCLI()
    asyncio.run(cli.run_loop())


if __name__ == "__main__":
    main()

