"""
Smart Crawl - Adaptive project crawling based on project type detection.

[20251226_FEATURE] Pro tier feature for crawl_project.

Detects project type and adapts crawl behavior:
- Node.js (package.json): prioritize src/, lib/, skip node_modules
- Python (pyproject.toml/setup.py): follow Poetry/PDM conventions
- Go (go.mod): follow Go module structure
- Rust (Cargo.toml): follow Rust conventions

Usage:
    from code_scalpel.analysis.smart_crawl import SmartCrawler, detect_project_type

    project_type = detect_project_type("/path/to/project")
    crawler = SmartCrawler("/path/to/project")
    config = crawler.get_crawl_config()
"""

from __future__ import annotations

import json
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any


@dataclass
class ProjectTypeInfo:
    """Detected project type information."""

    project_type: str  # "nodejs", "python", "go", "rust", "java", "mixed", "unknown"
    confidence: float  # 0.0 - 1.0
    markers: list[str]  # Files/patterns that triggered detection
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class SmartCrawlConfig:
    """Configuration generated by smart crawl detection."""

    priority_dirs: list[str]  # Directories to crawl first
    skip_dirs: set[str]  # Directories to skip entirely
    include_patterns: list[str]  # File patterns to include
    exclude_patterns: list[str]  # File patterns to exclude
    max_depth_overrides: dict[str, int]  # Per-directory depth limits
    project_type: ProjectTypeInfo


class SmartCrawler:
    """Adaptive crawler that adjusts behavior based on project type."""

    # Default directories to skip for each project type
    PROJECT_EXCLUDES: dict[str, set[str]] = {
        "nodejs": {
            "node_modules",
            "dist",
            "build",
            ".next",
            ".nuxt",
            "coverage",
            ".turbo",
        },
        "python": {
            "__pycache__",
            ".venv",
            "venv",
            ".tox",
            ".pytest_cache",
            ".mypy_cache",
            "htmlcov",
            "dist",
            "build",
            "*.egg-info",
            ".eggs",
        },
        "go": {"vendor"},
        "rust": {"target"},
        "java": {"target", "build", ".gradle", "out"},
        "mixed": set(),  # Will combine relevant excludes
        "unknown": set(),
    }

    # Priority directories for each project type
    PROJECT_PRIORITIES: dict[str, list[str]] = {
        "nodejs": ["src", "lib", "app", "pages", "components", "server"],
        "python": ["src", "lib", "app", "core", "api", "services"],
        "go": ["cmd", "pkg", "internal"],
        "rust": ["src"],
        "java": ["src/main/java", "src"],
        "mixed": ["src", "lib", "app"],
        "unknown": ["src"],
    }

    def __init__(self, project_root: str | Path):
        """Initialize smart crawler with project root."""
        self.root = Path(project_root)
        self._project_type: ProjectTypeInfo | None = None

    def detect_project_type(self) -> ProjectTypeInfo:
        """Detect the project type based on marker files."""
        if self._project_type is not None:
            return self._project_type

        markers_found: list[str] = []
        detected_types: dict[str, float] = {}

        # Node.js detection
        pkg_json = self.root / "package.json"
        if pkg_json.exists():
            markers_found.append("package.json")
            try:
                content = json.loads(pkg_json.read_text())
                # Higher confidence if it has dependencies
                has_deps = bool(content.get("dependencies") or content.get("devDependencies"))
                detected_types["nodejs"] = 0.9 if has_deps else 0.7
            except Exception:
                detected_types["nodejs"] = 0.5

        # Check for specific Node.js frameworks
        if (self.root / "next.config.js").exists() or (self.root / "next.config.mjs").exists():
            markers_found.append("next.config.js")
            detected_types["nodejs"] = max(detected_types.get("nodejs", 0), 0.95)

        # Python detection
        py_markers = [
            "pyproject.toml",
            "setup.py",
            "setup.cfg",
            "requirements.txt",
            "Pipfile",
        ]
        for marker in py_markers:
            if (self.root / marker).exists():
                markers_found.append(marker)
                # pyproject.toml is strongest indicator
                if marker == "pyproject.toml":
                    detected_types["python"] = max(detected_types.get("python", 0), 0.9)
                else:
                    detected_types["python"] = max(detected_types.get("python", 0), 0.7)

        # Go detection
        if (self.root / "go.mod").exists():
            markers_found.append("go.mod")
            detected_types["go"] = 0.95

        # Rust detection
        if (self.root / "Cargo.toml").exists():
            markers_found.append("Cargo.toml")
            detected_types["rust"] = 0.95

        # Java detection
        java_markers = ["pom.xml", "build.gradle", "build.gradle.kts"]
        for marker in java_markers:
            if (self.root / marker).exists():
                markers_found.append(marker)
                detected_types["java"] = max(detected_types.get("java", 0), 0.9)

        # Determine final project type
        if not detected_types:
            self._project_type = ProjectTypeInfo(
                project_type="unknown",
                confidence=0.0,
                markers=[],
            )
        elif len(detected_types) == 1:
            pt, conf = next(iter(detected_types.items()))
            self._project_type = ProjectTypeInfo(
                project_type=pt,
                confidence=conf,
                markers=markers_found,
            )
        else:
            # Multiple types detected - it's a mixed/monorepo
            best_type = max(detected_types.items(), key=lambda x: x[1])
            self._project_type = ProjectTypeInfo(
                project_type="mixed",
                confidence=0.8,
                markers=markers_found,
                metadata={
                    "detected_types": detected_types,
                    "primary_type": best_type[0],
                },
            )

        return self._project_type

    def get_crawl_config(self) -> SmartCrawlConfig:
        """Generate crawl configuration based on project type."""
        project_type = self.detect_project_type()
        pt = project_type.project_type

        # Get base excludes for project type
        skip_dirs = set(self.PROJECT_EXCLUDES.get(pt, set()))

        # For mixed projects, combine excludes from detected types
        if pt == "mixed" and project_type.metadata.get("detected_types"):
            for detected_type in project_type.metadata["detected_types"].keys():
                skip_dirs.update(self.PROJECT_EXCLUDES.get(detected_type, set()))

        # Add common excludes
        skip_dirs.update(
            {
                ".git",
                ".svn",
                ".hg",
                ".idea",
                ".vscode",
                "*.egg-info",
            }
        )

        # Get priority directories
        priority_dirs = list(self.PROJECT_PRIORITIES.get(pt, ["src"]))

        # For mixed, combine priorities
        if pt == "mixed" and project_type.metadata.get("primary_type"):
            primary = project_type.metadata["primary_type"]
            priority_dirs = list(self.PROJECT_PRIORITIES.get(primary, ["src"]))

        # Filter to only existing directories
        priority_dirs = [d for d in priority_dirs if (self.root / d).exists()]

        # Build include/exclude patterns based on project type
        include_patterns: list[str] = []
        exclude_patterns: list[str] = []

        if pt in ("nodejs", "mixed"):
            include_patterns.extend(["*.js", "*.jsx", "*.ts", "*.tsx", "*.mjs", "*.cjs"])
            exclude_patterns.extend(["*.min.js", "*.bundle.js"])
        if pt in ("python", "mixed"):
            include_patterns.extend(["*.py", "*.pyi"])
            exclude_patterns.extend(["*_pb2.py", "*_pb2_grpc.py"])  # Protobuf generated
        if pt in ("go", "mixed"):
            include_patterns.extend(["*.go"])
            exclude_patterns.extend(["*_test.go"])  # Optionally skip tests
        if pt in ("rust", "mixed"):
            include_patterns.extend(["*.rs"])
        if pt in ("java", "mixed"):
            include_patterns.extend(["*.java", "*.kt", "*.scala"])

        return SmartCrawlConfig(
            priority_dirs=priority_dirs,
            skip_dirs=skip_dirs,
            include_patterns=include_patterns,
            exclude_patterns=exclude_patterns,
            max_depth_overrides={},
            project_type=project_type,
        )


def detect_project_type(project_root: str | Path) -> ProjectTypeInfo:
    """Convenience function to detect project type."""
    crawler = SmartCrawler(project_root)
    return crawler.detect_project_type()


def get_smart_crawl_config(project_root: str | Path) -> SmartCrawlConfig:
    """Convenience function to get smart crawl configuration."""
    crawler = SmartCrawler(project_root)
    return crawler.get_crawl_config()
