import { Node } from '@atlaskit/editor-prosemirror/model';
import { getNodeIdProvider } from '../../node-anchor/node-anchor-provider';
import { processRawFragmentValue, processRawValue, processRawValueWithoutValidation } from '../../utils/processRawValue';
import { editorCommandToPMCommand } from '../editor-commands';
import { createThrottleSchedule, returnDocumentRequest, returnDocumentRequestNoThrowError } from './requestDocument';

/**
 * Core plugin that is always included in the preset.
 * Allows for executing `EditorCommand` and other core functionality.
 */
export const corePlugin = ({
  config
}) => {
  // Create the document request throttler per editor (rather than at a module level)
  const scheduleDocumentRequest = createThrottleSchedule(returnDocumentRequest);
  const scheduleDocumentRequestNoThrowError = createThrottleSchedule(returnDocumentRequestNoThrowError);
  return {
    name: 'core',
    getSharedState(state) {
      return {
        schema: state === null || state === void 0 ? void 0 : state.schema
      };
    },
    actions: {
      execute: command => {
        const editorView = config === null || config === void 0 ? void 0 : config.getEditorView();
        if (!editorView || !command) {
          return false;
        }
        const {
          state,
          dispatch
        } = editorView;
        return editorCommandToPMCommand(command)(state, dispatch);
      },
      // Code copied from `EditorActions.focus()`
      focus: options => {
        var _options$scrollIntoVi;
        const editorView = config === null || config === void 0 ? void 0 : config.getEditorView();
        if (!editorView || editorView.hasFocus()) {
          return false;
        }
        editorView.focus();
        if ((_options$scrollIntoVi = options === null || options === void 0 ? void 0 : options.scrollIntoView) !== null && _options$scrollIntoVi !== void 0 ? _options$scrollIntoVi : true) {
          editorView.dispatch(editorView.state.tr.scrollIntoView());
        }
        return true;
      },
      // Code copied from `EditorActions.blur()`
      blur: () => {
        const editorView = config === null || config === void 0 ? void 0 : config.getEditorView();
        if (!editorView || !editorView.hasFocus()) {
          return false;
        }

        // Ignored via go/ees005
        // eslint-disable-next-line @atlaskit/editor/no-as-casting
        editorView.dom.blur();
        return true;
      },
      scrollToPos(pos, scrollOptions) {
        const editorView = config === null || config === void 0 ? void 0 : config.getEditorView();
        if (!editorView) {
          return false;
        }
        const tr = editorView.state.tr;
        const isValidPos = typeof pos === 'number' && pos >= 0 && pos <= tr.doc.content.size;
        if (!isValidPos) {
          return false;
        }
        const dom = editorView.domAtPos(pos).node;
        if (!(dom instanceof Element)) {
          // If it's not an element (e.g. #text node), we'll try the parent.
          // This is expected to cover most of the scenarios, if not, window.scrollTo is an alternative
          if (dom.parentNode instanceof Element) {
            dom.parentNode.scrollIntoView(scrollOptions);
            return true;
          }
          return false;
        }
        dom.scrollIntoView(scrollOptions);
        return true;
      },
      replaceDocument: (replaceValue, options) => {
        const editorView = config === null || config === void 0 ? void 0 : config.getEditorView();
        if (!editorView || replaceValue === undefined || replaceValue === null) {
          return false;
        }
        const {
          state
        } = editorView;
        const {
          schema
        } = state;
        const content = options !== null && options !== void 0 && options.skipValidation ? processRawValueWithoutValidation(schema, replaceValue) : Array.isArray(replaceValue) ? processRawFragmentValue(schema, replaceValue) : processRawValue(schema, replaceValue);

        // Don't replace the document if it's the same document, as full size
        // replace transactions cause issues for collaborative editing and
        // content reconciliation (eg. inline comments getting dropped)
        if (content instanceof Node && state.doc.eq(content)) {
          return false;
        }
        if (content) {
          var _options$scrollIntoVi2;
          const tr = state.tr.replaceWith(0, state.doc.nodeSize - 2, content);
          if ((options === null || options === void 0 ? void 0 : options.addToHistory) === false) {
            tr.setMeta('addToHistory', false);
          }
          if ((_options$scrollIntoVi2 = options === null || options === void 0 ? void 0 : options.scrollIntoView) !== null && _options$scrollIntoVi2 !== void 0 ? _options$scrollIntoVi2 : true) {
            editorView.dispatch(tr.scrollIntoView());
          } else {
            editorView.dispatch(tr.setMeta('scrollIntoView', false));
          }
          return true;
        }
        return false;
      },
      // Ignored via go/ees005
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      requestDocument(onReceive, options) {
        var _config$getEditorView;
        const view = (_config$getEditorView = config === null || config === void 0 ? void 0 : config.getEditorView()) !== null && _config$getEditorView !== void 0 ? _config$getEditorView : null;
        scheduleDocumentRequest(view, onReceive, options === null || options === void 0 ? void 0 : options.transformer, config === null || config === void 0 ? void 0 : config.fireAnalyticsEvent, options === null || options === void 0 ? void 0 : options.alwaysFire);
      },
      requestDocumentAsync: options => {
        return new Promise(resolve => {
          var _config$getEditorView2;
          const view = (_config$getEditorView2 = config === null || config === void 0 ? void 0 : config.getEditorView()) !== null && _config$getEditorView2 !== void 0 ? _config$getEditorView2 : null;
          scheduleDocumentRequestNoThrowError(view, doc => {
            resolve(doc);
          }, options === null || options === void 0 ? void 0 : options.transformer, config === null || config === void 0 ? void 0 : config.fireAnalyticsEvent, true);
        });
      },
      createTransformer(cb) {
        var _config$getEditorView3;
        const view = (_config$getEditorView3 = config === null || config === void 0 ? void 0 : config.getEditorView()) !== null && _config$getEditorView3 !== void 0 ? _config$getEditorView3 : null;
        if (!(view !== null && view !== void 0 && view.state.schema)) {
          return undefined;
        }
        return cb(view === null || view === void 0 ? void 0 : view.state.schema);
      },
      getAnchorIdForNode(node, pos) {
        var _config$getEditorView4;
        const view = (_config$getEditorView4 = config === null || config === void 0 ? void 0 : config.getEditorView()) !== null && _config$getEditorView4 !== void 0 ? _config$getEditorView4 : null;
        if (!view) {
          return undefined;
        }
        const nodeIdProvider = getNodeIdProvider(view);
        const cachedId = nodeIdProvider.getIdForNode(node);
        if (cachedId) {
          return cachedId;
        }
        if (pos < 0) {
          return undefined;
        }
        const generatedId = nodeIdProvider.getOrGenerateId(node, pos);
        if (generatedId) {
          return generatedId;
        }
        const nodeDOM = view.nodeDOM(pos);
        if (nodeDOM instanceof HTMLElement) {
          return nodeDOM.getAttribute('data-node-anchor') || undefined;
        }
        return undefined;
      }
    }
  };
};