import _defineProperty from "@babel/runtime/helpers/defineProperty";
import React from 'react';
import PropTypes from 'prop-types';
import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '../analytics';
import { createDispatch } from '../event-dispatcher';
import { analyticsEventKey, startMeasure, stopMeasure } from '../utils';
const DEFAULT_SAMPLING_RATE = 100;
const DEFAULT_SLOW_THRESHOLD = 4;

// That context was exctract from the old WithPluginState from editor-core
// It was using some private types from
// - EditorAction: packages/editor/editor-core/src/actions/index.ts
// - EditorSharedConfig: packages/editor/editor-core/src/labs/next/internal/context/shared-config.tsx

/**
 * @private
 * @deprecated
 *
 * Using this component is deprecated. It should be replaced with `useSharedPluginState`.
 * This requires having access to the injection API from the plugin itself.
 *
 * An example of the refactor with the new hook (using hyperlink as an example) is:
 *
 * Before:
 * ```ts
 * <WithPluginState
 *   editorView={editorView}
 *   plugins={{
 *     hyperlinkState: hyperlinkPluginKey
 *   }}
 *   render={({ hyperlinkState }) =>
 *     renderComponent({ hyperlinkState })
 *   }
 * />
 * ```
 *
 * After:
 * ```ts
 * import { useSharedPluginState } from '@atlaskit/editor-common/hooks';
 * import type { ExtractInjectionAPI } from '@atlaskit/editor-common/types';
 *
 * function ComponentWithState(
 *   api: ExtractInjectionAPI<typeof hyperlinkPlugin> | undefined
 * ) {
 *   const { hyperlinkState } = useSharedPluginState(api, ['hyperlink']);
 *   return renderComponent({ hyperlinkState })
 * }
 * ```
 *
 */
class WithPluginState extends React.Component {
  constructor(props, context) {
    super(props, context);
    _defineProperty(this, "listeners", {});
    _defineProperty(this, "debounce", null);
    _defineProperty(this, "notAppliedState", {});
    _defineProperty(this, "isSubscribed", false);
    _defineProperty(this, "callsCount", 0);
    _defineProperty(this, "handlePluginStateChange", (propName, pluginName, performanceOptions, skipEqualityCheck) => pluginState => {
      // skipEqualityCheck is being used for old plugins since they are mutating plugin state instead of creating a new one
      if (this.state[propName] !== pluginState || skipEqualityCheck) {
        this.updateState({
          stateSubset: {
            [propName]: pluginState
          },
          pluginName,
          performanceOptions
        });
      }
    });
    /**
     * Debounces setState calls in order to reduce number of re-renders caused by several plugin state changes.
     */
    _defineProperty(this, "updateState", ({
      stateSubset,
      pluginName,
      performanceOptions
    }) => {
      this.notAppliedState = {
        ...this.notAppliedState,
        ...stateSubset
      };
      if (this.debounce) {
        window.clearTimeout(this.debounce);
      }
      const debounce = this.props.debounce !== false ? fn => window.setTimeout(fn, 0) : fn => fn();
      this.debounce = debounce(() => {
        const measure = `🦉${pluginName}::WithPluginState`;
        performanceOptions.trackingEnabled && startMeasure(measure);
        this.setState(this.notAppliedState, () => {
          performanceOptions.trackingEnabled && stopMeasure(measure, duration => {
            // Each WithPluginState component will fire analytics event no more than once every `samplingLimit` times
            if (++this.callsCount % performanceOptions.samplingRate === 0 && duration > performanceOptions.slowThreshold) {
              this.dispatchAnalyticsEvent({
                action: ACTION.WITH_PLUGIN_STATE_CALLED,
                actionSubject: ACTION_SUBJECT.EDITOR,
                eventType: EVENT_TYPE.OPERATIONAL,
                attributes: {
                  plugin: pluginName,
                  duration
                }
              });
            }
          });
        });
        this.debounce = null;
        this.notAppliedState = {};
      });
    });
    _defineProperty(this, "dispatchAnalyticsEvent", payload => {
      const eventDispatcher = this.getEventDispatcher();
      if (eventDispatcher) {
        const dispatch = createDispatch(eventDispatcher);
        dispatch(analyticsEventKey, {
          payload
        });
      }
    });
    _defineProperty(this, "onContextUpdate", () => {
      this.subscribe(this.props);
    });
    this.state = this.getPluginsStates(this.props.plugins, this.getEditorView(props, context));
  }
  getEditorView(maybeProps, maybeContext) {
    const props = maybeProps || this.props;
    const context = maybeContext || this.context;
    return props.editorView || context && context.editorActions && context.editorActions._privateGetEditorView() || context && context.editorSharedConfig && context.editorSharedConfig.editorView;
  }
  getEventDispatcher(maybeProps) {
    const props = maybeProps || this.props;
    return props.eventDispatcher || this.context && this.context.editorActions && this.context.editorActions._privateGetEventDispatcher() || this.context && this.context.editorSharedConfig && this.context.editorSharedConfig.eventDispatcher;
  }
  getPluginsStates(plugins, editorView) {
    if (!editorView || !plugins) {
      return {};
    }
    const keys = Object.keys(plugins);
    return keys.reduce((acc, propName) => {
      const pluginKey = plugins[propName];
      if (!pluginKey) {
        return acc;
      }
      acc[propName] = pluginKey.getState(editorView.state);
      return acc;
    }, {});
  }
  subscribe(props) {
    var _uiTracking$samplingR, _uiTracking$slowThres;
    const plugins = props.plugins;
    const eventDispatcher = this.getEventDispatcher(props);
    const editorView = this.getEditorView(props);
    if (!eventDispatcher || !editorView || this.isSubscribed) {
      return;
    }

    // TODO: ED-15663
    // Please, do not copy or use this kind of code below
    // @ts-ignore
    const fakePluginKey = {
      key: 'analyticsPlugin$',
      getState: state => {
        return state['analyticsPlugin$'];
      }
    };
    const analyticsPlugin = fakePluginKey.getState(editorView.state);
    const uiTracking = analyticsPlugin && analyticsPlugin.performanceTracking ? analyticsPlugin.performanceTracking.uiTracking || {} : {};
    const trackingEnabled = uiTracking.enabled === true;
    const samplingRate = (_uiTracking$samplingR = uiTracking.samplingRate) !== null && _uiTracking$samplingR !== void 0 ? _uiTracking$samplingR : DEFAULT_SAMPLING_RATE;
    const slowThreshold = (_uiTracking$slowThres = uiTracking.slowThreshold) !== null && _uiTracking$slowThres !== void 0 ? _uiTracking$slowThres : DEFAULT_SLOW_THRESHOLD;
    this.isSubscribed = true;
    const pluginsStates = this.getPluginsStates(plugins, editorView);
    this.setState(pluginsStates);
    Object.keys(plugins).forEach(propName => {
      const pluginKey = plugins[propName];
      if (!pluginKey) {
        return;
      }
      const pluginName = pluginKey.key;
      const pluginState = pluginsStates[propName];
      const isPluginWithSubscribe = pluginState && pluginState.subscribe;
      const handler = this.handlePluginStateChange(propName, pluginName, {
        samplingRate,
        slowThreshold,
        trackingEnabled
      }, isPluginWithSubscribe);
      if (isPluginWithSubscribe) {
        pluginState.subscribe(handler);
      } else {
        eventDispatcher.on(pluginKey.key, handler);
      }
      this.listeners[pluginKey.key] = {
        handler,
        pluginKey
      };
    });
  }
  unsubscribe() {
    const eventDispatcher = this.getEventDispatcher();
    const editorView = this.getEditorView();
    if (!eventDispatcher || !editorView || !this.isSubscribed) {
      return;
    }
    Object.keys(this.listeners).forEach(key => {
      const pluginState = this.listeners[key].pluginKey.getState(editorView.state);
      if (pluginState && pluginState.unsubscribe) {
        pluginState.unsubscribe(this.listeners[key].handler);
      } else {
        eventDispatcher.off(key, this.listeners[key].handler);
      }
    });
    this.listeners = [];
  }
  subscribeToContextUpdates(context) {
    if (context && context.editorActions) {
      context.editorActions._privateSubscribe(this.onContextUpdate);
    }
  }
  unsubscribeFromContextUpdates(context) {
    if (context && context.editorActions) {
      context.editorActions._privateUnsubscribe(this.onContextUpdate);
    }
  }
  componentDidMount() {
    this.subscribe(this.props);
    this.subscribeToContextUpdates(this.context);
  }
  UNSAFE_componentWillReceiveProps(nextProps) {
    if (!this.isSubscribed) {
      this.subscribe(nextProps);
    }
  }
  componentWillUnmount() {
    if (this.debounce) {
      window.clearTimeout(this.debounce);
    }
    this.unsubscribeFromContextUpdates(this.context);
    this.unsubscribe();
  }
  render() {
    const {
      render
    } = this.props;
    return render(this.state);
  }
}
_defineProperty(WithPluginState, "displayName", 'WithPluginState');
_defineProperty(WithPluginState, "contextTypes", {
  editorActions: PropTypes.object,
  editorSharedConfig: PropTypes.object
});
export { WithPluginState };