# :nodoc:
module Clear::Model::Relations::HasManyMacro
  # has many
  macro generate(self_type, relation)
    {%
      foreign_key = (relation[:foreign_key] || "#{self_type.stringify.underscore.gsub(/::/, "_").id}_id").id

      method_name = relation[:name].id
      relation_type = relation[:type]
    %}

    __define_association_cache__({{method_name}}, Array({{relation_type}}))

    def self.__relation_filter_{{method_name}}__(query)
      query.inner_join({{self_type}}.table){ var( {{self_type}}.table, {{self_type}}.__pkey__ ) == var( {{relation_type}}.table, "{{foreign_key}}" ) }
    end

    # :nodoc:
    def self.__relation_key_table_{{method_name}}__ : Tuple(String, String)
      {
        {{relation_type}}.table.to_s,
        {{"#{foreign_key}"}}
      }
    end

    __on_init__ do
      {{self_type}}::RELATION_FILTERS["{{method_name}}"] = -> (x : Clear::SQL::SelectBuilder) { __relation_filter_{{method_name}}__(x) }
    end

    # The method {{method_name}} is a `has_many` relation
    # to {{relation_type}}
    def {{method_name}} : {{ relation_type }}::Collection
      cache = @cache

      %foreign_key = {{"#{foreign_key}"}}

      query = {{relation_type}}.query
        .tags({ %foreign_key => self.__pkey_column__.to_sql_value })
        .where{ var({{relation_type}}.table, %foreign_key) == self.__pkey__ }

      if @_cached_{{method_name}}
        query.with_cached_result(@_cached_{{method_name}})
      elsif ( cache && cache.active?("{{method_name}}") )
        @_cached_{{method_name}} = cache.hit("{{method_name}}", self.__pkey_column__.to_sql_value, {{relation_type}})
        # This relation will trigger the cache if it exists
        query.with_cached_result(@_cached_{{method_name}})
      end

      {% # Allow to add an element in this collection by changing the `foreign_key`
# value of the distant model.

      %}
      query.append_operation = -> (x : {{relation_type}}) do
        x.{{foreign_key}} = self.__pkey__
        self.save! unless self.persisted?
        x.reset(query.tags).save!
      end

      query
    end

    # Addition of the method for eager loading and N+1 avoidance.
    class Collection
      # Eager load the has many relation {{method_name}}.
      # Use it to avoid N+1 queries.
      def with_{{method_name}}(fetch_columns = false, &block : {{relation_type}}::Collection -> ) : self
        before_query do
          %foreign_key = {{"#{foreign_key}"}}

          %key = {{self_type}}.__pkey__
          %table = {{self_type}}.table

          #SELECT * FROM foreign WHERE foreign_key IN ( SELECT primary_key FROM users )
          sub_query = self.dup.clear_select.select("#{%table}.#{%key}")

          qry = {{relation_type}}.query.where{ var({{relation_type}}.table, %foreign_key).in?(sub_query) }
          block.call(qry)

          @cache.active "{{method_name}}"

          h = {} of Clear::SQL::Any => Array({{relation_type}})

          qry.each(fetch_columns: true) do |mdl|
            unless h[mdl.attributes[%foreign_key]]?
              h[mdl.attributes[%foreign_key]] = [] of {{relation_type}}
            end

            h[mdl.attributes[%foreign_key]] << mdl
          end

          h.each{ |key, value| @cache.set("{{method_name}}", key, value) }
        end

        self
      end

      def with_{{method_name}}(fetch_columns = false)
        with_{{method_name}}(fetch_columns){ } #empty block
      end
    end

  end
end
