module DynamicScaffold
  module Controller
    extend ActiveSupport::Concern
    include ControllerUtilities

    included do
      helper_method :dynamic_scaffold_path, :dynamic_scaffold_icon, :dynamic_scaffold, :primary_key_value, :request_queries
      attr_reader :dynamic_scaffold
      before_action lambda {
        @dynamic_scaffold = Config.new(self.class.dynamic_scaffold_model, self)
        self.class.dynamic_scaffold_initializer.call(@dynamic_scaffold)
      }
    end

    class_methods do
      attr_accessor :dynamic_scaffold_model, :dynamic_scaffold_initializer

      def dynamic_scaffold(model, &block)
        self.dynamic_scaffold_model = model
        self.dynamic_scaffold_initializer = block
      end
    end

    # Actions

    def index # rubocop:disable Metrics/AbcSize
      @records = dynamic_scaffold.list.build_sql(scope_params)
      @count = @records.count unless dynamic_scaffold.max_count.nil?
      @records = handle_pagination(@records)
      @records = @records.order dynamic_scaffold.list.sorter if dynamic_scaffold.list.sorter
      @records = @records.order(*dynamic_scaffold.list.order) unless dynamic_scaffold.list.order.empty?

      ret = yield(@records) if block_given?
      @records = ret unless ret.nil?
      unless @records.is_a? ::ActiveRecord::Relation
        raise(
          Error::InvalidOperation,
          'You must return ActiveRecord::Relation from super block'
        )
      end

      @records
    end

    def new # rubocop:disable Metrics/AbcSize
      unless dynamic_scaffold.max_count.nil?
        count = dynamic_scaffold.list.build_sql(scope_params).count
        raise Error::InvalidOperation, 'You can not add any more.' if dynamic_scaffold.max_count?(count)
      end

      @record = dynamic_scaffold.model.new

      defaults = dynamic_scaffold.form.items.each_with_object({}) do |item, memo|
        memo[item.name] = item.default if dynamic_scaffold.model.column_names.include?(item.name.to_s)
      end

      @record.attributes = defaults.merge(scope_params)
    end

    def edit
      @record = find_record(edit_params)
    end

    def create
      @record = dynamic_scaffold.model.new
      @record.attributes = update_values
      bind_sorter_value(@record) if dynamic_scaffold.list.sorter
      dynamic_scaffold.model.transaction do
        check_max_count!
        yield(@record) if block_given?
        if @record.save
          redirect_to dynamic_scaffold_path(:index, request_queries)
        else
          render "#{params[:controller]}/new"
        end
      end
    end

    def update
      values = update_values
      @record = find_record(dynamic_scaffold.model.primary_key => params['id'])
      prev_attribute = @record.attributes
      @record.attributes = values
      dynamic_scaffold.model.transaction do
        yield(@record, prev_attribute) if block_given?
        if @record.save
          redirect_to dynamic_scaffold_path(:index, request_queries)
        else
          render "#{params[:controller]}/edit"
        end
      end
    end

    def destroy
      # `Destroy` also does not support multiple primary keys too.
      record = find_record(dynamic_scaffold.model.primary_key => params['id'])
      begin
        dynamic_scaffold.model.transaction do
          record.destroy
        end
      rescue ::ActiveRecord::InvalidForeignKey => _error
        flash[:dynamic_scaffold_danger] = I18n.t('dynamic_scaffold.alert.destroy.invalid_foreign_key')
      rescue StandardError => error
        flash[:dynamic_scaffold_danger] = I18n.t('dynamic_scaffold.alert.destroy.failed')
        logger.error(error)
      end
      redirect_to dynamic_scaffold_path(:index, request_queries)
    end

    def sort
      pkeys_list = sort_params
      reset_sequence(pkeys_list.size)
      dynamic_scaffold.model.transaction do
        pkeys_list.each do |pkeys|
          rec = find_record(pkeys.to_hash)
          next_sec = next_sequence!
          rec[dynamic_scaffold.list.sorter_attribute] = next_sec
          rec.save
        end
      end
      redirect_to dynamic_scaffold_path(:index, request_queries)
    end
  end
end