//
//  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: "startEHICScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startDocumentDataExtractor", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startCheckScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startMedicalCertificateScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startVinScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeDocumentScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeFinderDocumentScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeCroppingScreen", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeEHICScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeDocumentDataExtractor", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeCheckScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeMedicalCertificateScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "closeVinScanner", returnType: CAPPluginReturnPromise),
        /* RTU UI V2 */
        CAPPluginMethod(name: "startBarcodeScanner", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "registerBarcodeItemMapperCallback", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "onBarcodeItemMapper", returnType: CAPPluginReturnNone),
        CAPPluginMethod(name: "startDocumentScannerV2", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startCroppingScreenV2", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startMRZScannerV2", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startTextPatternScannerV2", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "startCreditCardScannerV2", 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: "recognizeCreditCard" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "documentDataExtractor" , 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),
        /* Imageref Operations */
        CAPPluginMethod(name: "imageRefSerialize" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "imageRefDeserialize" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "imageRefFromImageFileUri" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "imageRefFromEncodedBuffer" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "imageRefClone" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "imageRefHibernate" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "imageRefClear" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "imageRefInfo" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "imageRefSaveImage" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "imageRefEncodeImage" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "imageRefRelease" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "makeSnapshot" , returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "imageRefReleaseAll" , 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), framework: .rnCap)
    }
    
    @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) {
        let optionsAsDictionary = pluginCallOptionsToDictionary(call)
        
        SBDRecognizers.recognizeMrz(options: optionsAsDictionary, resultDelegate: ResultDelegate(call))
    }
    
    @objc func recognizeMedicalCertificate(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)
        
        SBDRecognizers.recognizeMedicalCertificate(options: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }
    
    @objc func recognizeEHIC(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)
        
        SBDRecognizers.recognizeEHIC(options: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }
    
    @objc func recognizeCreditCard(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)
        
        SBDRecognizers.recognizeCreditCard(options: configurationAsDictionary, resultDelegate: ResultDelegate(call))
    }
    
    @objc func documentDataExtractor(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)
        
        SBDRecognizers.documentDataExtractor(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 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 startDocumentDataExtractor(_ call: CAPPluginCall) {
        let configurationAsDictionary = pluginCallOptionsToDictionary(call)
        
        DispatchQueue.main.async {
            guard let parentVC = self.presentViewControllerOrReject(call)
            else {
                return
            }
            
            SBDataRTUUI.startDDEScanner(parentViewController: parentVC, configuration: configurationAsDictionary, resultDelegate: ResultDelegate(call))
        }
    }
    
    @objc func closeDocumentDataExtractor(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBDataRTUUI.closeDDEScanner(resultDelegate: ResultDelegate(call))
        }
    }
    
    @objc func startCheckScanner(_ 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 closeCheckScanner(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            SBDataRTUUI.closeCheckScanner(resultDelegate: ResultDelegate(call))
        }
    }
    
    @objc func startMedicalCertificateScanner(_ 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 closeMedicalCertificateScanner(_ 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 startBarcodeScanner(_ 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 {
            SBDocumentRTUUIV2.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 {
            SBDocumentRTUUIV2.startCroppingScreenV2(parentViewController: parentVC, configuration: configuration, resultDelegate: ResultDelegate(call))
        }
    }
    
    @objc func startMRZScannerV2( _ 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 {
            SBDataRTUUIV2.startMRZScanner(parentViewController: parentVC, configuration: configuration, resultDelegate: ResultDelegate(call))
        }
    }
    
    @objc func startTextPatternScannerV2( _ 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 {
            SBDataRTUUIV2.startTextPatternScanner(parentViewController: parentVC, configuration: configuration, resultDelegate: ResultDelegate(call))
        }
    }
    
    @objc func startCreditCardScannerV2( _ 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 {
            SBDataRTUUIV2.startCreditCardScanner(parentViewController: parentVC, configuration: configuration, resultDelegate: ResultDelegate(call))
        }
    }
    
    // MARK: - ImageRef operations
    
    @objc func imageRefSerialize( _ call: CAPPluginCall){
        SBImageRef.imageRefSerialize(uniqueId: call.getString("uniqueId")!, resultDelegate: ResultDelegate(call))
    }
    
    @objc func imageRefDeserialize( _ call: CAPPluginCall){
        SBImageRef.imageRefDeserialize(uniqueId: call.getString("uniqueId")!, resultDelegate: ResultDelegate(call))
    }
    
    @objc func imageRefFromImageFileUri( _ call: CAPPluginCall){
        SBImageRef.imageRefFromImageFileUri(path: call.getString("uri")!, optionsAsDictionary: call.options["options"] as! [String: Any], resultDelegate: ResultDelegate(call))
    }
    
    @objc func imageRefFromEncodedBuffer( _ call: CAPPluginCall){
        SBImageRef.imageRefFromEncodedBuffer(buffer: call.getString("buffer")!, optionsAsDictionary: call.options["options"] as! [String: Any], resultDelegate: ResultDelegate(call))
    }
    
    @objc func imageRefClone( _ call: CAPPluginCall){
        SBImageRef.imageRefClone(uniqueId: call.getString("uniqueId")!, resultDelegate: ResultDelegate(call))
    }
    
    @objc func imageRefHibernate( _ call: CAPPluginCall){
        SBImageRef.imageRefHibernate(uniqueId: call.getString("uniqueId")!, resultDelegate: ResultDelegate(call))
    }
    
    @objc func imageRefClear( _ call: CAPPluginCall){
        SBImageRef.imageRefClear(uniqueId: call.getString("uniqueId")!, resultDelegate: ResultDelegate(call))
    }
    
    @objc func imageRefInfo( _ call: CAPPluginCall){
        SBImageRef.imageRefInfo(uniqueId: call.getString("uniqueId")!, resultDelegate: ResultDelegate(call))
    }
    
    @objc func imageRefSaveImage( _ call: CAPPluginCall){
        SBImageRef.imageRefSaveImage(uniqueId: call.getString("uniqueId")!, path: call.getString("path")!, optionsAsDictionary: call.options["options"] as! [String: Any], resultDelegate: ResultDelegate(call))
    }
    
    @objc func imageRefEncodeImage( _ call: CAPPluginCall){
        SBImageRef.imageRefEncodeImage(uniqueId: call.getString("uniqueId")!, optionsAsDictionary: call.options["options"] as! [String: Any], resultDelegate: ResultDelegate(call))
    }
    
    @objc func imageRefRelease( _ call: CAPPluginCall){
        SBImageRef.imageRefRelease(uniqueId: call.getString("uniqueId")!, resultDelegate: ResultDelegate(call))
    }
    
    @objc func makeSnapshot( _ call: CAPPluginCall){
        SBImageRef.imageRefProfilerSnapshot(resultDelegate: ResultDelegate(call))
    }
    
    @objc func imageRefReleaseAll( _ call: CAPPluginCall){
        SBImageRef.imageRefReleaseAll(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
    }
}
