module ActiveForm::Form
  def self.included(base)
    base.class_eval do
      extend ActiveModel::Naming
      include ActiveModel::Conversion
      include ActiveModel::Validations
    end
    base.extend ClassMethods
  end

  module ClassMethods
    delegate :model_name, :reflect_on_association, to: :main_class
    attr_accessor :main_class, :reflected_class, :main_model

    def properties(*attributes, prefix: false, on: nil)
      if on.nil?
        attr_accessor *attributes
      else
        delegate_to_model(attributes, on, prefix)
      end
    end

    def i18n_scope
      :activerecord
    end

    def models
      @models ||= []
    end

    def main_model
      @main_model ||= UnpersistentModel.new(self)
    end

    def main_class
      @main_class ||= if main_model.kind_of?(Symbol)
        main_model.to_s.camelize.constantize
      else
        @main_model
      end
    end

    def alias_property(new_method, old_method)
      alias_method new_method.to_sym, old_method.to_sym
      alias_method "#{new_method}=".to_sym, "#{old_method}=".to_sym
    end

    private

    def add_model_on_list(model_name)
      models << model_name unless models.include?(model_name)
    end

    def add_accessor(model_name)
      attr_accessor model_name
    end

    def assign_delegators(attributes, model_name, prefix)
      attributes.each do |attribute|
        delegate attribute, to: model_name, prefix: prefix
        delegate "#{attribute}=", to: model_name, prefix: prefix
      end
    end

    def delegate_to_model(attributes, on, prefix)
      assign_delegators(attributes, on, prefix)
      add_model_on_list(on)
      add_accessor(on)
    end
  end

  delegate :to_key, :to_param, :id, :persisted?, to: :main_model

  def initialize(attributes = {})
    assign_from_hash(attributes)
  end

  def fill_attributes(params)
    assign_from_hash(params)
  end

  def save(&block)
    ensure_persistable
    valid?.tap do
      call_action_or_block(:save, &block)
    end
  end

  def save!(&block)
    ensure_persistable
    ActiveRecord::Base.transaction do
      call_action_or_block(:save!, &block)
    end
  end

  def main_model
    if self.class.main_model.kind_of?(Symbol)
      send(self.class.main_model)
    else
      self.class.main_model
    end
  end

  def new_record?
    !persisted?
  end

  private

  def ensure_persistable
    message = 'The Form object is not backed by models so cannot be saved'
    if self.class.models.empty?
      raise ActiveForm::CannotBePersisted.new(message)
    end
  end

  def each_models
    self.class.models.each do |model_name|
      yield(send(model_name))
    end
  end

  def assign_from_hash(hash)
    hash.each { |key, value| send("#{key}=", value) }
  end

  def call_action_or_block(action, &block)
    block_given? ? block.call(self) : each_models(&action)
  end
end
