import ExpoUpdatesModule from './ExpoUpdates';
import type {
  UpdatesNativeStateChangeEvent,
  UpdatesNativeStateMachineContext,
} from './Updates.types';

export let latestContext = transformNativeStateMachineContext(ExpoUpdatesModule.initialContext);

ExpoUpdatesModule.addListener('Expo.nativeUpdatesStateChangeEvent', _handleNativeStateChangeEvent);

interface UpdatesStateChangeSubscription {
  remove(): void;
}

const _updatesStateChangeListeners = new Set<(event: UpdatesNativeStateChangeEvent) => void>();

// Reemits native state change events
function _handleNativeStateChangeEvent(params: any) {
  const newParams = typeof params === 'string' ? JSON.parse(params) : { ...params };
  const transformedContext = transformNativeStateMachineContext(newParams.context);

  // only process state change events if they are in order
  if (transformedContext.sequenceNumber <= latestContext.sequenceNumber) {
    return;
  }

  newParams.context = transformedContext;
  latestContext = transformedContext;
  _updatesStateChangeListeners.forEach((listener) => listener(newParams));
}

/**
 * Add listener for state change events
 * @hidden
 */
export const addUpdatesStateChangeListener = (
  listener: (event: UpdatesNativeStateChangeEvent) => void
): UpdatesStateChangeSubscription => {
  _updatesStateChangeListeners.add(listener);
  return {
    remove() {
      _updatesStateChangeListeners.delete(listener);
    },
  };
};

/**
 * Allows JS test to emit a simulated native state change event (used in unit testing)
 * @hidden
 */
export const emitTestStateChangeEvent = (event: UpdatesNativeStateChangeEvent) => {
  _handleNativeStateChangeEvent(event);
};

/**
 * Allows JS test to reset latest context (and sequence number)
 * @hidden
 */
export const resetLatestContext = () => {
  latestContext = transformNativeStateMachineContext(ExpoUpdatesModule.initialContext);
};

function transformNativeStateMachineContext(
  originalNativeContext: UpdatesNativeStateMachineContext & {
    latestManifestString?: string;
    downloadedManifestString?: string;
    lastCheckForUpdateTimeString?: string;
    rollbackString?: string;
  }
): UpdatesNativeStateMachineContext {
  const nativeContext = { ...originalNativeContext };
  if (nativeContext.latestManifestString) {
    nativeContext.latestManifest = JSON.parse(nativeContext.latestManifestString);
    delete nativeContext.latestManifestString;
  }
  if (nativeContext.downloadedManifestString) {
    nativeContext.downloadedManifest = JSON.parse(nativeContext.downloadedManifestString);
    delete nativeContext.downloadedManifestString;
  }
  if (nativeContext.lastCheckForUpdateTimeString) {
    nativeContext.lastCheckForUpdateTime = new Date(nativeContext.lastCheckForUpdateTimeString);
    delete nativeContext.lastCheckForUpdateTimeString;
  }
  if (nativeContext.rollbackString) {
    nativeContext.rollback = JSON.parse(nativeContext.rollbackString);
    delete nativeContext.rollbackString;
  }
  return nativeContext;
}
