"""Static analysis of Python modules using AST."""

from __future__ import annotations

import ast
from dataclasses import dataclass, field
from pathlib import Path


@dataclass
class FunctionInfo:
    """Information about a function or method."""

    name: str
    lineno: int
    is_method: bool = False
    is_private: bool = False

    @property
    def display_name(self) -> str:
        """Name for display, with privacy indicator."""
        return self.name


@dataclass
class ClassInfo:
    """Information about a class."""

    name: str
    lineno: int
    methods: list[FunctionInfo] = field(default_factory=list)
    is_private: bool = False

    @property
    def public_methods(self) -> list[FunctionInfo]:
        """Get non-private, non-dunder methods."""
        return [
            m for m in self.methods
            if not m.name.startswith("_") or m.name.startswith("__") and m.name.endswith("__")
        ]


@dataclass
class ModuleSummary:
    """Summary of a Python module's contents."""

    classes: list[ClassInfo] = field(default_factory=list)
    functions: list[FunctionInfo] = field(default_factory=list)
    imports: list[str] = field(default_factory=list)
    parse_error: str | None = None

    @property
    def public_classes(self) -> list[ClassInfo]:
        """Get non-private classes."""
        return [c for c in self.classes if not c.name.startswith("_")]

    @property
    def public_functions(self) -> list[FunctionInfo]:
        """Get non-private module-level functions."""
        return [f for f in self.functions if not f.name.startswith("_")]

    def format_summary(self, max_items: int = 8) -> str:
        """Format a concise summary for display.

        Args:
            max_items: Maximum items to show per category before truncating.

        Returns:
            Formatted summary string.
        """
        if self.parse_error:
            return f"_Could not parse: {self.parse_error}_"

        lines = []

        # Classes
        pub_classes = self.public_classes
        if pub_classes:
            names = [c.name for c in pub_classes[:max_items]]
            class_str = ", ".join(f"`{n}`" for n in names)
            if len(pub_classes) > max_items:
                class_str += f" _+{len(pub_classes) - max_items} more_"
            lines.append(f"**Classes:** {class_str}")

        # Functions
        pub_funcs = self.public_functions
        if pub_funcs:
            names = [f.name for f in pub_funcs[:max_items]]
            func_str = ", ".join(f"`{n}`" for n in names)
            if len(pub_funcs) > max_items:
                func_str += f" _+{len(pub_funcs) - max_items} more_"
            lines.append(f"**Functions:** {func_str}")

        if not lines:
            return "_No public classes or functions_"

        return "\n".join(lines)


def analyze_python_file(path: Path) -> ModuleSummary:
    """Analyze a Python file and extract its structure.

    Args:
        path: Path to the Python file.

    Returns:
        ModuleSummary with classes, functions, and imports.
    """
    summary = ModuleSummary()

    try:
        source = path.read_text(encoding="utf-8")
        tree = ast.parse(source, filename=str(path))
    except SyntaxError as e:
        summary.parse_error = f"Syntax error at line {e.lineno}"
        return summary
    except Exception as e:
        summary.parse_error = str(e)
        return summary

    for node in ast.iter_child_nodes(tree):
        if isinstance(node, ast.ClassDef):
            class_info = ClassInfo(
                name=node.name,
                lineno=node.lineno,
                is_private=node.name.startswith("_"),
            )

            # Extract methods
            for item in node.body:
                if isinstance(item, ast.FunctionDef | ast.AsyncFunctionDef):
                    class_info.methods.append(
                        FunctionInfo(
                            name=item.name,
                            lineno=item.lineno,
                            is_method=True,
                            is_private=item.name.startswith("_"),
                        )
                    )

            summary.classes.append(class_info)

        elif isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
            summary.functions.append(
                FunctionInfo(
                    name=node.name,
                    lineno=node.lineno,
                    is_method=False,
                    is_private=node.name.startswith("_"),
                )
            )

        elif isinstance(node, ast.Import):
            for alias in node.names:
                summary.imports.append(alias.name)

        elif isinstance(node, ast.ImportFrom):
            if node.module:
                summary.imports.append(node.module)

    return summary


def analyze_module(path: str, cwd: Path | None = None) -> ModuleSummary:
    """Analyze a module by path.

    Args:
        path: Relative path to the module.
        cwd: Working directory (defaults to cwd).

    Returns:
        ModuleSummary for the module.
    """
    if cwd is None:
        cwd = Path.cwd()

    full_path = cwd / path

    if not full_path.exists():
        return ModuleSummary(parse_error="File not found")

    if not full_path.suffix == ".py":
        return ModuleSummary(parse_error="Not a Python file")

    return analyze_python_file(full_path)
