module Madmin
  module Resourceable
    ##
    # This module extends methods into class methods on the resource.
    module ClassMethods
      ##
      # This method is a wrapper for the class variable fields.
      # In the event there is no field defined yet, we rescue
      # NameError and return an empty hash to begin the
      # population of the fields class variable.
      def fields
        class_variable_get(:@@fields)
      rescue NameError
        {}
      end

      ##
      # This becomes a DSL for adding a field into the fields class variable.
      # It is responsible for validating and parsing the arguments before
      # placing them into the fields class variable.
      def field(*args)
        validate_arguments!(args)

        key        = args[0].to_sym
        field_type = args[1]

        # We reassign the entire list of fields to prevent
        # duplication each time the class is evaluated.
        fresh_fields = fields
        fresh_fields[key] = field_type.new(args[2].merge(key: key, model: model, resource: self))

        class_variable_set(:@@fields, fresh_fields)
      end

      ##
      # This method is a wrapper for the class variable scopes.
      # In the event there is no scope defined yet, we rescue
      # NameError and return an empty array to begin the
      # population of the scopes class variable.
      def scopes
        class_variable_get(:@@scopes)
      rescue NameError
        []
      end

      ##
      # This becomes a DSL for adding a field into the scopes class variable.
      # It is responsible for validating and parsing the arguments before
      # placing them into the scopes class variable.
      def scope(*args)
        validate_scopes!(args)

        # We reassign the entire list of scopes to prevent
        # duplication each time the class is evaluated.
        fresh_scopes = scopes << args

        class_variable_set(:@@scopes, fresh_scopes.flatten.uniq)
      end

      ##
      # This method, when used in the resource, sets all fields
      # to be used when the form partial is rendered.
      def form_all_fields!
        class_variable_set(:@@form_all_fields, true)
      end

      ##
      # This method exposes a convenient way to see if all fields
      # for a resource should be available in a form partial.
      def form_all_fields?
        class_variable_get(:@@form_all_fields)
      rescue NameError
        false
      end

      ##
      # This method exposes the underlying model.
      def model
        model_name.constantize
      end

      ##
      # This method extracts the namespace to reveal
      # the underlying models name as a string.
      def model_name
        to_s.split("Madmin::Resources::").last.to_s
      end

      ##
      # This method exposes a convenient way to see if all fields
      # for a resource should be available in a show partial.
      def show_all_fields!
        class_variable_set(:@@show_all_fields, true)
      end

      ##
      # This method exposes a convenient way to see if all fields
      # for a resource should be available in a show partial.
      def show_all_fields?
        class_variable_get(:@@show_all_fields)
      rescue NameError
        false
      end

      private

      ##
      # This method validates that a given attribute for
      # a field is provided in the correct format of
      # either a symbol or a string.
      def attribute?(attribute)
        attribute.is_a?(Symbol) || attribute.is_a?(String)
      end

      ##
      # We want to make sure a provided type is
      # registered within our system.
      def valid_type?(type)
        type.to_s.deconstantize == Madmin::Field.to_s
      end

      ##
      # This method looks at the first two arguments (attribute, type)
      # and performs their prospective validations. If either
      # validation fails, a WrongArgumentError is raised.
      def validate_arguments!(args)
        if !attribute?(args.first)
          raise WrongArgumentError,
            "#{model_name} expected the attribute name as a symbol or string as the first argument."
        elsif !valid_type?(args.second)
          raise WrongArgumentError,
            "#{model_name} expected Madmin::Field type as the field type as the second argument."
        end
      end

      ##
      # This method looks at a given scope(s) and validates
      # the scope exists on the underlying model.
      #
      # Rails scopes are defined as class methods as the
      # class is evaluated, requiring this method
      # of performing the validation.
      def validate_scopes!(args)
        args.each do |arg|
          unless model.respond_to?(arg)
            raise UndefinedScopeError, ".#{arg} is not a valid scope on #{name}"
          end
        end
      end
    end
  end
end