import Foundation
import React
import UIKit

// Import FINOS eKYC iOS SDK frameworks
import Ekyc      // SdkEkyc
import EkycUI    // SdkEkycUI, AppKeyConfig, OptionConfig, StyleConfig, EKYCConfigSDK, SDKType, EKYCEvent
import SDKCore   // SDKFaceDetectStatus
import Liveness  // SdkEkycLiveness
import Nfc       // SdkEkycNfc
import Ocr       // SdkEkycOcr
import FaceService // SdkEkycFaceService
import C06       // SdkEkycC06
import SmsOtp    // SdkSmsOtpService, SmsOtpConfig
import EasyCA    // ESignSDKManager

/// React Native iOS bridge – mirrors EkycNativeAndroid EKYCModule.kt exactly.
@objc(EKYCModule)
class EKYCModule: RCTEventEmitter {

  // MARK: - Event Support

  private static var hasListeners = false

  override func supportedEvents() -> [String]! {
    return [
      // eKYC UI
      "onEkycUISuccess", "onEkycUIError",
      // Liveness
      "onLivenessSuccess", "onLivenessError",
      // OCR
      "onOcrSuccess", "onOcrError",
      // NFC
      "onNfcScanStart", "onNfcScanSuccess", "onNfcError", "onNfcEvent",
      // FaceService
      "onFaceCompareSuccess", "onFaceCompareError",
      // C06
      "onC06Success", "onC06Error",
      // SmsOtp
      "onSmsOtpSendSuccess", "onSmsOtpVerifySuccess", "onSmsOtpResendSuccess", "onSmsOtpError",
      // eSign
      "onESignSuccess", "onESignError",
      // SDK init
      "EKYCInitEvent",
      // Exit Handler
      "onShowExitConfirm"
    ]
  }

  override static func requiresMainQueueSetup() -> Bool { return true }
  override func startObserving() { EKYCModule.hasListeners = true }
  override func stopObserving()  { EKYCModule.hasListeners = false }

  private func emit(_ name: String, body: Any?) {
    guard EKYCModule.hasListeners else { return }
    sendEvent(withName: name, body: body)
  }

  private var rootVC: UIViewController? {
    UIApplication.shared.connectedScenes
      .compactMap { ($0 as? UIWindowScene)?.keyWindow?.rootViewController }
      .first
  }

  // MARK: - SDK Init

  @objc(initSdkEkyc:rejecter:)
  func initSdkEkyc(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    emit("EKYCInitEvent", body: ["status": "success", "message": "SDK EKYC initialized successfully"])
    resolve(true)
  }

  @objc(getVersion:rejecter:)
  func getVersion(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    resolve("1.4.1")
  }

  @objc(getSDKInfo:rejecter:)
  func getSDKInfo(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    resolve(["name": "@finos_sdk/sdk-ekyc", "version": "1.4.1", "buildNumber": "1"])
  }

  @objc(setTransactionId:resolver:rejecter:)
  func setTransactionId(_ transactionId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    SdkEkyc.setTransactionId(transactionId)
    resolve(true)
  }

  // MARK: - Exit Handler (Dummy for iOS compatibility if not implemented yet)

  @objc(registerExitHandler:rejecter:)
  func registerExitHandler(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    // Currently Android specific, just resolve true to not break JS
    resolve(true)
  }

  @objc(resolveExit:resolver:rejecter:)
  func resolveExit(_ action: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    // Currently Android specific, just resolve true to not break JS
    resolve(true)
  }

  // MARK: - NFC

  @objc(startNfcScan:documentNumber:birthDate:expireDate:transactionId:facePathStorage:resolver:rejecter:)
  func startNfcScan(_ appKey: String, documentNumber: String, birthDate: String, expireDate: String, transactionId: String?, facePathStorage: String?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    DispatchQueue.main.async { [weak self] in
      guard let vc = self?.rootVC else { reject("NO_VC", "No root view controller", nil); return }
      let nfcConfig = NfcConfig(documentNumber: documentNumber, birthDate: birthDate, expireDate: expireDate, facePathStorage: facePathStorage ?? "", transactionId: transactionId ?? "")
      let ekycConfig = EKYCConfigSDK(appKey: AppKeyConfig(appKey: appKey), sdkType: .nfc, nfcConfig: nfcConfig)
      SdkEkycNfc.startEkyc(from: vc, ekycConfigSDK: ekycConfig,
        callbackSuccess: { event, data in
          switch event {
          case .SCAN_NFC_START:
            self?.emit("onNfcScanStart", body: ["event": event.description, "data": (data as? SDKEkycResult)?.nfcResponse ?? "{}"])
          case .SCAN_NFC_SUCCESS:
            let nfcResponse = (data as? SDKEkycResult)?.nfcResponse ?? ""
            let body: [String: Any] = ["event": event.description, "data": nfcResponse]
            self?.emit("onNfcScanSuccess", body: body)
            resolve(body)
          default:
            self?.emit("onNfcEvent", body: ["event": event.description, "data": (data as? SDKEkycResult)?.nfcResponse ?? "{}"])
          }
        },
        callbackError: { event, errorResult in
          let errBody: [String: Any] = ["event": event.description, "customCode": errorResult.code ?? "", "customMessage": errorResult.message ?? "", "message": errorResult.message ?? ""]
          self?.emit("onNfcError", body: errBody)
          reject(event.description, errorResult.message, nil)
        }
      )
    }
  }

  // MARK: - C06

  @objc(checkC06:sod:idCardNumber:recentLocation:transactionId:resolver:rejecter:)
  func checkC06(_ appKey: String, sod: String, idCardNumber: String, recentLocation: String, transactionId: String?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    let c06Config = C06Config(transactionId: transactionId ?? "", sod: sod, idCardNumber: idCardNumber, recentLocation: recentLocation)
    let ekycConfig = EKYCConfigSDK(appKey: AppKeyConfig(appKey: appKey), c06Config: c06Config)
    SdkEkycC06.startEkyc(ekycConfigSDK: ekycConfig,
      callbackSuccess: { [weak self] event, data in
        let c06Response = (data as? SDKEkycResult)?.checkC06Response?.description ?? "{}"
        let body: [String: Any] = ["event": event.description, "data": c06Response]
        self?.emit("onC06Success", body: body)
        resolve(body)
      },
      callbackError: { [weak self] event, errorResult in
        let errBody: [String: Any] = ["event": event.description, "customCode": errorResult.code ?? "", "customMessage": errorResult.message ?? "", "message": errorResult.message ?? ""]
        self?.emit("onC06Error", body: errBody)
        reject(event.description, errorResult.message, nil)
      }
    )
  }

  // MARK: - OCR

  @objc(startOcr:idImage:expectedDocumentSide:transactionId:resolver:rejecter:)
  func startOcr(_ appKey: String, idImage: String, expectedDocumentSide: String, transactionId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    guard let imageData = resolveImageInput(idImage) else {
      reject("IMAGE_INVALID", "Invalid ID image input", nil); return
    }
    let ocrConfig = OcrConfig(idImage: imageData, expectedDocumentSide: expectedDocumentSide, transactionId: transactionId)
    let ekycConfig = EKYCConfigSDK(appKey: AppKeyConfig(appKey: appKey), sdkType: .ocr, ocrConfig: ocrConfig)
    SdkEkycOcr.startEkyc(ekycConfigSDK: ekycConfig,
      callbackSuccess: { [weak self] event, data in
        let ocrResponse = (data as? SDKEkycResult)?.ocrResponse?.description ?? ""
        let body: [String: Any] = ["event": event.description, "data": ocrResponse]
        self?.emit("onOcrSuccess", body: body)
        resolve(body)
      },
      callbackError: { [weak self] event, errorResult in
        let errBody: [String: Any] = ["event": event.description, "customCode": errorResult.code ?? "", "customMessage": errorResult.message ?? "", "message": errorResult.message ?? ""]
        self?.emit("onOcrError", body: errBody)
        reject(event.description, errorResult.message, nil)
      }
    )
  }

  // MARK: - Liveness

  @objc(startLiveness:selfieImage:transactionId:isActiveLiveness:isShowCameraFont:customActionsArray:activeActionCount:forceCaptureTimeout:isActiveLivenessColor:resolver:rejecter:)
  func startLiveness(_ appKey: String, selfieImage: String, transactionId: String, isActiveLiveness: Bool, isShowCameraFont: Bool, customActionsArray: [String]?, activeActionCount: Int, forceCaptureTimeout: Double, isActiveLivenessColor: Bool, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    DispatchQueue.main.async { [weak self] in
      guard let vc = self?.rootVC else { reject("NO_VC", "No root view controller", nil); return }
      guard let selfieUIImage = self?.resolveImageInput(selfieImage) else {
        reject("IMAGE_INVALID", "Invalid selfie image input", nil); return
      }
      let customActions: [SDKFaceDetectStatus]? = customActionsArray?.compactMap {
        switch $0 { case "LEFT": return .left; case "RIGHT": return .right; case "STRAIGHT": return .straight; default: return nil }
      }
      let livenessConfig = LivenessConfig(
        isActiveLiveness: isActiveLiveness,
        isActiveLivenessColor: isActiveLivenessColor,
        isShowCameraFont: isShowCameraFont,
        customActions: customActions?.isEmpty == false ? customActions : nil,
        activeActionCount: activeActionCount > 0 ? activeActionCount : 2,
        forceCaptureTimeout: Int64(forceCaptureTimeout * 1000),
        selfieImage: selfieUIImage, transactionId: transactionId
      )
      let ekycConfig = EKYCConfigSDK(appKey: AppKeyConfig(appKey: appKey), sdkType: .liveness, livenessConfig: livenessConfig)
      SdkEkycLiveness.startEkyc(from: vc, ekycConfigSDK: ekycConfig,
        callbackSuccess: { event, data in
          let livenessResponse = (data as? SDKEkycResult)?.checkLivenessResponse
          let livenessDict: Any
          if let resp = livenessResponse,
             let encoded = try? JSONEncoder().encode(resp),
             let dict = try? JSONSerialization.jsonObject(with: encoded) as? [String: Any] {
            livenessDict = dict
          } else {
            livenessDict = [String: Any]()
          }
          let body: [String: Any] = ["event": event.description, "data": livenessDict]
          self?.emit("onLivenessSuccess", body: body)
          resolve(body)
        },
        callbackError: { event, errorResult in
          let errBody: [String: Any] = ["event": event.description, "customCode": errorResult.code ?? "", "message": errorResult.message ?? ""]
          self?.emit("onLivenessError", body: errBody)
          reject(event.description, errorResult.message, nil)
        }
      )
    }
  }

  // MARK: - FaceCompare

  @objc(startFaceCompare:transactionId:selfieImage:idImage:resolver:rejecter:)
  func startFaceCompare(_ appKey: String, transactionId: String, selfieImage: String, idImage: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    let selfiePath = selfieImage.replacingOccurrences(of: "file://", with: "")
    let idPath = idImage.replacingOccurrences(of: "file://", with: "")
    guard let selfieImg = resolveImageInput(selfiePath) else { reject("SELFIE_IMAGE_INVALID", "Invalid selfie image", nil); return }
    guard let idImg = resolveImageInput(idPath) else { reject("ID_IMAGE_INVALID", "Invalid ID image", nil); return }
    let faceConfig = FaceServiceConfig(transactionId: transactionId, selfieImage: selfieImg, idImage: idImg)
    let ekycConfig = EKYCConfigSDK(appKey: AppKeyConfig(appKey: appKey, appKeyFaceService: appKey), faceServiceConfig: faceConfig)
    DispatchQueue.main.async { [weak self] in
      SdkEkycFaceService.startEkyc(ekycConfigSDK: ekycConfig,
        callbackSuccess: { event, data in
          let faceResponse = (data as? SDKEkycResult)?.checkFaceResponse
          var faceDict: [String: Any] = [:]
          if let v = faceResponse?.requestId { faceDict["requestId"] = v }
          if let r = faceResponse?.result {
            faceDict["result"] = ["conf": r.conf, "match": r.match, "matchScore": r.matchScore, "toBeReviewed": r.toBeReviewed]
          }
          if let v = faceResponse?.status { faceDict["status"] = v }
          if let v = faceResponse?.statusCode { faceDict["statusCode"] = v }
          if let v = faceResponse?.error { faceDict["error"] = v }
          if let v = faceResponse?.type { faceDict["type"] = v }
          let body: [String: Any] = ["event": event.description, "data": faceDict]
          self?.emit("onFaceCompareSuccess", body: body)
          resolve(body)
        },
        callbackError: { event, errorResult in
          let errBody: [String: Any] = ["event": event.description, "customCode": errorResult.code ?? "", "message": errorResult.message ?? ""]
          self?.emit("onFaceCompareError", body: errBody)
          reject(event.description, errorResult.message, nil)
        }
      )
    }
  }

  // MARK: - eKYC UI Flow

  @objc(startEkycUI:flowSDK:language:transactionId:optionConfigJson:appKeyConfigJson:styleConfigJson:resolver:rejecter:)
  func startEkycUI(_ appKey: String, flowSDK: String, language: String, transactionId: String, optionConfigJson: String, appKeyConfigJson: String, styleConfigJson: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    DispatchQueue.main.async { [weak self] in
      guard let vc = self?.rootVC else { reject("NO_VC", "No root view controller", nil); return }
      let flowTypes: [SDKType] = flowSDK
        .replacingOccurrences(of: "[", with: "").replacingOccurrences(of: "]", with: "").replacingOccurrences(of: "\"", with: "")
        .split(separator: ",").compactMap {
          switch $0.trimmingCharacters(in: .whitespaces).uppercased() {
          case "OCR": return SDKType.ocr; case "NFC": return SDKType.nfc
          case "LIVENESS": return SDKType.liveness; case "C06": return SDKType.c06
          case "FACESERVICE": return SDKType.faceService; default: return nil
          }
        }

      // Parse appKeyConfig from JSON
      var appKeyConfig = AppKeyConfig(appKey: appKey)
      if let appKeyJson = self?.jsonStringToDictionary(appKeyConfigJson) {
        appKeyConfig = AppKeyConfig(
          appKey: appKeyJson["appKey"] as? String ?? appKey,
          appKeyNfc: appKeyJson["appKeyNfc"] as? String ?? appKey,
          appKeyC06: appKeyJson["appKeyC06"] as? String ?? appKey,
          appKeyOcr: appKeyJson["appKeyOcr"] as? String ?? appKey,
          appKeyLiveness: appKeyJson["appKeyLiveness"] as? String ?? appKey,
          appKeyFaceService: appKeyJson["appKeyFaceService"] as? String ?? appKey
        )
      }

      // Parse optionConfig from JSON
      var maxRetry = 3
      if let optionJson = self?.jsonStringToDictionary(optionConfigJson) {
        maxRetry = optionJson["countMaxRetry"] as? Int ?? optionJson["maxRetry"] as? Int ?? 3
      }
      let optionConfig = OptionConfig(countMaxRetry: maxRetry, language: language == "en" ? "en" : "vi")
      let styleConfig = StyleConfig()

      // LivenessConfig matching Android
      let livenessConfig = LivenessConfig(
        isActiveLiveness: true,
        isActiveLivenessColor: false,
        isShowCameraFont: true,
        forceCaptureTimeout: 60_000
      )

      // Parse appIDType from optionConfig (default: .none)
      var appIDType: AppIDType = .none
      if let optionJson = self?.jsonStringToDictionary(optionConfigJson),
         let appIDTypeStr = optionJson["appIDType"] as? String {
        switch appIDTypeStr.uppercased() {
        case "VIKKI":
          appIDType = .vikki
        case "HD_BANK":
          appIDType = .hdBank
        default:
          appIDType = .none
        }
      }

      let ekycConfig = EKYCConfigSDK(appKey: appKeyConfig, optionConfig: optionConfig, styleConfig: styleConfig, livenessConfig: livenessConfig, appIDType: appIDType, flowSDK: flowTypes.isEmpty ? [.ocr, .nfc, .liveness] : flowTypes)
      SdkEkycUI.startEkyc(from: vc, ekycConfigSDK: ekycConfig,
        callbackSuccess: { event, data in
          let result = data as? SDKEkycResult
          if event.description.contains("START") && result?.ekycStateModel == nil {
            self?.emit("onEkycUISuccess", body: ["status": "started", "event": event.description])
            return
          }
          let files = result?.ekycStateModel?.eKYCFileModel
          var dataDict: [String: Any] = [:]
          if let v = result?.nfcResponse { dataDict["nfcResponse"] = v }
          if let v = result?.checkC06Response?.description { dataDict["checkC06Response"] = v }
          if let v = result?.ocrResponse?.description { dataDict["ocrResponse"] = v }
          if let v = result?.checkLivenessResponse?.description { dataDict["checkLivenessResponse"] = v }
          if let v = result?.checkFaceResponse?.description { dataDict["checkFaceResponse"] = v }
          if let v = result?.customData { dataDict["customData"] = v }
          let dataJson = (try? JSONSerialization.data(withJSONObject: dataDict))
            .flatMap { String(data: $0, encoding: .utf8) } ?? "{}"
          var body: [String: Any] = ["status": "success", "event": event.description, "data": dataJson, "transactionId": result?.ekycStateModel?.transactionId ?? ""]
          if let facePath = files?.imageFace { body["imageFacePath"] = facePath.path }
          if let frontPath = files?.imageOcrFront { body["imageOcrFrontPath"] = frontPath.path }
          if let backPath = files?.imageOcrBack { body["imageOcrBackPath"] = backPath.path }
          self?.emit("onEkycUISuccess", body: body)
          resolve(body)
        },
        callbackError: { event, errorResult in
          let errBody: [String: Any] = ["status": "error", "event": event.description, "customCode": errorResult.code ?? "", "customMessage": errorResult.message ?? "", "message": errorResult.message ?? ""]
          self?.emit("onEkycUIError", body: errBody)
          reject(event.description, errorResult.message, nil)
        }
      )
    }
  }

  // MARK: - SMS OTP

  @objc(sendOtp:phoneNumber:purpose:referenceId:resolver:rejecter:)
  func sendOtp(_ appKey: String, phoneNumber: String, purpose: String?, referenceId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    let config = SmsOtpConfig(appKey: appKey, phoneNumber: phoneNumber, purpose: purpose ?? "transaction", referenceId: referenceId, requestId: nil)
    SdkSmsOtpService.shared.sendOtp(config: config,
      callbackSuccess: { [weak self] event, data in
        var resp: [String: Any] = ["event": "\(event)"]
        if let customData = data?.customData { resp["customData"] = customData }
        self?.emit("onSmsOtpSendSuccess", body: resp)
        resolve(resp)
      },
      callbackError: { [weak self] _, err in
        self?.emit("onSmsOtpError", body: ["code": err.code, "message": err.message ?? ""])
        reject(err.code, err.message, nil)
      }
    )
  }

  @objc(verifyOtp:phoneNumber:purpose:referenceId:requestId:otpCode:resolver:rejecter:)
  func verifyOtp(_ appKey: String, phoneNumber: String, purpose: String?, referenceId: String, requestId: String, otpCode: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    let config = SmsOtpConfig(appKey: appKey, phoneNumber: phoneNumber, purpose: purpose ?? "transaction", referenceId: referenceId, requestId: requestId)
    SdkSmsOtpService.shared.verifyOtp(config: config, otpCode: otpCode,
      callbackSuccess: { [weak self] event, data in
        var resp: [String: Any] = ["event": "\(event)"]
        if let customData = data?.customData { resp["customData"] = customData }
        self?.emit("onSmsOtpVerifySuccess", body: resp)
        resolve(resp)
      },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(resendOtp:phoneNumber:purpose:referenceId:requestId:resolver:rejecter:)
  func resendOtp(_ appKey: String, phoneNumber: String, purpose: String?, referenceId: String, requestId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    let config = SmsOtpConfig(appKey: appKey, phoneNumber: phoneNumber, purpose: purpose ?? "transaction", referenceId: referenceId, requestId: requestId)
    SdkSmsOtpService.shared.resendOtp(config: config,
      callbackSuccess: { [weak self] event, data in
        var resp: [String: Any] = ["event": "\(event)"]
        if let customData = data?.customData { resp["customData"] = customData }
        self?.emit("onSmsOtpResendSuccess", body: resp)
        resolve(resp)
      },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  // MARK: - eSign

  @objc(initializeESign:isProd:resolver:rejecter:)
  func initializeESign(_ finosToken: String?, isProd: Bool, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.initialize(licenseCode: finosToken,
      callbackSuccess: { code, msg in resolve(["code": "\(code)", "message": msg]) },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(getSdkToken:name:deviceId:resolver:rejecter:)
  func getSdkToken(_ identity: String, name: String, deviceId: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.getSdkToken(identity: identity, name: name, deviceId: deviceId,
      callbackSuccess: { token in resolve(token) },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(openSessionId:username:rememberMe:resolver:rejecter:)
  func openSessionId(_ accessToken: String?, username: String?, rememberMe: Bool, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.openSessionId(accessToken: accessToken, username: username ?? "", rememberMe: rememberMe,
      callbackSuccess: { deviceState, code, msg in resolve(["deviceState": deviceState, "code": code, "message": msg]) },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(registerDevice:pinCode:fcmToken:resolver:rejecter:)
  func registerDevice(_ recoverCode: String, pinCode: String, fcmToken: String?, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.registerDevice(recoverCode: recoverCode, pinCode: pinCode, fcmToken: fcmToken ?? "",
      callbackSuccess: { code, msg in resolve(["code": "\(code)", "message": msg]) },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(listCerts:pageSize:resolver:rejecter:)
  func listCerts(_ pageNumber: Int, pageSize: Int, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.listCerts(pageNumber: pageNumber, pageSize: pageSize,
      callbackSuccess: { certs in resolve(certs.map { ["serial": $0.serial, "subject": $0.subject, "validFrom": $0.validFrom as Any, "validTo": $0.validTo as Any] }) },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(verifyCert:resolver:rejecter:)
  func verifyCert(_ serial: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.verifyCert(serial: serial,
      callbackSuccess: { code, msg in resolve(["code": "\(code)", "message": msg]) },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(listSignRequest:pageSize:resolver:rejecter:)
  func listSignRequest(_ pageNumber: Int, pageSize: Int, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.listSignRequest(pageNumber: pageNumber, pageSize: pageSize,
      callbackSuccess: { requests in resolve(requests.map { ["requestId": $0.requestId, "authData": $0.authData as Any, "authId": $0.authId as Any] }) },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(confirmSign:pinCode:authId:authData:confirm:resolver:rejecter:)
  func confirmSign(_ signRequestId: String, pinCode: String, authId: String?, authData: String?, confirm: Bool, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.confirmSign(signRequestId: signRequestId, pinCode: pinCode, authId: authId ?? "", authData: authData ?? "", confirm: confirm,
      callbackSuccess: { code, msg in resolve(["code": "\(code)", "message": msg]) },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  private func jsonStringToDictionary(_ text: String) -> [String: Any]? {
    if let data = text.data(using: .utf8) {
      return try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
    }
    return nil
  }

  @objc(registerRemoteSigning:resolver:rejecter:)
  func registerRemoteSigning(_ requestJson: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.registerRemoteSigning(requestJson: requestJson,
      callbackSuccess: { raw in 
        var res = self.jsonStringToDictionary(raw) ?? ["status": 1, "msg": "OK"]
        res["rawResponse"] = raw
        resolve(res)
      },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(signPdf:resolver:rejecter:)
  func signPdf(_ requestJson: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.signPdf(requestJson: requestJson,
      callbackSuccess: { raw in
        var res = self.jsonStringToDictionary(raw) ?? ["status": 1, "message": "OK"]
        res["rawResponse"] = raw
        if let msg = res["msg"] as? String {
            res["message"] = msg
        }
        resolve(res)
      },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(signPdfMultiplePositions:resolver:rejecter:)
  func signPdfMultiplePositions(_ requestJson: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.signPdfMultiplePositions(requestJson: requestJson,
      callbackSuccess: { raw in
        var res = self.jsonStringToDictionary(raw) ?? ["status": 1, "message": "OK"]
        res["rawResponse"] = raw
        if let msg = res["msg"] as? String {
            res["message"] = msg
        }
        resolve(res)
      },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(sendConfirmationDocument:resolver:rejecter:)
  func sendConfirmationDocument(_ requestJson: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.sendConfirmationDocument(requestJson: requestJson,
      callbackSuccess: { raw in resolve(raw) },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(registerAndConfirm:confirmationDocBase64:resolver:rejecter:)
  func registerAndConfirm(_ requestJson: String, confirmationDocBase64: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.registerAndConfirm(requestJson: requestJson, confirmationDocBase64: confirmationDocBase64,
      callbackSuccess: { raw in
        var res = self.jsonStringToDictionary(raw) ?? ["status": 1, "msg": "OK"]
        res["rawResponse"] = raw
        resolve(res)
      },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(initAuthorize:quantity:time:message:resolver:rejecter:)
  func initAuthorize(_ serial: String, quantity: Int, time: Int, message: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.initAuthorize(serial: serial, quantity: quantity, time: time, message: message,
      callbackSuccess: { code, msg in resolve(["code": "\(code)", "message": msg]) },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(listAuthorize:pageSize:status:resolver:rejecter:)
  func listAuthorize(_ pageNumber: Int, pageSize: Int, status: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.listAuthorize(pageNumber: pageNumber, pageSize: pageSize, status: status,
      callbackSuccess: { list in
        resolve(list.map { [
          "authorizeRequestId": $0.authorizeRequestId, "serial": $0.serial,
          "quantity": $0.quantity, "time": $0.time, "message": $0.message as Any,
          "authId": $0.authId as Any, "authData": $0.authData as Any,
          "createTime": $0.createTime as Any, "expireTime": $0.expireTime as Any
        ]})
      },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  @objc(registerAuthorize:authData:authorizeRequestId:userPin:confirm:resolver:rejecter:)
  func registerAuthorize(_ authId: String, authData: String, authorizeRequestId: String, userPin: String, confirm: Bool, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    ESignSDKManager.shared.registerAuthorize(authId: authId, authData: authData, authorizeRequestId: authorizeRequestId, userPin: userPin, confirm: confirm,
      callbackSuccess: { code, msg in resolve(["code": "\(code)", "message": msg]) },
      callbackError: { _, err in reject(err.code, err.message, nil) }
    )
  }

  // MARK: - Lifecycle / NFC stubs (iOS NFC lifecycle is handled differently)

  @objc(onResume)
  func onResume() {}

  @objc(onPause)
  func onPause() {}

  @objc(handleNewIntent)
  func handleNewIntent() {}

  @objc(extractNfcDataForC06:resolver:rejecter:)
  func extractNfcDataForC06(_ nfcResultJson: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
    // On iOS the NFC data is already parsed; return the raw JSON as-is
    resolve(nfcResultJson)
  }

  // MARK: - Helpers

  /// Resolve image input: file path, file:// URI, or base64 string → UIImage
  private func resolveImageInput(_ input: String) -> UIImage? {
    let path = input.replacingOccurrences(of: "file://", with: "")
    // File path
    if let img = UIImage(contentsOfFile: path) { return img }
    // Base64
    let b64 = input.contains(",") ? String(input.split(separator: ",").last ?? Substring(input)) : input
    if let data = Data(base64Encoded: b64, options: .ignoreUnknownCharacters) {
      return UIImage(data: data)
    }
    return nil
  }
}
