# frozen_string_literal: true

module RepositoryFilters
  class ColumnNotFoundException < StandardError; end

  class ValueNotFoundException < StandardError; end
end

class RepositoryDatatableService
  include MyModulesHelper
  attr_reader :repository_rows, :all_count, :filtered_count, :mappings

  PREDEFINED_COLUMNS = %w(row_id row_name added_on added_by archived_on archived_by
                          assigned relationships updated_on updated_by).freeze

  def initialize(repository, params, user, my_module = nil, preload_cells: true, disable_reminders: false)
    @repository = repository
    @user = user
    @my_module = my_module
    @preload_cells = preload_cells
    @disable_reminders = disable_reminders
    @params = params
    @assigned_view =  @params[:assigned].in?(%w(assigned assigned_simple))
    @sortable_columns = build_sortable_columns
    create_columns_mappings
    @repository_rows = process_query
  end

  private

  def create_columns_mappings
    # Make mappings of custom columns, so we have same id for every
    # column
    index = @repository.default_columns_count
    @mappings = {}
    @repository.repository_columns.order(:id).each do |column|
      @mappings[column.id] = index.to_s
      index += 1
    end
  end

  def process_query
    search_value = @params[:search][:value]
    order_params = @params[:order].first

    order_by_column = { column: order_params[:column].to_i, dir: order_params[:dir] }

    repository_rows = fetch_rows(search_value)

    # Adding assigned counters
    if @my_module
      if @assigned_view
        repository_rows = repository_rows.joins(my_module_repository_rows: { my_module: :experiment })
                                         .where(my_module_repository_rows: { my_module_id: @my_module })
        if @repository.has_stock_management?
          repository_rows = repository_rows
                            .select('SUM(DISTINCT my_module_repository_rows.stock_consumption) AS "consumed_stock"')
        end
      else
        repository_rows = repository_rows.left_outer_joins(my_module_repository_rows: { my_module: :experiment })
        repository_rows = repository_rows.where(my_module_repository_rows: { my_module: @my_module })
                                         .or(repository_rows.active)
        repository_rows = repository_rows.select("EXISTS (SELECT 1 FROM my_module_repository_rows " \
                                                 "WHERE my_module_repository_rows.repository_row_id = repository_rows.id " \
                                                 "AND my_module_repository_rows.my_module_id = #{@my_module.id}) AS row_assigned")
      end
    else
      repository_rows = repository_rows.left_outer_joins(my_module_repository_rows: { my_module: :experiment })
    end

    if Repository.reminders_enabled? && !@disable_reminders
      repository_rows =
        if @repository.archived? || @repository.is_a?(RepositorySnapshot)
          # don't load reminders for archived repositories or snapshots
          repository_rows.select('FALSE AS has_active_reminders')
        else
          repository_rows.select("EXISTS (#{RepositoryCell.with_active_reminder(@user)
                                                          .joins(:repository_column)
                                                          .where(repository_column: { repository: @repository })
                                                          .where('repository_cells.repository_row_id = repository_rows.id')
                                                          .select(1)
                                                          .to_sql}) AS has_active_reminders")
        end
    end

    repository_rows = repository_rows
                      .select('COUNT(DISTINCT my_module_repository_rows.id) AS "assigned_my_modules_count"')
                      .select('COUNT(DISTINCT my_modules.experiment_id) AS "assigned_experiments_count"')
                      .select('COUNT(DISTINCT experiments.project_id) AS "assigned_projects_count"')
                      .select('COALESCE(parent_connections_count, 0) + COALESCE(child_connections_count, 0)
                               AS "relationships_count"')
    repository_rows = repository_rows.preload(:repository_columns, repository_cells: { value: @repository.cell_preload_includes }) if @preload_cells
    repository_rows = repository_rows.preload(:repository_stock_cell, :repository_stock_value) if @repository.has_stock_management?

    sort_rows(order_by_column, repository_rows)
  end

  def fetch_rows(search_value)
    repository_rows = @repository.repository_rows
    if @params[:archived] && !@repository.archived?
      repository_rows = repository_rows.where(archived: @params[:archived])
    end

    @all_count =
      if @my_module && @assigned_view
        repository_rows.joins(:my_module_repository_rows)
                       .where(my_module_repository_rows: { my_module_id: @my_module })
                       .count
      else
        @repository.repository_rows_count
      end

    repository_rows = repository_rows.where(external_id: @params[:external_ids]) if @params[:external_ids]

    # filter only rows with reminders if filter param is present
    repository_rows = repository_rows.with_active_reminders(@repository, @user) if @params[:only_reminders]

    if search_value.present?
      if @repository.default_search_fileds.include?('users.full_name')
        repository_rows = repository_rows.joins(:created_by)
      end
      repository_row_matches = repository_rows.where_attributes_like(@repository.default_search_fileds, search_value)
      results = repository_rows.where(id: repository_row_matches)

      data_types = @repository.repository_columns.pluck(:data_type).uniq

      Extends::REPOSITORY_EXTRA_SEARCH_ATTR.each do |data_type, config|
        next unless data_types.include?(data_type.to_s)

        custom_cell_matches = repository_rows.joins(config[:includes])
                                             .where_attributes_like(config[:field], search_value)
        results = results.or(repository_rows.where(id: custom_cell_matches))
      end

      repository_rows = results
    end

    repository_rows = repository_rows.where(id: advanced_search(repository_rows)) if @params[:advanced_search].present?

    @filtered_count =
      if @assigned_view
        repository_rows.joins(:my_module_repository_rows)
                       .where(my_module_repository_rows: { my_module_id: @my_module }).count
      else
        repository_rows.where(archived: !@params[:archived].nil? && @params[:archived]).count
      end

    repository_rows.joins('LEFT OUTER JOIN "users" "created_by" ON "created_by"."id" = "repository_rows"."created_by_id"')
                   .joins('LEFT OUTER JOIN "users" "last_modified_by" ON "last_modified_by"."id" = "repository_rows"."last_modified_by_id"')
                   .joins('LEFT OUTER JOIN "users" "archived_by" ON "archived_by"."id" = "repository_rows"."archived_by_id"')
                   .select('repository_rows.*')
                   .select('MAX("created_by"."full_name") AS created_by_full_name')
                   .select('MAX("last_modified_by"."full_name") AS last_modified_by_full_name')
                   .select('MAX("archived_by"."full_name") AS archived_by_full_name')
                   .group('repository_rows.id')
  end

  def advanced_search(repository_rows)
    adv_search_params = @params[:advanced_search]
    filter = @repository.repository_table_filters.new
    adv_search_params[:filter_elements].each_with_index do |filter_element_params, index|
      repository_rows =
        if PREDEFINED_COLUMNS.include?(filter_element_params[:repository_column_id])
          add_predefined_column_filter_condition(repository_rows, filter_element_params)
        else
          add_custom_column_filter_condition(repository_rows, filter, index, filter_element_params)
        end
    end

    repository_rows
  end

  def add_predefined_column_filter_condition(repository_rows, filter_element_params)
    case filter_element_params[:repository_column_id]
    when 'row_id'
      build_row_id_filter_condition(repository_rows, filter_element_params)
    when 'row_name'
      build_name_filter_condition(repository_rows, filter_element_params)
    when 'relationships'
      build_relationship_filter_condition(repository_rows, filter_element_params)
    when 'added_on'
      build_added_on_filter_condition(repository_rows, filter_element_params)
    when 'added_by'
      build_added_by_filter_condition(repository_rows, filter_element_params)
    when 'updated_on'
      build_updated_on_filter_condition(repository_rows, filter_element_params)
    when 'updated_by'
      build_updated_by_filter_condition(repository_rows, filter_element_params)
    when 'archived_by'
      build_archived_by_filter_condition(repository_rows, filter_element_params)
    when 'archived_on'
      build_archived_on_filter_condition(repository_rows, filter_element_params)
    when 'assigned'
      build_assigned_filter_condition(repository_rows, filter_element_params)
    else
      repository_rows
    end
  end

  def build_row_id_filter_condition(repository_rows, filter_element_params)
    return repository_rows if filter_element_params.dig(:parameters, :text).blank?

    case filter_element_params[:operator]
    when 'contains'
      repository_rows
        .where("(#{RepositoryRow::PREFIXED_ID_SQL})::text ILIKE ?",
               "%#{ActiveRecord::Base.sanitize_sql_like(filter_element_params.dig(:parameters, :text))}%")
    when 'doesnt_contain'
      repository_rows
        .where.not("(#{RepositoryRow::PREFIXED_ID_SQL})::text ILIKE ?",
                   "%#{ActiveRecord::Base.sanitize_sql_like(filter_element_params.dig(:parameters, :text))}%")
    else
      raise ArgumentError, 'Wrong operator for RepositoryRow ID!'
    end
  end

  def build_name_filter_condition(repository_rows, filter_element_params)
    return repository_rows if filter_element_params.dig(:parameters, :text).blank?

    case filter_element_params[:operator]
    when 'contains'
      repository_rows.where('repository_rows.name ILIKE ?',
                            "%#{ActiveRecord::Base.sanitize_sql_like(filter_element_params.dig(:parameters, :text))}%")
    when 'doesnt_contain'
      repository_rows
        .where.not('repository_rows.name ILIKE ?',
                   "%#{ActiveRecord::Base.sanitize_sql_like(filter_element_params.dig(:parameters, :text))}%")
    else
      raise ArgumentError, 'Wrong operator for RepositoryRow Name!'
    end
  end

  def build_relationship_filter_condition(repository_rows, filter_element_params)
    case filter_element_params[:operator]
    when 'contains'
      text = "%#{ActiveRecord::Base.sanitize_sql_like(filter_element_params.dig(:parameters, :text))}%"

      repository_rows.where(
        id: repository_rows.left_outer_joins(:parent_repository_rows, :child_repository_rows)
                           .where("trim_html_tags(child_repository_rows_repository_rows.name)::text ILIKE ? OR
                                  trim_html_tags(parent_repository_rows_repository_rows.name)::text ILIKE ? OR
                                  ('#{RepositoryRow::ID_PREFIX}' ||
                                  child_repository_rows_repository_rows.id)::text ILIKE ? OR
                                  ('#{RepositoryRow::ID_PREFIX}' ||
                                  parent_repository_rows_repository_rows.id)::text ILIKE ?",
                                  text, text, text, text).select(:id)
      )
    when 'contains_relationship'
      repository_rows = repository_rows.left_outer_joins(:parent_connections, :child_connections)
      repository_rows.where.not(parent_connections: { id: nil })
                     .or(repository_rows.where.not(child_connections: { id: nil }))
    when 'doesnt_contain_relationship'
      repository_rows.where.missing(:parent_connections, :child_connections)
    else
      raise ArgumentError, 'Wrong operator for RepositoryRow Relationship!'
    end
  end

  def build_added_on_filter_condition(repository_rows, filter_element_params)
    case filter_element_params[:operator]
    when 'today'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"created_at\") >= ? AND " \
        "date_trunc('minute', \"repository_rows\".\"created_at\") < ?",
        Time.zone.now.beginning_of_day,
        Time.zone.now.end_of_day
      )
    when 'yesterday'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"created_at\") >= ? AND "\
        "date_trunc('minute', \"repository_rows\".\"created_at\") < ?",
        Time.zone.now.beginning_of_day - 1.day, Time.zone.now.beginning_of_day
      )
    when 'last_week'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"created_at\") >= ? AND "\
        "date_trunc('minute', \"repository_rows\".\"created_at\") < ?",
        Time.zone.now.beginning_of_week - 1.week, Time.zone.now.beginning_of_week
      )
    when 'this_month'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"created_at\") >= ? AND date_trunc('minute', \"repository_rows\".\"created_at\") <= ?",
        Time.zone.now.beginning_of_month,
        Time.zone.now.end_of_month
      )
    when 'last_year'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"created_at\") >= ? AND "\
        "date_trunc('minute', \"repository_rows\".\"created_at\") < ?",
        Time.zone.now.beginning_of_year - 1.year, Time.zone.now.beginning_of_year
      )
    when 'this_year'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"created_at\") >= ? AND "\
        "date_trunc('minute', \"repository_rows\".\"created_at\") <= ?",
        Time.zone.now.beginning_of_year,
        Time.zone.now.end_of_year
      )
    when 'equal_to'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"created_at\") = ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'unequal_to'
      repository_rows.where.not("date_trunc('minute', \"repository_rows\".\"created_at\") = ?",
                                Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'greater_than'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"created_at\") > ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'greater_than_or_equal_to'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"created_at\") >= ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'less_than'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"created_at\") < ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'less_than_or_equal_to'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"created_at\") <= ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'between'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"created_at\") > ? AND "\
        "date_trunc('minute', \"repository_rows\".\"created_at\") < ?",
        Time.zone.parse(filter_element_params.dig(:parameters, :start_datetime)),
        Time.zone.parse(filter_element_params.dig(:parameters, :end_datetime))
      )
    else
      raise ArgumentError, 'Wrong operator for RepositoryRow Added On!'
    end
  end

  def build_archived_on_filter_condition(repository_rows, filter_element_params)
    return repository_rows unless @params[:archived]

    case filter_element_params[:operator]
    when 'today'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"archived_on\") >= ? AND date_trunc('minute', \"repository_rows\".\"archived_on\") <= ?",
                            Time.zone.now.beginning_of_day, Time.zone.now.end_of_day)
    when 'yesterday'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"archived_on\") >= ? AND date_trunc('minute', \"repository_rows\".\"archived_on\") < ?",
                            Time.zone.now.beginning_of_day - 1.day, Time.zone.now.beginning_of_day)
    when 'last_week'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"archived_on\") >= ? AND date_trunc('minute', \"repository_rows\".\"archived_on\") < ?",
                            Time.zone.now.beginning_of_week - 1.week, Time.zone.now.beginning_of_week)
    when 'this_month'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"archived_on\") >= ? AND date_trunc('minute', \"repository_rows\".\"archived_on\") <= ?",
        Time.zone.now.beginning_of_month,
        Time.zone.now.end_of_month
      )
    when 'last_year'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"archived_on\") >= ? AND date_trunc('minute', \"repository_rows\".\"archived_on\") < ?",
                            Time.zone.now.beginning_of_year - 1.year, Time.zone.now.beginning_of_year)
    when 'this_year'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"archived_on\") >= ? AND "\
        "date_trunc('minute', \"repository_rows\".\"archived_on\") <= ?",
        Time.zone.now.beginning_of_year,
        Time.zone.now.end_of_year
      )
    when 'equal_to'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"archived_on\") = ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'unequal_to'
      repository_rows.where.not("date_trunc('minute', \"repository_rows\".\"archived_on\") = ?",
                                Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'greater_than'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"archived_on\") > ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'greater_than_or_equal_to'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"archived_on\") >= ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'less_than'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"archived_on\") < ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'less_than_or_equal_to'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"archived_on\") <= ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'between'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"archived_on\") > ? AND date_trunc('minute', \"repository_rows\".\"archived_on\") < ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :start_datetime)),
                            Time.zone.parse(filter_element_params.dig(:parameters, :end_datetime)))
    else
      raise ArgumentError, 'Wrong operator for RepositoryRow Archived On!'
    end
  end

  def build_updated_on_filter_condition(repository_rows, filter_element_params)
    case filter_element_params[:operator]
    when 'today'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"updated_at\") >= ? AND " \
        "date_trunc('minute', \"repository_rows\".\"updated_at\") < ?",
        Time.zone.now.beginning_of_day,
        Time.zone.now.end_of_day
      )
    when 'yesterday'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"updated_at\") >= ? AND "\
        "date_trunc('minute', \"repository_rows\".\"updated_at\") < ?",
        Time.zone.now.beginning_of_day - 1.day, Time.zone.now.beginning_of_day
      )
    when 'last_week'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"updated_at\") >= ? AND "\
        "date_trunc('minute', \"repository_rows\".\"updated_at\") < ?",
        Time.zone.now.beginning_of_week - 1.week, Time.zone.now.beginning_of_week
      )
    when 'this_month'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"updated_at\") >= ? AND "\
        "date_trunc('minute', \"repository_rows\".\"updated_at\") <= ?",
        Time.zone.now.beginning_of_month,
        Time.zone.now.end_of_month
      )
    when 'last_year'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"updated_at\") >= ? AND "\
        "date_trunc('minute', \"repository_rows\".\"updated_at\") < ?",
        Time.zone.now.beginning_of_year - 1.year, Time.zone.now.beginning_of_year
      )
    when 'this_year'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"updated_at\") >= ? AND "\
        "date_trunc('minute', \"repository_rows\".\"updated_at\") <= ?",
        Time.zone.now.beginning_of_year,
        Time.zone.now.end_of_year
      )
    when 'equal_to'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"updated_at\") = ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'unequal_to'
      repository_rows.where.not("date_trunc('minute', \"repository_rows\".\"updated_at\") = ?",
                                Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'greater_than'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"updated_at\") > ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'greater_than_or_equal_to'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"updated_at\") >= ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'less_than'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"updated_at\") < ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'less_than_or_equal_to'
      repository_rows.where("date_trunc('minute', \"repository_rows\".\"updated_at\") <= ?",
                            Time.zone.parse(filter_element_params.dig(:parameters, :datetime)))
    when 'between'
      repository_rows.where(
        "date_trunc('minute', \"repository_rows\".\"updated_at\") > ? AND "\
        "date_trunc('minute', \"repository_rows\".\"updated_at\") < ?",
        Time.zone.parse(filter_element_params.dig(:parameters, :start_datetime)),
        Time.zone.parse(filter_element_params.dig(:parameters, :end_datetime))
      )
    else
      raise ArgumentError, 'Wrong operator for RepositoryRow Updated on!'
    end
  end

  def build_added_by_filter_condition(repository_rows, filter_element_params)
    case filter_element_params[:operator]
    when 'any_of'
      repository_rows.joins(:created_by)
                     .where(created_by: { id: filter_element_params.dig(:parameters, :user_ids) })
    when 'none_of'
      repository_rows.joins(:created_by)
                     .where.not(created_by: { id: filter_element_params.dig(:parameters, :user_ids) })
    else
      raise ArgumentError, 'Wrong operator for RepositoryRow Added By!'
    end
  end

  def build_archived_by_filter_condition(repository_rows, filter_element_params)
    return repository_rows unless @params[:archived]

    case filter_element_params[:operator]
    when 'any_of'
      repository_rows.joins(:archived_by)
                     .where(archived_by: { id: filter_element_params.dig(:parameters, :user_ids) })
    when 'none_of'
      repository_rows.joins(:archived_by)
                     .where.not(archived_by: { id: filter_element_params.dig(:parameters, :user_ids) })
    else
      raise ArgumentError, 'Wrong operator for RepositoryRow Archived By!'
    end
  end

  def build_updated_by_filter_condition(repository_rows, filter_element_params)
    case filter_element_params[:operator]
    when 'any_of'
      repository_rows.joins(:last_modified_by)
                     .where(last_modified_by: { id: filter_element_params.dig(:parameters, :user_ids) })
    when 'none_of'
      repository_rows.joins(:last_modified_by)
                     .where.not(last_modified_by: { id: filter_element_params.dig(:parameters, :user_ids) })
    else
      raise ArgumentError, 'Wrong operator for RepositoryRow Updated By!'
    end
  end

  def build_assigned_filter_condition(repository_rows, filter_element_params)
    return repository_rows if filter_element_params.dig(:parameters, :my_module_ids).blank?

    case filter_element_params[:operator]
    when 'any_of'
      repository_rows.joins(:my_modules)
                     .where(my_modules: { id: filter_element_params.dig(:parameters, :my_module_ids) })
    when 'none_of'
      repository_rows.where('NOT EXISTS (SELECT NULL FROM my_module_repository_rows
                             WHERE my_module_repository_rows.repository_row_id = repository_rows.id AND
                             my_module_repository_rows.my_module_id IN (?))', filter_element_params.dig(:parameters, :my_module_ids))
    when 'all_of'
      repository_rows
        .joins(:my_modules)
        .where(my_modules: { id: filter_element_params.dig(:parameters, :my_module_ids) })
        .having('COUNT(my_modules.id) = ?', filter_element_params.dig(:parameters, :my_module_ids).count)
        .group(:id)
    else
      raise ArgumentError, 'Wrong operator for RepositoryRow Assigned To!'
    end
  end

  def add_custom_column_filter_condition(repository_rows, filter, index, filter_element_params)
    repository_column = @repository.repository_columns.find_by(id: filter_element_params['repository_column_id'])
    raise RepositoryFilters::ColumnNotFoundException unless repository_column

    filter_element = filter.repository_table_filter_elements.new(
      repository_column: repository_column,
      operator: filter_element_params[:operator],
      parameters: filter_element_params[:parameters]
    )
    config = Extends::REPOSITORY_ADVANCED_SEARCH_ATTR[filter_element.repository_column.data_type.to_sym]
    join_cells_alias = "repository_cells_#{index}"
    join_values_alias = "repository_values_#{index}"

    enforce_referenced_value_existence!(filter_element)

    repository_rows =
      repository_rows
      .joins(
        "LEFT OUTER JOIN \"repository_cells\" AS \"#{join_cells_alias}\"" \
        " ON  \"repository_rows\".\"id\" = \"#{join_cells_alias}\".\"repository_row_id\"" \
        " AND \"#{join_cells_alias}\".\"repository_column_id\" = '#{filter_element.repository_column.id}'"
      ).joins(
        "LEFT OUTER JOIN \"#{config[:table_name]}\" AS \"#{join_values_alias}\"" \
        " ON  \"#{join_values_alias}\".\"id\" = \"#{join_cells_alias}\".\"value_id\""
      )

    if %w(empty file_not_attached).include?(filter_element_params[:operator])
      repository_rows.where(join_values_alias => { id: nil })
    else
      value_klass = filter_element.repository_column.data_type.constantize
      value_klass.add_filter_condition(repository_rows, join_values_alias, filter_element)
    end
  end

  def enforce_referenced_value_existence!(filter_element)
    relation_method =
      Extends::REPOSITORY_ADVANCED_SEARCH_REFERENCED_VALUE_RELATIONS[
        filter_element.repository_column.data_type.to_sym
      ]

    return unless relation_method

    relation = filter_element.repository_column.public_send(relation_method)
    value_item_ids = filter_element.parameters['item_ids']
    raise RepositoryFilters::ValueNotFoundException if relation.where(id: value_item_ids).count != value_item_ids.length
  end

  def build_sortable_columns
    array = @repository.default_sortable_columns
    @repository.repository_columns.count.times do
      array << 'repository_cell.value'
    end

    array << 'consumed_stock' if @repository.has_stock_management? && @assigned_view
    array
  end

  def map_simple_view_column(index)
    return assigned_repository_simple_view_name_column_id(@repository) unless @repository.has_stock_management?

    case index
    when 0
      assigned_repository_simple_view_name_column_id(@repository)
    when 1
      @mappings[@repository.repository_stock_column.id]
    when 2
      @sortable_columns.length
    end
  end

  def sort_rows(column_obj, records)
    dir = %w(DESC ASC).find do |direction|
      direction == column_obj[:dir].upcase
    end || 'ASC'

    column_index = column_obj[:column]
    service = RepositoryTableStateService.new(@user, @repository)
    col_order = service.load_state.state['ColReorder']
    column_id = case @params[:assigned]
                when 'assigned_simple'
                  map_simple_view_column(column_index).to_i
                else
                  @params[:draw].to_i == 1 ? column_index.to_i : col_order[column_index].to_i
                end

    case @sortable_columns[column_id - 1]
    when 'assigned'
      return records if @my_module && @assigned_view

      records.order("assigned_my_modules_count #{dir}")
    when 'repository_cell.value'
      id = @mappings.key(column_id.to_s)
      sorting_column = @repository.repository_columns.find_by(id: id)
      return records unless sorting_column

      sorting_data_type = sorting_column.data_type.constantize
      cells = sorting_data_type.joins(
        "INNER JOIN repository_cells AS repository_sort_cells "\
        "ON repository_sort_cells.value_id = #{sorting_data_type.table_name}.id "\
        "AND repository_sort_cells.value_type = '#{sorting_data_type.base_class.name}'"
      ).where('repository_sort_cells.repository_column_id': sorting_column.id)

      if sorting_data_type.const_defined?('EXTRA_SORTABLE_VALUE_INCLUDE')
        cells = cells.joins(sorting_data_type::EXTRA_SORTABLE_VALUE_INCLUDE)
      end

      cells = if sorting_column.repository_checklist_value?
                cells
                  .select("repository_sort_cells.repository_row_id, \
                           STRING_AGG(#{sorting_data_type::SORTABLE_COLUMN_NAME}, ' ' \
                           ORDER BY #{sorting_data_type::SORTABLE_COLUMN_NAME}) AS value")
                  .group('repository_sort_cells.repository_row_id')
              else
                cells
                  .select("repository_sort_cells.repository_row_id, #{sorting_data_type::SORTABLE_COLUMN_NAME} AS value")
              end

      records.joins("LEFT OUTER JOIN (#{cells.to_sql}) AS values ON values.repository_row_id = repository_rows.id")
             .group('values.value')
             .order("values.value #{dir}")
    when 'users.full_name'
      records.group('created_by.full_name').order("created_by.full_name #{dir}")
    when 'consumed_stock'
      records.order("#{@sortable_columns[column_id - 1]} #{dir}")
    when 'relationships'
      records.order("relationships_count #{dir}")
    else
      records.group(@sortable_columns[column_id - 1]).order("#{@sortable_columns[column_id - 1]} #{dir}")
    end
  end
end
