"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.createSearchSessionRestorationDataProvider = createSearchSessionRestorationDataProvider;
exports.getDiscoverStateContainer = getDiscoverStateContainer;
var _i18n = require("@kbn/i18n");
var _public = require("@kbn/kibana-utils-plugin/public");
var _public2 = require("@kbn/data-plugin/public");
var _public3 = require("@kbn/data-views-plugin/public");
var _uuid = require("uuid");
var _rxjs = require("rxjs");
var _load_saved_search = require("./load_saved_search");
var _restore_from_saved_search = require("../../../services/saved_searches/restore_from_saved_search");
var _types = require("../../types");
var _change_data_view = require("../hooks/utils/change_data_view");
var _build_state_subscribe = require("../hooks/utils/build_state_subscribe");
var _add_log = require("../../../utils/add_log");
var _kibana_services = require("../../../kibana_services");
var _discover_data_state_container = require("./discover_data_state_container");
var _discover_search_session = require("./discover_search_session");
var _common = require("../../../../common");
var _discover_app_state_container = require("./discover_app_state_container");
var _discover_internal_state_container = require("./discover_internal_state_container");
var _discover_saved_search_container = require("./discover_saved_search_container");
var _update_filter_references = require("../utils/update_filter_references");
var _discover_global_state_container = require("./discover_global_state_container");
/*
 * 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.
 */

/**
 * Builds and returns appState and globalState containers and helper functions
 * Used to sync URL with UI state
 */
function getDiscoverStateContainer({
  history,
  services,
  mode = 'standalone'
}) {
  const storeInSessionStorage = services.uiSettings.get('state:storeInSessionStorage');
  const toasts = services.core.notifications.toasts;

  /**
   * state storage for state in the URL
   */
  const stateStorage = (0, _public.createKbnUrlStateStorage)({
    useHash: storeInSessionStorage,
    history,
    useHashQuery: mode !== 'embedded',
    ...(toasts && (0, _public.withNotifyOnErrors)(toasts))
  });

  /**
   * Search session logic
   */
  const searchSessionManager = new _discover_search_session.DiscoverSearchSessionManager({
    history,
    session: services.data.search.session
  });

  /**
   * Global State Container, synced with the _g part URL
   */
  const globalStateContainer = (0, _discover_global_state_container.getDiscoverGlobalStateContainer)(stateStorage);

  /**
   * Saved Search State Container, the persisted saved object of Discover
   */
  const savedSearchContainer = (0, _discover_saved_search_container.getSavedSearchContainer)({
    services,
    globalStateContainer
  });

  /**
   * App State Container, synced with the _a part URL
   */
  const appStateContainer = (0, _discover_app_state_container.getDiscoverAppStateContainer)({
    stateStorage,
    savedSearch: savedSearchContainer.getState(),
    services
  });

  /**
   * Internal State Container, state that's not persisted and not part of the URL
   */
  const internalStateContainer = (0, _discover_internal_state_container.getInternalStateContainer)();
  const pauseAutoRefreshInterval = async dataView => {
    if (dataView && (!dataView.isTimeBased() || dataView.type === _public3.DataViewType.ROLLUP)) {
      const state = globalStateContainer.get();
      if (state !== null && state !== void 0 && state.refreshInterval && !state.refreshInterval.pause) {
        await globalStateContainer.set({
          ...state,
          refreshInterval: {
            ...(state === null || state === void 0 ? void 0 : state.refreshInterval),
            pause: true
          }
        });
      }
    }
  };
  const setDataView = dataView => {
    internalStateContainer.transitions.setDataView(dataView);
    pauseAutoRefreshInterval(dataView);
    savedSearchContainer.getState().searchSource.setField('index', dataView);
  };
  const dataStateContainer = (0, _discover_data_state_container.getDataStateContainer)({
    services,
    searchSessionManager,
    getAppState: appStateContainer.getState,
    getInternalState: internalStateContainer.getState,
    getSavedSearch: savedSearchContainer.getState,
    setDataView
  });
  const loadDataViewList = async () => {
    const dataViewList = await services.dataViews.getIdsWithTitle(true);
    internalStateContainer.transitions.setSavedDataViews(dataViewList);
  };

  /**
   * When saving a saved search with an ad hoc data view, a new id needs to be generated for the data view
   * This is to prevent duplicate ids messing with our system
   */
  const updateAdHocDataViewId = async () => {
    const prevDataView = internalStateContainer.getState().dataView;
    if (!prevDataView || prevDataView.isPersisted()) return;
    const newDataView = await services.dataViews.create({
      ...prevDataView.toSpec(),
      id: (0, _uuid.v4)()
    });
    services.dataViews.clearInstanceCache(prevDataView.id);
    (0, _update_filter_references.updateFiltersReferences)(prevDataView, newDataView);
    internalStateContainer.transitions.replaceAdHocDataViewWithId(prevDataView.id, newDataView);
    await appStateContainer.replaceUrlState({
      index: newDataView.id
    });
    const trackingEnabled = Boolean(newDataView.isPersisted() || savedSearchContainer.getId());
    (0, _kibana_services.getUrlTracker)().setTrackingEnabled(trackingEnabled);
    return newDataView;
  };
  const onOpenSavedSearch = async newSavedSearchId => {
    (0, _add_log.addLog)('[discoverState] onOpenSavedSearch', newSavedSearchId);
    const currentSavedSearch = savedSearchContainer.getState();
    if (currentSavedSearch.id && currentSavedSearch.id === newSavedSearchId) {
      (0, _add_log.addLog)('[discoverState] undo changes since saved search did not change');
      await undoSavedSearchChanges();
    } else {
      (0, _add_log.addLog)('[discoverState] onOpenSavedSearch open view URL');
      services.locator.navigate({
        savedSearchId: newSavedSearchId
      });
    }
  };
  const onDataViewCreated = async nextDataView => {
    if (!nextDataView.isPersisted()) {
      internalStateContainer.transitions.appendAdHocDataViews(nextDataView);
    } else {
      await loadDataViewList();
    }
    if (nextDataView.id) {
      await onChangeDataView(nextDataView);
    }
  };
  const onDataViewEdited = async editedDataView => {
    if (editedDataView.isPersisted()) {
      // Clear the current data view from the cache and create a new instance
      // of it, ensuring we have a new object reference to trigger a re-render
      services.dataViews.clearInstanceCache(editedDataView.id);
      setDataView(await services.dataViews.create(editedDataView.toSpec(), true));
    } else {
      await updateAdHocDataViewId();
    }
    loadDataViewList();
    (0, _add_log.addLog)('[getDiscoverStateContainer] onDataViewEdited triggers data fetching');
    fetchData();
  };
  const loadSavedSearch = async params => {
    return (0, _load_saved_search.loadSavedSearch)(params !== null && params !== void 0 ? params : {}, {
      appStateContainer,
      dataStateContainer,
      internalStateContainer,
      savedSearchContainer,
      services,
      setDataView
    });
  };

  /**
   * state containers initializing and subscribing to changes triggering e.g. data fetching
   */
  const initializeAndSync = () => {
    // initialize app state container, syncing with _g and _a part of the URL
    const appStateInitAndSyncUnsubscribe = appStateContainer.initAndSync(savedSearchContainer.getState());
    // subscribing to state changes of appStateContainer, triggering data fetching
    const appStateUnsubscribe = appStateContainer.subscribe((0, _build_state_subscribe.buildStateSubscribe)({
      appState: appStateContainer,
      savedSearchState: savedSearchContainer,
      dataState: dataStateContainer,
      internalState: internalStateContainer,
      services,
      setDataView
    }));
    // start subscribing to dataStateContainer, triggering data fetching
    const unsubscribeData = dataStateContainer.subscribe();

    // updates saved search when query or filters change, triggers data fetching
    const filterUnsubscribe = (0, _rxjs.merge)(services.filterManager.getFetches$()).subscribe(() => {
      savedSearchContainer.update({
        nextDataView: internalStateContainer.getState().dataView,
        nextState: appStateContainer.getState(),
        useFilterAndQueryServices: true
      });
      (0, _add_log.addLog)('[getDiscoverStateContainer] filter changes triggers data fetching');
      fetchData();
    });
    services.data.search.session.enableStorage(createSearchSessionRestorationDataProvider({
      appStateContainer,
      data: services.data,
      getSavedSearch: () => savedSearchContainer.getState()
    }), {
      isDisabled: () => services.capabilities.discover.storeSearchSession ? {
        disabled: false
      } : {
        disabled: true,
        reasonText: _public2.noSearchSessionStorageCapabilityMessage
      }
    });
    return () => {
      unsubscribeData();
      appStateUnsubscribe();
      appStateInitAndSyncUnsubscribe();
      filterUnsubscribe.unsubscribe();
    };
  };
  const createAndAppendAdHocDataView = async dataViewSpec => {
    var _newDataView$fields$g;
    const newDataView = await services.dataViews.create(dataViewSpec);
    if (((_newDataView$fields$g = newDataView.fields.getByName('@timestamp')) === null || _newDataView$fields$g === void 0 ? void 0 : _newDataView$fields$g.type) === 'date') {
      newDataView.timeFieldName = '@timestamp';
    }
    internalStateContainer.transitions.appendAdHocDataViews(newDataView);
    await onChangeDataView(newDataView);
    return newDataView;
  };
  /**
   * Triggered when a user submits a query in the search bar
   */
  const onUpdateQuery = (payload, isUpdate) => {
    if (isUpdate === false) {
      // remove the search session if the given query is not just updated
      searchSessionManager.removeSearchSessionIdFromURL({
        replace: false
      });
      (0, _add_log.addLog)('[getDiscoverStateContainer] onUpdateQuery triggers data fetching');
      dataStateContainer.fetch();
    }
  };

  /**
   * Function e.g. triggered when user changes data view in the sidebar
   */
  const onChangeDataView = async id => {
    await (0, _change_data_view.changeDataView)(id, {
      services,
      internalState: internalStateContainer,
      appState: appStateContainer
    });
  };
  /**
   * Undo all changes to the current saved search
   */
  const undoSavedSearchChanges = async () => {
    (0, _add_log.addLog)('undoSavedSearchChanges');
    const nextSavedSearch = savedSearchContainer.getInitial$().getValue();
    savedSearchContainer.set(nextSavedSearch);
    (0, _restore_from_saved_search.restoreStateFromSavedSearch)({
      savedSearch: nextSavedSearch,
      timefilter: services.timefilter
    });
    const newAppState = (0, _discover_saved_search_container.getDefaultAppState)(nextSavedSearch, services);
    await appStateContainer.replaceUrlState(newAppState);
    return nextSavedSearch;
  };
  const fetchData = (initial = false) => {
    (0, _add_log.addLog)('fetchData', {
      initial
    });
    if (!initial || dataStateContainer.getInitialFetchStatus() === _types.FetchStatus.LOADING) {
      dataStateContainer.fetch();
    }
  };
  return {
    globalState: globalStateContainer,
    appState: appStateContainer,
    internalState: internalStateContainer,
    dataState: dataStateContainer,
    savedSearchState: savedSearchContainer,
    stateStorage,
    searchSessionManager,
    actions: {
      initializeAndSync,
      fetchData,
      loadDataViewList,
      loadSavedSearch,
      onChangeDataView,
      createAndAppendAdHocDataView,
      onDataViewCreated,
      onDataViewEdited,
      onOpenSavedSearch,
      onUpdateQuery,
      setDataView,
      undoSavedSearchChanges,
      updateAdHocDataViewId
    }
  };
}
function createSearchSessionRestorationDataProvider(deps) {
  const getSavedSearch = () => deps.getSavedSearch();
  return {
    getName: async () => {
      const savedSearch = deps.getSavedSearch();
      return savedSearch.id && savedSearch.title || _i18n.i18n.translate('discover.discoverDefaultSearchSessionName', {
        defaultMessage: 'Discover'
      });
    },
    getLocatorData: async () => {
      return {
        id: _common.DISCOVER_APP_LOCATOR,
        initialState: createUrlGeneratorState({
          ...deps,
          getSavedSearch,
          shouldRestoreSearchSession: false
        }),
        restoreState: createUrlGeneratorState({
          ...deps,
          getSavedSearch,
          shouldRestoreSearchSession: true
        })
      };
    }
  };
}
function createUrlGeneratorState({
  appStateContainer,
  data,
  getSavedSearch,
  shouldRestoreSearchSession
}) {
  const appState = appStateContainer.get();
  const dataView = getSavedSearch().searchSource.getField('index');
  return {
    filters: data.query.filterManager.getFilters(),
    dataViewId: appState.index,
    query: appState.query,
    savedSearchId: getSavedSearch().id,
    timeRange: shouldRestoreSearchSession ? data.query.timefilter.timefilter.getAbsoluteTime() : data.query.timefilter.timefilter.getTime(),
    searchSessionId: shouldRestoreSearchSession ? data.search.session.getSessionId() : undefined,
    columns: appState.columns,
    sort: appState.sort,
    savedQuery: appState.savedQuery,
    interval: appState.interval,
    refreshInterval: shouldRestoreSearchSession ? {
      pause: true,
      // force pause refresh interval when restoring a session
      value: 0
    } : undefined,
    useHash: false,
    viewMode: appState.viewMode,
    hideAggregatedPreview: appState.hideAggregatedPreview,
    breakdownField: appState.breakdownField,
    dataViewSpec: !(dataView !== null && dataView !== void 0 && dataView.isPersisted()) ? dataView === null || dataView === void 0 ? void 0 : dataView.toMinimalSpec() : undefined
  };
}