module CustomAttributes
  module ActsAsCustomField
    extend ActiveSupport::Concern

    included do
    end

    module ClassMethods
      def acts_as_custom_field(_options = {})
        include CustomAttributes::ActsAsCustomField::InstanceMethods

        scope :sorted, -> { order(:position) }

        serialize :possible_values

        attr_accessor :edit_tag_style

        validates_numericality_of :min_length, only_integer: true, greater_than_or_equal_to: 0
        validates_numericality_of :max_length, only_integer: true, greater_than_or_equal_to: 0
        validates_presence_of :name, :field_type
        validates_length_of :name, maximum: 30
        validates_inclusion_of :field_type, in: proc { CustomAttributes::FieldType.available_types.map { |ft| ft.name.gsub(/CustomAttributes::|FieldType/, '') } }
        validate :validate_custom_field

        before_create do |field|
          field.set_slug

          field.ensure_position_integrity
          field.set_position
        end

        before_save do |field|
          field.type.before_custom_field_save(field)
        end
      end
    end

    module InstanceMethods
      def type
        @type ||= CustomAttributes::FieldType.find(field_type)
      end

      # Called upon Customizable Model validation
      # Actual validation handled by FieldType
      def validate_custom_value(custom_value)
        value = custom_value.value
        errs = type.validate_custom_value(custom_value)

        unless errs.any?
          if value.is_a?(Array)
            errs << ::I18n.t('activerecord.errors.messages.invalid') unless multiple?
            if is_required? && value.detect(&:present?).nil?
              errs << ::I18n.t('activerecord.errors.messages.blank')
            end
          else
            if is_required? && value.blank?
              errs << ::I18n.t('activerecord.errors.messages.blank')
            end
          end
        end

        errs
      end

      # Returns possible *options* that are determined by the *FieldType*
      # Don't mistake for possible_values, which is a CustomField specific dynamic setting
      def possible_custom_value_options(custom_value)
        type.possible_custom_value_options(custom_value)
      end

      # Validate the CustomField according to type rules and check if the selected default value
      # is indeed a valid value for this field
      def validate_custom_field
        if type.nil?
          errors.add :default, ::I18n.t('activerecord.errors.messages.invalid_type')
          return
        end

        type.validate_custom_field(self).each do |attribute, message|
          errors.add attribute, message
        end

        if default.present?
          validate_field_value(default).each do |message|
            errors.add :default, message
          end
        end

        if position.present? && self.class.where(position: position, model_type: model_type).where.not(id: id).count > 0
          errors.add :position, ::I18n.t('activerecord.errors.messages.invalid_position')
        end
      end

      # Helper function used in validate_custom_field
      def validate_field_value(value)
        validate_custom_value(CustomAttributes::CustomFieldValue.new(custom_field: self, value: value))
      end

      # Helper function to check if a value is a valid value for this field
      def valid_field_value?(value)
        validate_field_value(value).empty?
      end

      # Used to set the value of CustomFieldValue. No database persistance happening.
      # A convenient way to override how values are being parsed via FieldType
      def set_custom_field_value(custom_field_value, value)
        type.set_custom_field_value(self, custom_field_value, value)
      end

      # Called after CustomValue has been saved
      # Overrideable through FieldType
      def after_save_custom_value(custom_value)
        type.after_save_custom_value(self, custom_value)
      end

      # Serializer for possible values attribute
      def possible_values
        values = read_attribute(:possible_values)
        if values.is_a?(Array)
          values.each do |value|
            value.to_s.force_encoding('UTF-8')
          end
          values
        else
          []
        end
      end

      # Returns the value in type specific form (Integer, Float, ...)
      def cast_value(value)
        type.cast_value(self, value)
      end

      # Finds a value in a field that has predefined possible values.
      # Returns array of values if the field supports multiple values
      # Comma delimited keywords possible
      def value_from_keyword(keyword, customized)
        type.value_from_keyword(self, keyword, customized)
      end

      def field_type=(arg)
        # cannot change type of a saved custom field
        if new_record?
          @type = nil
          super
        end
      end

      def possible_values=(arg)
        if arg.is_a?(Array)
          values = arg.compact.map { |a| a.to_s.strip }.reject(&:blank?)
          write_attribute(:possible_values, values)
        else
          self.possible_values = arg.to_s.split(/[\n\r]+/)
        end
      end

      def decrement_position
        change_position_by(-1)
      end

      def increment_position
        change_position_by(1)
      end

      def customizable_class
        model_type.gsub('CustomField', '').constantize
      rescue NameError
        false
      end

      protected

      def set_slug
        self.slug = create_slug
      end

      def set_position
        return unless position.nil?

        last_position = self.class.where(model_type: model_type).order(position: :desc).first.try(:position)
        self.position = 1
        self.position = last_position + 1 unless last_position.nil?
      end

      def change_position_by(diff)
        new_pos = position + diff unless position.nil?

        swap_field = self.class.find_by(model_type: model_type, position: new_pos)

        if swap_field.present?
          swap_field.position = position
          swap_field.save(validate: false)

          self.position = new_pos
          save
        end
      end

      def create_slug(iterator = 0)
        new_slug = name.strip.gsub(/([^A-Za-z0-9])+/) { '_' }.downcase

        new_slug = "#{new_slug}_#{iterator}" unless iterator == 0
        new_slug = create_slug(iterator += 1) unless CustomField.where(slug: new_slug).count == 0
        new_slug
      end

      def ensure_position_integrity
        expected_position = 1
        self.class.where(model_type: model_type).order(position: :asc).each do |field|
          if expected_position != field.position
            field.position = expected_position
            field.save
          end

          expected_position += 1
        end
      end
    end
  end
end