require "../sql"
require "./collection"
require "./column"
require "./modules/**"
require "./converters/**"
require "./validation/**"
require "./factory"

module Clear::Model
  include Clear::ErrorMessages
  include Clear::Model::Connection
  include Clear::Model::HasHooks
  include Clear::Model::HasColumns
  include Clear::Model::HasTimestamps
  include Clear::Model::HasSerialPkey
  include Clear::Model::HasSaving
  include Clear::Model::HasValidation
  include Clear::Model::HasRelations
  include Clear::Model::HasScope
  include Clear::Model::ClassMethods
  include Clear::Model::HasFactory
  include Clear::Model::Initializer
  include Clear::Model::JSONDeserialize

  getter cache : Clear::Model::QueryCache?

  # Alias method for primary key.
  #
  # If `Model#id` IS the primary key, then calling `Model#__pkey__` is exactly the same as `Model#id`.
  #
  # This method exists to tremendously simplify the meta-programming code.
  # If no primary key has been setup to this model, raise an exception.
  def __pkey__
    raise lack_of_primary_key(self.class.name)
  end

  # Comparison between models is made by comparing their primary keys.
  def ==(model : self)
    self.__pkey__ == model.__pkey__
  end

  # We use here included for errors purpose.
  # The overload are shown in this case, but not in the case the constructors
  # are directly defined without the included block.
  macro included
    {% raise "Do NOT include Clear::Model on struct-like objects.\n" +
             "It would behave very strangely otherwise." unless @type < Reference %}    # <~ Models are mutable objects;

    extend Clear::Model::HasHooks::ClassMethods

    getter cache : Clear::Model::QueryCache?

    def initialize
      @persisted = false
    end

    def initialize(h : Hash(String, _), @cache : Clear::Model::QueryCache? = nil, @persisted = false, fetch_columns = false )
      @attributes.merge!(h) if fetch_columns

      reset(h)
    end

    def initialize(json : ::JSON::Any, @cache : Clear::Model::QueryCache? = nil, @persisted = false )
      reset(json.as_h)
    end

    def initialize(t : NamedTuple, @persisted = false)
      reset(t)
    end

    # Invalidate local-to-relation cache and eager-loading cache.
    # Useful to forcefully query again when calling relation defined method
    def invalidate_caches : self
      @cache = nil
      self
    end

    # :nodoc:
    # This is a tricky method which is overriden by inherited models.
    #
    # The problem is usage of static array initialisation under `finalize` macro; they are initialized
    # AFTER the main code is executed, preventing it to work properly.
    #
    # The strategy here is to execute the static array initialization under a method and execute this method
    # before main.
    #
    # Then to redefine this method in the finalize block. The current behavior seems to be a crystal compiler bug
    # and should be fixed in near future.
    def self.__main_init__
    end
  end
end

require "./reflection/**"
