#!/usr/bin/env python
"""Interactive script to set up a Python package for PyPI distribution.

Features:
- Interactive CLI wizard for package configuration
- Automatic Python version requirement detection from source code
- Platform compatibility detection (Windows/Linux/macOS binaries)
- Input validation (PyPI package names, PEP 8 module names, email format)
- Support for both package and py-modules distribution types
- Complete and compact package building options
- Automatic build process integration
- Existing pyproject.toml support

Python Version Detection:
The script automatically analyzes source code to detect the minimum Python version
requirement by examining syntax features and imports:
- Match/Case statements → Python 3.10+
- Walrus operator (:=) → Python 3.8+
- Dataclasses → Python 3.7+
- F-strings → Python 3.6+
- Type hints (variable annotations) → Python 3.6+
- Async/Await → Python 3.5+
- Explicit version comments in code

Platform Compatibility Detection:
Automatically detects platform-specific binaries (.exe, .dll, .so, .dylib, etc.)
and warns about wheel compatibility, helping users understand if they need
platform-specific wheels or can use universal wheels.
"""

import argparse
import ast
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
from typing import List, Set, Optional, Tuple


def verify_dependencies() -> Tuple[bool, List[str]]:
    """Verify that all required dependencies are installed.
    
    Checks for external dependencies required by easywheel.py:
    - build: Required for building Python packages
    - setuptools: Usually comes with build, but checked separately
    - wheel: Usually comes with build, but checked separately
    
    Returns:
        Tuple[bool, List[str]]: 
            - First element: True if all dependencies are available, False otherwise
            - Second element: List of missing dependency names
    
    Example:
        >>> available, missing = verify_dependencies()
        >>> if not available:
        ...     print(f"Missing: {', '.join(missing)}")
    """
    required_modules = {
        'build': {
            'name': 'build',
            'install_cmd': 'pip install build',
            'description': 'Python build frontend (PEP 517)',
        },
        'setuptools': {
            'name': 'setuptools',
            'install_cmd': 'pip install setuptools',
            'description': 'Python packaging library',
        },
        'wheel': {
            'name': 'wheel',
            'install_cmd': 'pip install wheel',
            'description': 'Built-package format for Python',
        },
    }
    
    missing = []
    
    for module_name, info in required_modules.items():
        try:
            __import__(module_name)
        except ImportError:
            missing.append(module_name)
    
    return len(missing) == 0, missing


def check_dependencies() -> Tuple[bool, List[str]]:
    """Check dependencies and return status.
    
    This function checks for required dependencies and returns their status.
    If dependencies are missing, it displays error messages but does not exit,
    allowing the caller to decide whether to continue or exit.
    
    Returns:
        Tuple[bool, List[str]]: 
            - First element: True if all dependencies are available, False otherwise
            - Second element: List of missing dependency names (empty if all available)
    """
    print("Checking dependencies...")
    available, missing = verify_dependencies()
    
    if not available:
        print("\n" + "=" * 60)
        print("✗ Missing Required Dependencies")
        print("=" * 60)
        print("\nThe following required modules are not installed:")
        print()
        
        dependency_info = {
            'build': {
                'install_cmd': 'pip install build',
                'description': 'Python build frontend (PEP 517) - Required for building packages',
            },
            'setuptools': {
                'install_cmd': 'pip install setuptools',
                'description': 'Python packaging library - Required for setuptools configuration',
            },
            'wheel': {
                'install_cmd': 'pip install wheel',
                'description': 'Built-package format - Required for wheel distribution',
            },
        }
        
        for module in missing:
            info = dependency_info.get(module, {})
            print(f"  • {module}")
            if info.get('description'):
                print(f"    {info['description']}")
            if info.get('install_cmd'):
                print(f"    Install with: {info['install_cmd']}")
            print()
        
        print("=" * 60)
        print("\nTo install all required dependencies, run:")
        print("  pip install build setuptools wheel")
        print("\nOr install them individually as shown above.")
        print("=" * 60)
        return False, missing
    else:
        print("✓ All dependencies are available")
        print()
        return True, []


def ask_question(prompt: str, default: str = None, validator=None) -> str:
    """Ask a question and return the answer."""
    if default:
        full_prompt = f"{prompt} (default: {default}): "
    else:
        full_prompt = f"{prompt}: "
    
    while True:
        answer = input(full_prompt).strip()
        
        if not answer and default:
            return default
        
        if not answer:
            print("This field is required. Please provide an answer.")
            continue
        
        if validator:
            try:
                validator(answer)
            except ValueError as e:
                print(f"Invalid input: {e}")
                continue
        
        return answer


def validate_directory(path: str) -> None:
    """Validate that the directory or file exists."""
    if not os.path.exists(path):
        raise ValueError(f"Path does not exist: {path}")


def validate_source_path(path: str) -> None:
    """Validate that the source path exists (can be directory or file)."""
    if not os.path.exists(path):
        raise ValueError(f"Source path does not exist: {path}")


def detect_platform_compatibility(source_dir: str) -> dict:
    """Detect platform-specific files in the source directory.
    
    Returns a dictionary with:
    - 'platforms': set of detected platforms ('windows', 'linux', 'macos', 'any')
    - 'has_binaries': bool indicating if platform-specific binaries are found
    - 'platform_specific_files': list of platform-specific file patterns found
    """
    result = {
        'platforms': set(),
        'has_binaries': False,
        'platform_specific_files': []
    }
    
    if not os.path.exists(source_dir):
        return result
    
    # Windows-specific file patterns
    windows_patterns = ['.exe', '.dll', '.bat', '.cmd', '.ps1']
    
    # Linux-specific patterns (ELF binaries often have no extension or specific ones)
    linux_patterns = ['.so']  # Shared libraries
    
    # macOS-specific patterns
    macos_patterns = ['.dylib', '.framework']
    
    # Platform-independent patterns
    platform_independent = ['.py', '.pyc', '.pyo', '.txt', '.md', '.json', '.yaml', '.yml', '.toml', '.cfg', '.ini']
    
    def check_file(file_path: str, file_name: str) -> None:
        """Check a single file for platform indicators."""
        file_lower = file_name.lower()
        file_ext = os.path.splitext(file_name)[1].lower()
        
        # Check Windows patterns
        if any(file_lower.endswith(pat) for pat in windows_patterns) or file_ext in windows_patterns:
            result['platforms'].add('windows')
            result['has_binaries'] = True
            result['platform_specific_files'].append(f"Windows: {file_name}")
        
        # Check Linux patterns
        elif any(file_lower.endswith(pat) for pat in linux_patterns) or file_ext in linux_patterns:
            result['platforms'].add('linux')
            result['has_binaries'] = True
            result['platform_specific_files'].append(f"Linux: {file_name}")
        
        # Check macOS patterns
        elif any(file_lower.endswith(pat) for pat in macos_patterns) or file_ext in macos_patterns:
            result['platforms'].add('macos')
            result['has_binaries'] = True
            result['platform_specific_files'].append(f"macOS: {file_name}")
        
        # Check for ELF binaries (Linux) - files without extension that are executable
        elif not file_ext and os.path.isfile(file_path):
            try:
                # Try to read first bytes to detect ELF magic number
                with open(file_path, 'rb') as f:
                    magic = f.read(4)
                    if magic == b'\x7fELF':  # ELF magic number
                        result['platforms'].add('linux')
                        result['has_binaries'] = True
                        result['platform_specific_files'].append(f"Linux (ELF): {file_name}")
            except:
                pass
        
        # Check for Mach-O binaries (macOS) - files without extension
        elif not file_ext and os.path.isfile(file_path):
            try:
                with open(file_path, 'rb') as f:
                    magic = f.read(4)
                    # Mach-O magic numbers: 0xfeedface (32-bit), 0xfeedfacf (64-bit), 0xcafebabe (fat binary)
                    if magic in [b'\xfe\xed\xfa\xce', b'\xfe\xed\xfa\xcf', b'\xca\xfe\xba\xbe']:
                        result['platforms'].add('macos')
                        result['has_binaries'] = True
                        result['platform_specific_files'].append(f"macOS (Mach-O): {file_name}")
            except:
                pass
    
    # Scan the source directory
    if os.path.isfile(source_dir):
        # Single file
        check_file(source_dir, os.path.basename(source_dir))
    elif os.path.isdir(source_dir):
        # Directory - scan recursively
        for root, dirs, files in os.walk(source_dir):
            for file in files:
                file_path = os.path.join(root, file)
                check_file(file_path, file)
    
    # If no platform-specific files found, assume platform-independent
    if not result['platforms']:
        result['platforms'].add('any')
    
    return result


def detect_python_version_requirement(source_dir: str) -> Optional[str]:
    """Detect minimum Python version requirement from source code.
    
    This function automatically analyzes Python source files to determine the
    minimum Python version required based on syntax features and imports found
    in the code. It uses AST parsing to detect language features that require
    specific Python versions.
    
    Detection Features:
    - Match/Case statements (Python 3.10+): Detects ast.Match nodes
    - Walrus operator (Python 3.8+): Detects ast.NamedExpr nodes
    - Dataclasses (Python 3.7+): Detects 'from dataclasses import' statements
    - F-strings (Python 3.6+): Detects ast.JoinedStr nodes
    - Type hints (Python 3.6+): Detects ast.AnnAssign nodes (variable annotations)
    - Async/Await (Python 3.5+): Detects async function/loop definitions
    - Explicit version comments: Searches for version requirements in comments/docstrings
    
    The function scans up to 50 Python files for performance reasons and skips
    common directories like .git, __pycache__, venv, etc.
    
    Args:
        source_dir: Path to source directory or single Python file to analyze
        
    Returns:
        Optional[str]: Suggested Python version requirement string (e.g., ">=3.8")
                      Returns None if detection fails or no Python files are found.
                      The returned string follows PEP 440 version specifier format.
    
    Example:
        >>> detect_python_version_requirement("./src")
        ">=3.8"
        >>> detect_python_version_requirement("./my_module.py")
        ">=3.10"
    """
    python_files = []
    
    # Collect all Python files
    if os.path.isfile(source_dir) and source_dir.endswith('.py'):
        python_files.append(source_dir)
    elif os.path.isdir(source_dir):
        for root, dirs, files in os.walk(source_dir):
            # Skip common directories that shouldn't be analyzed
            dirs[:] = [d for d in dirs if d not in {'.git', '__pycache__', '.pytest_cache', 
                                                      'venv', 'env', 'build', 'dist', '.egg-info'}]
            for file in files:
                if file.endswith('.py'):
                    python_files.append(os.path.join(root, file))
    
    if not python_files:
        return None
    
    # Features that require specific Python versions
    detected_features = {
        'match_case': False,      # Python 3.10+
        'walrus_operator': False,  # Python 3.8+
        'f_string': False,        # Python 3.6+
        'type_hints': False,       # Python 3.5+ (basic), 3.6+ (variable annotations)
        'dataclass': False,       # Python 3.7+
        'from_typing': False,      # Python 3.5+ (typing module)
        'async_await': False,     # Python 3.5+
    }
    
    # Version requirements based on features
    version_requirements = {
        'match_case': (3, 10),
        'walrus_operator': (3, 8),
        'dataclass': (3, 7),
        'f_string': (3, 6),
        'type_hints': (3, 6),  # Variable annotations require 3.6+
        'async_await': (3, 5),
    }
    
    # Check for explicit version requirements in code comments or docstrings
    explicit_requirements = []
    
    # Analyze each Python file
    for py_file in python_files[:50]:  # Limit to first 50 files for performance
        try:
            with open(py_file, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
                
            # Check for explicit version requirements in comments/docstrings
            version_patterns = [
                r'python\s*[>=<]+\s*3\.\d+',
                r'requires.*python\s*[>=<]+\s*3\.\d+',
                r'python\s*3\.\d+',
            ]
            for pattern in version_patterns:
                matches = re.findall(pattern, content, re.IGNORECASE)
                for match in matches:
                    # Extract version number
                    version_match = re.search(r'3\.(\d+)', match)
                    if version_match:
                        minor = int(version_match.group(1))
                        explicit_requirements.append((3, minor))
            
            # Parse AST to detect syntax features
            try:
                tree = ast.parse(content, filename=py_file)
                
                # Visit AST nodes to detect features
                for node in ast.walk(tree):
                    # Match/Case statement (Python 3.10+)
                    if isinstance(node, ast.Match):
                        detected_features['match_case'] = True
                    
                    # Walrus operator (Python 3.8+)
                    if isinstance(node, ast.NamedExpr):
                        detected_features['walrus_operator'] = True
                    
                    # F-strings (Python 3.6+)
                    if isinstance(node, ast.JoinedStr):
                        detected_features['f_string'] = True
                    
                    # Type hints (Python 3.5+)
                    if isinstance(node, ast.AnnAssign):
                        detected_features['type_hints'] = True
                    
                    # Async/Await (Python 3.5+)
                    if isinstance(node, (ast.AsyncFunctionDef, ast.AsyncFor, ast.AsyncWith)):
                        detected_features['async_await'] = True
                    
                    # Check imports
                    if isinstance(node, ast.ImportFrom):
                        module = node.module or ''
                        if module == 'dataclasses':
                            detected_features['dataclass'] = True
                        elif module == 'typing':
                            detected_features['from_typing'] = True
                    
                    if isinstance(node, ast.Import):
                        for alias in node.names:
                            if alias.name == 'typing':
                                detected_features['from_typing'] = True
                
            except SyntaxError:
                # If file has syntax errors, skip AST parsing but continue checking
                pass
            
            # Simple regex checks for features that might not be caught by AST
            # F-strings
            if re.search(r'f["\']', content):
                detected_features['f_string'] = True
            
            # Walrus operator (simple pattern)
            if re.search(r':=\s*\w+', content):
                detected_features['walrus_operator'] = True
            
            # Match/Case (simple pattern)
            if re.search(r'\bmatch\s+\w+:', content):
                detected_features['match_case'] = True
            
        except Exception:
            # Skip files that can't be read or parsed
            continue
    
    # Determine minimum version based on detected features
    min_version = (3, 8)  # Default minimum
    
    # Check explicit requirements first
    if explicit_requirements:
        max_explicit = max(explicit_requirements, key=lambda x: (x[0], x[1]))
        min_version = max_explicit
    
    # Check feature-based requirements
    for feature, required_version in version_requirements.items():
        if detected_features.get(feature, False):
            if required_version > min_version:
                min_version = required_version
    
    # Format as version string
    major, minor = min_version
    return f">={major}.{minor}"


def validate_email(email: str) -> None:
    """Basic email validation."""
    if "@" not in email or "." not in email.split("@")[1]:
        raise ValueError("Invalid email format")


def validate_url(url: str) -> None:
    """Validate URL format - must start with http:// or https://."""
    if not url:
        return  # Empty URL is allowed (optional field)
    url_lower = url.lower().strip()
    if not (url_lower.startswith('http://') or url_lower.startswith('https://')):
        raise ValueError("URL must start with http:// or https://")


def normalize_url(url: str) -> str:
    """Normalize URL by adding https:// if protocol is missing.
    
    Args:
        url: URL string (may or may not have protocol)
    
    Returns:
        str: URL with protocol (https:// added if missing)
    """
    if not url:
        return url
    
    url = url.strip()
    url_lower = url.lower()
    
    # If URL doesn't start with a protocol, add https://
    if not (url_lower.startswith('http://') or url_lower.startswith('https://')):
        # Check if it looks like a domain (contains dots and doesn't start with special chars)
        if '.' in url and not url.startswith(('//', '/', '?')):
            url = 'https://' + url
        else:
            # If it doesn't look like a valid domain, return as-is (user will see validation error)
            pass
    
    return url


def validate_package_choice(choice: str) -> None:
    """Validate package type choice."""
    if choice not in ["1", "2"]:
        raise ValueError("Please enter 1 or 2")


def validate_distribution_type(choice: str) -> None:
    """Validate distribution type choice."""
    if choice not in ["1", "2"]:
        raise ValueError("Please enter 1 or 2")


def validate_yes_no(choice: str) -> None:
    """Validate yes/no choice."""
    if choice.lower() not in ["y", "n", "yes", "no"]:
        raise ValueError("Please enter 'y' or 'n'")


def validate_dependency_choice(choice: str) -> None:
    """Validate dependency action choice (1, 2, 3, or 4)."""
    if choice not in ['1', '2', '3', '4']:
        raise ValueError("Please enter 1, 2, 3, or 4")


def validate_output_choice(choice: str) -> None:
    """Validate output directory choice (1, 2, or 3)."""
    if choice not in ['1', '2', '3']:
        raise ValueError("Please enter 1, 2, or 3")


def validate_package_name(name: str) -> None:
    """Validate PyPI package name according to PyPI naming policies.
    
    PyPI package names must:
    - Contain only lowercase letters, numbers, hyphens (-), underscores (_), and dots (.)
    - Start and end with a letter or number (not a special character)
    - Not be all numbers
    - Be between 1 and 214 characters
    """
    if not name:
        raise ValueError("Package name cannot be empty")
    
    if len(name) > 214:
        raise ValueError("Package name cannot exceed 214 characters")
    
    # PyPI naming pattern: lowercase letters, numbers, hyphens, underscores, dots
    # Must start and end with alphanumeric (not special characters)
    # Pattern: starts with alphanumeric, optionally followed by any allowed chars, ends with alphanumeric
    # Handles single character names and multi-character names
    if len(name) == 1:
        pattern = r'^[a-z0-9]$'
    else:
        pattern = r'^[a-z0-9][a-z0-9._-]*[a-z0-9]$'
    
    if not re.match(pattern, name):
        raise ValueError(
            "Invalid package name. Must contain only lowercase letters, numbers, "
            "hyphens (-), underscores (_), and dots (.), and must start/end with a letter or number"
        )
    
    # Cannot be all numbers
    if name.replace('.', '').replace('-', '').replace('_', '').isdigit():
        raise ValueError("Package name cannot be all numbers")


def validate_module_name(name: str) -> None:
    """Validate Python module name according to PEP 8 and Python identifier rules.
    
    Python module names must:
    - Be a valid Python identifier (start with letter or underscore, contain letters, numbers, underscores)
    - Follow PEP 8: lowercase with underscores for readability
    - Not start with a number
    """
    if not name:
        raise ValueError("Module name cannot be empty")
    
    # Check if it's a valid Python identifier
    if not name.isidentifier():
        raise ValueError(
            "Invalid module name. Must be a valid Python identifier "
            "(start with letter or underscore, contain only letters, numbers, and underscores)"
        )
    
    # PEP 8 recommendation: lowercase with underscores
    if not re.match(r'^[a-z_][a-z0-9_]*$', name):
        raise ValueError(
            "Module name should follow PEP 8: use lowercase letters, numbers, and underscores only. "
            "Cannot start with a number or uppercase letter."
        )


def ensure_package_dir_in_pyproject(pyproject_path: str) -> bool:
    """Ensure pyproject.toml has package-dir configuration pointing to input/ directory.
    
    Returns True if updated, False if already had it or couldn't update.
    """
    try:
        with open(pyproject_path, 'r', encoding='utf-8') as f:
            content = f.read()
        
        # Check if package-dir already exists
        if 'package-dir' in content or 'package_dir' in content:
            # Check if it points to "input"
            if re.search(r'package[-_]dir\s*=\s*\{.*?""\s*=\s*["\']input["\']', content):
                return False  # Already configured correctly
        
        # Try to add package-dir configuration
        # Look for [tool.setuptools] section
        if '[tool.setuptools]' in content:
            # Add package-dir right after [tool.setuptools] line
            pattern = r'(\[tool\.setuptools\]\s*\n)'
            replacement = r'\1package-dir = {"" = "input"}\n'
            new_content = re.sub(pattern, replacement, content)
            
            if new_content != content:
                with open(pyproject_path, 'w', encoding='utf-8') as f:
                    f.write(new_content)
                return True
        
        return False
    except Exception as e:
        print(f"Warning: Could not update pyproject.toml with package-dir: {e}")
        return False


def ensure_package_dir_in_pyproject(pyproject_path: str) -> bool:
    """Ensure pyproject.toml has package-dir configuration pointing to input/ directory.
    
    Returns True if updated, False if already had it or couldn't update.
    """
    try:
        with open(pyproject_path, 'r', encoding='utf-8') as f:
            content = f.read()
        
        # Check if package-dir already exists and points to "input"
        if re.search(r'package[-_]dir\s*=\s*\{.*?""\s*=\s*["\']input["\']', content):
            return False  # Already configured correctly
        
        # Try to add package-dir configuration
        # Look for [tool.setuptools] section
        if '[tool.setuptools]' in content:
            # Add package-dir right after [tool.setuptools] line
            pattern = r'(\[tool\.setuptools\]\s*\n)'
            replacement = r'\1package-dir = {"" = "input"}\n'
            new_content = re.sub(pattern, replacement, content)
            
            if new_content != content:
                with open(pyproject_path, 'w', encoding='utf-8') as f:
                    f.write(new_content)
                return True
        
        return False
    except Exception as e:
        print(f"Warning: Could not update pyproject.toml with package-dir: {e}")
        return False


def parse_existing_pyproject_toml(pyproject_path: str) -> dict:
    """Parse existing pyproject.toml and extract configuration."""
    config = {}
    
    try:
        # Try using tomli first (Python 3.11+)
        try:
            import tomli
            with open(pyproject_path, 'rb') as f:
                pyproject_data = tomli.load(f)
                project = pyproject_data.get('project', {})
                tool_setuptools = pyproject_data.get('tool', {}).get('setuptools', {})
                
                config = {
                    'package_name': project.get('name', ''),
                    'version': project.get('version', ''),
                    'description': project.get('description', ''),
                    'requires_python': project.get('requires-python', '>=3.8'),
                    'author_name': '',
                    'author_email': '',
                    'project_url': '',
                    'module_name': '',
                }
                
                # Extract author info
                authors = project.get('authors', [])
                if authors and len(authors) > 0:
                    config['author_name'] = authors[0].get('name', '')
                    config['author_email'] = authors[0].get('email', '')
                
                # Extract project URL
                urls = project.get('urls', {})
                if urls:
                    config['project_url'] = urls.get('Homepage', '')
                
                # Extract module name from setuptools packages or py-modules
                packages = tool_setuptools.get('packages', [])
                py_modules = tool_setuptools.get('py-modules', [])
                
                if packages and len(packages) > 0:
                    config['module_name'] = packages[0] if isinstance(packages, list) else packages
                    config['use_package'] = True
                elif py_modules and len(py_modules) > 0:
                    config['module_name'] = py_modules[0] if isinstance(py_modules, list) else py_modules
                    config['use_package'] = False
                elif isinstance(packages, str):
                    config['module_name'] = packages
                    config['use_package'] = True
                else:
                    config['use_package'] = True  # Default to package
                    
        except ImportError:
            # Fallback: simple text parsing using regex
            with open(pyproject_path, 'r', encoding='utf-8') as f:
                content = f.read()
                
                # Extract package name
                match = re.search(r'name\s*=\s*["\']([^"\']+)["\']', content)
                config['package_name'] = match.group(1) if match else ''
                
                # Extract version
                match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
                config['version'] = match.group(1) if match else ''
                
                # Extract description
                match = re.search(r'description\s*=\s*["\']([^"\']+)["\']', content)
                config['description'] = match.group(1) if match else ''
                
                # Extract requires-python
                match = re.search(r'requires-python\s*=\s*["\']([^"\']+)["\']', content)
                config['requires_python'] = match.group(1) if match else '>=3.8'
                
                # Extract author name (look specifically in authors section)
                author_match = re.search(r'authors\s*=\s*\[.*?\{.*?name\s*=\s*["\']([^"\']+)["\']', content, re.DOTALL)
                config['author_name'] = author_match.group(1) if author_match else ''
                
                # Extract author email (look specifically in authors section)
                email_match = re.search(r'authors\s*=\s*\[.*?\{.*?email\s*=\s*["\']([^"\']+)["\']', content, re.DOTALL)
                config['author_email'] = email_match.group(1) if email_match else ''
                
                # Extract project URL
                url_match = re.search(r'Homepage\s*=\s*["\']([^"\']+)["\']', content)
                config['project_url'] = url_match.group(1) if url_match else ''
                
                # Extract module name from packages or py-modules
                module_match = re.search(r'packages\s*=\s*\[["\']([^"\']+)["\']', content)
                if module_match:
                    config['module_name'] = module_match.group(1)
                    config['use_package'] = True
                else:
                    # Try py-modules
                    module_match = re.search(r'py-modules\s*=\s*\[["\']([^"\']+)["\']', content)
                    if module_match:
                        config['module_name'] = module_match.group(1)
                        config['use_package'] = False
                    else:
                        config['module_name'] = ''
                        config['use_package'] = True  # Default to package
                
    except Exception as e:
        print(f"Warning: Error parsing pyproject.toml: {e}")
        raise ValueError(f"Could not parse pyproject.toml: {e}")
    
    return config


def get_python_version_classifiers(requires_python: str) -> List[str]:
    """Generate Python version classifiers from requires-python string."""
    classifiers = []
    
    # Extract version numbers from strings like ">=3.8", ">=3.8,<4.0", etc.
    if "3.8" in requires_python:
        classifiers.append('    "Programming Language :: Python :: 3.8",')
    if "3.9" in requires_python:
        classifiers.append('    "Programming Language :: Python :: 3.9",')
    if "3.10" in requires_python:
        classifiers.append('    "Programming Language :: Python :: 3.10",')
    if "3.11" in requires_python:
        classifiers.append('    "Programming Language :: Python :: 3.11",')
    if "3.12" in requires_python:
        classifiers.append('    "Programming Language :: Python :: 3.12",')
    if "3.13" in requires_python:
        classifiers.append('    "Programming Language :: Python :: 3.13",')
    
    # Always add base Python 3 classifier
    if classifiers:
        classifiers.insert(0, '    "Programming Language :: Python :: 3",')
    
    return classifiers


def clear_package_directory(package_dir: str, preserve_init: bool = True) -> None:
    """Clear the package directory, optionally preserving __init__.py."""
    if not os.path.exists(package_dir):
        return
    
    init_file = os.path.join(package_dir, "__init__.py")
    init_content = None
    
    # Preserve __init__.py if requested
    if preserve_init and os.path.exists(init_file):
        with open(init_file, 'r', encoding='utf-8') as f:
            init_content = f.read()
    
    # Remove all contents except __init__.py
    for item in os.listdir(package_dir):
        item_path = os.path.join(package_dir, item)
        if item == "__init__.py" and preserve_init:
            continue
        if os.path.isdir(item_path):
            shutil.rmtree(item_path)
        else:
            os.remove(item_path)
    
    # Restore __init__.py if it was preserved
    if preserve_init and init_content:
        with open(init_file, 'w', encoding='utf-8') as f:
            f.write(init_content)


def complete_copy(source_dir: str, dest_dir: str) -> None:
    """Copy all files and directories from source to destination."""
    print(f"Copying all files from {source_dir} to {dest_dir}...")
    
    if not os.path.exists(dest_dir):
        os.makedirs(dest_dir, exist_ok=True)
    else:
        # Clear existing files before copying (preserve __init__.py)
        print("Clearing existing files in destination directory...")
        clear_package_directory(dest_dir, preserve_init=True)
    
    # Handle single file source
    if os.path.isfile(source_dir):
        dest_file = os.path.join(dest_dir, os.path.basename(source_dir))
        shutil.copy2(source_dir, dest_file)
        print("✓ Complete copy finished.")
        return
    
    # Copy everything from directory
    for item in os.listdir(source_dir):
        source_path = os.path.join(source_dir, item)
        dest_path = os.path.join(dest_dir, item)
        
        # Skip if destination already exists (to avoid overwriting __init__.py)
        if os.path.exists(dest_path):
            if os.path.isdir(dest_path):
                # Merge directories
                if os.path.isdir(source_path):
                    for subitem in os.listdir(source_path):
                        sub_source = os.path.join(source_path, subitem)
                        sub_dest = os.path.join(dest_path, subitem)
                        if os.path.isdir(sub_source):
                            shutil.copytree(sub_source, sub_dest, dirs_exist_ok=True)
                        else:
                            shutil.copy2(sub_source, sub_dest)
                continue
            elif item == "__init__.py":
                # Don't overwrite the __init__.py we created
                continue
        
        if os.path.isdir(source_path):
            shutil.copytree(source_path, dest_path, dirs_exist_ok=True)
        else:
            shutil.copy2(source_path, dest_path)
    
    print("✓ Complete copy finished.")


def compact_copy(source_dir: str, dest_dir: str) -> None:
    """Copy only necessary files, excluding PDB files, test files, etc."""
    print(f"Copying necessary files from {source_dir} to {dest_dir}...")
    
    # File extensions to exclude
    exclude_extensions = {
        '.pdb',  # Program database files (debug symbols)
        '.ilk',  # Incremental linker files
        '.exp',  # Export files
        '.lib',  # Static library files (usually not needed for runtime)
        '.map',  # Map files
    }
    
    # Directory names to exclude (case-insensitive)
    exclude_dirs = {
        'test', 'tests', 'testing', '__pycache__', '.git', '.svn', '.hg',
        '.idea', '.vscode', 'build', 'dist', '.egg-info', 'venv', 'env',
        '.pytest_cache', '.mypy_cache', '.tox', '.nox'
    }
    
    if not os.path.exists(dest_dir):
        os.makedirs(dest_dir, exist_ok=True)
    else:
        # Clear existing files before copying (preserve __init__.py)
        # This ensures a clean state when switching from complete to compact
        print("Clearing existing files in destination directory...")
        clear_package_directory(dest_dir, preserve_init=True)
    
    # Handle single file source
    if os.path.isfile(source_dir):
        file_ext = os.path.splitext(source_dir)[1].lower()
        file_lower = os.path.basename(source_dir).lower()
        
        # Check if file should be excluded
        if file_ext in exclude_extensions:
            print(f"✓ Compact copy finished. Skipped excluded file: {os.path.basename(source_dir)}")
            return
        
        if file_lower.endswith(('.pyc', '.pyo')):
            print(f"✓ Compact copy finished. Skipped Python cache file: {os.path.basename(source_dir)}")
            return
        
        # Copy the single file
        dest_file = os.path.join(dest_dir, os.path.basename(source_dir))
        shutil.copy2(source_dir, dest_file)
        print(f"✓ Compact copy finished. Copied 1 file.")
        return
    
    copied_count = 0
    skipped_count = 0
    
    for root, dirs, files in os.walk(source_dir):
        # Filter out excluded directories
        dirs[:] = [d for d in dirs if d.lower() not in exclude_dirs]
        
        # Calculate relative path
        rel_path = os.path.relpath(root, source_dir)
        
        # Skip excluded directories
        path_parts = rel_path.split(os.sep)
        if any(part.lower() in exclude_dirs for part in path_parts):
            continue
        
        # Create destination directory structure
        if rel_path != '.':
            dest_subdir = os.path.join(dest_dir, rel_path)
            os.makedirs(dest_subdir, exist_ok=True)
        else:
            dest_subdir = dest_dir
        
        # Copy files
        for file in files:
            file_path = os.path.join(root, file)
            file_ext = os.path.splitext(file)[1].lower()
            file_lower = file.lower()
            
            # Skip excluded extensions
            if file_ext in exclude_extensions:
                skipped_count += 1
                continue
            
            # Skip Python cache files
            if file_lower.endswith(('.pyc', '.pyo')) or file == '__pycache__':
                skipped_count += 1
                continue
            
            # Skip test files
            if 'test' in file_lower and file_lower.startswith('test_'):
                skipped_count += 1
                continue
            
            # Skip PDB files (case-insensitive)
            if file_lower.endswith('.pdb'):
                skipped_count += 1
                continue
            
            # Skip if it's __init__.py in root (we already created it)
            if rel_path == '.' and file == '__init__.py':
                skipped_count += 1
                continue
            
            # Copy the file
            dest_file = os.path.join(dest_subdir, file)
            shutil.copy2(file_path, dest_file)
            copied_count += 1
    
    print(f"✓ Compact copy finished. Copied {copied_count} files, skipped {skipped_count} files.")


def create_pyproject_toml(config: dict, example_path: str = "pyproject.toml.example") -> str:
    """Create pyproject.toml content based on user configuration."""
    
    # Read example file if it exists
    example_content = ""
    if os.path.exists(example_path):
        with open(example_path, 'r', encoding='utf-8') as f:
            example_content = f.read()
    
    # Generate Python version classifiers
    python_classifiers = get_python_version_classifiers(config['requires_python'])
    
    # Build pyproject.toml content
    content = """[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "{package_name}"
version = "{version}"
description = "{description}"
readme = "README.md"
requires-python = "{requires_python}"
license = "BSD-3-Clause"
authors = [
    {{name = "{author_name}", email = "{author_email}"}}
]
urls = {{Homepage = "{project_url}"}}
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "Intended Audience :: Science/Research",
    # Note: License classifier removed - using SPDX license expression instead (license = "BSD-3-Clause")
""".format(**config)
    
    # Add Python version classifiers
    for classifier in python_classifiers:
        content += classifier + "\n"
    
    content += """    "Topic :: Scientific/Engineering :: Image Processing",
    "Topic :: Multimedia :: Video",
    # Platform-specific classifiers will be added dynamically during build
]

""".format(**config)
    
    # Add setuptools configuration based on distribution type
    module_name = config['module_name']
    use_package = config.get('use_package', True)  # Default to package if not specified
    
    if use_package:
        # Package distribution (folder with __init__.py)
        content += """[tool.setuptools]
package-dir = {{"" = "input"}}
packages = ["{module_name}"]
include-package-data = true

[tool.setuptools.package-data]
{module_name} = ["**/*"]

# Note: For PyPI distribution, all package files must be in the input/{module_name}/ directory
# The package-dir configuration tells setuptools to look for packages in the input/ directory
# README.md is automatically included via [project] readme setting
# LICENSE is included via MANIFEST.in (created automatically)
""".format(module_name=module_name)
    else:
        # Py-modules distribution (single file)
        content += """[tool.setuptools]
package-dir = {{"" = "input"}}
py-modules = ["{module_name}"]
include-package-data = true

# Note: For PyPI distribution, the module file must be in the input/ directory
# as {module_name}.py (single file, no folder structure needed)
# README.md is automatically included via [project] readme setting
# LICENSE is automatically included via [project] license-files setting
""".format(module_name=module_name)
    
    return content


def create_manifest_in(cwd: str) -> None:
    """Create MANIFEST.in file to ensure README.md, LICENSE, and CLA.md are included in distribution."""
    manifest_path = os.path.join(cwd, "MANIFEST.in")
    
    # Check if easywheel/README.md exists relative to project directory
    easywheel_readme = os.path.join(cwd, "easywheel", "README.md")
    has_easywheel_readme = os.path.exists(easywheel_readme)
    
    # Check if CLA.md exists
    cla_path = os.path.join(cwd, "CLA.md")
    has_cla = os.path.exists(cla_path)
    
    manifest_content = """# Include README.md and LICENSE files in distribution
include README.md
include LICENSE"""
    
    # Include CLA.md if it exists
    if has_cla:
        manifest_content += "\n# Include Contributor License Agreement\ninclude CLA.md"
    
    # Include easywheel/README.md if it exists
    if has_easywheel_readme:
        manifest_content += "\n# Include README.md from easywheel subdirectory\ninclude easywheel/README.md"
    
    manifest_content += "\nrecursive-include input *\n"
    
    with open(manifest_path, 'w', encoding='utf-8') as f:
        f.write(manifest_content)
    
    print(f"✓ Created {manifest_path}")
    if has_cla:
        print(f"  → Included CLA.md")
    if has_easywheel_readme:
        print(f"  → Included easywheel/README.md")


def create_init_file(module_name: str, version: str, package_dir: str) -> None:
    """Create __init__.py file in the package directory."""
    init_path = os.path.join(package_dir, "__init__.py")
    
    content = f'''"""Package initialization for {module_name}."""

__version__ = "{version}"
'''
    
    with open(init_path, 'w', encoding='utf-8') as f:
        f.write(content)
    
    print(f"✓ Created {init_path}")


def check_build_requirements(pyproject_path: str, package_dir: str, use_package: bool = True) -> bool:
    """Check if pyproject.toml and Package Directory exist."""
    if not os.path.exists(pyproject_path):
        print(f"✗ Error: pyproject.toml not found at {pyproject_path}")
        return False
    
    if not os.path.exists(package_dir):
        print(f"✗ Error: Package directory not found at {package_dir}")
        return False
    
    if use_package:
        # Package distribution: check for directory and __init__.py
        if not os.path.isdir(package_dir):
            print(f"✗ Error: Package directory path is not a directory: {package_dir}")
            return False
        
        # Check if __init__.py exists
        init_file = os.path.join(package_dir, "__init__.py")
        if not os.path.exists(init_file):
            print(f"✗ Warning: __init__.py not found in package directory")
            print(f"  Creating it now...")
            # Extract module name from package_dir
            module_name = os.path.basename(package_dir)
            # Try to read version from pyproject.toml
            version = '0.0.0'
            try:
                # Try using tomli first (Python 3.11+)
                try:
                    import tomli
                    with open(pyproject_path, 'rb') as f:
                        pyproject_data = tomli.load(f)
                        version = pyproject_data.get('project', {}).get('version', '0.0.0')
                except ImportError:
                    # Fallback: simple text parsing
                    with open(pyproject_path, 'r', encoding='utf-8') as f:
                        content = f.read()
                        match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
                        if match:
                            version = match.group(1)
            except Exception as e:
                print(f"  Warning: Could not extract version from pyproject.toml: {e}")
            create_init_file(module_name, version, package_dir)
    else:
        # Py-modules distribution: check for .py file(s) in input directory
        if not os.path.isdir(package_dir):
            print(f"✗ Error: Input directory path is not a directory: {package_dir}")
            return False
        
        # Check if there's at least one .py file
        py_files = [f for f in os.listdir(package_dir) if f.endswith('.py')]
        if not py_files:
            print(f"✗ Error: No Python files found in {package_dir}")
            print(f"  Py-modules distribution requires at least one .py file")
            return False
        
        # Try to extract module name from pyproject.toml to verify
        try:
            import tomli
            with open(pyproject_path, 'rb') as f:
                pyproject_data = tomli.load(f)
                py_modules = pyproject_data.get('tool', {}).get('setuptools', {}).get('py-modules', [])
                if py_modules:
                    expected_module = py_modules[0]
                    expected_file = f"{expected_module}.py"
                    if expected_file not in py_files:
                        print(f"⚠ Warning: Expected {expected_file} but found: {', '.join(py_files)}")
        except:
            pass  # If we can't parse, just continue
    
    return True


def display_build_instructions(build_info: dict, output_dir: str, cwd: str) -> None:
    """Display post-build instructions for installing, distributing, and using the package.
    
    Args:
        build_info: Dictionary containing package_name, version, wheel_file, sdist_file
        output_dir: Output directory where built packages are located
        cwd: Project directory (current working directory for build)
    """
    package_name = build_info.get('package_name', 'your-package')
    version = build_info.get('version', 'x.x.x')
    wheel_file = build_info.get('wheel_file')
    sdist_file = build_info.get('sdist_file')
    
    print("\n" + "=" * 60)
    print("📦 Post-Build Instructions")
    print("=" * 60)
    
    # 1. Install locally built distribution
    print("\n1️⃣  Install the locally built distribution:")
    print("   " + "-" * 56)
    if wheel_file:
        wheel_path = os.path.join(output_dir, wheel_file)
        # Use forward slashes for better cross-platform compatibility in instructions
        wheel_path_display = wheel_path.replace(os.sep, '/')
        print(f"   pip install \"{wheel_path_display}\"")
        print(f"   # Or using relative path from project directory:")
        try:
            rel_wheel_path = os.path.relpath(wheel_path, cwd).replace(os.sep, '/')
            print(f"   cd {os.path.basename(cwd)}")
            print(f"   pip install \"{rel_wheel_path}\"")
        except ValueError:
            # If paths are on different drives (Windows), use absolute path
            print(f"   pip install \"{wheel_path_display}\"")
    elif sdist_file:
        sdist_path = os.path.join(output_dir, sdist_file)
        sdist_path_display = sdist_path.replace(os.sep, '/')
        print(f"   pip install \"{sdist_path_display}\"")
        print(f"   # Or using relative path from project directory:")
        try:
            rel_sdist_path = os.path.relpath(sdist_path, cwd).replace(os.sep, '/')
            print(f"   cd {os.path.basename(cwd)}")
            print(f"   pip install \"{rel_sdist_path}\"")
        except ValueError:
            # If paths are on different drives (Windows), use absolute path
            print(f"   pip install \"{sdist_path_display}\"")
    else:
        output_dir_display = output_dir.replace(os.sep, '/')
        print(f"   pip install \"{output_dir_display}\"")
    
    # 2. Upload to PyPI
    print("\n2️⃣  Upload to PyPI:")
    print("   " + "-" * 56)
    print("   # First, install twine (if not already installed):")
    print("   pip install twine")
    print()
    print("   # TestPyPI (Recommended for testing first):")
    print("   # TestPyPI is a separate test instance of PyPI where you can")
    print("   # upload and test packages without affecting the real PyPI.")
    print("   # Create account at: https://test.pypi.org/account/register/")
    print("   # Create API token at: https://test.pypi.org/manage/account/token/")
    print()
    print("   # Upload to TestPyPI:")
    print(f"   twine upload --repository testpypi {output_dir}/*")
    print()
    print("   # Test installation from TestPyPI:")
    print(f"   pip install --index-url https://test.pypi.org/simple/ {package_name}")
    print()
    print("   # PyPI (Production - after testing):")
    print("   # Create account at: https://pypi.org/account/register/")
    print("   # Create API token at: https://pypi.org/manage/account/token/")
    print()
    print("   # Upload to PyPI (production):")
    print(f"   twine upload {output_dir}/*")
    print()
    print("   # Note: Use API tokens instead of username/password for better security")
    print("   # Configure credentials: ~/.pypirc or use environment variables")
    
    # 3. Install from PyPI (for end users)
    print("\n3️⃣  For end users to install from PyPI:")
    print("   " + "-" * 56)
    print(f"   pip install {package_name}")
    print()
    print("   # Install specific version:")
    print(f"   pip install {package_name}=={version}")
    print()
    print("   # Upgrade to latest version:")
    print(f"   pip install --upgrade {package_name}")
    
    print("\n" + "=" * 60)


def run_build_process(cwd: str, output_dir: str = None, override_output: bool = True) -> tuple:
    """Run the build process using python -m build.
    
    Args:
        cwd: Project directory where pyproject.toml is located (project directory)
        output_dir: Output directory for built packages (default: <PROJECT_DIR>/output/dist/)
        override_output: If True, clear existing output before building. If False, preserve existing files.
    
    Returns:
        tuple: (success: bool, build_info: dict)
            - success: True if build succeeded, False otherwise
            - build_info: Dictionary containing package_name, version, wheel_file, sdist_file, output_dir
    """
    print("\n" + "=" * 60)
    print("Starting build process...")
    print("=" * 60)
    
    try:
        # Dependencies should already be verified at startup via check_dependencies(),
        # but double-check here as a safety measure in case the module was removed
        # between verification and execution
        try:
            import build
        except ImportError:
            print("✗ Error: 'build' module not found.")
            print("  Dependencies should have been verified at startup.")
            print("  Please install it with: pip install build")
            print("  Or run the script again to verify all dependencies.")
            return False, {}
        
        # Set default output directory if not specified
        if output_dir is None:
            output_dir = os.path.join(cwd, "output", "dist")
        
        # Handle output directory override/preservation
        if override_output and os.path.exists(output_dir) and os.listdir(output_dir):
            print(f"ℹ Clearing existing output directory: {output_dir}")
            # Clear existing output directory
            for item in os.listdir(output_dir):
                item_path = os.path.join(output_dir, item)
                if os.path.isdir(item_path):
                    shutil.rmtree(item_path)
                else:
                    os.remove(item_path)
            print("✓ Cleared existing output directory")
        elif not override_output:
            print(f"ℹ Preserving existing output directory: {output_dir}")
            print("   New build files will be added alongside existing files")
        
        # Ensure output directory exists
        os.makedirs(output_dir, exist_ok=True)
        
        # Run the build command with output directory
        build_cmd = [sys.executable, "-m", "build", "--outdir", output_dir]
        print(f"Running: {' '.join(build_cmd)}")
        print(f"Output directory: {output_dir}")
        print()
        
        result = subprocess.run(
            build_cmd,
            cwd=cwd,
            check=False,
            capture_output=False
        )
        
        if result.returncode == 0:
            print("\n" + "=" * 60)
            print("✓ Build completed successfully!")
            print("=" * 60)
            
            # Extract package info from pyproject.toml
            build_info = {
                'package_name': None,
                'version': None,
                'wheel_file': None,
                'sdist_file': None,
                'output_dir': output_dir
            }
            
            # Try to read package name and version from pyproject.toml
            pyproject_path = os.path.join(cwd, "pyproject.toml")
            try:
                with open(pyproject_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                    # Extract package name
                    name_match = re.search(r'name\s*=\s*["\']([^"\']+)["\']', content)
                    if name_match:
                        build_info['package_name'] = name_match.group(1)
                    # Extract version
                    version_match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
                    if version_match:
                        build_info['version'] = version_match.group(1)
            except Exception:
                pass
            
            # Check for output directory and find built files
            if os.path.exists(output_dir):
                print(f"\nBuilt packages are in: {output_dir}")
                dist_files = os.listdir(output_dir)
                if dist_files:
                    print("Files created:")
                    for file in dist_files:
                        file_path = os.path.join(output_dir, file)
                        if os.path.isfile(file_path):
                            size = os.path.getsize(file_path)
                            size_mb = size / (1024 * 1024)
                            print(f"  - {file} ({size_mb:.2f} MB)")
                            
                            # Identify wheel and source distribution files
                            if file.endswith('.whl'):
                                build_info['wheel_file'] = file
                            elif file.endswith('.tar.gz'):
                                build_info['sdist_file'] = file
                        else:
                            print(f"  - {file}/ (directory)")
            
            return True, build_info
        else:
            print("\n" + "=" * 60)
            print("✗ Build failed!")
            print("=" * 60)
            print(f"Exit code: {result.returncode}")
            return False, {}
            
    except subprocess.CalledProcessError as e:
        print(f"\n✗ Build process error: {e}")
        return False, {}
    except Exception as e:
        print(f"\n✗ Unexpected error during build: {e}")
        import traceback
        traceback.print_exc()
        return False, {}


def interactive_session() -> tuple:
    """Run the interactive session to collect package configuration.
    
    Returns:
        tuple: (config, source_dir, is_complete, reuse_existing)
            - config: Configuration dictionary
            - source_dir: Source directory path
            - is_complete: True for complete package, False for compact
            - reuse_existing: True if reusing existing project directory (skip file modifications)
    """
    print("=" * 60)
    print("Python Package Setup Wizard")
    print("=" * 60)
    print()
    
    # Question 1: Source directory/file
    print("1. Which directory or file do you want to build a package from?")
    source_dir = ask_question(
        "   Enter the directory or file path",
        validator=validate_source_path
    )
    source_dir = os.path.abspath(source_dir)
    print(f"   → Using: {source_dir}\n")
    
    # Auto-detect distribution type based on source
    auto_use_package = None  # None means we'll ask the user
    if os.path.isdir(source_dir):
        py_files = [f for f in os.listdir(source_dir) if f.endswith('.py')]
        if len(py_files) > 1:
            # Multiple Python files - must use package distribution
            auto_use_package = True
            print("ℹ Detected multiple Python files in source directory.")
            print("   Automatically using 'package' distribution (py-modules requires single file).\n")
        elif len(py_files) == 1:
            # Single Python file in directory - can use either
            auto_use_package = None  # Ask user
        else:
            # No Python files - default to package
            auto_use_package = True
            print("ℹ No Python files detected in source directory.")
            print("   Defaulting to 'package' distribution.\n")
    elif os.path.isfile(source_dir):
        # Single file source - can use py-modules
        if source_dir.endswith('.py'):
            auto_use_package = None  # Ask user
        else:
            # Not a Python file - default to package
            auto_use_package = True
            print("ℹ Source is not a Python file.")
            print("   Defaulting to 'package' distribution.\n")
    
    # Question 2: Package name
    print("2. What is the package name? (Use hyphen if needed)")
    package_name = ask_question("   Enter package name", validator=validate_package_name)
    print(f"   → Package name: {package_name}\n")
    
    # Create project directory based on package name
    base_cwd = os.getcwd()
    project_dir = os.path.join(base_cwd, package_name)
    reuse_existing = False
    
    # Check if project directory already exists
    if os.path.exists(project_dir):
        print(f"ℹ Project directory already exists: {project_dir}")
        choice = ask_question(
            "   Do you want to use the existing directory? ('y' to use existing, 'n' to override it)",
            validator=validate_yes_no
        )
        if choice.lower() not in ['y', 'yes']:
            # Override: delete existing directory and create a fresh one
            print(f"   ⚠ Overriding existing directory: {project_dir}")
            print(f"   → This will delete all contents in the existing directory!")
            confirm = ask_question(
                "   Are you sure you want to override? ('y' to confirm, 'n' to cancel)",
                validator=validate_yes_no
            )
            if confirm.lower() in ['y', 'yes']:
                # Remove existing directory
                if os.path.isdir(project_dir):
                    shutil.rmtree(project_dir)
                else:
                    os.remove(project_dir)
                print(f"   → Deleted existing directory")
                # Create fresh directory
                os.makedirs(project_dir, exist_ok=True)
                print(f"   → Created new project directory: {project_dir}\n")
            else:
                # User cancelled override - create new directory with suffix instead
                print(f"   → Cancelled override. Creating new directory with suffix...")
                counter = 1
                new_project_dir = os.path.join(base_cwd, f"{package_name}_{counter}")
                while os.path.exists(new_project_dir):
                    counter += 1
                    new_project_dir = os.path.join(base_cwd, f"{package_name}_{counter}")
                project_dir = new_project_dir
                os.makedirs(project_dir, exist_ok=True)
                print(f"   → Created new project directory: {project_dir}\n")
        else:
            # Use existing directory - check if pyproject.toml exists
            reuse_existing = True
            os.makedirs(project_dir, exist_ok=True)
            print(f"   → Using existing project directory: {project_dir}\n")
            
            # Check if pyproject.toml exists in project directory
            project_pyproject = os.path.join(project_dir, "pyproject.toml")
            if os.path.exists(project_pyproject):
                print(f"ℹ Found existing pyproject.toml in project directory")
                print(f"   → Will reuse existing configuration and skip file modifications\n")
    else:
        print(f"ℹ Creating project directory: {project_dir}\n")
        # Create project directory
        os.makedirs(project_dir, exist_ok=True)
        print(f"✓ Project directory created: {project_dir}\n")
    
    # Check if we're reusing existing directory with pyproject.toml
    project_pyproject = os.path.join(project_dir, "pyproject.toml")
    if reuse_existing and os.path.exists(project_pyproject):
        # Parse existing pyproject.toml and skip all configuration questions
        print("=" * 60)
        print("Reusing Existing Project Directory")
        print("=" * 60)
        print(f"\nFound existing pyproject.toml in: {project_dir}")
        print("→ Will reuse existing configuration")
        print("→ No files will be modified\n")
        
        try:
            config = parse_existing_pyproject_toml(project_pyproject)
            config['project_dir'] = project_dir
            
            # Detect platform compatibility (for information only, won't modify files)
            print("Analyzing source directory for platform compatibility...")
            platform_info = detect_platform_compatibility(source_dir)
            platform_info['detected_platforms'] = platform_info['platforms']
            config['platform_info'] = platform_info
            
            # Display configuration from pyproject.toml
            print("\nConfiguration from existing pyproject.toml:")
            print(f"  Package name: {config.get('package_name', 'N/A')}")
            print(f"  Module name: {config.get('module_name', 'N/A')}")
            print(f"  Version: {config.get('version', 'N/A')}")
            print(f"  Description: {config.get('description', 'N/A')}")
            print(f"  Python requirement: {config.get('requires_python', 'N/A')}")
            print()
            
            # Check if output directory exists and ask user what to do
            output_dir_path = os.path.join(project_dir, "output", "dist")
            output_override_choice = None
            
            if os.path.exists(output_dir_path) and os.listdir(output_dir_path):
                print("ℹ Found existing output directory with files:")
                print(f"   Location: {output_dir_path}")
                try:
                    output_files = [f for f in os.listdir(output_dir_path) if os.path.isfile(os.path.join(output_dir_path, f))]
                    if output_files:
                        print(f"   Files found: {len(output_files)} file(s)")
                        for f in output_files[:3]:
                            print(f"     - {f}")
                        if len(output_files) > 3:
                            print(f"     ... and {len(output_files) - 3} more")
                except:
                    pass
                
                print("\nWhat would you like to do with the existing output?")
                print("  1. Override it (delete existing output and create new)")
                print("  2. Preserve it (keep existing output, build will add new files)")
                print("  3. Cancel (exit without building)")
                print()
                output_choice = ask_question(
                    "Enter your choice (1, 2, or 3)",
                    validator=validate_output_choice
                )
                output_override_choice = output_choice
                
                if output_choice == '1':
                    print("   → Will override existing output directory\n")
                elif output_choice == '2':
                    print("   → Will preserve existing output directory\n")
                elif output_choice == '3':
                    print("\n" + "=" * 60)
                    print("Cancelled by user.")
                    print("=" * 60)
                    print(f"\nExisting output directory preserved: {output_dir_path}")
                    print("=" * 60)
                    return None, None, None, True  # Return None to signal cancellation
            
            # Only ask for package type if needed for building (but won't modify files)
            # Default to compact to avoid unnecessary file operations
            is_complete = False
            print("ℹ Using existing project directory - files will not be modified")
            print("   Package type set to 'compact' (no file copying will occur)\n")
            
            # Store output override choice in config
            config['output_override_choice'] = output_override_choice
            
            return config, source_dir, is_complete, True
            
        except Exception as e:
            print(f"\n✗ Error parsing existing pyproject.toml: {e}")
            print("Falling back to full interactive session...\n")
            reuse_existing = False
    
    # Continue with full interactive session if not reusing or if parsing failed
    # Question 3: Importable module name
    print("3. What is the importable module name? (Use underscore if needed)")
    module_name = ask_question("   Enter module name", validator=validate_module_name)
    print(f"   → Module name: {module_name}\n")
    
    # Question 4: Version
    print("4. What is the package version?")
    version = ask_question("   Enter version")
    print(f"   → Version: {version}\n")
    
    # Question 5: Description
    print("5. What is the package description?")
    description = ask_question("   Enter description")
    print(f"   → Description: {description}\n")
    
    # Detect Python version requirement from source code
    print("Analyzing source code for Python version requirements...")
    detected_python_version = detect_python_version_requirement(source_dir)
    
    if detected_python_version:
        print(f"ℹ Detected Python version requirement: {detected_python_version}")
        print("   (Based on syntax features and imports found in source code)")
    else:
        print("ℹ Could not detect Python version requirement from source code")
        detected_python_version = ">=3.8"  # Fallback to default
    print()
    
    # Question 6: Python version requirement
    print("6. What is the python version requirement?")
    requires_python = ask_question("   Enter requirement", default=detected_python_version)
    print(f"   → Python requirement: {requires_python}\n")
    
    # Question 7: Author name
    print("7. What is author name?")
    author_name = ask_question("   Enter author name")
    print(f"   → Author: {author_name}\n")
    
    # Question 8: Author email
    print("8. What is author email?")
    author_email = ask_question("   Enter email", validator=validate_email)
    print(f"   → Email: {author_email}\n")
    
    # Question 9: Project URL
    print("9. What is project url?")
    print("   (Must start with http:// or https://, e.g., https://github.com/username/project)")
    project_url = ask_question("   Enter URL", validator=validate_url)
    # Normalize URL (add https:// if missing)
    project_url = normalize_url(project_url)
    print(f"   → URL: {project_url}\n")
    
    # Detect platform compatibility before asking distribution type
    print("\nAnalyzing source directory for platform compatibility...")
    platform_info = detect_platform_compatibility(source_dir)
    detected_platforms = platform_info['platforms']
    has_binaries = platform_info['has_binaries']
    
    if has_binaries:
        platform_list = ', '.join(sorted(detected_platforms))
        print(f"ℹ Detected platform-specific binaries: {platform_list}")
        if len(detected_platforms) > 1:
            print("   ⚠ Multiple platforms detected - consider building separate wheels per platform")
        elif 'any' not in detected_platforms:
            print(f"   → Source appears to be for {platform_list} platform(s)")
        if platform_info['platform_specific_files']:
            print("   Sample files found:")
            for file_info in platform_info['platform_specific_files'][:5]:
                print(f"     - {file_info}")
            if len(platform_info['platform_specific_files']) > 5:
                print(f"     ... and {len(platform_info['platform_specific_files']) - 5} more")
    else:
        print("ℹ No platform-specific binaries detected - suitable for universal wheel (-none-any)")
    print()
    
    # Question 10: Distribution type (skip if auto-detected)
    if auto_use_package is None:
        # Ask user to choose
        print("10. What distribution type do you want?")
        print("    Press 1 or 2 -- 1: package (folder with __init__.py, default)  2: py-modules (single file)")
        dist_type_choice = ask_question("   Enter choice (1 or 2)", default="1", validator=validate_distribution_type)
        use_package = dist_type_choice == "1"
        print(f"   → Distribution type: {'Package' if use_package else 'Py-modules'}\n")
    else:
        # Auto-detected - skip the question
        use_package = auto_use_package
        dist_type_name = 'Package' if use_package else 'Py-modules'
        print(f"10. Distribution type: {dist_type_name} (auto-selected)")
        print(f"   → Distribution type: {dist_type_name}\n")
    
    # Store platform info in config for later use
    platform_info['detected_platforms'] = detected_platforms
    platform_info['has_binaries'] = has_binaries
    
    # Question 11: Package type (complete/compact)
    print("11. Do you want a complete or compact package?")
    print("    Press 1 or 2 -- 1: complete  2: compact")
    package_type = ask_question("   Enter choice (1 or 2)", validator=validate_package_choice)
    is_complete = package_type == "1"
    print(f"   → Package type: {'Complete' if is_complete else 'Compact'}\n")
    
    # Create configuration dictionary
    # Note: use_package may have been changed by validation above
    config = {
        'package_name': package_name,
        'module_name': module_name,
        'version': version,
        'description': description,
        'requires_python': requires_python,
        'author_name': author_name,
        'author_email': author_email,
        'project_url': project_url,
        'use_package': use_package,  # True for package, False for py-modules (may have been auto-corrected)
        'platform_info': platform_info,  # Store platform detection results
        'project_dir': project_dir,  # Store project directory path
    }
    
    return config, source_dir, is_complete, False


def setup_package(config: dict, source_dir: str, is_complete: bool, cwd: str, 
                  pyproject_path: str = None, skip_pyproject: bool = False, skip_copy: bool = False) -> tuple:
    """Set up the package directory and optionally create pyproject.toml.
    
    Args:
        config: Package configuration dictionary
        source_dir: Source directory or file path
        is_complete: True for complete package, False for compact
        cwd: Current working directory
        pyproject_path: Optional path to pyproject.toml (defaults to cwd/pyproject.toml)
        skip_pyproject: If True, skip creating pyproject.toml
        skip_copy: If True, skip copying source files to input folder
    """
    module_name = config['module_name']
    version = config['version']
    use_package = config.get('use_package', True)  # Default to package if not specified
    
    # Create input directory if it doesn't exist
    input_dir = os.path.join(cwd, "input")
    os.makedirs(input_dir, exist_ok=True)
    
    # Determine package directory
    if use_package:
        package_dir = os.path.join(input_dir, module_name)
    else:
        package_dir = input_dir
    
    # Handle file copying if not skipped
    if not skip_copy:
        print("=" * 60)
        print("Creating package structure...")
        print("=" * 60)
        
        if use_package:
            # Package distribution: create folder structure with __init__.py
            # Create package directory
            os.makedirs(package_dir, exist_ok=True)
            
            # Check if package directory already exists and has files
            # Clear existing files to ensure clean state when switching between complete/compact
            if os.path.exists(package_dir) and os.listdir(package_dir):
                print(f"Package directory already exists. Clearing existing files for clean state...")
                clear_package_directory(package_dir, preserve_init=False)
            
            print(f"✓ Created/cleared package directory: {package_dir}")
            
            # Create __init__.py
            create_init_file(module_name, version, package_dir)
            
            # Copy files based on user choice
            print(f"\nCopying files from {source_dir}...")
            if is_complete:
                complete_copy(source_dir, package_dir)
            else:
                compact_copy(source_dir, package_dir)
        else:
            # Py-modules distribution: single file, no folder structure
            # Check if input directory has files and clear them
            if os.path.exists(input_dir) and os.listdir(input_dir):
                print(f"Input directory already has files. Clearing for clean state...")
                for item in os.listdir(input_dir):
                    item_path = os.path.join(input_dir, item)
                    if os.path.isdir(item_path):
                        shutil.rmtree(item_path)
                    else:
                        os.remove(item_path)
            
            print(f"✓ Prepared input directory: {input_dir}")
            
            # Copy files based on user choice
            print(f"\nCopying files from {source_dir}...")
            if is_complete:
                complete_copy(source_dir, package_dir)
            else:
                compact_copy(source_dir, package_dir)
            
            # For py-modules, ensure the main module file exists with correct name
            # Check if source is a single file
            if os.path.isfile(source_dir):
                # Source is a single file
                expected_file = os.path.join(input_dir, f"{module_name}.py")
                if not os.path.exists(expected_file):
                    # Rename the copied file to match module name
                    copied_files = [f for f in os.listdir(input_dir) if f.endswith('.py')]
                    if copied_files:
                        old_file = os.path.join(input_dir, copied_files[0])
                        if old_file != expected_file:
                            shutil.move(old_file, expected_file)
                            print(f"✓ Renamed file to {module_name}.py")
            else:
                # Source is a directory - find the main Python file
                py_files = [f for f in os.listdir(input_dir) if f.endswith('.py')]
                if py_files and not any(f == f"{module_name}.py" for f in py_files):
                    # If there's a single Python file, rename it to match module name
                    if len(py_files) == 1:
                        old_file = os.path.join(input_dir, py_files[0])
                        new_file = os.path.join(input_dir, f"{module_name}.py")
                        shutil.move(old_file, new_file)
                        print(f"✓ Renamed {py_files[0]} to {module_name}.py")
    else:
        # Copy is skipped, but we still need to determine package_dir for return value
        if use_package:
            package_dir = os.path.join(input_dir, module_name)
        else:
            package_dir = input_dir
    
    # Create pyproject.toml if not skipping
    if not skip_pyproject:
        if pyproject_path is None:
            pyproject_path = os.path.join(cwd, "pyproject.toml")
        
        print("\nGenerating pyproject.toml...")
        pyproject_content = create_pyproject_toml(config)
        
        with open(pyproject_path, 'w', encoding='utf-8') as f:
            f.write(pyproject_content)
        
        print(f"✓ Created {pyproject_path}")
        
        # Create MANIFEST.in to ensure README.md and LICENSE are included
        create_manifest_in(cwd)
    
    return package_dir, pyproject_path


def parse_arguments():
    """Parse command-line arguments."""
    parser = argparse.ArgumentParser(
        description="Interactive script to set up and build a Python package for PyPI distribution."
    )
    parser.add_argument(
        "-o", "--output",
        dest="output_dir",
        type=str,
        default=None,
        help="Output directory for built packages (default: <CWD>/output/dist/)"
    )
    return parser.parse_args()


def main():
    """Main function that checks for existing pyproject.toml and handles setup."""
    # Verify dependencies first
    deps_available, missing_deps = check_dependencies()
    
    # If dependencies are missing, ask user what they want to do
    skip_build = False
    skip_pyproject = False
    skip_copy = False
    
    if not deps_available:
        missing_list = ', '.join(missing_deps)
        print(f"\n⚠ Warning: Dependencies ({missing_list}) are not installed.")
        print("   The build process will be skipped, but you can still prepare the package.")
        print()
        print("What would you like to do?")
        print("  1. Create pyproject.toml only")
        print("  2. Copy source to input folder only")
        print("  3. Prepare both pyproject.toml and input folder")
        print("  4. Cancel")
        print()
        choice = ask_question(
            f"Dependencies ({missing_list}) are not installed. Enter your choice (1, 2, 3, or 4)",
            validator=validate_dependency_choice
        )
        
        if choice == '1':
            # Only create pyproject.toml
            skip_build = True
            skip_copy = True
            print("\n✓ Will create pyproject.toml only")
            print("  (Source copy and build process will be skipped)\n")
        elif choice == '2':
            # Only copy source
            skip_build = True
            skip_pyproject = True
            print("\n✓ Will copy source to input folder only")
            print("  (pyproject.toml creation and build process will be skipped)\n")
        elif choice == '3':
            # Prepare both
            skip_build = True
            print("\n✓ Will prepare both pyproject.toml and input folder")
            print("  (Build process will be skipped)\n")
        elif choice == '4':
            # Cancel
            print("\n" + "=" * 60)
            print("Cancelled by user.")
            print("=" * 60)
            print("\nTo install all required dependencies, run:")
            print("  pip install build setuptools wheel")
            print("=" * 60)
            sys.exit(0)
    
    # Parse command-line arguments
    args = parse_arguments()
    
    # Get base current working directory (where script is run from)
    base_cwd = os.getcwd()
    
    config = None
    source_dir = None
    is_complete = True
    reuse_existing = False
    project_dir = None
    
    # Run interactive session to get package name and determine project directory
    # Note: We no longer check for pyproject.toml in CWD - only in project directory
    result = interactive_session()
    # Check if user cancelled (returned None)
    if result[0] is None:
        print("\nSession cancelled. Exiting.")
        return
    config, source_dir, is_complete, reuse_existing = result
    project_dir = config.get('project_dir', base_cwd)
    pyproject_path = os.path.join(project_dir, "pyproject.toml")
    
    # If reusing existing directory, skip file modifications
    if reuse_existing:
        skip_pyproject = True
        skip_copy = True
    
    # Use project_dir as the base directory (cwd) for all operations
    cwd = project_dir
    
    # Set output directory (default: <PROJECT_DIR>/output/dist/)
    if args.output_dir:
        output_dir = os.path.abspath(args.output_dir)
    else:
        output_dir = os.path.join(cwd, "output", "dist")
    
    # Set up the package (cwd is now project_dir)
    # If reusing existing directory, skip all file modifications
    if reuse_existing:
        skip_pyproject = True
        skip_copy = True
        # Determine package directory path for return value
        module_name = config.get('module_name', '')
        use_package = config.get('use_package', True)
        input_dir = os.path.join(cwd, "input")
        if use_package:
            package_dir = os.path.join(input_dir, module_name)
        else:
            package_dir = input_dir
        pyproject_path = os.path.join(cwd, "pyproject.toml")
    else:
        package_dir, pyproject_path = setup_package(
            config, source_dir, is_complete, cwd, 
            pyproject_path=None,
            skip_pyproject=skip_pyproject,
            skip_copy=skip_copy
        )
    
    # Handle pyproject.toml configuration if it was created (skip if reusing existing)
    if not skip_pyproject and not reuse_existing:
        # Ensure pyproject.toml has package-dir configuration pointing to input/
        print("\nChecking pyproject.toml configuration...")
        updated = ensure_package_dir_in_pyproject(pyproject_path)
        if updated:
            print("✓ Updated pyproject.toml with package-dir configuration")
        else:
            print("✓ pyproject.toml already has package-dir configuration")
    elif reuse_existing:
        print("\nℹ Reusing existing project directory - pyproject.toml will not be modified")
    
    # Get distribution type from config
    use_package = config.get('use_package', True) if config else True
    
    # Verify build requirements only if we're going to build
    if not skip_build:
        print("\nVerifying build requirements...")
        if not check_build_requirements(pyproject_path, package_dir, use_package):
            print("\n" + "=" * 60)
            print("Setup completed but build requirements check failed!")
            print("=" * 60)
            print(f"Package directory: {package_dir}")
            print(f"pyproject.toml: {pyproject_path}")
            print("\nPlease fix the issues above and run: python -m build")
            print("=" * 60)
            return
    
    # Ask user to confirm before building
    print("\n" + "=" * 60)
    print("Ready to build wheels. Please recheck pyproject.toml.")
    print("=" * 60)
    print(f"pyproject.toml location: {pyproject_path}")
    print(f"Package directory: {package_dir}")
    print(f"Output directory: {output_dir}")
    
    # Show platform compatibility warning if applicable
    platform_info = config.get('platform_info', {})
    if platform_info.get('has_binaries', False):
        detected_platforms = platform_info.get('platforms', set())
        platform_list = ', '.join(sorted(detected_platforms))
        print()
        print("⚠ Platform Compatibility Notice:")
        print(f"   Detected platform-specific binaries for: {platform_list}")
        print("   The built wheel will be tagged as '-none-any' (universal).")
        print("   If you need platform-specific wheels, consider building on each target platform.")
        print("   Or configure setuptools with platform-specific tags in pyproject.toml.")
    
    print()
    print("Note: You can edit pyproject.toml now if needed.")
    if not skip_build:
        print("      The build process will use the latest version of the file.")
    print()
    
    # Skip build confirmation and process if dependencies are missing
    if skip_build:
        print("\n" + "=" * 60)
        print("Summary")
        print("=" * 60)
        
        if reuse_existing:
            print(f"\n✓ Reusing existing project directory: {project_dir}")
            print(f"  → No files were modified")
            print(f"  → Existing pyproject.toml was used")
        
        if not skip_pyproject:
            print(f"\n✓ pyproject.toml has been created successfully!")
            print(f"  Location: {pyproject_path}")
        
        if not skip_copy:
            print(f"\n✓ Source files have been copied to input folder")
            print(f"  Package directory: {package_dir}")
        elif not reuse_existing:
            print(f"\nℹ Package directory: {package_dir} (no files copied)")
        
        if skip_pyproject and skip_copy and not reuse_existing:
            print("\n⚠ No actions were performed (both pyproject.toml and copy were skipped)")
        
        print("\nTo build the package later, install dependencies and run:")
        print(f"  pip install build setuptools wheel")
        print(f"  cd {cwd}")
        print(f"  python -m build --outdir output/dist")
        print("=" * 60)
        return
    
    build_confirm = ask_question(
        "Does the information in pyproject.toml all correct? ('y' to proceed building, 'n' to cancel)",
        validator=validate_yes_no
    )
    
    if build_confirm.lower() not in ['y', 'yes']:
        print("\n" + "=" * 60)
        print("Build cancelled by user.")
        print("=" * 60)
        print(f"\nYou can review and edit pyproject.toml at: {pyproject_path}")
        print("After making changes, you can run the build manually with:")
        print(f"  cd {cwd}")
        print(f"  python -m build --outdir output/dist")
        print("=" * 60)
        return
    
    # Verify pyproject.toml still exists and is readable after potential user edits
    print("\nVerifying pyproject.toml before build...")
    if not os.path.exists(pyproject_path):
        print(f"\n✗ Error: pyproject.toml not found at {pyproject_path}")
        print("Build cancelled.")
        return
    
    try:
        # Try to read the file to ensure it's valid
        with open(pyproject_path, 'r', encoding='utf-8') as f:
            content = f.read()
        if not content.strip():
            print(f"\n✗ Warning: pyproject.toml appears to be empty")
            print("Build may fail. Please check the file.")
    except Exception as e:
        print(f"\n✗ Warning: Could not read pyproject.toml: {e}")
        print("Build may fail. Please check the file.")
    
    print("✓ pyproject.toml verified. Starting build process...")
    print("  (The build will use the current version of pyproject.toml)")
    
    # Determine if output should be overridden
    override_output = True  # Default to override
    if reuse_existing:
        output_override_choice = config.get('output_override_choice')
        if output_override_choice == '2':
            override_output = False  # Preserve existing output
        elif output_override_choice == '3':
            # Should not reach here as cancellation is handled earlier, but just in case
            print("\nBuild cancelled.")
            return
    
    # Start build process
    # Note: python -m build reads pyproject.toml directly from disk, so any edits
    # made before this point will be automatically used
    build_success, build_info = run_build_process(cwd, output_dir, override_output=override_output)
    
    # Summary
    print("\n" + "=" * 60)
    if build_success:
        print("Setup and Build Complete!")
    else:
        print("Setup Complete, but Build Failed!")
    print("=" * 60)
    print(f"Package directory: {package_dir}")
    print(f"pyproject.toml: {pyproject_path}")
    print(f"Output directory: {output_dir}")
    
    if build_success:
        # Display post-build instructions
        display_build_instructions(build_info, output_dir, cwd)
    else:
        print("\nNext steps:")
        print("1. Review the error messages above")
        print("2. Fix any issues in pyproject.toml or package directory")
        print(f"3. cd {cwd}")
        print(f"4. Run: python -m build --outdir output/dist")
    
    print("=" * 60)


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n\nSetup cancelled by user.")
    except Exception as e:
        print(f"\n\nError: {e}")
        import traceback
        traceback.print_exc()

