import Foundation
import Capacitor
import ScanbotBarcodeSDKWrapper

@objc(ScanbotBarcodeSDKPlugin)
public class ScanbotBarcodeSDKPlugin: CAPPlugin, CAPBridgedPlugin {
  
  private var barcodeItemMapperPluginCall : CAPPluginCall? = nil
  
  // MARK: - Methods from CAPBridgedPlugin. We use this protocol to avoid creating .h and .m files for exporting swift to objc
  public var identifier: String = "ScanbotBarcodeSDKPlugin"
  
  public var jsName: String = "ScanbotBarcodeSDK"
  
  // Need to define all methods here to be available for the Capacitor web runtime
  public var pluginMethods: [CAPPluginMethod] = [
    CAPPluginMethod(name: "initializeSdk", returnType: CAPPluginReturnPromise),
    CAPPluginMethod(name: "getLicenseInfo", returnType: CAPPluginReturnPromise),
    CAPPluginMethod(name: "detectBarcodesOnImage", returnType: CAPPluginReturnPromise),
    CAPPluginMethod(name: "extractImagesFromPDF", returnType: CAPPluginReturnPromise),
    CAPPluginMethod(name: "cleanup", returnType: CAPPluginReturnPromise),
    CAPPluginMethod(name: "getImageData", returnType: CAPPluginReturnPromise),
    CAPPluginMethod(name: "startBarcodeScanner", returnType: CAPPluginReturnPromise),
    CAPPluginMethod(name: "closeBarcodeScanner", returnType: CAPPluginReturnPromise),
    CAPPluginMethod(name: "startBatchBarcodeScanner", returnType: CAPPluginReturnPromise),
    CAPPluginMethod(name: "closeBatchBarcodeScanner", returnType: CAPPluginReturnPromise),
    CAPPluginMethod(name: "startBarcodeScannerV2", returnType: CAPPluginReturnPromise),
    CAPPluginMethod(name: "closeBarcodeScannerV2", returnType: CAPPluginReturnPromise),
    CAPPluginMethod(name: "registerBarcodeItemMapperCallback", returnType: CAPPluginReturnNone),
    CAPPluginMethod(name: "onBarcodeItemMapper", returnType: CAPPluginReturnNone),
  ];
  
  // MARK: - Public methods
  @objc func initializeSdk(_ pluginCall: CAPPluginCall) {
    let configurationAsDictionary = pluginCallOptionsToDictionary(pluginCall)
    
    SBBWrapper.initializeSDK(configuration: configurationAsDictionary,
                             resultDelegate: ScanbotBarcodeSDKPluginResultDelegate(pluginCall))
  }
  
  @objc func getLicenseInfo(_ pluginCall: CAPPluginCall) {
    SBBWrapper.getLicenseInfo(resultDelegate: ScanbotBarcodeSDKPluginResultDelegate(pluginCall))
  }
  
  @objc func detectBarcodesOnImage(_ pluginCall: CAPPluginCall) {
    let configurationAsDictionary = pluginCallOptionsToDictionary(pluginCall)
    
    SBBOperations.detectBarcodesOnImage(optionsConfig: configurationAsDictionary,
                                        resultDelegate: ScanbotBarcodeSDKPluginResultDelegate(pluginCall))
  }
  
  @objc func extractImagesFromPDF(_ pluginCall: CAPPluginCall) {
    let argsAsDictionary = pluginCallOptionsToDictionary(pluginCall)
    
    SBBOperations.extractImagesFromPDF(optionsConfig: argsAsDictionary,
                                       resultDelegate: ScanbotBarcodeSDKPluginResultDelegate(pluginCall))
  }
  
  @objc func cleanup(_ pluginCall: CAPPluginCall) {
    SBBWrapper.cleanup(resultDelegate: ScanbotBarcodeSDKPluginResultDelegate(pluginCall))
  }
  
  @objc func getImageData(_ pluginCall: CAPPluginCall) {
    guard let fileUri = pluginCall.getString("imageFileUri") else {
        pluginCall.reject("Missing required property imageFileUri")
        return
    }
    
    SBBOperations.getImageData(imageFilePath: fileUri, resultDelegate: ScanbotBarcodeSDKPluginResultDelegate(pluginCall))
  }
  
  
  // MARK: RTUUI
  @objc func startBarcodeScanner(_ pluginCall: CAPPluginCall) {
    let configurationAsDictionary = pluginCallOptionsToDictionary(pluginCall)
    
    guard let parentVC = getPresentedViewControllerOrReject(pluginCall) else {
      return
    }
    DispatchQueue.main.async {
      SBBRTUUI.startBarcodeScanner(parentViewController: parentVC,
                                   configuration: configurationAsDictionary,
                                   resultDelegate: ScanbotBarcodeSDKPluginResultDelegate(pluginCall))
    }
  }
  
  @objc func closeBarcodeScanner(_ pluginCall: CAPPluginCall) {
    DispatchQueue.main.async {
      SBBRTUUI.closeBarcodeScanner(resultDelegate: ScanbotBarcodeSDKPluginResultDelegate(pluginCall))
    }
  }
  
  @objc func startBatchBarcodeScanner(_ pluginCall: CAPPluginCall) {
    let configurationAsDictionary = pluginCallOptionsToDictionary(pluginCall)
    
    guard let parentVC = getPresentedViewControllerOrReject(pluginCall) else {
      return
    }
    
    DispatchQueue.main.async {
      SBBRTUUI.startBatchBarcodeScanner(parentViewController: parentVC,
                                        configuration: configurationAsDictionary,
                                        resultDelegate: ScanbotBarcodeSDKPluginResultDelegate(pluginCall))
    }
  }
  
  @objc func closeBatchBarcodeScanner(_ pluginCall: CAPPluginCall) {
    DispatchQueue.main.async {
      SBBRTUUI.closeBatchBarcodeScanner(resultDelegate: ScanbotBarcodeSDKPluginResultDelegate(pluginCall))
    }
  }
  
  @objc func startBarcodeScannerV2(_ pluginCall: CAPPluginCall) {
    
    guard let configuration = self.pluginCallOptionsToString(pluginCall)
    else {
      return
    }
    
    guard let parentVC = getPresentedViewControllerOrReject(pluginCall)
    else {
      return
    }
    
    DispatchQueue.main.async {
      if self.barcodeItemMapperPluginCall != nil {
        
        let pluginResultDelegate = ScanbotBarcodeSDKPluginResultDelegate(pluginCall) {
          
          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: ScanbotBarcodeSDKPluginResultDelegate(pluginCall))
      }
    }
  }
  
  private func pluginCallOptionsToDictionary(_ pluginCall: CAPPluginCall) -> [String: Any] {
    return pluginCall.options as? [String: Any] ?? [:]
  }
  
  private func getPresentedViewControllerOrReject(_ pluginCall: CAPPluginCall) -> UIViewController? {
    guard let presentedVC = self.bridge?.viewController else {
      pluginCall.reject("No active view controller")
      return nil
    }
    
    return presentedVC
  }
  
  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("Invalid scanner configuration was provided")
      return nil
    }
    
    return stringDict
  }
  
  // MARK: - Helper methods that are used only by us from the plugin middle layer (not exposed to the users)
  
  @objc func registerBarcodeItemMapperCallback(_ pluginCall: CAPPluginCall) {
    pluginCall.keepAlive = true
    barcodeItemMapperPluginCall = pluginCall
  }
  
  @objc func onBarcodeItemMapper(_ call: CAPPluginCall) {
    guard let barcodeUUID = call.options["barcodeItemUuid"] as? String else {
      call.reject("Missing required property barcodeItemUuid")
      return
    }
    
    let barcodeMappedData = call.options["barcodeMappedData"] as? [String: Any]
    
    SBBRTUUI.onBarcodeItemMapper(barcodeItemUUID: barcodeUUID, barcodeMappedDataAsDictionary: barcodeMappedData)
  }
}
