import {type EmitterSubscription, NativeEventEmitter, NativeModules, Platform} from "react-native";
import {EnumCapturedResultItemType, type ImageData, ImageSourceAdapter} from "../core";
import type {CapturedResultReceiver} from "./CapturedResultReceiver";
import {type CapturedResult, _populateResults} from "./CapturedResult";
import type {BarcodeResultItem, DecodedBarcodesResult} from "../dbr";
import type {ProcessedDocumentResult} from "../ddn";
import type {RecognizedTextLinesResult} from "../dlr";
import type {ParsedResult} from "../dcp";
import type {CapturedResultFilter} from "./CapturedResultFilter";
import type {SimplifiedCaptureVisionSettings} from "./SimplifiedCaptureVisionSettings";
import {MultiFrameResultCrossFilter} from "../utility";
import {CameraEnhancer} from "../dce";

// @ts-ignore Check whether __turboModuleProxy exists, it may not
const isTurboModuleEnabled = global.__turboModuleProxy != null;

const CvrModule = !isTurboModuleEnabled ?
  NativeModules.DynamsoftCaptureVisionRouterModule :
  require("./NativeDynamsoftCaptureVisionRouterModule").default;

const CVREventEmitter = new NativeEventEmitter(CvrModule);
let isInstalled = false
const installMethods = () => {
  if (!isInstalled) {
    CvrModule.install()
    isInstalled = true
  }
}

/**
 * The singleton instance of CaptureVisionRouter.
 * */
let cvr: CaptureVisionRouter | null = null;

/**
 * The CaptureVisionRouter class defines how a user interacts with image-processing and semantic-processing products in their applications.
 * A CaptureVisionRouter instance accepts and processes images from an image source and returns processing results which may contain Final results or Intermediate Results.
 * <p>
 * In js end of react-native, CaptureVisionRouter uses singleton mode. You can only get CaptureVisionRouter instance by {@link CaptureVisionRouter.getInstance}.
 * */
export class CaptureVisionRouter {
  private constructor() {
    CvrModule.createInstance();
  }

  /**
   * Get the singleton instance of CaptureVisionRouter.
   * <p>
   * This method ensures that only one instance of CaptureVisionRouter is created
   * and reused throughout the application. If an instance already exists, it will
   * return the existing one. Otherwise, it will create a new instance and return it.
   *
   * @returns {CaptureVisionRouter} The singleton instance of CaptureVisionRouter.
   * */
  static getInstance(): CaptureVisionRouter {
    if (cvr) {
      return cvr;
    }
    return cvr = new CaptureVisionRouter();
  }

  /**
   * Destroys the CaptureVisionRouter instance and releases all associated resources.
   * After calling this method, the instance will be set to null and cannot be used anymore.
   * Only call this method when you want to completely dispose the CaptureVisionRouter instance, otherwise just call {@link CaptureVisionRouter.stopCapturing} to stop the capturing process.
   */
  dispose() {
    CvrModule.destroyInstance();
    cvr = null;
  }

  /**
   * Sets the global number of threads used internally for model execution.
   *
   * @param intraOpNumThreads - Number of threads used internally for model execution. Valid range: [0, 256].
   * If the value is outside the range [0, 256], it will be treated as 0 (default).
   */
  static setGlobalIntraOpNumThreads(intraOpNumThreads: number) {
    CvrModule.setGlobalIntraOpNumThreads(intraOpNumThreads);
  }

  /**
   * Clears all deep learning models from buffer to free up memory.
   */
  static clearDLModelBuffers() {
    CvrModule.clearDLModelBuffers();
  }

  /**
   * Initiates a capturing process based on a specified template. This process is repeated for each image fetched from the source.
   * <p>Code Snippet:</p>
   *
   *```
   * let router = CaptureVisionRouter.getInstance();
   * await router.startCapturing('ReadSingleBarcode');
   * ```
   * @param templateName - Specifies a “CaptureVisionTemplate” to use. The following value are available for this parameter:
   *  - One of the {@link EnumPresetTemplate} member. This is available only if you have never upload a new template via {@link initSettingsFromFile} or {@link initSettings}.
   *  - A string that represents one of the template name that you have uploaded via {@link initSettingsFromFile} or {@link initSettings}.
   *  - ""(empty string) to use the default template. The first template will be used if you have uploaded a template file via {@link initSettingsFromFile} or {@link initSettings}.
   *
   *  @return Promise<void> - A promise that resolves when the capturing process has successfully started. It does not provide any value upon resolution.
   *  @throws Error - If the capturing process fails to start, the promise will be rejected with an error.
   *  The error may occur due to invalid template names, or a capturing process is already in progress, etc.
   *
   * @remarks - Always make sure there is no capturing in process or just call `await CaptureVisionRouter.stopCapturing()` to stop any ongoing capturing process
   * before calling `startCapturing`.
   *
   * @see {@link EnumPresetTemplate}
   * */
  startCapturing(templateName: string | undefined | null = ""): Promise<void> {
    return CvrModule.startCapturing(templateName)
  }


  /**
   * Stops the capturing process.
   * <p>Code Snippet:</p>
   *
   *```
   * let router = await CaptureVisionRouter.getInstance();
   * await router.startCapturing("ReadSingleBarcode");
   * // ...
   * router.stopCapturing();
   * ```
   * */
  stopCapturing(): Promise<void> {
    return CvrModule.stopCapturing()
  }

  /**
   * Switch the capturing template during the image processing workflow.
   *
   * @param templateName -The name of the new capturing template to apply.
   *
   * @return Promise<void> - A promise that resolves when the template switch is successful. If the template switch fails, the promise will be rejected with an error.
   */
  switchCapturingTemplate(templateName: string): Promise<void> {
    return CvrModule.switchCapturingTemplate(templateName)
  }
  /**
   * Sets up an image source to provide images for continuous processing.
   * <p>Code Snippet:</p>
   *
   *```
   * let router = await CaptureVisionRouter.getInstance();
   * let cameraEnhancer = CameraEnhancer.getInstance();
   * router.setInput(cameraEnhancer);
   * ```
   *
   * @param input - The image source which is compliant with the {@link ImageSourceAdapter} interface.
   * @see {@link ImageSourceAdapter}
   * @see {@link CameraEnhancer}
   * */
  setInput(input: ImageSourceAdapter) {
    CvrModule.setInput(input._getIsaId())
  }

  private receiverMap = new Map<string, CapturedResultReceiver>();
  private onCapturedResultReceived?: EmitterSubscription;
  private onProcessedDocumentResultReceived?: EmitterSubscription;
  private onDecodedBarcodesReceived?: EmitterSubscription;
  private onRecognizedTextLinesReceived?: EmitterSubscription;
  private onParsedResultsReceived?: EmitterSubscription;

  /**
   * Adds a CapturedResultReceiver object as the receiver of captured results.
   * It will return the receiver added itself, convenient to use {@link removeResultReceiver} method.
   *
   * <p>Code Snippet:</p>
   *
   * ```
   * let router = await CaptureVisionRouter.getInstance();
   * let receiver = router.addResultReceiver({
   *      onCapturedResultReceived: result => {
   *          // Do something with the result
   *      },
   * });
   * ```
   *
   * @param receiver - The receiver object, of type {@link CapturedResultReceiver}.
   * @return return the receiver added in CapturedResultReceiver.
   * @see CaptureVisionRouter.removeResultReceiver
   * */
  addResultReceiver(receiver: CapturedResultReceiver): CapturedResultReceiver {
    let receiverEx = receiver as CapturedResultReceiver & { receiverId?: string };
    receiverEx.receiverId = receiverEx.receiverId || Math.random().toString(36).slice(-6);
    this.receiverMap.set(receiverEx.receiverId, receiverEx);
    if (!this.onCapturedResultReceived && receiver.onCapturedResultReceived) {
      if (Platform.OS == 'ios') {
        CvrModule.addResultReceiver('onCapturedResultReceived')
      }
      this.onCapturedResultReceived = CVREventEmitter.addListener('onCapturedResultReceived',
        async (result: CapturedResult) => {
          _populateResults(result)
          let processingDocumentResult = result.processingDocumentResult;
          if (processingDocumentResult && processingDocumentResult.deskewedImageResultItems && processingDocumentResult.deskewedImageResultItems.length > 0) {
            installMethods();
            let imageArr: ImageData[] | undefined | null = global.getCurrentDeskewedImages();
            if (imageArr) {
              for (let i = 0; i < processingDocumentResult.deskewedImageResultItems.length; i++) {
                processingDocumentResult.deskewedImageResultItems[i]!!.imageData = imageArr[i]!!;
              }
            }
          }
          if (processingDocumentResult && processingDocumentResult.enhancedImageResultItems && processingDocumentResult.enhancedImageResultItems.length > 0) {
            installMethods();
            let imageArr: ImageData[] | undefined | null = global.getCurrentEnhancedImages();
            if (imageArr) {
              for (let i = 0; i < processingDocumentResult.enhancedImageResultItems.length; i++) {
                processingDocumentResult.enhancedImageResultItems[i]!!.imageData = imageArr[i]!!;
              }
            }
          }
          for (let _receiver of this.receiverMap.values()) {
            await _receiver.onCapturedResultReceived?.(result);
          }
          CvrModule.continueCRR()
        }
      )
    }

    if (!this.onDecodedBarcodesReceived && receiver.onDecodedBarcodesReceived) {
      if (Platform.OS == 'ios') {
        CvrModule.addResultReceiver('onDecodedBarcodesReceived')
      }
      this.onDecodedBarcodesReceived = CVREventEmitter.addListener('onDecodedBarcodesReceived',
        async (result: DecodedBarcodesResult) => {
          result.items?.forEach((item: BarcodeResultItem, _) => {
            item.format = BigInt(item._formatNumberString!!)
          });
          for (let _receiver of this.receiverMap.values()) {
            await _receiver.onDecodedBarcodesReceived?.(result);
          }
          CvrModule.continueCRR()
        }
      )
    }

    if (!this.onRecognizedTextLinesReceived && receiver.onRecognizedTextLinesReceived) {
      if (Platform.OS == 'ios') {
        CvrModule.addResultReceiver('onRecognizedTextLinesReceived')
      }
      this.onRecognizedTextLinesReceived = CVREventEmitter.addListener('onRecognizedTextLinesReceived',
        async (result: RecognizedTextLinesResult) => {
          for (let _receiver of this.receiverMap.values()) {
            await _receiver.onRecognizedTextLinesReceived?.(result);
          }
          CvrModule.continueCRR()
        }
      )
    }

    if (!this.onProcessedDocumentResultReceived && receiver.onProcessedDocumentResultReceived) {
      if (Platform.OS == 'ios') {
        CvrModule.addResultReceiver('onProcessedDocumentResultReceived')
      }
      this.onProcessedDocumentResultReceived = CVREventEmitter.addListener('onProcessedDocumentResultReceived',
        async (result: ProcessedDocumentResult) => {
          if (result && result.deskewedImageResultItems && result.deskewedImageResultItems.length > 0) {
            installMethods();
            let imageArr: ImageData[] = global.getCurrentDeskewedImages();
            for (let i = 0; i < result.deskewedImageResultItems.length; i++) {
              result.deskewedImageResultItems[i]!!.imageData = imageArr[i]!!;
            }
          }
          if (result && result.enhancedImageResultItems && result.enhancedImageResultItems.length > 0) {
            installMethods();
            let imageArr: ImageData[] = global.getCurrentEnhancedImages();
            for (let i = 0; i < result.enhancedImageResultItems.length; i++) {
              result.enhancedImageResultItems[i]!!.imageData = imageArr[i]!!;
            }
          }
          for (let _receiver of this.receiverMap.values()) {
            await _receiver.onProcessedDocumentResultReceived?.(result);
          }
          CvrModule.continueCRR()
        }
      )
    }

    if (!this.onParsedResultsReceived && receiver.onParsedResultsReceived) {
      if (Platform.OS == 'ios') {
        CvrModule.addResultReceiver('onParsedResultsReceived')
      }
      this.onParsedResultsReceived = CVREventEmitter.addListener('onParsedResultsReceived',
        async (result: ParsedResult) => {
          for (let _receiver of this.receiverMap.values()) {
            await _receiver.onParsedResultsReceived?.(result);
          }
          CvrModule.continueCRR()
        }
      )
    }
    return receiver
  }

  /**
   * Removes the specified CapturedResultReceiver object.
   * <p>Code Snippet:</p>
   *
   *```
   * let router = await CaptureVisionRouter.getInstance();
   * let receiver = router.addResultReceiver({
   *      onCapturedResultReceived: result => {
   *          // Do something with the result
   *      },
   * });
   * //...
   * router.removeResultReceiver(receiver);
   * ```
   * @param receiver - The receiver object, of type CapturedResultReceiver.
   * */
  removeResultReceiver(receiver: CapturedResultReceiver): void {
    let receiverEx = receiver as CapturedResultReceiver & { receiverId?: string };
    if (receiverEx.receiverId) this.receiverMap.delete(receiverEx.receiverId);

    let needToKeepTypes = 0;
    let needToKeep_onCapturedResultReceived = false;
    for (let _receiver of this.receiverMap.values()) {
      if (_receiver.onCapturedResultReceived) needToKeep_onCapturedResultReceived = true;
      needToKeepTypes |= _receiver.onDecodedBarcodesReceived ? EnumCapturedResultItemType.CRIT_BARCODE : 0;
      needToKeepTypes |= _receiver.onRecognizedTextLinesReceived ? EnumCapturedResultItemType.CRIT_TEXT_LINE : 0;
      needToKeepTypes |= _receiver.onProcessedDocumentResultReceived ? (EnumCapturedResultItemType.CRIT_DETECTED_QUAD | EnumCapturedResultItemType.CRIT_DESKEWED_IMAGE | EnumCapturedResultItemType.CRIT_ENHANCED_IMAGE) : 0;
      needToKeepTypes |= _receiver.onParsedResultsReceived ? EnumCapturedResultItemType.CRIT_PARSED_RESULT : 0;
    }
    if (!needToKeep_onCapturedResultReceived) {
      this.onCapturedResultReceived = this.onCapturedResultReceived?.remove() || undefined
      CvrModule.removeResultReceiver('onCapturedResultReceived')
      CVREventEmitter.removeAllListeners('onCapturedResultReceived')
    }
    if ((needToKeepTypes & EnumCapturedResultItemType.CRIT_BARCODE) == 0) {
      this.onDecodedBarcodesReceived = this.onDecodedBarcodesReceived?.remove() || undefined
      CvrModule.removeResultReceiver('onDecodedBarcodesReceived')
      CVREventEmitter.removeAllListeners('onDecodedBarcodesReceived')
    }
    if ((needToKeepTypes & EnumCapturedResultItemType.CRIT_TEXT_LINE) == 0) {
      this.onRecognizedTextLinesReceived = this.onRecognizedTextLinesReceived?.remove() || undefined
      CvrModule.removeResultReceiver('onRecognizedTextLinesReceived')
      CVREventEmitter.removeAllListeners('onRecognizedTextLinesReceived')
    }
    if ((needToKeepTypes & (EnumCapturedResultItemType.CRIT_DETECTED_QUAD | EnumCapturedResultItemType.CRIT_DESKEWED_IMAGE | EnumCapturedResultItemType.CRIT_ENHANCED_IMAGE)) == 0) {
      this.onProcessedDocumentResultReceived = this.onProcessedDocumentResultReceived?.remove() || undefined
      CvrModule.removeResultReceiver('onProcessedDocumentResultReceived')
      CVREventEmitter.removeAllListeners('onProcessedDocumentResultReceived')
    }
    if ((needToKeepTypes & EnumCapturedResultItemType.CRIT_PARSED_RESULT) == 0) {
      this.onParsedResultsReceived = this.onParsedResultsReceived?.remove() || undefined
      CvrModule.removeResultReceiver('onParsedResultsReceived')
      CVREventEmitter.removeAllListeners('onParsedResultsReceived')
    }
  }

  /**
   *  Removes all CapturedResultReceiver object added in.
   * */
  removeAllResultListeners(): void {
    CvrModule.removeAllResultListeners()
    this.receiverMap.clear()
    this.onCapturedResultReceived?.remove()
    this.onDecodedBarcodesReceived?.remove()
    this.onProcessedDocumentResultReceived?.remove()
    this.onRecognizedTextLinesReceived?.remove()
    this.onParsedResultsReceived?.remove()
  }

  /**
   * Adds a CapturedResultFilter object  to filter non-essential results.
   * It will return the CapturedResultFilter added itself, convenient to use {@link removeFilter} method.
   *
   * Code snippet:
   * ```
   * let router = await CaptureVisionRouter.getInstance();
   * let filter = router.addFilter(new MultiFrameResultCrossFilter());
   * //...
   * router.removeFilter(filter);
   * ```
   *
   * @param filter - The result filter object, of type {@link CapturedResultFilter}.
   * @return return the result filter added in CapturedResultReceiver.
   * @see CaptureVisionRouter.removeFilter
   * @remarks Can only add {@link MultiFrameResultCrossFilter} object for now.
   * */
  addFilter(filter: CapturedResultFilter): CapturedResultFilter {
    CvrModule.addFilter(filter._getFilterId())
    return filter
  }

  /**
   * Removes the specified MultiFrameResultCrossFilter object.
   *
   * * @param filter - The specified result filter object removed, of type {@link CapturedResultFilter}.
   * */
  removeFilter(filter: CapturedResultFilter) {
    CvrModule.removeFilter(filter._getFilterId())
  }

  /**
   * Restores all runtime settings to their original default values.
   * @return {Promise<void>} - A promise that resolves when the operation has completed.
   * @throws Error - If resetSettings fails, the promise will be rejected with an error. The error may occur when
   *  - Function call is rejected when capturing in progress.
   * */
  resetSettings(): Promise<void> {
    return CvrModule.resetSettings()
  }

  /**
   * Get a simplified settings object for the specified template name.
   * @param templateName Specify a template with a templateName for the data capturing. If not specified, the preset template named 'Default' will be used.
   * @return {Promise<SimplifiedCaptureVisionSettings>} - A promise that resolves an object of SimplifiedCaptureVisionSettings.
   * @throws Error - If getSimplifiedSettings fails, the promise will be rejected with an error. The error may occur when
   *  - The target template name is invalid.
   *  - The template you specified is a complex template which can not be output as a SimplifiedCaptureVisionSettings object.
   *  - Function call is rejected when capturing in progress.
   * */
  async getSimplifiedSettings(templateName: string | undefined | null = ""): Promise<SimplifiedCaptureVisionSettings> {
    try {
      let settings: SimplifiedCaptureVisionSettings = await CvrModule.getSimplifiedSettings(templateName)
      if (settings.barcodeSettings) {
        settings.barcodeSettings.barcodeFormatIds = BigInt(settings.barcodeSettings?._barcodeFormatIdsNumberString!!);
      }
      return settings
    } catch (e) {
      throw e
    }
  }

  /**
   * Updates the specified templateName with an updated SimplifiedCaptureVisionSettings object.
   * Defined properties will be updated, and undefined properties will retain their original values.
   *
   * Code Snippet:
   * ```
   * let router = CaptureVisionRouter.getInstance();
   * let settings = {
   *    timeout: 1000,
   *    maxParallelTasks: 1,
   *    barcodeSettings: {
   *      expectedBarcodesCount: 999,
   *      barcodeFormatIds: EnumBarcodeFormat.BF_ONED | EnumBarcodeFormat.BF_QR_CODE,
   *    }
   * };
   * //Only timeout, maxParallelTasks, expectedBarcodesCount and barcodeFormatIds will be updated.
   * router.updateSettings("ReadSingleBarcode", settings);
   *
   * ```
   *
   * @param templateName Specify the name of the template that you want to update. If undefined, the preset template named 'Default' will be used.
   * @param settings An object of SimplifiedCaptureVisionSettings. If undefined, will not change settings.
   * @return {Promise<void>} - A promise that resolves when the operation has completed.
   * @throws Error - If updateSettings fails, the promise will be rejected with an error. The error may occur when
   *  - The target template name is invalid.
   *  - There exists invalid parameter value in your SimplifiedCaptureVisionSettings.
   *  - The template you specified is a complex template which can not be output as a SimplifiedCaptureVisionSettings object.
   *  - Function call is rejected when capturing in progress.
   *
   * @see {@link SimplifiedCaptureVisionSettings}
   *  */
  async updateSettings(settings?: SimplifiedCaptureVisionSettings | null, templateName: string | undefined | null = ""): Promise<void> {
    let barcodeFormatIds = settings?.barcodeSettings?.barcodeFormatIds
    try {
      if (barcodeFormatIds != undefined) {
        settings!!.barcodeSettings!!.barcodeFormatIds = undefined
        let settingsTemp: SimplifiedCaptureVisionSettings = JSON.parse(JSON.stringify(settings)) as SimplifiedCaptureVisionSettings;
        settingsTemp!!.barcodeSettings!!._barcodeFormatIdsNumberString = barcodeFormatIds.toString()
        settings!!.barcodeSettings!!.barcodeFormatIds = barcodeFormatIds
        await CvrModule.updateSettings(settingsTemp, templateName)
      } else {
        await CvrModule.updateSettings(settings ?? {}, templateName)
      }
    } catch (e) {
      throw e
    }
  }

  /**
   * Configures runtime settings using a provided JSON string, which contains settings for one or more CaptureVisionTemplates.
   *
   * @param content - A JSON string that contains Capture Vision settings.
   * @return {Promise<void>} - A promise that resolves or rejected with an error when the operation has completed.
   * */
  initSettings(content: string): Promise<void> {
    return CvrModule.initSettings(content)
  }

  /**
   * Configures runtime settings using a provided JSON file, which contains settings for one or more CaptureVisionTemplates.
   *
   * @param file - Absolute path of a JSON file that contains Capture Vision settings.
   * @return {Promise<void>} - A promise that resolves or rejected with an error when the operation has completed.
   * */
  initSettingsFromFile(file: string): Promise<void> {
    return CvrModule.initSettingsFromFile(file)
  }

  /**
   * Get a JSON string that contains settings for the specified templateName.
   *
   * @param templateName - The name of the template that you want to output.
   * @param includeDefaultValues - Whether to include default values in the output.
   * @return {Promise<string>} - A promise that resolves a JSON string that contains settings for the specified templateName.
   * */
  outputSettings(templateName: string, includeDefaultValues: boolean): Promise<string> {
    return CvrModule.outputSettings(templateName, includeDefaultValues)
  }

  /**
   * Generates a JSON file download containing the settings for the specified templateName and saved to specified file path.
   * @param templateName - The name of the template that you want to output.
   * @param file - The absolute file path that you want to save the template.
   * @param includeDefaultValues - Whether to include default values in the output.
   * @return {Promise<void>} - A promise that resolves or rejected with an error when the operation has completed.
   * */
  outputSettingsToFile(templateName: string, file: string, includeDefaultValues: boolean): Promise<void> {
    return CvrModule.outputSettingsToFile(templateName, file, includeDefaultValues)
  }

  /**
   * Processes {@link ImageData} object to derive important information.
   * <p>Code Snippet:</p>
   *
   *```
   * let cameraEnhancer = CameraEnhancer.getInstance();
   * let router = CaptureVisionRouter.getInstance();
   * let imageData = cameraEnhancer.getImage();
   * let result = router.capture(imageData, "ReadSingleBarcode");
   * for(let i = 0; i < result.items.length; i++) {
   *     //...
   * }
   * ```
   *
   * @param imageData - An {@link ImageData} object that contains image info.
   * @param template - Specifies a “CaptureVisionTemplate” to use. The following value are available for this parameter:
   *  - One of the {@link EnumPresetTemplate} member. This is available only if you have never upload a new template via {@link initSettingsFromFile} or {@link initSettings}.
   *  - A string that represents one of the template name that you have uploaded via {@link initSettingsFromFile} or {@link initSettings}.
   *  - ""(empty string) to use the default template. The first template will be used if you have uploaded a template file via {@link initSettingsFromFile} or {@link initSettings}.
   *
   * @return A {@link CapturedResult} object which contains the derived information from the image processed.
   * If an error occurs when processing the image, the CapturedResult object will include error code and error message that describes the reason of the error.
   *
   * @see {@link EnumPresetTemplate}
   * @see {@link CapturedResult}
   * */
  capture(imageData: ImageData, template: string = ""): CapturedResult | undefined | null {
    installMethods();
    if (imageData) {
      if (typeof global.captureImageData !== 'function') {
        return null; 
      }
      let result = global.captureImageData(imageData, template);
      _populateResults(result)
      return result;
    } else {
      return null;
    }
  }

  /**
   * Processes a file containing a single image to derive important information.
   * <p>Code Snippet:</p>
   *
   *```
   * let router = CaptureVisionRouter.getInstance();
   * let result = router.capture("absolute-image-file-path", "ReadSingleBarcode");
   * for(let i = 0; i < result.items.length; i++) {
   *     //...
   * }
   * ```
   *
   * @param filePath - The absolute file path and name that you want to capture data from.
   *                    You have to specify the file name with extension name in the filePath.
   *                    Supported file type includes “.bmp”, “.jpg”, “.png”, “.gif” or one-page “.tiff”.
   * @param template - Specifies a “CaptureVisionTemplate” to use. The following value are available for this parameter:
   *  - One of the {@link EnumPresetTemplate} member. This is available only if you have never upload a new template via {@link initSettingsFromFile} or {@link initSettings}.
   *  - A string that represents one of the template name that you have uploaded via {@link initSettingsFromFile} or {@link initSettings}.
   *  - ""(empty string) to use the default template. The first template will be used if you have uploaded a template file via {@link initSettingsFromFile} or {@link initSettings}.
   *
   * @return A {@link CapturedResult} object which contains the derived information from the image processed.
   * If an error occurs when processing the image, the CapturedResult object will include error code and error message that describes the reason of the error.
   *
   * @see {@link EnumPresetTemplate}
   * @see {@link CapturedResult}
   * */
  captureFile(filePath: string, template: string = ""): CapturedResult | undefined | null {
    installMethods();
    if (filePath) {
      if (typeof global.captureFile !== 'function') {
        return null; 
      }
      let result = global.captureFile(filePath, template);
      _populateResults(result)
      return result;
    } else {
      return null;
    }
  }

  /**
   * Processes An ArrayBuffer object that points to a file in memory to derive important information.
   *
   * @param fileBytes - An ArrayBuffer object that points to a file in memory.
   * @param template - Specifies a “CaptureVisionTemplate” to use. The following value are available for this parameter:
   *  - One of the {@link EnumPresetTemplate} member. This is available only if you have never upload a new template via {@link initSettingsFromFile} or {@link initSettings}.
   *  - A string that represents one of the template name that you have uploaded via {@link initSettingsFromFile} or {@link initSettings}.
   *  - ""(empty string) to use the default template. The first template will be used if you have uploaded a template file via {@link initSettingsFromFile} or {@link initSettings}.
   *
   * @return A {@link CapturedResult} object which contains the derived information from the image processed.
   * If an error occurs when processing the image, the CapturedResult object will include error code and error message that describes the reason of the error.
   *
   * @see {@link EnumPresetTemplate}
   * @see {@link CapturedResult}
   * */
  captureFileBytes(fileBytes: ArrayBuffer, template: string = ""): CapturedResult | undefined | null {
    installMethods();
    if (fileBytes) {
      if (typeof global.captureFileBytes !== 'function') {
        return null; 
      }
      let result = global.captureFileBytes(fileBytes, template);
      _populateResults(result)
      return result;
    } else {
      return null;
    }
  }

  private intermediateResultManager: IntermediateResultManager | undefined = undefined;

  /**@see {@link IntermediateResultManager}*/
  getIntermediateResultManager(): IntermediateResultManager {
    if (!this.intermediateResultManager) {
      this.intermediateResultManager = new IntermediateResultManager();
    }
    return this.intermediateResultManager!!;
  }
}

/**
 * The IntermediateResultManager class is responsible for handling intermediate results obtained during the process of an image.
 * @see {@link CaptureVisionRouter.getIntermediateResultManager}
 * @hideconstructor
 * */
export class IntermediateResultManager {
  /**
   * Retrieves the original image data.
   * <p>Code Snippet:</p>
   *
   *```
   * let router = CaptureVisionRouter.getInstance();
   * let intermediateResultManager = router.getIntermediateResultManager()
   * router.addResultReceiver({
   *    onCaptureResultReceive: result => {
   *      let imageData = intermediateResultManager.getOriginalImage(result.originalImageHashId)
   *      //...
   *    }
   * });
   * ```
   *
   * @param imageHashId - The image hash ID. Get from {@link CapturedResult.originalImageHashId}, {@link DecodedBarcodesResult.originalImageHashId},
   *    {@link ProcessedDocumentResult.originalImageHashId}, {@link RecognizedTextLinesResult.originalImageHashId}.
   *
   * @remarks `Do not call this method after calling stopCapturing() or outside the CapturedResultReceiver callback where you get the hashId, otherwise you will not get any valid ImageData.`
   * @see {@link CaptureVisionRouter.addResultReceiver}
   * @see {@link CapturedResultReceiver}
   * */
  getOriginalImage(imageHashId: string): ImageData | undefined | null {
    installMethods();
    if (typeof global.cvr_getOriginalImage !== 'function') {
      return null;
    }
    return global.cvr_getOriginalImage(imageHashId);
  }
}

declare var global: {
  captureImageData: (imageData: ImageData, template: string) => CapturedResult | undefined | null;
  captureFile: (file: string, template: string) => CapturedResult | undefined | null;
  captureFileBytes: (fileBytes: ArrayBuffer, template: string) => CapturedResult | undefined | null;
  getCurrentDeskewedImages: () => ImageData[] | undefined | null;
  getCurrentEnhancedImages: () => ImageData[] | undefined | null;
  cvr_getOriginalImage: (imageHashId: string) => ImageData | undefined | null;
}
