require "thor"
require "erb"

require "active_support/core_ext/string/filters"
require "active_support/core_ext/string/inflections"

require "rails/command/actions"

module Rails
  module Command
    class Base < Thor
      class Error < Thor::Error # :nodoc:
      end

      include Actions

      class << self
        # Returns true when the app is a Rails engine.
        def engine?
          defined?(ENGINE_ROOT)
        end

        # Tries to get the description from a USAGE file one folder above the command
        # root.
        def desc(usage = nil, description = nil, options = {})
          if usage
            super
          else
            @desc ||= ERB.new(File.read(usage_path)).result(binding) if usage_path
          end
        end

        # Convenience method to get the namespace from the class name. It's the
        # same as Thor default except that the Command at the end of the class
        # is removed.
        def namespace(name = nil)
          if name
            super
          else
            @namespace ||= super.chomp("_command").sub(/:command:/, ":")
          end
        end

        # Convenience method to hide this command from the available ones when
        # running rails command.
        def hide_command!
          Rails::Command.hidden_commands << self
        end

        def inherited(base) #:nodoc:
          super

          if base.name && base.name !~ /Base$/
            Rails::Command.subclasses << base
          end
        end

        def perform(command, args, config) # :nodoc:
          if Rails::Command::HELP_MAPPINGS.include?(args.first)
            command, args = "help", []
          end

          dispatch(command, args.dup, nil, config)
        end

        def printing_commands
          namespaced_commands
        end

        def executable
          "bin/rails #{command_name}"
        end

        # Use Rails' default banner.
        def banner(*)
          "#{executable} #{arguments.map(&:usage).join(' ')} [options]".squish!
        end

        # Sets the base_name taking into account the current class namespace.
        #
        #   Rails::Command::TestCommand.base_name # => 'rails'
        def base_name
          @base_name ||= begin
            if base = name.to_s.split("::").first
              base.underscore
            end
          end
        end

        # Return command name without namespaces.
        #
        #   Rails::Command::TestCommand.command_name # => 'test'
        def command_name
          @command_name ||= begin
            if command = name.to_s.split("::").last
              command.chomp!("Command")
              command.underscore
            end
          end
        end

        # Path to lookup a USAGE description in a file.
        def usage_path
          if default_command_root
            path = File.join(default_command_root, "USAGE")
            path if File.exist?(path)
          end
        end

        # Default file root to place extra files a command might need, placed
        # one folder above the command file.
        #
        # For a `Rails::Command::TestCommand` placed in `rails/command/test_command.rb`
        # would return `rails/test`.
        def default_command_root
          path = File.expand_path(File.join("../commands", command_root_namespace), __dir__)
          path if File.exist?(path)
        end

        private
          # Allow the command method to be called perform.
          def create_command(meth)
            if meth == "perform"
              alias_method command_name, meth
            else
              # Prevent exception about command without usage.
              # Some commands define their documentation differently.
              @usage ||= ""
              @desc  ||= ""

              super
            end
          end

          def command_root_namespace
            (namespace.split(":") - %w( rails )).first
          end

          def namespaced_commands
            commands.keys.map do |key|
              key == command_root_namespace ? key : "#{command_root_namespace}:#{key}"
            end
          end
      end

      def help
        if command_name = self.class.command_name
          self.class.command_help(shell, command_name)
        else
          super
        end
      end
    end
  end
end
