# frozen_string_literal: true

module PriceHubble
  # The base entity, with a lot of known ActiveRecord/ActiveModel features.
  class BaseEntity
    include ActiveModel::Model
    include ActiveModel::Dirty

    include PriceHubble::Utils::Bangers

    # Additional singleton class functionalities
    class << self
      include PriceHubble::Utils::Bangers
    end

    include PriceHubble::EntityConcern::Callbacks
    include PriceHubble::EntityConcern::Attributes
    include PriceHubble::EntityConcern::Associations
    include PriceHubble::EntityConcern::Client
    include PriceHubble::EntityConcern::Persistence

    # We collect all unknown attributes instead of raising while creating a new
    # instance. The unknown attributes are wrapped inside a
    # +RecursiveOpenStruct+ to ease the accessibility. This also allows us to
    # handle responses in a forward-compatible way.
    attr_accessor :_unmapped

    # Create a new instance of an entity with a lot of known
    # ActiveRecord/ActiveModel features.
    #
    # @param struct [Hash{Mixed => Mixed}, RecursiveOpenStruct] the initial data
    # @return [PriceHubble::BaseEntity] a compatible instance
    # @yield [PriceHubble::BaseEntity] the entity itself in the end
    def initialize(struct = {})
      # Set the initial unmapped struct
      self._unmapped = RecursiveOpenStruct.new
      # Build a RecursiveOpenStruct and a simple hash from the given data
      struct, hash = sanitize_data(struct)
      # Initialize associations and map them accordingly
      struct, hash = initialize_associations(struct, hash)
      # Initialize attributes and map unknown ones and pass back the known
      known = initialize_attributes(struct, hash)
      # Mass assign the known attributes via ActiveModel
      super(known)
      # Follow the ActiveRecord API
      yield self if block_given?
      # Run the initializer callbacks
      _run_initialize_callbacks
    end

    class << self
      # Initialize the class we were inherited to. We trigger all our methods
      # which start with +inherited_setup_+ to allow per-concern/feature based
      # initialization after BaseEntity inheritance.
      #
      # @param child_class [Class] the child class which inherits us
      def inherited(child_class)
        super
        match = ->(sym) { sym.to_s.start_with? 'inherited_setup_' }
        trigger = ->(sym) { send(sym, child_class) }
        methods.select(&match).each(&trigger)
      end
    end
  end
end