import { isEqual } from 'lodash';
import { v4 as uuidv4 } from 'uuid';

import { isValidMultiStepDef, isValidStepDef, TriggerStepType, ValidStepDef } from '../types/page/event.js';
import { refactorNameInCode, refactorNameInValue, ScopedDataTree } from './refactor.js';
import { Extractor } from './types.js';

type AddUpdate = (value: { propertyName: string; propertyValue: unknown }) => void;

export function createTriggerStepRenamer({
  oldName,
  newName,
  dataTree,
  addUpdate,
  extractEvaluationPairs,
  updateStepIds,
  namespace
}: {
  oldName: string;
  newName: string;
  dataTree: ScopedDataTree;
  extractEvaluationPairs: Extractor;
  addUpdate: AddUpdate;
  updateStepIds?: boolean; // whether or not to regenerate IDs
  namespace?: string;
}) {
  const renameInTriggerStep = async (step: ValidStepDef, key: string, index: number) => {
    if (!isValidStepDef(step)) return;
    if (updateStepIds) {
      addUpdate({
        propertyName: `${key}[${index}].id`,
        propertyValue: uuidv4()
      });
    }
    switch (step.type) {
      case TriggerStepType.RUN_APIS: {
        if (step.apiNames) {
          const newValue = step.apiNames.map((name) => (name === oldName ? newName : name));
          if (!isEqual(step.apiNames, newValue)) {
            addUpdate({
              propertyName: `${key}[${index}].apiNames`,
              propertyValue: newValue
            });
          }
        }
        if (step.onSuccess && isValidMultiStepDef(step.onSuccess)) {
          let i = 0;
          for (const nestedStep of step.onSuccess) {
            await renameInTriggerStep(nestedStep, `${key}[${index}].onSuccess`, i++);
          }
        }
        if (step.onError && isValidMultiStepDef(step.onError)) {
          let i = 0;
          for (const nestedStep of step.onError) {
            await renameInTriggerStep(nestedStep, `${key}[${index}].onError`, i++);
          }
        }
        break;
      }
      case TriggerStepType.CANCEL_APIS: {
        if (step.apiNames) {
          const newValue = step.apiNames.map((name) => (name === oldName ? newName : name));
          if (!isEqual(step.apiNames, newValue)) {
            addUpdate({
              propertyName: `${key}[${index}].apiNames`,
              propertyValue: newValue
            });
          }
        }
        break;
      }
      case TriggerStepType.RUN_JS: {
        const newValue = await refactorNameInCode({
          value: step.code ?? '',
          oldName,
          newName,
          dataTree,
          extractPairs: extractEvaluationPairs,
          namespace
        });
        if (!isEqual(step.code, newValue)) {
          addUpdate({
            propertyName: `${key}[${index}].code`,
            propertyValue: newValue
          });
        }
        break;
      }
      case TriggerStepType.CONTROL_SLIDEOUT:
      /* falls through */
      case TriggerStepType.CONTROL_MODAL:
      /* falls through */
      case TriggerStepType.CONTROL_TIMER: {
        if (step.name === oldName) {
          addUpdate({
            propertyName: `${key}[${index}].name`,
            propertyValue: newName
          });
        }
        break;
      }
      case TriggerStepType.NAVIGATE_TO: {
        const newValue = await refactorNameInValue({
          oldValue: step.url,
          oldName,
          newName,
          dataTree,
          extractPairs: extractEvaluationPairs,
          namespace
        });
        if (!isEqual(step.url, newValue)) {
          addUpdate({
            propertyName: `${key}[${index}].url`,
            propertyValue: newValue
          });
        }
        break;
      }
      case TriggerStepType.NAVIGATE_TO_APP: {
        const newValue = await refactorNameInValue({
          oldValue: step.queryParams,
          oldName,
          newName,
          dataTree,
          extractPairs: extractEvaluationPairs,
          namespace
        });
        if (!isEqual(step.queryParams, newValue)) {
          addUpdate({
            propertyName: `${key}[${index}].queryParams`,
            propertyValue: newValue
          });
        }
        break;
      }
      case TriggerStepType.NAVIGATE_TO_ROUTE: {
        const newQueryParams = await refactorNameInValue({
          oldValue: step.queryParams,
          oldName,
          newName,
          dataTree,
          extractPairs: extractEvaluationPairs,
          namespace
        });
        const newRouteParams = await refactorNameInValue({
          oldValue: step.routeParams,
          oldName,
          newName,
          dataTree,
          extractPairs: extractEvaluationPairs,
          namespace
        });

        if (!isEqual(step.queryParams, newQueryParams)) {
          addUpdate({
            propertyName: `${key}[${index}].queryParams`,
            propertyValue: newQueryParams
          });
        }
        if (!isEqual(step.routeParams, newRouteParams)) {
          addUpdate({
            propertyName: `${key}[${index}].routeParams`,
            propertyValue: newRouteParams
          });
        }
        break;
      }
      case TriggerStepType.SET_QUERY_PARAMS: {
        const newQueryParams = await refactorNameInValue({
          oldValue: step.queryParams,
          oldName,
          newName,
          dataTree,
          extractPairs: extractEvaluationPairs,
          namespace
        });
        if (!isEqual(step.queryParams, newQueryParams)) {
          addUpdate({
            propertyName: `${key}[${index}].queryParams`,
            propertyValue: newQueryParams
          });
        }
        break;
      }
      case TriggerStepType.SET_STATE_VAR: {
        const newValue = await refactorNameInValue({
          oldValue: step.value,
          oldName,
          newName,
          dataTree,
          extractPairs: extractEvaluationPairs,
          namespace
        });
        if (!isEqual(step.value, newValue)) {
          addUpdate({
            propertyName: `${key}[${index}].value`,
            propertyValue: newValue
          });
        }
        if (step.state?.name === oldName) {
          addUpdate({
            propertyName: `${key}[${index}].state.name`,
            propertyValue: newName
          });
        }
        break;
      }
      case TriggerStepType.RESET_STATE_VAR: {
        if (step.state?.name === oldName) {
          addUpdate({
            propertyName: `${key}[${index}].state.name`,
            propertyValue: newName
          });
        }
        break;
      }
      case TriggerStepType.RESET_COMPONENT: {
        if (step.widget?.name === oldName) {
          addUpdate({
            propertyName: `${key}[${index}].widget.name`,
            propertyValue: newName
          });
        }
        break;
      }
      case TriggerStepType.SET_COMPONENT_PROPERTY: {
        if (step.widget?.name === oldName) {
          addUpdate({
            propertyName: `${key}[${index}].widget.name`,
            propertyValue: newName
          });
        }
        const newValue = await refactorNameInValue({
          oldValue: step.propertyValue,
          oldName,
          newName,
          dataTree,
          extractPairs: extractEvaluationPairs,
          namespace
        });
        if (step?.propertyValue !== newValue) {
          addUpdate({
            propertyName: `${key}[${index}].propertyValue`,
            propertyValue: newValue
          });
        }
        break;
      }
      case TriggerStepType.SHOW_ALERT: {
        const newValue = await refactorNameInValue({
          oldValue: step.message,
          oldName,
          newName,
          dataTree,
          extractPairs: extractEvaluationPairs,
          namespace
        });
        if (!isEqual(step.message, newValue)) {
          addUpdate({
            propertyName: `${key}[${index}].message`,
            propertyValue: newValue
          });
        }
        break;
      }
      case TriggerStepType.SET_PROFILE: {
        const newValue = await refactorNameInValue({
          oldValue: step.profileId,
          oldName,
          newName,
          dataTree,
          extractPairs: extractEvaluationPairs,
          namespace
        });
        if (!isEqual(step.profileId, newValue)) {
          addUpdate({
            propertyName: `${key}[${index}].profileId`,
            propertyValue: newValue
          });
        }
        break;
      }
      case TriggerStepType.TRIGGER_EVENT: {
        const payload = step.eventPayload ?? {};
        // 1. Refactor the name in the values of the eventPayload
        const newValues: string[] = (await refactorNameInValue({
          oldValue: Object.values(payload),
          oldName,
          newName,
          dataTree,
          extractPairs: extractEvaluationPairs,
          namespace
        })) as string[];
        const newEventPayload = Object.keys(payload).reduce(
          (acc, key, i) => {
            acc[key] = newValues[i];
            return acc;
          },
          {} as Record<string, string>
        );
        if (!isEqual(payload, newEventPayload)) {
          addUpdate({
            propertyName: `${key}[${index}].eventPayload`,
            propertyValue: newEventPayload
          });
        }
        // 2. Update the event.name if it matches the oldName
        if (step.event?.name === oldName) {
          addUpdate({
            propertyName: `${key}[${index}].event.name`,
            propertyValue: newName
          });
        }
        break;
      }
      case TriggerStepType.RUN_SUPERBLOCKS_OPTIMIZED_APIS: {
        break;
      }
      default: {
        const exhaustiveCheck: never = step;
        throw new Error(`Unhandled action case: ${exhaustiveCheck}`);
      }
    }
  };
  return renameInTriggerStep;
}
