// Copyright (C) 2025 Acoustic, L.P. All rights reserved.
//
// NOTICE: This file contains material that is confidential and proprietary to
// Acoustic, L.P. and/or other developers. No license is granted under any intellectual or
// industrial property rights of Acoustic, L.P. except as may be provided in an agreement with
// Acoustic, L.P. Any unauthorized copying or distribution of content from this file is
// prohibited.
//
//
//  Created by Omar Hernandez on 5/9/25.
//

import Foundation
import Connect
import NitroModules
import OSLog

// Two loggers under one subsystem so developers can filter Console.app / Xcode
// output by category. `config` covers anything about reading and validating
// `AcousticConnectRNConfig.bundle`; `bridge` covers SDK lifecycle calls
// (`enable`, `disable`, the resolved-config summary line).
private let configLog = Logger(subsystem: "com.acoustic.AcousticConnectRN", category: "config")
private let bridgeLog = Logger(subsystem: "com.acoustic.AcousticConnectRN", category: "bridge")

// Small descriptor on `ConnectPushConfig` so the bridge can log "off" /
// "automatic" / "manual" without round-tripping through the JS-side enum.
private extension ConnectPushConfig {
    var modeDescription: String {
        switch self.mode {
        case .off:       return "off"
        case .automatic: return "automatic"
        case .manual:    return "manual"
        @unknown default: return "unknown"
        }
    }
}

// Single compat entry point for the config-item store. Works against every
// shipping iOS SDK variant (Release with separate EOCore/Tealeaf, current merged
// Debug 2.1.2, future merged-only) because `ConnectApplicationHelper` ships with
// each of them. Not importing `EOCore`/`Tealeaf` avoids a link dependency on
// frameworks that aren't always packaged into the app bundle.
private enum ConnectConfigStore {
    @discardableResult
    static func set(_ key: String, value: Any) -> Bool {
        return ConnectApplicationHelper.sharedInstance().setConfigurableItem(key, value: value)
    }

    static func bool(forKey key: String, default def: Bool) -> Bool {
        let raw = ConnectApplicationHelper.sharedInstance().value(forConfigurableItem: key)
        if let n = raw as? NSNumber { return n.boolValue }
        if let s = raw as? String   { return (s as NSString).boolValue }
        return def
    }

    static func string(forKey key: String, default def: String) -> String? {
        return (ConnectApplicationHelper.sharedInstance().value(forConfigurableItem: key) as? String) ?? def
    }

    static func number(forKey key: String, default def: Double) -> Double {
        return (ConnectApplicationHelper.sharedInstance().value(forConfigurableItem: key) as? NSNumber)?.doubleValue ?? def
    }
}

class HybridAcousticConnectRN: HybridAcousticConnectRNSpec {

    // Constructor — auto-inits the SDK from `ConnectConfig.json`. Matches the
    // pre-CA-137696 behaviour; consumers that need consent-gated init can call
    // `disable()` immediately and `enable()` once they have permission.
    override init() {
        super.init()

        Task { @MainActor [weak self] in
            self?.load()
        }

        bridgeLog.info("HybridAcousticConnectRN constructed; load() dispatched on main actor.")
    }

    // `load()` calls into the MainActor-isolated `ConnectSDK.shared` API.
    // Marking the method `@MainActor` means Swift can verify the SDK calls
    // without an inner dispatch, and it makes the contract explicit for any
    // future caller.
    @MainActor
    func load() {
        let connectData = self.parseConnectConfigJSON()

        guard let connectData = connectData else {
            configLog.error("AcousticConnectRNConfig.bundle missing or unreadable. SDK not initialised. Verify ConnectConfig.json at your project root and re-run `pod install`.")
            return
        }
        configLog.info("Read AcousticConnectRNConfig.bundle (\(connectData.count) top-level keys).")

        let appKey  = (connectData["AppKey"]         as? String) ?? ""
        let postURL = (connectData["PostMessageUrl"] as? String) ?? ""

        // Skip enable entirely if the consumer's config didn't reach us —
        // proceeding with empty strings would let the SDK silently fall back
        // to its bundled demo collector, which is exactly the bug this flow
        // is designed to prevent. Most likely cause: missing or malformed
        // ConnectConfig.json at the consumer's project root, or `pod install`
        // didn't run after the file was added.
        guard !appKey.isEmpty, !postURL.isEmpty else {
            configLog.error("SDK NOT ENABLED — empty AppKey or PostMessageUrl. Verify ConnectConfig.json at your project root contains both fields, then re-run `pod install`.")
            return
        }

        // Log every field the bridge consumes so the developer can see in
        // Xcode / Console.app exactly what's in effect, without cross-checking
        // ConnectConfig.json.
        configLog.info("AppKey: \(appKey, privacy: .public) (length=\(appKey.count))")
        configLog.info("PostMessageUrl: \(postURL, privacy: .public)")
        if let killSwitch = connectData["KillSwitchUrl"] as? String, !killSwitch.isEmpty {
            configLog.info("KillSwitchUrl: \(killSwitch, privacy: .public)")
        }

        let pushConfig = self.resolvePushConfig(from: connectData)

        let sdk = ConnectSDK.shared
        bridgeLog.info("Calling ConnectSDK.shared.enable(with: ConnectConfig(push: \(pushConfig.modeDescription, privacy: .public)))")
        sdk.enable(with: ConnectConfig(appKey: appKey, postURL: postURL, push: pushConfig))
        _ = sdk.setReactNative(true, wrapNavigationContainer: true)

        // Apply remaining programmatic overrides (everything other than the
        // two enable parameters) AFTER enable — matches the order in the SDK's
        // own ConnectSDK.shared.enable(with:) implementation.
        self.applyConnectConfig(connectData)

        bridgeLog.info("SDK initialised: appKey length=\(appKey.count), push=\(pushConfig.modeDescription, privacy: .public), isReactNative=true, wrapNavigationContainer=true")
    }

    /// Resolves `PushEnabled` + `iOSPushMode` + `iOSAppGroupIdentifier` from the
    /// bundled config into a `ConnectPushConfig`. Validation errors are logged
    /// at `.error`; soft mismatches at `.warning`. The SDK always falls back to
    /// a safe default (`.off`) so an invalid config never crashes the app.
    @MainActor
    private func resolvePushConfig(from connectData: [String: Any]) -> ConnectPushConfig {
        let pushEnabled = self.parsePushEnabled(connectData["PushEnabled"])
        let iOSPushModeRaw = connectData["iOSPushMode"] as? String
        let groupId = connectData["iOSAppGroupIdentifier"] as? String

        configLog.info("PushEnabled: \(pushEnabled)")

        // Inconsistency: iOSPushMode is set but PushEnabled is false. The
        // mode value would never take effect; warn the developer so they
        // know to fix one or the other.
        if !pushEnabled, let raw = iOSPushModeRaw, !raw.isEmpty {
            configLog.error("iOSPushMode \"\(raw, privacy: .public)\" is set but PushEnabled is false. Ignoring iOSPushMode; SDK will run with push disabled.")
            return .off
        }

        guard pushEnabled else {
            configLog.info("iOSPushMode: (not used, PushEnabled is false)")
            configLog.info("iOSAppGroupIdentifier: (not used, PushEnabled is false)")
            return .off
        }

        let mode = self.parseIOSPushMode(iOSPushModeRaw)
        configLog.info("iOSPushMode: \(mode.descriptor, privacy: .public)")
        if let groupId = groupId, !groupId.isEmpty {
            configLog.info("iOSAppGroupIdentifier: \(groupId, privacy: .public)")
        } else {
            configLog.warning("iOSAppGroupIdentifier is not set. Required if your app uses a Notification Service or Notification Content extension to render rich push payloads.")
        }

        switch mode {
        case .automatic:
            return ConnectPushConfig(mode: .automatic, appGroupIdentifier: groupId)
        case .manual:
            return ConnectPushConfig(mode: .manual, appGroupIdentifier: groupId)
        }
    }

    /// Lenient `PushEnabled` parser — accepts a `Bool` directly, or a string
    /// like `"true"`/`"false"`/`"yes"`/`"no"`. Anything else falls back to
    /// `false` with a warning so a typo in the JSON doesn't silently enable
    /// push when the developer thought they'd turned it off.
    private func parsePushEnabled(_ raw: Any?) -> Bool {
        if let b = raw as? Bool { return b }
        if let n = raw as? NSNumber { return n.boolValue }
        if let s = raw as? String {
            switch s.lowercased() {
            case "true", "yes", "1": return true
            case "false", "no", "0", "": return false
            default:
                configLog.warning("PushEnabled string \"\(s, privacy: .public)\" not recognised. Falling back to false.")
                return false
            }
        }
        if raw != nil {
            configLog.warning("PushEnabled is set but is neither a Bool nor a recognised string. Falling back to false.")
        }
        return false
    }

    private enum IOSPushMode { case automatic, manual
        var descriptor: String { self == .automatic ? "automatic" : "manual" } }

    private func parseIOSPushMode(_ raw: String?) -> IOSPushMode {
        switch raw?.lowercased() {
        case "automatic", "auto", nil, "":
            return .automatic
        case "manual":
            return .manual
        default:
            configLog.error("iOSPushMode \"\(raw ?? "", privacy: .public)\" not recognised. Allowed values: \"automatic\", \"manual\". Falling back to \"automatic\".")
            return .automatic
        }
    }

    /// Reads and returns the `Connect` dictionary from the bundled
    /// `AcousticConnectRNConfig.json`. The bundle is populated at pod install
    /// time by the podspec — see `AcousticConnectRN.podspec`. Returns nil when
    /// the bundle or file is missing; callers fall back to empty values.
    private func parseConnectConfigJSON() -> [String: Any]? {
        let bundle = Bundle(for: Self.self)
        let bundleURL = bundle.resourceURL?.appendingPathComponent("AcousticConnectRNConfig.bundle")
        let resourceBundle = bundleURL.flatMap { Bundle(url: $0) }
        let path = resourceBundle?.path(forResource: "AcousticConnectRNConfig", ofType: "json")
        let data = path.flatMap { try? Data(contentsOf: URL(fileURLWithPath: $0)) }
        let jsonData = data.flatMap { try? JSONSerialization.jsonObject(with: $0, options: []) as? [String: Any] }
        return jsonData?["Connect"] as? [String: Any]
    }
    
    /// Applies every recognised key from the parsed `Connect` config as a
    /// programmatic override. Called by `load()` AFTER enable so values land on
    /// a fully-initialised SDK. AppKey and PostMessageUrl are skipped here —
    /// they're passed to enable directly because the SDK's bundle-read
    /// short-circuits `tempConfigDict` for those two specifically.
    private func applyConnectConfig(_ connectData: [String: Any]) {
        let eocoreKeys = [
            "CachingLevel", "DoPostAppComesFromBackground", "DoPostAppGoesToBackground", "DoPostAppGoesToClose",
            "DoPostAppIsLaunched", "DoPostOnIntervals", "DynamicConfigurationEnabled", "HasToPersistLocalCache",
            "LoggingLevel", "ManualPostEnabled", "PostMessageLevelCellular", "PostMessageLevelWiFi",
            "PostMessageTimeIntervals", "CachedFileMaxBytesSize", "CompressPostMessage", "DefaultOrientation",
            "LibraryVersion", "MaxNumberOfFilesToCache", "MessageVersion", "PostMessageMaxBytesSize",
            "PostMessageTimeout", "TurnOffCorrectOrientationUpdates"
        ]

        let tealeafKeys = [
            "AppKey", "DisableAutoInstrumentation", "GetImageDataOnScreenLayout", "JavaScriptInjectionDelay",
            "KillSwitchEnabled", "KillSwitchMaxNumberOfTries", "KillSwitchTimeInterval", "KillSwitchTimeout",
            "KillSwitchUrl", "UseWhiteList", "WhiteListParam", "LogLocationEnabled", "MaxStringsLength",
            "PercentOfScreenshotsSize", "PercentToCompressImage", "ScreenShotPixelDensity", "PostMessageUrl",
            "DoPostOnScreenChange", "printScreen", "ScreenshotFormat", "SessionTimeout", "SessionizationCookieName",
            "CookieSecure", "disableTLTDID", "SetGestureDetector", "AddGestureRecognizerUIButton",
            "AddGestureRecognizerUIDatePicker", "AddGestureRecognizerUIPageControl", "AddGestureRecognizerUIPickerView",
            "AddGestureRecognizerUIScrollView", "AddGestureRecognizerUISegmentedControl", "AddGestureRecognizerUISwitch",
            "AddGestureRecognizerUITextView", "AddGestureRecognizerWKWebView", "AddMessageTypeHeader",
            "DisableAlertAutoCapture", "DisableAlertBackgroundForDisabledLogViewLayout", "DisableKeyboardCapture",
            "EnableWebViewInjectionForDisabledAutoCapture", "FilterMessageTypes", "InitialZIndex", "IpPlaceholder",
            "LibraryVersion", "LogFullRequestResponsePayloads", "LogViewLayoutOnScreenTransition", "MessageTypeHeader",
            "MessageTypes", "RemoveIp", "RemoveSwiftUIDuplicates", "SubViewArrayZIndexIncrementTrigger",
            "SwiftUICaptureNonVariadic", "TextFieldBeingEditedUseSender", "TreatJsonDictionariesAsString", "UICPayload",
            "UIKeyboardCaptureTouches", "UseJPGForReplayImagesExtension", "UseXpathId", "actionSheet:buttonIndex",
            "actionSheet:show", "alertView:buttonIndex", "alertView:show", "autolog:pageControl",
            "autolog:textBox:_searchFieldEndEditing", "button:click", "button:load", "canvas:click", "connection",
            "customEvent", "datePicker:dateChange", "exception", "gestures", "label:load", "label:textChange", "layout",
            "location", "mobileState", "orientation", "pageControl:valueChanged", "pickerView:valueChanged",
            "screenChangeLevel", "scroller:scrollChange", "selectList:UITableViewSelectionDidChangeNotification",
            "selectList:load", "selectList:valueChange", "slider:valueChange", "stepper:valueChange",
            "textBox:_searchFieldBeginChanged", "textBox:_searchFieldBeginEditing", "textBox:_searchFieldEditingChanged",
            "textBox:textChange", "textBox:textChanged", "textBox:textFieldDidChange", "toggleButton:click"
        ]

        for (key, value) in connectData {
            // Skip keys load() passes directly to enable — re-applying is harmless but redundant.
            if key == "AppKey" || key == "PostMessageUrl" { continue }

            if tealeafKeys.contains(key) || eocoreKeys.contains(key) {
                ConnectConfigStore.set(key, value: value)
            } else if key == "layoutConfig",
                      let layoutConfig = value as? [String: Any] {
                if let autoLayout = layoutConfig["AutoLayout"] {
                    ConnectConfigStore.set("AutoLayout", value: autoLayout)
                }
                if let appendMapIds = layoutConfig["AppendMapIds"] {
                    ConnectConfigStore.set("AppendMapIds", value: appendMapIds)
                }
            }
        }
    }

    // MARK: - Gate-keeper API

    /// Re-enables the Connect SDK after a prior `disable()`. All configuration
    /// comes from `ConnectConfig.json` — re-runs `load()` on the main actor,
    /// which the native SDK no-ops if it's already enabled.
    func enable() throws -> Bool {
        bridgeLog.info("enable() called from JS.")
        Task { @MainActor [weak self] in self?.load() }
        return true
    }

    /// Disables the Connect SDK. Idempotent — the native SDK handles repeat
    /// calls safely. After this call, sessions and push state are released;
    /// a subsequent `enable()` re-initialises from `ConnectConfig.json`.
    func disable() throws -> Bool {
        bridgeLog.info("disable() called from JS.")
        Task { @MainActor in
            ConnectSDK.shared.disable()
            bridgeLog.info("SDK disabled.")
        }
        return true
    }

    /// Sets the module's configuration item from AdvancedConfig.json or BasicConfig.plist that matches the specified key as a BOOL value.
    /// - Parameters:
    ///   - key: Key to update value in configuration settings.
    ///   - value: Value to use.
    ///   - moduleName: The name of the module to be updated. For EOCore settings, please use 'EOCore' which can be found the following files EOCoreBasicConfig.plist, EOCoreBasicConfig.properties or EOCoreAdvancedConfig.json and 'Connect' for Connect which can be found the following files ConnectBasicConfig.plist, ConnectBasicConfig.properties or ConnectAdvancedConfig.json.
    /// - Returns: Whether it was able to set the value as Boolean value.
    func setBooleanConfigItemForKey(key: String, value: Bool, moduleName: String) throws -> Bool {
        return ConnectConfigStore.set(key, value: value)
    }
    
    /// Sets the module's configuration item from AdvancedConfig.json or BasicConfig.plist that matches the specified key as a NString value.
    /// - Parameters:
    ///   - key: Key to update value in configuration settings.
    ///   - value: Value to use.
    ///   - moduleName: The name of the module to be updated. For EOCore settings, please use 'EOCore' which can be found the following files EOCoreBasicConfig.plist, EOCoreBasicConfig.properties or EOCoreAdvancedConfig.json and 'Connect' for Connect which can be found the following files ConnectBasicConfig.plist, ConnectBasicConfig.properties or ConnectAdvancedConfig.json.
    /// - Returns: Whether it was able to set the value as Boolean value.
    func setStringItemForKey(key: String, value: String, moduleName: String) throws -> Bool {
        return ConnectConfigStore.set(key, value: value)
    }
    
    /// Sets the module's configuration item from AdvancedConfig.json or BasicConfig.plist that matches the specified key as a NSNumber value.
    /// - Parameters:
    ///   - key: Key to update value in configuration settings.
    ///   - value: Value to use.
    ///   - moduleName: The name of the module to be updated. For EOCore settings, please use 'EOCore' which can be found the following files EOCoreBasicConfig.plist, EOCoreBasicConfig.properties or EOCoreAdvancedConfig.json and 'Connect' for Connect which can be found the following files ConnectBasicConfig.plist, ConnectBasicConfig.properties or ConnectAdvancedConfig.json.
    /// - Returns: Whether it was able to set the value as Boolean value.
    func setNumberItemForKey(key: String, value: Double, moduleName: String) throws -> Bool {
        return ConnectConfigStore.set(key, value: value)
    }
  
  
    /// Sets the module's configuration item from AdvancedConfig.json or BasicConfig.properties that matches the specified key.
    /// - Parameters:
    ///   - key: Key to update value in configuration settings.
    ///   - value: Value to use.
    ///   - moduleName: The name of the module to be updated. For EOCore settings, please use 'EOCore' which can be found the following files EOCoreBasicConfig.plist, EOCoreBasicConfig.properties or EOCoreAdvancedConfig.json and 'Connect' for Connect which can be found the following files ConnectBasicConfig.plist, ConnectBasicConfig.properties or ConnectAdvancedConfig.json.
    /// - Returns: Whether it was able to set the value.
    func setConfigItemForKey(key: String, value: Variant_Bool_String_Double, moduleName: String) throws -> Bool {
        return ConnectConfigStore.set(key, value: convertVariantToAny(value))
    }
    
    /// Gets the module's configuration item from AdvancedConfig.json or BasicConfig.plist that matches the specified key as a BOOL value.
    /// - Parameters:
    ///   - key: Key to update value in configuration settings.
    ///   - theDefault: Default value if not found.
    ///   - moduleName: The name of the module to be updated. For EOCore settings, please use 'EOCore' which can be found the following files EOCoreBasicConfig.plist, EOCoreBasicConfig.properties or EOCoreAdvancedConfig.json and 'Connect' for Connect which can be found the following files ConnectBasicConfig.plist, ConnectBasicConfig.properties or ConnectAdvancedConfig.json.
    /// - Returns: The value of the configuration item key as a BOOL value.
    func getBooleanConfigItemForKey(theDefault: Bool, key: String, moduleName: String) throws -> Bool {
        return ConnectConfigStore.bool(forKey: key, default: theDefault)
    }
    
    /// Gets the module's configuration item from AdvancedConfig.json or BasicConfig.plist that matches the specified key as a NString value.
    /// - Parameters:
    ///   - key: Key to update value in configuration settings.
    ///   - theDefault: Default value if not found.
    ///   - moduleName: The name of the module to be updated. For EOCore settings, please use 'EOCore' which can be found the following files EOCoreBasicConfig.plist, EOCoreBasicConfig.properties or EOCoreAdvancedConfig.json and 'Connect' for Connect which can be found the following files ConnectBasicConfig.plist, ConnectBasicConfig.properties or ConnectAdvancedConfig.json.
    /// - Returns: The value of the configuration item key as a NString value.
    func getStringItemForKey(theDefault: String, key: String, moduleName: String) throws -> Variant_NullType_String? {
        let result = ConnectConfigStore.string(forKey: key, default: theDefault)
        return result.map { .second($0) }
    }
    
    /// Gets the module's configuration item from AdvancedConfig.json or BasicConfig.plist that matches the specified key as a NSNumber value.
    /// - Parameters:
    ///   - key: Key to update value in configuration settings.
    ///   - theDefault: Default value if not found.
    ///   - moduleName: The name of the module to be updated. For EOCore settings, please use 'EOCore' which can be found the following files EOCoreBasicConfig.plist, EOCoreBasicConfig.properties or EOCoreAdvancedConfig.json and 'Connect' for Connect which can be found the following files ConnectBasicConfig.plist, ConnectBasicConfig.properties or ConnectAdvancedConfig.json.
    /// - Returns: The value of the configuration item key as a NSNumber value.
    func getNumberItemForKey(theDefault: Double, key: String, moduleName: String) throws -> Double {
        return ConnectConfigStore.number(forKey: key, default: theDefault)
    }
    
    /// Log custom event.
    /// - Parameters:
    ///   - eventName:the name of the event to be logged this will appear in the posted json.
    ///   - values: additional key value pairs to be logged with the message.
    ///   - level: set a custom log level to the event.
    /// - Returns: Boolean value will return whether it was able to log the custom event.
    func logCustomEvent(eventName: String, values: Dictionary<String, Variant_Bool_String_Double>, level: Double) throws -> Bool {
        let logLevel = try getLogLevel(level: level)
        let result = ConnectCustomEvent().logEvent(eventName, values: convertToAnyDictionary(input: values), level: logLevel)
        return result
    }
    
    /// Log signal data.
    /// - Parameters:
    ///   - values: additional key value pairs to be logged with the signal message.
    ///   - level: set a custom log level to the event.
    /// - Returns: Boolean value will return whether it was able to log the signal message.
    func logSignal(values: Dictionary<String, Variant_Bool_String_Double>, level: Double) throws -> Bool {
        let logLevel = try getLogLevel(level: level)
        let result = ConnectCustomEvent().logSignal(convertToAnyDictionary(input: values), level: logLevel)
        return result
    }
    
    /// Log exception.
    /// - Parameters:
    ///   - message: the message of the error/exception to be logged this will appear in the posted json.
    ///   - stackInfo: the stack trace to be logged with the message.
    ///   - unhandled: Whether exception is unhandled.
    /// - Returns: Boolean value will return whether it was able to log the exception event.
    func logExceptionEvent(message: String, stackInfo: String, unhandled: Bool) throws -> Bool {
        let exceptionDict: [String: Any] = [
            "type": "React Plugin",
            "message": message,
            "stacktrace": stackInfo
        ]
        let result = ConnectCustomEvent().logNSExceptionEvent(nil, dataDictionary: exceptionDict, isUnhandled: unhandled)
        return result
    }
    
    /// Requests that the framework logs a geographic location
    /// - Returns: Boolean value will return whether it was able to log the location event.
    func logLocation() throws -> Bool {
        let result = ConnectCustomEvent().logLocation(nil)
        return result
    }
    
    /// Requests that the framework logs the location information. This is not logged automatically to avoid making unnecessary location updates and to protect the privacy of your application's users by ensuring that location is reported only when the app has some other reason to request it. Your application must include the Core Location framework.
    /// - Parameters:
    ///   - latitude: The geographic latitude of the user.
    ///   - longitude: The geographic longitude of the user.
    ///   - level: lThe monitoring level of the event.
    /// - Returns: Boolean value will return whether it was able to log the location event.
    func logLocationWithLatitudeLongitude(latitude: Double, longitude: Double, level: Double) throws -> Bool {
        let logLevel = try getLogLevel(level: level)
        let result = ConnectCustomEvent().logLocationUpdate(withLatitude: latitude, longitude: longitude, level: logLevel)
        return result
    }
    
    /// Requests that the framework logs the click events on any UIControl or UIView. Click event is a normalized form of touch up inside event.
    /// - Parameters:
    ///   - target: Native node handle for a component from React Native.
    ///   - controlId: Control id a component from React Native.
    /// - Returns: Boolean value will return whether it was able to log the click event.
    func logClickEvent(target: Double, controlId: String) throws -> Bool {
      var result: Bool = false
      Task { @MainActor in
        let view: UIView? = nil
        let result = ConnectCustomEvent().logClick(view, controlId: controlId, data: nil)
        _ = result
      }
      return result
    }
    
    /// Requests that the framework logs the text change events.
    /// - Parameters:
    ///   - target: Native node handle for a component from React Native.
    ///   - controlId: Control id a component from React Native.
    ///   - text: The input string from txt control.
    /// - Returns: Boolean value will return whether it was able to log the text change event.
    func logTextChangeEvent(target: Double, controlId: String, text: Variant_NullType_String?) throws -> Bool {
        let view:UIView? = nil
        var data: [String: Any] = [:]

        if case .second(let str) = text {
            data["text"] = str
        }
        let result = ConnectCustomEvent().logTextChange(view, controlId: controlId, data: data)
        return result
    }
    
    /// Requests that the framework save the current  application page name.
    /// - Parameter logicalPageName: Page name or title e.g. "Login View Controller"; Must not be empty.
    /// - Returns: Boolean value will return whether it was able to log the screenview event.
    func setCurrentScreenName(logicalPageName: String) throws -> Bool {
        let result = ConnectApplicationHelper().setCurrentScreenName(logicalPageName)
        return result
    }
    
    /// Requests that the framework logs an application context for load.
    /// - Parameters:
    ///   - logicalPageName: Page name or title e.g. "Login View Controller"; Must not be empty.
    ///   - referrer: Page name or title that loads logicalPageName. Could be empty.
    /// - Returns: Boolean value will return whether it was able to log the screenview event.
    func logScreenViewContextLoad(logicalPageName: Variant_NullType_String?, referrer: Variant_NullType_String?) throws -> Bool {
        let pageName: String? = { if case .second(let s) = logicalPageName { return s }; return nil }()
        let ref: String? = { if case .second(let s) = referrer { return s }; return nil }()
        let cllasss = pageName == nil ? "ReactNative" : "ReactNative_\(pageName!)"
        let result = ConnectCustomEvent().logScreenViewContext(pageName, withClass: cllasss, applicationContext: ConnectScreenViewType.load, referrer: ref)
        return result
    }
    
    /// Requests that the framework logs an application context for unload.
    /// - Parameters:
    ///   - logicalPageName: Page name or title e.g. "Login View Controller"; Must not be empty.
    ///   - referrer: Page name or title that loads logicalPageName. Could be empty.
    /// - Returns: Boolean value will return whether it was able to log the screenview event.
    func logScreenViewContextUnload(logicalPageName: Variant_NullType_String?, referrer: Variant_NullType_String?) throws -> Bool {
        let pageName: String? = { if case .second(let s) = logicalPageName { return s }; return nil }()
        let ref: String? = { if case .second(let s) = referrer { return s }; return nil }()
        let cllasss = pageName == nil ? "ReactNative" : "ReactNative_\(pageName!)"
        let result = ConnectCustomEvent().logScreenViewContext(pageName, withClass: cllasss, applicationContext: ConnectScreenViewType.unload, referrer: ref)
        return result
    }
    
    /// Requests that the framework logs the layout of the screen
    /// - Parameters:
    ///   - name: Custom name to associate with the viewcontroller.
    ///   - delay: number of seconds to wait before logging the view.
    /// - Returns: Boolean value will return whether it was able to log the screen layout event.
    func logScreenLayout(name: String, delay: Double) throws -> Bool {
        // RCTLogInfo(@"logScreenLayout Name:%@ with Delay:%@", name, delay);
        let uvc: UIViewController! = nil
        var result: Bool = false
        if (delay <= 0) {
            result = ConnectCustomEvent().logScreenLayout(with: uvc, andName: name)
        } else {
            result = ConnectCustomEvent().logScreenLayout(with: uvc, andDelay: delay, andName: name)
        }
        
        return result
    }
    
    /// Logs a dialog show event with the specified dialog information.
    /// - Parameters:
    ///   - dialogId: Unique identifier for the dialog.
    ///   - dialogTitle: The title of the dialog.
    ///   - dialogType: The type of dialog (alert, custom, modal).
    /// - Returns: Boolean value will return whether it was able to log the dialog show event.
    func logDialogShowEvent(dialogId: String, dialogTitle: String, dialogType: String) throws -> Bool {
        let values: [String: Any] = [
            "dialogId": dialogId,
            "dialogTitle": dialogTitle,
            "dialogType": dialogType,
            "eventType": "dialog_show",
            "timestamp": String(Int(Date().timeIntervalSince1970 * 1000))
        ]
        
        return true
    }
    
    /// Logs a dialog dismiss event with the specified dialog information.
    /// - Parameters:
    ///   - dialogId: Unique identifier for the dialog.
    ///   - dismissReason: The reason for dismissing the dialog.
    /// - Returns: Boolean value will return whether it was able to log the dialog dismiss event.
    func logDialogDismissEvent(dialogId: String, dismissReason: String) throws -> Bool {
        let values: [String: Any] = [
            "dialogId": dialogId,
            "dismissReason": dismissReason,
            "eventType": "dialog_dismiss",
            "timestamp": String(Int(Date().timeIntervalSince1970 * 1000))
        ]
        
        return true
    }
    
    /// Logs a dialog button click event with the specified button information.
    /// - Parameters:
    ///   - dialogId: Unique identifier for the dialog.
    ///   - buttonText: The text of the clicked button.
    ///   - buttonIndex: The index of the clicked button.
    /// - Returns: Boolean value will return whether it was able to log the dialog button click event.
    func logDialogButtonClickEvent(dialogId: String, buttonText: String, buttonIndex: Double) throws -> Bool {
        let values: [String: Any] = [
            "dialogId": dialogId,
            "buttonText": buttonText,
            "buttonIndex": String(Int(buttonIndex)),
            "eventType": "dialog_button_click",
            "timestamp": String(Int(Date().timeIntervalSince1970 * 1000))
        ]
        
        return true
    }
    
    /// Logs a custom dialog event with the specified event information.
    /// - Parameters:
    ///   - dialogId: Unique identifier for the dialog.
    ///   - eventName: The name of the custom event.
    ///   - values: A map of values associated with the event.
    /// - Returns: Boolean value will return whether it was able to log the dialog custom event.
    func logDialogCustomEvent(dialogId: String, eventName: String, values: Dictionary<String, Variant_Bool_String_Double>) throws -> Bool {
        var eventValues: [String: Any] = [
            "dialogId": dialogId,
            "customEventName": eventName,
            "eventType": "dialog_custom_event",
            "timestamp": String(Int(Date().timeIntervalSince1970 * 1000))
        ]
        
        // Add the custom values
        let convertedValues = convertToAnyDictionary(input: values)
        for (key, value) in convertedValues {
            eventValues[key] = value
        }
        
        return true
    }
    
    func testModuleName(moduleName: String) throws -> String {
        if (moduleName.caseInsensitiveCompare("Connect") == .orderedSame) {
            return "TLFCoreModule"
        }
        return moduleName
    }
  
    func convertToAnyDictionary(input: [String: Variant_Bool_String_Double]) -> [String: Any] {
        var result: [String: Any] = [:]

        for (key, value) in input {
            switch value {
            case .first(let boolValue):
                result[key] = boolValue
            case .second(let stringValue):
                result[key] = stringValue
            case .third(let doubleValue):
                result[key] = doubleValue
            }
        }

        return result
    }

    func convertVariantToAny(_ variant: Variant_Bool_String_Double) -> Any {
        switch variant {
        case .first(let boolValue):
            return boolValue
        case .second(let stringValue):
            return stringValue
        case .third(let doubleValue):
            return doubleValue
        }
    }
    
//    func convertKeyValueMapToDictionary(_ keyValueMap: KeyValueMap) -> [AnyHashable: Any] {
//        var dictionary: [AnyHashable: Any] = [:]
//        // Assuming KeyValueMap provides a method to access keys and values
//        for key in keyValueMap.keys {
//            if let value = keyValueMap[key] {
//                dictionary[key] = value
//            }
//        }
//        return dictionary
//    }
    
    func getLogLevelHelper(level: Double) throws -> kConnectMonitoringLevelType {
        let intValue: Int = Int(level)
        if intValue == 0 {
            return kConnectMonitoringLevelType.connectMonitoringLevelIgnore
        } else if intValue == 1 {
            return kConnectMonitoringLevelType.connectMonitoringLevelCellularAndWiFi
        } else {
            return kConnectMonitoringLevelType.connectMonitoringLevelWiFi
        }
    }
    
    func getLogLevel(level: Double) throws -> kConnectMonitoringLevelType {
        do {
            // Try to get the log level
            return try getLogLevelHelper(level: level)
        } catch {
            // Return a default value if an error occurs
            return kConnectMonitoringLevelType.connectMonitoringLevelIgnore
        }
    }
    
    deinit {
        // Perform any necessary cleanup here
        print("HybridAcousticConnectRN deinitialized")
    }
}
