"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.getExpensiveSuggestionAggregationBuilder = void 0;
var _lodash = require("lodash");
var _common = require("@kbn/data-views-plugin/common");
var _types = require("../../common/options_list/types");
var _ip_search = require("../../common/options_list/ip_search");
var _options_list_suggestion_query_helpers = require("./options_list_suggestion_query_helpers");
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */

/**
 * Suggestion aggregations
 */
const getExpensiveSuggestionAggregationBuilder = ({
  fieldSpec
}) => {
  if ((fieldSpec === null || fieldSpec === void 0 ? void 0 : fieldSpec.type) === 'boolean') {
    return expensiveSuggestionAggSubtypes.boolean;
  }
  if ((fieldSpec === null || fieldSpec === void 0 ? void 0 : fieldSpec.type) === 'ip') {
    return expensiveSuggestionAggSubtypes.ip;
  }
  return expensiveSuggestionAggSubtypes.textOrKeywordOrNested;
};
exports.getExpensiveSuggestionAggregationBuilder = getExpensiveSuggestionAggregationBuilder;
const expensiveSuggestionAggSubtypes = {
  /**
   * The "textOrKeywordOrNested" query / parser should be used whenever the field is built on some type of string field,
   * regardless of if it is keyword only, keyword+text, or some nested keyword/keyword+text field.
   */
  textOrKeywordOrNested: {
    buildAggregation: ({
      searchTechnique,
      searchString,
      fieldName,
      fieldSpec,
      sort,
      size
    }) => {
      const subTypeNested = fieldSpec && (0, _common.getFieldSubtypeNested)(fieldSpec);
      let textOrKeywordQuery = {
        suggestions: {
          terms: {
            size,
            field: fieldName,
            shard_size: 10,
            order: (0, _options_list_suggestion_query_helpers.getSortType)(sort)
          }
        },
        unique_terms: {
          cardinality: {
            field: fieldName
          }
        }
      };
      // disabling for date fields because applying a search string will return an error
      if ((fieldSpec === null || fieldSpec === void 0 ? void 0 : fieldSpec.type) !== 'date' && searchString && searchString.length > 0) {
        textOrKeywordQuery = {
          filteredSuggestions: {
            filter: {
              [searchTechnique !== null && searchTechnique !== void 0 ? searchTechnique : _types.OPTIONS_LIST_DEFAULT_SEARCH_TECHNIQUE]: {
                [fieldName]: {
                  value: searchTechnique === 'wildcard' ? `*${(0, _options_list_suggestion_query_helpers.getEscapedWildcardQuery)(searchString)}*` : searchString,
                  case_insensitive: true
                }
              }
            },
            aggs: {
              ...textOrKeywordQuery
            }
          }
        };
      }
      if (subTypeNested) {
        textOrKeywordQuery = {
          nestedSuggestions: {
            nested: {
              path: subTypeNested.nested.path
            },
            aggs: {
              ...textOrKeywordQuery
            }
          }
        };
      }
      return textOrKeywordQuery;
    },
    parse: (rawEsResult, request) => {
      var _get;
      let basePath = 'aggregations';
      const isNested = request.fieldSpec && (0, _common.getFieldSubtypeNested)(request.fieldSpec);
      basePath += isNested ? '.nestedSuggestions' : '';
      basePath += request.searchString ? '.filteredSuggestions' : '';
      const suggestions = (_get = (0, _lodash.get)(rawEsResult, `${basePath}.suggestions.buckets`)) === null || _get === void 0 ? void 0 : _get.reduce((acc, suggestion) => {
        acc.push({
          value: suggestion.key,
          docCount: suggestion.doc_count
        });
        return acc;
      }, []);
      return {
        suggestions,
        totalCardinality: (0, _lodash.get)(rawEsResult, `${basePath}.unique_terms.value`)
      };
    }
  },
  /**
   * the "Boolean" query / parser should be used when the options list is built on a field of type boolean. The query is slightly different than a keyword query.
   */
  boolean: {
    buildAggregation: ({
      fieldName,
      sort
    }) => ({
      suggestions: {
        terms: {
          field: fieldName,
          shard_size: 10,
          order: (0, _options_list_suggestion_query_helpers.getSortType)(sort)
        }
      }
    }),
    parse: rawEsResult => {
      var _get2;
      const suggestions = (_get2 = (0, _lodash.get)(rawEsResult, 'aggregations.suggestions.buckets')) === null || _get2 === void 0 ? void 0 : _get2.reduce((acc, suggestion) => {
        acc.push({
          value: suggestion.key_as_string,
          docCount: suggestion.doc_count
        });
        return acc;
      }, []);
      return {
        suggestions,
        totalCardinality: suggestions.length
      }; // cardinality is only ever 0, 1, or 2 so safe to use length here
    }
  },

  /**
   * the "IP" query / parser should be used when the options list is built on a field of type IP.
   */
  ip: {
    buildAggregation: ({
      fieldName,
      searchString,
      sort,
      size
    }) => {
      let ipRangeQuery = {
        validSearch: true,
        rangeQuery: [{
          key: 'ipv6',
          from: '::',
          to: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'
        }]
      };
      if (searchString && searchString.length > 0) {
        ipRangeQuery = (0, _ip_search.getIpRangeQuery)(searchString);
        if (!ipRangeQuery.validSearch) {
          // ideally should be prevented on the client side but, if somehow an invalid search gets through to the server,
          // simply don't return an aggregation query for the ES search request
          return undefined;
        }
      }
      return {
        suggestions: {
          ip_range: {
            field: fieldName,
            ranges: ipRangeQuery.rangeQuery,
            keyed: true
          },
          aggs: {
            filteredSuggestions: {
              terms: {
                size,
                field: fieldName,
                shard_size: 10,
                order: (0, _options_list_suggestion_query_helpers.getSortType)(sort)
              }
            },
            unique_terms: {
              cardinality: {
                field: fieldName
              }
            }
          }
        }
      };
    },
    parse: (rawEsResult, request) => {
      var _rawEsResult$aggregat, _request$sort, _get3, _get4;
      if (!Boolean((_rawEsResult$aggregat = rawEsResult.aggregations) === null || _rawEsResult$aggregat === void 0 ? void 0 : _rawEsResult$aggregat.suggestions)) {
        // if this is happens, that means there is an invalid search that snuck through to the server side code;
        // so, might as well early return with no suggestions
        return {
          suggestions: [],
          totalCardinality: 0
        };
      }
      const buckets = [];
      (0, _options_list_suggestion_query_helpers.getIpBuckets)(rawEsResult, buckets, 'ipv4'); // modifies buckets array directly, i.e. "by reference"
      (0, _options_list_suggestion_query_helpers.getIpBuckets)(rawEsResult, buckets, 'ipv6');
      const sortedSuggestions = ((_request$sort = request.sort) === null || _request$sort === void 0 ? void 0 : _request$sort.direction) === 'asc' ? buckets.sort((bucketA, bucketB) => bucketA.doc_count - bucketB.doc_count) : buckets.sort((bucketA, bucketB) => bucketB.doc_count - bucketA.doc_count);
      const suggestions = sortedSuggestions.slice(0, request.size).reduce((acc, suggestion) => {
        acc.push({
          value: suggestion.key,
          docCount: suggestion.doc_count
        });
        return acc;
      }, []);
      const totalCardinality = ((_get3 = (0, _lodash.get)(rawEsResult, `aggregations.suggestions.buckets.ipv4.unique_terms.value`)) !== null && _get3 !== void 0 ? _get3 : 0) + ((_get4 = (0, _lodash.get)(rawEsResult, `aggregations.suggestions.buckets.ipv6.unique_terms.value`)) !== null && _get4 !== void 0 ? _get4 : 0);
      return {
        suggestions,
        totalCardinality
      };
    }
  }
};