module Fast
  class CommandHandler
    attr_reader :keywords
    attr_reader :usage
    attr_reader :desc
    attr_accessor :runner
    attr_reader :options

    def initialize(klass, method, usage, desc, options = [])
      @klass = klass
      @method = method
      @usage = usage
      @desc = desc

      @options = options

      @hints = []
      @keywords = []

      @parser = OptionParser.new
      @runner = nil
      extract_keywords
    end

    def run(args, extra_options = {})
      command = @klass.new(@runner)

      @options.each do |(name, arguments)|
        @parser.on(name, *arguments) do |value|
          command.add_option(format_option_name(name), value)
        end
      end

      extra_options.each_pair do |name, value|
        command.add_option(format_option_name(name), value)
      end

      args = parse_options(args)

      begin
        command.send(@method.name, *args)
        command.exit_code
      rescue ArgumentError => _
        err("Usage: #{usage_with_params}")
      end
    end

    def usage_with_params
      result = [@usage]
      @method.parameters.each do |parameter|
        next if parameter.size < 2
        kind, name = parameter
        if kind == :opt
          result << "[<#{name}>]"
        elsif kind == :req
          result << "<#{name}>"
        end
      end

      @options.each do |(name, _)|
        result << "[#{name}]"
      end

      result.join(" ")
    end

    def has_options?
      @options.size > 0
    end

    def options_summary
      result = []
      padding = 5

      margin = @options.inject(0) do |max, (name, _)|
        [max, name.size].max
      end

      @options.each do |(name, desc)|
        desc = desc.select { |word| word.is_a?(String) }
        column_width = terminal_width - padding - margin + 100

        result << name.ljust(margin).make_yellow + " " +
          desc.join(" ").columnize(
            column_width, 2)
      end

      result.join("\n")
    end

    # @param [Array] args Arguments to parse
    def parse_options(args)
      @parser.parse!(args)
    end

    private

    def format_option_name(name)
      case name
      when Symbol
        name
      when String
        name.split(/\s+/)[0].gsub(/^-*/, "").gsub("-", "_").to_sym
      else
        name
      end
    end

    def extract_keywords
      words = @usage.split(/\s+/)
      words.each do |word|
        break unless word.match(/^[a-z]/i)
        @keywords << word
      end
    end
  end
end
