# Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/

class Service::Search < Service::BaseWithCurrentUser
  def execute(term:, objects:, options: { limit: 10 })
    options[:limit] = 10 if options[:limit].blank?

    perform_search(term: term, objects: objects, options: options)
  end

  def perform_search(term:, objects:, options:)
    if SearchIndexBackend.enabled?
      # Performance optimization: some models may allow combining their Elasticsearch queries into one.
      result_by_model = combined_backend_search(term: term, objects: objects, options: options)

      # Other models require dedicated handling, e.g. for permission checks.
      result_by_model.merge!(models(objects: objects, direct_search_index: false).index_with do |model|
        model_search(model: model, term: term, options: options)
      end)

      # Finally, sort by object priority.
      models(objects: objects).map do |model|
        result_by_model[model]
      end.flatten.compact
    else
      models(objects: objects).map do |model|
        model_search(model: model, term: term, options: options)
      end.flatten.compact
    end
  end

  # Perform a direct, cross-module Elasticsearch query and map the results by class.
  def combined_backend_search(term:, objects:, options:)
    result_by_model = {}
    models_with_direct_search_index = models(objects: objects, direct_search_index: true).map(&:to_s)
    if models_with_direct_search_index
      SearchIndexBackend.search(term, models_with_direct_search_index, options).each do |item|
        klass = "::#{item[:type]}".constantize
        record = klass.lookup(id: item[:id])
        (result_by_model[klass] ||= []).push(record) if record
      end
    end
    result_by_model
  end

  # Call the model specific search, which will query Elasticsearch if available,
  #   or the Database otherwise.
  def model_search(model:, term:, options:)
    model.search({ query: term, current_user: current_user, limit: options[:limit], ids: options[:ids] })
  end

  # Get a prioritized list of searchable models
  def models(objects:, direct_search_index: nil)
    objects.select do |model|
      prefs = model.search_preferences(current_user)
      next false if !prefs
      next false if !direct_search_index.nil? && prefs[:direct_search_index] != direct_search_index

      true
    end.sort_by do |model|
      model.search_preferences(current_user)[:prio]
    end.reverse
  end
end