#!/usr/bin/env python3
"""
个人项目与数字资产管理器
=========================
使用SQLite本地数据库管理项目进度、技术决策、踩坑记录、知识卡和训练语料索引。
纯标准库实现，命令行交互，单文件可运行。

数据库文件: digital_assets.db（与脚本同目录）
"""

import argparse
import json
import os
import sqlite3
import textwrap
from datetime import datetime

# ── 全局配置 ──────────────────────────────────────────────
DB_FILENAME = "digital_assets.db"
DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), DB_FILENAME)


# ═══════════════════════════════════════════════════════════
#  数据库核心
# ═══════════════════════════════════════════════════════════


def get_conn():
    """获取数据库连接。
    
    启用外键约束(FOREIGN KEYS)确保级联删除正确工作，
    启用WAL模式提升并发读写性能。row_factory=sqlite3.Row
    让查询结果支持 dict-style 列名访问(row["col"])。
    """
    conn = sqlite3.connect(DB_PATH)
    conn.execute("PRAGMA foreign_keys = ON")
    conn.execute("PRAGMA journal_mode = WAL")
    conn.row_factory = sqlite3.Row  # 让查询结果支持按列名访问
    return conn


def init_db():
    """初始化数据库建表（幂等操作，已存在的表不重建）。
    
    创建6张核心表:
    - projects: 项目信息
    - decisions: 技术决策
    - pitfalls: 踩坑记录
    - knowledge_cards: 知识卡(项目文件/工作日志)
    - training_corpus: 训练语料索引
    - import_history: 导入历史记录
    
    同时创建7个索引加速常用查询(project_id/date/status/category/type)。
    """
    conn = get_conn()
    cursor = conn.cursor()

    # ── 项目表 ──
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS projects (
            id              INTEGER PRIMARY KEY AUTOINCREMENT,
            name            TEXT    NOT NULL UNIQUE,
            status          TEXT    NOT NULL DEFAULT 'active'
                                    CHECK(status IN ('active','paused','archived')),
            description     TEXT    DEFAULT '',
            key_milestone   TEXT    DEFAULT '',
            related_files   TEXT    DEFAULT '[]',   -- JSON数组
            created_at      TEXT    NOT NULL,
            updated_at      TEXT    NOT NULL
        );
    """)

    # ── 决策表 ──
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS decisions (
            id              INTEGER PRIMARY KEY AUTOINCREMENT,
            project_id      INTEGER NOT NULL,
            date            TEXT    NOT NULL,
            title           TEXT    NOT NULL,
            content         TEXT    DEFAULT '',
            basis           TEXT    DEFAULT '',
            conclusion      TEXT    DEFAULT '',
            tags            TEXT    DEFAULT '',
            created_at      TEXT    NOT NULL,
            updated_at      TEXT    NOT NULL,
            FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
        );
    """)

    # ── 踩坑记录表 ──
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS pitfalls (
            id              INTEGER PRIMARY KEY AUTOINCREMENT,
            project_id      INTEGER NOT NULL,
            date            TEXT    NOT NULL,
            problem         TEXT    NOT NULL,
            root_cause      TEXT    DEFAULT '',
            solution        TEXT    DEFAULT '',
            tags            TEXT    DEFAULT '',
            created_at      TEXT    NOT NULL,
            updated_at      TEXT    NOT NULL,
            FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
        );
    """)

    # ── 知识卡表 ──
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS knowledge_cards (
            id              INTEGER PRIMARY KEY AUTOINCREMENT,
            topic           TEXT    NOT NULL,
            category        TEXT    DEFAULT '',
            file_path       TEXT    DEFAULT '',
            summary         TEXT    DEFAULT '',
            version         TEXT    DEFAULT '1.0',
            created_at      TEXT    NOT NULL,
            updated_at      TEXT    NOT NULL
        );
    """)

    # ── 训练语料索引表 ──
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS training_corpus (
            id              INTEGER PRIMARY KEY AUTOINCREMENT,
            date            TEXT    NOT NULL,
            type            TEXT    NOT NULL
                                    CHECK(type IN ('qa','decision','pitfall')),
            tags            TEXT    DEFAULT '',
            file_path       TEXT    DEFAULT '',
            record_count    INTEGER DEFAULT 0,
            created_at      TEXT    NOT NULL,
            updated_at      TEXT    NOT NULL
        );
    """)

    # ── 导入历史表 ──
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS import_history (
            id              INTEGER PRIMARY KEY AUTOINCREMENT,
            import_date     TEXT    NOT NULL,
            import_type     TEXT    NOT NULL,
            folder_path     TEXT    DEFAULT '',
            project_name    TEXT    DEFAULT '',
            file_count      INTEGER DEFAULT 0,
            summary         TEXT    DEFAULT '',
            created_at      TEXT    NOT NULL
        );
    """)

    # ── 索引：加速按外键、标签、日期、状态查询 ──
    cursor.execute(
        "CREATE INDEX IF NOT EXISTS idx_decisions_project   ON decisions(project_id);"
    )
    cursor.execute(
        "CREATE INDEX IF NOT EXISTS idx_pitfalls_project    ON pitfalls(project_id);"
    )
    cursor.execute(
        "CREATE INDEX IF NOT EXISTS idx_decisions_date      ON decisions(date);"
    )
    cursor.execute(
        "CREATE INDEX IF NOT EXISTS idx_pitfalls_date       ON pitfalls(date);"
    )
    cursor.execute(
        "CREATE INDEX IF NOT EXISTS idx_projects_status     ON projects(status);"
    )
    cursor.execute(
        "CREATE INDEX IF NOT EXISTS idx_knowledge_category  ON knowledge_cards(category);"
    )
    cursor.execute(
        "CREATE INDEX IF NOT EXISTS idx_corpus_type         ON training_corpus(type);"
    )

    conn.commit()
    conn.close()
    print(f"[OK] 数据库初始化完成: {DB_PATH}")


# ═══════════════════════════════════════════════════════════
#  工具函数
# ═══════════════════════════════════════════════════════════


def now():
    """返回当前时间字符串，ISO格式精确到秒。
    
    格式: "YYYY-MM-DD HH:MM:SS"
    所有数据库记录的 created_at/updated_at 统一使用此函数。
    """
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def print_rows(rows, columns=None):
    """以表格形式打印查询结果。
    
    Args:
        rows: sqlite3.Row 列表，每个 row 支持 dict-style 访问
        columns: 要显示的列名列表，默认取第一行的 keys()
    
    输出格式: 表头 | 分隔线 | N行数据 | 总条数
    无结果时打印"(无结果)"。
    """
    if not rows:
        print("（无结果）")
        return
    if columns is None:
        columns = rows[0].keys()
    # 表头
    header = " | ".join(columns)
    print(header)
    print("-" * len(header))
    for row in rows:
        vals = []
        for c in columns:
            v = row.get(c, "")
            vals.append(str(v) if v is not None else "")
        print(" | ".join(vals))
    print(f"（共 {len(rows)} 条）")


def resolve_project_id(conn, project_ref):
    """将名称或ID解析为 project_id。
    
    Args:
        conn: 数据库连接
        project_ref: 纯数字字符串→按ID查，否则→按项目名查
    
    Returns:
        int: project_id, 找不到返回 None
    """
    cursor = conn.cursor()
    if project_ref.isdigit():
        cursor.execute("SELECT id FROM projects WHERE id = ?", (int(project_ref),))
    else:
        cursor.execute("SELECT id FROM projects WHERE name = ?", (project_ref,))
    row = cursor.fetchone()
    return row["id"] if row else None


def timestamp_fields():
    """返回 (created_at, updated_at) 的当前时间元组。
    
    用于 INSERT 语句一次性获取两个时间戳字段的值。
    """
    t = now()
    return (t, t)


def validate_date(date_str):
    """校验日期是否严格符合 YYYY-MM-DD 格式。
    
    Args:
        date_str: 待校验字符串
    Returns:
        bool: 合法且可解析则 True
    
    用于所有 --date 参数的输入验证，防止非法日期写入数据库。
    """
    if date_str is None:
        return False
    try:
        datetime.strptime(date_str, "%Y-%m-%d")
        return True
    except ValueError:
        return False


def check_db_initialized():
    """检测数据库是否已初始化。
    
    通过查询 sqlite_master 确认 projects 表存在。
    未初始化时打印提示信息指导用户运行 init 命令。
    
    Returns:
        bool: 已初始化返回 True
    """
    try:
        conn = sqlite3.connect(DB_PATH)
        cursor = conn.cursor()
        cursor.execute(
            "SELECT name FROM sqlite_master WHERE type='table' AND name='projects'"
        )
        exists = cursor.fetchone() is not None
        conn.close()
        if not exists:
            print(
                "[ERR] 数据库未初始化，请先运行: python digital_assets_manager.py init"
            )
        return exists
    except Exception:
        print("[ERR] 数据库未初始化，请先运行: python digital_assets_manager.py init")
        return False


# ═══════════════════════════════════════════════════════════
#  命令处理
# ═══════════════════════════════════════════════════════════

# ────────── 项目 (projects) ──────────


def cmd_project_add(args):
    """添加新项目"""
    conn = get_conn()
    t = now()
    try:
        conn.execute(
            """INSERT INTO projects (name, status, description, key_milestone, related_files, created_at, updated_at)
               VALUES (?, ?, ?, ?, ?, ?, ?)""",
            (
                args.name,
                args.status or "active",
                args.desc or "",
                args.milestone or "",
                json.dumps(args.files or [], ensure_ascii=False),
                t,
                t,
            ),
        )
        conn.commit()
        print(f"[OK] 项目 '{args.name}' 已添加")
    except sqlite3.IntegrityError as e:
        print(f"[ERR] 添加失败: {e}")
    finally:
        conn.close()


def cmd_project_list(args):
    """列出项目，支持按状态过滤"""
    conn = get_conn()
    if args.status:
        rows = conn.execute(
            "SELECT id, name, status, key_milestone, updated_at FROM projects WHERE status = ? ORDER BY updated_at DESC",
            (args.status,),
        ).fetchall()
    else:
        rows = conn.execute(
            "SELECT id, name, status, key_milestone, updated_at FROM projects ORDER BY updated_at DESC"
        ).fetchall()
    print_rows(rows)
    conn.close()


def cmd_project_update(args):
    """更新项目字段"""
    conn = get_conn()
    pid = resolve_project_id(conn, args.project)
    if pid is None:
        print(f"[ERR] 未找到项目: {args.project}")
        conn.close()
        return

    # 构建动态UPDATE
    updates = {}
    if args.status:
        if args.status not in ("active", "paused", "archived"):
            print("[ERR] status 必须是 active / paused / archived")
            conn.close()
            return
        updates["status"] = args.status
    if args.desc is not None:
        updates["description"] = args.desc
    if args.milestone is not None:
        updates["key_milestone"] = args.milestone
    if args.files is not None:
        updates["related_files"] = json.dumps(args.files, ensure_ascii=False)

    if not updates:
        print("[WARN]️ 未指定要更新的字段")
        conn.close()
        return

    updates["updated_at"] = now()
    set_clause = ", ".join(f"{k} = ?" for k in updates)
    values = list(updates.values()) + [pid]
    conn.execute(f"UPDATE projects SET {set_clause} WHERE id = ?", values)
    conn.commit()
    print(f"[OK] 项目 '{args.project}' 已更新")
    conn.close()


def cmd_project_delete(args):
    """删除项目（级联删除关联的决策和踩坑记录）"""
    conn = get_conn()
    pid = resolve_project_id(conn, args.project)
    if pid is None:
        print(f"[ERR] 未找到项目: {args.project}")
        conn.close()
        return

    if not args.force:
        confirm = (
            input(
                f"[WARN] 确定删除项目 '{args.project}' 及其关联的所有决策/踩坑记录？(y/N): "
            )
            .strip()
            .lower()
        )
        if confirm != "y":
            print("已取消")
            conn.close()
            return

    conn.execute("DELETE FROM projects WHERE id = ?", (pid,))
    conn.commit()
    print(f"[OK] 项目 '{args.project}' 已删除")
    conn.close()


# ────────── 决策 (decisions) ──────────


def cmd_decision_add(args):
    """添加决策记录"""
    if not validate_date(args.date):
        print("[ERR] 日期格式错误，需要 YYYY-MM-DD")
        return
    conn = get_conn()
    pid = resolve_project_id(conn, args.project)
    if pid is None:
        print(f"[ERR] 未找到项目: {args.project}")
        conn.close()
        return

    t = now()
    conn.execute(
        """INSERT INTO decisions (project_id, date, title, content, basis, conclusion, tags, created_at, updated_at)
           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
        (
            pid,
            args.date,
            args.title,
            args.content or "",
            args.basis or "",
            args.conclusion or "",
            args.tags or "",
            t,
            t,
        ),
    )
    conn.commit()
    print(f"[OK] 决策 '{args.title}' 已添加到项目 '{args.project}'")
    conn.close()


def cmd_decision_list(args):
    """列出决策，支持按项目过滤"""
    conn = get_conn()
    if args.project:
        pid = resolve_project_id(conn, args.project)
        if pid is None:
            print(f"[ERR] 未找到项目: {args.project}")
            conn.close()
            return
        rows = conn.execute(
            """SELECT d.id, p.name AS project, d.date, d.title, d.tags, d.updated_at
               FROM decisions d JOIN projects p ON d.project_id = p.id
               WHERE d.project_id = ? ORDER BY d.date DESC""",
            (pid,),
        ).fetchall()
    else:
        rows = conn.execute(
            """SELECT d.id, p.name AS project, d.date, d.title, d.tags, d.updated_at
               FROM decisions d JOIN projects p ON d.project_id = p.id
               ORDER BY d.date DESC"""
        ).fetchall()
    print_rows(rows)
    conn.close()


def cmd_decision_update(args):
    """更新决策 — 通过ID"""
    conn = get_conn()
    row = conn.execute("SELECT id FROM decisions WHERE id = ?", (args.id,)).fetchone()
    if not row:
        print(f"[ERR] 未找到决策 ID={args.id}")
        conn.close()
        return

    updates = {}
    if args.title is not None:
        updates["title"] = args.title
    if args.content is not None:
        updates["content"] = args.content
    if args.basis is not None:
        updates["basis"] = args.basis
    if args.conclusion is not None:
        updates["conclusion"] = args.conclusion
    if args.tags is not None:
        updates["tags"] = args.tags
    if args.date is not None:
        if not validate_date(args.date):
            print("[ERR] 日期格式错误，需要 YYYY-MM-DD")
            conn.close()
            return
        updates["date"] = args.date

    if not updates:
        print("[WARN] 未指定要更新的字段")
        conn.close()
        return

    updates["updated_at"] = now()
    set_clause = ", ".join(f"{k} = ?" for k in updates)
    values = list(updates.values()) + [args.id]
    conn.execute(f"UPDATE decisions SET {set_clause} WHERE id = ?", values)
    conn.commit()
    print(f"[OK] 决策 ID={args.id} 已更新")
    conn.close()


def cmd_decision_delete(args):
    """删除决策"""
    conn = get_conn()
    conn.execute("DELETE FROM decisions WHERE id = ?", (args.id,))
    conn.commit()
    print(f"[OK] 决策 ID={args.id} 已删除（如存在）")
    conn.close()


def cmd_decision_show(args):
    """查看单条决策完整内容"""
    conn = get_conn()
    row = conn.execute(
        """SELECT d.*, p.name AS project_name
           FROM decisions d JOIN projects p ON d.project_id = p.id
           WHERE d.id = ?""",
        (args.id,),
    ).fetchone()
    if not row:
        print(f"[ERR] 未找到决策 ID={args.id}")
    else:
        print(f"ID:       {row['id']}")
        print(f"项目:     {row['project_name']}")
        print(f"日期:     {row['date']}")
        print(f"标题:     {row['title']}")
        print(f"内容:     {row['content']}")
        print(f"依据:     {row['basis']}")
        print(f"结论:     {row['conclusion']}")
        print(f"标签:     {row['tags']}")
        print(f"更新时间: {row['updated_at']}")
    conn.close()


# ────────── 踩坑记录 (pitfalls) ──────────


def cmd_pitfall_add(args):
    """添加踩坑记录"""
    if not validate_date(args.date):
        print("[ERR] 日期格式错误，需要 YYYY-MM-DD")
        return
    conn = get_conn()
    pid = resolve_project_id(conn, args.project)
    if pid is None:
        print(f"[ERR] 未找到项目: {args.project}")
        conn.close()
        return

    t = now()
    conn.execute(
        """INSERT INTO pitfalls (project_id, date, problem, root_cause, solution, tags, created_at, updated_at)
           VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
        (
            pid,
            args.date,
            args.problem,
            args.root_cause or "",
            args.solution or "",
            args.tags or "",
            t,
            t,
        ),
    )
    conn.commit()
    print(f"[OK] 踩坑记录已添加到项目 '{args.project}'")
    conn.close()


def cmd_pitfall_list(args):
    """列出踩坑记录"""
    conn = get_conn()
    if args.project:
        pid = resolve_project_id(conn, args.project)
        if pid is None:
            print(f"[ERR] 未找到项目: {args.project}")
            conn.close()
            return
        rows = conn.execute(
            """SELECT pf.id, p.name AS project, pf.date, pf.problem, pf.tags, pf.updated_at
               FROM pitfalls pf JOIN projects p ON pf.project_id = p.id
               WHERE pf.project_id = ? ORDER BY pf.date DESC""",
            (pid,),
        ).fetchall()
    else:
        rows = conn.execute(
            """SELECT pf.id, p.name AS project, pf.date, pf.problem, pf.tags, pf.updated_at
               FROM pitfalls pf JOIN projects p ON pf.project_id = p.id
               ORDER BY pf.date DESC"""
        ).fetchall()
    print_rows(rows)
    conn.close()


def cmd_pitfall_update(args):
    """更新踩坑记录"""
    conn = get_conn()
    row = conn.execute("SELECT id FROM pitfalls WHERE id = ?", (args.id,)).fetchone()
    if not row:
        print(f"[ERR] 未找到踩坑记录 ID={args.id}")
        conn.close()
        return

    updates = {}
    if args.problem is not None:
        updates["problem"] = args.problem
    if args.root_cause is not None:
        updates["root_cause"] = args.root_cause
    if args.solution is not None:
        updates["solution"] = args.solution
    if args.tags is not None:
        updates["tags"] = args.tags
    if args.date is not None:
        if not validate_date(args.date):
            print("[ERR] 日期格式错误，需要 YYYY-MM-DD")
            conn.close()
            return
        updates["date"] = args.date

    if not updates:
        print("[WARN] 未指定要更新的字段")
        conn.close()
        return

    updates["updated_at"] = now()
    set_clause = ", ".join(f"{k} = ?" for k in updates)
    values = list(updates.values()) + [args.id]
    conn.execute(f"UPDATE pitfalls SET {set_clause} WHERE id = ?", values)
    conn.commit()
    print(f"[OK] 踩坑记录 ID={args.id} 已更新")
    conn.close()


def cmd_pitfall_delete(args):
    """删除踩坑记录"""
    conn = get_conn()
    conn.execute("DELETE FROM pitfalls WHERE id = ?", (args.id,))
    conn.commit()
    print(f"[OK] 踩坑记录 ID={args.id} 已删除（如存在）")
    conn.close()


def cmd_pitfall_show(args):
    """查看单条踩坑记录完整内容"""
    conn = get_conn()
    row = conn.execute(
        """SELECT pf.*, p.name AS project_name
           FROM pitfalls pf JOIN projects p ON pf.project_id = p.id
           WHERE pf.id = ?""",
        (args.id,),
    ).fetchone()
    if not row:
        print(f"[ERR] 未找到踩坑记录 ID={args.id}")
    else:
        print(f"ID:       {row['id']}")
        print(f"项目:     {row['project_name']}")
        print(f"日期:     {row['date']}")
        print(f"问题:     {row['problem']}")
        print(f"根因:     {row['root_cause']}")
        print(f"解决方案: {row['solution']}")
        print(f"标签:     {row['tags']}")
        print(f"更新时间: {row['updated_at']}")
    conn.close()


# ────────── 知识卡 (knowledge_cards) ──────────


def cmd_knowledge_add(args):
    """添加知识卡"""
    conn = get_conn()
    t = now()
    conn.execute(
        """INSERT INTO knowledge_cards (topic, category, file_path, summary, version, created_at, updated_at)
           VALUES (?, ?, ?, ?, ?, ?, ?)""",
        (
            args.topic,
            args.category or "",
            args.file or "",
            args.summary or "",
            args.version or "1.0",
            t,
            t,
        ),
    )
    conn.commit()
    print(f"[OK] 知识卡 '{args.topic}' 已添加")
    conn.close()


def cmd_knowledge_list(args):
    """列出知识卡，支持按分类过滤"""
    conn = get_conn()
    if args.category:
        rows = conn.execute(
            """SELECT id, topic, category, version, updated_at
               FROM knowledge_cards WHERE category = ? ORDER BY updated_at DESC""",
            (args.category,),
        ).fetchall()
    else:
        rows = conn.execute(
            "SELECT id, topic, category, version, updated_at FROM knowledge_cards ORDER BY updated_at DESC"
        ).fetchall()
    print_rows(rows)
    conn.close()


def cmd_knowledge_update(args):
    """更新知识卡"""
    conn = get_conn()
    row = conn.execute(
        "SELECT id FROM knowledge_cards WHERE id = ?", (args.id,)
    ).fetchone()
    if not row:
        print(f"[ERR] 未找到知识卡 ID={args.id}")
        conn.close()
        return

    updates = {}
    if args.topic is not None:
        updates["topic"] = args.topic
    if args.category is not None:
        updates["category"] = args.category
    if args.file is not None:
        updates["file_path"] = args.file
    if args.summary is not None:
        updates["summary"] = args.summary
    if args.version is not None:
        updates["version"] = args.version

    if not updates:
        print("[WARN]️ 未指定要更新的字段")
        conn.close()
        return

    updates["updated_at"] = now()
    set_clause = ", ".join(f"{k} = ?" for k in updates)
    values = list(updates.values()) + [args.id]
    conn.execute(f"UPDATE knowledge_cards SET {set_clause} WHERE id = ?", values)
    conn.commit()
    print(f"[OK] 知识卡 ID={args.id} 已更新")
    conn.close()


def cmd_knowledge_delete(args):
    """删除知识卡"""
    conn = get_conn()
    conn.execute("DELETE FROM knowledge_cards WHERE id = ?", (args.id,))
    conn.commit()
    print(f"[OK] 知识卡 ID={args.id} 已删除（如存在）")
    conn.close()


def cmd_knowledge_show(args):
    """查看单条知识卡完整内容"""
    conn = get_conn()
    row = conn.execute(
        "SELECT * FROM knowledge_cards WHERE id = ?", (args.id,)
    ).fetchone()
    if not row:
        print(f"[ERR] 未找到知识卡 ID={args.id}")
    else:
        print(f"ID:       {row['id']}")
        print(f"主题:     {row['topic']}")
        print(f"分类:     {row['category']}")
        print(f"文件路径: {row['file_path']}")
        print(f"摘要:     {row['summary']}")
        print(f"版本:     {row['version']}")
        print(f"更新时间: {row['updated_at']}")
    conn.close()


# ────────── 训练语料 (training_corpus) ──────────


def cmd_corpus_add(args):
    """添加训练语料索引记录"""
    if not validate_date(args.date):
        print("[ERR] 日期格式错误，需要 YYYY-MM-DD")
        return
    conn = get_conn()
    if args.type not in ("qa", "decision", "pitfall"):
        print("[ERR] type 必须是 qa / decision / pitfall")
        conn.close()
        return

    t = now()
    conn.execute(
        """INSERT INTO training_corpus (date, type, tags, file_path, record_count, created_at, updated_at)
           VALUES (?, ?, ?, ?, ?, ?, ?)""",
        (args.date, args.type, args.tags or "", args.file or "", args.count or 0, t, t),
    )
    conn.commit()
    print("[OK] 训练语料记录已添加")
    conn.close()


def cmd_corpus_list(args):
    """列出训练语料"""
    conn = get_conn()
    query = """SELECT id, date, type, tags, file_path, record_count, updated_at
               FROM training_corpus WHERE 1=1"""
    params = []
    if args.type:
        query += " AND type = ?"
        params.append(args.type)
    query += " ORDER BY date DESC"
    rows = conn.execute(query, params).fetchall()
    print_rows(rows)
    conn.close()


def cmd_corpus_update(args):
    """更新训练语料记录"""
    conn = get_conn()
    row = conn.execute(
        "SELECT id FROM training_corpus WHERE id = ?", (args.id,)
    ).fetchone()
    if not row:
        print(f"[ERR] 未找到语料记录 ID={args.id}")
        conn.close()
        return

    updates = {}
    if args.type is not None:
        if args.type not in ("qa", "decision", "pitfall"):
            print("[ERR] type 必须是 qa / decision / pitfall")
            conn.close()
            return
        updates["type"] = args.type
    if args.tags is not None:
        updates["tags"] = args.tags
    if args.file is not None:
        updates["file_path"] = args.file
    if args.count is not None:
        updates["record_count"] = args.count
    if args.date is not None:
        if not validate_date(args.date):
            print("[ERR] 日期格式错误，需要 YYYY-MM-DD")
            conn.close()
            return
        updates["date"] = args.date

    if not updates:
        print("[WARN] 未指定要更新的字段")
        conn.close()
        return

    updates["updated_at"] = now()
    set_clause = ", ".join(f"{k} = ?" for k in updates)
    values = list(updates.values()) + [args.id]
    conn.execute(f"UPDATE training_corpus SET {set_clause} WHERE id = ?", values)
    conn.commit()
    print(f"[OK] 语料记录 ID={args.id} 已更新")
    conn.close()


def cmd_corpus_delete(args):
    """删除训练语料记录"""
    conn = get_conn()
    conn.execute("DELETE FROM training_corpus WHERE id = ?", (args.id,))
    conn.commit()
    print(f"[OK] 语料记录 ID={args.id} 已删除（如存在）")
    conn.close()


def cmd_corpus_show(args):
    """查看单条训练语料索引完整内容"""
    conn = get_conn()
    row = conn.execute(
        "SELECT * FROM training_corpus WHERE id = ?", (args.id,)
    ).fetchone()
    if not row:
        print(f"[ERR] 未找到语料记录 ID={args.id}")
    else:
        print(f"ID:         {row['id']}")
        print(f"日期:       {row['date']}")
        print(f"类型:       {row['type']}")
        print(f"标签:       {row['tags']}")
        print(f"文件路径:   {row['file_path']}")
        print(f"记录数:     {row['record_count']}")
        print(f"创建时间:   {row['created_at']}")
        print(f"更新时间:   {row['updated_at']}")
    conn.close()


def cmd_import(args):
    """批量导入文件夹，自动按子目录创建项目并分类文件。
    
    核心流程:
    1. 扫描目录(可选递归) → 子文件夹名→项目名
    2. 文件类型判断: .jsonl(含Q&A)→语料库, 含日志关键词→工作日志, 其余→项目资产
    3. 二进制文件只存元数据(路径+类型标签)，文本文件读全文(超10000字截断)
    4. 自动创建不存在的项目，状态设为 active
    5. 写入 import_history 记录
    
    Args:
        args.dir: 要导入的目录路径
        args.recursive: 是否递归子目录(每个子文件夹=一个项目)
        args.project: 手动指定项目名(覆盖自动检测)
    """
    import_dir = os.path.abspath(args.dir)
    if not os.path.isdir(import_dir):
        print(f"[ERR] 目录不存在: {import_dir}")
        return
    recursive = getattr(args, "recursive", False)
    project_name = args.project if args.project else None

    conn = get_conn()
    cursor = conn.cursor()

    # 收集目录（os.walk 真递归，单次遍历整棵树，按所属顶级目录分配项目名）
    top_dirs = {}
    if recursive:
        for entry in os.listdir(import_dir):
            epath = os.path.join(import_dir, entry)
            if os.path.isdir(epath):
                top_dirs[os.path.abspath(epath)] = project_name or entry
        scan_groups = [("未归类", import_dir)]
    else:
        scan_groups = [(project_name or "未归类", import_dir)]

    # 文件类型
    text_ext = (
        ".md",
        ".txt",
        ".jsonl",
        ".py",
        ".c",
        ".h",
        ".cpp",
        ".hpp",
        ".json",
        ".csv",
        ".log",
        ".yaml",
        ".yml",
        ".ini",
        ".cfg",
        ".toml",
    )
    binary_ext = (
        ".pdf",
        ".docx",
        ".xlsx",
        ".pptx",
        ".png",
        ".jpg",
        ".jpeg",
        ".gif",
        ".bmp",
        ".svg",
        ".bin",
        ".hex",
        ".elf",
        ".uf2",
        ".sch",
        ".brd",
        ".kicad_pcb",
        ".kicad_sch",
        ".zip",
        ".rar",
        ".7z",
        ".gz",
    )
    supported_ext = text_ext + binary_ext

    all_files = []
    for pname, dpath in scan_groups:
        if not os.path.isdir(dpath):
            continue
        for root, dirs, files in os.walk(dpath):
            dirs.sort()
            if not recursive:
                dirs.clear()
            abs_root = os.path.abspath(root)
            owner = None
            if recursive and top_dirs:
                for td, td_name in top_dirs.items():
                    if abs_root == td or abs_root.startswith(td + os.sep):
                        owner = td_name
                        break
            fn_pname = owner or pname
            for fn in sorted(files):
                fp = os.path.join(root, fn)
                fn_low = fn.lower()
                if fn_low.endswith(supported_ext) and os.path.isfile(fp):
                    is_bin = fn_low.endswith(binary_ext)
                    is_jsonl = fn_low.endswith(".jsonl")
                    all_files.append((fn_pname, root, fn, fp, is_bin, is_jsonl))

    if not all_files:
        print("[WARN] 目录下未找到可导入的文件")
        conn.close()
        return

    # 确保项目存在
    project_cache = {}
    for pname, _, _, _, _, _ in all_files:
        if pname in project_cache:
            continue
        cursor.execute("SELECT id FROM projects WHERE name=?", (pname,))
        row = cursor.fetchone()
        if row:
            project_cache[pname] = row["id"]
        else:
            t = now()
            cursor.execute(
                "INSERT INTO projects (name,status,description,created_at,updated_at) VALUES(?,'active','封存导入',?,?)",
                (pname, t, t),
            )
            conn.commit()
            project_cache[pname] = cursor.lastrowid
            print(f"[OK] 创建项目: {pname}")

    # 逐文件导入
    stats = {"imported": 0, "skipped": 0, "projects": 0, "logs": 0, "corpus": 0}
    total = len(all_files)
    processed = set()

    for idx, (pname, dpath, filename, filepath, is_binary, is_jsonl) in enumerate(
        all_files, 1
    ):
        if dpath not in processed:
            print(f"\n  扫描 [{pname}] {dpath}")
            processed.add(dpath)
        project_cache[pname]
        title = os.path.splitext(filename)[0]

        # JSONL -> 训练语料（一个文件一条记录，带去重）
        if is_jsonl:
            existing = conn.execute("SELECT id FROM training_corpus WHERE file_path=?", (filepath,)).fetchone()
            if existing:
                stats["skipped"] += 1
                print(f"  [{idx}/{total}] [SKIP] {filename}")
                continue
            with open(filepath, "r", encoding="utf-8", errors="replace") as fh:
                raw = fh.read()
            qa_n = dec_n = pf_n = other_n = 0
            tags_set = set()
            dates = []
            for line in raw.strip().split("\n"):
                line = line.strip()
                if not line:
                    continue
                try:
                    obj = json.loads(line)
                except json.JSONDecodeError:
                    continue
                if "question" in obj and "answer" in obj:
                    qa_n += 1
                elif "decision" in obj or "conclusion" in obj:
                    dec_n += 1
                elif "problem" in obj or "solution" in obj:
                    pf_n += 1
                else:
                    other_n += 1
                _tags = obj.get("tags")
                if _tags:
                    if isinstance(_tags, list):
                        tags_set.update(str(t) for t in _tags)
                    elif isinstance(_tags, str):
                        tags_set.update(
                            t.strip() for t in _tags.split(",") if t.strip()
                        )
                    else:
                        tags_set.add(str(_tags))
                if obj.get("date"):
                    dates.append(obj["date"])
            total_valid = qa_n + dec_n + pf_n + other_n
            if total_valid == 0:
                stats["skipped"] += 1
                print(f"  [{idx}/{total}] [SKIP] {filename} (无有效行)")
                continue
            tags = ",".join(sorted(tags_set)[:5]) if tags_set else ""
            date = dates[0] if dates else now()[:10]
            # 按多数类型
            if qa_n >= dec_n and qa_n >= pf_n and qa_n > 0:
                best_type = "qa"
            elif dec_n >= pf_n and dec_n > 0:
                best_type = "decision"
            elif pf_n > 0:
                best_type = "pitfall"
            else:
                best_type = "qa"
            t = now()
            conn.execute(
                "INSERT INTO training_corpus (date,type,tags,file_path,record_count,created_at,updated_at) VALUES(?,?,?,?,?,?,?)",
                (date, best_type, tags, filepath, total_valid, t, t),
            )
            stats["imported"] += 1
            stats["corpus"] += 1
            parts = [f"QA {qa_n}"] if qa_n else []
            if dec_n:
                parts.append(f"决策 {dec_n}")
            if pf_n:
                parts.append(f"踩坑 {pf_n}")
            if other_n:
                parts.append(f"其他 {other_n}")
            print(
                f"  [{idx}/{total}] [OK] {filename} -> 语料库 ({total_valid}条, {', '.join(parts)})"
            )
            continue

        # 工作日志判定（按分词匹配，避免 logo/catalog 含 log 子串被误判）
        stem = os.path.splitext(filename)[0].lower()
        _toks = stem.replace("-", " ").replace("_", " ").replace(".", " ").split()
        is_worklog = any(kw in stem for kw in ["日志", "工作记录", "周报", "日报"]) or any(
            t in ("log", "diary", "journal") for t in _toks
        )
        category = "工作日志" if is_worklog else pname

        # 二进制文件
        if is_binary:
            existing = conn.execute(
                "SELECT id FROM knowledge_cards WHERE topic=? AND file_path=?",
                (title, filepath),
            ).fetchone()
            if existing:
                stats["skipped"] += 1
                print(f"  [{idx}/{total}] [SKIP] {filename}")
                continue
            t = now()
            ext = os.path.splitext(filename)[1].lower()
            cat_map = {
                ".pdf": "PDF",
                ".docx": "Word",
                ".xlsx": "Excel",
                ".png": "图片",
                ".jpg": "图片",
                ".jpeg": "图片",
                ".gif": "图片",
                ".bmp": "图片",
                ".svg": "矢量图",
                ".bin": "固件",
                ".hex": "固件",
                ".elf": "固件",
                ".uf2": "固件",
                ".sch": "原理图",
                ".brd": "PCB",
                ".kicad_pcb": "KiCad",
                ".kicad_sch": "KiCad",
                ".zip": "压缩包",
                ".rar": "压缩包",
                ".7z": "压缩包",
                ".gz": "压缩包",
            }
            cn = cat_map.get(ext, "文档")
            file_mtime = datetime.fromtimestamp(os.path.getmtime(filepath)).strftime("%Y-%m-%d")
            conn.execute(
                "INSERT INTO knowledge_cards (topic,category,file_path,summary,version,created_at,updated_at) VALUES(?,?,?,?,?,?,?)",
                (title, category, filepath, f"[{cn} 文件] {filepath}", file_mtime, t, t),
            )
            stats["imported"] += 1
            if is_worklog:
                stats["logs"] += 1
            else:
                stats["projects"] += 1
            print(
                f"  [{idx}/{total}] [OK] {filename} -> {'工作日志' if is_worklog else '项目资产'} ({cn})"
            )
            continue

        # 文本文件
        with open(filepath, "r", encoding="utf-8", errors="replace") as fh:
            raw = fh.read()
        existing = conn.execute(
            "SELECT id FROM knowledge_cards WHERE topic=? AND file_path=?",
            (title, filepath),
        ).fetchone()
        if existing:
            stats["skipped"] += 1
            print(f"  [{idx}/{total}] [SKIP] {filename}")
            continue
        t = now()
        file_mtime = datetime.fromtimestamp(os.path.getmtime(filepath)).strftime(
            "%Y-%m-%d"
        )
        conn.execute(
            "INSERT INTO knowledge_cards (topic,category,file_path,summary,version,created_at,updated_at) VALUES(?,?,?,?,?,?,?)",
            (title, category, filepath, raw[:10000], file_mtime, t, t),
        )
        conn.commit()
        stats["imported"] += 1
        if is_worklog:
            stats["logs"] += 1
        else:
            stats["projects"] += 1
        print(
            f"  [{idx}/{total}] [OK] {filename} -> {'工作日志' if is_worklog else '项目资产'}"
        )

    conn.commit()
    conn.close()

    print()
    print("=" * 50)
    print(f"  导入完成: 成功 {stats['imported']} 条, 跳过 {stats['skipped']} 条")
    print(
        f"  项目资产: {stats['projects']} | 工作日志: {stats['logs']} | 训练语料: {stats['corpus']}"
    )
    print(f"  涉及项目: {len(project_cache)} 个")
    print("=" * 50)

    # 写入导入历史（使 UI 导入历史面板可见）
    try:
        hconn = get_conn()
        hconn.execute(
            "INSERT INTO import_history (import_date, import_type, folder_path, project_name, file_count, summary, created_at) VALUES (?,?,?,?,?,?,?)",
            (
                now()[:10],
                "CLI导入",
                import_dir,
                project_name or "",
                stats["imported"],
                f"成功{stats['imported']}条, 跳过{stats['skipped']}条",
                now(),
            ),
        )
        hconn.commit()
        hconn.close()
    except Exception as e:
        print(f"[WARN] 导入历史写入失败: {e}")


# ═══════════════════════════════════════════════════════════
#  搜索功能
# ═══════════════════════════════════════════════════════════


def highlight_keyword(text, keywords, context=30):
    """在文本中高亮关键词。
    
    找到关键词在文本中最早出现的位置，截取前后各 context 个字符，
    用【】包裹每个关键词。跨行不区分大小写。
    
    Args:
        text: 原始文本
        keywords: 关键词列表(AND逻辑)
        context: 前后截取字符数，默认30
    Returns:
        str: 高亮后的文本片段，前后加 ... 标记截断
    """
    if not text or not keywords:
        return text[: context * 2] if text else ""
    import re

    best_pos = len(text)
    best_kw = keywords[0]
    # 找到最早出现的关键词位置
    for kw in keywords:
        pos = text.lower().find(kw.lower())
        if pos != -1 and pos < best_pos:
            best_pos = pos
            best_kw = kw
    if best_pos >= len(text):
        return text[: context * 2] if text else ""
    start = max(0, best_pos - context)
    end = min(len(text), best_pos + len(best_kw) + context)
    snippet = text[start:end]
    # 用【】包裹关键词（不区分大小写）
    for kw in keywords:
        snippet = re.sub(f"({re.escape(kw)})", r"【\1】", snippet, flags=re.IGNORECASE)
    return ("..." if start > 0 else "") + snippet + ("..." if end < len(text) else "")


def cmd_search(args):
    """全局搜索，支持关键词AND逻辑和结果片段高亮。
    
    搜索范围: decisions(title/content/basis/conclusion/tags) +
              pitfalls(problem/root_cause/solution/tags) +
              knowledge_cards(topic/summary/category)
    
    关键词用空格分隔 → AND逻辑(每条记录必须同时包含所有关键词)
    结果显示: 关键词前后30字上下文，用【】包裹关键词
    
    支持叠加筛选: --project / --tag / --date-from / --date-to
    
    Args:
        args.keyword: 空格分隔的关键词(可选)
        args.project: 项目名模糊匹配(可选)
        args.tag: 标签模糊匹配(可选)
        args.date_from/date_to: 日期范围(可选)
    """
    conn = get_conn()

    # 解析关键词（空格分隔，AND逻辑）
    keywords = []
    if hasattr(args, "keyword") and args.keyword:
        keywords = [kw.strip() for kw in args.keyword.split() if kw.strip()]

    # 构建过滤条件
    project_clause = ""
    project_params = []
    if args.project:
        project_clause = "AND p.name LIKE ?"
        project_params = [f"%{args.project}%"]

    tag_clause = ""
    tag_params = []
    if args.tag:
        tag_clause = "AND d.tags LIKE ?"
        tag_params = [f"%{args.tag}%"]

    date_clause = ""
    date_params = []
    if args.date_from:
        date_clause += " AND d.date >= ?"
        date_params.append(args.date_from)
    if args.date_to:
        date_clause += " AND d.date <= ?"
        date_params.append(args.date_to)

    # ── 关键词搜索子句：在多个字段中模糊匹配，多个关键词AND逻辑 ──
    def build_kw_clause(fields, table_alias=""):
        """为指定字段列表构建关键词LIKE子句，返回(clause_sql, params_list)"""
        if not keywords:
            return "", []
        prefix = f"{table_alias}." if table_alias else ""
        clauses = []
        params = []
        for kw in keywords:
            or_parts = " OR ".join([f"{prefix}{f} LIKE ?" for f in fields])
            clauses.append(f"({or_parts})")
            params.extend([f"%{kw}%"] * len(fields))
        return " AND " + " AND ".join(clauses), params

    kw_d_clause, kw_d_params = build_kw_clause(
        ["title", "content", "basis", "conclusion", "tags"], "d"
    )
    kw_pf_clause, kw_pf_params = build_kw_clause(
        ["problem", "root_cause", "solution", "tags"], "pf"
    )
    kw_kc_clause, kw_kc_params = build_kw_clause(["topic", "summary", "category"])

    results = []

    # 搜索决策
    d_query = f"""SELECT 'decision' AS source, d.id, p.name AS project, d.date,
                      d.title AS summary, d.tags,
                      d.title AS t_title, d.content AS t_content, d.basis AS t_basis, d.conclusion AS t_conclusion
               FROM decisions d JOIN projects p ON d.project_id = p.id
               WHERE 1=1 {project_clause} {tag_clause} {date_clause} {kw_d_clause}
               ORDER BY d.date DESC LIMIT 100"""
    d_params = project_params + tag_params + date_params + kw_d_params
    results.extend([dict(r) for r in conn.execute(d_query, d_params).fetchall()])

    # 搜索踩坑记录
    d_tag_clause = tag_clause.replace("d.", "pf.")
    pf_query = f"""SELECT 'pitfall' AS source, pf.id, p.name AS project, pf.date,
                       pf.problem AS summary, pf.tags,
                       pf.problem AS t_problem, pf.root_cause AS t_root_cause, pf.solution AS t_solution
                FROM pitfalls pf JOIN projects p ON pf.project_id = p.id
                WHERE 1=1 {project_clause} {d_tag_clause} {date_clause} {kw_pf_clause}
                ORDER BY pf.date DESC LIMIT 100"""
    pf_params = project_params + tag_params + date_params + kw_pf_params
    results.extend([dict(r) for r in conn.execute(pf_query, pf_params).fetchall()])

    # 搜索知识卡
    kc_tag_clause = ""
    kc_tag_params = []
    if args.tag:
        kc_tag_clause = "AND (category LIKE ? OR topic LIKE ?)"
        kc_tag_params = [f"%{args.tag}%", f"%{args.tag}%"]

    kc_query = f"""SELECT 'knowledge' AS source, id, topic AS label, updated_at AS date,
                       summary, category,
                       topic AS t_topic, summary AS t_summary
                FROM knowledge_cards
                WHERE 1=1 {kc_tag_clause} {kw_kc_clause}
                ORDER BY updated_at DESC LIMIT 100"""
    kc_params = kc_tag_params + kw_kc_params
    results.extend([dict(r) for r in conn.execute(kc_query, kc_params).fetchall()])

    if results:
        print(f"\n[Search] 搜索结果（共 {len(results)} 条）:\n")
        for r in results:
            source = r["source"]
            if source == "knowledge":
                line1 = f"[{source}] ID={r['id']} | {r['label']} | {r['date']}"
                line2 = f"       摘要: {r['summary'][:80]}"
                print(line1)
                print(line2)
            else:
                line1 = f"[{source}] ID={r['id']} | {r['project']} | {r['date']} | {r['summary'][:60]}"
                print(line1)

            # 关键词高亮：在内容字段中查找并显示片段
            if keywords:
                text_fields = {
                    "decision": ["t_title", "t_content", "t_basis", "t_conclusion"],
                    "pitfall": ["t_problem", "t_root_cause", "t_solution"],
                    "knowledge": ["t_topic", "t_summary"],
                }.get(source, [])
                for tf in text_fields:
                    val = r.get(tf, "")
                    if val:
                        snippet = highlight_keyword(val, keywords)
                        print(f"       [{tf.replace('t_', '')}]: {snippet}")
                print()
    else:
        print("[Search] 无匹配结果")

    conn.close()


# ═══════════════════════════════════════════════════════════
#  导出功能
# ═══════════════════════════════════════════════════════════


def cmd_export(args):
    """将指定项目的全部数据导出为 Markdown 文件。
    
    导出内容: 项目信息 + 技术决策 + 踩坑记录 + 知识卡 + 训练语料索引
    输出路径: 脚本同目录下 {项目名}_export.md
    
    Args:
        args.project: 项目名称或ID
    """
    conn = get_conn()
    pid = resolve_project_id(conn, args.project)
    if pid is None:
        print(f"[ERR] 未找到项目: {args.project}")
        conn.close()
        return

    # 获取项目基本信息
    proj = conn.execute("SELECT * FROM projects WHERE id = ?", (pid,)).fetchone()
    # 获取该项目的决策
    decisions = conn.execute(
        "SELECT * FROM decisions WHERE project_id = ? ORDER BY date DESC", (pid,)
    ).fetchall()
    # 获取该项目的踩坑记录
    pitfalls = conn.execute(
        "SELECT * FROM pitfalls WHERE project_id = ? ORDER BY date DESC", (pid,)
    ).fetchall()

    # 组装Markdown内容
    md = []
    md.append(f"# 项目: {proj['name']}\n")
    md.append(f"- **状态**: {proj['status']}")
    md.append(f"- **描述**: {proj['description']}")
    md.append(f"- **关键里程碑**: {proj['key_milestone']}")
    md.append(f"- **关联文件**: {proj['related_files']}")
    md.append(f"- **创建时间**: {proj['created_at']}")
    md.append(f"- **最后更新**: {proj['updated_at']}")
    md.append("")

    md.append("---\n")
    md.append(f"## 技术决策（{len(decisions)} 条）\n")
    for i, d in enumerate(decisions, 1):
        md.append(f"### 决策 {i}: {d['title']}")
        md.append(f"- **日期**: {d['date']}")
        md.append(f"- **标签**: {d['tags']}")
        md.append(f"\n**内容**:\n\n{d['content']}\n")
        md.append(f"**依据**:\n\n{d['basis']}\n")
        md.append(f"**结论**:\n\n{d['conclusion']}\n")
        md.append("")

    md.append("---\n")
    md.append(f"## 踩坑记录（{len(pitfalls)} 条）\n")
    for i, pf in enumerate(pitfalls, 1):
        md.append(f"### 踩坑 {i}: {pf['problem'][:50]}")
        md.append(f"- **日期**: {pf['date']}")
        md.append(f"- **标签**: {pf['tags']}")
        md.append(f"\n**问题**:\n\n{pf['problem']}\n")
        md.append(f"**根因分析**:\n\n{pf['root_cause']}\n")
        md.append(f"**解决方案**:\n\n{pf['solution']}\n")
        md.append("")

    # ── 关联知识卡：按项目名模糊匹配 category，或者遍历全部知识卡 ──
    # 获取全部知识卡，显示与项目名可能相关的（category包含项目关键词，或输出全部由用户自行筛选）
    all_knowledge = conn.execute(
        "SELECT * FROM knowledge_cards ORDER BY updated_at DESC"
    ).fetchall()

    md.append("---\n")
    md.append(f"## 知识卡（{len(all_knowledge)} 条）\n")
    if all_knowledge:
        for i, kc in enumerate(all_knowledge, 1):
            md.append(f"### 知识卡 {i}: {kc['topic']}")
            md.append(f"- **分类**: {kc['category']}")
            md.append(f"- **版本**: {kc['version']}")
            md.append(f"- **关联文件**: {kc['file_path']}")
            md.append(f"- **更新时间**: {kc['updated_at']}")
            md.append(f"\n**摘要**:\n\n{kc['summary']}\n")
            md.append("")
    else:
        md.append("（无）\n")

    # ── 训练语料索引：按项目名模糊匹配 tags ──
    training_corpus_rows = conn.execute(
        "SELECT * FROM training_corpus ORDER BY date DESC"
    ).fetchall()

    md.append("---\n")
    md.append(f"## 训练语料索引（{len(training_corpus_rows)} 条）\n")
    if training_corpus_rows:
        for i, tc in enumerate(training_corpus_rows, 1):
            md.append(f"### 语料 {i}")
            md.append(f"- **日期**: {tc['date']}")
            md.append(f"- **类型**: {tc['type']}")
            md.append(f"- **标签**: {tc['tags']}")
            md.append(f"- **文件路径**: {tc['file_path']}")
            md.append(f"- **记录数**: {tc['record_count']}")
            md.append(f"- **更新时间**: {tc['updated_at']}")
            md.append("")
    else:
        md.append("（无）\n")

    md.append(f"> 导出时间: {now()}")

    # 写入文件
    filename = f"{proj['name'].replace(' ', '_')}_export.md"
    output_path = os.path.join(os.path.dirname(DB_PATH), filename)
    with open(output_path, "w", encoding="utf-8") as f:
        f.write("\n".join(md))

    print(f"[OK] 已导出到: {output_path}")
    conn.close()


# ═══════════════════════════════════════════════════════════
#  统计功能
# ═══════════════════════════════════════════════════════════


def cmd_stats(args):
    """显示数据库统计信息概览。
    
    统计内容: 项目数量+状态分布, 各项目决策/踩坑数,
             全局总决策/总踩坑/总知识卡/总语料,
             最近更新的10条记录。
    """
    conn = get_conn()

    print("=" * 60)
    print("  [STATS] 数据库统计")
    print("=" * 60)

    # 项目概览
    projects = conn.execute(
        "SELECT name, status, updated_at FROM projects ORDER BY status, updated_at DESC"
    ).fetchall()

    print(f"\n[Projects] 项目概览（共 {len(projects)} 个）:\n")
    for p in projects:
        # 统计该项目下的决策和踩坑数
        d_count = conn.execute(
            "SELECT COUNT(*) AS cnt FROM decisions WHERE project_id = (SELECT id FROM projects WHERE name = ?)",
            (p["name"],),
        ).fetchone()["cnt"]
        pf_count = conn.execute(
            "SELECT COUNT(*) AS cnt FROM pitfalls WHERE project_id = (SELECT id FROM projects WHERE name = ?)",
            (p["name"],),
        ).fetchone()["cnt"]
        status_icon = {"active": "[A]", "paused": "[P]", "archived": "[X]"}.get(
            p["status"], "[?]"
        )
        print(f"  {status_icon} {p['name']}")
        print(
            f"     状态: {p['status']} | 决策: {d_count} | 踩坑: {pf_count} | 更新: {p['updated_at']}"
        )

    # 全局统计
    total_decisions = conn.execute("SELECT COUNT(*) AS cnt FROM decisions").fetchone()[
        "cnt"
    ]
    total_pitfalls = conn.execute("SELECT COUNT(*) AS cnt FROM pitfalls").fetchone()[
        "cnt"
    ]
    total_knowledge = conn.execute(
        "SELECT COUNT(*) AS cnt FROM knowledge_cards"
    ).fetchone()["cnt"]
    total_corpus = conn.execute(
        "SELECT COUNT(*) AS cnt FROM training_corpus"
    ).fetchone()["cnt"]

    print("\n[Summary] 全局汇总:")
    print(f"  总决策数:     {total_decisions}")
    print(f"  总踩坑记录:   {total_pitfalls}")
    print(f"  总知识卡:     {total_knowledge}")
    print(f"  总语料索引:   {total_corpus}")

    # 最近更新
    print("\n[Recent] 最近更新的10条记录:")
    recent = conn.execute("""
        SELECT 'decision' AS kind, title AS label, updated_at FROM decisions
        UNION ALL
        SELECT 'pitfall', problem, updated_at FROM pitfalls
        UNION ALL
        SELECT 'knowledge', topic, updated_at FROM knowledge_cards
        ORDER BY updated_at DESC LIMIT 10
    """).fetchall()
    for r in recent:
        print(f"  [{r['kind']}] {r['label'][:50]} — {r['updated_at']}")

    conn.close()


# ═══════════════════════════════════════════════════════════
#  命令行界面
# ═══════════════════════════════════════════════════════════


def build_parser():
    """构建 argparse 命令行解析器，注册所有子命令和参数。
    
    命令树:
        init | project {add,list,update,delete} |
        decision {add,list,update,delete,show} |
        pitfall {add,list,update,delete,show} |
        knowledge {add,list,update,delete,show} |
        corpus {add,list,update,delete,show} |
        search [--keyword] [--project] [--tag] [--date-*] |
        import --dir [--recursive] [--project] |
        export --project | stats
    Returns:
        argparse.ArgumentParser: 配置好的解析器
    """
    parser = argparse.ArgumentParser(
        prog="python digital_assets_manager.py",
        description="个人项目与数字资产管理器 — SQLite本地数据库 + CLI",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=textwrap.dedent("""\
            使用示例:
              # 初始化数据库
              python digital_assets_manager.py init

              # 项目管理
              python digital_assets_manager.py project add --name "电子宠物V1.0" --desc "ESP32驱动项目"
              python digital_assets_manager.py project list
              python digital_assets_manager.py project list --status active
              python digital_assets_manager.py project update "电子宠物V1.0" --status paused
              python digital_assets_manager.py project delete "电子宠物V1.0"

              # 决策管理
              python digital_assets_manager.py decision add --project "电子宠物V1.0" --date 2026-07-01 --title "选择主控芯片" --content "对比了ESP32-S3和ESP32-C3" --basis "需要USB OTG和WiFi+BLE共存" --conclusion "选用ESP32-S3-PICO-1" --tags "芯片选型,ESP32"
              python digital_assets_manager.py decision list
              python digital_assets_manager.py decision list --project "电子宠物V1.0"
              python digital_assets_manager.py decision show 1
              python digital_assets_manager.py decision update 1 --conclusion "改用ESP32-S3-WROOM-1"
              python digital_assets_manager.py decision delete 1

              # 踩坑记录
              python digital_assets_manager.py pitfall add --project "电子宠物V1.0" --date 2026-07-15 --problem "TFT屏幕花屏" --root_cause "SPI时钟过高且杜邦线过长" --solution "降频到40MHz，缩短接线并加磁环" --tags "硬件,SPI,TFT"
              python digital_assets_manager.py pitfall list
              python digital_assets_manager.py pitfall show 1

              # 知识卡
              python digital_assets_manager.py knowledge add --topic "ESP32-S3 GPIO12上拉问题" --category "ESP32" --summary "GPIO12为内部flash电压选择引脚，启动时必须为高电平" --file "docs/esp32_gpio12.md"
              python digital_assets_manager.py knowledge list --category ESP32

              # 训练语料
              python digital_assets_manager.py corpus add --date 2026-07-20 --type qa --tags "硬件调试" --file "corpus/qa_20260720.jsonl" --count 150

              # 搜索
              python digital_assets_manager.py search --tag "ESP32"
              python digital_assets_manager.py search --project "电子宠物" --date-from 2026-06-01

              # 导出
              python digital_assets_manager.py export --project "电子宠物V1.0"

              # 统计
              python digital_assets_manager.py stats
        """),
    )

    subparsers = parser.add_subparsers(dest="command", help="可用命令")

    # ── init ──
    subparsers.add_parser("init", help="初始化数据库（建表+索引）")

    # ── project ──
    p_project = subparsers.add_parser("project", help="项目管理")
    p_project_subs = p_project.add_subparsers(dest="action")

    p_add = p_project_subs.add_parser("add", help="添加项目")
    p_add.add_argument("--name", required=True, help="项目名称")
    p_add.add_argument(
        "--status",
        default="active",
        choices=["active", "paused", "archived"],
        help="状态",
    )
    p_add.add_argument("--desc", default="", help="描述")
    p_add.add_argument("--milestone", default="", help="关键里程碑")
    p_add.add_argument("--files", nargs="*", default=[], help="关联文件路径列表")

    p_list = p_project_subs.add_parser("list", help="列出项目")
    p_list.add_argument(
        "--status", choices=["active", "paused", "archived"], help="按状态过滤"
    )

    p_update = p_project_subs.add_parser("update", help="更新项目")
    p_update.add_argument("project", help="项目名称或ID")
    p_update.add_argument(
        "--status", choices=["active", "paused", "archived"], help="新状态"
    )
    p_update.add_argument("--desc", help="新描述")
    p_update.add_argument("--milestone", help="新里程碑")
    p_update.add_argument("--files", nargs="*", help="新关联文件列表")

    p_delete = p_project_subs.add_parser("delete", help="删除项目")
    p_delete.add_argument("project", help="项目名称或ID")
    p_delete.add_argument("--force", action="store_true", help="跳过确认直接删除")

    # ── decision ──
    p_dec = subparsers.add_parser("decision", help="决策管理")
    p_dec_subs = p_dec.add_subparsers(dest="action")

    p_da = p_dec_subs.add_parser("add", help="添加决策")
    p_da.add_argument("--project", required=True, help="项目名称或ID")
    p_da.add_argument("--date", required=True, help="决策日期 (YYYY-MM-DD)")
    p_da.add_argument("--title", required=True, help="决策标题")
    p_da.add_argument("--content", default="", help="决策内容")
    p_da.add_argument("--basis", default="", help="决策依据")
    p_da.add_argument("--conclusion", default="", help="结论")
    p_da.add_argument("--tags", default="", help="标签（逗号分隔）")

    p_dl = p_dec_subs.add_parser("list", help="列出决策")
    p_dl.add_argument("--project", help="按项目过滤")

    p_du = p_dec_subs.add_parser("update", help="更新决策")
    p_du.add_argument("id", type=int, help="决策ID")
    p_du.add_argument("--date", help="新日期")
    p_du.add_argument("--title", help="新标题")
    p_du.add_argument("--content", help="新内容")
    p_du.add_argument("--basis", help="新依据")
    p_du.add_argument("--conclusion", help="新结论")
    p_du.add_argument("--tags", help="新标签")

    p_dd = p_dec_subs.add_parser("delete", help="删除决策")
    p_dd.add_argument("id", type=int, help="决策ID")

    p_ds = p_dec_subs.add_parser("show", help="查看决策详情")
    p_ds.add_argument("id", type=int, help="决策ID")

    # ── pitfall ──
    p_pf = subparsers.add_parser("pitfall", help="踩坑记录管理")
    p_pf_subs = p_pf.add_subparsers(dest="action")

    p_pa = p_pf_subs.add_parser("add", help="添加踩坑记录")
    p_pa.add_argument("--project", required=True, help="项目名称或ID")
    p_pa.add_argument("--date", required=True, help="日期 (YYYY-MM-DD)")
    p_pa.add_argument("--problem", required=True, help="问题描述")
    p_pa.add_argument("--root_cause", default="", help="根因分析")
    p_pa.add_argument("--solution", default="", help="解决方案")
    p_pa.add_argument("--tags", default="", help="标签")

    p_pl = p_pf_subs.add_parser("list", help="列出踩坑记录")
    p_pl.add_argument("--project", help="按项目过滤")

    p_pu = p_pf_subs.add_parser("update", help="更新踩坑记录")
    p_pu.add_argument("id", type=int, help="记录ID")
    p_pu.add_argument("--date", help="新日期")
    p_pu.add_argument("--problem", help="新问题描述")
    p_pu.add_argument("--root_cause", help="新根因分析")
    p_pu.add_argument("--solution", help="新解决方案")
    p_pu.add_argument("--tags", help="新标签")

    p_pd = p_pf_subs.add_parser("delete", help="删除踩坑记录")
    p_pd.add_argument("id", type=int, help="记录ID")

    p_ps = p_pf_subs.add_parser("show", help="查看踩坑记录详情")
    p_ps.add_argument("id", type=int, help="记录ID")

    # ── knowledge ──
    p_kc = subparsers.add_parser("knowledge", help="知识卡管理")
    p_kc_subs = p_kc.add_subparsers(dest="action")

    p_ka = p_kc_subs.add_parser("add", help="添加知识卡")
    p_ka.add_argument("--topic", required=True, help="主题")
    p_ka.add_argument("--category", default="", help="分类")
    p_ka.add_argument("--file", default="", help="关联文件路径")
    p_ka.add_argument("--summary", default="", help="摘要")
    p_ka.add_argument("--version", default="1.0", help="版本号")

    p_kl = p_kc_subs.add_parser("list", help="列出知识卡")
    p_kl.add_argument("--category", help="按分类过滤")

    p_ku = p_kc_subs.add_parser("update", help="更新知识卡")
    p_ku.add_argument("id", type=int, help="知识卡ID")
    p_ku.add_argument("--topic", help="新主题")
    p_ku.add_argument("--category", help="新分类")
    p_ku.add_argument("--file", help="新文件路径")
    p_ku.add_argument("--summary", help="新摘要")
    p_ku.add_argument("--version", help="新版本号")

    p_kd = p_kc_subs.add_parser("delete", help="删除知识卡")
    p_kd.add_argument("id", type=int, help="知识卡ID")

    p_ks = p_kc_subs.add_parser("show", help="查看知识卡详情")
    p_ks.add_argument("id", type=int, help="知识卡ID")

    # ── corpus ──
    p_cp = subparsers.add_parser("corpus", help="训练语料管理")
    p_cp_subs = p_cp.add_subparsers(dest="action")

    p_ca = p_cp_subs.add_parser("add", help="添加语料索引")
    p_ca.add_argument("--date", required=True, help="日期 (YYYY-MM-DD)")
    p_ca.add_argument(
        "--type", required=True, choices=["qa", "decision", "pitfall"], help="语料类型"
    )
    p_ca.add_argument("--tags", default="", help="标签")
    p_ca.add_argument("--file", default="", help="JSONL文件路径")
    p_ca.add_argument("--count", type=int, default=0, help="记录数")

    p_cl = p_cp_subs.add_parser("list", help="列出语料索引")
    p_cl.add_argument(
        "--type", choices=["qa", "decision", "pitfall"], help="按类型过滤"
    )

    p_cu = p_cp_subs.add_parser("update", help="更新语料索引")
    p_cu.add_argument("id", type=int, help="记录ID")
    p_cu.add_argument("--date", help="新日期")
    p_cu.add_argument("--type", choices=["qa", "decision", "pitfall"], help="新类型")
    p_cu.add_argument("--tags", help="新标签")
    p_cu.add_argument("--file", help="新文件路径")
    p_cu.add_argument("--count", type=int, help="新记录数")

    p_cd = p_cp_subs.add_parser("delete", help="删除语料索引")
    p_cd.add_argument("id", type=int, help="记录ID")

    p_cs = p_cp_subs.add_parser("show", help="查看语料索引详情")
    p_cs.add_argument("id", type=int, help="记录ID")

    # ── search ──
    p_search = subparsers.add_parser("search", help="全局搜索")
    p_search.add_argument("--keyword", help="全文搜索关键词（空格分隔，AND逻辑）")
    p_search.add_argument("--project", help="项目名（模糊匹配）")
    p_search.add_argument("--tag", help="标签（模糊匹配）")
    p_search.add_argument("--date-from", help="起始日期 (YYYY-MM-DD)")
    p_search.add_argument("--date-to", help="截止日期 (YYYY-MM-DD)")

    # ── import ──
    p_import = subparsers.add_parser("import", help="批量导入文件")
    p_import.add_argument("--dir", required=True, help="扫描目录路径")
    p_import.add_argument(
        "--project", help="指定项目名（覆盖自动检测，全部归入该项目）"
    )
    p_import.add_argument(
        "--recursive",
        action="store_true",
        help="递归扫描子文件夹，每个子文件夹作为一个项目",
    )

    # ── export ──
    p_export = subparsers.add_parser("export", help="导出项目为Markdown")
    p_export.add_argument("--project", required=True, help="项目名称或ID")

    # ── stats ──
    subparsers.add_parser("stats", help="显示统计信息")

    return parser


def main():
    parser = build_parser()
    args = parser.parse_args()

    if args.command is None:
        parser.print_help()
        return

    # 除了 init 之外，所有数据库操作前检查是否已初始化
    if args.command != "init" and not check_db_initialized():
        return

    # 路由到对应处理函数
    if args.command == "init":
        init_db()

    elif args.command == "project":
        if args.action == "add":
            cmd_project_add(args)
        elif args.action == "list":
            cmd_project_list(args)
        elif args.action == "update":
            cmd_project_update(args)
        elif args.action == "delete":
            cmd_project_delete(args)
        else:
            print("[ERR] 未知 project 子命令，可用: add / list / update / delete")

    elif args.command == "decision":
        if args.action == "add":
            cmd_decision_add(args)
        elif args.action == "list":
            cmd_decision_list(args)
        elif args.action == "update":
            cmd_decision_update(args)
        elif args.action == "delete":
            cmd_decision_delete(args)
        elif args.action == "show":
            cmd_decision_show(args)
        else:
            print(
                "[ERR] 未知 decision 子命令，可用: add / list / update / delete / show"
            )

    elif args.command == "pitfall":
        if args.action == "add":
            cmd_pitfall_add(args)
        elif args.action == "list":
            cmd_pitfall_list(args)
        elif args.action == "update":
            cmd_pitfall_update(args)
        elif args.action == "delete":
            cmd_pitfall_delete(args)
        elif args.action == "show":
            cmd_pitfall_show(args)
        else:
            print(
                "[ERR] 未知 pitfall 子命令，可用: add / list / update / delete / show"
            )

    elif args.command == "knowledge":
        if args.action == "add":
            cmd_knowledge_add(args)
        elif args.action == "list":
            cmd_knowledge_list(args)
        elif args.action == "update":
            cmd_knowledge_update(args)
        elif args.action == "delete":
            cmd_knowledge_delete(args)
        elif args.action == "show":
            cmd_knowledge_show(args)
        else:
            print(
                "[ERR] 未知 knowledge 子命令，可用: add / list / update / delete / show"
            )

    elif args.command == "corpus":
        if args.action == "add":
            cmd_corpus_add(args)
        elif args.action == "list":
            cmd_corpus_list(args)
        elif args.action == "update":
            cmd_corpus_update(args)
        elif args.action == "delete":
            cmd_corpus_delete(args)
        elif args.action == "show":
            cmd_corpus_show(args)
        else:
            print("[ERR] 未知 corpus 子命令，可用: add / list / update / delete / show")

    elif args.command == "search":
        cmd_search(args)

    elif args.command == "import":
        cmd_import(args)

    elif args.command == "export":
        cmd_export(args)

    elif args.command == "stats":
        cmd_stats(args)

    else:
        print(f"[ERR] 未知命令: {args.command}")
        parser.print_help()


if __name__ == "__main__":
    main()
