"""Package Workflow - Advanced Python project packaging tool.

This module provides a comprehensive packaging solution that integrates pyprojectparse,
pysourcepack, pyembedinstall, and pyloadergen tools with configurable serial and
parallel execution for optimal efficiency.

The module follows established design patterns:
- Factory pattern for configuration creation
- Strategy pattern for cleaning operations
- Builder pattern for workflow construction
- Singleton pattern for logging configuration
"""

from __future__ import annotations

import argparse
import asyncio
import logging
import os
import platform
import shutil
import subprocess
import time
from dataclasses import dataclass, field
from enum import Enum
from functools import cached_property
from pathlib import Path
from typing import Any, Callable, Protocol

from sfi.pyprojectparse.models.solution import Solution
from sfi.pyprojectparse.models.project import Project

logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
cwd = Path.cwd()
is_windows = platform.system() == "Windows"
ext = ".exe" if is_windows else ""

__version__ = "1.0.0"
__build__ = "20260120"


# Enums for better type safety
class LoaderType(Enum):
    """Enumeration of supported loader types."""

    CONSOLE = "console"
    GUI = "gui"


class ArchiveFormat(Enum):
    """Enumeration of supported archive formats."""

    ZIP = "zip"
    SEVEN_ZIP = "7z"
    NSIS = "nsis"


class MirrorSource(Enum):
    """Enumeration of supported PyPI mirror sources."""

    PYPI = "pypi"
    TSINGHUA = "tsinghua"
    ALIYUN = "aliyun"
    USTC = "ustc"
    DOUBAN = "douban"
    TENCENT = "tencent"


# Protocol for cleaning strategies
class CleaningStrategy(Protocol):
    """Protocol for cleaning strategies."""

    def should_clean(self, entry: Path) -> bool:
        """Determine if an entry should be cleaned."""
        ...

    def clean_entry(self, entry: Path) -> tuple[bool, str]:
        """Clean an entry and return success status and message."""
        ...


# Constants
LOG_SEPARATOR = "=" * 50
PROTECTED_DIRS = {
    ".git",
    ".venv",
    ".virtualenv",
    ".vscode",
    ".idea",
    ".codebuddy",
    ".qoder",
}
CLEANABLE_DIRS = {"build", "dist", "pysfi_build", "cbuild", "benchmarks"}


# Configuration Factory Pattern
class ConfigFactory:
    """Factory for creating workflow configurations with validation."""

    @staticmethod
    def create_from_args(args: argparse.Namespace, cwd: Path) -> WorkflowConfig:
        """Create configuration from command line arguments."""
        # For commands that don't need project-specific config, use defaults
        project_name = getattr(args, "project", None)

        return WorkflowConfig(
            directory=cwd,
            project_name=project_name,
            python_version=getattr(args, "python_version", "3.8.10"),
            loader_type=LoaderType(getattr(args, "loader_type", "console")),
            entry_suffix=getattr(args, "entry_suffix", ".ent"),
            generate_loader=not getattr(args, "no_loader", False),
            offline=getattr(args, "offline", False),
            max_concurrent=getattr(args, "jobs", 4),
            debug=getattr(args, "debug", False),
            cache_dir=Path(args.cache_dir)
            if getattr(args, "cache_dir", None)
            else None,
            archive_format=ArchiveFormat(getattr(args, "archive_format", "zip")),
            mirror=MirrorSource(getattr(args, "mirror", "aliyun")),
            archive_type=getattr(args, "archive", None),
        )

    @staticmethod
    def create_default(directory: Path) -> WorkflowConfig:
        """Create default configuration."""
        return WorkflowConfig(directory=directory)


@dataclass
class WorkflowConfig:
    """Configuration for package workflow with type-safe enums."""

    directory: Path
    project_name: str | None = None
    python_version: str = "3.8.10"
    loader_type: LoaderType = LoaderType.CONSOLE
    entry_suffix: str = ".ent"
    generate_loader: bool = True
    offline: bool = False
    max_concurrent: int = 4
    debug: bool = False
    cache_dir: Path | None = None
    archive_format: ArchiveFormat = ArchiveFormat.ZIP
    mirror: MirrorSource = MirrorSource.ALIYUN
    archive_type: str | None = None

    @cached_property
    def normalized_directory(self) -> Path:
        """Get normalized and resolved directory path."""
        return self.directory.resolve()

    @cached_property
    def dist_dir(self) -> Path:
        """Get distribution directory path."""
        return self.normalized_directory / "dist"

    @cached_property
    def build_dir(self) -> Path:
        """Get build directory path."""
        return self.normalized_directory / "build"


# Strategy Pattern Implementation for Cleaning
class StandardCleaningStrategy:
    """Standard cleaning strategy implementation."""

    def should_clean(self, entry: Path) -> bool:
        """Determine if entry should be cleaned using standard rules."""
        # Special case: projects.json file should always be cleaned
        if entry.is_file():
            return entry.name == "projects.json"

        # Protected directories starting with dot
        if entry.name.startswith(".") and entry.name in PROTECTED_DIRS:
            return False

        # Clean temporary and build directories
        return (
            entry.name.startswith(".")
            or entry.name.startswith("__")
            or entry.name in CLEANABLE_DIRS
        )

    def clean_entry(self, entry: Path) -> tuple[bool, str]:
        """Clean entry and return success status with message."""
        if not entry.exists():
            return True, "Entry does not exist"

        try:
            if entry.is_dir():
                shutil.rmtree(entry)
                return True, f"Removed directory: {entry}"
            elif entry.is_file():
                entry.unlink()
                return True, f"Removed file: {entry}"
            else:
                return False, f"Unknown entry type: {entry}"
        except Exception as e:
            return False, f"Failed to remove {entry}: {e}"


@dataclass(frozen=True)
class PackageWorkflow:
    """Package workflow orchestrator using strategy pattern for cleaning."""

    root_dir: Path
    config: WorkflowConfig
    cleaning_strategy: CleaningStrategy = field(
        default_factory=StandardCleaningStrategy
    )

    @cached_property
    def solution(self) -> Solution:
        """Get the solution for the root directory.

        Returns:
            Solution: The solution for the root directory.
        """
        return Solution.from_directory(root_dir=self.root_dir)

    def _analyze_entry_file_type(self, entry_file_path: Path) -> LoaderType:
        """Analyze entry file content to determine if it's a GUI or console application.

        Args:
            entry_file_path: Path to the entry Python file

        Returns:
            LoaderType indicating whether it's a GUI or console application
        """
        if not entry_file_path.exists():
            # Default to console if file doesn't exist
            return LoaderType.CONSOLE

        try:
            content = entry_file_path.read_text(encoding="utf-8", errors="ignore")

            # Check for GUI framework imports
            gui_indicators = [
                "import PySide2",
                "from PySide2",
                "import PyQt5",
                "from PyQt5",
                "import PyQt6",
                "from PyQt6",
                "import PySide6",
                "from PySide6",
                "import tkinter",
                "from tkinter",
                "import wx",
                "from wx",
                "import kivy",
                "from kivy",
                "import pygame",
                "from pygame",
            ]

            # Check for Qt application patterns
            qt_app_indicators = ["QApplication", "QMainWindow", "QWidget", "QDialog"]

            # Check for CLI framework imports
            cli_indicators = [
                "import argparse",
                "from argparse",
                "import click",
                "from click",
                "import typer",
                "from typer",
                "import fire",
                "from fire",
                "sys.argv",
            ]

            # Count GUI vs CLI indicators
            gui_matches = sum(1 for indicator in gui_indicators if indicator in content)
            qt_matches = sum(
                1 for indicator in qt_app_indicators if indicator in content
            )
            cli_matches = sum(1 for indicator in cli_indicators if indicator in content)

            # Also check for specific patterns in the content
            has_main_guard = (
                'if __name__ == "__main__"' in content
                or "if __name__ == '__main__':" in content
            )
            has_argparse_usage = "ArgumentParser" in content
            has_click_usage = "@app.command" in content or "click.command" in content

            # Decision logic
            # If it has GUI frameworks and Qt application patterns, it's definitely a GUI
            if gui_matches > 0 or qt_matches > 0:
                return LoaderType.GUI

            # If it has CLI frameworks and argparse usage patterns, it's a console app
            if cli_matches > 0 and (
                has_argparse_usage or has_click_usage or has_main_guard
            ):
                return LoaderType.CONSOLE

            # Check for file name patterns as secondary indicator
            file_stem_lower = entry_file_path.stem.lower()
            if any(keyword in file_stem_lower for keyword in ["gui", "window", "app"]):
                return LoaderType.GUI
            if any(
                keyword in file_stem_lower for keyword in ["cli", "cmd", "tool", "util"]
            ):
                return LoaderType.CONSOLE

            # Default to console if no strong indicators found
            return LoaderType.CONSOLE

        except Exception:
            # If there's an error reading the file, default to console
            return LoaderType.CONSOLE

    @cached_property
    def projects(self) -> dict[str, Project]:
        """Get the projects for the solution.

        Returns:
            dict[str, Project]: The projects for the solution.
        """
        return self.solution.projects

    @cached_property
    def sorted_projects(self) -> dict[str, Project]:
        return dict(sorted(self.projects.items()))

    def get_entry_file_loader_type(
        self, project: Project, entry_stem: str
    ) -> LoaderType:
        """Get the appropriate loader type for a specific entry file in a project.

        Args:
            project: The project containing the entry file
            entry_stem: The stem (filename without extension) of the entry file

        Returns:
            LoaderType for the specific entry file
        """
        # Find the actual entry file path
        entry_file_path = project.root_dir / f"{entry_stem}.py"
        if not entry_file_path.exists():
            # If the specific entry file doesn't exist in project root,
            # look for it in other common locations
            possible_paths = [
                project.root_dir / entry_stem / f"{entry_stem}.py",
                project.root_dir / "src" / f"{entry_stem}.py",
                project.root_dir / "src" / entry_stem / f"{entry_stem}.py",
            ]
            for path in possible_paths:
                if path.exists():
                    entry_file_path = path
                    break

        # Analyze the specific entry file
        return self._analyze_entry_file_type(entry_file_path)

    @property
    def dist_dir(self) -> Path:
        """Get distribution directory path."""
        return self.config.dist_dir

    @property
    def build_dir(self) -> Path:
        """Get build directory path."""
        return self.config.build_dir

    def clean_project(self) -> None:
        """Clean build artifacts and package files using strategy pattern."""
        logger.info("Cleaning build artifacts using strategy pattern...")

        entries_to_clean = [
            entry
            for entry in self.root_dir.iterdir()
            if self.cleaning_strategy.should_clean(entry)
        ]

        if not entries_to_clean:
            logger.info("No build artifacts found to clean")
            return

        # Track cleaning results
        cleaned_dirs: list[str] = []
        cleaned_files: list[str] = []
        failed_operations: list[str] = []

        for entry in entries_to_clean:
            success, message = self.cleaning_strategy.clean_entry(entry)
            if success:
                if entry.is_dir():
                    cleaned_dirs.append(str(entry))
                else:
                    cleaned_files.append(str(entry))
                logger.debug(message)
            else:
                failed_operations.append(message)
                logger.warning(message)

        # Summary logging
        logger.info(
            f"Cleaned {len(cleaned_dirs)} directories and {len(cleaned_files)} file(s)"
        )

        if failed_operations:
            logger.error(f"Failed operations: {len(failed_operations)}")

    async def _run_sync_task(self, name: str, setup_func: Callable[[], None]) -> None:
        """Run a synchronous task in thread pool executor.

        Args:
            name: Name of the task for logging
            setup_func: Function that returns the task to execute
        """
        logger.info(LOG_SEPARATOR)
        logger.info(f"Packing {name}...")

        loop = asyncio.get_running_loop()
        await loop.run_in_executor(None, setup_func)
        logger.info(f"{name.capitalize()} packed.")

    async def pack_embed_python(self) -> None:
        """Pack embed python."""
        from sfi.pyembedinstall.pyembedinstall import EmbedInstaller

        def _run():
            installer = EmbedInstaller(
                root_dir=self.root_dir,
                cache_dir=self.config.cache_dir,
                offline=self.config.offline,
            )
            installer.run()

        await self._run_sync_task("embed python", _run)

    async def pack_loaders(self) -> None:
        """Pack loaders for all projects concurrently."""
        from sfi.pyloadergen.pyloadergen import PyLoaderGenerator

        def _run():
            generator = PyLoaderGenerator(root_dir=self.root_dir)
            generator.run()

        await self._run_sync_task("loaders", _run)

    async def pack_libraries(self) -> None:
        """Pack libraries for all projects concurrently."""
        from sfi.pylibpack.pylibpack import PyLibPacker, PyLibPackerConfig

        def _run():
            libpacker = PyLibPacker(
                working_dir=self.root_dir,
                config=PyLibPackerConfig(self.config.cache_dir),
            )
            libpacker.run()

        await self._run_sync_task("libraries", _run)

    async def pack_source(self) -> None:
        """Pack source code for all projects concurrently."""
        from sfi.pysourcepack.pysourcepack import PySourcePacker

        def _run():
            source_packer = PySourcePacker(root_dir=self.root_dir)
            source_packer.run()

        await self._run_sync_task("source code", _run)

    async def pack_archive(self, archive_format: str) -> None:
        """Create archive for all projects.

        Args:
            archive_format: Archive format (zip, tar, gztar, bztar, xztar, 7z, nsis)
        """
        from sfi.pyarchive.pyarchive import PyArchiveConfig, PyArchiver

        def _run():
            config = PyArchiveConfig(verbose=self.config.debug)
            archiver = PyArchiver(root_dir=self.root_dir, config=config)
            archiver.archive_projects(format=archive_format)

        await self._run_sync_task(f"{archive_format} archives", _run)

    async def build(self) -> dict[str, Any]:
        """Execute the packaging workflow with concurrent optimization.

        Workflow stages:
        1. Pack embed python (must be first)
        2. Pack loaders, libraries, and source in parallel
        3. Create archive (optional, if archive_type is set)

        Returns:
            Dict with results and summary including output_dir and metadata

        Raises:
            FileNotFoundError: If required directories don't exist
            RuntimeError: If any packaging step fails
        """
        logger.info("Starting packaging workflow execution")
        start_time = time.perf_counter()

        try:
            # Stage 1: Pack embed python (prerequisite for other tasks)
            await self.pack_embed_python()

            # Stage 2: Pack loaders, libraries, and source concurrently
            logger.info(LOG_SEPARATOR)
            logger.info("Running parallel tasks: loaders, libraries, source...")
            await asyncio.gather(
                self.pack_loaders(),
                self.pack_libraries(),
                self.pack_source(),
            )

            # Stage 3: Create archive (optional)
            if self.config.archive_type:
                await self.pack_archive(self.config.archive_type)

        except (FileNotFoundError, RuntimeError):
            raise
        except Exception as e:
            logger.error(f"Workflow execution failed: {e}")
            raise RuntimeError(f"Packaging workflow failed: {e}") from e

        elapsed = time.perf_counter() - start_time
        logger.info(LOG_SEPARATOR)
        logger.info(f"Packaging workflow completed in {elapsed:.2f}s")
        return {"output_dir": str(self.dist_dir), "metadata": {}}

    def list_projects(self) -> None:
        """List all available projects."""
        logger.info(f"Listing projects in {self.root_dir}")
        if logger.isEnabledFor(logging.DEBUG):
            logger.debug(f"Listing operation started for directory: {self.root_dir}")
            logger.debug(f"Found {len(self.sorted_projects)} projects:")
            logger.debug(f"Root directory: {self.root_dir}")
            logger.debug(f"Projects: {list(self.sorted_projects.keys())}")

        for project in self.sorted_projects.values():
            logger.info(f"  - {project}")
            if logger.isEnabledFor(logging.DEBUG):
                logger.debug(f"    Details: {project}")

    def _scan_executables(self) -> list[Path]:
        """Scan dist directory for executable files.

        Returns:
            List of executable file paths found in dist directory.
        """
        dist_dir = self.dist_dir
        if not dist_dir.exists():
            return []
        return [f for f in dist_dir.glob(f"*{ext}") if f.is_file()]

    def _resolve_executable(self, match_name: str | None) -> Path | None:
        """Resolve executable by scanning dist directory and matching name.

        Args:
            match_name: Executable name or partial name to match

        Returns:
            Path to matched executable, or None if not found/ambiguous.
        """
        executables = self._scan_executables()
        if not executables:
            return None

        # Auto-select if only one executable and no name specified
        if not match_name:
            return executables[0] if len(executables) == 1 else None

        lower_name = match_name.lower()

        # Try exact match (without extension)
        for exe in executables:
            if exe.stem.lower() == lower_name:
                return exe

        # Try fuzzy match (case-insensitive substring)
        matches = [exe for exe in executables if lower_name in exe.stem.lower()]
        return matches[0] if len(matches) == 1 else None

    def list_executables(self) -> None:
        """List all executables in dist directory."""
        executables = self._scan_executables()
        if not executables:
            logger.info("No executables found in dist directory")
            return
        logger.info(f"Available executables in {self.dist_dir}:")
        for exe in executables:
            logger.info(f"  - {exe.stem}")

    def run_project(
        self, match_name: str | None, project_args: list[str] | None = None
    ) -> None:
        """Run an executable with fuzzy name matching support and enhanced error handling.

        Args:
            match_name: Executable name or partial name to match
            project_args: Additional arguments to pass to the executable
        """
        exe_path = self._resolve_executable(match_name)

        # Handle executable not found cases
        if not exe_path:
            self._handle_executable_not_found(match_name)
            return

        # Enhanced pre-execution checks
        if not self._pre_execution_checks(exe_path):
            return

        # Build and execute command
        cmd = self._build_executable_command(exe_path, project_args)

        try:
            logger.info(f"正在启动应用程序: {exe_path.name}")
            logger.info(f"工作目录: {self.dist_dir}")
            if project_args:
                logger.info(f"传递参数: {' '.join(project_args)}")

            # Run with enhanced error capture
            result = subprocess.run(
                cmd,
                cwd=self.dist_dir,
                capture_output=True,
                text=True,
                encoding="utf-8",
                errors="replace",
            )

            if result.returncode == 0:
                logger.info(f"✓ {exe_path.stem} 运行成功")
                # 显示标准输出（如果有）
                if result.stdout.strip():
                    logger.info(f"程序输出:\n{result.stdout}")
            else:
                self._handle_execution_error(exe_path, result)

        except FileNotFoundError:
            logger.error(f"❌ 可执行文件未找到: {exe_path}")
            logger.error("请检查文件是否存在或重新构建项目")
        except PermissionError:
            logger.error(f"❌ 权限不足: 无法执行 {exe_path}")
            logger.error("请检查文件权限或以管理员身份运行")
        except Exception as e:
            logger.error(f"❌ 启动 {exe_path.name} 时发生未知错误: {e}")
            logger.error("建议检查系统环境和依赖项")

    def _handle_executable_not_found(self, match_name: str | None) -> None:
        """Handle cases where executable cannot be found with enhanced messaging.

        Args:
            match_name: Executable name that was being searched for
        """
        executables = self._scan_executables()

        logger.error("==================================================")
        if not match_name:
            if len(executables) == 0:
                logger.error("❌ 未找到可执行文件")
                logger.error("请先构建项目: pypack build")
            elif len(executables) > 1:
                logger.error("❌ 找到多个可执行文件，请指定要运行的程序:")
                self.list_executables()
            else:
                logger.error("❌ 无法自动选择可执行文件")
        else:
            logger.error(f"❌ 未找到可执行文件: '{match_name}'")
            if executables:
                logger.error("可用的程序:")
                self.list_executables()
            else:
                logger.error("请先构建项目: pypack build")
        logger.error("==================================================")

    def _pre_execution_checks(self, exe_path: Path) -> bool:
        """Perform pre-execution checks and dependency validation.

        Args:
            exe_path: Path to the executable to be run

        Returns:
            bool: True if all checks pass, False otherwise
        """
        logger.info("==================================================")
        logger.info("执行前检查 - 依赖验证")
        logger.info("==================================================")

        # Check if executable exists
        if not exe_path.exists():
            logger.error(f"❌ 可执行文件不存在: {exe_path}")
            return False

        # Check file permissions
        if not os.access(exe_path, os.X_OK):
            logger.error(f"❌ 可执行文件没有执行权限: {exe_path}")
            logger.error("请检查文件权限或以管理员身份运行")
            return False

        # Check runtime directory existence
        runtime_dir = self.dist_dir / "runtime"
        if not runtime_dir.exists():
            logger.error(f"❌ 运行时目录不存在: {runtime_dir}")
            logger.error("请重新构建项目以确保运行时环境完整")
            return False

        # Check Python interpreter in runtime
        python_exe = runtime_dir / ("python.exe" if is_windows else "bin/python3")
        if not python_exe.exists():
            logger.error(f"❌ Python 解释器未找到: {python_exe}")
            logger.error("运行时环境可能不完整，请重新构建项目")
            return False

        # Check site-packages directory
        site_packages = self.dist_dir / "site-packages"
        if site_packages.exists():
            package_count = len(list(site_packages.iterdir()))
            logger.info(f"✓ 发现 {package_count} 个依赖包")
        else:
            logger.warning("⚠ 未找到 site-packages 目录，可能缺少依赖")

        # Check for common GUI dependencies
        if self._is_gui_application(exe_path):
            if not self._check_gui_dependencies():
                logger.warning("⚠ GUI 依赖可能不完整，应用程序可能无法正常显示界面")

        logger.info("✓ 所有预执行检查通过")
        logger.info("==================================================")
        return True

    def _is_gui_application(self, exe_path: Path) -> bool:
        """Determine if the executable is a GUI application.

        Args:
            exe_path: Path to executable

        Returns:
            bool: True if it's likely a GUI application
        """
        # Check filename for GUI indicators
        name_lower = exe_path.stem.lower()
        gui_indicators = ["gui", "app", "interface", "window"]
        return any(indicator in name_lower for indicator in gui_indicators)

    def _check_gui_dependencies(self) -> bool:
        """Check for common GUI framework dependencies.

        Returns:
            bool: True if GUI dependencies appear to be present
        """
        site_packages = self.dist_dir / "site-packages"
        if not site_packages.exists():
            return False

        # Check for common GUI frameworks
        gui_frameworks = [
            "PySide2",
            "PyQt5",
            "PyQt6",
            "tkinter",
            "wx",
            "kivy",
            "pygame",
        ]

        found_frameworks = []
        for framework in gui_frameworks:
            if (site_packages / framework).exists():
                found_frameworks.append(framework)

        if found_frameworks:
            logger.info(f"✓ 检测到 GUI 框架: {', '.join(found_frameworks)}")
            return True
        else:
            logger.warning("⚠ 未检测到常见的 GUI 框架")
            return False

    def _build_executable_command(
        self, exe_path: Path, project_args: list[str] | None
    ) -> list[str]:
        """Build command list for executable execution.

        Args:
            exe_path: Path to executable
            project_args: Additional arguments to pass

        Returns:
            List of command arguments
        """
        cmd = [str(exe_path.resolve())]
        if project_args:
            cmd.extend(project_args)
        return cmd

    def _handle_execution_error(
        self, exe_path: Path, result: subprocess.CompletedProcess
    ) -> None:
        """Handle execution errors with detailed error reporting.

        Args:
            exe_path: Path to the executable that failed
            result: Completed process result with error information
        """
        logger.error("==================================================")
        logger.error(f"❌ 应用程序运行失败: {exe_path.name}")
        logger.error(f"退出代码: {result.returncode}")

        # 显示错误输出
        if result.stderr.strip():
            logger.error("\n错误详情:")
            # 分行显示错误信息，提高可读性
            error_lines = result.stderr.strip().split("\n")
            max_display_lines = 50  # 增加显示行数限制
            for _, line in enumerate(error_lines[:max_display_lines]):
                if line.strip():
                    logger.error(f"  {line}")
            if len(error_lines) > max_display_lines:
                logger.error(
                    f"  ... (还有 {len(error_lines) - max_display_lines} 行错误信息)"
                )
                # 提供完整错误信息的保存选项
                error_file = self.dist_dir / f"error_details_{exe_path.stem}.txt"
                try:
                    error_file.write_text(result.stderr, encoding="utf-8")
                    logger.error(f"  完整错误信息已保存到: {error_file}")
                except Exception as save_error:
                    logger.error(f"  无法保存完整错误信息: {save_error}")

        # 根据错误类型提供具体建议
        error_output = result.stderr.lower()
        if "modulenotfounderror" in error_output or "importerror" in error_output:
            logger.error("\n💡 可能的解决方案:")
            logger.error("  • 缺少必要的 Python 包，请重新运行: pypack build")
            logger.error("  • 检查依赖包版本兼容性")
            logger.error("  • 确认所有必需的库都已正确打包")
        elif "filenotfounderror" in error_output:
            logger.error("\n💡 可能的解决方案:")
            logger.error("  • 检查程序文件完整性")
            logger.error("  • 确认运行时文件存在")
            logger.error("  • 重新构建项目")
        elif result.returncode == -1073741819:  # Windows ACCESS_VIOLATION
            logger.error("\n💡 可能的解决方案:")
            logger.error("  • 程序可能存在内存访问错误")
            logger.error("  • 尝试以管理员身份运行")
            logger.error("  • 检查系统兼容性")
        else:
            logger.error("\n💡 通用解决方案:")
            logger.error("  • 检查程序依赖和环境配置")
            logger.error("  • 查看完整的错误日志获取更多信息")
            logger.error("  • 重新构建项目: pypack build")

        logger.error("==================================================")


def parse_args() -> argparse.Namespace:
    """Parse command line arguments using subcommand structure.

    Returns:
        Parsed arguments namespace
    """
    parser = argparse.ArgumentParser(
        prog="pypack", description="Python packaging tool with workflow orchestration"
    )

    # Add common arguments to parent parser
    parent_parser = argparse.ArgumentParser(add_help=False)
    parent_parser.add_argument("--debug", "-d", action="store_true", help="Debug mode")

    # Create subparsers
    subparsers = parser.add_subparsers(
        dest="command", required=True, help="Available commands"
    )

    # Version subcommand
    subparsers.add_parser(
        "version",
        aliases=["v"],
        help="Show version information",
        parents=[parent_parser],
    )

    # List subcommand
    subparsers.add_parser(
        "list",
        aliases=["l", "ls"],
        help="List available projects",
        parents=[parent_parser],
    )

    # Clean subcommand
    subparsers.add_parser(
        "clean", aliases=["c"], help="Clean build artifacts", parents=[parent_parser]
    )

    # Run subcommand
    run_parser = subparsers.add_parser(
        "run", aliases=["r"], help="Run a project", parents=[parent_parser]
    )
    run_parser.add_argument(
        "project",
        type=str,
        nargs="?",
        help="Project or executable name (e.g., 'docscan' or 'docscan-gui')",
    )
    run_parser.add_argument(
        "args",
        type=str,
        nargs="*",
        help="Additional arguments to pass to the project",
    )

    # Build subcommand
    build_parser = subparsers.add_parser(
        "build", aliases=["b"], help="Build project packages", parents=[parent_parser]
    )
    build_parser.add_argument(
        "--python-version", type=str, default="3.8.10", help="Python version to install"
    )
    build_parser.add_argument(
        "--loader-type",
        type=str,
        choices=[lt.value for lt in LoaderType],
        default=LoaderType.CONSOLE.value,
        help="Loader type",
    )
    build_parser.add_argument(
        "--entry-suffix",
        type=str,
        default=".ent",
        help="Entry file suffix (default: .ent, alternatives: .py)",
    )
    build_parser.add_argument(
        "--no-loader", action="store_true", help="Skip loader generation"
    )
    build_parser.add_argument(
        "-o", "--offline", action="store_true", help="Offline mode"
    )
    build_parser.add_argument(
        "-j", "--jobs", type=int, default=4, help="Maximum concurrent tasks"
    )

    # Library packing arguments (build-specific)
    build_parser.add_argument(
        "--cache-dir",
        type=str,
        default=None,
        help="Custom cache directory for dependencies",
    )
    build_parser.add_argument(
        "--archive-format",
        type=str,
        choices=[af.value for af in ArchiveFormat],
        default=ArchiveFormat.ZIP.value,
        help="Archive format for dependencies",
    )
    build_parser.add_argument(
        "--mirror",
        type=str,
        default=MirrorSource.ALIYUN.value,
        choices=[ms.value for ms in MirrorSource],
        help="PyPI mirror source for faster downloads",
    )

    # Archive options (optional post-build step)
    build_parser.add_argument(
        "--archive",
        "-a",
        type=str,
        nargs="?",
        const="zip",
        default=None,
        choices=["zip", "tar", "gztar", "bztar", "xztar", "7z", "nsis"],
        help="Create archive after build (default format: zip if flag used)",
    )

    return parser.parse_args()


def main() -> None:
    """Main entry point for package workflow tool using factory pattern."""
    args = parse_args()

    # Build workflow configuration using factory pattern
    config = ConfigFactory.create_from_args(args, cwd)

    workflow = PackageWorkflow(
        root_dir=config.directory,
        config=config,
        cleaning_strategy=StandardCleaningStrategy(),
    )

    # Command dispatch using pattern matching
    command = args.command

    # Configure logging level with enhanced debug information
    if args.debug:
        logging.getLogger().setLevel(logging.DEBUG)
        logger.debug("==================================================")
        logger.debug("DEBUG MODE ENABLED - Enhanced Diagnostics")
        logger.debug("==================================================")
        logger.debug(f"Command: {command}")
        logger.debug(f"Working directory: {cwd}")
        logger.debug(f"Arguments: {vars(args)}")
        logger.debug(f"Platform: {platform.system()} {platform.release()}")
        logger.debug(f"Python version: {platform.python_version()}")
        logger.debug("==================================================")

    if command in ("version", "v"):
        logger.info(f"pypack {__version__} (build {__build__})")
        if args.debug:
            logger.debug(
                f"Version command executed at: {time.strftime('%Y-%m-%d %H:%M:%S')}"
            )
    elif command in ("list", "l", "ls"):
        if args.debug:
            logger.debug("Starting project listing with enhanced debug info...")
        workflow.list_projects()
        if args.debug:
            logger.debug("Project listing completed")
    elif command in ("run", "r"):
        if args.debug:
            logger.debug(f"Running project: {args.project}")
            logger.debug(f"Additional arguments: {args.args}")
        workflow.run_project(args.project, args.args)
    elif command in ("clean", "c"):
        if args.debug:
            logger.debug("Starting cleanup process...")
        workflow.clean_project()
        if args.debug:
            logger.debug("Cleanup completed")
    elif command in ("build", "b"):
        try:
            if args.debug:
                logger.debug("Starting build workflow execution...")
                logger.debug(f"Build configuration: {config}")
                start_time = time.perf_counter()

            asyncio.run(workflow.build())

            if args.debug:
                elapsed = time.perf_counter() - start_time
                logger.debug(f"Build workflow completed in {elapsed:.2f} seconds")
                logger.debug("==================================================")
                logger.debug("BUILD SUMMARY")
                logger.debug("==================================================")
                logger.debug(f"Root directory: {config.directory}")
                logger.debug(f"Project name: {config.project_name or 'Auto-detected'}")
                logger.debug(f"Python version: {config.python_version}")
                logger.debug(f"Loader type: {config.loader_type.value}")
                logger.debug(f"Offline mode: {config.offline}")
                logger.debug(f"Max concurrent jobs: {config.max_concurrent}")
                logger.debug(f"Mirror source: {config.mirror.value}")
                logger.debug("==================================================")

            logger.info("Packaging completed successfully!")
        except Exception as e:
            if args.debug:
                logger.debug(f"Build failed with exception: {type(e).__name__}: {e}")
                import traceback

                logger.debug("Stack trace:")
                logger.debug(traceback.format_exc())
            logger.error(f"Packaging failed: {e}")
            raise


if __name__ == "__main__":
    main()
