"""Enhanced error formatting for IncludeCPP build system.

Provides user-friendly error messages with context and suggestions.
"""

from typing import List, Optional
import re


class BuildErrorFormatter:
    """Format C++ compilation errors with context and suggestions."""

    @staticmethod
    def format_type_mismatch(expected: str, got: str, location: str) -> str:
        """Format type mismatch error with suggestions.

        Args:
            expected: Expected type string
            got: Actual type string
            location: Location of error (file:line)

        Returns:
            Formatted error message with suggestions
        """
        return f"""
╔══════════════════════════════════════════════════════════════╗
║ TYPE MISMATCH ERROR                                          ║
╚══════════════════════════════════════════════════════════════╝

Location: {location}

Expected: {expected}
Got:      {got}

Suggestions:
  • Check if you're passing the correct type
  • For struct → dict: use struct_instance.to_dict()
  • For dict → struct: use StructName.from_dict(dict_instance)
  • For vector conversions: Python list ↔ std::vector is automatic

Type Conversion Examples:
  # Struct to dict
  point_dict = point.to_dict()

  # Dict to struct
  point = Point.from_dict({{"x": 10, "y": 20}})

  # Vector of structs
  points: List[Point] = [...]  # Automatically converts
"""

    @staticmethod
    def format_dependency_error(module: str, missing_dep: str) -> str:
        """Format missing dependency error.

        Args:
            module: Module name that has missing dependency
            missing_dep: Name of missing dependency module

        Returns:
            Formatted error message with fix instructions
        """
        return f"""
╔══════════════════════════════════════════════════════════════╗
║ DEPENDENCY ERROR                                             ║
╚══════════════════════════════════════════════════════════════╝

Module: {module}
Missing Dependency: {missing_dep}

To Fix:
  1. Add dependency to {module}.cp:

     SOURCE(path/to/{module}.cpp) {module}
     DEPENDS({missing_dep})
     PUBLIC(
         ...
     )

  2. Ensure {missing_dep} module exists in plugins/ directory

  3. Rebuild:
     python -m includecpp rebuild --verbose

Dependency Chain:
  {module} → {missing_dep}
"""

    @staticmethod
    def format_circular_dependency(cycle: List[str]) -> str:
        """Format circular dependency error.

        Args:
            cycle: List of module names forming the cycle

        Returns:
            Formatted error message with refactoring suggestions
        """
        cycle_display = " → ".join(cycle + [cycle[0]])
        return f"""
╔══════════════════════════════════════════════════════════════╗
║ CIRCULAR DEPENDENCY DETECTED                                 ║
╚══════════════════════════════════════════════════════════════╝

Dependency Cycle:
  {cycle_display}

To Fix:
  • Refactor modules to remove circular dependency
  • Use forward declarations where possible
  • Consider merging modules if they're tightly coupled
  • Create a third module for shared types

Example Refactoring:
  Before:
    module_a → module_b → module_a  ❌

  After:
    module_a → shared_types
    module_b → shared_types         ✓
"""

    @staticmethod
    def format_module_not_found(module: str, available: List[str]) -> str:
        """Format module not found error.

        Args:
            module: Requested module name
            available: List of available module names

        Returns:
            Formatted error message
        """
        return f"""
╔══════════════════════════════════════════════════════════════╗
║ MODULE NOT FOUND                                             ║
╚══════════════════════════════════════════════════════════════╝

Requested: {module}

Available modules:
  {', '.join(available) if available else '(none)'}

To Fix:
  • Check spelling of module name
  • Ensure {module}.cp exists in plugins/ directory
  • Run 'python -m includecpp rebuild' to build all modules
  • Run 'python -m includecpp list' to see available modules
"""

    @staticmethod
    def format_build_failed(module: str, reason: str) -> str:
        """Format general build failure error.

        Args:
            module: Module name that failed to build
            reason: Reason for failure

        Returns:
            Formatted error message
        """
        return f"""
╔══════════════════════════════════════════════════════════════╗
║ BUILD FAILED                                                 ║
╚══════════════════════════════════════════════════════════════╝

Module: {module}

Reason:
  {reason}

To Debug:
  • Run with --verbose flag for detailed output
  • Check {module}.cpp for syntax errors
  • Verify all includes are available
  • Check build log in AppData/IncludeCPP/
"""

    @staticmethod
    def parse_compiler_error(stderr: str, module_name: str) -> str:
        """Parse C++ compiler errors and add helpful context.

        Args:
            stderr: Standard error output from compiler
            module_name: Name of module being compiled

        Returns:
            Formatted error messages with context
        """
        lines = stderr.split('\n')
        formatted = []
        error_count = 0

        for line in lines:
            # GCC/Clang format: file:line:col: error: message
            if ': error:' in line:
                error_count += 1
                parts = line.split(':')

                if len(parts) >= 4:
                    file_path = parts[0].strip()
                    line_num = parts[1].strip() if parts[1].strip().isdigit() else "?"
                    error_msg = ':'.join(parts[3:]).strip()

                    formatted.append("")
                    formatted.append("╔══════════════════════════════════════════╗")
                    formatted.append("║ COMPILATION ERROR                        ║")
                    formatted.append("╚══════════════════════════════════════════╝")
                    formatted.append("")
                    formatted.append(f"Module: {module_name}")
                    formatted.append(f"File: {file_path}")
                    formatted.append(f"Line: {line_num}")
                    formatted.append(f"Error: {error_msg}")
                    formatted.append("")

                    # Add context based on error type
                    if "undefined reference" in error_msg.lower() or "unresolved external" in error_msg.lower():
                        formatted.append("Suggestion:")
                        formatted.append("  • Check if function/class is declared in header")
                        formatted.append("  • Verify all source files are listed in .cp file")
                        formatted.append("  • Check for missing DEPENDS() if using types from other modules")
                        formatted.append("  • Ensure function is in 'includecpp' namespace")

                    elif "no matching function" in error_msg.lower() or "no member named" in error_msg.lower():
                        formatted.append("Suggestion:")
                        formatted.append("  • Check parameter types match function signature")
                        formatted.append("  • Verify template instantiations are correct")
                        formatted.append("  • Check for const/reference qualifiers")
                        formatted.append("  • Ensure all required headers are included")

                    elif "expected" in error_msg.lower() and "before" in error_msg.lower():
                        formatted.append("Suggestion:")
                        formatted.append("  • Check for missing semicolons")
                        formatted.append("  • Verify bracket/parenthesis matching")
                        formatted.append("  • Check for typos in type names")

                    elif "does not name a type" in error_msg.lower():
                        formatted.append("Suggestion:")
                        formatted.append("  • Check if type is defined before use")
                        formatted.append("  • Verify #include statements are correct")
                        formatted.append("  • Check namespace qualifications (std::, includecpp::)")

                    elif "incomplete type" in error_msg.lower():
                        formatted.append("Suggestion:")
                        formatted.append("  • Add forward declaration or full type definition")
                        formatted.append("  • Check if header file is included")
                        formatted.append("  • Verify struct/class definition is complete")

            # MSVC format: file(line): error C####: message
            elif re.match(r'.*\(\d+\):\s*error\s+C\d+:', line):
                error_count += 1
                match = re.match(r'(.*)\((\d+)\):\s*error\s+C\d+:\s*(.*)', line)
                if match:
                    file_path = match.group(1).strip()
                    line_num = match.group(2)
                    error_msg = match.group(3).strip()

                    formatted.append("")
                    formatted.append("╔══════════════════════════════════════════╗")
                    formatted.append("║ COMPILATION ERROR                        ║")
                    formatted.append("╚══════════════════════════════════════════╝")
                    formatted.append("")
                    formatted.append(f"Module: {module_name}")
                    formatted.append(f"File: {file_path}")
                    formatted.append(f"Line: {line_num}")
                    formatted.append(f"Error: {error_msg}")
                    formatted.append("")

            # Warning lines - pass through but mark them
            elif ': warning:' in line or 'warning C' in line:
                if not formatted or formatted[-1] != "":
                    formatted.append("")
                formatted.append(f"⚠️  {line}")

            # Note lines
            elif ': note:' in line:
                formatted.append(f"ℹ️  {line}")

            # Empty lines
            elif not line.strip():
                if formatted and formatted[-1] != "":
                    formatted.append("")

        if error_count > 0:
            header = [
                "",
                "═" * 60,
                f"COMPILATION FAILED: {error_count} error(s) in {module_name}",
                "═" * 60,
                ""
            ]
            formatted = header + formatted

        return '\n'.join(formatted) if formatted else stderr

    @staticmethod
    def format_cmake_error(stderr: str, module_name: str) -> str:
        """Format CMake configuration errors.

        Args:
            stderr: CMake error output
            module_name: Name of module being configured

        Returns:
            Formatted error message
        """
        return f"""
╔══════════════════════════════════════════════════════════════╗
║ CMAKE CONFIGURATION ERROR                                    ║
╚══════════════════════════════════════════════════════════════╝

Module: {module_name}

CMake Output:
{stderr}

Common Issues:
  • CMake not installed or not in PATH
  • C++ compiler not found (install g++, clang++, or MSVC)
  • pybind11 not found (auto-installed, check network)
  • Python development headers missing

To Fix:
  • Install CMake 3.15+ from cmake.org
  • Install C++ compiler:
    - Windows: Visual Studio or MinGW
    - Linux: apt-get install g++ cmake python3-dev
    - macOS: xcode-select --install
  • Run with --verbose for detailed CMake output
"""

    @staticmethod
    def format_import_error(module: str, error_msg: str, pyd_path: str) -> str:
        """Format Python import errors for .pyd modules.

        Args:
            module: Module name
            error_msg: Import error message
            pyd_path: Path to .pyd file

        Returns:
            Formatted error message
        """
        return f"""
╔══════════════════════════════════════════════════════════════╗
║ MODULE IMPORT ERROR                                          ║
╚══════════════════════════════════════════════════════════════╝

Module: {module}
Path: {pyd_path}

Error:
  {error_msg}

Common Causes:
  • Missing dependencies (DLLs on Windows, .so on Linux)
  • Python version mismatch (rebuild for current Python)
  • Corrupted .pyd file (rebuild module)
  • Missing Visual C++ Redistributable (Windows)

To Fix:
  1. Rebuild module:
     python -m includecpp rebuild {module} --clean

  2. Check Python version matches build:
     python --version

  3. Windows: Install Visual C++ Redistributable
     https://aka.ms/vs/17/release/vc_redist.x64.exe

  4. Run with --verbose for detailed output
"""

    @staticmethod
    def format_namespace_error(module: str, source_file: str) -> str:
        """Format namespace error when includecpp namespace is missing.

        Args:
            module: Module name
            source_file: Path to source file

        Returns:
            Formatted error message
        """
        return f"""
╔══════════════════════════════════════════════════════════════╗
║ NAMESPACE ERROR                                              ║
╚══════════════════════════════════════════════════════════════╝

Module: {module}
Source: {source_file}

Problem:
  Your C++ code is not wrapped in the 'includecpp' namespace.
  IncludeCPP requires all exported code to be in this namespace.

Why this matters:
  The generated Python bindings expect functions/classes in the
  'includecpp' namespace. Without it, the compiler cannot find
  your code and you'll get "undefined reference" errors.

To Fix:
  Wrap your code in the namespace:

  // {source_file}
  #include <...>

  namespace includecpp {{

      // Your functions and classes here
      int my_function(int x) {{
          return x * 2;
      }}

      class MyClass {{
      public:
          void method() {{ ... }}
      }};

  }} // namespace includecpp

After fixing:
  python -m includecpp rebuild
"""

    @staticmethod
    def format_syntax_error(module: str, file: str, line: int, error: str) -> str:
        """Format C++ syntax error.

        Args:
            module: Module name
            file: Source file path
            line: Line number
            error: Error message

        Returns:
            Formatted error message
        """
        return f"""
╔══════════════════════════════════════════════════════════════╗
║ C++ SYNTAX ERROR                                             ║
╚══════════════════════════════════════════════════════════════╝

Module: {module}
File:   {file}
Line:   {line}

Error:
  {error}

Common Causes:
  • Missing semicolon (;) at end of statement
  • Unbalanced braces {{ }} or parentheses ( )
  • Missing #include directive
  • Typo in keyword or identifier
  • Wrong order of template/class declaration

How to Debug:
  1. Open {file} at line {line}
  2. Check the line and lines immediately before it
  3. Look for missing ; or }}
  4. Verify all includes are correct

After fixing:
  python -m includecpp rebuild --verbose
"""

    @staticmethod
    def format_linker_error(module: str, undefined_symbol: str, hint: str = "") -> str:
        """Format linker error for undefined references.

        Args:
            module: Module name
            undefined_symbol: The undefined symbol
            hint: Optional hint about cause

        Returns:
            Formatted error message
        """
        return f"""
╔══════════════════════════════════════════════════════════════╗
║ LINKER ERROR - UNDEFINED REFERENCE                           ║
╚══════════════════════════════════════════════════════════════╝

Module: {module}
Missing Symbol: {undefined_symbol}
{f"Hint: {hint}" if hint else ""}

What this means:
  The linker cannot find the implementation of '{undefined_symbol}'.
  The symbol is declared (perhaps in a header) but not defined.

Common Causes:
  • Function declared but not implemented
  • Typo in function/class name
  • Missing namespace qualifier (forgot 'includecpp::')
  • Missing source file in .cp configuration
  • Template function defined in .cpp instead of header

To Fix:
  1. Check if '{undefined_symbol}' is fully implemented
  2. Verify the function is in 'namespace includecpp'
  3. For templates: move implementation to header file
  4. Check your .cp file includes all necessary sources:

     SOURCE(path/to/all_sources.cpp) {module}

  5. If using multiple files:

     SOURCES(file1.cpp, file2.cpp)
     HEADER(headers.h) {module}

After fixing:
  python -m includecpp rebuild --clean
"""

    @staticmethod
    def format_missing_include_error(module: str, missing_header: str, file: str) -> str:
        """Format missing include error.

        Args:
            module: Module name
            missing_header: The missing header file
            file: Source file that needs the include

        Returns:
            Formatted error message
        """
        common_headers = {
            'string': '#include <string>',
            'vector': '#include <vector>',
            'map': '#include <map>',
            'iostream': '#include <iostream>',
            'cmath': '#include <cmath>',
            'memory': '#include <memory>',
            'algorithm': '#include <algorithm>',
            'functional': '#include <functional>',
        }

        suggestion = common_headers.get(missing_header.lower(), f'#include <{missing_header}>')

        return f"""
╔══════════════════════════════════════════════════════════════╗
║ MISSING INCLUDE ERROR                                        ║
╚══════════════════════════════════════════════════════════════╝

Module: {module}
File:   {file}
Missing: {missing_header}

To Fix:
  Add at the top of {file}:

    {suggestion}

Common Standard Library Headers:
  #include <string>     // std::string
  #include <vector>     // std::vector
  #include <map>        // std::map, std::unordered_map
  #include <memory>     // std::unique_ptr, std::shared_ptr
  #include <algorithm>  // std::sort, std::find, etc.
  #include <cmath>      // Mathematical functions
  #include <iostream>   // std::cout, std::cin

After fixing:
  python -m includecpp rebuild
"""

    @staticmethod
    def format_generic_build_error(module: str, stage: str, error: str) -> str:
        """Format a generic build error with helpful context.

        Args:
            module: Module name
            stage: Build stage (compile, link, etc.)
            error: Error message

        Returns:
            Formatted error message
        """
        return f"""
╔══════════════════════════════════════════════════════════════╗
║ BUILD ERROR                                                  ║
╚══════════════════════════════════════════════════════════════╝

Module: {module}
Stage:  {stage}

Error Details:
{error}

Troubleshooting Steps:
  1. Run with verbose output:
     python -m includecpp rebuild --verbose

  2. Check your C++ code for errors:
     - Syntax errors (missing ; or }})
     - Missing namespace includecpp {{ }}
     - Undefined functions or classes

  3. Verify your .cp configuration:
     - All source files listed
     - Correct module name
     - Valid DEPENDS() if using other modules

  4. Try a clean rebuild:
     python -m includecpp rebuild --clean

  5. Check compiler is installed:
     g++ --version   (Linux/macOS)
     cl              (Windows MSVC)

If the error persists:
  - Check the full error output above
  - Verify all #include statements are correct
  - Ensure no circular dependencies between modules
"""

    @staticmethod
    def analyze_error(stderr: str, module_name: str = "") -> str:
        """Analyze stderr and return appropriate formatted error.

        Args:
            stderr: Standard error output from build
            module_name: Name of module being built

        Returns:
            Formatted error message with hints
        """
        import re

        stderr_lower = stderr.lower()

        # Check for namespace error
        if 'namespace includecpp' in stderr or 'using namespace includecpp' in stderr:
            if 'undefined' in stderr_lower or 'not found' in stderr_lower:
                return BuildErrorFormatter.format_namespace_error(module_name, "your source file")

        # Check for undefined reference (linker error)
        undefined_match = re.search(r"undefined reference to ['\"]?([^'\"]+)['\"]?", stderr)
        if undefined_match:
            symbol = undefined_match.group(1)
            hint = ""
            if '::' in symbol and 'includecpp' not in symbol:
                hint = "Symbol is not in 'includecpp' namespace"
            return BuildErrorFormatter.format_linker_error(module_name, symbol, hint)

        # Check for missing include
        include_match = re.search(r"fatal error: ([^:]+): No such file", stderr)
        if not include_match:
            include_match = re.search(r"cannot open include file[:\s]+['\"]?([^'\"]+)['\"]?", stderr, re.IGNORECASE)
        if include_match:
            missing = include_match.group(1).strip()
            return BuildErrorFormatter.format_missing_include_error(module_name, missing, "your source file")

        # Check for syntax error with line number
        syntax_match = re.search(r"([^:]+):(\d+):\d*:?\s*error:\s*(.+)", stderr)
        if syntax_match:
            file = syntax_match.group(1)
            line = int(syntax_match.group(2))
            error = syntax_match.group(3)
            return BuildErrorFormatter.format_syntax_error(module_name, file, line, error)

        # Check for circular dependency
        if 'circular' in stderr_lower and 'dependency' in stderr_lower:
            cycle_match = re.findall(r'\b([a-zA-Z_]\w*)\b', stderr)
            if cycle_match:
                return BuildErrorFormatter.format_circular_dependency(list(set(cycle_match[:5])))

        # Check for missing dependency
        dep_match = re.search(r"depends on unknown module[:\s]+['\"]?(\w+)['\"]?", stderr, re.IGNORECASE)
        if dep_match:
            missing_dep = dep_match.group(1)
            return BuildErrorFormatter.format_dependency_error(module_name, missing_dep)

        # Default: generic build error
        return BuildErrorFormatter.format_generic_build_error(module_name, "build", stderr)


class BuildSuccessFormatter:
    """Format build success messages with professional styling."""

    @staticmethod
    def format_build_success(modules: List[str], build_time: float, stats: dict = None) -> str:
        """Format successful build completion message.

        Args:
            modules: List of successfully built modules
            build_time: Total build time in seconds
            stats: Optional build statistics dict

        Returns:
            Formatted success message
        """
        module_count = len(modules)
        module_list = "\n  ".join(f"✓ {m}" for m in modules) if modules else "  (no modules)"

        stats_section = ""
        if stats:
            stats_lines = []
            if 'total_functions' in stats:
                stats_lines.append(f"  Functions exported: {stats['total_functions']}")
            if 'total_classes' in stats:
                stats_lines.append(f"  Classes exported:   {stats['total_classes']}")
            if 'total_structs' in stats:
                stats_lines.append(f"  Structs exported:   {stats['total_structs']}")
            if stats_lines:
                stats_section = "\n" + "\n".join(stats_lines)

        return f"""
╔══════════════════════════════════════════════════════════════╗
║ ✓ BUILD SUCCESSFUL                                           ║
╚══════════════════════════════════════════════════════════════╝

Modules built ({module_count}):
  {module_list}

Build time: {build_time:.2f}s{stats_section}

Your modules are ready to use:
  from module_name import function_name
"""

    @staticmethod
    def format_module_compile_start(module: str) -> str:
        """Format module compilation start message.

        Args:
            module: Module name

        Returns:
            Formatted start message
        """
        return f"┌─ Building: {module}"

    @staticmethod
    def format_module_compile_success(module: str, time_seconds: float) -> str:
        """Format module compilation success message.

        Args:
            module: Module name
            time_seconds: Compilation time

        Returns:
            Formatted success message
        """
        return f"└─ ✓ {module} ({time_seconds:.2f}s)"

    @staticmethod
    def format_module_compile_failed(module: str, error_summary: str) -> str:
        """Format module compilation failure message.

        Args:
            module: Module name
            error_summary: Brief error summary

        Returns:
            Formatted failure message
        """
        return f"└─ ✗ {module}: {error_summary}"

    @staticmethod
    def format_build_start(modules: List[str], incremental: bool = False) -> str:
        """Format build start message.

        Args:
            modules: List of modules to build
            incremental: Whether this is an incremental build

        Returns:
            Formatted start message
        """
        build_type = "Incremental" if incremental else "Full"
        module_count = len(modules) if modules else "all"

        return f"""
╔══════════════════════════════════════════════════════════════╗
║ IncludeCPP {build_type} Build
╚══════════════════════════════════════════════════════════════╝
Modules: {module_count}
"""

    @staticmethod
    def format_build_failed(failed_modules: List[str], succeeded_modules: List[str]) -> str:
        """Format build failure summary.

        Args:
            failed_modules: List of modules that failed
            succeeded_modules: List of modules that succeeded

        Returns:
            Formatted failure message
        """
        failed_list = "\n  ".join(f"✗ {m}" for m in failed_modules)
        succeeded_list = "\n  ".join(f"✓ {m}" for m in succeeded_modules) if succeeded_modules else "  (none)"

        return f"""
╔══════════════════════════════════════════════════════════════╗
║ ✗ BUILD FAILED                                               ║
╚══════════════════════════════════════════════════════════════╝

Failed ({len(failed_modules)}):
  {failed_list}

Succeeded ({len(succeeded_modules)}):
  {succeeded_list}

To fix:
  • Check the error messages above
  • Run with --verbose for more details
  • Fix errors and run: python -m includecpp rebuild
"""

    @staticmethod
    def format_up_to_date(modules: List[str]) -> str:
        """Format message when all modules are up to date.

        Args:
            modules: List of up-to-date modules

        Returns:
            Formatted message
        """
        return f"""
╔══════════════════════════════════════════════════════════════╗
║ ✓ ALL MODULES UP TO DATE                                     ║
╚══════════════════════════════════════════════════════════════╝

{len(modules)} module(s) already built and unchanged.
Use --clean to force a full rebuild.
"""

    @staticmethod
    def format_clean_success(cleaned_items: List[str]) -> str:
        """Format successful clean operation message.

        Args:
            cleaned_items: List of cleaned items/modules

        Returns:
            Formatted message
        """
        items = "\n  ".join(f"• {item}" for item in cleaned_items) if cleaned_items else "  (nothing to clean)"
        return f"""
╔══════════════════════════════════════════════════════════════╗
║ ✓ CLEAN COMPLETE                                             ║
╚══════════════════════════════════════════════════════════════╝

Cleaned:
  {items}
"""
