# frozen_string_literal: true

require "thor"
require "thor/actions"
require "thor_plus/actions"
require "refinements/strings"
require "refinements/hashes"
require "runcom"
require "gemsmith/helpers/cli"
require "gemsmith/helpers/template"

module Gemsmith
  # The Command Line Interface (CLI) for the gem.
  # rubocop:disable Metrics/ClassLength
  class CLI < Thor
    include Thor::Actions
    include ThorPlus::Actions
    include Helpers::CLI
    include Helpers::Template

    using Refinements::Strings
    using Refinements::Hashes

    package_name Identity.version_label

    # Overwrites Thor's template source root.
    def self.source_root
      File.expand_path File.join(File.dirname(__FILE__), "templates")
    end

    # rubocop:disable Metrics/MethodLength
    def self.configuration
      Runcom::Configuration.new file_name: Identity.file_name, defaults: {
        year: Time.now.year,
        github_user: Git.github_user,
        gem: {
          name: "undefined",
          path: "undefined",
          class: "Undefined",
          platform: "Gem::Platform::RUBY",
          url: Git.github_url("undefined"),
          license: "MIT"
        },
        author: {
          name: Git.config_value("user.name"),
          email: Git.config_value("user.email"),
          url: ""
        },
        organization: {
          name: "",
          url: ""
        },
        versions: {
          ruby: RUBY_VERSION,
          rails: "5.1"
        },
        generate: {
          cli: false,
          rails: false,
          security: true,
          pry: true,
          guard: true,
          rspec: true,
          reek: true,
          rubocop: true,
          scss_lint: false,
          git_hub: false,
          code_climate: false,
          gemnasium: false,
          travis: false,
          patreon: false
        },
        publish: {
          sign: false
        }
      }
    end

    def self.generators
      [
        Generators::Gem,
        Generators::Documentation,
        Generators::Rake,
        Generators::CLI,
        Generators::Ruby,
        Generators::Rails,
        Generators::Rspec,
        Generators::Reek,
        Generators::Rubocop,
        Generators::SCSSLint,
        Generators::CodeClimate,
        Generators::Guard,
        Generators::Travis,
        Generators::Bundler,
        Generators::GitHub,
        Generators::Pragma,
        Generators::Git
      ]
    end

    # Initialize.
    def initialize args = [], options = {}, config = {}
      super args, options, config
      @configuration = {}
    end

    desc "-g, [--generate=GEM]", "Generate new gem."
    map %w[-g --generate] => :generate
    method_option :cli,
                  desc: "Add CLI support.",
                  type: :boolean,
                  default: configuration.to_h.dig(:generate, :cli)
    method_option :rails,
                  desc: "Add Rails support.",
                  type: :boolean,
                  default: configuration.to_h.dig(:generate, :rails)
    method_option :security,
                  desc: "Add security support.",
                  type: :boolean,
                  default: configuration.to_h.dig(:generate, :security)
    method_option :pry,
                  desc: "Add Pry support.",
                  type: :boolean,
                  default: configuration.to_h.dig(:generate, :pry)
    method_option :guard,
                  desc: "Add Guard support.",
                  type: :boolean,
                  default: configuration.to_h.dig(:generate, :guard)
    method_option :rspec,
                  desc: "Add RSpec support.",
                  type: :boolean,
                  default: configuration.to_h.dig(:generate, :rspec)
    method_option :reek,
                  desc: "Add Reek support.",
                  type: :boolean,
                  default: configuration.to_h.dig(:generate, :reek)
    method_option :rubocop,
                  desc: "Add Rubocop support.",
                  type: :boolean,
                  default: configuration.to_h.dig(:generate, :rubocop)
    method_option :scss_lint,
                  desc: "Add SCSS Lint support.",
                  type: :boolean,
                  default: configuration.to_h.dig(:generate, :scss_lint)
    method_option :git_hub,
                  desc: "Add GitHub support.",
                  type: :boolean,
                  default: configuration.to_h.dig(:generate, :git_hub)
    method_option :code_climate,
                  desc: "Add Code Climate support.",
                  type: :boolean,
                  default: configuration.to_h.dig(:generate, :code_climate)
    method_option :gemnasium,
                  desc: "Add Gemnasium support.",
                  type: :boolean,
                  default: configuration.to_h.dig(:generate, :gemnasium)
    method_option :travis,
                  desc: "Add Travis CI support.",
                  type: :boolean,
                  default: configuration.to_h.dig(:generate, :travis)
    method_option :patreon,
                  desc: "Add Patreon support.",
                  type: :boolean,
                  default: configuration.to_h.dig(:generate, :patreon)
    # rubocop:disable Metrics/AbcSize
    def generate name
      print_cli_and_rails_engine_option_error && return if options.cli? && options.rails?

      info "Generating gem..."

      setup_configuration name: name, options: options.to_h
      self.class.generators.each { |generator| generator.run self, configuration: configuration }

      info "Gem generation finished."
    end

    desc "-o, [--open=GEM]", "Open a gem in default editor."
    map %w[-o --open] => :open
    def open name
      process_gem name, "edit"
    end

    desc "-r, [--read=GEM]", "Open a gem in default browser."
    map %w[-r --read] => :read
    def read name
      error "Gem home page is not defined." unless process_gem(name, "visit")
    end

    desc "-c, [--config]", %(Manage gem configuration ("#{configuration.computed_path}").)
    map %w[-c --config] => :config
    method_option :edit,
                  aliases: "-e",
                  desc: "Edit gem configuration.",
                  type: :boolean, default: false
    method_option :info,
                  aliases: "-i",
                  desc: "Print gem configuration.",
                  type: :boolean, default: false
    def config
      path = self.class.configuration.computed_path

      if options.edit? then `#{editor} #{path}`
      elsif options.info? then say(path)
      else help(:config)
      end
    end

    desc "-v, [--version]", "Show gem version."
    map %w[-v --version] => :version
    def version
      say Identity.version_label
    end

    desc "-h, [--help=COMMAND]", "Show this message or get help for a command."
    map %w[-h --help] => :help
    def help task = nil
      say and super
    end

    private

    attr_reader :configuration

    def setup_configuration name:, options: {}
      @configuration = self.class.configuration.to_h.merge(
        gem: {
          name: name,
          path: name.snakecase,
          class: name.camelcase,
          platform: "Gem::Platform::RUBY",
          url: Git.github_url(name),
          license: "MIT"
        },
        generate: options.symbolize_keys
      )
    end

    def print_cli_and_rails_engine_option_error
      error "Generating a gem with CLI and Rails Engine functionality is not allowed. " \
            "Build separate gems for improved separation of concerns and design."
    end
  end
end