import { NativeEventEmitter, Platform } from 'react-native';
import {
  BarcodeDocumentFormat,
  BarcodeDocumentParserResult,
  BarcodeItem,
  BarcodeItemMapper,
  BarcodeMappedData,
  BarcodeScannerConfiguration,
  BarcodeScannerResult,
  BarcodeScannerScreenConfiguration,
  BarcodeScannerUiResult,
  ImageInput,
  MultipleScanningMode,
  ResultWrapper,
  SingleScanningMode,
  createSBError,
  handleImageInput,
  mapRTUUIResult,
  withSBErrorHandling,
} from '../types';
import { ScanbotBarcodeSDKModule } from './scanbotBarcodeSDKModule';

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

/**
 * @internal
 * @hidden
 */
export const ScanbotBarcodeImpl = {
  async startScanner(
    configuration: BarcodeScannerScreenConfiguration
  ): Promise<ResultWrapper<BarcodeScannerUiResult>> {
    const barcodeItemMapperEventName: string = 'barcodeItemMapperEvent';
    let barcodeItemMapperCallback: BarcodeItemMapper | null = null;
    let barcodeItemMapperEventEmitter: NativeEventEmitter | undefined;

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

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

        barcodeItemMapperEventEmitter.addListener(
          barcodeItemMapperEventName,
          (barcodeItem: BarcodeItem) => {
            const barcodeItemUuid = `${barcodeItem.text}${barcodeItem.upcEanExtension}_${barcodeItem.format}`;

            barcodeItemMapperCallback!(
              barcodeItem,
              (barcodeMappedData: BarcodeMappedData) =>
                ScanbotBarcodeSDKModule.onBarcodeItemMapper(barcodeItemUuid, barcodeMappedData),
              () => ScanbotBarcodeSDKModule.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
        configuration.useCase.barcodeInfoMapping.barcodeItemMapper = null;
      }
    }

    try {
      const barcodeResult = await ScanbotBarcodeSDKModule.startBarcodeScanner(
        isIOS ? JSON.stringify(configuration) : configuration,
        !!barcodeItemMapperCallback
      );
      return mapRTUUIResult<BarcodeScannerUiResult>(barcodeResult, BarcodeScannerUiResult);
    } catch (error: any) {
      throw createSBError(error);
    } finally {
      barcodeItemMapperEventEmitter?.removeAllListeners(barcodeItemMapperEventName);
      barcodeItemMapperEventEmitter = undefined;
    }
  },

  scanFromImage(params: {
    image: ImageInput;
    configuration: BarcodeScannerConfiguration;
  }): Promise<BarcodeScannerResult> {
    return withSBErrorHandling(
      async () =>
        new BarcodeScannerResult(
          await ScanbotBarcodeSDKModule.scanBarcodesFromImage({
            configuration: params.configuration,
            image: handleImageInput(params.image),
          })
        )
    );
  },

  scanFromPdf(params: {
    pdfFileUri: string;
    configuration: BarcodeScannerConfiguration;
  }): Promise<BarcodeScannerResult> {
    return withSBErrorHandling(
      async () =>
        new BarcodeScannerResult(await ScanbotBarcodeSDKModule.scanBarcodesFromPdf(params))
    );
  },

  parseDocument(params: {
    rawText: string;
    acceptedFormats?: BarcodeDocumentFormat[];
  }): Promise<BarcodeDocumentParserResult> {
    return withSBErrorHandling(
      async () =>
        new BarcodeDocumentParserResult(await ScanbotBarcodeSDKModule.parseBarcodeDocument(params))
    );
  },
};
