require "./inspection"
require "compiler/crystal/syntax"

module Lattice
  module Inspections
    # Flags `rescue` clauses with an empty body.
    #
    # An empty rescue clause silently swallows exceptions, which almost always
    # indicates a mistake or unfinished error-handling code.
    #
    # Detection strategy: walk `ExceptionHandler` nodes in the AST and inspect
    # each rescue body.  A body is considered empty when:
    #   - it is a `Nop` node, OR
    #   - it is an `Expressions` node with zero non-Nop children.
    #
    # Bare `rescue` with no type filter is flagged; typed rescue clauses with
    # empty bodies (e.g. `rescue Foo; end`) are also flagged.
    class EmptyRescueInspection < Inspection
      def run(
        uri : String,
        source : String,
        lines : Array(String),
        ast : ::Crystal::ASTNode,
      ) : Array(::LSP::Diagnostic)
        diagnostics = [] of ::LSP::Diagnostic
        visitor = Visitor.new(diagnostics)
        begin
          ast.accept(visitor)
        rescue ex : Exception
          # Never let a visitor crash block other inspections.
        end
        diagnostics
      end

      private class Visitor < ::Crystal::Visitor
        def initialize(@diagnostics : Array(::LSP::Diagnostic))
        end

        def visit(node : ::Crystal::ExceptionHandler) : Bool
          node.rescues.try &.each do |rescue_clause|
            body = rescue_clause.body
            next unless body_empty?(body)

            loc = rescue_clause.location
            next unless loc

            ln  = loc.line_number - 1
            col = loc.column_number - 1

            @diagnostics << ::LSP::Diagnostic.new(
              ::LSP::Range.new(
                ::LSP::Position.new(ln, col),
                ::LSP::Position.new(ln, col + "rescue".size),
              ),
              "Empty rescue clause — exceptions are silently swallowed",
              ::LSP::DiagnosticSeverity::Warning,
              "lattice",
            )
          end
          true
        end

        def visit(node : ::Crystal::ASTNode) : Bool
          true
        end

        private def body_empty?(body : ::Crystal::ASTNode) : Bool
          case body
          when ::Crystal::Nop
            true
          when ::Crystal::Expressions
            body.expressions.all? { |e| e.is_a?(::Crystal::Nop) }
          else
            false
          end
        end
      end
    end
  end
end
