"""Specialist: unreachable code after return/raise/break/continue."""

from __future__ import annotations

import ast
from autodebug.agent.specialists.base import ASTData, Finding, Specialist


_TERMINAL = (ast.Return, ast.Raise, ast.Break, ast.Continue)


class UnreachableCodeSpecialist(Specialist):
    name = "unreachable_code"
    trigger_features: list[str] = []  # always run

    async def run(self, source: str, ast_data: ASTData) -> list[Finding]:
        findings: list[Finding] = []

        for node in ast.walk(ast_data.tree):
            # Check function/method bodies and if/else/for/while/try bodies
            bodies = []
            if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                bodies.append(node.body)
            elif isinstance(node, ast.If):
                bodies.append(node.body)
                if node.orelse:
                    bodies.append(node.orelse)
            elif isinstance(node, (ast.For, ast.While, ast.AsyncFor)):
                bodies.append(node.body)
            elif isinstance(node, ast.Try):
                bodies.append(node.body)
                for handler in node.handlers:
                    bodies.append(handler.body)
                if node.orelse:
                    bodies.append(node.orelse)
                if node.finalbody:
                    bodies.append(node.finalbody)
            elif isinstance(node, ast.With) or isinstance(node, ast.AsyncWith):
                bodies.append(node.body)

            for body in bodies:
                for i, stmt in enumerate(body):
                    if isinstance(stmt, _TERMINAL) and i < len(body) - 1:
                        next_stmt = body[i + 1]
                        # Skip if the next statement is a function/class def (hoisted)
                        if isinstance(next_stmt, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
                            continue
                        # Skip if it's just a string (docstring after early return)
                        if (isinstance(next_stmt, ast.Expr)
                                and isinstance(next_stmt.value, ast.Constant)
                                and isinstance(next_stmt.value.value, str)):
                            continue
                        terminal_name = type(stmt).__name__.lower()
                        findings.append(Finding(
                            line=next_stmt.lineno,
                            severity="MEDIUM",
                            message=f"Unreachable code after `{terminal_name}` on line {stmt.lineno}",
                            specialist=self.name,
                            confidence="DEFINITE",
                        ))
                        break  # only report first unreachable stmt per block

        return findings
