"""
transpiler/linter.py — Stage 17: Roblox-specific static analysis.
"""

from transpiler.middleend import ir
from transpiler.infrastructure.diagnostics import manager, Severity
from typing import Optional, Set

class LinterPass:
    def __init__(self, diagnostics=None):
        self.diagnostics = diagnostics or manager

    def lint(self, node: ir.IRNode):
        method_name = f"visit_{type(node).__name__}"
        visitor = getattr(self, method_name, self.generic_visit)
        visitor(node)

    def generic_visit(self, node: ir.IRNode):
        for field in node.__dataclass_fields__:
            value = getattr(node, field)
            if isinstance(value, ir.IRNode):
                self.lint(value)
            elif isinstance(value, list):
                for item in value:
                    if isinstance(item, ir.IRNode):
                        self.lint(item)

    def visit_IRFunctionCall(self, node: ir.IRFunctionCall):
        if isinstance(node.func, ir.IRVariable):
            name = node.func.name
            if name == "wait":
                self.diagnostics.warning(
                    "Legacy 'wait()' detected. Use 'task.wait()' for better performance and reliability.",
                    line=node.lineno, col=node.col_offset,
                    hint="task.wait() respects the 60Hz heartbeat and prevents throttling issues."
                )
            elif name == "spawn":
                self.diagnostics.warning(
                    "Legacy 'spawn()' detected. Use 'task.spawn()' for predictable execution order.",
                    line=node.lineno, col=node.col_offset,
                    hint="task.spawn() executes the thread immediately without the 0.03s delay."
                )
            elif name == "delay":
                self.diagnostics.warning(
                    "Legacy 'delay()' detected. Use 'task.delay()'.",
                    line=node.lineno, col=node.col_offset
                )
        self.generic_visit(node)

    def visit_IRVariable(self, node: ir.IRVariable):
        if node.name == "_G":
            self.diagnostics.warning(
                "Usage of global table '_G' detected. This is generally discouraged in Roblox.",
                line=node.lineno, col=node.col_offset,
                hint="Consider using ModuleScripts for shared state management."
            )
        elif node.name == "shared":
             self.diagnostics.warning(
                "Usage of 'shared' table detected. This is generally discouraged in Roblox.",
                line=node.lineno, col=node.col_offset,
                hint="Consider using ModuleScripts for shared state management."
            )

    def visit_IRModule(self, node: ir.IRModule):
        self.generic_visit(node)

    def visit_IRFunctionDef(self, node: ir.IRFunctionDef):
        self.generic_visit(node)

    def visit_IRIfStatement(self, node: ir.IRIfStatement):
        self._check_truthiness(node.condition)
        self.generic_visit(node)

    def visit_IRWhileStatement(self, node: ir.IRWhileStatement):
        self._check_truthiness(node.condition)
        self.generic_visit(node)

    def _check_truthiness(self, node: ir.IRExpression):
        # Python literal 0 and empty string "" are truthy in Lua but falsy in Python.
        # RPy wraps these in py_bool() usually, but if the user uses them directly
        # in a way that bypasses our safety checks (rare but possible in IR), we warn.
        if isinstance(node, ir.IRLiteral):
            if node.value == 0:
                self.diagnostics.warning(
                    "Literal '0' detected in truthiness context. In Lua, 0 is truthy.",
                    line=node.lineno, col=node.col_offset,
                    hint="RPy usually handles this, but explicit 'x != 0' is safer for AAA scale code."
                )
            elif node.value == "":
                self.diagnostics.warning(
                    "Literal empty string detected in truthiness context. In Lua, '' is truthy.",
                    line=node.lineno, col=node.col_offset,
                    hint="Use 'len(s) > 0' for unambiguous cross-language intent."
                )

    def visit_IRIndexOperation(self, node: ir.IRIndexOperation):
        # Off-by-one detection: Python range starts at 0, Lua at 1.
        # If we see a literal 0 being used to index, it's a high-probability bug
        # since RPy doesn't (yet) automatically offset every single index access.
        if isinstance(node.index, ir.IRLiteral) and node.index.value == 0:
            self.diagnostics.warning(
                "Index 0 detected. Roblox Luau uses 1-based indexing.",
                line=node.lineno, col=node.col_offset,
                hint="Python's s[0] becomes s[1] in Lua. Ensure this index is intended."
            )
        self.generic_visit(node)

    def visit_IRForStatement(self, node: ir.IRForStatement):
        self.generic_visit(node)

    def visit_IRGenericForStatement(self, node: ir.IRGenericForStatement):
        self.generic_visit(node)

def lint_ir(module: ir.IRModule, diagnostics=None):
    linter = LinterPass(diagnostics=diagnostics)
    linter.lint(module)
