import { transformDedupeMarks, transformIndentationMarks, transformInvalidMediaContent, transformMediaLinkMarks, transformNodesMissingContent, transformTextLinkCodeMarks } from '@atlaskit/adf-utils/transforms';
import { Node } from '@atlaskit/editor-prosemirror/model';
import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '../analytics';
import { isEmptyParagraph } from './editor-core-utils';
import { sanitizeNodeForPrivacy } from './filter/privacy-filter';
import { findAndTrackUnsupportedContentNodes } from './track-unsupported-content';
import { validateADFEntity } from './validate-using-spec';
export var getStepRange = function getStepRange(transaction) {
  var from = -1;
  var to = -1;
  transaction.mapping.maps.forEach(function (stepMap, index) {
    stepMap.forEach(function (oldStart, oldEnd) {
      var newStart = transaction.mapping.slice(index).map(oldStart, -1);
      var newEnd = transaction.mapping.slice(index).map(oldEnd);
      from = newStart < from || from === -1 ? newStart : from;
      to = newEnd > to || to === -1 ? newEnd : to;
    });
  });
  if (from !== -1) {
    return {
      from: from,
      to: to
    };
  }
  return null;
};

// Checks to see if the parent node is the document, ie not contained within another entity
export function hasDocAsParent($anchor) {
  return $anchor.depth === 1;
}

/**
 * Checks if a node looks like an empty document
 */
export function isEmptyDocument(node) {
  var nodeChild = node.content.firstChild;
  if (node.childCount !== 1 || !nodeChild) {
    return false;
  }
  return isEmptyParagraph(nodeChild);
}
export function bracketTyped(state) {
  var selection = state.selection;
  var _ref = selection,
    $cursor = _ref.$cursor,
    $anchor = _ref.$anchor;
  if (!$cursor) {
    return false;
  }
  var node = $cursor.nodeBefore;
  if (!node) {
    return false;
  }
  if (node.type.name === 'text' && node.text === '{') {
    var paragraphNode = $anchor.node();
    return paragraphNode.marks.length === 0 && hasDocAsParent($anchor);
  }
  return false;
}
export function nodesBetweenChanged(tr, f, startPos) {
  var stepRange = getStepRange(tr);
  if (!stepRange) {
    return;
  }
  tr.doc.nodesBetween(stepRange.from, stepRange.to, f, startPos);
}
export function processRawValue(schema, value, providerFactory, sanitizePrivateContent, contentTransformer, dispatchAnalyticsEvent) {
  if (!value) {
    return;
  }
  var node;
  if (typeof value === 'string') {
    try {
      if (contentTransformer) {
        var doc = contentTransformer.parse(value);
        node = doc.toJSON();
      } else {
        node = JSON.parse(value);
      }
    } catch (e) {
      // eslint-disable-next-line no-console
      console.error("Error processing value: ".concat(value, " isn't a valid JSON"));
      return;
    }
  } else {
    node = value;
  }
  if (Array.isArray(node)) {
    // eslint-disable-next-line no-console
    console.error("Error processing value: ".concat(node, " is an array, but it must be an object."));
    return;
  }
  try {
    // ProseMirror always require a child under doc
    if (node.type === 'doc') {
      if (Array.isArray(node.content) && node.content.length === 0) {
        node.content.push({
          type: 'paragraph',
          content: []
        });
      }
      // Just making sure doc is always valid
      if (!node.version) {
        node.version = 1;
      }
    }
    if (contentTransformer) {
      return Node.fromJSON(schema, node);
    }

    // link mark on mediaSingle is deprecated, need to move link mark to child media node
    // https://product-fabric.atlassian.net/browse/ED-14043
    var _transformMediaLinkMa = transformMediaLinkMarks(node),
      transformedAdf = _transformMediaLinkMa.transformedAdf,
      isTransformed = _transformMediaLinkMa.isTransformed;
    if (isTransformed && dispatchAnalyticsEvent) {
      dispatchAnalyticsEvent({
        action: ACTION.MEDIA_LINK_TRANSFORMED,
        actionSubject: ACTION_SUBJECT.EDITOR,
        eventType: EVENT_TYPE.OPERATIONAL
      });
    }

    // See: HOT-97965 https://product-fabric.atlassian.net/browse/ED-14400
    // We declared in code mark spec that links and marks should not co-exist on
    // text nodes. This util strips code marks from bad text nodes and preserves links.
    // Otherwise, prosemirror will try to repair the invalid document by stripping links
    // and preserving code marks during content changes.
    var _transformTextLinkCod = transformTextLinkCodeMarks(transformedAdf);
    transformedAdf = _transformTextLinkCod.transformedAdf;
    isTransformed = _transformTextLinkCod.isTransformed;
    if (isTransformed && dispatchAnalyticsEvent) {
      dispatchAnalyticsEvent({
        action: ACTION.TEXT_LINK_MARK_TRANSFORMED,
        actionSubject: ACTION_SUBJECT.EDITOR,
        eventType: EVENT_TYPE.OPERATIONAL
      });
    }
    var discardedMarks = [];
    var _transformDedupeMarks = transformDedupeMarks(transformedAdf);
    transformedAdf = _transformDedupeMarks.transformedAdf;
    isTransformed = _transformDedupeMarks.isTransformed;
    discardedMarks = _transformDedupeMarks.discardedMarks;
    if (isTransformed && dispatchAnalyticsEvent) {
      dispatchAnalyticsEvent({
        action: ACTION.DEDUPE_MARKS_TRANSFORMED_V2,
        actionSubject: ACTION_SUBJECT.EDITOR,
        eventType: EVENT_TYPE.OPERATIONAL,
        attributes: {
          /** UGC WARNING
           *
           * DO NOT include the `mark` attributes inside, we map here to only
           * extract the mark type as that is the only non-UGC safe information
           * that we can add to event-attributes
           *
           */
          discardedMarkTypes: discardedMarks.map(function (mark) {
            return mark.type;
          })
        }
      });
    }
    var _transformNodesMissin = transformNodesMissingContent(transformedAdf);
    transformedAdf = _transformNodesMissin.transformedAdf;
    isTransformed = _transformNodesMissin.isTransformed;
    if (isTransformed && dispatchAnalyticsEvent) {
      dispatchAnalyticsEvent({
        action: ACTION.NODES_MISSING_CONTENT_TRANSFORMED,
        actionSubject: ACTION_SUBJECT.EDITOR,
        eventType: EVENT_TYPE.OPERATIONAL
      });
    }
    var _transformIndentation = transformIndentationMarks(transformedAdf);
    transformedAdf = _transformIndentation.transformedAdf;
    isTransformed = _transformIndentation.isTransformed;
    if (isTransformed && dispatchAnalyticsEvent) {
      dispatchAnalyticsEvent({
        action: ACTION.INDENTATION_MARKS_TRANSFORMED,
        actionSubject: ACTION_SUBJECT.EDITOR,
        eventType: EVENT_TYPE.OPERATIONAL
      });
    }
    var _transformInvalidMedi = transformInvalidMediaContent(transformedAdf);
    transformedAdf = _transformInvalidMedi.transformedAdf;
    isTransformed = _transformInvalidMedi.isTransformed;
    if (isTransformed && dispatchAnalyticsEvent) {
      dispatchAnalyticsEvent({
        action: ACTION.INVALID_MEDIA_CONTENT_TRANSFORMED,
        actionSubject: ACTION_SUBJECT.EDITOR,
        eventType: EVENT_TYPE.OPERATIONAL
      });
    }
    var entity = validateADFEntity(schema, transformedAdf || node, dispatchAnalyticsEvent);
    var newEntity = maySanitizePrivateContent(entity, providerFactory, sanitizePrivateContent);
    var parsedDoc = Node.fromJSON(schema, newEntity);

    // throws an error if the document is invalid
    try {
      parsedDoc.check();
    } catch (err) {
      if (dispatchAnalyticsEvent) {
        dispatchAnalyticsEvent({
          action: ACTION.INVALID_PROSEMIRROR_DOCUMENT,
          actionSubject: ACTION_SUBJECT.EDITOR,
          eventType: EVENT_TYPE.OPERATIONAL
        });
      }
      throw err;
    }
    if (dispatchAnalyticsEvent) {
      findAndTrackUnsupportedContentNodes(parsedDoc, schema, dispatchAnalyticsEvent);
    }
    return parsedDoc;
  } catch (e) {
    if (dispatchAnalyticsEvent) {
      dispatchAnalyticsEvent({
        action: ACTION.DOCUMENT_PROCESSING_ERROR,
        actionSubject: ACTION_SUBJECT.EDITOR,
        eventType: EVENT_TYPE.OPERATIONAL
      });
    }

    // eslint-disable-next-line no-console
    console.error("Error processing document:\n".concat(e instanceof Error ? e.message : String(e), "\n\n"), JSON.stringify(node));
    if (isProseMirrorSchemaCheckError(e)) {
      throw e;
    }
    return;
  }
}
function isProseMirrorSchemaCheckError(error) {
  return error instanceof RangeError && (!!error.message.match(/^Invalid collection of marks for node/) || !!error.message.match(/^Invalid content for node/));
}
var maySanitizePrivateContent = function maySanitizePrivateContent(entity, providerFactory, sanitizePrivateContent) {
  if (sanitizePrivateContent && providerFactory) {
    return sanitizeNodeForPrivacy(entity, providerFactory);
  }
  return entity;
};

/**
 * Returns false if node contains only empty inline nodes and hardBreaks.
 */
export function hasVisibleContent(node) {
  var isInlineNodeHasVisibleContent = function isInlineNodeHasVisibleContent(inlineNode) {
    return inlineNode.isText ? !!inlineNode.textContent.trim() : inlineNode.type.name !== 'hardBreak';
  };
  if (node.isInline) {
    return isInlineNodeHasVisibleContent(node);
  } else if (node.isBlock && (node.isLeaf || node.isAtom)) {
    return true;
  } else if (!node.childCount) {
    return false;
  }
  for (var _index = 0; _index < node.childCount; _index++) {
    var child = node.child(_index);
    var invisibleNodeTypes = ['paragraph', 'text', 'hardBreak'];
    if (!invisibleNodeTypes.includes(child.type.name) || hasVisibleContent(child)) {
      return true;
    }
  }
  return false;
}
export var isSelectionEndOfParagraph = function isSelectionEndOfParagraph(state) {
  return state.selection.$to.parent.type === state.schema.nodes.paragraph && state.selection.$to.pos === state.doc.resolve(state.selection.$to.pos).end();
};
function getChangedNodesIn(_ref2) {
  var tr = _ref2.tr,
    doc = _ref2.doc;
  var nodes = [];
  var stepRange = getStepRange(tr);
  if (!stepRange) {
    return nodes;
  }
  var from = Math.min(doc.nodeSize - 2, stepRange.from);
  var to = Math.min(doc.nodeSize - 2, stepRange.to);
  doc.nodesBetween(from, to, function (node, pos) {
    nodes.push({
      node: node,
      pos: pos
    });
  });
  return nodes;
}
export function getChangedNodes(tr) {
  return getChangedNodesIn({
    tr: tr,
    doc: tr.doc
  });
}