import _defineProperty from "@babel/runtime/helpers/defineProperty";
import isEqual from 'lodash/isEqual';
import throttle from 'lodash/throttle';
import { corePlugin } from './core-plugin';

// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any

function hasGetSharedState(
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
plugin
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) {
  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return typeof plugin.getSharedState === 'function';
}
function hasActions(
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
plugin
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) {
  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return typeof plugin.actions === 'object';
}
function hasCommands(
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
plugin
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) {
  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return typeof plugin.commands === 'object';
}
const filterPluginsWithListeners = ({
  listeners,
  plugins
}) => Array.from(listeners.keys()).map(pluginName => plugins.get(pluginName)).filter(plugin => plugin !== undefined && hasGetSharedState(plugin));
const extractSharedStateFromPlugins = ({
  oldEditorState,
  newEditorState,
  plugins
}) => {
  const isInitialization = !oldEditorState && newEditorState;
  const result = new Map();
  for (const plugin of plugins) {
    if (!plugin || !hasGetSharedState(plugin)) {
      continue;
    }
    const nextSharedState = plugin.getSharedState(newEditorState);
    const prevSharedState = !isInitialization && oldEditorState ? plugin.getSharedState(oldEditorState) : undefined;
    const isSamePluginState = isEqual(prevSharedState, nextSharedState);
    if (isInitialization || !isSamePluginState) {
      result.set(plugin.name, {
        nextSharedState,
        prevSharedState
      });
    }
  }
  return result;
};
const THROTTLE_CALLS_FOR_MILLISECONDS = 0;
const notifyListenersThrottled = throttle(({
  listeners,
  updatesToNotifyQueue
}) => {
  const callbacks = [];
  for (const [pluginName, diffs] of updatesToNotifyQueue.entries()) {
    const pluginListeners = listeners.get(pluginName) || [];
    pluginListeners.forEach(callback => {
      diffs.forEach(diff => {
        callbacks.push(callback.bind(callback, diff));
      });
    });
  }
  updatesToNotifyQueue.clear();
  if (callbacks.length === 0) {
    return;
  }
  callbacks.reverse().forEach(cb => {
    cb();
  });
}, THROTTLE_CALLS_FOR_MILLISECONDS);
export class PluginsData {}
class ActionsAPI {
  createAPI(
  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  plugin
  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  ) {
    if (!plugin || !hasActions(plugin)) {
      return {};
    }
    return new Proxy(plugin.actions || {}, {
      get: function (target, prop, _receiver) {
        // We will be able to track perfomance here
        return Reflect.get(target, prop);
      }
    });
  }
}
class EditorCommandsAPI {
  createAPI(
  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  plugin
  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  ) {
    if (!plugin || !hasCommands(plugin)) {
      return {};
    }
    return new Proxy(plugin.commands || {}, {
      get: function (target, prop, _receiver) {
        // We will be able to track perfomance here
        return Reflect.get(target, prop);
      }
    });
  }
}
export class SharedStateAPI {
  constructor({
    getEditorState
  }) {
    _defineProperty(this, "updatesToNotifyQueue", new Map());
    this.getEditorState = getEditorState;
    this.listeners = new Map();
  }
  createAPI(plugin
  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  ) {
    if (!plugin) {
      return {
        currentState: () => undefined,
        onChange: _sub => {
          return () => {};
        }
      };
    }
    const pluginName = plugin.name;
    return {
      currentState: () => {
        if (!hasGetSharedState(plugin)) {
          return undefined;
        }
        const state = this.getEditorState();
        return plugin.getSharedState(state);
      },
      onChange: sub => {
        const pluginListeners = this.listeners.get(pluginName) || new Set();
        pluginListeners.add(sub);
        this.listeners.set(pluginName, pluginListeners);
        return () => this.cleanupSubscription(pluginName, sub);
      }
    };
  }
  cleanupSubscription(pluginName, sub) {
    (this.listeners.get(pluginName) || new Set()).delete(sub);
  }
  notifyListeners({
    newEditorState,
    oldEditorState,
    plugins
  }) {
    const {
      listeners,
      updatesToNotifyQueue
    } = this;
    const pluginsFiltered = filterPluginsWithListeners({
      plugins,
      listeners
    });
    const sharedStateDiffs = extractSharedStateFromPlugins({
      oldEditorState,
      newEditorState,
      plugins: pluginsFiltered
    });
    if (sharedStateDiffs.size === 0) {
      return;
    }
    for (const [pluginName, nextDiff] of sharedStateDiffs) {
      const currentDiffQueue = updatesToNotifyQueue.get(pluginName) || [];
      updatesToNotifyQueue.set(pluginName, [...currentDiffQueue, nextDiff]);
    }
    notifyListenersThrottled({
      updatesToNotifyQueue,
      listeners
    });
  }
  destroy() {
    this.listeners.clear();
    this.updatesToNotifyQueue.clear();
  }
}
const editorAPICache = new WeakMap();
export class EditorPluginInjectionAPI {
  constructor({
    getEditorState,
    getEditorView,
    fireAnalyticsEvent
  }) {
    _defineProperty(this, "onEditorViewUpdated", ({
      newEditorState,
      oldEditorState
    }) => {
      this.sharedStateAPI.notifyListeners({
        newEditorState,
        oldEditorState,
        plugins: this.plugins
      });
    });
    _defineProperty(this, "onEditorPluginInitialized", plugin => {
      this.addPlugin(plugin);
    });
    _defineProperty(this, "addPlugin", plugin => {
      // Plugins other than `core` are checked by the preset itself
      // For some reason in some tests we have duplicates that are missed.
      // To follow-up in ED-19611
      if (plugin.name === 'core' && this.plugins.has(plugin.name)) {
        throw new Error(`Plugin ${plugin.name} has already been initialised in the Editor API!
        There cannot be duplicate plugins or you will have unexpected behaviour`);
      }
      this.plugins.set(plugin.name, plugin);
    });
    _defineProperty(this, "getPluginByName", pluginName => {
      const plugin = this.plugins.get(pluginName);
      return plugin;
    });
    this.sharedStateAPI = new SharedStateAPI({
      getEditorState
    });
    this.plugins = new Map();
    this.actionsAPI = new ActionsAPI();
    this.commandsAPI = new EditorCommandsAPI();
    // Special core plugin that is always added
    this.addPlugin(corePlugin({
      config: {
        getEditorView,
        fireAnalyticsEvent
      }
    }));
  }
  createAPI() {
    const {
      sharedStateAPI,
      actionsAPI,
      commandsAPI,
      getPluginByName
    } = this;

    // Ignored via go/ees005
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    return new Proxy({}, {
      get: function (target, prop, _receiver) {
        // If we pass this as a prop React hates us
        // Let's just reflect the result and ignore these
        if (prop === 'toJSON') {
          return Reflect.get(target, prop);
        }
        const plugin = getPluginByName(prop);
        if (!plugin) {
          return undefined;
        }
        const sharedState = sharedStateAPI.createAPI(plugin);
        const actions = actionsAPI.createAPI(plugin);
        const commands = commandsAPI.createAPI(plugin);

        // Ignored via go/ees005
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const proxyCoreAPI = {
          sharedState,
          actions,
          commands
        };
        return proxyCoreAPI;
      }
    });
  }
  api() {
    if (!editorAPICache.get(this)) {
      editorAPICache.set(this, this.createAPI());
    }

    // Ignored via go/ees005
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    return editorAPICache.get(this);
  }
}