# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Analyze function-level imports to determine which could be moved to header.

Categories:
1. CIRCULAR - Import would cause circular dependency (must stay in function)
2. REDUNDANT - Same import already exists in header (can be removed)
3. MOVABLE - Could potentially be moved to header
"""

import csv
from pathlib import Path
from typing import Dict, List, Set, Tuple
from collections import defaultdict

# Paths
SCRIPT_DIR = Path(__file__).parent
INPUT_FILE = SCRIPT_DIR / "import_analysis.csv"
OUTPUT_FILE = SCRIPT_DIR / "function_imports_analysis.txt"


def _getContainer(moduleName: str) -> str:
    """Extract container name from module path."""
    if moduleName == "gateway.app":
        return "app"
    
    parts = moduleName.replace("gateway.", "").split(".")
    if len(parts) < 2:
        return "app"
    
    container = parts[1]
    
    # Skip tests and scripts
    if container in ("tests", "scripts") or container.startswith("script_"):
        return None
    if parts[0] in ("tests", "scripts"):
        return None
    
    # Handle features sub-containers
    if container == "features" and len(parts) > 2:
        return f"features.{parts[2]}"
    
    return container


def _analyzeImports() -> Tuple[Dict, Dict, Dict, Set]:
    """
    Analyze imports and return:
    - headerImports: Dict[module] -> Set[imported_modules]
    - functionImports: Dict[module] -> List[(imported_module, function_name)]
    - allModuleImports: Dict[module] -> Set[all_imports] (for circular detection)
    - allModules: Set of all modules
    """
    headerImports = defaultdict(set)
    functionImports = defaultdict(list)
    allModuleImports = defaultdict(set)
    allModules = set()
    
    with open(INPUT_FILE, "r", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for row in reader:
            sourceFull = row["module_name"]
            targetFull = row["imported_module_name"]
            position = row["position"]
            
            # Skip external imports and relative imports
            if not targetFull.startswith("modules."):
                continue
            if targetFull.startswith("(relative)"):
                continue
            
            # Skip tests/scripts
            sourceContainer = _getContainer(sourceFull)
            if sourceContainer is None:
                continue
            
            # Add gateway prefix for consistency
            targetFull = f"gateway.{targetFull}"
            
            allModules.add(sourceFull)
            allModules.add(targetFull)
            allModuleImports[sourceFull].add(targetFull)
            
            if position == "header":
                headerImports[sourceFull].add(targetFull)
            else:
                # Extract function name
                funcName = position.replace("function ", "")
                functionImports[sourceFull].append((targetFull, funcName))
    
    return dict(headerImports), dict(functionImports), dict(allModuleImports), allModules


def _detectCircularDependency(source: str, target: str, allModuleImports: Dict) -> bool:
    """
    Check if moving target import to header would create circular dependency.
    Returns True if target already imports source (directly or indirectly).
    """
    visited = set()
    
    def _canReach(current: str, goal: str) -> bool:
        if current == goal:
            return True
        if current in visited:
            return False
        visited.add(current)
        
        for imported in allModuleImports.get(current, []):
            if _canReach(imported, goal):
                return True
        return False
    
    # Check if target can reach source through its imports
    return _canReach(target, source)


def main():
    """Main analysis function."""
    print("Analyzing function imports...")
    headerImports, functionImports, allModuleImports, allModules = _analyzeImports()
    
    # Categorize function imports
    circular = []      # Must stay in function (would cause circular import)
    redundant = []     # Already imported in header (can be removed)
    movable = []       # Could be moved to header
    
    totalFunctionImports = 0
    
    for source, imports in sorted(functionImports.items()):
        headerSet = headerImports.get(source, set())
        
        for target, funcName in imports:
            totalFunctionImports += 1
            
            # Check if already in header
            if target in headerSet:
                redundant.append((source, target, funcName))
                continue
            
            # Check for circular dependency
            if _detectCircularDependency(source, target, allModuleImports):
                circular.append((source, target, funcName))
                continue
            
            # Otherwise, could be moved to header
            movable.append((source, target, funcName))
    
    # Generate report
    lines = []
    lines.append("=" * 80)
    lines.append("FUNCTION IMPORTS ANALYSIS")
    lines.append("=" * 80)
    lines.append(f"\nTotal function imports (internal modules): {totalFunctionImports}")
    lines.append(f"  - CIRCULAR (must stay):     {len(circular):4}")
    lines.append(f"  - REDUNDANT (can remove):   {len(redundant):4}")
    lines.append(f"  - MOVABLE (can move):       {len(movable):4}")
    
    # Group movable by source module
    movableBySource = defaultdict(list)
    for source, target, funcName in movable:
        movableBySource[source].append((target, funcName))
    
    lines.append(f"\n\n{'=' * 80}")
    lines.append("MOVABLE TO HEADER (grouped by source module)")
    lines.append("These imports could potentially be moved to the module header.")
    lines.append("=" * 80)
    
    for source in sorted(movableBySource.keys()):
        imports = movableBySource[source]
        lines.append(f"\n{source}")
        lines.append("-" * len(source))
        for target, funcName in sorted(set(imports)):
            shortTarget = target.replace("gateway.", "")
            lines.append(f"  [{funcName}] {shortTarget}")
    
    # Redundant imports
    if redundant:
        lines.append(f"\n\n{'=' * 80}")
        lines.append("REDUNDANT IMPORTS (already in header - can be removed)")
        lines.append("=" * 80)
        
        redundantBySource = defaultdict(list)
        for source, target, funcName in redundant:
            redundantBySource[source].append((target, funcName))
        
        for source in sorted(redundantBySource.keys()):
            imports = redundantBySource[source]
            lines.append(f"\n{source}")
            lines.append("-" * len(source))
            for target, funcName in sorted(set(imports)):
                shortTarget = target.replace("gateway.", "")
                lines.append(f"  [{funcName}] {shortTarget}")
    
    # Circular imports (for reference)
    if circular:
        lines.append(f"\n\n{'=' * 80}")
        lines.append("CIRCULAR DEPENDENCY (must stay in function)")
        lines.append("=" * 80)
        
        circularBySource = defaultdict(list)
        for source, target, funcName in circular:
            circularBySource[source].append((target, funcName))
        
        for source in sorted(circularBySource.keys()):
            imports = circularBySource[source]
            lines.append(f"\n{source}")
            lines.append("-" * len(source))
            for target, funcName in sorted(set(imports)):
                shortTarget = target.replace("gateway.", "")
                lines.append(f"  [{funcName}] {shortTarget}")
    
    # Write report
    report = "\n".join(lines)
    with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
        f.write(report)
    
    print(report)
    print(f"\n\nReport saved to: {OUTPUT_FILE}")


if __name__ == "__main__":
    main()
