# frozen_string_literal: true

module Toys
  module Templates
    ##
    # A template for tools that run rubocop
    #
    class Rubocop
      include Template

      ##
      # Default version requirements for the rubocop gem.
      # @return [Array<String>]
      #
      DEFAULT_GEM_VERSION_REQUIREMENTS = [].freeze

      ##
      # Default tool name
      # @return [String]
      #
      DEFAULT_TOOL_NAME = "rubocop"

      ##
      # Create the template settings for the Rubocop template.
      #
      # @param name [String] Name of the tool to create. Defaults to
      #     {DEFAULT_TOOL_NAME}.
      # @param gem_version [String,Array<String>] Version requirements for
      #     the rubocop gem. Defaults to {DEFAULT_GEM_VERSION_REQUIREMENTS}.
      # @param fail_on_error [boolean] If true, exits with a nonzero code if
      #     Rubocop fails. Defaults to true.
      # @param options [Array<String>] Additional options passed to the Rubocop
      #     CLI.
      # @param bundler [boolean,Hash] If `false` (the default), bundler is not
      #     enabled for this tool. If `true` or a Hash of options, bundler is
      #     enabled. See the documentation for the
      #     [bundler mixin](https://dazuma.github.io/toys/gems/toys-core/latest/Toys/StandardMixins/Bundler)
      #     for information on available options.
      # @param context_directory [String] A custom context directory to use
      #     when executing this tool.
      #
      def initialize(name: DEFAULT_TOOL_NAME,
                     gem_version: nil,
                     fail_on_error: true,
                     options: [],
                     bundler: false,
                     context_directory: nil)
        @name = name
        @gem_version = gem_version
        @fail_on_error = fail_on_error
        @options = options
        @bundler = bundler
        @context_directory = context_directory
      end

      ##
      # Name of the tool to create.
      #
      # @param value [String]
      # @return [String]
      #
      attr_writer :name

      ##
      # Version requirements for the rubocop gem.
      # If set to `nil`, uses the bundled version if bundler is enabled, or
      # defaults to {DEFAULT_GEM_VERSION_REQUIREMENTS} if bundler is not
      # enabled.
      #
      # @param value [String,Array<String>,nil]
      # @return [String,Array<String>,nil]
      #
      attr_writer :gem_version

      ##
      # Whether to exit with a nonzero code if Rubocop fails.
      #
      # @param value [boolean]
      # @return [boolean]
      #
      attr_writer :fail_on_error

      ##
      # Additional options to pass to Rubocop
      #
      # @param value [Array<String>]
      # @return [Array<String>]
      #
      attr_writer :options

      ##
      # Custom context directory for this tool.
      #
      # @param value [String]
      # @return [String]
      #
      attr_writer :context_directory

      ##
      # Set the bundler state and options for this tool.
      #
      # Pass `false` to disable bundler. Pass `true` or a hash of options to
      # enable bundler. See the documentation for the
      # [bundler mixin](https://dazuma.github.io/toys/gems/toys-core/latest/Toys/StandardMixins/Bundler)
      # for information on the options that can be passed.
      #
      # @param value [boolean,Hash]
      # @return [boolean,Hash]
      #
      attr_writer :bundler

      ##
      # Activate bundler for this tool.
      #
      # See the documentation for the
      # [bundler mixin](https://dazuma.github.io/toys/gems/toys-core/latest/Toys/StandardMixins/Bundler)
      # for information on the options that can be passed.
      #
      # @param opts [keywords] Options for bundler
      # @return [self]
      #
      def use_bundler(**opts)
        @bundler = opts
        self
      end

      ##
      # @private
      #
      attr_reader :fail_on_error

      ##
      # @private
      #
      attr_reader :context_directory

      ##
      # @private
      #
      def name
        @name || DEFAULT_TOOL_NAME
      end

      ##
      # @private
      #
      def gem_version
        return Array(@gem_version) if @gem_version
        @bundler ? [] : DEFAULT_GEM_VERSION_REQUIREMENTS
      end

      ##
      # @private
      #
      def options
        Array(@options)
      end

      ##
      # @private
      #
      def bundler_settings
        if @bundler && !@bundler.is_a?(::Hash)
          {}
        else
          @bundler
        end
      end

      on_expand do |template|
        tool(template.name) do
          desc "Run rubocop on the current project."

          set_context_directory template.context_directory if template.context_directory

          static :gem_version, template.gem_version
          static :rubocop_options, template.options
          static :fail_on_error, template.fail_on_error

          include :gems
          include :exec

          bundler_settings = template.bundler_settings
          include :bundler, **bundler_settings if bundler_settings

          disable_argument_parsing

          # @private
          def run
            gem "rubocop", *gem_version

            ::Dir.chdir(context_directory || ::Dir.getwd) do
              logger.info "Running RuboCop..."
              rubocop_args = rubocop_options + args
              code = <<~CODE
                gem 'rubocop', *#{gem_version.inspect}
                require 'rubocop'
                exit(::RuboCop::CLI.new.run(#{rubocop_args.inspect}))
              CODE
              result = exec_ruby(["-e", code])
              if result.error?
                logger.error "RuboCop failed!"
                exit(1) if fail_on_error
              end
            end
          end
        end
      end
    end
  end
end
