#!/usr/bin/env python3
"""
modules.py - Visualize Python module imports with docstring hover

Part of daredevil: understand code without reading it.
"""

import ast
import sys
from pathlib import Path
from pyvis.network import Network


def get_module_info(filepath):
    """
    Extract docstring, imports, and line count from a Python file.
    
    Returns:
        dict with 'docstring', 'imports', and 'lines' keys
    """
    try:
        source = filepath.read_text()
        tree = ast.parse(source)
        lines = len(source.splitlines())
    except (SyntaxError, UnicodeDecodeError):
        return {'docstring': '(parse error)', 'imports': [], 'lines': 0}
    
    # Get module docstring
    docstring = ast.get_docstring(tree) or '(no docstring)'
    
    # Get imports
    imports = []
    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            for alias in node.names:
                imports.append(alias.name)
        elif isinstance(node, ast.ImportFrom):
            if node.module:
                imports.append(node.module)
    
    return {
        'docstring': docstring,
        'imports': imports,
        'lines': lines
    }


def get_functions_summary(filepath):
    """
    Get a summary of functions/classes in a module.
    
    Returns:
        str with function names and their first-line docstrings
    """
    try:
        source = filepath.read_text()
        tree = ast.parse(source)
    except (SyntaxError, UnicodeDecodeError):
        return ''
    
    items = []
    for node in ast.iter_child_nodes(tree):
        if isinstance(node, ast.FunctionDef):
            doc = ast.get_docstring(node)
            first_line = doc.split('\n')[0] if doc else ''
            items.append(f"def {node.name}(): {first_line}")
        elif isinstance(node, ast.ClassDef):
            doc = ast.get_docstring(node)
            first_line = doc.split('\n')[0] if doc else ''
            items.append(f"class {node.name}: {first_line}")
    
    return '\n'.join(items[:10])  # Limit to first 10


def build_module_graph(package_path, output_file='~/.cache/daredevil/module_graph.html'):
    """
    Build an interactive module graph from a Python package.
    
    Args:
        package_path: Path to package directory
        output_file: Output HTML file path
    
    Returns:
        Path to generated HTML file
    """
    import shutil
    
    package_path = Path(package_path).resolve()
    output_path = Path(output_file).expanduser().resolve()
    
    # Clean and recreate output directory
    if output_path.parent.name == 'daredevil':
        if output_path.parent.exists():
            shutil.rmtree(output_path.parent)
        output_path.parent.mkdir(parents=True)
    
    if not package_path.exists():
        print(f"Error: {package_path} not found")
        sys.exit(1)
    
    # Find all Python files
    py_files = list(package_path.glob('**/*.py'))
    
    if not py_files:
        print(f"No Python files found in {package_path}")
        sys.exit(1)
    
    # Build module info dict
    modules = {}
    package_name = package_path.name
    
    for py_file in py_files:
        # Get module name relative to package
        rel_path = py_file.relative_to(package_path)
        
        if rel_path.name == '__init__.py':
            parts = list(rel_path.parent.parts)
            if not parts:
                module_name = package_name
            else:
                module_name = '.'.join([package_name] + parts)
        else:
            parts = list(rel_path.parent.parts) + [rel_path.stem]
            module_name = '.'.join([package_name] + parts)
        
        info = get_module_info(py_file)
        functions = get_functions_summary(py_file)
        
        modules[module_name] = {
            'docstring': info['docstring'],
            'imports': info['imports'],
            'functions': functions,
            'lines': info['lines'],
            'path': str(py_file)
        }
    
    # Create network
    net = Network(
        height='800px',
        width='100%',
        bgcolor='#1a1a2e',
        font_color='white',
        directed=True
    )
    
    # Physics settings for better layout
    net.barnes_hut(
        gravity=-3000,
        central_gravity=0.3,
        spring_length=200,
        spring_strength=0.01
    )
    
    # Add nodes
    for module_name, info in modules.items():
        # Build tooltip - docstring + line count
        tooltip = f"{info['lines']} lines\n\n{info['docstring'][:500]}"
        
        # Color by depth
        depth = module_name.count('.')
        colors = ['#4361ee', '#3a0ca3', '#7209b7', '#f72585', '#4cc9f0']
        color = colors[min(depth, len(colors)-1)]
        
        # Size by line count
        size = 15 + min(info['lines'] // 10, 30)
        
        net.add_node(
            module_name,
            label=f"{module_name.split('.')[-1]}\n({info['lines']})",  # Short name + lines
            title=tooltip,
            color=color,
            size=size
        )
    
    # Add edges for internal imports
    for module_name, info in modules.items():
        for imp in info['imports']:
            # Check if import is internal to package
            if imp.startswith(package_name):
                if imp in modules:
                    net.add_edge(module_name, imp, color='#ffffff44')
            # Also check for relative-style imports that might match
            for other_module in modules:
                if other_module.endswith('.' + imp) or other_module == imp:
                    net.add_edge(module_name, other_module, color='#ffffff44')
    
    # Generate HTML
    net.show(str(output_path), notebook=False)
    
    print(f"Generated: {output_path}")
    print(f"Modules: {len(modules)}")
    
    return str(output_path)


def main():
    """CLI entry point."""
    import argparse
    
    parser = argparse.ArgumentParser(
        description='Visualize Python module imports with docstring hover'
    )
    parser.add_argument('package', nargs='?', default='.',
                        help='Path to Python package (default: current directory)')
    parser.add_argument('-o', '--output', default='~/.cache/daredevil/module_graph.html',
                        help='Output HTML file')
    
    args = parser.parse_args()
    build_module_graph(args.package, args.output)


if __name__ == '__main__':
    main()