"""Data structures for static analysis pipeline.

These dataclasses represent intermediate results that flow between
pipeline stages. They are internal to the analysis package — the
orchestrator converts them to Module/FileSnapshot models for persistence.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Literal

if TYPE_CHECKING:
    from unfamiliar.constants import EntryType, InvestigationCategory


@dataclass(frozen=True)
class FileInfo:
    """Metadata about a discovered source file.

    Attributes:
        path: Relative path from repo root (no leading slash).
        language: Language name from adapter (e.g., "python", "unknown").
        size_bytes: File size in bytes.
        content_hash: SHA-256 hex digest of file contents.
    """

    path: str
    language: str
    size_bytes: int
    content_hash: str


@dataclass(frozen=True)
class ParsedImport:
    """A single import statement extracted from source code.

    Attributes:
        source_path: Path of the file containing this import.
        target: The raw import target (e.g., "os.path", ".utils", "../helpers").
        is_relative: True for relative imports (from . import x).
        names: Specific names imported (empty for "import x", ["*"] for star imports).
    """

    source_path: str
    target: str
    is_relative: bool
    names: list[str] = field(default_factory=list)


@dataclass(frozen=True)
class FunctionDef:
    """A function or class definition found in a source file.

    Attributes:
        name: Function or class name.
        line_start: 1-indexed starting line number.
        line_end: 1-indexed ending line number (inclusive).
        is_async: True for async functions.
        kind: "function", "class", or "method" (function inside a class).
    """

    name: str
    line_start: int
    line_end: int
    is_async: bool
    kind: Literal["function", "class", "method"]


@dataclass(frozen=True)
class EntryPoint:
    """A detected entry point into the codebase.

    Attributes:
        file_path: Relative path from repo root.
        entry_type: Type of entry point (CLI, API route, main, etc.).
        description: Human-readable description of the entry point.
        line: 1-indexed line number where entry point is defined.
    """

    file_path: str
    entry_type: EntryType
    description: str
    line: int


@dataclass
class ParsedFile:
    """Complete parse results for one source file.

    Attributes:
        path: Relative path from repo root.
        language: Language name from adapter.
        imports: All import statements found in the file.
        definitions: All function/class definitions found.
        entry_points: Detected entry points (main blocks, CLI apps, etc.).
        complexity: Approximate cyclomatic complexity (branching node count).
        line_count: Total lines in the file.
    """

    path: str
    language: str
    imports: list[ParsedImport] = field(default_factory=list)
    definitions: list[FunctionDef] = field(default_factory=list)
    entry_points: list[EntryPoint] = field(default_factory=list)
    complexity: int = 0
    line_count: int = 0


@dataclass
class ImportGraph:
    """Directed graph of import relationships between repo files.

    Attributes:
        edges: Adjacency dict mapping source_path to set of target_paths.
        files: All files in the graph, mapped by path.
        in_degree: Count of files that import each path (importers).
    """

    edges: dict[str, set[str]] = field(default_factory=dict)
    files: dict[str, FileInfo] = field(default_factory=dict)
    in_degree: dict[str, int] = field(default_factory=dict)


@dataclass
class DetectedModule:
    """A cluster of related files detected as a logical module.

    Attributes:
        files: File paths belonging to this module.
        primary_path: Dominant directory or file representing the module.
        file_count: Number of files (convenience for len(files)).
        confidence: Cluster cohesion score (0.0 to 1.0).
        category_hints: Investigation categories likely relevant to this module.
    """

    files: list[str] = field(default_factory=list)
    primary_path: str = ""
    file_count: int = 0
    confidence: float = 0.0
    category_hints: list[InvestigationCategory] = field(default_factory=list)


@dataclass
class FileTestInfo:
    """Test detection results including optional coverage data.

    Attributes:
        test_files: Set of paths identified as test files.
        source_to_tests: Mapping from source file paths to test file paths.
        untested_files: Source files not covered by any test file.
        tier: "basic" (naming convention only) or "enhanced" (with coverage data).
        coverage_summary: Per-file line coverage (0.0 to 1.0), empty in basic tier.
    """

    test_files: set[str] = field(default_factory=set)
    source_to_tests: dict[str, list[str]] = field(default_factory=dict)
    untested_files: set[str] = field(default_factory=set)
    tier: Literal["basic", "enhanced"] = "basic"
    coverage_summary: dict[str, float] = field(default_factory=dict)


@dataclass
class ScoredFile:
    """A file with its computed priority score for LLM selection.

    Attributes:
        path: Relative path from repo root.
        score: Priority score (higher = more important).
        reasons: Human-readable reasons for the score components.
    """

    path: str
    score: int
    reasons: list[str] = field(default_factory=list)


@dataclass(frozen=True)
class SecretFinding:
    """A detected potential secret in source code.

    Attributes:
        file_path: Relative path to the file containing the secret.
        line_number: 1-indexed line number where the secret was found.
        secret_type: Plugin name (e.g., "Base64 High Entropy String").
        description: Human-readable summary of the finding.
    """

    file_path: str
    line_number: int
    secret_type: str
    description: str


@dataclass(frozen=True)
class VulnerabilityFinding:
    """A known CVE in a dependency.

    Attributes:
        package_name: Name of the vulnerable package.
        installed_version: Currently installed version.
        vulnerability_id: CVE, PYSEC, or GHSA identifier.
        severity: "low", "moderate", "high", or "critical".
        description: Human-readable description of the vulnerability.
        fix_version: Version that fixes the issue (empty if no fix).
        ecosystem: "python" or "javascript".
    """

    package_name: str
    installed_version: str
    vulnerability_id: str
    severity: Literal["low", "moderate", "high", "critical"]
    description: str
    fix_version: str
    ecosystem: Literal["python", "javascript"]


@dataclass
class SecurityResult:
    """Aggregate security analysis results.

    Attributes:
        secrets: List of detected potential secrets.
        vulnerabilities: List of known CVEs in dependencies.
        manifests_scanned: Which manifest files were checked.
        scan_errors: Errors encountered during scanning.
    """

    secrets: list[SecretFinding] = field(default_factory=list)
    vulnerabilities: list[VulnerabilityFinding] = field(default_factory=list)
    manifests_scanned: list[str] = field(default_factory=list)
    scan_errors: list[str] = field(default_factory=list)


@dataclass
class AnalysisResult:
    """Complete output of the static analysis pipeline.

    This is the top-level result consumed by Phase 6 LLM integration
    to generate briefings and missions.

    Attributes:
        files: All discovered source files with metadata.
        parsed_files: Parse results keyed by file path.
        import_graph: Import relationship graph with centrality metrics.
        modules: Detected module clusters.
        test_info: Test file detection and coverage results.
        scored_files: Files ranked by priority (sorted by score desc).
        entry_points: All detected entry points across the codebase.
        languages: Distribution of languages (language -> file count).
        repo_path: Absolute path to the analyzed repository.
        security: Security analysis results (secrets, vulnerabilities).
        excluded_count: Number of files excluded by filter (for reporting).
    """

    files: list[FileInfo] = field(default_factory=list)
    parsed_files: dict[str, ParsedFile] = field(default_factory=dict)
    import_graph: ImportGraph = field(default_factory=ImportGraph)
    modules: list[DetectedModule] = field(default_factory=list)
    test_info: FileTestInfo = field(default_factory=FileTestInfo)
    scored_files: list[ScoredFile] = field(default_factory=list)
    entry_points: list[EntryPoint] = field(default_factory=list)
    languages: dict[str, int] = field(default_factory=dict)
    repo_path: str = ""
    security: SecurityResult = field(default_factory=SecurityResult)
    excluded_count: int = 0
