"""
cli/watcher.py — Incremental build system and file watcher for RPy.
"""

import os
import time
from pathlib import Path
from typing import Dict, List, Set, Optional

from transpiler.infrastructure.dependency_graph import DependencyGraph
from transpiler.infrastructure.cache_manager import CacheManager
from transpiler.frontend.parser import parse_file
from transpiler.transformer import transform
from transpiler.backend.generator import generate

class IncrementalBuilder:
    def __init__(self, root_dir: str):
        self.root_dir = Path(root_dir).resolve()
        self.graph = DependencyGraph(self.root_dir)
        self.cache = CacheManager(str(self.root_dir / ".rpy_cache"))
        self.mtimes = {} # Fixed from self.last_mtimes
        print(f"[Debug] IncrementalBuilder: Root set to {self.root_dir}")
        self._sync_mtimes()

    def _is_ignored(self, p: Path) -> bool:
        """Checks if a path should be ignored (internal/hidden)."""
        p_abs = p.resolve()
        try:
            rel = p_abs.relative_to(self.root_dir)
            parts = rel.parts
        except ValueError as e:
            # print(f"[Debug] Not under root: {p_abs}")
            return True
            
        ignored_names = {".gemini", ".rpy", ".git", "venv", "__pycache__", ".rpy_cache"}
        for part in parts:
            if part in ignored_names:
                return True
        return False

    def _sync_mtimes(self):
        """Updates the internal mtime cache."""
        found = 0
        all_files = list(self.root_dir.rglob("*.py"))
        print(f"[Debug] _sync_mtimes: rglob found {len(all_files)} files total.")
        for py_file in all_files:
             if self._is_ignored(py_file):
                 continue
             found += 1
             self.mtimes[py_file.resolve()] = os.path.getmtime(py_file) # Changed from self.last_mtimes
        print(f"[Debug] _sync_mtimes: Initialized {found} source files.")
    def get_changed_files(self) -> List[Path]:
        """Detects which files have changed since last sync."""
        changed = []
        
        # Check for config changes first (Auto-Sync)
        for config_name in ["rpy.json", "wally.toml", "rpy.toml"]:
            config_path = self.root_dir / config_name
            if config_path.exists():
                mtime = os.path.getmtime(config_path)
                if config_path not in self.mtimes or mtime > self.mtimes[config_path]:
                    print(f"[*] Detected change in {config_name}. Auto-syncing dependencies...")
                    from transpiler.package_manager import PackageManager
                    pm = PackageManager(str(self.root_dir))
                    pm.install()
                    self.mtimes[config_path] = mtime

        # rglob can be slow on large projects, but necessary for now
        all_py_files = list(self.root_dir.rglob("*.py"))
        
        for py_file in all_py_files:
            if self._is_ignored(py_file):
                continue
            
            abs_path = py_file.resolve()
            mtime = os.path.getmtime(py_file)
            
            if abs_path not in self.mtimes or mtime > self.mtimes[abs_path]:
                changed.append(abs_path)
                self.mtimes[abs_path] = mtime
                
        return changed

    def build_incremental(self):
        """Builds only the files affected by recent changes."""
        changed = self.get_changed_files()
        if not changed:
            return

        print(f"[Incremental] Detected changes in {len(changed)} files.")
        
        # Identify all affected files (including dependents)
        affected: Set[Path] = set()
        for f in changed:
            affected.add(f)
            affected.update(self.graph.get_dependents(f))
            
        print(f"[Incremental] Total affected files to re-evaluate: {len(affected)}")
        
        if not affected:
            print("[Incremental] No source files affected.")
            return

        # Re-scan changed files to update the graph
        for f in changed:
            if f.exists():
                self.graph._process_file(f)
        
        # Build in topological order if possible, but for simple transpilation 
        # we can just build the affected set.
        for f in affected:
            if not f.exists():
                 continue
            self._compile_file(f)

    def _compile_file(self, file_path: Path):
        """Compiles a single file, using cache if possible."""
        try:
            with open(file_path, "r", encoding="utf-8") as f:
                source = f.read()
            
            # Check cache
            cached_ir = self.cache.get_cached_ir(source)
            if cached_ir:
                # Still check if we need to regenerate the Luau file 
                # (in case the .lua file was deleted but cache exists)
                print(f"  [Cache] {file_path.name} is up to date.")
                # For now, we always regenerate the text output to be safe
                # but we skip the heavy transformation steps.
                self._generate_luau(cached_ir, file_path)
                return

            print(f"  [Build] Compiling {file_path.name}...") # Retained original print, as the snippet's print was incomplete.
            tree = parse_file(file_path) # Changed parse_python(source) to parse_file(file_path)
            ir_module = transform(tree, filename=file_path) # Changed transform(tree) to transform(tree, filename=file_path)
            
            # Save to cache
            self.cache.save_ir(source, ir_module)
            
            # Generate Luau
            self._generate_luau(ir_module, file_path)
            
        except Exception as e:
            print(f"  [Error] Failed to compile {file_path.name}: {e}")

    def _generate_luau(self, ir_module, source_path: Path):
        """Generates Luau code from IR."""
        # Determine output path (mirroring logic from main.py)
        # For this prototype, we'll just put it in the same folder with .lua
        out_path = source_path.with_suffix(".lua")
        try:
            luau_code = generate(ir_module)
            with open(out_path, "w", encoding="utf-8") as f:
                f.write(luau_code)
        except Exception as e:
             print(f"  [Error] Generation failed for {source_path.name}: {e}")

def start_watcher(path: str, interval: float = 1.0):
    print(f"[*] Starting RPy Watcher on {path} (interval: {interval}s)")
    builder = IncrementalBuilder(path)
    
    try:
        while True:
            builder.build_incremental()
            time.sleep(interval)
    except KeyboardInterrupt:
        print("\n[*] Watcher stopped.")
