"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.valueTypeToSelectedType = exports.useFieldPreviewContext = exports.defaultValueFormatter = exports.FieldPreviewProvider = void 0;
var _react = _interopRequireWildcard(require("react"));
var _server = require("react-dom/server");
var _useDebounce = _interopRequireDefault(require("react-use/lib/useDebounce"));
var _i18n = require("@kbn/i18n");
var _lodash = require("lodash");
var _rxjs = require("rxjs");
var _state_utils = require("../../state_utils");
var _runtime_field_validation = require("../../lib/runtime_field_validation");
var _field_editor_context = require("../field_editor_context");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/*
 * 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.
 */

const fieldPreviewContext = /*#__PURE__*/(0, _react.createContext)(undefined);
const defaultParams = {
  name: null,
  index: null,
  script: null,
  document: null,
  type: null,
  format: null,
  parentName: null
};
const defaultValueFormatter = value => {
  var _String;
  const content = typeof value === 'object' ? JSON.stringify(value) : (_String = String(value)) !== null && _String !== void 0 ? _String : '-';
  return (0, _server.renderToString)( /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, content));
};
exports.defaultValueFormatter = defaultValueFormatter;
const valueTypeToSelectedType = value => {
  const valueType = typeof value;
  if (valueType === 'string') return 'keyword';
  if (valueType === 'number') return 'double';
  if (valueType === 'boolean') return 'boolean';
  return 'keyword';
};
exports.valueTypeToSelectedType = valueTypeToSelectedType;
const documentsSelector = state => {
  const currentDocument = state.documents[state.currentIdx];
  return {
    currentDocument,
    totalDocs: state.documents.length,
    currentDocIndex: currentDocument === null || currentDocument === void 0 ? void 0 : currentDocument._index,
    currentDocId: currentDocument === null || currentDocument === void 0 ? void 0 : currentDocument._id
  };
};
const FieldPreviewProvider = ({
  controller,
  children
}) => {
  const {
    dataView,
    services: {
      notifications,
      api: {
        getFieldPreview
      }
    },
    fieldName$
  } = (0, _field_editor_context.useFieldEditorContext)();
  const fieldPreview$ = (0, _react.useRef)(new _rxjs.BehaviorSubject(undefined));

  /** The parameters required for the Painless _execute API */
  const [params, setParams] = (0, _react.useState)(defaultParams);
  const [scriptEditorValidation, setScriptEditorValidation] = (0, _react.useState)({
    isValidating: false,
    isValid: true,
    message: null
  });
  const {
    currentDocument,
    currentDocIndex,
    currentDocId
  } = (0, _state_utils.useStateSelector)(controller.state$, documentsSelector);
  const {
    name,
    document,
    script,
    format,
    type,
    parentName
  } = params;
  const updateParams = (0, _react.useCallback)(updated => {
    setParams(prev => ({
      ...prev,
      ...updated
    }));
  }, []);
  const updatePreview = (0, _react.useCallback)(async () => {
    var _dataView$getRuntimeF;
    // don't prevent rendering if we're working with a composite subfield (has parentName)
    if (!parentName && scriptEditorValidation.isValidating) {
      return;
    }
    if (!parentName && (!controller.allParamsDefined(type, script === null || script === void 0 ? void 0 : script.source, currentDocIndex) || !controller.hasSomeParamsChanged(type, script === null || script === void 0 ? void 0 : script.source, currentDocId) || scriptEditorValidation.isValid === false)) {
      controller.setIsLoadingPreview(false);
      return;
    }

    // Not sure why this is getting called without currentDocIndex
    // would be much better to prevent this function from being called at all
    if (!currentDocIndex) {
      controller.setIsLoadingPreview(false);
      return;
    }
    controller.setLastExecutePainlessRequestParams({
      type,
      script: script === null || script === void 0 ? void 0 : script.source,
      documentId: currentDocId
    });
    const currentApiCall = controller.incrementPreviewCount(); // ++previewCount.current;

    const previewScript = parentName && ((_dataView$getRuntimeF = dataView.getRuntimeField(parentName)) === null || _dataView$getRuntimeF === void 0 ? void 0 : _dataView$getRuntimeF.script) || script;
    const response = await getFieldPreview({
      index: currentDocIndex,
      document: document === null || document === void 0 ? void 0 : document._source,
      context: parentName ? 'composite_field' : `${type}_field`,
      script: previewScript
    });
    if (currentApiCall !== controller.getPreviewCount()) {
      // Discard this response as there is another one inflight
      // or we have called reset() and no longer need the response.
      return;
    }
    const {
      error: serverError
    } = response;
    if (serverError) {
      // Server error (not an ES error)
      const title = _i18n.i18n.translate('indexPatternFieldEditor.fieldPreview.errorTitle', {
        defaultMessage: 'Failed to load field preview'
      });
      notifications.toasts.addError(serverError, {
        title
      });
      controller.setIsLoadingPreview(false);
      return;
    }
    if (response.data) {
      const {
        values,
        error
      } = response.data;
      if (error) {
        controller.setPreviewResponse({
          fields: [{
            key: name !== null && name !== void 0 ? name : '',
            value: '',
            formattedValue: defaultValueFormatter('')
          }],
          error: {
            code: 'PAINLESS_SCRIPT_ERROR',
            error: (0, _runtime_field_validation.parseEsError)(error)
          }
        });
      } else {
        if (!Array.isArray(values)) {
          controller.updateCompositeFieldPreview(values, parentName, name, fieldName$.getValue(), type, format, value => fieldPreview$.current.next(value));
        } else {
          controller.updateSingleFieldPreview(name, values, type, format);
        }
      }
    }
    controller.setInitialPreviewComplete(true);
    controller.setIsLoadingPreview(false);
  }, [name, type, script, parentName, dataView, document, currentDocId, getFieldPreview, notifications.toasts, scriptEditorValidation, currentDocIndex, controller, format, fieldName$]);
  const ctx = (0, _react.useMemo)(() => ({
    controller,
    fieldPreview$: fieldPreview$.current,
    params: {
      value: params,
      update: updateParams
    },
    validation: {
      // todo do this next
      setScriptEditorValidation
    }
  }), [controller, fieldPreview$, params, updateParams]);

  /**
   * In order to immediately display the "Updating..." state indicator and not have to wait
   * the 500ms of the debounce, we set the isFetchingDocument state in this effect whenever
   * "customDocIdToLoad" changes
   */

  /**
   * Each time the current document changes we update the parameters
   * that will be sent in the _execute HTTP request.
   */
  // This game me problems
  (0, _react.useEffect)(() => {
    updateParams({
      document: currentDocument,
      index: currentDocument === null || currentDocument === void 0 ? void 0 : currentDocument._index
    });
  }, [currentDocument, updateParams]);

  /**
   * Whenever the name or the format changes we immediately update the preview
   */
  (0, _react.useEffect)(() => {
    const {
      previewResponse: prev
    } = controller.state$.getValue();
    const {
      fields
    } = prev;
    let updatedFields = fields.map(field => {
      let key = name !== null && name !== void 0 ? name : '';
      if (type === 'composite') {
        // restore initial key segement (the parent name), which was not returned
        const {
          1: fieldName
        } = field.key.split('.');
        key = `${name !== null && name !== void 0 ? name : ''}.${fieldName}`;
      }
      return {
        ...field,
        key
      };
    });

    // If the user has entered a name but not yet any script we will display
    // the field in the preview with just the name
    if (updatedFields.length === 0 && name !== null) {
      updatedFields = [{
        key: name,
        value: undefined,
        formattedValue: undefined,
        type: undefined
      }];
    }
    controller.setPreviewResponse({
      ...prev,
      fields: updatedFields
    });
  }, [name, type, parentName, controller]);

  /**
   * Whenever the format changes we immediately update the preview
   */
  (0, _react.useEffect)(() => {
    const {
      previewResponse: prev
    } = controller.state$.getValue();
    const {
      fields
    } = prev;
    controller.setPreviewResponse({
      ...prev,
      fields: fields.map(field => {
        var _get;
        const nextValue = script === null && Boolean(document) ? (_get = (0, _lodash.get)(document === null || document === void 0 ? void 0 : document._source, name !== null && name !== void 0 ? name : '')) !== null && _get !== void 0 ? _get : (0, _lodash.get)(document === null || document === void 0 ? void 0 : document.fields, name !== null && name !== void 0 ? name : '') // When there is no script we try to read the value from _source/fields
        : field === null || field === void 0 ? void 0 : field.value;
        const formattedValue = controller.valueFormatter({
          value: nextValue,
          type,
          format
        });
        return {
          ...field,
          value: nextValue,
          formattedValue
        };
      })
    });
  }, [name, script, document, controller, type, format]);
  (0, _react.useEffect)(() => {
    if ((script === null || script === void 0 ? void 0 : script.source) === undefined) {
      // Whenever the source is not defined ("Set value" is toggled off or the
      // script is empty) we clear the error and update the params cache.
      controller.setLastExecutePainlessRequestParams({
        script: undefined
      });
      controller.setPreviewError(null);
    }
  }, [script === null || script === void 0 ? void 0 : script.source, controller]);

  // Handle the validation state coming from the Painless DiagnosticAdapter
  // (see @kbn-monaco/src/painless/diagnostics_adapter.ts)
  (0, _react.useEffect)(() => {
    if (scriptEditorValidation.isValidating) {
      return;
    }
    if (scriptEditorValidation.isValid === false) {
      var _scriptEditorValidati;
      // Make sure to remove the "Updating..." spinner
      controller.setIsLoadingPreview(false);

      // Set preview response error so it is displayed in the flyout footer
      const error = (script === null || script === void 0 ? void 0 : script.source) === undefined ? null : {
        code: 'PAINLESS_SYNTAX_ERROR',
        error: {
          reason: (_scriptEditorValidati = scriptEditorValidation.message) !== null && _scriptEditorValidati !== void 0 ? _scriptEditorValidati : _i18n.i18n.translate('indexPatternFieldEditor.fieldPreview.error.painlessSyntax', {
            defaultMessage: 'Invalid Painless syntax'
          })
        }
      };
      controller.setPreviewError(error);

      // Make sure to update the lastExecutePainlessRequestParams cache so when the user updates
      // the script and fixes the syntax the "updatePreview()" will run
      // lastExecutePainlessRequestParams.current.script = script?.source;
      controller.setLastExecutePainlessRequestParams({
        script: script === null || script === void 0 ? void 0 : script.source
      });
    } else {
      // Clear possible previous syntax error
      controller.clearPreviewError('PAINLESS_SYNTAX_ERROR');
    }
  }, [scriptEditorValidation, script === null || script === void 0 ? void 0 : script.source, controller]);

  /**
   * Whenever updatePreview() changes (meaning whenever a param changes)
   * we call it to update the preview response with the field(s) value or possible error.
   */
  (0, _useDebounce.default)(updatePreview, 500, [updatePreview]);
  return /*#__PURE__*/_react.default.createElement(fieldPreviewContext.Provider, {
    value: ctx
  }, children);
};
exports.FieldPreviewProvider = FieldPreviewProvider;
const useFieldPreviewContext = () => {
  const ctx = (0, _react.useContext)(fieldPreviewContext);
  if (ctx === undefined) {
    throw new Error('useFieldPreviewContext must be used within a <FieldPreviewProvider />');
  }
  return ctx;
};
exports.useFieldPreviewContext = useFieldPreviewContext;