export const isReferencedSource = (state, node) => {
  var _node$attrs, _node$marks, _node$marks$find, _node$marks$find$attr;
  if (!node) {
    return false;
  }
  let found = false;

  // Handle nodes having 2 uuids. They could have a localId or a fragment. Regardless this needs
  // to check if either id is used by a data consumer.
  const localIds = new Set([(_node$attrs = node.attrs) === null || _node$attrs === void 0 ? void 0 : _node$attrs.localId, (_node$marks = node.marks) === null || _node$marks === void 0 ? void 0 : (_node$marks$find = _node$marks.find(mark => mark.type === state.schema.marks.fragment)) === null || _node$marks$find === void 0 ? void 0 : (_node$marks$find$attr = _node$marks$find.attrs) === null || _node$marks$find$attr === void 0 ? void 0 : _node$marks$find$attr.localId].filter(Boolean));

  // If there are no uuids on the node then it's not possible for it to be referenced anywhere.
  if (!localIds.size) {
    return false;
  }
  state.doc.descendants(node => {
    var _dataConsumer$attrs$s, _dataConsumer$attrs$s2;
    if (found) {
      return false;
    }
    const dataConsumer = node.marks.find(mark => mark.type === state.schema.marks.dataConsumer);
    if (!dataConsumer) {
      return true;
    }
    found = (_dataConsumer$attrs$s = (_dataConsumer$attrs$s2 = dataConsumer.attrs.sources) === null || _dataConsumer$attrs$s2 === void 0 ? void 0 : _dataConsumer$attrs$s2.some(src => localIds.has(src))) !== null && _dataConsumer$attrs$s !== void 0 ? _dataConsumer$attrs$s : false;
    return !found;
  });
  return found;
};
export const getConnections = state => {
  const result = {};
  const {
    doc,
    schema
  } = state;

  // Keeps a map of all raw ids -> to their preferred normalised varient. A node with both fragmentMark & localId will
  // have both ids mapped here to the same normalized version.
  const normalizedIds = new Map();
  const dataConsumerSources = new Map();

  // Perform a prelim scan creating the initial id to connection link mappings.
  // This will also save a list of data sources consumed per node.
  doc.descendants((node, pos) => {
    var _node$attrs2, _fragmentMark$attrs$l, _fragmentMark, _fragmentMark$attrs, _node$attrs3, _fragmentMark2, _fragmentMark2$attrs, _node$attrs4, _fragmentMark3, _fragmentMark3$attrs;
    let dataConsumer;
    let fragmentMark;
    node.marks.some(mark => {
      if (mark.type === schema.marks.dataConsumer) {
        dataConsumer = mark;
      } else if (mark.type === schema.marks.fragment) {
        fragmentMark = mark;
      }

      // Stop searching marks if we've found both consumer and fragment.
      return !!dataConsumer && !!fragmentMark;
    });

    // If node cannot be referenced by any means then abort.
    if (!fragmentMark && !((_node$attrs2 = node.attrs) !== null && _node$attrs2 !== void 0 && _node$attrs2.localId)) {
      return true;
    }
    const normalizedId = (_fragmentMark$attrs$l = (_fragmentMark = fragmentMark) === null || _fragmentMark === void 0 ? void 0 : (_fragmentMark$attrs = _fragmentMark.attrs) === null || _fragmentMark$attrs === void 0 ? void 0 : _fragmentMark$attrs.localId) !== null && _fragmentMark$attrs$l !== void 0 ? _fragmentMark$attrs$l : (_node$attrs3 = node.attrs) === null || _node$attrs3 === void 0 ? void 0 : _node$attrs3.localId;
    if (!normalizedId) {
      return true;
    }
    if (!!((_fragmentMark2 = fragmentMark) !== null && _fragmentMark2 !== void 0 && (_fragmentMark2$attrs = _fragmentMark2.attrs) !== null && _fragmentMark2$attrs !== void 0 && _fragmentMark2$attrs.localId)) {
      normalizedIds.set(fragmentMark.attrs.localId, normalizedId);
    }
    if (!!((_node$attrs4 = node.attrs) !== null && _node$attrs4 !== void 0 && _node$attrs4.localId)) {
      normalizedIds.set(node.attrs.localId, normalizedId);
    }
    if (!!result[normalizedId]) {
      // Duplicate ID has been found, we'll care about the first one for now.
      return true;
    }
    result[normalizedId] = {
      localId: normalizedId,
      name: (_fragmentMark3 = fragmentMark) === null || _fragmentMark3 === void 0 ? void 0 : (_fragmentMark3$attrs = _fragmentMark3.attrs) === null || _fragmentMark3$attrs === void 0 ? void 0 : _fragmentMark3$attrs.name,
      node,
      pos,
      targets: []
    };
    if (!!dataConsumer && dataConsumer.attrs.sources.length) {
      dataConsumerSources.set(normalizedId, dataConsumer.attrs.sources);
    }

    // Do not descend into children of a node which has a dataConsumer attached. This assumes all children of the node cannot
    // be referenced by another node.
    return !dataConsumer;
  });

  // This 2nd-pass only looks at the consumer sources and updates all connections with the correct id refs.
  for (const [localId, sources] of dataConsumerSources) {
    // This is a ref to the node (connection link) which contains the consumer.

    sources.forEach(src => {
      var _normalizedIds$get;
      const normalizedId = (_normalizedIds$get = normalizedIds.get(src)) !== null && _normalizedIds$get !== void 0 ? _normalizedIds$get : src;
      const srcLink = result[normalizedId];
      if (srcLink && normalizedId !== localId) {
        srcLink.targets.push(localId);
      }
    });
  }
  return result;
};
export const removeConnectedNodes = (state, node) => {
  if (!node) {
    return state.tr;
  }
  const selectedLocalIds = getSelectedLocalIds(state, node);
  const allNodes = getConnections(state);
  const idsToBeDeleted = getIdsToBeDeleted(selectedLocalIds, allNodes);
  if (!(idsToBeDeleted !== null && idsToBeDeleted !== void 0 && idsToBeDeleted.length)) {
    return state.tr;
  }
  const {
    tr
  } = state;
  let newTr = tr;
  idsToBeDeleted.forEach(id => {
    if (!allNodes[id]) {
      return;
    }
    const {
      node,
      pos
    } = allNodes[id];
    newTr = newTr.delete(newTr.mapping.map(pos), newTr.mapping.map(node.nodeSize + pos));
  });
  return newTr;
};

// find all ids need to be remove connected to selected extension
const getIdsToBeDeleted = (selectedIds, allNodes) => {
  if (!selectedIds.size) {
    return [];
  }
  let searchSet = [...selectedIds];
  const deletedIds = new Set();
  while (searchSet.length) {
    // Ignored via go/ees005
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    const id = searchSet.pop();
    if (allNodes[id]) {
      var _allNodes$id$targets$, _allNodes$id;
      deletedIds.add(allNodes[id].localId);
      searchSet = searchSet.concat((_allNodes$id$targets$ = (_allNodes$id = allNodes[id]) === null || _allNodes$id === void 0 ? void 0 : _allNodes$id.targets.filter(targetId => {
        var _allNodes$targetId;
        return !deletedIds.has((_allNodes$targetId = allNodes[targetId]) === null || _allNodes$targetId === void 0 ? void 0 : _allNodes$targetId.localId);
      })) !== null && _allNodes$id$targets$ !== void 0 ? _allNodes$id$targets$ : []);
    }
  }
  return Array.from(deletedIds);
};

// for get children info for confirmation dialog
export const getChildrenInfo = (state, node) => {
  let allChildrenHadName = true;
  if (!node) {
    return [];
  }
  const childrenIdSet = new Set();
  const childrenInfoArray = [];
  const allNodes = getConnections(state);
  const selectedNodeIds = getSelectedLocalIds(state, node);
  selectedNodeIds.forEach(id => {
    if (allNodes[id]) {
      allNodes[id].targets.forEach(childrenIdSet.add, childrenIdSet);
    }
  });
  for (const id of childrenIdSet) {
    if (!getNodeNameById(id, allNodes)) {
      allChildrenHadName = false;
      break;
    } else {
      childrenInfoArray.push({
        id,
        name: getNodeNameById(id, allNodes),
        amount: getChildrenNodeAmount(id, allNodes)
      });
    }
  }
  return allChildrenHadName ? childrenInfoArray : [];
};
const getChildrenNodeAmount = (id, allNodes) => {
  const searchTerms = new Set([id]);
  const traverseHistory = new Map();
  const childrenIds = new Set();
  traverseHistory.set(id, false);
  while (searchTerms.size > 0) {
    const [currTerm] = searchTerms;
    const targets = getNodeTargetsById(currTerm, allNodes);
    targets.forEach(target => {
      const isTargetCounted = traverseHistory.get(target);
      if (!isTargetCounted) {
        searchTerms.add(target);
        childrenIds.add(target);
      }
      target !== id && childrenIds.add(target);
    });
    traverseHistory.set(currTerm, true);
    searchTerms.delete(currTerm);
  }
  return childrenIds.size;
};

// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const getNodeTargetsById = (id, allNodes) => {
  if (!id || !allNodes[id]) {
    return [];
  }
  return allNodes[id].targets;
};
const getNodeNameById = (id, allNodes) => {
  if (typeof id === 'object') {
    let name;
    id.forEach(localId => {
      var _name, _allNodes$localId;
      name = (_name = name) !== null && _name !== void 0 ? _name : (_allNodes$localId = allNodes[localId]) === null || _allNodes$localId === void 0 ? void 0 : _allNodes$localId.name;
    });
    return name || null;
  }
  if (!id || !allNodes[id]) {
    return null;
  }
  return allNodes[id].name;
};
const getSelectedLocalIds = (state, node) => {
  var _node$attrs5, _node$marks2, _node$marks2$find, _node$marks2$find$att;
  if (!node) {
    return new Set([]);
  }
  const localIds = new Set([(_node$attrs5 = node.attrs) === null || _node$attrs5 === void 0 ? void 0 : _node$attrs5.localId, (_node$marks2 = node.marks) === null || _node$marks2 === void 0 ? void 0 : (_node$marks2$find = _node$marks2.find(mark => mark.type === state.schema.marks.fragment)) === null || _node$marks2$find === void 0 ? void 0 : (_node$marks2$find$att = _node$marks2$find.attrs) === null || _node$marks2$find$att === void 0 ? void 0 : _node$marks2$find$att.localId].filter(Boolean));
  return localIds;
};
// eslint-disable-next-line @atlaskit/editor/no-re-export
export { getNodeName } from './getNodeName';