import { NativeEventEmitter, NativeModules, Platform } from 'react-native';

import { ResultWrapper } from '../customTypes';

import { BarcodeScannerConfiguration } from './barcode/BarcodeScannerConfiguration';
import { BarcodeScannerResult } from './barcode/BarcodeResult';
import { BarcodeMappedData } from './barcode/BarcodeInfoMapping';
import { BarcodeItemMapper } from './BarcodeItemMapper';
import { SingleScanningMode } from './barcode/SingleScanningModeUseCase';
import { MultipleScanningMode } from './barcode/MultipleScanningModeUseCase';
import { BarcodeItem } from './barcode/BarcodeItem';

const LINKING_ERROR =
  `The package 'react-native-scanbot-barcode-scanner-sdk' doesn't seem to be linked. Make sure: \n\n` +
  Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
  '- You rebuilt the app after installing the package\n' +
  '- You are not using Expo Go\n';

const ScanbotBarcodeSDKImpl = NativeModules.ScanbotBarcodeSdk
  ? NativeModules.ScanbotBarcodeSdk
  : new Proxy(
      {},
      {
        get() {
          throw new Error(LINKING_ERROR);
        },
      }
    );

const isIOS: boolean = Platform.OS === 'ios';

/**
 * Opens the Ready-To-Use UI screen for scanning barcodes with the desired configuration.
 *
 * @param {BarcodeScannerConfiguration} config
 * @returns {Promise<ResultWrapper<BarcodeScannerResult>>}
 */
export function startBarcodeScanner(
  config: BarcodeScannerConfiguration
): Promise<ResultWrapper<BarcodeScannerResult>> {
  const barcodeItemMapperEventName: string = 'barcodeItemMapperEvent';
  let barcodeItemMapperCallback: BarcodeItemMapper | null = null;
  let barcodeItemMapperEventEmitter: NativeEventEmitter | null = null;

  if (
    config.useCase instanceof SingleScanningMode ||
    config.useCase instanceof MultipleScanningMode
  ) {
    barcodeItemMapperCallback = config.useCase.barcodeInfoMapping.barcodeItemMapper;

    if (barcodeItemMapperCallback) {
      barcodeItemMapperEventEmitter = new NativeEventEmitter(ScanbotBarcodeSDKImpl);
      barcodeItemMapperEventEmitter.removeAllListeners(barcodeItemMapperEventName);

      barcodeItemMapperEventEmitter.addListener(
        barcodeItemMapperEventName,
        (barcodeItem: BarcodeItem) => {
          const barcodeItemUuid = `${barcodeItem.textWithExtension}_${barcodeItem.type ?? ''}`;

          barcodeItemMapperCallback!(
            barcodeItem,
            (barcodeMappedData: BarcodeMappedData) =>
              ScanbotBarcodeSDKImpl.onBarcodeItemMapper(barcodeItemUuid, barcodeMappedData),
            () => ScanbotBarcodeSDKImpl.onBarcodeItemMapper(barcodeItemUuid, null)
          );
        }
      );

      // On iOS, the communication with the native part is throwing an error (startBarcodeScannerV2) because of the barcodeItemMapper
      // callback in the configuration class (not parsable), that's why we need to remove it before executing startBarcodeScannerV2 method
      config.useCase.barcodeInfoMapping.barcodeItemMapper = null;

      return new Promise<ResultWrapper<BarcodeScannerResult>>((resolve, reject) => {
        ScanbotBarcodeSDKImpl.startBarcodeScannerV2(isIOS ? JSON.stringify(config) : config, true)
          .then((result: ResultWrapper<BarcodeScannerResult>) => {
            barcodeItemMapperEventEmitter?.removeAllListeners(barcodeItemMapperEventName);
            barcodeItemMapperEventEmitter = null;

            resolve(result);
          })
          .catch((error: any) => {
            barcodeItemMapperEventEmitter?.removeAllListeners(barcodeItemMapperEventName);
            barcodeItemMapperEventEmitter = null;

            reject(error);
          });
      });
    }
  }

  return ScanbotBarcodeSDKImpl.startBarcodeScannerV2(
    isIOS ? JSON.stringify(config) : config,
    false
  );
}

export * from './barcode/ArTrackingOverlayConfiguration';
export * from './barcode/BarcodeInfoMapping';
export * from './barcode/BarcodeItem';
export * from './barcode/BarcodeRecognizerConfiguration';
export * from './barcode/BarcodeResult';
export * from './barcode/BarcodeScannerConfiguration';
export * from './barcode/BarcodeTextLocalization';
export * from './barcode/BarcodeUseCase';
export * from './barcode/FindAndPickScanningModeUseCase';
export * from './barcode/MultipleScanningModeUseCase';
export * from './barcode/SingleScanningModeUseCase';

export * from './common/ActionBarConfiguration';
export * from './common/CameraConfiguration';
export * from './common/CameraPermission';
export * from './common/Common';
export * from './common/NavigationBarConfiguration';
export * from './common/ScanbotAlertDialog';
export * from './common/TopBarConfiguration';
export * from './common/UserGuidanceConfiguration';
export * from './common/ViewFinderConfiguration';

export * from './BarcodeItemMapper';
