Source: management-system/src/frontend/helpers/process-export/export-preparation.js

import { processInterface } from '@/frontend/backend-api/index.js';
import { flattenSubprocesses } from '@/shared-frontend-backend/helpers/process-hierarchy.js';
import { getSubprocessContent } from '@proceed/bpmn-helper';
/**
 * @param {Object} process
 * @returns - Returns xml for a process from the store
 */
export async function getXmlByProcess(process) {
  if (!process.id || !process.name) {
    console.warn('Try to retrieve xml for invalid process: ', process);
    return null;
  }
  const bpmn = (await processInterface.getProcess(process.id)).bpmn;
  return bpmn;
}

/**
 * @param {Object} process
 * @returns - Returns usertasks for a process from the store
 */
export async function getUserTasksByProcess(process) {
  if (!process.id || !process.name) {
    console.warn('Try to retrieve user tasks for invalid process: ', process);
    return null;
  }
  const userTasks = await processInterface.getUserTasksHTML(process.id);
  return userTasks;
}

export function getCleanedUpName(name) {
  return name
    .replace(/[`@^()_={}[\]|;!=&/\\#,+()$~%.'":*?<>{}]/g, '')
    .trim()
    .replace(/[ ]/g, '_')
    .replace(/-$/, '');
}

/**
 * Retrieve every subprocess and subprocesses of call activities for the
 * processes to export and their meta information, bpmn and user tasks
 *
 * @param {Object[]} allProcesses all known processes to search for referenced call activities
 * @param {Object[]} processesToExport - all processes that need to be exported
 * @param {Object} options - export format
 */
export async function prepareProcesses(allProcesses, processesToExport, options) {
  const fullInfoPromises = processesToExport.map(async (process) => {
    const augmentedProcess = {
      ...process,
      bpmn: await getXmlByProcess(process),
    };
    if (options.additionalParam.format === 'withUserTasks') {
      augmentedProcess.userTasks = await getUserTasksByProcess(process);
    }

    if (options.additionalParam.includeCallActivityProcess) {
      // get all callactivities, optionally also add collapsed subprocesses
      augmentedProcess.callActivities = await getAllSubprocesses(
        allProcesses,
        augmentedProcess,
        [],
        options.additionalParam.format === 'withUserTasks',
        options.additionalParam.includeCollapsedSubprocess
      );
    } else if (options.additionalParam.includeCollapsedSubprocess) {
      // only get all collapsed subprocesses
      augmentedProcess.collapsedSubprocesses = await getAllCollapsedSubprocesses(augmentedProcess);
    }

    return augmentedProcess;
  });

  processesToExport = await Promise.all(fullInfoPromises);

  // export callActivities seperately from their calling processes but only once
  // (might be marked for export too or used by multiple exported processes)
  if (options.format === 'bpmn' && options.additionalParam.includeCallActivityProcess) {
    processesToExport = processesToExport.reduce((acc, process) => {
      // adds the process if it isn't there already
      function addIfUnknown(p) {
        if (!acc.some((accP) => accP.id === p.id)) {
          acc.push(p);
        }
      }

      addIfUnknown(process);

      // add every call activity that isn't marked for export yet
      if (process.callActivities && Array.isArray(process.callActivities)) {
        process.callActivities.forEach((callActivity) => {
          addIfUnknown(callActivity);
        });
      }

      return acc;
    }, []);
  }

  return processesToExport;
}

/**
 * Creates a set of all called processes inside a process and their called processeses recursively
 *
 * additionally adds the collapsed subprocesses of a process as an attribute to the respective process
 *
 * @param {Object[]} allProcesses all known processes to search for referenced call activities
 * @param {Object} currentProcess current selected process of the hierarchy
 * @param {Object[]} includedProcesses container containing all (nested) call activities
 * @param {Boolean} [addUserTasks] if we want to get the user tasks of the called processes
 * @param {Boolean} includeCollapsed - signal if also collapsed subprocesses should be retrieved
 */
export async function getAllSubprocesses(
  allProcesses,
  currentProcess,
  includedProcesses,
  addUserTasks,
  includeCollapsed
) {
  if (!Array.isArray(currentProcess.subprocesses) || currentProcess.subprocesses.length === 0) {
    return includedProcesses;
  }
  const flatSubprocesses = flattenSubprocesses(currentProcess);

  if (includeCollapsed) {
    currentProcess.collapsedSubprocesses = await getAllCollapsedSubprocesses(currentProcess);
  }

  const callActivities = flatSubprocesses.filter((subprocess) => subprocess.isCallActivity);

  // recursively iterate through all callActivities and add the ones that contain a called process
  for (const { calledProcessId } of callActivities) {
    if (calledProcessId) {
      let callActivityProcess = allProcesses.find((pro) => pro.id === calledProcessId);
      if (callActivityProcess) {
        // to prevent updating the original process object
        callActivityProcess = { ...callActivityProcess };

        callActivityProcess.bpmn = await getXmlByProcess(callActivityProcess);
        if (addUserTasks) {
          callActivityProcess.userTasks = await getUserTasksByProcess(callActivityProcess);
        }
        // check if process was in recursive loop before
        if (!includedProcesses.some((process) => process.id === callActivityProcess.id)) {
          includedProcesses.push(callActivityProcess);
          includedProcesses = await getAllSubprocesses(
            allProcesses,
            callActivityProcess,
            includedProcesses,
            addUserTasks,
            includeCollapsed
          );
        }
      }
    }
  }

  return includedProcesses;
}

/**
 * Returns content of all collapsed subprocesses for given process
 * @param {Object} process current selected process of the hierarchy
 */
export async function getAllCollapsedSubprocesses(process) {
  const flatSubprocesses = flattenSubprocesses(process);

  const collapsedSubprocesses = flatSubprocesses
    // get only collapsed subprocesses
    .filter((subprocess) => !subprocess.isCallActivity && !subprocess.isExpanded)
    // add bpmn
    .map(async (subprocess) => ({
      ...subprocess,
      bpmn: await getSubprocessContent(process.bpmn, subprocess.elementId),
    }));

  return Promise.all(collapsedSubprocesses);
}