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

module Lattice
  module Inspections
    # Reports method parameters that are declared but never referenced inside
    # the method body.
    #
    # Parameters whose names begin with `_` are intentionally skipped.
    class UnusedParameterInspection < Inspection
      def run(
        uri : String,
        source : String,
        lines : Array(String),
        ast : ::Crystal::ASTNode,
      ) : Array(::LSP::Diagnostic)
        diagnostics = [] of ::LSP::Diagnostic
        visitor = Visitor.new(diagnostics)
        ast.accept(visitor)
        diagnostics
      end

      private class Visitor < ::Crystal::Visitor
        def initialize(@diagnostics : Array(::LSP::Diagnostic))
          @method_stack = [] of MethodInfo
        end

        def visit(node : ::Crystal::Def) : Bool
          @method_stack.push(MethodInfo.new(node))
          true
        end

        def end_visit(node : ::Crystal::Def) : Nil
          info = @method_stack.pop
          info.params.each do |arg|
            name = arg.name
            next if name.starts_with?('_')
            next if info.reads.includes?(name)

            if loc = arg.location
              line = (loc.line_number - 1).clamp(0, Int32::MAX)
              col  = (loc.column_number - 1).clamp(0, Int32::MAX)
              @diagnostics << ::LSP::Diagnostic.new(
                ::LSP::Range.new(
                  ::LSP::Position.new(line, col),
                  ::LSP::Position.new(line, col + name.size),
                ),
                "Unused parameter '#{name}'",
                ::LSP::DiagnosticSeverity::Warning,
                "lattice",
              )
            end
          end
        end

        def visit(node : ::Crystal::Var) : Bool
          if info = @method_stack.last?
            info.reads.add(node.name)
          end
          true
        end

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

      private class MethodInfo
        getter params : Array(::Crystal::Arg)
        property reads = Set(String).new

        def initialize(defn : ::Crystal::Def)
          @params = defn.args
        end
      end
    end
  end
end
