# frozen_string_literal: true

#  Copyright (c) 2014-2024, 2019 Pfadibewegung Schweiz. This file is part of
#  hitobito and licensed under the Affero General Public License version 3
#  or later. See the COPYING file at the top-level directory or at
#  https://github.com/hitobito/hitobito.

module Group::NestedSet
  extend ActiveSupport::Concern

  included do
    acts_as_nested_set dependent: :destroy
    belongs_to :layer_group, class_name: "Group"

    before_save :store_new_display_name
    after_save :move_to_alphabetic_position
  end

  # The hierarchy from top to bottom of and including this group.
  def hierarchy
    @hierarchy ||= self_and_ancestors
  end

  # The layer hierarchy from top to bottom of this group.
  def layer_hierarchy(**scope_args)
    scope_args.to_a.inject(hierarchy) do |groups, (method, args)|
      groups.send(method, args)
    end.select { |g| g.class.layer }
  end

  # The group hierarchy inside the layer of this group.
  def local_hierarchy
    hierarchy.select { |g| g.layer_group_id == layer_group_id }
  end

  # siblings with the same type
  def sister_groups
    self_and_sister_groups.where.not(id: id)
  end

  def self_and_sister_groups
    Group.without_deleted
      .where(parent_id: parent_id, type: type)
  end

  # siblings with the same type and all their descendant groups, including self
  def sister_groups_with_descendants
    Group.without_deleted
      .joins("LEFT JOIN #{Group.quoted_table_name} AS sister_groups " \
                "ON #{Group.quoted_table_name}.lft >= sister_groups.lft " \
                "AND #{Group.quoted_table_name}.lft < sister_groups.rgt")
      .where(sister_groups: {type: type, parent_id: parent_id})
  end

  # The layer hierarchy without the layer of this group.
  def upper_layer_hierarchy
    return [] unless parent

    if new_record?
      if layer?
        parent.layer_hierarchy
      else
        parent.layer_hierarchy - [parent.layer_group]
      end
    else
      layer_hierarchy - [layer_group]
    end
  end

  def use_hierarchy_from_parent(parent)
    parent_hierarchy = parent.is_a?(Group) ? parent.hierarchy : parent
    @hierarchy = parent_hierarchy + [self]
  end

  def groups_in_same_layer
    Group.where(layer_group_id: layer_group_id)
      .without_deleted
      .order(:lft)
  end

  def has_sublayers? # rubocop:disable Naming/PredicateName
    children.any? { |child| child.layer? }
  end

  def no_touch_on_move
    true
  end

  def sorting_name
    display_name.downcase
  end

  def sorting_required?
    name_changed? || short_name_changed?
  end

  private

  def store_new_display_name
    @move_to_new_name = sorting_required? ? sorting_name : false
    true # force callback to return true
  end

  def move_to_alphabetic_position
    return unless move_required?

    left_neighbor = find_left_neighbor(parent, :sorting_name, true)
    if left_neighbor
      move_to_right_of_if_change(left_neighbor)
    else
      move_to_most_left_if_change
    end
  end

  def move_required?
    (@move_to_new_parent_id != false || @move_to_new_name != false) &&
      parent_id &&
      parent.children.count > 1
  end

  def move_to_right_of_if_change(node)
    if node.sorting_name != sorting_name && node.rgt != lft - 1
      move_to_right_of(node)
    end
  end

  def move_to_most_left_if_change
    first = parent.children[0]
    move_to_left_of(first) unless first == self
  end

  module ClassMethods
    def root_id
      @root_id ||= root.id
    end

    def reset_root_id
      @root_id = nil
    end
  end
end
