function updateStepId(value, newStepIds) {
  let newValue = value;

  const stepIdEntries = Object.entries(newStepIds);
  for (const stepIdEntry of stepIdEntries) {
    const [oldStepId, newStepId] = stepIdEntry;
    const partialOldVariable = `{{step.${oldStepId}.`;
    const partialNewVariable = `{{step.${newStepId}.`;

    newValue = newValue.replace(partialOldVariable, partialNewVariable);
  }

  return newValue;
}

function updateStepVariables(parameters, newStepIds) {
  const entries = Object.entries(parameters);
  return entries.reduce((result, [key, value]) => {
    if (typeof value === 'string') {
      return {
        ...result,
        [key]: updateStepId(value, newStepIds),
      };
    }

    if (Array.isArray(value)) {
      return {
        ...result,
        [key]: value.map((item) => updateStepVariables(item, newStepIds)),
      };
    }

    return {
      ...result,
      [key]: value,
    };
  }, {});
}

const duplicateFlow = async (_parent, params, context) => {
  context.currentUser.can('create', 'Flow');

  const flow = await context.currentUser
    .$relatedQuery('flows')
    .withGraphJoined('[steps]')
    .orderBy('steps.position', 'asc')
    .findOne({ 'flows.id': params.input.id })
    .throwIfNotFound();

  const duplicatedFlow = await context.currentUser
    .$relatedQuery('flows')
    .insert({
      name: `Copy of ${flow.name}`,
      active: false,
    });

  const newStepIds = {};
  for (const step of flow.steps) {
    const duplicatedStep = await duplicatedFlow.$relatedQuery('steps').insert({
      key: step.key,
      appKey: step.appKey,
      type: step.type,
      connectionId: step.connectionId,
      position: step.position,
      parameters: updateStepVariables(step.parameters, newStepIds),
    });

    if (duplicatedStep.isTrigger) {
      await duplicatedStep.updateWebhookUrl();
    }

    newStepIds[step.id] = duplicatedStep.id;
  }

  return duplicatedFlow;
};

export default duplicateFlow;
