"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.initializeDashboard = exports.createDashboard = void 0;
var _uuid = require("uuid");
var _rxjs = require("rxjs");
var _lodash = require("lodash");
var _common = require("@kbn/controls-plugin/common");
var _public = require("@kbn/embeddable-plugin/public");
var _public2 = require("@kbn/presentation-util-plugin/public");
var _public3 = require("@kbn/data-plugin/public");
var _dashboard_container = require("../dashboard_container");
var _plugin_services = require("../../../services/plugin_services");
var _sync_dashboard_data_views = require("./data_views/sync_dashboard_data_views");
var _sync_dashboard_unified_search_state = require("./unified_search/sync_dashboard_unified_search_state");
var _place_new_panel_strategies = require("../../component/panel_placement/place_new_panel_strategies");
var _dashboard_constants = require("../../../dashboard_constants");
var _dashboard_control_group_integration = require("./controls/dashboard_control_group_integration");
var _start_dashboard_search_session_integration = require("./search_sessions/start_dashboard_search_session_integration");
/*
 * 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 a new Dashboard from scratch.
 */
const createDashboard = async (creationOptions, dashboardCreationStartTime, savedObjectId) => {
  var _savedObjectResult$da;
  const {
    data: {
      dataViews
    },
    dashboardContentManagement: {
      loadDashboardState
    }
  } = _plugin_services.pluginServices.getServices();

  // --------------------------------------------------------------------------------------
  // Create method which allows work to be done on the dashboard container when it's ready.
  // --------------------------------------------------------------------------------------
  const dashboardContainerReady$ = new _rxjs.Subject();
  const untilDashboardReady = () => new Promise(resolve => {
    const subscription = dashboardContainerReady$.subscribe(container => {
      subscription.unsubscribe();
      resolve(container);
    });
  });

  // --------------------------------------------------------------------------------------
  // Lazy load required systems and Dashboard saved object.
  // --------------------------------------------------------------------------------------
  const reduxEmbeddablePackagePromise = (0, _public2.lazyLoadReduxToolsPackage)();
  const defaultDataViewAssignmentPromise = dataViews.getDefaultDataView();
  const dashboardSavedObjectPromise = loadDashboardState({
    id: savedObjectId
  });
  const [reduxEmbeddablePackage, savedObjectResult, defaultDataView] = await Promise.all([reduxEmbeddablePackagePromise, dashboardSavedObjectPromise, defaultDataViewAssignmentPromise]);
  if (!defaultDataView) {
    throw new Error('Dashboard requires at least one data view before it can be initialized.');
  }

  // --------------------------------------------------------------------------------------
  // Initialize Dashboard integrations
  // --------------------------------------------------------------------------------------
  const initializeResult = await initializeDashboard({
    loadDashboardReturn: savedObjectResult,
    untilDashboardReady,
    creationOptions
  });
  if (!initializeResult) return;
  const {
    input,
    searchSessionId
  } = initializeResult;

  // --------------------------------------------------------------------------------------
  // Build and return the dashboard container.
  // --------------------------------------------------------------------------------------
  const initialComponentState = {
    lastSavedInput: (_savedObjectResult$da = savedObjectResult === null || savedObjectResult === void 0 ? void 0 : savedObjectResult.dashboardInput) !== null && _savedObjectResult$da !== void 0 ? _savedObjectResult$da : {
      ..._dashboard_constants.DEFAULT_DASHBOARD_INPUT,
      id: input.id
    },
    hasRunClientsideMigrations: savedObjectResult.anyMigrationRun,
    isEmbeddedExternally: creationOptions === null || creationOptions === void 0 ? void 0 : creationOptions.isEmbeddedExternally,
    animatePanelTransforms: false,
    // set panel transforms to false initially to avoid panels animating on initial render.
    hasUnsavedChanges: false,
    // if there is initial unsaved changes, the initial diff will catch them.
    managed: savedObjectResult.managed,
    lastSavedId: savedObjectId
  };
  const dashboardContainer = new _dashboard_container.DashboardContainer(input, reduxEmbeddablePackage, searchSessionId, dashboardCreationStartTime, undefined, creationOptions, initialComponentState);
  dashboardContainerReady$.next(dashboardContainer);
  return dashboardContainer;
};

/**
 * Initializes a Dashboard and starts all of its integrations
 */
exports.createDashboard = createDashboard;
const initializeDashboard = async ({
  loadDashboardReturn,
  untilDashboardReady,
  creationOptions,
  controlGroup
}) => {
  var _loadDashboardReturn$, _creationOptions$getI;
  const {
    dashboardBackup,
    embeddable: {
      getEmbeddableFactory
    },
    dashboardCapabilities: {
      showWriteControls
    },
    data: {
      query: queryService,
      search: {
        session
      }
    }
  } = _plugin_services.pluginServices.getServices();
  const {
    queryString,
    filterManager,
    timefilter: {
      timefilter: timefilterService
    }
  } = queryService;
  const {
    getInitialInput,
    searchSessionSettings,
    unifiedSearchSettings,
    validateLoadedSavedObject,
    useControlGroupIntegration,
    useUnifiedSearchIntegration,
    useSessionStorageIntegration
  } = creationOptions !== null && creationOptions !== void 0 ? creationOptions : {};

  // --------------------------------------------------------------------------------------
  // Run validation.
  // --------------------------------------------------------------------------------------
  const validationResult = loadDashboardReturn && (validateLoadedSavedObject === null || validateLoadedSavedObject === void 0 ? void 0 : validateLoadedSavedObject(loadDashboardReturn));
  if (validationResult === 'invalid') {
    // throw error to stop the rest of Dashboard loading and make the factory return an ErrorEmbeddable.
    throw new Error('Dashboard failed saved object result validation');
  } else if (validationResult === 'redirected') {
    return;
  }

  // --------------------------------------------------------------------------------------
  // Gather input from session storage and local storage if integration is used.
  // --------------------------------------------------------------------------------------
  const sessionStorageInput = (() => {
    if (!useSessionStorageIntegration) return;
    return dashboardBackup.getState(loadDashboardReturn.dashboardId);
  })();

  // --------------------------------------------------------------------------------------
  // Combine input from saved object, session storage, & passed input to create initial input.
  // --------------------------------------------------------------------------------------
  const initialViewMode = (() => {
    if (loadDashboardReturn.managed || !showWriteControls) return _public.ViewMode.VIEW;
    if (loadDashboardReturn.newDashboardCreated || dashboardBackup.dashboardHasUnsavedEdits(loadDashboardReturn.dashboardId)) {
      return _public.ViewMode.EDIT;
    }
    return dashboardBackup.getViewMode();
  })();
  const overrideInput = getInitialInput === null || getInitialInput === void 0 ? void 0 : getInitialInput();
  const initialInput = (0, _lodash.cloneDeep)({
    ..._dashboard_constants.DEFAULT_DASHBOARD_INPUT,
    ...((_loadDashboardReturn$ = loadDashboardReturn === null || loadDashboardReturn === void 0 ? void 0 : loadDashboardReturn.dashboardInput) !== null && _loadDashboardReturn$ !== void 0 ? _loadDashboardReturn$ : {}),
    ...sessionStorageInput,
    ...(initialViewMode ? {
      viewMode: initialViewMode
    } : {}),
    ...overrideInput
  });

  // Back up any view mode passed in explicitly.
  if (overrideInput !== null && overrideInput !== void 0 && overrideInput.viewMode) {
    dashboardBackup.storeViewMode(overrideInput === null || overrideInput === void 0 ? void 0 : overrideInput.viewMode);
  }
  initialInput.executionContext = {
    type: 'dashboard',
    description: initialInput.title
  };

  // --------------------------------------------------------------------------------------
  // Set up unified search integration.
  // --------------------------------------------------------------------------------------
  if (useUnifiedSearchIntegration && unifiedSearchSettings !== null && unifiedSearchSettings !== void 0 && unifiedSearchSettings.kbnUrlStateStorage) {
    const {
      query,
      filters,
      timeRestore,
      timeRange: savedTimeRange,
      refreshInterval: savedRefreshInterval
    } = initialInput;
    const {
      kbnUrlStateStorage
    } = unifiedSearchSettings;

    // apply filters and query to the query service
    filterManager.setAppFilters((0, _lodash.cloneDeep)(filters !== null && filters !== void 0 ? filters : []));
    queryString.setQuery(query !== null && query !== void 0 ? query : queryString.getDefaultQuery());

    /**
     * Get initial time range, and set up dashboard time restore if applicable
     */
    const initialTimeRange = (_kbnUrlStateStorage$g => {
      // if there is an explicit time range in the URL it always takes precedence.
      const urlOverrideTimeRange = (_kbnUrlStateStorage$g = kbnUrlStateStorage.get(_dashboard_constants.GLOBAL_STATE_STORAGE_KEY)) === null || _kbnUrlStateStorage$g === void 0 ? void 0 : _kbnUrlStateStorage$g.time;
      if (urlOverrideTimeRange) return urlOverrideTimeRange;

      // if this Dashboard has timeRestore return the time range that was saved with the dashboard.
      if (timeRestore && savedTimeRange) return savedTimeRange;

      // otherwise fall back to the time range from the timefilterService.
      return timefilterService.getTime();
    })();
    initialInput.timeRange = initialTimeRange;
    if (timeRestore) {
      if (savedTimeRange) timefilterService.setTime(savedTimeRange);
      if (savedRefreshInterval) timefilterService.setRefreshInterval(savedRefreshInterval);
    }

    // start syncing global query state with the URL.
    const {
      stop: stopSyncingQueryServiceStateWithUrl
    } = (0, _public3.syncGlobalQueryStateWithUrl)(queryService, kbnUrlStateStorage);
    untilDashboardReady().then(dashboardContainer => {
      const stopSyncingUnifiedSearchState = _sync_dashboard_unified_search_state.syncUnifiedSearchState.bind(dashboardContainer)(kbnUrlStateStorage);
      dashboardContainer.stopSyncingWithUnifiedSearch = () => {
        stopSyncingUnifiedSearchState();
        stopSyncingQueryServiceStateWithUrl();
      };
    });
  }

  // --------------------------------------------------------------------------------------
  // Place the incoming embeddable if there is one
  // --------------------------------------------------------------------------------------
  const incomingEmbeddable = creationOptions === null || creationOptions === void 0 ? void 0 : (_creationOptions$getI = creationOptions.getIncomingEmbeddable) === null || _creationOptions$getI === void 0 ? void 0 : _creationOptions$getI.call(creationOptions);
  if (incomingEmbeddable) {
    const scrolltoIncomingEmbeddable = (container, id) => {
      container.setScrollToPanelId(id);
      container.setHighlightPanelId(id);
    };
    initialInput.viewMode = _public.ViewMode.EDIT; // view mode must always be edit to recieve an embeddable.
    if (incomingEmbeddable.embeddableId && Boolean(initialInput.panels[incomingEmbeddable.embeddableId])) {
      // this embeddable already exists, we will update the explicit input.
      const panelToUpdate = initialInput.panels[incomingEmbeddable.embeddableId];
      const sameType = panelToUpdate.type === incomingEmbeddable.type;
      panelToUpdate.type = incomingEmbeddable.type;
      panelToUpdate.explicitInput = {
        // if the incoming panel is the same type as what was there before we can safely spread the old panel's explicit input
        ...(sameType ? panelToUpdate.explicitInput : {}),
        ...incomingEmbeddable.input,
        id: incomingEmbeddable.embeddableId,
        // maintain hide panel titles setting.
        hidePanelTitles: panelToUpdate.explicitInput.hidePanelTitles
      };
      untilDashboardReady().then(container => scrolltoIncomingEmbeddable(container, incomingEmbeddable.embeddableId));
    } else {
      // otherwise this incoming embeddable is brand new and can be added after the dashboard container is created.

      untilDashboardReady().then(async container => {
        const createdEmbeddable = await (async _incomingEmbeddable$e => {
          // if there is no width or height we can add the panel using the default behaviour.
          if (!incomingEmbeddable.size) {
            return await container.addNewEmbeddable(incomingEmbeddable.type, incomingEmbeddable.input);
          }

          // if the incoming embeddable has an explicit width or height we add the panel to the grid directly.
          const {
            width,
            height
          } = incomingEmbeddable.size;
          const currentPanels = container.getInput().panels;
          const embeddableId = (_incomingEmbeddable$e = incomingEmbeddable.embeddableId) !== null && _incomingEmbeddable$e !== void 0 ? _incomingEmbeddable$e : (0, _uuid.v4)();
          const {
            findTopLeftMostOpenSpace
          } = _place_new_panel_strategies.panelPlacementStrategies;
          const {
            newPanelPlacement
          } = findTopLeftMostOpenSpace({
            width: width !== null && width !== void 0 ? width : _dashboard_constants.DEFAULT_PANEL_WIDTH,
            height: height !== null && height !== void 0 ? height : _dashboard_constants.DEFAULT_PANEL_HEIGHT,
            currentPanels
          });
          const newPanelState = {
            explicitInput: {
              ...incomingEmbeddable.input,
              id: embeddableId
            },
            type: incomingEmbeddable.type,
            gridData: {
              ...newPanelPlacement,
              i: embeddableId
            }
          };
          container.updateInput({
            panels: {
              ...container.getInput().panels,
              [newPanelState.explicitInput.id]: newPanelState
            }
          });
          return await container.untilEmbeddableLoaded(embeddableId);
        })();
        scrolltoIncomingEmbeddable(container, createdEmbeddable.id);
      });
    }
  }

  // --------------------------------------------------------------------------------------
  // Set up search sessions integration.
  // --------------------------------------------------------------------------------------
  let initialSearchSessionId;
  if (searchSessionSettings) {
    const {
      sessionIdToRestore
    } = searchSessionSettings;

    // if this incoming embeddable has a session, continue it.
    if (incomingEmbeddable !== null && incomingEmbeddable !== void 0 && incomingEmbeddable.searchSessionId) {
      session.continue(incomingEmbeddable.searchSessionId);
    }
    if (sessionIdToRestore) {
      session.restore(sessionIdToRestore);
    }
    const existingSession = session.getSessionId();
    initialSearchSessionId = sessionIdToRestore !== null && sessionIdToRestore !== void 0 ? sessionIdToRestore : existingSession && incomingEmbeddable ? existingSession : session.start();
    untilDashboardReady().then(container => {
      _start_dashboard_search_session_integration.startDashboardSearchSessionIntegration.bind(container)(creationOptions === null || creationOptions === void 0 ? void 0 : creationOptions.searchSessionSettings);
    });
  }

  // --------------------------------------------------------------------------------------
  // Start the control group integration.
  // --------------------------------------------------------------------------------------
  if (useControlGroupIntegration) {
    const controlsGroupFactory = getEmbeddableFactory(_common.CONTROL_GROUP_TYPE);
    const {
      filters,
      query,
      timeRange,
      viewMode,
      controlGroupInput,
      id
    } = initialInput;
    const fullControlGroupInput = {
      id: `control_group_${id !== null && id !== void 0 ? id : 'new_dashboard'}`,
      ...(0, _common.getDefaultControlGroupInput)(),
      ...(0, _lodash.pickBy)(controlGroupInput, _lodash.identity),
      // undefined keys in initialInput should not overwrite defaults
      timeRange,
      viewMode,
      filters,
      query
    };
    if (controlGroup) {
      controlGroup.updateInputAndReinitialize(fullControlGroupInput);
    } else {
      const newControlGroup = await (controlsGroupFactory === null || controlsGroupFactory === void 0 ? void 0 : controlsGroupFactory.create(fullControlGroupInput));
      if (!newControlGroup || (0, _public.isErrorEmbeddable)(newControlGroup)) {
        throw new Error('Error in control group startup');
      }
      controlGroup = newControlGroup;
    }
    untilDashboardReady().then(dashboardContainer => {
      dashboardContainer.controlGroup = controlGroup;
      _dashboard_control_group_integration.startSyncingDashboardControlGroup.bind(dashboardContainer)();
    });
    await controlGroup.untilInitialized();
  }

  // --------------------------------------------------------------------------------------
  // Start the data views integration.
  // --------------------------------------------------------------------------------------
  untilDashboardReady().then(dashboardContainer => {
    dashboardContainer.integrationSubscriptions.add(_sync_dashboard_data_views.startSyncingDashboardDataViews.bind(dashboardContainer)());
  });

  // --------------------------------------------------------------------------------------
  // Start animating panel transforms 500 ms after dashboard is created.
  // --------------------------------------------------------------------------------------
  untilDashboardReady().then(dashboard => setTimeout(() => dashboard.dispatch.setAnimatePanelTransforms(true), 500));
  return {
    input: initialInput,
    searchSessionId: initialSearchSessionId
  };
};
exports.initializeDashboard = initializeDashboard;