"""File scoring and selection for LLM priority.

Scores files based on importance signals (entry points, centrality,
complexity, test coverage) and selects top files within token budget.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from unfamiliar.analysis.models import ScoredFile
from unfamiliar.constants import (
    FILE_PRIORITY_ENTRY_POINT,
    FILE_PRIORITY_HIGH_CENTRALITY,
    FILE_PRIORITY_HIGH_COMPLEXITY,
    FILE_PRIORITY_TEST_FILE,
)

if TYPE_CHECKING:
    from unfamiliar.analysis.models import (
        FileInfo,
        FileTestInfo,
        ImportGraph,
        ParsedFile,
    )

# Thresholds for scoring
_HIGH_CENTRALITY_THRESHOLD = 3  # in-degree >= this is a "hub"
_HIGH_COMPLEXITY_THRESHOLD = 15  # branching nodes >= this is "complex"


def score_files(
    files: list[FileInfo],
    parsed_files: dict[str, ParsedFile],
    graph: ImportGraph,
    test_info: FileTestInfo,
    entry_points: set[str],
) -> list[ScoredFile]:
    """Score files for LLM priority selection.

    Scoring criteria (additive):
    - Entry point file: +100
    - High centrality (hub): +50
    - High complexity: +30
    - Test file: +20

    Args:
        files: All discovered files with metadata.
        parsed_files: Parse results keyed by file path.
        graph: Import graph with centrality metrics.
        test_info: Test detection results.
        entry_points: Set of detected entry points.

    Returns:
        List of ScoredFile sorted by score descending, then by path.
    """
    scored: list[ScoredFile] = []

    for file_info in files:
        path = file_info.path
        score = 0
        reasons: list[str] = []

        # Entry point bonus
        if path in entry_points:
            score += FILE_PRIORITY_ENTRY_POINT
            reasons.append("entry point")

        # High centrality bonus
        in_degree = graph.in_degree.get(path, 0)
        if in_degree >= _HIGH_CENTRALITY_THRESHOLD:
            score += FILE_PRIORITY_HIGH_CENTRALITY
            reasons.append(f"hub (imported by {in_degree} files)")

        # High complexity bonus
        parsed = parsed_files.get(path)
        if parsed and parsed.complexity >= _HIGH_COMPLEXITY_THRESHOLD:
            score += FILE_PRIORITY_HIGH_COMPLEXITY
            reasons.append(f"complex (score: {parsed.complexity})")

        # Test file bonus
        if path in test_info.test_files:
            score += FILE_PRIORITY_TEST_FILE
            reasons.append("test file")

        scored.append(ScoredFile(path=path, score=score, reasons=reasons))

    # Sort by score descending, then by path for stability
    scored.sort(key=lambda f: (-f.score, f.path))
    return scored


def select_key_files(
    scored_files: list[ScoredFile],
    files: dict[str, FileInfo],
    token_budget: int,
    avg_tokens_per_byte: float = 0.25,
) -> list[ScoredFile]:
    """Select highest-priority files that fit within the token budget.

    NOTE: Reserved for future API use. Currently unused in production
    (orchestrator calls score_files only). Maintained for tests and
    potential future token-budget-aware selection.

    Walks scored files in priority order and adds files until the
    token budget is exhausted. At least one file is always selected
    if any exist, even if it exceeds the budget.

    Args:
        scored_files: Files sorted by priority (highest first).
        files: File metadata keyed by path.
        token_budget: Maximum estimated tokens for selected files.
        avg_tokens_per_byte: Conversion factor for token estimation.

    Returns:
        Subset of scored_files that fits within budget.
    """
    if not scored_files:
        return []

    selected: list[ScoredFile] = []
    total_tokens = 0

    for scored in scored_files:
        file_info = files.get(scored.path)
        if file_info is None:
            continue

        # Estimate tokens for this file
        estimated_tokens = int(file_info.size_bytes * avg_tokens_per_byte)

        # Always include at least one file
        if not selected:
            selected.append(scored)
            total_tokens = estimated_tokens
            continue

        # Check if adding this file would exceed budget
        if total_tokens + estimated_tokens <= token_budget:
            selected.append(scored)
            total_tokens += estimated_tokens

    return selected
