module JSONAPI::Relationships::Sorting

def apply_db_sorts(related, db_sorts)

def apply_db_sorts(related, db_sorts)
  db_sorts.each do |sort_field|
    direction = RelationshipHelpers.extract_sort_direction(sort_field)
    field = RelationshipHelpers.extract_sort_field_name(sort_field)
    related = related.order(field => direction)
  end
  related
end

def apply_sorting_to_relationship(related, association)

def apply_sorting_to_relationship(related, association)
  sorts = parse_sort_param
  return related if sorts.empty?
  related_model = association.klass
  db_sorts, virtual_sorts = partition_sorts_by_type(sorts, related_model)
  related = apply_db_sorts(related, db_sorts)
  apply_virtual_sorts_if_needed(related, virtual_sorts, related_model)
end

def apply_virtual_sorts_if_needed(related, virtual_sorts, related_model)

def apply_virtual_sorts_if_needed(related, virtual_sorts, related_model)
  return related unless virtual_sorts.any?
  resource_class = ResourceLoader.find_for_model(related_model)
  sort_records_by_virtual_attributes(related.to_a, virtual_sorts, resource_class)
end

def compare_by_field(record_a, record_b, sort_field, resource_class)

def compare_by_field(record_a, record_b, sort_field, resource_class)
  direction = RelationshipHelpers.extract_sort_direction(sort_field)
  field = RelationshipHelpers.extract_sort_field_name(sort_field)
  value_a = get_virtual_value(record_a, field, resource_class)
  value_b = get_virtual_value(record_b, field, resource_class)
  comparison = compare_values(value_a, value_b)
  direction == :desc ? -comparison : comparison
end

def compare_records(record_a, record_b, virtual_sorts, resource_class)

def compare_records(record_a, record_b, virtual_sorts, resource_class)
  virtual_sorts.each do |sort_field|
    comparison = compare_by_field(record_a, record_b, sort_field, resource_class)
    return comparison unless comparison.zero?
  end
  0
end

def compare_values(value_a, value_b)

def compare_values(value_a, value_b)
  return 0 if value_a.nil? && value_b.nil?
  return -1 if value_a.nil?
  return 1 if value_b.nil?
  value_a <=> value_b
end

def get_virtual_value(record, field, resource_class)

def get_virtual_value(record, field, resource_class)
  resource_instance = resource_class.new(record, {})
  field_sym = field.to_sym
  return resource_instance.public_send(field_sym) if resource_instance.respond_to?(field_sym, false)
  nil
end

def partition_sorts_by_type(sorts, related_model)

def partition_sorts_by_type(sorts, related_model)
  sorts.partition do |sort_field|
    field = RelationshipHelpers.extract_sort_field_name(sort_field)
    related_model.column_names.include?(field.to_s)
  end
end

def render_invalid_sort_fields(invalid_fields)

def render_invalid_sort_fields(invalid_fields)
  render_parameter_errors(
    invalid_fields,
    title: "Invalid Sort Field",
    detail_proc: ->(field) { "Invalid sort field requested: #{field}" },
    source_proc: ->(_field) { { parameter: "sort" } },
  )
end

def sort_records_by_virtual_attributes(records, virtual_sorts, resource_class)

def sort_records_by_virtual_attributes(records, virtual_sorts, resource_class)
  records.sort do |a, b|
    compare_records(a, b, virtual_sorts, resource_class)
  end
end

def validate_sort_fields_for_association(sorts, association)

def validate_sort_fields_for_association(sorts, association)
  resource_class = ResourceLoader.find_for_model(association.klass)
  valid_fields = valid_sort_fields_for_resource(resource_class, association.klass)
  invalid_fields = invalid_sort_fields_for_columns(sorts, valid_fields)
  return if invalid_fields.empty?
  render_invalid_sort_fields(invalid_fields)
end

def validate_sort_param

def validate_sort_param
  sorts = parse_sort_param
  return if sorts.empty?
  association = @resource.class.reflect_on_association(@relationship_name)
  return unless association&.collection?
  validate_sort_fields_for_association(sorts, association)
end