"""MakePython: Automated Python project building and management tool.

This module provides a command-line interface for common Python development tasks
including building, testing, publishing, and version management.

Features:
    - Automatic build tool detection (uv, poetry, hatch)
    - Project configuration management
    - Command execution with proper error handling
    - PyPI token management
    - Clean build artifacts
    - Test execution with various configurations

Example:
    >>> # Build project
    >>> makepython build
    >>>
    >>> # Run tests
    >>> makepython test
    >>>
    >>> # Publish to PyPI
    >>> makepython publish
"""

from __future__ import annotations

__version__: Final[str] = "0.2.0"
__author__: Final[str] = "pysfi Developers"
__email__: Final[str] = "developers@pysfi.org"

import argparse
import atexit
import contextlib
import datetime
import json
import logging
import os
import re
import shutil
import subprocess
import sys
import time
from dataclasses import dataclass
from enum import Enum
from functools import cached_property, lru_cache
from pathlib import Path
from typing import Any, Callable, Final, Generator, Protocol

from .models.cache import ProjectCache

try:
    from sfi.pyprojectparse.models.project import Project
except ImportError:
    raise RuntimeError("Import sfi.pyprojectparse failed!")

# Platform detection
IS_WINDOWS: Final[bool] = sys.platform == "win32"

# File and directory constants
CONFIG_DIR_NAME: Final[str] = ".pysfi"
CONFIG_FILE_NAME: Final[str] = "makepython.json"
PYPROJECT_FILE_NAME: Final[str] = "pyproject.toml"

# Default constants
DEFAULT_TIMEOUT_SECONDS: Final[int] = 300
DEFAULT_MAX_RETRIES: Final[int] = 3
DEFAULT_ENCODING: Final[str] = "utf-8"
DEFAULT_SHELL_CONFIG: Final[str] = ".bashrc"
DEFAULT_CACHE_SIZE: Final[int] = 100
DEFAULT_CACHE_EXPIRY: Final[int] = 300  # 5 minutes

# 延迟导入的模块（仅在需要时导入）
# argparse - 仅在main()中使用
# json - 仅在配置相关函数中使用
# 这些模块的导入已经放在文件顶部，因为它们在多个地方使用

# Clean command template
CLEAN_COMMANDS: Final[list[str]] = ["rm", "-rf"]

# Logging setup
LOG_FORMAT: Final[str] = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
LOG_DATE_FORMAT: Final[str] = "%Y-%m-%d %H:%M:%S"

logging.basicConfig(level=logging.INFO, format=LOG_FORMAT, datefmt=LOG_DATE_FORMAT)
logger = logging.getLogger(__name__)
CURRENT_WORKING_DIR: Final[Path] = Path.cwd()

CONFIG_FILE: Final[Path] = Path.home() / CONFIG_DIR_NAME / CONFIG_FILE_NAME


@lru_cache(maxsize=128)
def _is_tool_available(tool_name: str) -> bool:
    """检查构建工具是否可用，使用LRU缓存.

    Args:
        tool_name: 工具名称

    Returns:
        工具是否可用
    """
    return bool(shutil.which(tool_name))


class ToolAvailabilityCache:
    """构建工具可用性缓存，避免重复调用shutil.which."""

    def __init__(self):
        self._cache: dict[str, bool] = {}
        self._last_check: dict[str, float] = {}
        self.ttl_seconds = 60  # 1分钟缓存

    def is_available(self, tool_name: str) -> bool:
        """检查工具是否可用，带缓存.

        Args:
            tool_name: 工具名称

        Returns:
            工具是否可用
        """
        current_time = time.time()

        # 检查缓存是否存在且未过期
        if (
            tool_name in self._cache
            and current_time - self._last_check[tool_name] < self.ttl_seconds
        ):
            return self._cache[tool_name]

        # 缓存未命中或已过期，重新检查
        is_avail = _is_tool_available(tool_name)
        self._cache[tool_name] = is_avail
        self._last_check[tool_name] = current_time

        return is_avail

    def invalidate(self, tool_name: str) -> None:
        """使指定工具的缓存失效.

        Args:
            tool_name: 工具名称
        """
        if tool_name in self._cache:
            del self._cache[tool_name]
        if tool_name in self._last_check:
            del self._last_check[tool_name]

    def clear(self) -> None:
        """清空所有缓存."""
        self._cache.clear()
        self._last_check.clear()


# 全局工具缓存实例
_tool_cache = ToolAvailabilityCache()


class ConfigCache:
    """配置文件缓存，减少磁盘I/O操作."""

    def __init__(self):
        self._cache: dict[Path, tuple[dict, float]] = {}
        self.ttl_seconds = 30  # 30秒缓存

    def get(self, config_file: Path) -> dict | None:
        """获取缓存的配置数据.

        Args:
            config_file: 配置文件路径

        Returns:
            配置数据字典，如果缓存未命中或过期则返回None
        """
        if config_file not in self._cache:
            return None

        config_data, timestamp = self._cache[config_file]
        if time.time() - timestamp > self.ttl_seconds:
            del self._cache[config_file]
            return None

        return config_data

    def put(self, config_file: Path, config_data: dict) -> None:
        """存储配置数据到缓存.

        Args:
            config_file: 配置文件路径
            config_data: 配置数据字典
        """
        self._cache[config_file] = (config_data, time.time())

    def clear(self) -> None:
        """清空配置缓存."""
        self._cache.clear()


# 全局配置缓存实例
_config_cache = ConfigCache()


class PerformanceMonitor:
    """Monitor and report performance metrics for makepython operations."""

    def __init__(self):
        self.metrics: dict[str, list[float]] = {}
        self.start_times: dict[str, float] = {}

    def start_operation(self, operation_name: str) -> None:
        """Start timing an operation."""
        self.start_times[operation_name] = time.time()
        logger.debug(f"Started monitoring: {operation_name}")

    def end_operation(self, operation_name: str) -> float:
        """End timing an operation and record the duration."""
        if operation_name in self.start_times:
            duration = time.time() - self.start_times[operation_name]
            if operation_name not in self.metrics:
                self.metrics[operation_name] = []
            self.metrics[operation_name].append(duration)
            del self.start_times[operation_name]
            logger.debug(f"Completed {operation_name} in {_format_duration(duration)}")
            return duration
        return 0.0

    def get_stats(self, operation_name: str) -> dict[str, float] | None:
        """Get performance statistics for an operation."""
        if operation_name not in self.metrics or not self.metrics[operation_name]:
            return None

        durations = self.metrics[operation_name]
        return {
            "count": len(durations),
            "total": sum(durations),
            "average": sum(durations) / len(durations),
            "min": min(durations),
            "max": max(durations),
            "last": durations[-1],
        }

    def print_summary(self) -> None:
        """Print performance summary."""
        if not self.metrics:
            return

        print("\n📊 Performance Summary")
        print("=" * 35)
        for operation, _durations in self.metrics.items():
            stats = self.get_stats(operation)
            if stats:
                print(
                    f"{operation:20} | Avg: {_format_duration(stats['average']):>8} | "
                    f"Count: {stats['count']:>3} | Last: {_format_duration(stats['last']):>8}"
                )
        print("=" * 35)


# Global performance monitor instance
_perf_monitor = PerformanceMonitor()

# 全局项目缓存实例
_project_cache = ProjectCache()


class MakePythonError(Exception):
    """Base exception class for MakePython tool errors."""

    def __init__(self, message: str, error_code: int = 1):
        super().__init__(message)
        self.message = message
        self.error_code = error_code


class UserFacingError(MakePythonError):
    """Raised for errors that should be presented to the user in a friendly way."""

    def __init__(self, message: str, suggestion: str = "", error_code: int = 1):
        super().__init__(message, error_code)
        self.suggestion = suggestion


def _handle_user_error(error: UserFacingError) -> None:
    """Handle user-facing errors with friendly messaging."""
    logger.error(f"❌ {error.message}")
    if error.suggestion:
        logger.info(f"💡 Suggestion: {error.suggestion}")


def _show_project_summary() -> None:
    """Show comprehensive project summary including type detection."""
    project = _get_project_info()
    if not project:
        logger.warning("No pyproject.toml found in current directory")
        return

    print("\n📋 Project Summary")
    print("=" * 30)
    print(f"Name:        {project.name}")
    print(f"Version:     {project.version}")
    print(f"Type:        {project.project_type} {project.project_type_emoji}")
    print(f"Description: {project.description or 'No description'}")
    print(f"Python:      {project.requires_python or 'Any'}")

    if project.dependencies:
        print(f"Dependencies: {len(project.dependencies)} packages")
        if project.has_qt:
            print(f"Qt Framework: {project.qt_libname}")

    print("=" * 30)


class ConfigurationError(MakePythonError):
    """Raised when configuration loading or saving fails."""

    pass


class BuildToolNotFoundError(MakePythonError):
    """Raised when no suitable build tool is found."""

    pass


class TokenConfigurationError(MakePythonError):
    """Raised when PyPI token configuration fails."""

    pass


class CommandExecutionError(MakePythonError):
    """Raised when command execution fails."""

    def __init__(
        self, message: str, return_code: int = 1, stdout: str = "", stderr: str = ""
    ):
        super().__init__(message, return_code)
        self.stdout = stdout
        self.stderr = stderr


class BuildTool(Enum):
    """Enumeration of supported build tools.

    Attributes:
        UV: The uv build tool
        POETRY: The poetry build tool
        HATCH: The hatch build tool
    """

    UV = "uv"
    POETRY = "poetry"
    HATCH = "hatch"


class CommandType(Enum):
    """Enumeration of command types.

    Attributes:
        BUILD: Build project command
        CLEAN: Clean build artifacts command
        TEST: Run tests command
        PUBLISH: Publish package command
        BUMP_VERSION: Bump version command
        TOKEN: Manage PyPI token command
    """

    BUILD = "build"
    CLEAN = "clean"
    TEST = "test"
    PUBLISH = "publish"
    BUMP_VERSION = "bumpversion"
    TOKEN = "token"


@dataclass
class MakePythonConfig:
    """Configuration for MakePython tool.

    Manages the configuration settings for the MakePython tool including
    default build tools, verbosity settings, and retry policies.

    Attributes:
        default_build_tool: Default build tool to use
        auto_detect_tool: Whether to automatically detect build tools
        verbose_output: Enable verbose output
        max_retries: Maximum number of retries for failed operations
        timeout_seconds: Timeout for command execution in seconds
    """

    default_build_tool: BuildTool = BuildTool.UV
    auto_detect_tool: bool = True
    verbose_output: bool = False
    max_retries: int = DEFAULT_MAX_RETRIES
    timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS

    def __post_init__(self) -> None:
        """Initialize configuration after creation."""
        self._load_from_file()
        atexit.register(self.save)

    def _load_from_file(self) -> None:
        """从文件加载配置（如果存在）.

        如果配置文件不存在或无效，则静默继续.
        无效的配置将使用默认值.
        """
        if not CONFIG_FILE.exists():
            return

        # 首先尝试从缓存获取
        cached_config = _config_cache.get(CONFIG_FILE)
        if cached_config is not None:
            logger.debug("使用缓存的配置数据")
            config_data = cached_config
        else:
            try:
                config_data = json.loads(
                    CONFIG_FILE.read_text(encoding=DEFAULT_ENCODING)
                )
                # 缓存配置数据
                _config_cache.put(CONFIG_FILE, config_data)
                logger.debug("从文件读取并缓存配置数据")
            except (OSError, json.JSONDecodeError) as e:
                logger.warning(f"无法从 {CONFIG_FILE} 加载配置: {e}")
                return
            except Exception as e:
                logger.error(f"加载配置时出现意外错误: {e}")
                return

        # 应用配置
        for key, value in config_data.items():
            if hasattr(self, key):
                if key == "default_build_tool":
                    try:
                        setattr(self, key, BuildTool(value))
                    except ValueError:
                        logger.warning(f"无效的构建工具值 '{value}'，使用默认值")
                        setattr(self, key, self.default_build_tool)
                else:
                    setattr(self, key, value)

    def save(self) -> None:
        """Save current configuration to file.

        Creates parent directories if they don't exist.

        Raises:
            ConfigurationError: If saving configuration fails
        """
        try:
            CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
            config_dict = {
                "default_build_tool": self.default_build_tool.value,
                "auto_detect_tool": self.auto_detect_tool,
                "verbose_output": self.verbose_output,
                "max_retries": self.max_retries,
                "timeout_seconds": self.timeout_seconds,
            }
            CONFIG_FILE.write_text(
                json.dumps(config_dict, indent=4), encoding=DEFAULT_ENCODING
            )
            logger.debug(f"Configuration saved to {CONFIG_FILE}")
        except OSError as e:
            logger.error(f"Failed to save configuration: {e}")
            raise ConfigurationError(f"Failed to save configuration: {e}") from e
        except Exception as e:
            logger.error(f"Unexpected error saving configuration: {e}")
            raise ConfigurationError(
                f"Unexpected error saving configuration: {e}"
            ) from e

    @cached_property
    def supported_tools(self) -> list[BuildTool]:
        """获取可用的构建工具列表，使用缓存优化性能.

        使用缓存避免重复的系统调用以检测工具可用性.

        Returns:
            可用的BuildTool枚举列表
        """
        available_tools = []
        for tool in BuildTool:
            if _tool_cache.is_available(tool.value):
                available_tools.append(tool)
        return available_tools


class CommandExecutor(Protocol):
    """Protocol for command execution.

    Defines the interface for executing commands in a consistent manner.
    Implementations should handle subprocess execution with proper error handling.
    """

    def execute(
        self, command: list[str], cwd: Path
    ) -> subprocess.CompletedProcess[str]:
        """Execute a command in the specified directory.

        Args:
            command: Command to execute as list of arguments
            cwd: Working directory for command execution

        Returns:
            CompletedProcess object with command results

        Raises:
            subprocess.CalledProcessError: If command execution fails
            subprocess.TimeoutExpired: If command times out
        """
        ...


class SubprocessExecutor:
    """Execute commands using subprocess.

    Provides robust subprocess execution with configurable timeouts,
    proper error handling, and encoding support.

    Attributes:
        config: MakePython configuration object
    """

    def __init__(self, config: MakePythonConfig):
        """Initialize the executor with configuration.

        Args:
            config: MakePython configuration object
        """
        self.config = config

    def execute(
        self, command: list[str], cwd: Path
    ) -> subprocess.CompletedProcess[str]:
        """Execute command with proper error handling.

        Args:
            command: Command to execute as list of arguments
            cwd: Working directory for command execution

        Returns:
            CompletedProcess object with command results

        Raises:
            CommandExecutionError: For command execution failures
            subprocess.TimeoutExpired: If command times out
        """
        logger.debug(f"Executing command: {' '.join(command)} in {cwd}")

        try:
            return _execute_command(
                command,
                cwd,
                check=True,
                capture=True,
                timeout=self.config.timeout_seconds,
                fallback_encoding=True,
            )
        except subprocess.CalledProcessError as e:
            raise CommandExecutionError(
                f"Command failed: {' '.join(command)}",
                return_code=e.returncode,
                stdout=e.stdout or "",
                stderr=e.stderr or "",
            ) from e
        except Exception as e:
            raise CommandExecutionError(
                f"Unexpected error executing command: {e}"
            ) from e


def _get_optimized_build_command(
    directory: Path, config: MakePythonConfig
) -> str | None:
    """Get optimized build command based on project type and characteristics.

    Args:
        directory: Directory to analyze
        config: MakePython configuration

    Returns:
        Optimized build command string
    """
    # Get comprehensive project info
    project = _get_project_info()

    # Get base build command
    base_command = _get_build_command(directory, config)
    if not base_command:
        return None

    # Apply project-type-specific optimizations
    if project:
        logger.info(
            f"Detected project type: {project.project_type} {project.project_type_emoji}"
        )

        # GUI applications might need special handling
        if project.project_type == "窗口应用":
            logger.debug("Optimizing build for GUI application")
            # Could add GUI-specific build flags here

        # CLI tools might benefit from different optimization
        elif project.project_type == "控制台应用":
            logger.debug("Optimizing build for CLI application")
            # Could add CLI-specific optimizations

        # Libraries might need different approach
        elif project.project_type == "库/模块":
            logger.debug("Optimizing build for library/module")
            # Could optimize for library distribution

    return base_command


def _get_build_command(directory: Path, config: MakePythonConfig) -> str | None:
    """获取目录的构建命令，使用缓存优化性能.

    Args:
        directory: 要搜索项目配置的目录
        config: MakePython配置对象

    Returns:
        构建命令字符串，如果未找到则返回None

    Raises:
        BuildToolNotFoundError: 如果找不到合适的构建工具
    """
    pyproject_path = directory / PYPROJECT_FILE_NAME
    project = None

    # 尝试从缓存或文件获取项目信息
    if pyproject_path.exists():
        # 首先尝试从缓存获取
        cached_project = _project_cache.get(pyproject_path)
        if cached_project:
            logger.debug(f"使用缓存的项目信息: {cached_project.name}")
            project = cached_project
        else:
            # 缓存未命中，解析项目文件
            project = Project.from_toml_file(pyproject_path)
            if not project or not project.name:
                logger.error(f"无法解析 {directory} 中的 pyproject.toml")
                project = None
            else:
                # 存储到缓存
                _project_cache.put(pyproject_path, project)
                logger.debug(f"缓存项目信息: {project.name}")

    # 如果有项目信息，从构建系统部分检测构建工具
    build_tool = None
    if project and project.build_backend:
        backend = project.build_backend
        if backend.startswith("poetry."):
            build_tool = BuildTool.POETRY
        elif backend.startswith("hatchling."):
            build_tool = BuildTool.HATCH
        else:
            build_tool = BuildTool.UV

    if build_tool:
        logger.debug(f"检测到构建工具: {build_tool.value}")
        return build_tool.value

    # 回退到可用的工具
    if config.supported_tools:
        selected_tool = config.supported_tools[0]
        logger.debug(f"使用可用的构建工具: {selected_tool.value}")
        return selected_tool.value

    logger.error(f"在 {directory} 中未找到构建命令")
    logger.error("请安装 uv, poetry, 或 hatch")
    raise BuildToolNotFoundError(
        f"未找到构建工具。请在 {directory} 中安装 uv, poetry, 或 hatch"
    )


@dataclass
class Command:
    """Represents a command that can be executed."""

    name: str
    alias: str
    command_type: CommandType
    cmds: list[str] | Callable[..., Any] | None = None
    description: str = ""


def _show_progress(
    message: str, current: int | None = None, total: int | None = None
) -> None:
    """显示进度信息.

    Args:
        message: 进度消息
        current: 当前进度值
        total: 总进度值
    """
    if current is not None and total is not None:
        percentage = (current / total) * 100
        logger.info(f"{message} [{current}/{total}] ({percentage:.1f}%)")
    else:
        logger.info(message)


def _format_duration(seconds: float) -> str:
    """格式化持续时间.

    Args:
        seconds: 秒数

    Returns:
        格式化的时间字符串
    """
    if seconds < 1:
        return f"{seconds * 1000:.0f}ms"
    elif seconds < 60:
        return f"{seconds:.1f}s"
    else:
        minutes = int(seconds // 60)
        secs = seconds % 60
        return f"{minutes}m {secs:.1f}s"


def _validate_token_format(token: str) -> tuple[bool, str]:
    """验证PyPI令牌格式.

    Args:
        token: 要验证的令牌字符串

    Returns:
        (是否有效, 消息)的元组
    """
    if not token:
        return False, "令牌不能为空"

    if len(token) < 10:
        return False, "令牌似乎太短，可能无效"

    # 基本格式验证
    if not (token.startswith("pypi-") or token.startswith("PYPI-")):
        return True, "警告: 令牌不以 'pypi-' 开头 (但仍可能有效)"

    return True, "令牌格式看起来有效"
    """Validate PyPI token format.

    Args:
        token: Token string to validate

    Returns:
        Tuple of (is_valid, message)
    """
    if not token:
        return False, "Token cannot be empty"

    if len(token) < 10:
        return False, "Token appears too short to be valid"

    # Basic format validation
    if not (token.startswith("pypi-") or token.startswith("PYPI-")):
        return True, "Warning: Token doesn't start with 'pypi-' (may still be valid)"

    return True, "Token format appears valid"


def _show_detailed_help(commands: list[Command]) -> None:
    """Show detailed command descriptions and usage information.

    Args:
        commands: List of available commands
    """
    print("\nMakePython - Detailed Command Help")
    print("=" * 40)
    print(f"Version: {__version__}")
    print(f"Author: {__author__}")
    print("\nAvailable Commands:")
    print("-" * 20)

    # Group commands by type for better organization
    command_groups = {}
    for command in commands:
        group = command.command_type.value
        if group not in command_groups:
            command_groups[group] = []
        command_groups[group].append(command)

    for group_name, group_commands in command_groups.items():
        print(f"\n{group_name.upper()} COMMANDS:")
        for command in group_commands:
            aliases = f" ({command.alias})" if command.alias != command.name else ""
            print(f"  {command.name}{aliases:<8} - {command.description}")

    print("\nUSAGE EXAMPLES:")
    print("  makepython build          # Build the project")
    print("  makepython test           # Run tests")
    print("  makepython publish        # Publish to PyPI")
    print("  makepython token          # Configure PyPI token")
    print("  makepython clean          # Clean build artifacts")

    print("\nOPTIONS:")
    print("  --debug, -d              Enable debug output")
    print("  --verbose, -v            Enable verbose output")
    print("  --quiet, -q              Suppress most messages")
    print("  --version                Show version information")
    print("  --help-commands          Show this detailed help")

    print("\nFor more information, visit: https://github.com/pysfi/makepython")


def _clean(root_dir: Path = CURRENT_WORKING_DIR) -> None:
    """Clean build artifacts and temporary files.

    Args:
        root_dir: Root directory to clean
    """
    logger.info(f"Cleaning build artifacts in {root_dir}")

    # Clean common build directories
    targets = ["dist", "build", "*.egg-info"]
    cleaned_count = 0
    failed_count = 0

    for target in targets:
        try:
            _execute_command([*CLEAN_COMMANDS, target], root_dir, check=False)
            cleaned_count += 1
        except Exception as e:
            logger.warning(f"Failed to clean {target}: {e}")
            failed_count += 1
            continue

    logger.info(
        f"Clean operation completed: {cleaned_count} succeeded, {failed_count} failed"
    )


def _dispatch_command(command: Command, *, realtime: bool = False) -> None:
    """Execute a command based on its type.

    Args:
        command: Command object to execute
        realtime: Whether to stream output in real-time
    """
    if callable(command.cmds):
        command.cmds()
    elif isinstance(command.cmds, list):
        # Use realtime output for test commands or if explicitly requested
        use_realtime = realtime or command.command_type == CommandType.TEST
        _run_command(command.cmds, CURRENT_WORKING_DIR, realtime=use_realtime)
    else:
        logger.debug("No preset commands found for this command type")


def _handle_publish_command(build_command: str) -> None:
    """Handle publish command with token validation.

    Args:
        build_command: Build command string
    """
    if not _check_pypi_token(build_command):
        logger.warning("PyPI token not configured. Starting configuration process...")
        _set_token(build_command)

    # For hatch, we need to call 'hatch publish' directly
    # For other tools, we can use the build_command approach
    if build_command == "hatch":
        _run_command(["hatch", "publish"], CURRENT_WORKING_DIR)
    else:
        _run_command([build_command, "publish"], CURRENT_WORKING_DIR)


def main() -> None:
    """Main entry point for MakePython tool.

    Parses command line arguments, loads configuration, detects build tools,
    and executes the requested command.

    Raises:
        SystemExit: If command execution fails or unknown command is provided
    """
    try:
        # Initialize configuration
        config = MakePythonConfig()

        # Get build command
        try:
            build_command = _get_build_command(CURRENT_WORKING_DIR, config) or ""
        except BuildToolNotFoundError as e:
            logger.error(str(e))
            sys.exit(1)

        # Create commands with proper structure
        commands = [
            Command(
                name="activate",
                alias="a",
                command_type=CommandType.BUILD,
                cmds=_activate_py_env,
                description="Activate virtual environment",
            ),
            Command(
                name="build",
                alias="b",
                command_type=CommandType.BUILD,
                cmds=[build_command, "build"],
                description="Build the project package",
            ),
            Command(
                name="bumpversion",
                alias="bump",
                command_type=CommandType.BUMP_VERSION,
                cmds=lambda: _bump_version("patch"),
                description="Bump project version (patch/minor/major)",
            ),
            Command(
                name="bump-publish",
                alias="bp",
                command_type=CommandType.PUBLISH,
                cmds=_bump_publish,
                description="Bump version and publish project",
            ),
            Command(
                name="clean",
                alias="c",
                command_type=CommandType.CLEAN,
                cmds=_clean,
                description="Clean build artifacts and temporary files",
            ),
            Command(
                name="publish",
                alias="p",
                command_type=CommandType.PUBLISH,
                cmds=_publish_with_cleanup,
                description="Publish package to PyPI with cleanup and git operations",
            ),
            Command(
                name="test",
                alias="t",
                command_type=CommandType.TEST,
                cmds=["pytest", "-n", "8"],
                description="Run tests with parallel execution",
            ),
            Command(
                name="test",
                alias="td",
                command_type=CommandType.TEST,
                cmds=["pytest"],
                description="Run tests with default execution",
            ),
            Command(
                name="test-benchmark",
                alias="tb",
                command_type=CommandType.TEST,
                cmds=["pytest", "-m", "benchmark", "-n", "8"],
                description="Run benchmark tests specifically",
            ),
            Command(
                name="test-coverage",
                alias="tc",
                command_type=CommandType.TEST,
                cmds=lambda: _generate_coverage_report(False),
                description="Run tests with coverage reporting",
            ),
            Command(
                name="test-coverage-slow",
                alias="tcs",
                command_type=CommandType.TEST,
                cmds=lambda: _generate_coverage_report(True),
                description="Run tests with coverage reporting (slow mode)",
            ),
            Command(
                name="doc",
                alias="d",
                command_type=CommandType.BUILD,
                cmds=_generate_documentation,
                description="Generate Sphinx HTML documentation including API docs",
            ),
            Command(
                name="init",
                alias="i",
                command_type=CommandType.BUILD,
                cmds=_initialize_project,
                description="Initialize project with git and pre-commit hooks",
            ),
            Command(
                name="token",
                alias="tk",
                command_type=CommandType.TOKEN,
                cmds=lambda: _set_token(build_command),
                description="Configure PyPI token for publishing",
            ),
            Command(
                name="lint",
                alias="l",
                command_type=CommandType.BUILD,
                cmds=_lint_code,
                description="Lint code using Ruff with auto-fix",
            ),
            Command(
                name="update",
                alias="u",
                command_type=CommandType.BUILD,
                cmds=update_build_date,
                description="Update build dates in __init__.py files",
            ),
            Command(
                name="dist",
                alias="d",
                command_type=CommandType.BUILD,
                cmds=_generate_distribution,
                description="Generate distribution package with complete workflow",
            ),
        ]
        command_dict = {command.name: command for command in commands}
        command_dict.update({command.alias: command for command in commands})
        choices = [command.alias for command in commands]
        choices.extend([command.name for command in commands])

        # Parse arguments
        parser = argparse.ArgumentParser(
            prog="makepython",
            description="Automated Python project building and management tool",
            epilog="""Examples:
  makepython build           Build the project
  makepython test            Run tests with real-time output (default)
  makepython test -r         Run tests with real-time output (explicit)
  makepython publish          Publish to PyPI
  makepython clean            Clean build artifacts

Note: Test commands (test, test-benchmark, test-coverage) automatically use real-time output.
Use --realtime/-r for other commands if you want to see output as it happens.""",
            formatter_class=argparse.RawDescriptionHelpFormatter,
        )

        # Add argument groups for better organization
        info_group = parser.add_argument_group("Information Options")
        info_group.add_argument(
            "--version",
            action="version",
            version=f"MakePython v{__version__}",
            help="Show version information and exit",
        )
        # Add info command for project summary
        info_group.add_argument(
            "--info",
            "-i",
            action="store_true",
            help="Show detailed project information and exit",
        )
        info_group.add_argument(
            "--stats",
            action="store_true",
            help="Show performance statistics after execution",
        )

        config_group = parser.add_argument_group("Configuration Options")
        config_group.add_argument(
            "--config-path", type=Path, help="Specify custom configuration file path"
        )
        config_group.add_argument(
            "--build-tool",
            choices=[tool.value for tool in BuildTool],
            help="Force specific build tool (uv, poetry, hatch)",
        )

        logging_group = parser.add_argument_group("Logging Options")
        logging_group.add_argument(
            "--debug",
            "-d",
            action="store_true",
            help="Enable debug mode with verbose output",
        )
        logging_group.add_argument(
            "--verbose", "-v", action="store_true", help="Enable verbose output"
        )
        logging_group.add_argument(
            "--quiet", "-q", action="store_true", help="Suppress most output messages"
        )

        output_group = parser.add_argument_group("Output Options")
        output_group.add_argument(
            "--realtime",
            "-r",
            action="store_true",
            help="Stream command output in real-time (useful for tests)",
        )

        parser.add_argument(
            "command",
            nargs="?",
            type=str,
            choices=choices,
            help=f"Command to run. Available: {', '.join(choices)}",
        )

        args = parser.parse_args()

        # Handle info option
        if hasattr(args, "info") and args.info:
            _show_project_summary()
            return

        # Handle help commands option
        if hasattr(args, "help_commands") and args.help_commands:
            _show_detailed_help(commands)
            return

        # Validate that a command was provided
        if not args.command:
            parser.print_help()
            logger.error("No command specified. Use --help for usage information.")
            sys.exit(1)

        # Set logging level
        if args.quiet:
            logger.setLevel(logging.ERROR)
            config.verbose_output = False
        elif args.debug:
            logger.setLevel(logging.DEBUG)
            config.verbose_output = True
        elif args.verbose:
            logger.setLevel(logging.INFO)
            config.verbose_output = True

        logger.debug(f"Using build command: {build_command}")
        logger.debug(f"Working directory: {CURRENT_WORKING_DIR}")

        # Execute command
        command = command_dict.get(args.command)
        if command:
            # Start performance monitoring
            _perf_monitor.start_operation(command.name)

            logger.info(f"Executing command: {command.name} ({command.description})")
            _dispatch_command(command, realtime=args.realtime)

            # End performance monitoring
            duration = _perf_monitor.end_operation(command.name)
            logger.info(f"✅ Command completed in {_format_duration(duration)}")
        else:
            logger.error(f"Unknown command: {args.command}")
            sys.exit(1)

        # Show performance stats if requested
        if hasattr(args, "stats") and args.stats:
            _perf_monitor.print_summary()

        # Special handling for publish command
        if args.command in {"publish", "p"}:
            _handle_publish_command(build_command)

    except KeyboardInterrupt:
        logger.info("Operation cancelled by user")
        sys.exit(130)  # Standard exit code for Ctrl+C
    except MakePythonError as e:
        logger.error(f"MakePython error: {e.message}")
        sys.exit(e.error_code)
    except Exception as e:
        logger.error(f"Unexpected error: {e}")
        sys.exit(1)


def _set_token(build_command: str, show_header: bool = True) -> None:
    """Set PyPI token for the specified build command.

    Args:
        build_command: The build tool to configure (uv, poetry, hatch)
        show_header: Whether to show the header message

    Raises:
        TokenConfigurationError: If unknown build command is provided or token setting fails
    """
    if show_header:
        logger.info(f"Setting PyPI token for {build_command}...")
        logger.info(
            "Note: Your token will be stored securely in the appropriate configuration files."
        )

    if build_command.lower() not in [tool.value for tool in BuildTool]:
        error_msg = f"Unknown build command: {build_command}. Please use one of: {[tool.value for tool in BuildTool]}"
        logger.error(error_msg)
        raise TokenConfigurationError(error_msg)

    token = input("Enter your PyPI token (leave empty to cancel): ").strip()
    if not token:
        logger.info("Operation cancelled - no token provided.")
        return

    # Validate token format
    is_valid, message = _validate_token_format(token)
    if not is_valid:
        logger.error(f"Invalid token: {message}")
        raise TokenConfigurationError(message)
    elif "Warning" in message:
        logger.warning(message)

    try:
        build_tool = BuildTool(build_command)
        _set_build_tool_token(token, build_tool)
        logger.info("PyPI token configured successfully!")
        logger.info("You can now use 'makepython publish' to publish your package.")
    except Exception as e:
        error_msg = f"Failed to set token: {e}"
        logger.error(error_msg)
        raise TokenConfigurationError(error_msg) from e


def _save_token_to_file(filepath: Path, content: str, description: str) -> None:
    """Save token content to file with proper error handling.

    Args:
        filepath: Path to save the file
        content: Content to write
        description: Description for logging

    Raises:
        OSError: If file operation fails
    """
    try:
        filepath.parent.mkdir(parents=True, exist_ok=True)
        filepath.write_text(content, encoding=DEFAULT_ENCODING)
        logger.info(f"Token saved to {filepath} ({description})")
    except OSError as e:
        logger.error(f"Failed to save token to {filepath}: {e}")
        raise


def _set_build_tool_token(token: str, build_tool: BuildTool) -> None:
    """Set PyPI token for the specified build tool.

    Args:
        token: PyPI token to set
        build_tool: Build tool to configure

    Raises:
        TokenConfigurationError: If token setting fails
    """
    try:
        if build_tool == BuildTool.UV:
            # Set UV environment variable and config file
            _write_to_env_file("UV_PUBLISH_TOKEN", token)
            config_content = f"""[publish]
token = "{token}"
"""
            config_path = Path.home() / ".config" / "uv" / "uv.toml"
            _save_token_to_file(config_path, config_content, "UV config")

        elif build_tool == BuildTool.POETRY:
            # Set Poetry environment variable and config
            _write_to_env_file("POETRY_PYPI_TOKEN_PYPI", token)
            _run_command(
                ["poetry", "config", "pypi-token.pypi", token], CURRENT_WORKING_DIR
            )
            logger.debug("Token saved to Poetry configuration.")

        elif build_tool == BuildTool.HATCH:
            # Write to .pypirc
            pypirc_content = f"""[pypi]
repository = https://upload.pypi.org/legacy/
username = __token__
password = {token}
"""
            pypirc_path = Path.home() / ".pypirc"
            _save_token_to_file(pypirc_path, pypirc_content, "PyPI RC")
    except Exception as e:
        raise TokenConfigurationError(
            f"Failed to set token for {build_tool.value}: {e}"
        ) from e


def _check_uv_token() -> bool:
    """Check if UV PyPI token is configured."""
    # Check environment variables
    token_env_vars = ["UV_PUBLISH_TOKEN", "PYPI_API_TOKEN"]
    for var in token_env_vars:
        if os.getenv(var):
            logger.debug(f"Found PyPI token in environment variable: {var}")
            return True

    # Check config file
    config_path = Path.home() / ".config" / "uv" / "uv.toml"
    if config_path.exists():
        logger.debug(f"Found uv config file: {config_path}")
        return True

    return False


def _check_poetry_token() -> bool:
    """Check if Poetry PyPI token is configured."""
    # Check environment variable
    if os.getenv("POETRY_PYPI_TOKEN_PYPI"):
        logger.debug("Found PyPI token in POETRY_PYPI_TOKEN_PYPI environment variable")
        return True

    # Check Poetry config
    try:
        result = _execute_command(
            ["poetry", "config", "pypi-token.pypi"],
            CURRENT_WORKING_DIR,
            check=False,
            capture=True,
        )
        if result.stdout.strip() and result.stdout.strip() != "None":
            logger.debug("Found PyPI token in Poetry configuration")
            return True
    except Exception as e:
        logger.debug(f"Error checking Poetry token: {e}")
    return False


def _check_hatch_token() -> bool:
    """Check if Hatch PyPI token is configured."""
    pypirc_path = Path.home() / ".pypirc"
    if pypirc_path.exists():
        logger.debug(f"Found .pypirc file: {pypirc_path}")
        return True
    return False


def _check_build_tool_token(build_tool: BuildTool) -> bool:
    """Check if PyPI token is configured for the specified build tool.

    Args:
        build_tool: Build tool to check

    Returns:
        True if token is found, False otherwise
    """
    check_functions = {
        BuildTool.UV: _check_uv_token,
        BuildTool.POETRY: _check_poetry_token,
        BuildTool.HATCH: _check_hatch_token,
    }

    checker = check_functions.get(build_tool)
    if checker:
        return checker()

    logger.error(f"Unknown build tool for token checking: {build_tool}")
    return False


def _check_pypi_token(build_command: str) -> bool:
    """Check if PyPI token is configured before publishing.

    Args:
        build_command: Build tool name (uv, poetry, hatch)

    Returns:
        True if token is configured, False otherwise
    """
    logger.info("Checking PyPI token configuration...")

    try:
        build_tool = BuildTool(build_command.lower())
        return _check_build_tool_token(build_tool)
    except ValueError:
        logger.error(f"Unknown build command for token checking: {build_command}")
        return False


def _execute_command(
    cmd: list[str],
    directory: Path,
    *,
    check: bool = True,
    capture: bool = True,
    timeout: int | None = None,
    fallback_encoding: bool = True,
    realtime: bool = False,
) -> subprocess.CompletedProcess[str]:
    """Execute command with comprehensive error handling.

    Args:
        cmd: Command to execute as list of arguments
        directory: Working directory
        check: Whether to raise CalledProcessError on non-zero exit
        capture: Whether to capture stdout/stderr (must be False for realtime)
        timeout: Timeout in seconds (None for no timeout)
        fallback_encoding: Whether to try fallback encoding on Unicode errors
        realtime: Whether to stream output in real-time (overrides capture)

    Returns:
        CompletedProcess object with command results

    Raises:
        SystemExit: If command fails to execute and check=True
        subprocess.CalledProcessError: If command fails and check=True
        subprocess.TimeoutExpired: If command times out
    """
    logger.debug(f"Running command: {' '.join(cmd)} in {directory}")

    # If realtime output is requested, disable capture
    if realtime:
        capture = False

    def _run_with_encoding(encoding: str) -> subprocess.CompletedProcess[str]:
        """Run command with specified encoding."""
        return subprocess.run(
            cmd,
            cwd=directory,
            capture_output=capture,
            text=capture,
            encoding=encoding if capture else None,
            errors="replace" if capture else None,
            check=check,
            timeout=timeout,
        )

    try:
        result = _run_with_encoding(DEFAULT_ENCODING)
        if result.stdout and capture:
            print(result.stdout)
        if result.stderr and capture:
            print(result.stderr, file=sys.stderr)
        return result

    except subprocess.CalledProcessError as e:
        logger.error(f"Command failed with exit code {e.returncode}: {' '.join(cmd)}")
        if e.stdout:
            logger.error(f"STDOUT: {e.stdout}")
        if e.stderr:
            logger.error(f"STDERR: {e.stderr}")
        raise

    except subprocess.TimeoutExpired:
        logger.error(f"Command timed out: {' '.join(cmd)}")
        raise

    except UnicodeDecodeError as e:
        if not fallback_encoding or realtime:
            logger.error(f"Encoding error while processing command output: {e}")
            raise

        logger.warning(f"Encoding error, trying fallback encoding: {e}")
        try:
            result = _run_with_encoding("gbk" if IS_WINDOWS else "utf-8")
            if result.stdout and capture:
                print(result.stdout)
            if result.stderr and capture:
                print(result.stderr, file=sys.stderr)
            return result
        except Exception as fallback_error:
            logger.error(f"Fallback encoding also failed: {fallback_error}")
            raise

    except Exception as e:
        logger.error(f"Unexpected error executing command {' '.join(cmd)}: {e}")
        raise


def _run_command(cmd: list[str], directory: Path, *, realtime: bool = False) -> None:
    """Run a command in specified directory with proper error handling.

    Args:
        cmd: Command to execute as list of arguments
        directory: Directory to run the command in
        realtime: Whether to stream output in real-time

    Raises:
        SystemExit: If command fails to execute
        ValueError: If inputs are invalid
    """
    # Validate inputs
    if not directory.exists() or not directory.is_dir():
        raise ValueError(f"Invalid directory: {directory}")

    if not cmd or not all(isinstance(c, str) for c in cmd):
        raise ValueError("Invalid command list")

    try:
        _execute_command(cmd, directory, check=True, realtime=realtime)
    except subprocess.CalledProcessError as e:
        sys.exit(e.returncode)
    except subprocess.TimeoutExpired:
        sys.exit(124)  # Standard exit code for timeout
    except Exception as e:
        logger.error(f"Command execution failed: {e}")
        sys.exit(1)


def _write_to_env_file(key: str, value: str) -> None:
    """Write key-value pair to environment file.

    Sets environment variables either through system utilities (Windows)
    or shell configuration files (Unix-like systems).

    Args:
        key: Environment variable name
        value: Environment variable value
    """
    if IS_WINDOWS:
        try:
            _execute_command(
                ["setx", key, value],
                CURRENT_WORKING_DIR,
                check=True,
                capture=False,
            )
            logger.info(f"Environment variable {key} set successfully")
        except Exception as e:
            logger.warning(f"Failed to set environment variable {key}: {e}")
            logger.info("You may need to restart your shell for changes to take effect")
    else:
        _write_to_shell_config(f"export {key}='{value}'")


def _get_shell_config_path() -> Path:
    """Get the appropriate shell config file based on the current shell.

    Detects the user's shell and returns the corresponding configuration file path.
    Defaults to .bashrc for bash/zsh shells.

    Returns:
        Path to the appropriate shell configuration file
    """
    # Try to detect the shell
    shell = os.getenv("SHELL", "")
    if "zsh" in shell:
        return Path.home() / ".zshrc"
    else:
        # Default to .bashrc
        return Path.home() / DEFAULT_SHELL_CONFIG


def _activate_py_env() -> None:
    """Activate Python virtual environment."""
    venv_path = CURRENT_WORKING_DIR / ".venv"

    if IS_WINDOWS:
        activate_script = venv_path / "Scripts" / "activate.bat"
        if activate_script.exists():
            try:
                _run_command([str(activate_script)], CURRENT_WORKING_DIR)
            except Exception:
                logger.exception("Failed to activate virtual environment")
        else:
            logger.error(
                f"Virtual environment activation script not found: {activate_script}"
            )
    else:
        activate_script = venv_path / "bin" / "activate"
        if activate_script.exists():
            try:
                # For Unix-like systems, we need to source the script
                _run_command(
                    ["source", str(activate_script)], CURRENT_WORKING_DIR, realtime=True
                )
            except Exception:
                logger.exception("Failed to activate virtual environment")
        else:
            logger.error(
                f"Virtual environment activation script not found: {activate_script}"
            )


def _bump_version(version_type: str = "patch") -> None:
    """Bump project version.

    Args:
        version_type: Version type to bump (patch, minor, major)
    """
    valid_types = ["patch", "minor", "major"]
    if version_type not in valid_types:
        logger.error(
            f"Invalid version type: {version_type}. Valid types: {valid_types}"
        )
        return

    # Check which version bumping tool is available
    bump2version_available = _tool_cache.is_available("bump2version")
    bumpversion_available = _tool_cache.is_available("bumpversion")

    logger.debug(f"bump2version available: {bump2version_available}")
    logger.debug(f"bumpversion available: {bumpversion_available}")

    # Prefer bumpversion if available (as it's installed in the system)
    if bumpversion_available:
        try:
            logger.info(f"Executing command: bumpversion {version_type}")
            _run_command(["bumpversion", version_type], CURRENT_WORKING_DIR)
            logger.info(f"Successfully bumped {version_type} version")
            return
        except Exception as e:
            logger.error(f"Failed to bump version with bumpversion: {e}")

    # Fallback to bump2version if bumpversion is not available
    if bump2version_available:
        try:
            logger.info(f"Executing command: bump2version {version_type}")
            _run_command(["bump2version", version_type], CURRENT_WORKING_DIR)
            logger.info(
                f"Successfully bumped {version_type} version (using bump2version)"
            )
            return
        except Exception as e:
            logger.error(f"Failed to bump version with bump2version: {e}")

    # If neither tool is available, suggest installation
    logger.error("No version bumping tool found!")
    logger.info("Please install one of the following:")
    logger.info("  pip install bumpversion    # For bumpversion")
    logger.info("  pip install bump2version   # For bump2version")


def _bump_publish() -> None:
    """Bump version and publish project."""
    try:
        # First bump patch version
        _bump_version("patch")

        # Then build
        build_command = _get_build_command(CURRENT_WORKING_DIR, MakePythonConfig())
        if build_command:
            _run_command([build_command, "build"], CURRENT_WORKING_DIR)

            # Finally publish
            _handle_publish_command(build_command)
    except Exception as e:
        logger.error(f"Bump and publish failed: {e}")


def _publish_with_cleanup() -> None:
    """Publish project with cleanup and git operations."""
    try:
        build_command = _get_build_command(CURRENT_WORKING_DIR, MakePythonConfig())
        if not build_command:
            logger.error("Could not determine build command")
            return

        # Handle publish with token check
        _handle_publish_command(build_command)

        # Clean up
        _clean(CURRENT_WORKING_DIR)

        # Git operations
        try:
            _run_command(["git", "push", "--all"], CURRENT_WORKING_DIR)
        except Exception as e:
            logger.warning(f"Git push failed: {e}")

    except Exception as e:
        logger.error(f"Publish workflow failed: {e}")


def _generate_coverage_report(slow_mode: bool = False) -> None:
    """Generate test coverage report and open in browser.

    Args:
        slow_mode: Whether to run slow tests
    """
    try:
        # Run tests with coverage
        pytest_cmd = ["pytest", "--cov"]
        if slow_mode:
            pytest_cmd.append("--runslow")

        _run_command(pytest_cmd, CURRENT_WORKING_DIR, realtime=True)

        # Generate coverage report
        _run_command(["coverage", "report", "-m"], CURRENT_WORKING_DIR)

        # Generate HTML report
        _run_command(["coverage", "html"], CURRENT_WORKING_DIR)

        # Open in browser
        import webbrowser
        from urllib.request import pathname2url

        html_report = CURRENT_WORKING_DIR / "htmlcov" / "index.html"
        if html_report.exists():
            webbrowser.open("file://" + pathname2url(str(html_report)))
            logger.info("Coverage report opened in browser")
        else:
            logger.warning("HTML coverage report not found")

    except Exception as e:
        logger.error(f"Coverage report generation failed: {e}")


def _get_project_info() -> Project | None:
    """Get comprehensive project information using projectparse.

    Returns:
        Project object with full metadata, or None if not found
    """
    pyproject_path = CURRENT_WORKING_DIR / "pyproject.toml"
    if not pyproject_path.exists():
        return None

    try:
        # Use projectparse for comprehensive project analysis
        project = Project.from_toml_file(pyproject_path)
        if project and project.name:
            logger.debug(f"Project analysis: {project.name} ({project.project_type})")
            logger.debug(
                f"Project type: {project.project_type} {project.project_type_emoji}"
            )
            return project
        return None
    except Exception as e:
        logger.warning(f"Failed to parse project info: {e}")
        return None


def _get_project_name() -> str:
    """Get project name from pyproject.toml using projectparse.

    Returns:
        Project name or empty string if not found
    """
    project = _get_project_info()
    return project.name if project else ""


def _generate_documentation() -> None:
    """Generate Sphinx HTML documentation including API docs."""
    try:
        project_name = _get_project_name()
        if not project_name:
            logger.error("Could not determine project name from pyproject.toml")
            return

        # Clean existing documentation files
        _run_command(["rm", "-f", "./docs/modules.rst"], CURRENT_WORKING_DIR)
        _run_command(["rm", "-f", f"./docs/{project_name}*.rst"], CURRENT_WORKING_DIR)
        _run_command(["rm", "-rf", "./docs/_build"], CURRENT_WORKING_DIR)

        # Generate API documentation
        src_path = f"src/{project_name}"
        if Path(src_path).exists():
            _run_command(["sphinx-apidoc", "-o", "docs", src_path], CURRENT_WORKING_DIR)

        # Build documentation
        _run_command(["sphinx-build", "docs", "docs/_build"], CURRENT_WORKING_DIR)

        # Auto-build with live reloading
        _run_command(
            [
                "sphinx-autobuild",
                "docs",
                "docs/_build/html",
                "--watch",
                ".",
                "--open-browser",
            ],
            CURRENT_WORKING_DIR,
            realtime=True,
        )

    except Exception as e:
        logger.error(f"Documentation generation failed: {e}")


def _initialize_project() -> None:
    """Initialize a new project with git and pre-commit hooks."""
    try:
        # Clean existing files
        _clean(CURRENT_WORKING_DIR)

        # Sync project dependencies
        _run_command(["uv", "sync"], CURRENT_WORKING_DIR)

        # Initialize git repository
        _run_command(["git", "init"], CURRENT_WORKING_DIR)

        # Install pre-commit hooks
        _run_command(["uvx", "pre-commit", "install"], CURRENT_WORKING_DIR)

        logger.info("Project initialized successfully!")

    except Exception as e:
        logger.error(f"Project initialization failed: {e}")


def _lint_code() -> None:
    """Lint code using Ruff with auto-fix."""
    try:
        _run_command(
            ["uvx", "ruff", "check", ".", "--fix", "--unsafe-fixes"],
            CURRENT_WORKING_DIR,
        )
        logger.info("Code linting completed successfully!")
    except Exception as e:
        logger.error(f"Code linting failed: {e}")


def _generate_distribution() -> None:
    """Generate distribution package with complete workflow."""
    try:
        # Clean existing artifacts
        _clean(CURRENT_WORKING_DIR)

        # Build project
        build_command = _get_build_command(CURRENT_WORKING_DIR, MakePythonConfig())
        if build_command:
            _run_command([build_command, "build"], CURRENT_WORKING_DIR)

        # List distribution directory
        if IS_WINDOWS:
            _run_command(["cmd", "/c", "dir", "dist"], CURRENT_WORKING_DIR)
        else:
            _run_command(["ls", "-l", "dist"], CURRENT_WORKING_DIR)

        logger.info("Distribution package generated successfully!")

    except Exception as e:
        logger.error(f"Distribution generation failed: {e}")


# === Build Date Updater Functions (ported from makepythonold) ===


class _BuildDateConfig:
    """Configuration constants for build date updating."""

    SRC_DIR_NAME: Final[str] = "src"
    INIT_FILENAME: Final[str] = "__init__.py"
    BACKUP_SUFFIX: Final[str] = ".bak"
    TEMP_SUFFIX: Final[str] = ".tmp"
    DATE_FORMAT: Final[str] = "%Y-%m-%d"
    DATE_PATTERN: Final[str] = r"\d{4}-\d{2}-\d{2}"
    BUILD_DATE_VAR: Final[str] = "__build_date__"
    PRIMARY_ENCODING: Final[str] = "utf-8"
    FALLBACK_ENCODING: Final[str] = "latin-1"


@dataclass
class _ParseResult:
    """Parse result for build date updating."""

    needs_update: bool
    new_content: str = ""


@contextlib.contextmanager
def _managed_backup(file_path: Path) -> Generator[Path, None, None]:
    """Manage backup file lifecycle for atomic operations."""
    backup_file = None
    try:
        timestamp = datetime.datetime.now(tz=datetime.timezone.utc).strftime(
            "%Y%m%d_%H%M%S_%f"
        )
        backup_suffix = f"{_BuildDateConfig.BACKUP_SUFFIX}.{timestamp}"
        backup_file = file_path.with_suffix(file_path.suffix + backup_suffix)
        shutil.copy2(file_path, backup_file)
        logger.debug(f"Created backup file: {backup_file}")
        yield backup_file
    except OSError:
        logger.exception(f"Backup operation failed: {file_path}")
        raise
    finally:
        if backup_file and backup_file.exists():
            try:
                backup_file.unlink()
                logger.debug(f"Cleaned up backup file: {backup_file}")
            except OSError as e:
                logger.warning(f"Failed to delete backup file: {backup_file}, {e}")


def _update_file_build_date(
    file_path: Path, build_date: str, pattern: re.Pattern
) -> bool:
    """Update build date in a single file."""
    original_content = _read_file_content(file_path)
    if original_content is None:
        return False

    parse_result = _parse_and_validate_content(
        file_path, original_content, build_date, pattern
    )
    if not parse_result.needs_update:
        return False

    return _perform_file_update(file_path, parse_result.new_content, build_date)


def _read_file_content(file_path: Path) -> str | None:
    """Read file content with encoding fallback."""
    try:
        with file_path.open("r", encoding=_BuildDateConfig.PRIMARY_ENCODING) as f:
            return f.read()
    except UnicodeDecodeError:
        try:
            with file_path.open("r", encoding=_BuildDateConfig.FALLBACK_ENCODING) as f:
                content = f.read()
            logger.warning(
                f"File {file_path} read using {_BuildDateConfig.FALLBACK_ENCODING} encoding"
            )
            return content
        except OSError:
            logger.exception(f"Failed to read file: {file_path}")
            return None
    except OSError:
        logger.exception(f"Failed to read file: {file_path}")
        return None


def _parse_and_validate_content(
    file_path: Path, content: str, build_date: str, pattern: re.Pattern
) -> _ParseResult:
    """Parse and validate file content for build date updating."""
    try:
        match = pattern.search(content)
        if not match:
            logger.debug(
                f"File {file_path} does not contain {_BuildDateConfig.BUILD_DATE_VAR} definition, skipping"
            )
            return _ParseResult(needs_update=False)

        current_date = match.group(4)
        if not _validate_date_format(current_date):
            logger.warning(
                f"Invalid date format in file {file_path}: {current_date}, skipping"
            )
            return _ParseResult(needs_update=False)

        if not _validate_date_format(build_date):
            logger.error(f"Invalid new build date format: {build_date}")
            return _ParseResult(needs_update=False)

        quote = match.group(3) or ""
        new_line = f"{match.group(1)}{match.group(2)} = {quote}{build_date}{quote}{match.group(5)}"
        new_content = pattern.sub(new_line, content, count=1)

        if new_content == content:
            logger.debug(
                f"File {file_path} build date is already up to date, no update needed"
            )
            return _ParseResult(needs_update=False)

        return _ParseResult(needs_update=True, new_content=new_content)

    except (re.error, ValueError) as e:
        logger.exception(
            f"Data processing error: {file_path}, {e.__class__.__name__}: {e}"
        )
        return _ParseResult(needs_update=False)


def _perform_file_update(file_path: Path, new_content: str, build_date: str) -> bool:
    """Perform atomic file update with backup."""
    try:
        with _managed_backup(file_path):
            temp_file = file_path.with_suffix(
                file_path.suffix + _BuildDateConfig.TEMP_SUFFIX
            )
            try:
                with temp_file.open(
                    "w", encoding=_BuildDateConfig.PRIMARY_ENCODING
                ) as f:
                    f.write(new_content)
                    f.flush()
                    os.fsync(f.fileno())
                shutil.move(str(temp_file), str(file_path))
            except OSError:
                if temp_file.exists():
                    with contextlib.suppress(OSError):
                        temp_file.unlink()
                raise
            else:
                logger.info(
                    f"Updated file: {file_path}, {_BuildDateConfig.BUILD_DATE_VAR} -> {build_date}"
                )
                return True
    except OSError as e:
        logger.exception(
            f"File operation failed: {file_path}, {e.__class__.__name__}: {e}"
        )
        return False


def _validate_date_format(date_str: str) -> bool:
    """Validate date format is correct YYYY-MM-DD."""
    try:
        datetime.datetime.strptime(date_str, _BuildDateConfig.DATE_FORMAT)
        return True
    except ValueError:
        return False


def _get_build_date_pattern() -> re.Pattern:
    """Get regex pattern for matching build date."""
    return re.compile(
        r"^(\s*)"  # Group 1: Indentation
        rf"({_BuildDateConfig.BUILD_DATE_VAR})\s*=\s*"  # Group 2: Variable name
        r"([\"']?)"  # Group 3: Quote type
        rf"({_BuildDateConfig.DATE_PATTERN})"  # Group 4: Original date
        r"\3"  # Closing quote
        r"(\s*(#.*)?)$",  # Group 5: Trailing whitespace and comments
        flags=re.MULTILINE | re.IGNORECASE,
    )


def _cleanup_temp_files(directory: Path) -> None:
    """Clean up residual temp files."""
    try:
        for temp_file in directory.rglob(f"*{_BuildDateConfig.TEMP_SUFFIX}"):
            try:
                temp_file.unlink()
                logger.debug(f"Cleaned up temp file: {temp_file}")
            except OSError as e:
                logger.warning(f"Failed to clean up temp file: {temp_file}, {e}")
    except OSError as e:
        logger.warning(f"Failed to scan temp files: {directory}, {e}")


def update_build_date() -> None:
    """Update build dates in all __init__.py files under src directory.

    This function traverses the src directory, finds all __init__.py files,
    and updates any __build_date__ variables to the current date using atomic operations.
    """
    src_dir = CURRENT_WORKING_DIR / _BuildDateConfig.SRC_DIR_NAME
    if not src_dir.exists():
        logger.warning(
            f"{_BuildDateConfig.SRC_DIR_NAME} directory not found, cannot update build date"
        )
        return

    logger.debug("Cleaning up residual temp files...")
    _cleanup_temp_files(src_dir)

    try:
        init_files = list(src_dir.rglob(_BuildDateConfig.INIT_FILENAME))
    except OSError:
        logger.exception(f"Failed to scan {_BuildDateConfig.INIT_FILENAME} files")
        return

    if not init_files:
        logger.warning(
            f"No {_BuildDateConfig.INIT_FILENAME} files found for processing"
        )
        return

    logger.info(
        f"Found {len(init_files)} {_BuildDateConfig.INIT_FILENAME} files to process"
    )

    updated_files = 0
    skipped_files = 0
    failed_files = 0

    pattern = _get_build_date_pattern()
    build_date = datetime.datetime.now(datetime.timezone.utc).strftime(
        _BuildDateConfig.DATE_FORMAT
    )

    if not _validate_date_format(build_date):
        logger.error(f"Generated build date format invalid: {build_date}")
        return

    init_files.sort()

    for init_file in init_files:
        try:
            if _update_file_build_date(init_file, build_date, pattern):
                updated_files += 1
            else:
                skipped_files += 1
        except Exception as e:
            logger.exception(
                f"Failed to process file: {init_file}, {e.__class__.__name__}"
            )
            failed_files += 1

    total_files = updated_files + skipped_files + failed_files
    if updated_files > 0:
        logger.info(f"Build date update completed, updated {updated_files} files")
    if skipped_files > 0:
        logger.info(
            f"Skipped {skipped_files} files (no __build_date__ definition or no update needed)"
        )
    if failed_files > 0:
        logger.error(f"Failed to process {failed_files} files")
    if total_files == 0:
        logger.warning(
            f"No {_BuildDateConfig.INIT_FILENAME} files found for processing"
        )
    else:
        logger.info(
            f"Processing completed: Total {total_files} files, Success {updated_files}, Skipped {skipped_files}, Failed {failed_files}"
        )

    _cleanup_temp_files(src_dir)


def _write_to_shell_config(content: str) -> None:
    """Write content to shell configuration file, replacing existing entries.

    Updates shell configuration files by removing existing export statements
    for the same variable and adding the new content.

    Args:
        content: Content to write (should be export statements)

    Raises:
        ValueError: If export statement format is invalid
        OSError: If file operation fails
    """
    config_path = _get_shell_config_path()
    if not config_path.exists():
        logger.warning(f"{config_path} does not exist, creating it...")
        config_path.touch()

    # Extract the variable name from the export statement
    # Expected format: export VARIABLE_NAME=value
    var_name = None
    for line in content.strip().split("\n"):
        if line.startswith("export ") and "=" in line:
            var_name = line.split("=")[0].removeprefix("export ").strip()
            break

    if not var_name:
        raise ValueError(
            "Invalid export statement format. Expected: export VARIABLE_NAME=value"
        )

    # Read existing content
    existing_lines = config_path.read_text(encoding=DEFAULT_ENCODING).split("\n")

    # Find and remove existing export statements for this variable
    new_lines = []
    found_existing = False
    for line in existing_lines:
        # Check if this line exports the same variable
        if line.strip().startswith(f"export {var_name}=") or line.strip().startswith(
            f"export {var_name} ="
        ):
            found_existing = True
            continue
        new_lines.append(line)

    if found_existing:
        logger.debug(f"Found existing export statement for {var_name}, replacing it...")

    # Add new content
    new_lines.append(content.strip())

    # Write back to file
    config_path.write_text("\n".join(new_lines), encoding=DEFAULT_ENCODING)

    logger.info(f"Content written to {config_path}")
    logger.info(f"Run `source {config_path}` to apply the changes")
