"""
Git工具模块
该模块提供了与Git仓库交互的工具。
包含以下功能：
- 查找Git仓库的根目录
- 检查是否有未提交的更改
- 获取两个哈希值之间的提交历史
- 获取最新提交的哈希值
- 从Git差异中提取修改的行范围
"""

import datetime
import os

from jarvis.jarvis_utils.output import PrettyOutput

# -*- coding: utf-8 -*-
import re
import subprocess
import sys
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Set
from typing import Tuple

from jarvis.jarvis_utils.config import get_data_dir
from jarvis.jarvis_utils.config import is_confirm_before_apply_patch
from jarvis.jarvis_utils.input import user_confirm
from jarvis.jarvis_utils.utils import decode_output
from jarvis.jarvis_utils.utils import is_rag_installed

# 全局标记：记录 confirm_add_new_files 是否已在当前流程中被调用
# 用于避免在同一流程中重复询问用户是否添加新文件/二进制文件/大代码
_confirm_add_new_files_called = False


def reset_confirm_add_new_files_flag() -> None:
    """重置 confirm_add_new_files 的全局标记

    用于在新的流程开始时重置标记，允许重新进行文件确认
    """
    global _confirm_add_new_files_called
    _confirm_add_new_files_called = False


def find_git_root_and_cd(start_dir: str = ".", allow_init: bool = False) -> str:
    """
    切换到给定路径的Git根目录。

    参数:
        start_dir (str): 起始查找目录，默认为当前目录。
        allow_init (bool): 如果不是Git仓库，是否允许初始化新的Git仓库。默认为False。

    返回:
        str: Git仓库根目录路径。

    异常:
        subprocess.CalledProcessError: 如果不是Git仓库且不允许初始化。
    """
    os.chdir(start_dir)
    try:
        result = subprocess.run(
            ["git", "rev-parse", "--show-toplevel"],
            capture_output=True,
            text=False,
            check=True,
        )
        git_root = decode_output(result.stdout).strip()
        if not git_root:
            if allow_init:
                subprocess.run(["git", "init"], check=True)
                git_root = os.path.abspath(".")
            else:
                raise subprocess.CalledProcessError(
                    1, ["git", "rev-parse", "--show-toplevel"]
                )
    except subprocess.CalledProcessError:
        # 如果不是Git仓库
        if allow_init:
            subprocess.run(["git", "init"], check=True)
            git_root = os.path.abspath(".")
        else:
            raise
    os.chdir(git_root)
    return git_root


def has_uncommitted_changes() -> bool:
    """检查Git仓库中是否有未提交的更改

    返回:
        bool: 如果有未提交的更改返回True，否则返回False
    """
    # 在执行git add .之前，记录当前暂存区的文件（可能有用户手动添加的被gitignore的文件）
    process = subprocess.Popen(
        ["git", "diff", "--cached", "--name-only"],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=False,
    )
    stdout_bytes, _ = process.communicate()
    staged_files_before_add = decode_output(stdout_bytes)
    manually_staged_files = [
        f.strip() for f in staged_files_before_add.split("\n") if f.strip()
    ]

    # 静默添加所有更改
    subprocess.run(
        ["git", "add", "-A"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
    )

    # 检查工作目录更改
    working_changes = (
        subprocess.run(
            ["git", "diff", "--exit-code"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        ).returncode
        != 0
    )

    # 检查暂存区更改
    staged_changes = (
        subprocess.run(
            ["git", "diff", "--cached", "--exit-code"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        ).returncode
        != 0
    )

    # 静默重置更改
    subprocess.run(
        ["git", "reset"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
    )

    # 重置后，重新添加之前手动暂存的可能被gitignore忽略的文件
    for file_path in manually_staged_files:
        if os.path.exists(file_path):
            # 使用 -f 参数强制添加被 .gitignore 忽略的文件
            subprocess.run(["git", "add", "-f", file_path], check=False)

    return working_changes or staged_changes


def get_commits_between(start_hash: str, end_hash: str) -> List[Tuple[str, str]]:
    """获取两个提交哈希值之间的提交列表

    参数：
        start_hash: 起始提交哈希值（不包含）
        end_hash: 结束提交哈希值（包含）

    返回：
        List[Tuple[str, str]]: (提交哈希值, 提交信息) 元组列表
    """
    try:
        # 使用git log和pretty格式获取哈希值和信息
        result = subprocess.run(
            ["git", "log", f"{start_hash}..{end_hash}", "--pretty=format:%H|%s"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=False,  # 禁用自动文本解码
        )
        if result.returncode != 0:
            error_msg = decode_output(result.stderr)
            PrettyOutput.auto_print(f"❌ 获取commit历史失败: {error_msg}")
            return []

        output = decode_output(result.stdout)
        commits = []
        for line in output.splitlines():
            if "|" in line:
                commit_hash, message = line.split("|", 1)
                commits.append((commit_hash, message))
        return commits

    except Exception as e:
        PrettyOutput.auto_print(f"❌ 获取commit历史异常: {str(e)}")
        return []


# 修改后的获取差异函数


def get_diff() -> str:
    """使用git获取工作区差异，包括修改和新增的文件内容

    返回:
        str: 差异内容或错误信息
    """
    try:
        # 检查是否为空仓库
        head_check = subprocess.run(
            ["git", "rev-parse", "--verify", "HEAD"],
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
        )
        confirm_add_new_files()
        if head_check.returncode != 0:
            # 空仓库情况，直接获取工作区差异
            result = subprocess.run(
                ["git", "diff"], capture_output=True, text=False, check=True
            )
        else:
            # 暂存新增文件
            subprocess.run(["git", "add", "-N", "."], check=True)

            # 获取所有差异（包括新增文件）
            result = subprocess.run(
                ["git", "diff", "HEAD"], capture_output=True, text=False, check=True
            )

            # 重置暂存区
            subprocess.run(["git", "reset"], check=True)

        return decode_output(result.stdout)

    except subprocess.CalledProcessError as e:
        return f"获取差异失败: {str(e)}"
    except Exception as e:
        return f"发生意外错误: {str(e)}"


def get_diff_stat_between_commits(
    start_hash: str, end_hash: Optional[str] = None
) -> str:
    """获取两个commit之间的差异统计信息

    参数:
        start_hash: 起始commit哈希值（不包含）
        end_hash: 结束commit哈希值（包含），如果为None则使用HEAD

    返回:
        str: 差异统计信息或错误信息
    """
    try:
        if end_hash is None:
            end_hash = "HEAD"

        # 检查start_hash是否存在
        start_check = subprocess.run(
            ["git", "rev-parse", "--verify", start_hash],
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
        )
        if start_check.returncode != 0:
            return f"起始commit不存在: {start_hash}"

        # 检查end_hash是否存在
        end_check = subprocess.run(
            ["git", "rev-parse", "--verify", end_hash],
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
        )
        if end_check.returncode != 0:
            return f"结束commit不存在: {end_hash}"

        # 获取两个commit之间的差异统计信息
        result = subprocess.run(
            ["git", "diff", "--stat", f"{start_hash}..{end_hash}"],
            capture_output=True,
            text=False,
            check=True,
        )

        try:
            return decode_output(result.stdout)
        except Exception as e:
            return f"解码输出失败: {str(e)}"

    except subprocess.CalledProcessError as e:
        error_msg = decode_output(e.stderr) if e.stderr else str(e)
        return f"获取commit差异统计失败: {error_msg}"
    except Exception as e:
        return f"发生意外错误: {str(e)}"


def get_diff_between_commits(start_hash: str, end_hash: Optional[str] = None) -> str:
    """获取两个commit之间的差异

    参数:
        start_hash: 起始commit哈希值（不包含）
        end_hash: 结束commit哈希值（包含），如果为None则使用HEAD

    返回:
        str: 差异内容或错误信息
    """
    try:
        if end_hash is None:
            # 如果end_hash为None，使用HEAD
            end_hash = "HEAD"

        # 检查start_hash是否存在
        start_check = subprocess.run(
            ["git", "rev-parse", "--verify", start_hash],
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
        )
        if start_check.returncode != 0:
            return f"起始commit不存在: {start_hash}"

        # 检查end_hash是否存在
        end_check = subprocess.run(
            ["git", "rev-parse", "--verify", end_hash],
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
        )
        if end_check.returncode != 0:
            return f"结束commit不存在: {end_hash}"

        # 获取两个commit之间的差异
        result = subprocess.run(
            ["git", "diff", f"{start_hash}..{end_hash}"],
            capture_output=True,
            text=False,
            check=True,
        )

        try:
            return decode_output(result.stdout)
        except Exception as e:
            return f"解码输出失败: {str(e)}"

    except subprocess.CalledProcessError as e:
        error_msg = decode_output(e.stderr) if e.stderr else str(e)
        return f"获取commit差异失败: {error_msg}"
    except Exception as e:
        return f"发生意外错误: {str(e)}"


def revert_file(filepath: str) -> None:
    """增强版git恢复，处理新文件"""
    import subprocess

    try:
        # 检查文件是否在版本控制中
        result = subprocess.run(
            ["git", "ls-files", "--error-unmatch", filepath],
            stderr=subprocess.PIPE,
            text=False,  # 禁用自动文本解码
        )
        if result.returncode == 0:
            subprocess.run(["git", "checkout", "HEAD", "--", filepath], check=True)
        else:
            if os.path.exists(filepath):
                os.remove(filepath)
        subprocess.run(["git", "clean", "-f", "--", filepath], check=True)
    except subprocess.CalledProcessError as e:
        error_msg = decode_output(e.stderr) if e.stderr else str(e)
        PrettyOutput.auto_print(f"❌ 恢复文件失败: {error_msg}")


# 修改后的恢复函数


def revert_change() -> None:
    """恢复所有未提交的修改到HEAD状态"""
    import subprocess

    try:
        # 检查是否为空仓库
        head_check = subprocess.run(
            ["git", "rev-parse", "--verify", "HEAD"],
            stderr=subprocess.PIPE,
            stdout=subprocess.PIPE,
        )
        if head_check.returncode == 0:
            subprocess.run(["git", "reset", "--hard", "HEAD"], check=True)
        subprocess.run(["git", "clean", "-fd"], check=True)
    except subprocess.CalledProcessError as e:
        PrettyOutput.auto_print(f"❌ 恢复更改失败: {str(e)}")


def detect_large_code_deletion(threshold: int = 30) -> Optional[Dict[str, int]]:
    """检测是否有大量代码删除

    参数:
        threshold: 净删除行数阈值，默认200行

    返回:
        Optional[Dict[str, int]]: 如果检测到大量删除，返回包含统计信息的字典：
            {
                'insertions': int,  # 新增行数
                'deletions': int,   # 删除行数
                'net_deletions': int  # 净删除行数
            }
            如果没有大量删除或发生错误，返回None
    """
    try:
        # 临时暂存所有文件以便获取完整的diff统计
        subprocess.run(
            ["git", "add", "-N", "."],
            check=False,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )

        # 检查是否有HEAD
        head_check = subprocess.run(
            ["git", "rev-parse", "--verify", "HEAD"],
            stderr=subprocess.DEVNULL,
            stdout=subprocess.DEVNULL,
        )

        if head_check.returncode == 0:
            # 有HEAD，获取相对于HEAD的diff统计
            diff_result = subprocess.run(
                ["git", "diff", "HEAD", "--shortstat"],
                capture_output=True,
                check=False,
            )
        else:
            # 空仓库，获取工作区diff统计
            diff_result = subprocess.run(
                ["git", "diff", "--shortstat"],
                capture_output=True,
                check=False,
            )

        # 重置暂存区
        subprocess.run(
            ["git", "reset"],
            check=False,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )

        # 解析插入和删除行数
        if diff_result.returncode == 0 and diff_result.stdout:
            output = decode_output(diff_result.stdout)
            insertions = 0
            deletions = 0
            insertions_match = re.search(r"(\d+)\s+insertions?\(\+\)", output)
            deletions_match = re.search(r"(\d+)\s+deletions?\(\-\)", output)
            if insertions_match:
                insertions = int(insertions_match.group(1))
            if deletions_match:
                deletions = int(deletions_match.group(1))

            # 检查是否有大量代码删除（净删除超过阈值）
            net_deletions = deletions - insertions
            if net_deletions > threshold:
                return {
                    "insertions": insertions,
                    "deletions": deletions,
                    "net_deletions": net_deletions,
                }
        return None
    except Exception:
        # 如果检查过程中出错，返回None
        return None


# confirm_large_code_deletion函数已废弃，统一使用大模型询问


def check_large_code_deletion(threshold: int = 30) -> bool:
    """检查是否有大量代码删除

    参数:
        threshold: 净删除行数阈值，默认200行

    返回:
        bool: 始终返回True，由调用方统一处理大模型询问
    """
    # 检测功能现在由调用方统一处理
    return True


def handle_commit_workflow() -> bool:
    """Handle the git commit workflow and return the commit details.

    Returns:
        bool: 提交是否成功
    """
    if is_confirm_before_apply_patch() and not user_confirm(
        "是否要提交代码？", default=True
    ):
        revert_change()
        return False

    import subprocess

    try:
        confirm_add_new_files()

        if not has_uncommitted_changes():
            return False

        # 在提交前检查是否有大量代码删除
        if not check_large_code_deletion():
            return False

        # 获取当前分支的提交总数
        commit_result = subprocess.run(
            ["git", "rev-list", "--count", "HEAD"],
            capture_output=True,
            text=False,
        )
        if commit_result.returncode != 0:
            return False

        commit_count = int(decode_output(commit_result.stdout).strip())

        # 暂存所有修改
        subprocess.run(["git", "add", "."], check=True)

        # 提交变更
        subprocess.run(
            ["git", "commit", "-m", f"CheckPoint #{commit_count + 1}"], check=True
        )
        return True
    except subprocess.CalledProcessError:
        return False


def get_latest_commit_hash() -> str:
    """获取当前Git仓库的最新提交哈希值

    返回：
        str: 提交哈希值，如果不在Git仓库、空仓库或发生错误则返回空字符串
    """
    try:
        # 首先检查是否存在HEAD引用
        head_check = subprocess.run(
            ["git", "rev-parse", "--verify", "HEAD"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=False,
        )
        if head_check.returncode != 0:
            return ""  # 空仓库或无效HEAD

        # 获取HEAD的完整哈希值
        result = subprocess.run(
            ["git", "rev-parse", "HEAD"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=False,
        )
        return decode_output(result.stdout).strip() if result.returncode == 0 else ""
    except Exception:
        return ""


def get_modified_line_ranges() -> Dict[str, List[Tuple[int, int]]]:
    """从Git差异中获取所有更改文件的修改行范围

    返回：
        字典，将文件路径映射到包含修改部分的（起始行, 结束行）范围元组。
        行号从1开始。
    """
    # 获取所有文件的Git差异
    # 仅用于解析修改行范围，减少上下文以降低输出体积和解析成本
    proc = subprocess.run(
        ["git", "show", "--no-color"],
        capture_output=True,
    )
    diff_output = decode_output(proc.stdout)

    # 解析差异以获取修改的文件及其行范围
    result: Dict[str, List[Tuple[int, int]]] = {}
    current_file = None

    for line in diff_output.splitlines():
        # 匹配类似"+++ b/path/to/file"的行
        file_match = re.match(r"^\+\+\+ b/(.*)", line)
        if file_match:
            current_file = file_match.group(1)
            continue

        # 匹配类似"@@ -100,5 +100,7 @@"的行，其中+部分显示新行
        range_match = re.match(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@", line)
        if range_match and current_file:
            start_line = int(range_match.group(1))  # 保持从1开始
            line_count = int(range_match.group(2)) if range_match.group(2) else 1
            end_line = start_line + line_count - 1
            if current_file not in result:
                result[current_file] = []
            result[current_file].append((start_line, end_line))

    return result


def is_file_in_git_repo(filepath: str) -> bool:
    """检查文件是否在当前Git仓库中"""
    import subprocess

    try:
        # 获取Git仓库根目录
        repo_root_result = subprocess.run(
            ["git", "rev-parse", "--show-toplevel"], capture_output=True
        )
        repo_root = (
            decode_output(repo_root_result.stdout).strip()
            if repo_root_result.returncode == 0
            else ""
        )

        # 检查文件路径是否在仓库根目录下
        return os.path.abspath(filepath).startswith(os.path.abspath(repo_root))
    except Exception:
        return False


def check_and_update_git_repo(repo_path: str) -> bool:
    """检查并更新git仓库

    参数:
        repo_path: 仓库路径

    返回:
        bool: 是否执行了更新
    """
    # 检查上次检查日期
    last_check_file = os.path.join(get_data_dir(), "last_git_check")
    today_str = datetime.date.today().strftime("%Y-%m-%d")
    if os.path.exists(last_check_file):
        with open(last_check_file, "r") as f:
            last_check_date = f.read().strip()
        if last_check_date == today_str:
            return False

    curr_dir = os.path.abspath(os.getcwd())
    try:
        git_root = find_git_root_and_cd(repo_path)
    except Exception:
        return False

    try:
        # 检查是否有未提交的修改
        if has_uncommitted_changes():
            return False

        # 获取远程tag更新
        subprocess.run(["git", "fetch", "--tags"], cwd=git_root, check=True)
        # 获取最新本地tag
        local_tag_result = subprocess.run(
            ["git", "describe", "--tags", "--abbrev=0"],
            cwd=git_root,
            capture_output=True,
        )
        # 获取最新远程tag
        remote_tag_result = subprocess.run(
            ["git", "ls-remote", "--tags", "--refs", "origin"],
            cwd=git_root,
            capture_output=True,
        )
        if remote_tag_result.returncode == 0:
            # 提取最新的tag名称
            tags = [
                ref.split("/")[-1]
                for ref in decode_output(remote_tag_result.stdout).splitlines()
            ]
            tags = sorted(
                tags,
                key=lambda x: [
                    int(i) if i.isdigit() else i for i in re.split(r"([0-9]+)", x)
                ],
            )
            remote_tag = tags[-1] if tags else ""

        if (
            local_tag_result.returncode == 0
            and remote_tag_result.returncode == 0
            and decode_output(local_tag_result.stdout).strip() != remote_tag
        ):
            PrettyOutput.auto_print(
                f"ℹ️ 检测到新版本tag {remote_tag}，正在更新Jarvis..."
            )
            subprocess.run(
                ["git", "checkout", remote_tag],
                cwd=git_root,
                check=True,
            )
            PrettyOutput.auto_print(f"✅ Jarvis已更新到tag {remote_tag}")

            # 执行pip安装更新代码
            try:
                PrettyOutput.auto_print("ℹ️ 正在安装更新后的代码...")

                # 检查是否在虚拟环境中
                in_venv = hasattr(sys, "real_prefix") or (
                    hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix
                )

                # 检测 uv 可用性：优先虚拟环境内的 uv，其次 PATH 中的 uv
                from shutil import which as _which

                uv_executable = None
                if sys.platform == "win32":
                    venv_uv = os.path.join(sys.prefix, "Scripts", "uv.exe")
                else:
                    venv_uv = os.path.join(sys.prefix, "bin", "uv")
                if os.path.exists(venv_uv):
                    uv_executable = venv_uv
                else:
                    path_uv = _which("uv")
                    if path_uv:
                        uv_executable = path_uv

                # 根据环境选择安装命令
                # 检测是否安装了 RAG 特性（更精确）
                rag_installed = is_rag_installed()

                # 根据 uv 可用性与 RAG 特性选择安装命令（优先使用 uv）
                if uv_executable:
                    if rag_installed:
                        install_cmd = [uv_executable, "pip", "install", "-e", ".[rag]"]
                    else:
                        install_cmd = [uv_executable, "pip", "install", "-e", "."]
                else:
                    if rag_installed:
                        install_cmd = [
                            sys.executable,
                            "-m",
                            "pip",
                            "install",
                            "-e",
                            ".[rag]",
                        ]
                    else:
                        install_cmd = [
                            sys.executable,
                            "-m",
                            "pip",
                            "install",
                            "-e",
                            ".",
                        ]

                # 尝试安装
                result = subprocess.run(install_cmd, cwd=git_root, capture_output=True)

                if result.returncode == 0:
                    PrettyOutput.auto_print("✅ 代码更新安装成功")
                    return True

                # 处理权限错误
                error_msg = decode_output(result.stderr).strip()
                if not in_venv and (
                    "Permission denied" in error_msg or "not writeable" in error_msg
                ):
                    if user_confirm(
                        "检测到权限问题，是否尝试用户级安装(--user)？", True
                    ):
                        user_result = subprocess.run(
                            install_cmd + ["--user"],
                            cwd=git_root,
                            capture_output=True,
                        )
                        if user_result.returncode == 0:
                            PrettyOutput.auto_print("✅ 用户级代码安装成功")
                            return True
                        error_msg = decode_output(user_result.stderr).strip()

                PrettyOutput.auto_print(f"❌ 代码安装失败: {error_msg}")
                return False
            except Exception as e:
                PrettyOutput.auto_print(f"❌ 安装过程中发生意外错误: {str(e)}")
                return False
        # 更新检查日期文件
        with open(last_check_file, "w") as f:
            f.write(today_str)
        return False
    except Exception as e:
        PrettyOutput.auto_print(f"⚠️ Git仓库更新检查失败: {e}")
        return False
    finally:
        os.chdir(curr_dir)


def get_diff_file_list() -> List[str]:
    """获取HEAD到当前变更的文件列表，包括修改和新增的文件

    返回:
        List[str]: 修改和新增的文件路径列表
    """
    try:
        confirm_add_new_files()

        # 暂存新增文件
        subprocess.run(["git", "add", "-N", "."], check=True)

        # 获取所有差异文件（包括新增文件）
        result = subprocess.run(
            ["git", "diff", "--name-only", "HEAD"], capture_output=True
        )

        # 重置暂存区
        subprocess.run(["git", "reset"], check=True)

        if result.returncode != 0:
            PrettyOutput.auto_print(
                f"❌ 获取差异文件列表失败: {decode_output(result.stderr)}"
            )
            return []

        return [f for f in decode_output(result.stdout).splitlines() if f]

    except subprocess.CalledProcessError as e:
        PrettyOutput.auto_print(f"❌ 获取差异文件列表失败: {str(e)}")
        return []
    except Exception as e:
        PrettyOutput.auto_print(f"❌ 获取差异文件列表异常: {str(e)}")
        return []


def get_recent_commits_with_files() -> List[Dict[str, Any]]:
    """获取最近5次提交的commit信息和文件清单

    返回:
        List[Dict[str, Any]]: 包含commit信息和文件清单的字典列表，格式为:
            [
                {
                    'hash': str,
                    'message': str,
                    'author': str,
                    'date': str,
                    'files': List[str]  # 修改的文件列表 (最多20个文件)
                },
                ...
            ]
            失败时返回空列表
    """
    try:
        # 获取当前git用户名
        current_author_result = subprocess.run(
            ["git", "config", "user.name"],
            capture_output=True,
        )
        current_author = (
            decode_output(current_author_result.stdout).strip()
            if current_author_result.returncode == 0
            else ""
        )

        # 获取当前用户最近5次提交的基本信息
        result = subprocess.run(
            [
                "git",
                "log",
                "-5",
                "--author=" + current_author,
                "--pretty=format:%H%n%s%n%an%n%ad",
            ],
            capture_output=True,
        )
        if result.returncode != 0:
            return []

        # 解析提交信息
        commits: List[Dict[str, Any]] = []
        lines = decode_output(result.stdout).splitlines()
        for i in range(0, len(lines), 4):
            if i + 3 >= len(lines):
                break
            commit: Dict[str, Any] = {
                "hash": lines[i],
                "message": lines[i + 1],
                "author": lines[i + 2],
                "date": lines[i + 3],
                "files": [],
            }
            commits.append(commit)

        # 获取每个提交的文件修改清单
        for commit in commits:
            files_result = subprocess.run(
                ["git", "show", "--name-only", "--pretty=format:", commit["hash"]],
                capture_output=True,
            )
            if files_result.returncode == 0:
                file_lines = decode_output(files_result.stdout).splitlines()
                unique_files: Set[str] = set(filter(None, file_lines))
                commit["files"] = list(unique_files)[:20]  # 限制最多20个文件

        return commits

    except subprocess.CalledProcessError:
        return []


def _get_new_files() -> List[str]:
    """获取新增文件列表"""
    result = subprocess.run(
        ["git", "ls-files", "--others", "--exclude-standard"],
        capture_output=True,
        check=True,
    )
    return decode_output(result.stdout).splitlines()


def confirm_add_new_files() -> None:
    """确认新增文件、代码行数和二进制文件"""
    global _confirm_add_new_files_called

    # 如果已经确认过，直接返回，避免重复询问
    if _confirm_add_new_files_called:
        return

    _confirm_add_new_files_called = True

    def _get_added_lines() -> int:
        """获取新增代码行数"""
        diff_stats_result = subprocess.run(
            ["git", "diff", "--numstat"],
            capture_output=True,
            check=True,
        )
        diff_stats = decode_output(diff_stats_result.stdout).splitlines()

        added_lines = 0
        for stat in diff_stats:
            parts = stat.split()
            if len(parts) >= 1:
                try:
                    added_lines += int(parts[0])
                except ValueError:
                    pass
        return added_lines

    def _get_binary_files(files: List[str]) -> List[str]:
        """从文件列表中识别二进制文件"""
        binary_files = []
        for file in files:
            try:
                with open(file, "rb") as f:
                    if b"\x00" in f.read(1024):
                        binary_files.append(file)
            except (IOError, PermissionError):
                continue
        return binary_files

    def _check_conditions(
        new_files: List[str], added_lines: int, binary_files: List[str]
    ) -> bool:
        """检查各种条件并打印提示信息"""
        need_confirm = False
        output_lines = []

        if len(new_files) > 20:
            output_lines.append(f"检测到{len(new_files)}个新增文件(选择N将重新检测)")
            output_lines.append("新增文件列表:")
            output_lines.extend(f"  - {file}" for file in new_files)
            need_confirm = True

        if added_lines > 500:
            output_lines.append(f"检测到{added_lines}行新增代码(选择N将重新检测)")
            need_confirm = True

        if binary_files:
            output_lines.append(
                f"检测到{len(binary_files)}个二进制文件(选择N将重新检测)"
            )
            output_lines.append("二进制文件列表:")
            output_lines.extend(f"  - {file}" for file in binary_files)
            need_confirm = True

        if output_lines:
            emoji = "⚠️ " if need_confirm else "ℹ️ "
            PrettyOutput.auto_print(emoji + ("\n" + emoji).join(output_lines))

        return need_confirm

    while True:
        new_files = _get_new_files()
        added_lines = _get_added_lines()
        binary_files = _get_binary_files(new_files)

        if not _check_conditions(new_files, added_lines, binary_files):
            break

        if not user_confirm(
            "是否要添加这些变更（如果不需要请修改.gitignore文件以忽略不需要的文件）？",
            True,
        ):
            # 用户选择 N：自动将未跟踪文件列表添加到仓库根目录的 .gitignore
            try:
                repo_root_result = subprocess.run(
                    ["git", "rev-parse", "--show-toplevel"],
                    capture_output=True,
                    check=True,
                )
                repo_root = decode_output(repo_root_result.stdout).strip() or "."
            except Exception:
                repo_root = "."
            gitignore_path = os.path.join(repo_root, ".gitignore")

            # 仅对未跟踪的新文件进行忽略（已跟踪文件无法通过 .gitignore 忽略）
            files_to_ignore = sorted(set(new_files))

            # 读取已存在的 .gitignore 以避免重复添加
            existing_lines: Set[str] = set()
            try:
                if os.path.exists(gitignore_path):
                    with open(gitignore_path, "r", encoding="utf-8") as f:
                        existing_lines = set(line.strip() for line in f if line.strip())
            except Exception:
                existing_lines = set()

            # 追加未存在的文件路径到 .gitignore（使用相对于仓库根目录的路径）
            try:
                with open(gitignore_path, "a", encoding="utf-8") as f:
                    for file in files_to_ignore:
                        abs_path = os.path.abspath(file)
                        rel_path = os.path.relpath(abs_path, repo_root)
                        # 避免无效的相对路径（不应出现 .. 前缀），有则回退用原始值
                        entry = rel_path if not rel_path.startswith("..") else file
                        if entry not in existing_lines:
                            f.write(entry + "\n")
                PrettyOutput.auto_print(
                    "ℹ️ 已将未跟踪文件添加到 .gitignore，正在重新检测..."
                )
            except Exception as e:
                PrettyOutput.auto_print(f"⚠️ 更新 .gitignore 失败: {str(e)}")

            continue

        break
