# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
# This code was automatically generated from the Elasticsearch Specification
# See https://github.com/elastic/elasticsearch-specification
# See Elasticsearch::ES_SPECIFICATION_COMMIT for commit hash.
module Elasticsearch
  module API
    module Actions
      # Run multiple searches.
      # The format of the request is similar to the bulk API format and makes use of the newline delimited JSON (NDJSON) format.
      # The structure is as follows:
      #
      # ```
      # header
      #
      # body
      #
      # header
      #
      # body
      #
      # ```
      #
      # This structure is specifically optimized to reduce parsing if a specific search ends up redirected to another node.
      # IMPORTANT: The final line of data must end with a newline character `\n`.
      # Each newline character may be preceded by a carriage return `\r`.
      # When sending requests to this endpoint the `Content-Type` header should be set to `application/x-ndjson`.
      #
      # @option arguments [String, Array] :index Comma-separated list of data streams, indices, and index aliases to search.
      # @option arguments [Boolean] :allow_no_indices A setting that does two separate checks on the index expression.
      #  If `false`, the request returns an error (1) if any wildcard expression
      #  (including `_all` and `*`) resolves to zero matching indices or (2) if the
      #  complete set of resolved indices, aliases or data streams is empty after all
      #  expressions are evaluated. If `true`, index expressions that resolve to no
      #  indices are allowed and the request returns an empty result.
      # @option arguments [Boolean] :ccs_minimize_roundtrips If true, network roundtrips between the coordinating node and remote clusters are minimized for cross-cluster search requests. Server default: true.
      # @option arguments [String, Array<String>] :expand_wildcards Type of index that wildcard expressions can match. If the request can target data streams, this argument determines whether wildcard expressions match hidden data streams. Server default: open.
      # @option arguments [Boolean] :ignore_throttled If true, concrete, expanded or aliased indices are ignored when frozen.
      # @option arguments [Boolean] :ignore_unavailable If `false`, the request returns an error if it targets a concrete (non-wildcarded)
      #  index, alias, or data stream that is missing, closed, or otherwise unavailable.
      #  If `true`, unavailable concrete targets are silently ignored.
      # @option arguments [Boolean] :include_named_queries_score Indicates whether hit.matched_queries should be rendered as a map that includes
      #  the name of the matched query associated with its score (true)
      #  or as an array containing the name of the matched queries (false)
      #  This functionality reruns each named query on every hit in a search response.
      #  Typically, this adds a small overhead to a request.
      #  However, using computationally expensive named queries on a large number of hits may add significant overhead.
      # @option arguments [Integer] :max_concurrent_searches Maximum number of concurrent searches the multi search API can execute.
      #  Defaults to `max(1, (# of data nodes * min(search thread pool size, 10)))`.
      # @option arguments [Integer] :max_concurrent_shard_requests Maximum number of concurrent shard requests that each sub-search request executes per node. Server default: 5.
      # @option arguments [Integer] :pre_filter_shard_size Defines a threshold that enforces a pre-filter roundtrip to prefilter search shards based on query rewriting if the number of shards the search request expands to exceeds the threshold. This filter roundtrip can limit the number of shards significantly if for instance a shard can not match any documents based on its rewrite method i.e., if date filters are mandatory to match but the shard bounds and the query are disjoint.
      # @option arguments [String] :project_routing Specifies a subset of projects to target for a search using project metadata
      #  tags in a subset Lucene syntax. Allowed Lucene queries: the _alias tag
      #  and a single value (possible wildcarded). Examples:
      #   _alias:my-project
      #   _alias:_origin
      #   _alias:*pr*
      #  Supported in serverless only.
      # @option arguments [Boolean] :rest_total_hits_as_int If true, hits.total are returned as an integer in the response. Defaults to false, which returns an object.
      # @option arguments [String, Array<String>] :routing Custom routing value used to route search operations to a specific shard.
      #  Not allowed when `index.slice.enabled` is `true` for the target index; use `_slice` instead.
      # @option arguments [String] :search_type Indicates whether global term and document frequencies should be used when scoring returned documents.
      # @option arguments [String] :_slice The slice identifier for routing the search to a specific slice.
      #  When provided at the top level, all sub-searches are routed to shards matching the given slice value.
      #  Use the special value `_all` to query all slices without restricting to a routing value.
      #  Required when `index.slice.enabled` is `true` for the target index; not allowed when `index.slice.enabled` is `false`.
      #  Individual sub-search headers can also specify `_slice` to override the top-level setting.
      # @option arguments [Boolean] :typed_keys Specifies whether aggregation and suggester names should be prefixed by their respective types in the response.
      # @option arguments [Boolean] :error_trace When set to `true` Elasticsearch will include the full stack trace of errors
      #  when they occur.
      # @option arguments [String, Array<String>] :filter_path Comma-separated list of filters in dot notation which reduce the response
      #  returned by Elasticsearch.
      # @option arguments [Boolean] :human When set to `true` will return statistics in a format suitable for humans.
      #  For example `"exists_time": "1h"` for humans and
      #  `"exists_time_in_millis": 3600000` for computers. When disabled the human
      #  readable values will be omitted. This makes sense for responses being consumed
      #  only by machines.
      # @option arguments [Boolean] :pretty If set to `true` the returned JSON will be "pretty-formatted". Only use
      #  this option for debugging only.
      # @option arguments [Hash] :headers Custom HTTP headers
      # @option arguments [Hash] :body searches
      #
      # @see https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-msearch
      #
      def msearch(arguments = {})
        request_opts = { endpoint: arguments[:endpoint] || 'msearch' }

        defined_params = [:index].each_with_object({}) do |variable, set_variables|
          set_variables[variable] = arguments[variable] if arguments.key?(variable)
        end
        request_opts[:defined_params] = defined_params unless defined_params.empty?

        raise ArgumentError, "Required argument 'body' missing" unless arguments[:body]

        arguments = arguments.clone
        headers = arguments.delete(:headers) || {}

        body = arguments.delete(:body)

        _index = arguments.delete(:index)

        method = Elasticsearch::API::HTTP_POST
        path   = if _index
                   "#{Utils.listify(_index)}/_msearch"
                 else
                   '_msearch'
                 end
        params = Utils.process_params(arguments)

        if body.is_a?(Array) && body.any? { |d| d.key? :search }
          payload = body.each_with_object([]) do |item, sum|
            meta = item
            data = meta.delete(:search)

            sum << meta
            sum << data
          end.map { |item| Elasticsearch::API.serializer.dump(item) }
          payload << '' unless payload.empty?
          payload = payload.join("\n")
        elsif body.is_a?(Array)
          payload = body.map { |d| d.is_a?(String) ? d : Elasticsearch::API.serializer.dump(d) }
          payload << '' unless payload.empty?
          payload = payload.join("\n")
        else
          payload = body
        end

        Utils.update_ndjson_headers!(headers, transport.options.dig(:transport_options, :headers))
        Elasticsearch::API::Response.new(
          perform_request(method, path, params, payload, headers, request_opts)
        )
      end
    end
  end
end
