# frozen_string_literal: true

require "ripper"

module SmartTodo
  module Parser
    # A MethodNode represent an event associated to a TODO.
    class MethodNode
      attr_reader :method_name, :arguments

      # @param method_name [Symbol]
      # @param arguments [Array<String>]
      def initialize(method_name, arguments)
        @arguments = arguments
        @method_name = method_name
      end
    end

    # This class is used to parse the ruby TODO() comment.
    class MetadataParser < Ripper
      class << self
        # @param source [String] the actual Ruby code
        def parse(source)
          sexp = new(source).parse
          Visitor.new.tap { |v| v.process(sexp) }
        end
      end

      # @return [Array] an Array of Array
      #   the first element from each inner array is a token
      def on_stmts_add(_, data)
        data
      end

      # @param method [String] the name of the method
      #   when the parser hits one.
      # @param args [Array]
      # @return [Array, MethodNode]
      def on_method_add_arg(method, args)
        if method == "TODO"
          args
        else
          MethodNode.new(method, args)
        end
      end

      # @param list [nil, Array]
      # @param arg [String]
      # @return [Array]
      def on_args_add(list, arg)
        Array(list) << arg
      end

      # @param string_content [String]
      # @return [String]
      def on_string_add(_, string_content)
        string_content
      end

      # @param key [String]
      # @param value [String, Integer, MethodNode]
      def on_assoc_new(key, value)
        key.tr!(":", "")

        case key
        when "on"
          [:on_todo_event, value]
        when "to"
          [:on_todo_assignee, value]
        else
          [:unknown, value]
        end
      end

      # @param data [Hash]
      # @return [Hash]
      def on_bare_assoc_hash(data)
        data
      end
    end

    class Visitor
      attr_reader :events, :assignees, :errors

      def initialize
        @events = []
        @assignees = []
        @errors = []
      end

      # Iterate over each tokens returned from the parser and call
      # the corresponding method
      #
      # @param sexp [Array]
      # @return [void]
      def process(sexp)
        return unless sexp

        if sexp[0].is_a?(Array)
          sexp.each { |node| process(node) }
        else
          method, *args = sexp
          send(method, *args) if method.is_a?(Symbol) && respond_to?(method)
        end
      end

      # @param method_node [MethodNode]
      # @return [void]
      def on_todo_event(method_node)
        if method_node.is_a?(MethodNode)
          events << method_node
        else
          errors << "Incorrect `:on` event format: #{method_node}"
        end
      end

      # @param assignee [String]
      # @return [void]
      def on_todo_assignee(assignee)
        @assignees << assignee
      end
    end
  end
end