module Para
  module AttributeField
    class RelationField < AttributeField::Base
      include Para::Helpers::ResourceName

      def reflection
        @reflection ||= model.reflect_on_association(name)
      end

      def foreign_key
        @foreign_key ||= reflection && case reflection.macro
        when :belongs_to then reflection.foreign_key
        when :has_one then :"#{ reflection.name }_id"
        when :has_many then :"#{ reflection.name.to_s.singularize }_ids"
        end
      end

      def through_relation
        @through_relation ||= reflection.options[:through]
      end

      def through_reflection
        @through_reflection ||= through_relation && model.reflect_on_association(through_relation)
      end

      def through_relation_source_foreign_key
        @through_relation_source_foreign_key ||= reflection.source_reflection.foreign_key
      end

      def polymorphic_through_reflection?
        !!(through_relation && reflection.source_reflection.options[:polymorphic])
      end

      private

      def resource_name(resource)
        Para.config.resource_name_methods.each do |method|
          return resource.send(method) if resource.respond_to?(method)
        end

        model_name = resource.class.model_name.human
        "#{ model_name } - #{ resource.id }"
      end

      # Takes an array of ids and a block. Check for each id if model exists
      # and create one if not.
      #
      # Example : [12, "foo"] will try to create a model with
      #           'foo' as a name, title or any other attribute referenced in
      #           the `Para.config.resource_name_methods` configuration param.
      #
      def on_the_fly_creation ids, &block
        Array.wrap(ids).each do |id|
          if !reflection.klass.exists?(id: id)
            resource = reflection.klass.new

            Para.config.resource_name_methods.each do |method_name|
              setter_name = :"#{ method_name }="

              if resource.respond_to?(setter_name)
                resource.send(setter_name, id)

                # This check avoids multiple creation of the same resource with
                # a given attribute value and ensure all resources from the form
                # that reference that new resource name are associated to the
                # same parent resource.
                if (existing_resource = reflection.klass.find_by(method_name => id))
                  block.call(existing_resource, id)
                  break
                elsif resource.save
                  block.call(resource, id)
                  break
                end
              end
            end
          end
        end
      end
    end
  end
end
