//
//  ScanbotSDKCapacitorPlugin.swift
//  Plugin
//
//  Copyright © 2024 Scanbot SDK GmbH. All rights reserved.
//

import Capacitor
import Foundation
import ScanbotSDK
import ScanbotSDKNativeWrapper

@objc(ScanbotSDKCapacitorPlugin)
public class ScanbotSDKCapacitorPlugin: CAPPlugin, CAPBridgedPlugin {

    // MARK: - Methods from CAPBridgedPlugin. We use this protocol to avoid creating .h and .m files for exporting swift to objc
    public var identifier: String = "ScanbotSDKCapacitorPlugin"

    public var jsName: String = "ScanbotSDKCapacitor"

    // Need to define all methods here to be available for the Capacitor web runtime
    public var pluginMethods: [CAPPluginMethod] = [
        /* RTU UI */
        CAPPluginMethod(name: "startDocumentScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startFinderDocumentScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startCroppingScreen", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startMrzScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startBarcodeScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startTextDataScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startGenericDocumentRecognizer", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startBatchBarcodeScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startEHICScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startCheckRecognizer", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startLicensePlateScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startMedicalCertificateRecognizer", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startVinScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeDocumentScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeFinderDocumentScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeCroppingScreen", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeMrzScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeBarcodeScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeTextDataScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeGenericDocumentRecognizer", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeBatchBarcodeScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeEHICScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeCheckRecognizer", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeLicensePlateScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeMedicalCertificateRecognizer", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeVinScanner", returnType: CAPPluginReturnPromise),
        /* RTU UI V2 */
        CAPPluginMethod(name: "startBarcodeScannerV2", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeBarcodeScannerV2", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "registerBarcodeItemMapperCallback", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "onBarcodeItemMapper", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "startDocumentScannerV2", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startCroppingScreenV2", returnType: CAPPluginReturnPromise),
        /* SDK OPERATIONS */
        CAPPluginMethod(name: "initializeSDK" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "getLicenseInfo" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "detectBarcodesOnImage" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "applyImageFilters" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "applyImageFiltersOnPage" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "getImageData" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "rotateImage" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "createPage" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "removePage" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "rotatePage" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "setDocumentImage" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "detectDocument" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "detectDocumentOnPage" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "documentQualityAnalyzer" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "extractImagesFromPdf" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "extractPagesFromPdf" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "recognizeCheck" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "recognizeMrz" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "recognizeMedicalCertificate" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "recognizeEHIC" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "recognizeGenericDocument" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "refreshImageUris" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "getOCRConfigs" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "cleanup" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "performOCR" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "createPDF" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "writeTIFF" , returnType: CAPPluginReturnPromise),
        /* Document Operations */
        CAPPluginMethod(name: "createDocument" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "createDocumentFromLegacyPages" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "documentExists" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "loadDocument" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "storedDocumentIDs" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "cloneDocument" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "deleteDocument" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "createPDFForDocument" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "createTIFF" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "addPage" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "movePage" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "modifyPage" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "removePageFromDocument" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "removeAllPages" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "createDocumentFromPDF" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "deleteAllDocuments" , returnType: CAPPluginReturnPromise),
    ];

    private let missingRequiredPropertyErrorMsg = "Missing required property "
    private let rtuuiUnexpectedErrorMsg = "Invalid scanner configuration was provided"
    private let rtuuiCantBePresented = "View controller can't be presented"

    private var barcodeItemMapperPluginCall : CAPPluginCall? = nil

    // MARK: - SDK Operations
    @objc func initializeSDK(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDWrapper.initializeSDK(configuration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func getLicenseInfo(_ call: CAPPluginCall) {
        SBDWrapper.getLicenseInfo(resultDelegate: ResultDelegate(call))
    }

    @objc public func detectBarcodesOnImage(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDRecognizers.detectBarcodes(options: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc public func applyImageFilters(_ call: CAPPluginCall) {
        guard let imageFileUri = call.getString("imageFileUri") else {
            call.reject(self.missingRequiredPropertyErrorMsg + "imageFileUri")
            return
        }

        guard let filters = call.getArray<[String: Any]>("filters", [String: Any].self) else {
            call.reject(self.missingRequiredPropertyErrorMsg + "filters")
            return
        }

        SBDImageOperations.applyImageFilter(imageFileUri: imageFileUri, parametricFilters: filters, resultDelegate:  ResultDelegate(call))
    }

    @objc public func applyImageFiltersOnPage(_ call: CAPPluginCall) {
        guard let page = call.options["page"] as? [String: Any] else {
            call.reject(self.missingRequiredPropertyErrorMsg + "page")
            return
        }

        guard let filters = call.getArray<[String: Any]>("filters", [String: Any].self) else {
            call.reject(self.missingRequiredPropertyErrorMsg + "filters")
            return
        }

        SBDPageOperations.applyImageFilterOnPage(pageDict: page, parametricFilters: filters, delegate: ResultDelegate(call))
    }

    @objc func getImageData(_ call: CAPPluginCall) {
        guard let fileUri = call.getString("imageFileUri") else {
            call.reject(self.missingRequiredPropertyErrorMsg + "imageFileUri")
            return
        }

        SBDImageOperations.getImageData(imageFilePath: fileUri, resultDelegate: ResultDelegate(call))

    }

    @objc func rotateImage(_ call: CAPPluginCall) {
        guard let imageFileUri = call.getString("imageFileUri") else {
            call.reject(self.missingRequiredPropertyErrorMsg + "imageFileUri")
            return
        }

        guard let degrees = call.getDouble("degrees") else {
            call.reject(self.missingRequiredPropertyErrorMsg + "degrees")
            return
        }

        SBDImageOperations.rotateImage(imageFileUri: imageFileUri, degrees: degrees, resultDelegate: ResultDelegate(call))
    }

    @objc func createPage(_ call: CAPPluginCall) {
        guard let fileUri = call.getString("imageUri") else {
            call.reject(self.missingRequiredPropertyErrorMsg + "imageUri")
            return
        }

        SBDPageOperations.createPage(imageFileUri: fileUri, resultDelegate: ResultDelegate(call))
    }

    @objc public func removePage(_ call: CAPPluginCall) {
        guard let page = call.options["page"] as? [String: Any] else {
            call.reject(self.missingRequiredPropertyErrorMsg + "page")
            return
        }

        SBDPageOperations.removePage(pageDict: page, resultDelegate:  ResultDelegate(call))
    }

    @objc func rotatePage(_ call: CAPPluginCall) {
        guard let page = call.options["page"] as? [String: Any] else {
            call.reject(self.missingRequiredPropertyErrorMsg + "page")
            return
        }

        guard let times = call.getInt("times") else {
            call.reject(self.missingRequiredPropertyErrorMsg + "times")
            return
        }

        SBDPageOperations.rotatePage(pageDict: page, times: times, resultDelegate: ResultDelegate(call))
    }

    @objc func setDocumentImage(_ call: CAPPluginCall) {
        guard let page = call.options["page"] as? [String: Any] else {
            call.reject(self.missingRequiredPropertyErrorMsg + "page")
            return
        }

        guard let fileUri = call.getString("imageFileUri") else {
            call.reject(self.missingRequiredPropertyErrorMsg + "imageFileUri")
            return
        }

        SBDPageOperations.setDocumentImage(pageDict: page, imageFileUri: fileUri, resultDelegate: ResultDelegate(call))
    }

    @objc func detectDocument(_ call: CAPPluginCall) {

        guard let fileUri = call.getString("imageFileUri") else {
            call.reject(self.missingRequiredPropertyErrorMsg + "imageFileUri")
            return
        }

        SBDRecognizers.detectDocument(imageFilePath: fileUri, resultDelegate: ResultDelegate(call))
    }

    @objc func detectDocumentOnPage(_ call: CAPPluginCall) {
        guard let page = call.options["page"] as? [String: Any] else {
            call.reject(self.missingRequiredPropertyErrorMsg + "page")
            return
        }

        SBDPageOperations.detectDocumentOnPage(pageDict: page, resultDelegate: ResultDelegate(call))
    }

    @objc func documentQualityAnalyzer(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDImageOperations.documentQualityAnalyzer(operationConfig: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func extractPagesFromPdf(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDPDFExtractor.extractPagesFromPdf(operationConfig: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func extractImagesFromPdf(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDPDFExtractor.extractImagesFromPdf(operationConfig: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func recognizeCheck(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDRecognizers.recognizeCheckOnImage(options: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func recognizeMrz(_ call: CAPPluginCall) {
        guard let fileUri = call.getString("imageFileUri") else {
            call.reject(self.missingRequiredPropertyErrorMsg + "imageFileUri")
            return
        }

        SBDRecognizers.recognizeMrz(imageFilePath: fileUri, resultDelegate: ResultDelegate(call))
    }

    @objc func recognizeMedicalCertificate(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDRecognizers.recognizeMedicalCertificate(options: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func recognizeEHIC(_ call: CAPPluginCall) {
        guard let fileUri = call.getString("imageFileUri") else {
            call.reject(self.missingRequiredPropertyErrorMsg + "imageFileUri")
            return
        }

        SBDRecognizers.recognizeEHIC(imageFilePath: fileUri, resultDelegate: ResultDelegate(call))
    }

    @objc func recognizeGenericDocument(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDRecognizers.recognizeGenericDocument(options: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func refreshImageUris(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDPageOperations.refreshImageUris(operationConfig: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func getOCRConfigs(_ call: CAPPluginCall) {
        SBDWrapper.getOCRConfigs(resultDelegate:ResultDelegate(call))
    }

    @objc func cleanup(_ call: CAPPluginCall) {
        SBDWrapper.cleanup(resultDelegate: ResultDelegate(call))
    }

    @objc func performOCR(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDRecognizers.performOCR(operationConfig: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func createPDF(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDImageOperations.createPDF(operationConfig: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func writeTIFF(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDImageOperations.writeTIFF(operationConfig: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    // MARK: - DOCUMENT OPERATIONS

    @objc func createDocument(_ call: CAPPluginCall){
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDDocumentOperations.createScannedDocument(operationConfiguration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func createDocumentFromLegacyPages( _ call: CAPPluginCall){
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDDocumentOperations.documentFromLegacyPages(operationConfiguration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func documentExists( _ call: CAPPluginCall){
        guard let documentID = call.getString("documentID")
        else {
            call.reject(self.missingRequiredPropertyErrorMsg + "documentID")
            return
        }

        SBDDocumentOperations.documentExists(documentID: documentID, resultDelegate: ResultDelegate(call))
    }

    @objc func loadDocument( _ call: CAPPluginCall){
        guard let documentID = call.getString("documentID")
        else {
            call.reject(self.missingRequiredPropertyErrorMsg + "documentID")
            return
        }

        SBDDocumentOperations.loadDocument(documentID: documentID, resultDelegate: ResultDelegate(call))
    }

    @objc func storedDocumentIDs( _ call: CAPPluginCall){
        SBDDocumentOperations.storedDocumentIDs(resultDelegate: ResultDelegate(call))
    }

    @objc func cloneDocument( _ call: CAPPluginCall){
        guard let documentID = call.getString("documentID")
        else {
            call.reject(self.missingRequiredPropertyErrorMsg + "documentID")
            return
        }

        SBDDocumentOperations.cloneDocument(documentID: documentID, resultDelegate: ResultDelegate(call))

    }

    @objc func deleteDocument( _ call: CAPPluginCall){
        guard let documentID = call.getString("documentID")
        else {
            call.reject(self.missingRequiredPropertyErrorMsg + "documentID")
            return
        }

        SBDDocumentOperations.deleteDocument(documentID: documentID, resultDelegate: ResultDelegate(call))
    }

    @objc func createPDFForDocument( _ call: CAPPluginCall){
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDDocumentOperations.createPDF(operationConfiguration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func createTIFF( _ call: CAPPluginCall){
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDDocumentOperations.createTIFF(operationConfiguration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func addPage( _ call: CAPPluginCall){
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDDocumentOperations.addPage(operationConfiguration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func movePage( _ call: CAPPluginCall){
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDDocumentOperations.movePage(operationConfiguration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func modifyPage( _ call: CAPPluginCall){
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDDocumentOperations.modifyPage(operationConfiguration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func removePageFromDocument( _ call: CAPPluginCall){
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        SBDDocumentOperations.removePage(operationConfiguration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }

    @objc func removeAllPages( _ call: CAPPluginCall){
        guard let documentID = call.getString("documentID")
        else {
            call.reject(self.missingRequiredPropertyErrorMsg + "documentID")
            return
        }

        SBDDocumentOperations.removeAllPages(documentID: documentID, resultDelegate: ResultDelegate(call))
    }
    
    @objc func createDocumentFromPDF( _ call: CAPPluginCall){
        guard let pdfURL = call.getString("pdfUri")
        else {
            call.reject(self.missingRequiredPropertyErrorMsg + "pdfUri")
            return
        }
        
        SBDDocumentOperations.createDocumentFromPDF(pdfURL: pdfURL, resultDelegate: ResultDelegate(call))
    }
    
    @objc func deleteAllDocuments( _ call: CAPPluginCall){
        SBDDocumentOperations.deleteAllDocuments(resultDelegate: ResultDelegate(call))
    }


    // MARK: - RTU UI

    @objc func startDocumentScanner(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        DispatchQueue.main.async {
            guard let parentVC = self.presentViewControllerOrReject(call)
            else {
                return
            }

            SBDocumentRTUUI.startDocumentScanner(parentViewController: parentVC, configuration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
        }
    }

    @objc func closeDocumentScanner(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBDocumentRTUUI.closeDocumentScanner(resultDelegate: ResultDelegate(call))
        }
    }

    @objc func startFinderDocumentScanner(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        DispatchQueue.main.async {
            guard let parentVC = self.presentViewControllerOrReject(call)
            else {
                return
            }

            SBDocumentRTUUI.startFinderDocumentScanner(parentViewController: parentVC, configuration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
        }
    }

    @objc func closeFinderDocumentScanner(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBDocumentRTUUI.closeFinderDocumentScanner(resultDelegate: ResultDelegate(call))
        }
    }

    @objc func startMrzScanner(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        DispatchQueue.main.async {
            guard let parentVC = self.presentViewControllerOrReject(call)
            else {
                return
            }

            SBDataRTUUI.startMRZScanner(parentViewController: parentVC, configuration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
        }
    }

    @objc func closeMrzScanner(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBDataRTUUI.closeMRZScanner(resultDelegate: ResultDelegate(call))
        }
    }

    @objc func startBarcodeScanner(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        DispatchQueue.main.async {
            guard let parentVC = self.presentViewControllerOrReject(call)
            else {
                return
            }

            SBBRTUUI.startBarcodeScanner(parentViewController: parentVC, configuration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
        }
    }

    @objc func closeBarcodeScanner(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBBRTUUI.closeBarcodeScanner(resultDelegate: ResultDelegate(call))
        }
    }

    @objc func startBatchBarcodeScanner(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        DispatchQueue.main.async {
            guard let parentVC = self.presentViewControllerOrReject(call)
            else {
                return
            }

            SBBRTUUI.startBatchBarcodeScanner(parentViewController: parentVC, configuration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
        }
    }

    @objc func closeBatchBarcodeScanner(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBBRTUUI.closeBatchBarcodeScanner(resultDelegate: ResultDelegate(call))
        }
    }

    @objc func startEHICScanner(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        DispatchQueue.main.async {
            guard let parentVC = self.presentViewControllerOrReject(call)
            else {
                return
            }

            SBDataRTUUI.startEHICScanner(parentViewController: parentVC, configuration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
        }
    }

    @objc func closeEHICScanner(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBDataRTUUI.closeEHICScanner(resultDelegate: ResultDelegate(call))
        }
    }

    @objc func startTextDataScanner(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        DispatchQueue.main.async {
            guard let parentVC = self.presentViewControllerOrReject(call)
            else {
                return
            }

            SBDataRTUUI.startTextDataScanner(parentViewController: parentVC, configuration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
        }
    }

    @objc func closeTextDataScanner(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBDataRTUUI.closeTextDataScanner(resultDelegate: ResultDelegate(call))
        }
    }

    @objc func startGenericDocumentRecognizer(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        DispatchQueue.main.async {
            guard let parentVC = self.presentViewControllerOrReject(call)
            else {
                return
            }

            SBDataRTUUI.startGDRScanner(parentViewController: parentVC, configuration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
        }
    }

    @objc func closeGenericDocumentRecognizer(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBDataRTUUI.closeGDRScanner(resultDelegate: ResultDelegate(call))
        }
    }

    @objc func startCheckRecognizer(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        DispatchQueue.main.async {
            guard let parentVC = self.presentViewControllerOrReject(call)
            else {
                return
            }

            SBDataRTUUI.startCheckScanner(parentViewController: parentVC, configuration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
        }
    }

    @objc func closeCheckRecognizer(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBDataRTUUI.closeCheckScanner(resultDelegate: ResultDelegate(call))
        }
    }

    @objc func startLicensePlateScanner(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        DispatchQueue.main.async {
            guard let parentVC = self.presentViewControllerOrReject(call)
            else {
                return
            }

            SBDataRTUUI.startLincesePlateScanner(parentViewController: parentVC, configuration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
        }
    }

    @objc func closeLicensePlateScanner(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBDataRTUUI.closeLicensePlateScanner(resultDelegate: ResultDelegate(call))
        }
    }

    @objc func startMedicalCertificateRecognizer(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        DispatchQueue.main.async {
            guard let parentVC = self.presentViewControllerOrReject(call)
            else {
                return
            }

            SBDataRTUUI.startMedicalCertificateScanner(parentViewController: parentVC, configuration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
        }
    }

    @objc func closeMedicalCertificateRecognizer(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBDataRTUUI.closeMedicalCertificateScanner(resultDelegate: ResultDelegate(call))
        }
    }

    @objc func startCroppingScreen(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            guard let page = call.options["page"] as? [String: Any] else {
                call.reject(self.missingRequiredPropertyErrorMsg + "page")
                return
            }

            let configuration: [String: Any] = call.options["configuration"] as? [String: Any] ?? [:]

            guard let parentVC = self.presentViewControllerOrReject(call)
            else {
                return
            }

            SBDocumentRTUUI.startCroppingScreen(parentViewController: parentVC, configuration: configuration, pageDictionary: page, resultDelegate: ResultDelegate(call))
        }
    }

    @objc func closeCroppingScreen(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBDocumentRTUUI.closeCroppingScreen(resultDelegate: ResultDelegate(call))
        }
    }

    @objc func startVinScanner(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)

        DispatchQueue.main.async {
            guard let parentVC = self.presentViewControllerOrReject(call)
            else {
                return
            }

            SBDataRTUUI.startVINScanner(parentViewController: parentVC, configuration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
        }

    }

    @objc func closeVinScanner(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBDataRTUUI.closeVINScanner(resultDelegate: ResultDelegate(call))
        }
    }

    // MARK: - RTU UI V2

    @objc func startBarcodeScannerV2(_ call: CAPPluginCall) {

        guard let configuration = self.pluginCallOptionsToString(call)
        else {
            return
        }

        guard let parentVC = self.bridge?.viewController else {
            call.reject(self.rtuuiCantBePresented)
            return
        }

        DispatchQueue.main.async {
            if self.barcodeItemMapperPluginCall != nil {

                let pluginResultDelegate = ResultDelegate(call) {

                    if let safeBarcodeItemMapperPluginCall = self.barcodeItemMapperPluginCall {
                        self.bridge?.releaseCall(safeBarcodeItemMapperPluginCall)
                        self.barcodeItemMapperPluginCall = nil
                    }
                }

                SBBRTUUI.startBarcodeScannerV2(parentViewController: parentVC, configurationString: configuration, resultDelegate: pluginResultDelegate) { barcodeItem in
                    if let safeBarcodeItemMapperPluginCall = self.barcodeItemMapperPluginCall {
                        safeBarcodeItemMapperPluginCall.resolve(barcodeItem)
                    }
                }
            } else {
                SBBRTUUI.startBarcodeScannerV2(parentViewController: parentVC, configurationString: configuration, resultDelegate: ResultDelegate(call))
            }
        }
    }

    @objc func startDocumentScannerV2(_ call: CAPPluginCall){
        guard let configuration = self.pluginCallOptionsToString(call)
        else {
            return
        }

        guard let parentVC = self.bridge?.viewController else {
            call.reject(self.rtuuiCantBePresented)
            return
        }

        DispatchQueue.main.async {
            SBDocumentRTUUI.startDocumentScannerV2(parentViewController: parentVC, configuration: configuration, resultDelegate: ResultDelegate(call))
        }
    }

    @objc func startCroppingScreenV2( _ call: CAPPluginCall){
        guard let configuration = self.pluginCallOptionsToString(call)
        else {
            return
        }

        guard let parentVC = self.bridge?.viewController else {
            call.reject(self.rtuuiCantBePresented)
            return
        }

        DispatchQueue.main.async {
            SBDocumentRTUUI.startCroppingScreenV2(parentViewController: parentVC, configuration: configuration, resultDelegate: ResultDelegate(call))
        }
    }

    // MARK: - public helper methods that are used only by us from the plugin middle layer (not exposed to the users)

    @objc func registerBarcodeItemMapperCallback(_ call: CAPPluginCall) {
        call.keepAlive = true
        barcodeItemMapperPluginCall = call
    }

    @objc func onBarcodeItemMapper(_ call: CAPPluginCall) {
        guard let barcodeUUID = call.options["barcodeItemUuid"] as? String else {
            call.reject(self.missingRequiredPropertyErrorMsg + "barcodeItemUuid")
            return
        }

        let barcodeMappedData = call.options["barcodeMappedData"] as? [String: Any]

        SBBRTUUI.onBarcodeItemMapper(barcodeItemUUID: barcodeUUID, barcodeMappedDataAsDictionary: barcodeMappedData)
    }


    // MARK: - private helper methods

    private func presentViewControllerOrReject(_ call: CAPPluginCall) -> UIViewController? {
        guard let activeVC = self.bridge?.viewController else {
            call.reject(self.rtuuiCantBePresented)
            return nil
        }

        return activeVC
    }

    private func pluginCallOptionsToDictionary(_ pluginCall: CAPPluginCall) -> [String: Any] {
        return pluginCall.options as? [String: Any] ?? [:]
    }

    private func pluginCallOptionsToString(_ pluginCall: CAPPluginCall) -> String? {
        guard let dict = pluginCall.options as? [String: Any],
              let data = try? JSONSerialization.data(withJSONObject: dict),
              let stringDict = String(data: data, encoding: .utf8) else {
            pluginCall.reject(self.rtuuiUnexpectedErrorMsg)
            return nil
        }

        return stringDict
    }
}
