# frozen_string_literal: true

module Dynamoid
  module Fields
    # @private
    class Declare
      def initialize(source, name, type, options)
        @source = source
        @name = name.to_sym
        @type = type
        @options = options
      end

      def call
        # Register new field metadata
        @source.attributes = @source.attributes.merge(
          @name => { type: @type }.merge(@options)
        )

        # Should be called before `define_attribute_methods` method because it
        # defines an attribute getter itself
        warn_about_method_overriding

        # Dirty API
        @source.define_attribute_method(@name)

        # Generate getters and setters as well as other helper methods
        generate_instance_methods

        # If alias name specified - generate the same instance methods
        if @options[:alias]
          generate_instance_methods_for_alias
        end
      end

      private

      def warn_about_method_overriding
        warn_if_method_exists(@name)
        warn_if_method_exists("#{@name}=")
        warn_if_method_exists("#{@name}?")
        warn_if_method_exists("#{@name}_before_type_cast?")
      end

      def generate_instance_methods
        # only local variable is visible in `module_eval` block
        name = @name

        @source.generated_methods.module_eval do
          define_method(name) { read_attribute(name) }
          define_method(:"#{name}?") do
            value = read_attribute(name)
            case value
            when true        then true
            when false, nil  then false
            else
              !value.nil?
            end
          end
          define_method(:"#{name}=") { |value| write_attribute(name, value) }
          define_method(:"#{name}_before_type_cast") { read_attribute_before_type_cast(name) }
        end
      end

      def generate_instance_methods_for_alias
        # only local variable is visible in `module_eval` block
        name = @name

        alias_name = @options[:alias].to_sym

        @source.generated_methods.module_eval do
          alias_method alias_name, name
          alias_method :"#{alias_name}=", :"#{name}="
          alias_method :"#{alias_name}?", :"#{name}?"
          alias_method :"#{alias_name}_before_type_cast", :"#{name}_before_type_cast"
        end
      end

      def warn_if_method_exists(method)
        if @source.instance_methods.include?(method.to_sym)
          Dynamoid.logger.warn("Method #{method} generated for the field #{@name} overrides already existing method")
        end
      end
    end
  end
end
