module Binda
	# Fieldable associations are Binda's core feature. 
	# 
	# They provide classes like `Binda::Component` and `Binda::Board` with a collection of fields 
	#   (texts, assets, dates and so on) to store data in a simple yet powerful way. 
	module FieldableAssociations

		extend ActiveSupport::Concern

		included do 

	    include FieldableAssociationHelpers
	    
	    # Fieldable Associations 
	    # 
	    # If you add a new field remember to update:
	    #   - get_fieldables (see here below)
	    #   - get_field_types (see here below)
	    #   - component_params (app/controllers/binda/components_controller.rb)

	    has_many :texts,         as: :fieldable, dependent: :delete_all
	    has_many :strings,       as: :fieldable, dependent: :delete_all
	    has_many :dates,         as: :fieldable, dependent: :delete_all
	    has_many :galleries,     as: :fieldable, dependent: :delete_all
	    has_many :assets,        as: :fieldable, dependent: :delete_all
	    has_many :images,        as: :fieldable, dependent: :delete_all
	    has_many :videos,        as: :fieldable, dependent: :delete_all
	    has_many :audios,        as: :fieldable, dependent: :delete_all
	    has_many :radios,        as: :fieldable, dependent: :delete_all 
	    has_many :selections,    as: :fieldable, dependent: :delete_all 
	    has_many :checkboxes,    as: :fieldable, dependent: :delete_all 
	    has_many :svgs,          as: :fieldable, dependent: :delete_all
	    # Repeaters need destroy_all, not delete_all
	    has_many :repeaters,     as: :fieldable, dependent: :destroy
		has_many :relations,     as: :fieldable, dependent: :destroy 


    	has_many :owner_relations, class_name: "RelationLink", 
                                dependent: :destroy, 
                                as: :dependent

	    # Owner are connected to its Dependents in a Active Relation
	    # meaning its possible to connect a Owner to as many Dependents
	    # as it's needed.
	    # 
	    # The current version support components and boards separately
	    has_many :owner_components, through: :owner_relations, 
	                                source: :owner

	    has_many :owner_boards, through: :owner_relations, 
	                            source: :owner

	    has_many :owner_repeaters, through: :owner_relations, 
	                            source: :owner 


	    accepts_nested_attributes_for :texts, :strings, :dates, :assets, :images, :videos, :audios, :galleries, :repeaters, :radios, :selections, :checkboxes, :relations, :svgs, allow_destroy: true

			validates_associated :texts
			validates_associated :strings
			validates_associated :dates
			validates_associated :assets
			validates_associated :images
			validates_associated :videos
			validates_associated :audios
			validates_associated :repeaters
			validates_associated :radios
			validates_associated :selections
			validates_associated :checkboxes
			validates_associated :relations
			validates_associated :svgs

      after_save :generate_fields

      # Uncomment these "validate do" loop to better debug validation.
      # This makes method gather errors of the associated records and
      # make them available to the instance object. After using this method 
      # you will be able to see the actual error inside `instance.errors` array.
      # Example: @component.errors #=> [ ... ]
      # 
		  # validate do |instance|
		  #   instance.texts.each do |text|
		  #   	binding.pry
		  #     next if text.valid?
		  #     text.errors.full_messages.each do |msg|
		  #       # you can customize the error message here:
		  #       errors[:base] << "Error in #{text.field_setting.name} (text): #{msg}"
		  #     end
		  #   end
		  # end
		end

		# Find or create a field by field setting and field type
		# 
		# This is used in Binda's editor views.
		#   
		# Please, check the code to know more about the way this method works as it's pretty complex yet important.
		# 
		# @param field_setting_id [string] The field setting id
		# @param field_type [string] THe field type
		def find_or_create_a_field_by(field_setting_id, field_type)
			if FieldSetting.get_field_classes.include?( field_type.classify ) && field_setting_id.is_a?( Integer )
				get_field(field_type, field_setting_id)
			else
				raise ArgumentError, "One parameter in find_or_create_a_field_by() is not correct on instance (#{self.class.name} ##{self.id}).", caller
			end
		end

    # This method is called upon the creation/update of a fieldable record (component, board or repeater) 
    #   and generates all fields related to each field settings which belongs to it.
    # 
    # This avoids any situation in which, for example, a component have a field setting for a text
    #   but there is no text (meaning `Binda::Text` instance) that correspond to that field setting.
    #   This causes issues when looping a bunch of components which will thow a error if you try to access
    #   a component field, as some might have it some might not. This make sure that you can always expect 
    #   to find a field instance which might be empty, but certainly it exists.
    #   
    # TODO check if find_or_create_a_field_by method should be used instead (it's used in editors views)
    # 
    def generate_fields
      GenerateFieldsJob.perform_later self
    end

		# TODO: Update all helpers replacing `find` method with ruby `select`. 
		# This should improve performance avoiding generating useless ActiveRecord objects.

		private

			# Get field based on field type and field setting
			# @param field_type [string]
			# @param field_setting_id [integer]
			# @return [ActiveRecord]
			def get_field(field_type, field_setting_id)
				# It's mandatory to use `select{}.first`!!! 
				# If you use any ActiveRecord method (like `where` of `find`) the validation errors are wiped out 
				# from the object and not rendered next to the form in the editor view
				obj = self.send( field_type.pluralize ).select{|rf| rf.field_setting_id == field_setting_id}.first
				if obj.nil?
					# As we are using the `select{}.first` method, asynchronous requests passing through 
					# this method will create some inconsistentcy between this `self` object and the real object. 
					# In other words `self` should be reloaded, but we won't reload it otherwise we will 
					# erase the errors shipped with it initially. Therefore we will check again with `find_or_create_by!`.
					# This leads to another issue, which is: errors coming from asynchronous requests won't be considered
					# as the `self` object is the initial one, not the "updated" one. At the current time this is not
					# a problem because the only asynchronous requests are for brand new records which don't need validation.
					return self.send( field_type.pluralize ).find_or_create_by!( field_setting_id: field_setting_id ) 
				else
					return obj
				end 
			end
	end
end
