/**
 * Compares two arrays and returns an object holding an array for the added and another one for the removed items.
 *
 * @param current The array of values after the changes were performed
 * @param initial The array of values before the changes were performed
 */
export function getArrayDiff<T>(
  current: T[] | null | undefined,
  initial: T[] | null | undefined,
): {
  /** The items that were added */
  added: T[];
  /** The items that were removed */
  removed: T[];
};

/**
 * Compares two arrays and returns an object holding an array for the added and another one for the removed items.
 *
 * @param current The array of values after the changes were performed
 * @param initial The array of values before the changes were performed
 * @param key The name of the property that will be used to identify objects to compare them for updates
 */
export function getArrayDiff<T>(
  current: T[] | null | undefined,
  initial: T[] | null | undefined,
  key?: keyof T,
): {
  /** The items that were added */
  added: T[];
  /** The items that were removed */
  removed: T[];
  /** The items that were updated */
  updated: T[];
};

export function getArrayDiff<T>(
  current: T[] | null | undefined,
  initial: T[] | null | undefined,
  key?: keyof T,
): {
  /** The items that were added */
  added: T[];
  /** The items that were removed */
  removed: T[];
  /** The items that were updated */
  updated: T[];
} {
  const currentArray: T[] = current ?? [];
  const originalArray: T[] = initial ?? [];

  const added = currentArray.filter(
    (x: T) =>
      !originalArray.find((e) => {
        if (key) {
          return e[key] === x[key];
        }
        return e === x;
      }),
  );

  const removed = originalArray.filter(
    (x: T) =>
      !currentArray.find((e) => {
        if (key && typeof key === 'string') {
          return e[key] === x[key];
        }
        return e === x;
      }),
  );

  const updated: T[] = [];

  if (key && typeof key === 'string') {
    updated.push(
      ...currentArray.filter((x: T) =>
        originalArray.find((e) => e[key] === x[key] && isUpdated(e, x)),
      ),
    );
  }

  return { added, removed, updated };
}

function isUpdated<U>(x: U, y: U): boolean {
  let value = false;

  if (isObject(x) && isObject(y)) {
    Object.keys(x).forEach((key) => {
      if (x[key] !== y[key]) {
        value = true;
      }
    });
  }

  return value;
}

const isObject = (input: unknown): input is Record<string, unknown> => {
  return typeof input === 'object' && input !== null && !Array.isArray(input);
};
