"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.useLatencyCorrelations = useLatencyCorrelations;
var _react = require("react");
var _lodash = require("lodash");
var _constants = require("../../../../common/correlations/constants");
var _utils = require("../../../../common/correlations/utils");
var _latency_distribution_chart_types = require("../../../../common/latency_distribution_chart_types");
var _create_call_apm_api = require("../../../services/rest/create_call_apm_api");
var _analysis_hook_utils = require("./utils/analysis_hook_utils");
var _use_fetch_params = require("./use_fetch_params");
/*
 * 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; you may not use this file except in compliance with the Elastic License
 * 2.0.
 */

// Overall progress is a float from 0 to 1.
const LOADED_OVERALL_HISTOGRAM = 0.05;
const LOADED_FIELD_CANDIDATES = LOADED_OVERALL_HISTOGRAM + 0.05;
const LOADED_FIELD_VALUE_PAIRS = LOADED_FIELD_CANDIDATES + 0.3;
const LOADED_DONE = 1;
const PROGRESS_STEP_FIELD_VALUE_PAIRS = 0.3;
const PROGRESS_STEP_CORRELATIONS = 0.6;
function useLatencyCorrelations() {
  const fetchParams = (0, _use_fetch_params.useFetchParams)();

  // This use of useReducer (the dispatch function won't get reinstantiated
  // on every update) and debounce avoids flooding consuming components with updates.
  // `setResponse.flush()` can be used to enforce an update.
  const [response, setResponseUnDebounced] = (0, _react.useReducer)((0, _analysis_hook_utils.getReducer)(), (0, _analysis_hook_utils.getInitialResponse)());
  const setResponse = (0, _react.useMemo)(() => (0, _lodash.debounce)(setResponseUnDebounced, _constants.DEBOUNCE_INTERVAL), []);
  const abortCtrl = (0, _react.useRef)(new AbortController());
  const startFetch = (0, _react.useCallback)(async () => {
    abortCtrl.current.abort();
    abortCtrl.current = new AbortController();
    setResponse({
      ...(0, _analysis_hook_utils.getInitialResponse)(),
      isRunning: true,
      // explicitly set these to undefined to override a possible previous state.
      error: undefined,
      latencyCorrelations: undefined,
      percentileThresholdValue: undefined,
      overallHistogram: undefined,
      totalDocCount: undefined
    });
    setResponse.flush();
    try {
      // `responseUpdate` will be enriched with additional data with subsequent
      // calls to the overall histogram, field candidates, field value pairs, correlation results
      // and histogram data for statistically significant results.
      const responseUpdate = {
        ccsWarning: false
      };

      // Initial call to fetch the overall distribution for the log-log plot.
      const {
        overallHistogram,
        totalDocCount,
        percentileThresholdValue,
        durationMin,
        durationMax
      } = await (0, _create_call_apm_api.callApmApi)('POST /internal/apm/latency/overall_distribution/transactions', {
        signal: abortCtrl.current.signal,
        params: {
          body: {
            ...fetchParams,
            percentileThreshold: _constants.DEFAULT_PERCENTILE_THRESHOLD,
            chartType: _latency_distribution_chart_types.LatencyDistributionChartType.latencyCorrelations
          }
        }
      });
      responseUpdate.overallHistogram = overallHistogram;
      responseUpdate.totalDocCount = totalDocCount;
      responseUpdate.percentileThresholdValue = percentileThresholdValue;
      if (abortCtrl.current.signal.aborted) {
        return;
      }
      setResponse({
        ...responseUpdate,
        loaded: LOADED_OVERALL_HISTOGRAM
      });
      setResponse.flush();
      const {
        fieldCandidates
      } = await (0, _create_call_apm_api.callApmApi)('GET /internal/apm/correlations/field_candidates/transactions', {
        signal: abortCtrl.current.signal,
        params: {
          query: fetchParams
        }
      });
      if (abortCtrl.current.signal.aborted) {
        return;
      }
      setResponse({
        loaded: LOADED_FIELD_CANDIDATES
      });
      setResponse.flush();
      const chunkSize = 10;
      let chunkLoadCounter = 0;
      const fieldValuePairs = [];
      const fieldCandidateChunks = (0, _lodash.chunk)(fieldCandidates, chunkSize);
      for (const fieldCandidateChunk of fieldCandidateChunks) {
        const fieldValuePairChunkResponse = await (0, _create_call_apm_api.callApmApi)('POST /internal/apm/correlations/field_value_pairs/transactions', {
          signal: abortCtrl.current.signal,
          params: {
            body: {
              ...fetchParams,
              fieldCandidates: fieldCandidateChunk
            }
          }
        });
        if (fieldValuePairChunkResponse.fieldValuePairs.length > 0) {
          fieldValuePairs.push(...fieldValuePairChunkResponse.fieldValuePairs);
        }
        if (abortCtrl.current.signal.aborted) {
          return;
        }
        chunkLoadCounter++;
        setResponse({
          loaded: LOADED_FIELD_CANDIDATES + chunkLoadCounter / fieldCandidateChunks.length * PROGRESS_STEP_FIELD_VALUE_PAIRS
        });
      }
      if (abortCtrl.current.signal.aborted) {
        return;
      }
      setResponse.flush();
      chunkLoadCounter = 0;
      const fieldsToSample = new Set();
      const latencyCorrelations = [];
      const fieldValuePairChunks = (0, _lodash.chunk)((0, _utils.getPrioritizedFieldValuePairs)(fieldValuePairs), chunkSize);
      const fallbackResults = [];
      for (const fieldValuePairChunk of fieldValuePairChunks) {
        const significantCorrelations = await (0, _create_call_apm_api.callApmApi)('POST /internal/apm/correlations/significant_correlations/transactions', {
          signal: abortCtrl.current.signal,
          params: {
            body: {
              ...fetchParams,
              durationMin,
              durationMax,
              fieldValuePairs: fieldValuePairChunk
            }
          }
        });
        if (significantCorrelations.latencyCorrelations.length > 0) {
          significantCorrelations.latencyCorrelations.forEach(d => {
            fieldsToSample.add(d.fieldName);
          });
          latencyCorrelations.push(...significantCorrelations.latencyCorrelations);
          responseUpdate.latencyCorrelations = (0, _analysis_hook_utils.getLatencyCorrelationsSortedByCorrelation)([...latencyCorrelations]);
        } else {
          // If there's no correlation results that matches the criteria
          // Consider the fallback results
          if (significantCorrelations.fallbackResult) {
            fallbackResults.push(significantCorrelations.fallbackResult);
          }
        }
        chunkLoadCounter++;
        setResponse({
          ...responseUpdate,
          loaded: LOADED_FIELD_VALUE_PAIRS + chunkLoadCounter / fieldValuePairChunks.length * PROGRESS_STEP_CORRELATIONS
        });
        if (abortCtrl.current.signal.aborted) {
          return;
        }
      }
      if (latencyCorrelations.length === 0 && fallbackResults.length > 0) {
        // Rank the fallback results and show at least one value
        const sortedFallbackResults = fallbackResults.filter(r => r.correlation > 0).sort((a, b) => b.correlation - a.correlation);
        responseUpdate.latencyCorrelations = sortedFallbackResults.slice(0, 1).map(r => ({
          ...r,
          isFallbackResult: true
        }));
        setResponse({
          ...responseUpdate,
          loaded: LOADED_FIELD_VALUE_PAIRS + chunkLoadCounter / fieldValuePairChunks.length * PROGRESS_STEP_CORRELATIONS
        });
      }
      setResponse.flush();
      setResponse({
        ...responseUpdate,
        loaded: LOADED_DONE,
        isRunning: false
      });
      setResponse.flush();
    } catch (e) {
      if (!abortCtrl.current.signal.aborted) {
        var _err$body$message, _err$body, _err$response;
        const err = e;
        setResponse({
          error: 'response' in err ? (_err$body$message = (_err$body = err.body) === null || _err$body === void 0 ? void 0 : _err$body.message) !== null && _err$body$message !== void 0 ? _err$body$message : (_err$response = err.response) === null || _err$response === void 0 ? void 0 : _err$response.statusText : err.message,
          isRunning: false
        });
        setResponse.flush();
      }
    }
  }, [fetchParams, setResponse]);
  const cancelFetch = (0, _react.useCallback)(() => {
    abortCtrl.current.abort();
    setResponse({
      isRunning: false
    });
    setResponse.flush();
  }, [setResponse]);

  // auto-update
  (0, _react.useEffect)(() => {
    startFetch();
    return () => {
      abortCtrl.current.abort();
    };
  }, [startFetch, cancelFetch]);
  const {
    error,
    loaded,
    isRunning,
    ...returnedResponse
  } = response;
  const progress = (0, _react.useMemo)(() => ({
    error,
    loaded: Math.round(loaded * 100) / 100,
    isRunning
  }), [error, loaded, isRunning]);
  return {
    progress,
    response: returnedResponse,
    startFetch,
    cancelFetch
  };
}