#!/usr/bin/env python3
"""
Base compiler interface for the build system.
Defines the abstract Compiler class that all specific compilers must implement.
"""

from abc import ABC, abstractmethod
from pathlib import Path
from typing import List, Optional
from dataclasses import dataclass


@dataclass
class CompileResult:
    """Result of a compilation operation."""
    object_file: Path
    dependency_file: Optional[Path] = None
    success: bool = True


class Compiler(ABC):
    """Abstract base class for all compilers."""
    
    def __init__(self, name: str, executable: str):
        """
        Initialize the compiler.
        
        Args:
            name: Human-readable name of the compiler (e.g., "GCC", "MSVC")
            executable: Executable name or path (e.g., "gcc", "cl")
        """
        self.name = name
        self.executable = executable
    
    @abstractmethod
    def compile(self, 
                source: Path, 
                output: Path,
                flags: Optional[List[str]] = None,
                defines: Optional[List[str]] = None,
                include_dirs: Optional[List[Path]] = None,
                dependency_file: Optional[Path] = None) -> CompileResult:
        """
        Compile a single source file to an object file.
        
        Args:
            source: Source file path
            output: Output object file path
            flags: Additional compiler flags
            defines: Preprocessor defines (e.g., ["DEBUG", "VERSION=1.0"])
            include_dirs: Additional include directories
            dependency_file: Path for dependency file (.d file)
        
        Returns:
            CompileResult with object file path and success status
        
        Raises:
            subprocess.CalledProcessError: If compilation fails
        """
        pass
    
    @abstractmethod
    def link(self,
             objects: List[Path],
             output: Path,
             flags: Optional[List[str]] = None,
             libraries: Optional[List[str]] = None,
             library_dirs: Optional[List[Path]] = None) -> bool:
        """
        Link object files into an executable or library.
        
        Args:
            objects: List of object file paths
            output: Output executable or library path
            flags: Additional linker flags
            libraries: Libraries to link against (e.g., ["m", "pthread"])
            library_dirs: Additional library search directories
        
        Returns:
            True if linking succeeded, False otherwise
        
        Raises:
            subprocess.CalledProcessError: If linking fails
        """
        pass
    
    @abstractmethod
    def get_object_extension(self) -> str:
        """
        Get the file extension for object files.
        
        Returns:
            Object file extension (e.g., ".o" or ".obj")
        """
        pass
    
    @abstractmethod
    def get_dependency_extension(self) -> str:
        """
        Get the file extension for dependency files.
        
        Returns:
            Dependency file extension (e.g., ".d")
        """
        pass
    
    def supports_dependency_generation(self) -> bool:
        """
        Check if the compiler supports automatic dependency generation.
        
        Returns:
            True if dependency generation is supported, False otherwise
        """
        return True
    
    def get_define_flag(self, define: str) -> str:
        """
        Get the compiler flag for a preprocessor define.
        
        Args:
            define: Define string (e.g., "DEBUG" or "VERSION=1.0")
        
        Returns:
            Compiler-specific define flag (e.g., "-DDEBUG" or "/DDEBUG")
        """
        # Most compilers use -D, but can be overridden
        return f"-D{define}"
    
    def get_include_flag(self, include_dir: Path) -> str:
        """
        Get the compiler flag for an include directory.
        
        Args:
            include_dir: Include directory path
        
        Returns:
            Compiler-specific include flag (e.g., "-Iinclude" or "/Iinclude")
        """
        # Most compilers use -I, but can be overridden
        return f"-I{include_dir}"
    
    def get_output_flag(self, output: Path) -> List[str]:
        """
        Get the compiler flag(s) for specifying output file.
        
        Args:
            output: Output file path
        
        Returns:
            List of flags (e.g., ["-o", "output.o"] or ["/Fooutput.obj"])
        """
        # Most compilers use -o, but can be overridden
        return ["-o", str(output)]
    
    def get_compile_only_flag(self) -> str:
        """
        Get the flag to compile without linking.
        
        Returns:
            Compile-only flag (e.g., "-c")
        """
        return "-c"
    
    def __str__(self) -> str:
        """String representation of the compiler."""
        return f"{self.name} ({self.executable})"
    
    def __repr__(self) -> str:
        """Detailed string representation."""
        return f"Compiler(name='{self.name}', executable='{self.executable}')"
