import os
import json
import click
from pathlib import Path
from .parser import get_lang_for_extension, parse_code
from .minifier import minify_node
from .cache import CacheManager
from .config import load_config

DEFAULT_EXCLUDES = {
    "vendor", "node_modules", "venv", ".venv", ".git", "__pycache__", "dist", "build", ".ast_cache", "docs",
    # Symfony
    "var", "bin/.phpunit",
    # Next.js / React
    ".next", "out", ".vercel",
    # Common tools
    ".idea", ".vscode", "coverage", ".serverless"
}


@click.group()
def cli():
    """
    ast-ai: A tool to scan projects and compress AST for LLM context.
    """
    pass

@cli.command()
@click.argument("path", type=click.Path(exists=True, file_okay=True, dir_okay=True), default=".")
@click.option("--skeleton", is_flag=None, type=bool, help="Truncate function/method bodies")
@click.option("--exclude", "-e", multiple=True, help="Directories to exclude")
def scan(path, skeleton, exclude):
    """
    Scans the project and updates the AST cache incrementally.
    """
    path_obj = Path(path).resolve()
    project_root = path_obj if path_obj.is_dir() else path_obj.parent
    
    # Load configuration
    config = load_config(project_root)
    
    # Merge options: CLI takes precedence over config
    final_skeleton = skeleton if skeleton is not None else config.get("skeleton", False)
    config_exclude = config.get("exclude", [])
    exclude_set = set(DEFAULT_EXCLUDES) | set(config_exclude) | set(exclude)
    
    cache = CacheManager(project_root)
    
    files_to_process = []
    if path_obj.is_file():
        ext = path_obj.suffix
        if get_lang_for_extension(ext):
            files_to_process.append(path_obj)
    else:
        for root, dirs, files in os.walk(path_obj):
            # Prune excluded directories
            dirs[:] = [d for d in dirs if d not in exclude_set]
            for file in files:
                ext = Path(file).suffix
                if get_lang_for_extension(ext):
                    files_to_process.append(Path(root) / file)

    processed_count = 0
    updated_count = 0
    
    # Store relative paths for pruning
    all_rel_paths = set()

    with click.progressbar(files_to_process, label="Scanning files") as bar:
        for file_path in bar:
            try:
                rel_path = str(file_path.relative_to(project_root))
                all_rel_paths.add(rel_path)
                
                # Check cache
                current_hash = cache.get_file_hash(file_path)
                if cache.is_up_to_date(rel_path, current_hash):
                    processed_count += 1
                    continue
                
                # Parse and minify
                ext = file_path.suffix
                lang = get_lang_for_extension(ext)
                with open(file_path, "rb") as f:
                    content = f.read()
                
                tree = parse_code(content, lang)
                minified = minify_node(tree.root_node, content, skeleton=final_skeleton)
                
                # Update cache
                cache.update_cache(rel_path, current_hash, minified)
                updated_count += 1
                processed_count += 1
            except Exception as e:
                click.echo(f"\nError processing {file_path}: {e}", err=True)

    # Prune old entries
    if path_obj.is_dir():
        cache.prune_index(all_rel_paths)

    click.echo(f"Scan complete. Total files: {processed_count}, Updated: {updated_count}")

@cli.command()
@click.argument("file_path", type=click.Path(exists=True))
def get(file_path):
    """
    Retrieves the cached AST for a specific file.
    """
    path_obj = Path(file_path).resolve()
    
    # Find project root (looking for .ast_cache)
    project_root = None
    current = path_obj.parent
    while current != current.parent:
        if (current / ".ast_cache").exists():
            project_root = current
            break
        current = current.parent
    
    if not project_root:
        # Fallback to current directory parent
        project_root = path_obj.parent
        
    cache = CacheManager(project_root)
    try:
        rel_path = str(path_obj.relative_to(project_root))
    except ValueError:
        # If file is outside project root
        click.echo(f"Error: File {file_path} is outside project root {project_root}", err=True)
        return

    ast_data = cache.get_cached_ast(rel_path)
    if ast_data:
        click.echo(json.dumps(ast_data, separators=(",", ":")))
    else:
        click.echo(f"No cached AST found for {rel_path}. Run 'scan' first.", err=True)

@cli.command()
@click.option("--path", default=".", type=click.Path(exists=True))
def clear(path):
    """
    Clears the AST cache.
    """
    project_root = Path(path).resolve()
    cache = CacheManager(project_root)
    cache.clear_cache()
    click.echo("Cache cleared successfully.")

if __name__ == "__main__":
    cli()
