# frozen_string_literal: true

module Upgrow
  # Base class for Repositories. It offers a basic API for the state all
  # Repositories should have, as well as the logic on how to materialize data
  # into Models.
  class BasicRepository
    class << self
      attr_writer :base
      attr_writer :model_class

      # model_class [Class] the Model class to be used to map and return the
      # materialized data as instances of the domain. Defaults to a constant
      # derived from the Repository class' name. For example, a `UserRepository`
      # will have its default Model class set to `User`.
      #
      # @return [Class] the Repository Model class.
      def model_class
        @model_class || default_model_class
      end

      # the base object to be used internally to retrieve the persisted data.
      # For example, a base class in which queries can be performed for a
      # relational database adapter. Defaults to `nil`.
      #
      # @return [Object] the Repository base.
      def base
        @base || default_base
      end

      private

      def default_base; end

      def default_model_class
        model_class_name = name.delete_suffix('Repository')
        Object.const_get(model_class_name)
      end
    end

    attr_reader :base, :model_class

    # Sets the Basic Repositorie's state.
    def initialize
      @base = self.class.base
      @model_class = self.class.model_class
    end

    # Represents the raw Hash of data attributes as a Model instance from the
    # Repositorie's Model class.
    #
    # @param model_class_or_attributes [Class, Hash<Symbol, Object>] the Model
    #   class to be instantiated, in case it is a different class than the
    #   Repositorie's Model class, or the list of attributes the model will
    #   have, in case the Model class is the Repositorie's Model class.
    # @param attributes [Hash<Symbol, Object>] the list of attributes the Model
    #   will have, in case the Model to be instantiated is passed as the first
    #   argument.
    #
    # @return [Model] the Model instance populated with the given attributes.
    def to_model(model_class_or_attributes, attributes = {})
      model_class = model_class_or_attributes

      if model_class_or_attributes.respond_to?(:transform_keys)
        model_class = self.model_class
        attributes = model_class_or_attributes
      end

      model_class.new(**attributes.transform_keys(&:to_sym))
    end
  end
end