module CanBe
  module Builder
    class CanBe
      def self.build(klass)
        new(klass).define_methods
      end

      def initialize(klass)
        @klass = klass
      end

      def define_methods
        define_processor
        define_instance_methods
        define_class_methods
        define_validations
        define_details
        define_history
      end

      private
      def define_processor
        @klass.instance_eval do
          define_method :can_be_processor do
            @can_be_processor ||= Processor::Instance.new self
          end
        end

        @klass.class_eval do
          define_singleton_method :can_be_processor do
            @can_be_processor ||= Processor::Klass.new self
          end
        end
      end

      def define_instance_methods
        klass = @klass

        klass.instance_eval do
          define_method "#{klass.can_be_config.field_name}=" do |value|
            can_be_processor.field_value = value
          end
        end

        klass.can_be_config.types.each do |t|
          klass.instance_eval do
            define_method "#{t}?" do
              can_be_processor.boolean_eval(t)
            end

            define_method "change_to_#{t}" do |force_history_removal = false, &block|
              can_be_processor.update_field(t, force_history_removal: force_history_removal, &block)
            end

            define_method "change_to_#{t}!" do |force_history_removal = false, &block|
              can_be_processor.update_field(t, save: true, force_history_removal: force_history_removal, &block)
            end
          end
        end
      end

      def define_class_methods
        @klass.class_eval do
          define_singleton_method :find_by_can_be_types do |*types|
            can_be_processor.find_by_types(*types)
          end
        end

        @klass.can_be_config.types.each do |t|
          @klass.class_eval do
            define_singleton_method "create_#{t}" do |*args, &block|
              can_be_processor.create(t, *args, &block)
            end

            define_singleton_method "new_#{t}" do |*args, &block|
              can_be_processor.instantiate(t, *args, &block)
            end

            define_singleton_method t.pluralize.to_sym do
              can_be_processor.find_by_types t
            end

            after_initialize do |model|
              model.can_be_processor.set_default_field_value
            end
          end
        end
      end

      def define_validations
        @klass.class_eval do
          validates_inclusion_of self.can_be_config.field_name.to_sym, in: self.can_be_config.types
        end
      end

      def define_details
        @klass.class_eval do
          belongs_to self.can_be_config.details_name.to_sym, polymorphic: true, autosave: true

          after_initialize do |model|
            model.can_be_processor.initialize_details
          end

          after_save do |model|
            model.can_be_processor.clean_details
          end

          after_destroy do |model|
            model.can_be_processor.destroy_details
          end
        end
      end

      def define_history
        return unless @klass.can_be_config.keeps_history?

        @klass.class_eval do
          after_save do |model|
            model.can_be_processor.save_history
          end

          after_destroy do |model|
            model.can_be_processor.destroy_histories
          end
        end
      end
    end
  end
end
