"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.flattenHit = flattenHit;
exports.tabifyDocs = void 0;
var _lodash = require("lodash");
/*
 * 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.
 */

// meta fields we won't merge with our result hit
const EXCLUDED_META_FIELDS = ['_type', '_source'];

// This is an overwrite of the SearchHit type to add the ignored_field_values.
// Can be removed once the estypes.SearchHit knows about ignored_field_values
/**
 * Flattens an individual hit (from an ES response) into an object. This will
 * create flattened field names, like `user.name`.
 *
 * @param hit The hit from an ES reponse's hits.hits[]
 * @param indexPattern The index pattern for the requested index if available.
 * @param params Parameters how to flatten the hit
 */
function flattenHit(hit, indexPattern, params) {
  var _indexPattern$metaFie;
  const flat = {};
  function flatten(obj, keyPrefix = '') {
    for (const [k, val] of Object.entries(obj)) {
      const key = keyPrefix + k;
      const field = indexPattern === null || indexPattern === void 0 ? void 0 : indexPattern.fields.getByName(key);
      if ((params === null || params === void 0 ? void 0 : params.shallow) === false) {
        const isNestedField = (field === null || field === void 0 ? void 0 : field.type) === 'nested';
        if (Array.isArray(val) && !isNestedField) {
          val.forEach(v => (0, _lodash.isPlainObject)(v) && flatten(v, key + '.'));
          continue;
        }
      } else if (flat[key] !== undefined) {
        continue;
      }
      const hasValidMapping = field && field.type !== 'conflict';
      const isValue = !(0, _lodash.isPlainObject)(val);
      if (hasValidMapping || isValue) {
        if (!flat[key]) {
          flat[key] = val;
        } else if (Array.isArray(flat[key])) {
          flat[key].push(val);
        } else {
          flat[key] = [flat[key], val];
        }
        continue;
      }
      flatten(val, key + '.');
    }
  }
  flatten(hit.fields || {});
  if ((params === null || params === void 0 ? void 0 : params.source) !== false && hit._source) {
    flatten(hit._source);
  } else if (params !== null && params !== void 0 && params.includeIgnoredValues && hit.ignored_field_values) {
    // If enabled merge the ignored_field_values into the flattened hit. This will
    // merge values that are not actually indexed by ES (i.e. ignored), e.g. because
    // they were above the `ignore_above` limit or malformed for specific types.
    // This API will only contain the values that were actually ignored, i.e. for the same
    // field there might exist another value in the `fields` response, why this logic
    // merged them both together. We do not merge this (even if enabled) in case source has been
    // merged, since we would otherwise duplicate values, since ignore_field_values and _source
    // contain the same values.
    Object.entries(hit.ignored_field_values).forEach(([fieldName, fieldValue]) => {
      if (flat[fieldName]) {
        // If there was already a value from the fields API, make sure we're merging both together
        if (Array.isArray(flat[fieldName])) {
          flat[fieldName] = [...flat[fieldName], ...fieldValue];
        } else {
          flat[fieldName] = [flat[fieldName], ...fieldValue];
        }
      } else {
        // If no previous value was assigned we can simply use the value from `ignored_field_values` as it is
        flat[fieldName] = fieldValue;
      }
    });
  }

  // Merge all valid meta fields into the flattened object
  indexPattern === null || indexPattern === void 0 ? void 0 : (_indexPattern$metaFie = indexPattern.metaFields) === null || _indexPattern$metaFie === void 0 ? void 0 : _indexPattern$metaFie.forEach(fieldName => {
    const isExcludedMetaField = EXCLUDED_META_FIELDS.includes(fieldName) || fieldName.charAt(0) !== '_';
    if (isExcludedMetaField) {
      return;
    }
    flat[fieldName] = hit[fieldName];
  });

  // Use a proxy to make sure that keys are always returned in a specific order,
  // so we have a guarantee on the flattened order of keys.
  return new Proxy(flat, {
    ownKeys: target => {
      return Reflect.ownKeys(target).sort((a, b) => {
        var _indexPattern$metaFie2, _indexPattern$metaFie3;
        const aIsMeta = indexPattern === null || indexPattern === void 0 ? void 0 : (_indexPattern$metaFie2 = indexPattern.metaFields) === null || _indexPattern$metaFie2 === void 0 ? void 0 : _indexPattern$metaFie2.includes(String(a));
        const bIsMeta = indexPattern === null || indexPattern === void 0 ? void 0 : (_indexPattern$metaFie3 = indexPattern.metaFields) === null || _indexPattern$metaFie3 === void 0 ? void 0 : _indexPattern$metaFie3.includes(String(b));
        if (aIsMeta && bIsMeta) {
          return String(a).localeCompare(String(b));
        }
        if (aIsMeta) {
          return 1;
        }
        if (bIsMeta) {
          return -1;
        }
        return String(a).localeCompare(String(b));
      });
    }
  });
}
const tabifyDocs = (esResponse, index, params = {}) => {
  const columns = [];
  const rows = esResponse.hits.hits.map(hit => {
    const flat = flattenHit(hit, index, params);
    for (const [key, value] of Object.entries(flat)) {
      const field = index === null || index === void 0 ? void 0 : index.fields.getByName(key);
      const fieldName = (field === null || field === void 0 ? void 0 : field.name) || key;
      if (!columns.find(c => c.id === fieldName)) {
        const fieldType = (field === null || field === void 0 ? void 0 : field.type) || typeof value;
        const formatter = field && (index === null || index === void 0 ? void 0 : index.getFormatterForField(field));
        columns.push({
          id: fieldName,
          name: fieldName,
          meta: {
            type: fieldType,
            field: fieldName,
            index: index === null || index === void 0 ? void 0 : index.id,
            params: formatter ? formatter.toJSON() : undefined
          }
        });
      }
    }
    return flat;
  }).filter(hit => hit);
  return {
    type: 'datatable',
    columns,
    rows
  };
};
exports.tabifyDocs = tabifyDocs;