#!/usr/bin/env python3
"""
Utility functions for the build system.
Provides common operations like process execution,
file dependency checking, and tool detection.
"""

import subprocess
import shutil
from pathlib import Path
from typing import List, Union, Optional


# -----------------------------------------------------------------------------
# Process execution
# -----------------------------------------------------------------------------

def run(cmd: List[Union[str, Path]], 
        cwd: Optional[Path] = None,
        check: bool = True,
        capture_output: bool = False,
        verbose: bool = True) -> subprocess.CompletedProcess:
    """
    Execute a command and optionally capture its output.
    
    Args:
        cmd: Command and arguments to execute
        cwd: Working directory for the command
        check: If True, raise exception on non-zero exit code
        capture_output: If True, capture stdout and stderr
        verbose: If True, print the command before executing
    
    Returns:
        CompletedProcess instance with return code and output
    
    Raises:
        subprocess.CalledProcessError: If check=True and command fails
    """
    # Convert all Path objects to strings
    cmd_str = [str(c) for c in cmd]
    
    if verbose:
        print(">>", " ".join(cmd_str))
    
    return subprocess.run(
        cmd_str,
        cwd=cwd,
        check=check,
        capture_output=capture_output,
        text=True if capture_output else None
    )


def run_quiet(cmd: List[Union[str, Path]], 
              cwd: Optional[Path] = None,
              check: bool = False) -> subprocess.CompletedProcess:
    """
    Execute a command silently (no output, no verbose).
    
    Args:
        cmd: Command and arguments to execute
        cwd: Working directory for the command
        check: If True, raise exception on non-zero exit code
    
    Returns:
        CompletedProcess instance
    """
    cmd_str = [str(c) for c in cmd]
    return subprocess.run(
        cmd_str,
        cwd=cwd,
        check=check,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL
    )


# -----------------------------------------------------------------------------
# File dependency checking
# -----------------------------------------------------------------------------

def is_up_to_date(source: Path, output: Path, dependency: Optional[Path] = None) -> bool:
    """
    Check if an output file is up to date relative to its source and dependencies.
    
    Args:
        source: Source file path
        output: Output file path
        dependency: Optional dependency file (e.g., .d file)
    
    Returns:
        True if output is up to date, False if it needs rebuilding
    """
    # Files with 'meta' in their name are never up to date (force rebuild)
    if "meta" in source.stem or "meta" in output.stem:
        return False
    
    if dependency and "meta" in dependency.stem:
        return False
    
    # Output doesn't exist, needs building
    if not output.exists():
        return False
    
    # Check dependency file if provided
    if dependency:
        if dependency.exists() and dependency.stat().st_mtime > output.stat().st_mtime:
            return False
    
    # Check source file modification time
    return source.stat().st_mtime < output.stat().st_mtime


def needs_rebuild(sources: List[Path], output: Path) -> bool:
    """
    Check if any source file is newer than the output file.
    
    Args:
        sources: List of source file paths
        output: Output file path
    
    Returns:
        True if rebuild is needed, False otherwise
    """
    if not output.exists():
        return True
    
    output_time = output.stat().st_mtime
    return any(src.stat().st_mtime > output_time for src in sources if src.exists())


# -----------------------------------------------------------------------------
# Tool detection
# -----------------------------------------------------------------------------

def which(tool: str) -> Optional[Path]:
    """
    Find a tool in the system PATH.
    
    Args:
        tool: Tool name to find
    
    Returns:
        Path to the tool if found, None otherwise
    """
    result = shutil.which(tool)
    return Path(result) if result else None


def check_tool(tool: str) -> bool:
    """
    Check if a tool is available in the system PATH.
    
    Args:
        tool: Tool name to check
        error_message: Optional custom error message to print if not found
    
    Returns:
        True if tool is found, False otherwise
    """
    if which(tool):
        return True
    else:
        print(f"ERROR: Required tool '{tool}' not found in PATH")
    
    return False


def check_tools(tools: List[str]) -> bool:
    """
    Check if multiple tools are available.
    
    Args:
        tools: List of tool names to check
        error_messages: Optional list of custom error messages (same length as tools)
    
    Returns:
        True if all tools are found, False otherwise
    """
    all_found = True
    
    for tool in tools:
        if not check_tool(tool):
            all_found = False
    
    return all_found

