"""
Scan Python source files for @ai-decorated functions.

Uses AST parsing so we don't need to import the target file
(which could have side effects or missing dependencies).
"""

import ast
import textwrap
from dataclasses import dataclass
from pathlib import Path


@dataclass
class FuncSpec:
    """A function spec extracted from source code."""
    name: str
    signature: str
    docstring: str
    lineno: int
    source_file: str


def scan_file(filepath: str | Path) -> list[FuncSpec]:
    """Find all @ai-decorated stub functions in a Python file."""
    filepath = Path(filepath)
    source = filepath.read_text()
    tree = ast.parse(source, filename=str(filepath))
    
    specs = []
    for node in ast.walk(tree):
        if not isinstance(node, ast.FunctionDef):
            continue
        
        # check for @ai decorator
        has_ai = False
        for dec in node.decorator_list:
            if isinstance(dec, ast.Name) and dec.id == "ai":
                has_ai = True
            elif isinstance(dec, ast.Call):
                if isinstance(dec.func, ast.Name) and dec.func.id == "ai":
                    has_ai = True
        
        if not has_ai:
            continue
        
        # check if body is just ... or pass
        body_stmts = node.body
        # skip docstring if present
        start = 0
        if (body_stmts and isinstance(body_stmts[0], ast.Expr) 
                and isinstance(body_stmts[0].value, (ast.Constant, ast.Str))):
            start = 1
        
        remaining = body_stmts[start:]
        is_stub = all(
            (isinstance(s, ast.Expr) and isinstance(s.value, ast.Constant) 
             and s.value.value is Ellipsis)
            or (isinstance(s, ast.Pass))
            for s in remaining
        ) if remaining else True
        
        if not is_stub:
            continue
        
        # extract docstring
        docstring = ast.get_docstring(node) or ""
        
        # build signature string from the AST
        sig = _build_signature(node)
        
        specs.append(FuncSpec(
            name=node.name,
            signature=sig,
            docstring=docstring,
            lineno=node.lineno,
            source_file=str(filepath),
        ))
    
    return specs


def _build_signature(node: ast.FunctionDef) -> str:
    """Reconstruct the function signature from an AST node."""
    args = node.args
    parts = []
    
    # positional args
    num_defaults = len(args.defaults)
    num_args = len(args.args)
    non_default_count = num_args - num_defaults
    
    for i, arg in enumerate(args.args):
        s = arg.arg
        if arg.annotation:
            s += f": {ast.unparse(arg.annotation)}"
        if i >= non_default_count:
            default = args.defaults[i - non_default_count]
            s += f" = {ast.unparse(default)}"
        parts.append(s)
    
    # *args
    if args.vararg:
        s = f"*{args.vararg.arg}"
        if args.vararg.annotation:
            s += f": {ast.unparse(args.vararg.annotation)}"
        parts.append(s)
    
    # keyword-only args
    for i, arg in enumerate(args.kwonlyargs):
        s = arg.arg
        if arg.annotation:
            s += f": {ast.unparse(arg.annotation)}"
        if args.kw_defaults[i] is not None:
            s += f" = {ast.unparse(args.kw_defaults[i])}"
        parts.append(s)
    
    # **kwargs
    if args.kwarg:
        s = f"**{args.kwarg.arg}"
        if args.kwarg.annotation:
            s += f": {ast.unparse(args.kwarg.annotation)}"
        parts.append(s)
    
    sig = f"({', '.join(parts)})"
    
    if node.returns:
        sig += f" -> {ast.unparse(node.returns)}"
    
    return sig
