module SlimFormObject
  class Base
    include ActiveModel::Model
    include ::HelperMethods
    extend  ::HelperMethods

    attr_accessor :params, :data_objects_arr, :base_modules

    class << self
      attr_accessor :base_modules

      def set_model_name(name)
        define_method(:model_name) { ActiveModel::Name.new(self, nil, name) }
      end

      def base_modules
        @base_modules || {}
      end

      # data_structure
      def input_data_structure(**structure)
        instance_eval do
          define_method(:data_structure) { structure }
        end
        
        define_array_of_models(:array_of_all_models, get_main_models_from_structure(structure))
      end

      # CALLBACKS
      [
        'after_validation_form',
        'after_save_form',         
        'after_save_object',

        'allow_object_processing',
        'allow_to_validate_object',
        'allow_to_save_object',

        'check_params'
      ].each do |method_name|
        define_method("#{method_name}".to_sym) do |&block|
          instance_eval do
            define_method("#{method_name}_block".to_sym) { block }
          end if block
        end
      end
      # END CALLBACKS

      private

      def get_main_models_from_structure(structure)
        structure.keys.map do |main_model_name|
          get_class_of(main_model_name, base_modules[main_model_name])
        end
      end

      def define_array_of_models(name, args)
        instance_eval do
          define_method(name) { args }
        end
        make_methods_for_objects_of(args)
      end

      def make_methods_for_objects_of(models)
        models.each{ |model| attr_accessor snake(model.to_s).to_sym }

        delegate_models_attributes(models)
      end

      def delegate_models_attributes(models)
        models.each do |model|
          model.column_names.each do |attr|
            delegate attr.to_sym, "#{attr}=".to_sym, to: snake(model.to_s).to_sym, prefix: true
          end
        end
      end
    end

    def method_missing(name, *args, &block)
      if name[/_ids$/]
        model_name, attr_name = get_model_and_method_names(name)
        return send(model_name.to_sym).send(attr_name.to_sym)
      end
      super(name, args, block)
    end

    def initialize(params: {})
      require_extensions
      self.base_modules = self.class.base_modules
      self.params       = params
      get_or_add_default_objects
      default_settings
    end
    # END INIT

    def apply_parameters
      apply
      self
    end
    alias_method :submit, :apply_parameters

    def save
      Saver.new(self).save
    end

    def save!
      Saver.new(self).save!
    end

    def validation_models
      Validator.new(self).validate_form_object
    end

    def permit_params(params)
      return {} if params.empty?
      params.require(snake(model_name.name).gsub(/_+/, '_')).permit(data_structure)
    end

    private

    def require_extensions
      require "slim_form_object/form_helpers/extension_actionview"
    end

    def apply
      assign                = Assign.new(self)
      self.data_objects_arr = assign.apply_parameters_and_make_objects
    end

    def get_or_add_default_objects
      array_of_all_models.map do |model|
        if get_self_object(model) == nil
          method( "#{snake(model.to_s)}=" ).call(model.new)
        else
          get_self_object(model)
        end
      end
    end
    
    def default_settings
      define_singleton_method(:after_validation_form_block)    { Proc.new {} } unless respond_to?(:after_validation_form_block)
      define_singleton_method(:after_save_form_block)          { Proc.new {} } unless respond_to?(:after_save_form_block)
      define_singleton_method(:after_save_object_block)        { Proc.new { true } } unless respond_to?(:after_save_object_block)      
      define_singleton_method(:allow_object_processing_block)  { Proc.new { |data_object| true } } unless respond_to?(:allow_object_processing_block)
      define_singleton_method(:allow_to_validate_object_block) { Proc.new { true } } unless respond_to?(:allow_to_validate_object_block)
      define_singleton_method(:allow_to_save_object_block)     { Proc.new { |object| object.changed? } } unless respond_to?(:allow_to_save_object_block)
      define_singleton_method(:check_params_block)             { Proc.new {} } unless respond_to?(:check_params_block)
    end

  end
end







