import { is } from 'bpmn-js/lib/util/ModelUtil';

/**
 * loops up until it can find the root.
 * @param element
 */
export function getRoot(businessObject, moddle) {
  // HACK: get the root element. need a more formal way to do this
  if (moddle) {
    for (const elementId in moddle.ids._seed.hats) {
      if (elementId.startsWith('Definitions_')) {
        return moddle.ids._seed.hats[elementId];
      }
    }
  } else {
    // todo: Do we want businessObject to be a shape or moddle object?
    if (businessObject.$type === 'bpmn:Definitions') {
      return businessObject;
    }
    if (typeof businessObject.$parent !== 'undefined') {
      return getRoot(businessObject.$parent);
    }
  }
  return businessObject;
}

export function isMessageElement(shapeElement) {
  return (
    is(shapeElement, 'bpmn:SendTask') ||
    is(shapeElement, 'bpmn:ReceiveTask') ||
    isMessageEvent(shapeElement)
  );
}

export function isMessageEvent(shapeElement) {
  const { eventDefinitions } = shapeElement.businessObject;
  if (eventDefinitions && eventDefinitions[0]) {
    return eventDefinitions[0].$type === 'bpmn:MessageEventDefinition';
  }
  return false;
}

export function canReceiveMessage(shapeElement) {
  if (is(shapeElement, 'bpmn:ReceiveTask')) {
    return true;
  }
  if (isMessageEvent(shapeElement)) {
    return (
      is(shapeElement, 'bpmn:StartEvent') || is(shapeElement, 'bpmn:CatchEvent')
    );
  }
  return false;
}

export function getMessageRefElement(shapeElement) {
  if (isMessageEvent(shapeElement)) {
    const messageEventDefinition =
      shapeElement.businessObject.eventDefinitions[0];
    if (messageEventDefinition && messageEventDefinition.messageRef) {
      return messageEventDefinition.messageRef;
    }
  } else if (
    isMessageElement(shapeElement) &&
    shapeElement.businessObject.messageRef
  ) {
    return shapeElement.businessObject.messageRef;
  }
  return null;
}

export function findCorrelationKeyForCorrelationProperty(shapeElement, moddle) {
  const correlationKeyElements = findCorrelationKeys(shapeElement, moddle);
  for (const cke of correlationKeyElements) {
    if (cke.correlationPropertyRef) {
      for (const correlationPropertyRef of cke.correlationPropertyRef) {
        if (correlationPropertyRef.id === shapeElement.id) {
          return cke;
        }
      }
    }
  }
  return null;
}

export function findCorrelationPropertiesAndRetrievalExpressionsForMessage(
  shapeElement
) {
  const formalExpressions = [];
  const messageRefElement = getMessageRefElement(shapeElement);
  if (messageRefElement) {
    const root = getRoot(shapeElement.businessObject);
    if (root.$type === 'bpmn:Definitions') {
      for (const childElement of root.rootElements) {
        if (childElement.$type === 'bpmn:CorrelationProperty') {
          const retrievalExpression =
            getRetrievalExpressionFromCorrelationProperty(
              childElement,
              messageRefElement
            );
          if (retrievalExpression) {
            const formalExpression = {
              correlationPropertyModdleElement: childElement,
              correlationPropertyRetrievalExpressionModdleElement:
                retrievalExpression,
            };
            formalExpressions.push(formalExpression);
          }
        }
      }
    }
  }
  return formalExpressions;
}

export function getMessageElementForShapeElement(shapeElement) {
  const { businessObject } = shapeElement;
  const taskMessage = getMessageRefElement(shapeElement);
  const messages = findMessageModdleElements(businessObject);
  if (taskMessage) {
    for (const message of messages) {
      if (message.id === taskMessage.id) {
        return message;
      }
    }
  }
  return null;
}

function getRetrievalExpressionFromCorrelationProperty(
  correlationProperty,
  message
) {
  if (correlationProperty.correlationPropertyRetrievalExpression) {
    for (const retrievalExpression of correlationProperty.correlationPropertyRetrievalExpression) {
      if (
        retrievalExpression.$type ===
          'bpmn:CorrelationPropertyRetrievalExpression' &&
        retrievalExpression.messageRef &&
        retrievalExpression.messageRef.id === message.id
      ) {
        return retrievalExpression;
      }
    }
  }
  return null;
}

export function findCorrelationProperties(businessObject, moddle) {
  const root = getRoot(businessObject, moddle);
  const correlationProperties = [];
  for (const rootElement of root.rootElements) {
    if (rootElement.$type === 'bpmn:CorrelationProperty') {
      correlationProperties.push(rootElement);
    }
  }
  return correlationProperties;
}

export function findCorrelationKeys(businessObject, moddle) {
  const root = getRoot(businessObject, moddle);
  const correlationKeys = [];
  if (root.rootElements) {
    for (const rootElement of root.rootElements) {
      if (rootElement.$type === 'bpmn:Collaboration') {
        const currentKeys = rootElement.correlationKeys;
        for (const correlationKey in currentKeys) {
          const currentCorrelation = rootElement.correlationKeys[correlationKey];
          correlationKeys.push(currentCorrelation);
        }
      }
    }
  }
  return correlationKeys;
}

export function findMessageModdleElements(businessObject) {
  const messages = [];
  const root = getRoot(businessObject);
  if (root.rootElements) {
    for (const rootElement of root.rootElements) {
      if (rootElement.$type === 'bpmn:Message') {
        messages.push(rootElement);
      }
    }
  }
  return messages;
}