// 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 EOCore
import Tealeaf
import NitroModules

class HybridAcousticConnectRN: HybridAcousticConnectRNSpec {
  
    // Constructor
    override init() {
        // Call the superclass initializer
        super.init()
      
        // init library from ui thread
        // This is important to ensure that the library is initialized on the main thread
        // as it may involve UI components or other main-thread specific tasks.
        DispatchQueue.main.async { [weak self] in
        self?.load()
    }

        // Perform additional setup if needed
        print("HybridAcousticConnectRN initialized")
    }
    
    func load() {
        DispatchQueue.main.async {
            self.loadConnectConfig()
            EOApplicationHelper().setConfigItem(kConfigurableItemSetGestureDetector, value: "false", forModuleName: kTLFCoreModule)
            EOApplicationHelper().setConfigItem(kConfigurableItemLogViewLayoutOnScreenTransition, value: "false", forModuleName: kTLFCoreModule)
            ConnectApplicationHelper().enableFramework()
            ConnectApplicationHelper().isReactNative(true)
        }
    }
    
    /// <#Description#>
    /// - Returns: <#description#>
    func loadConnectConfig() {
        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"
        ]
        
        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] }
            
        if let jsonData = jsonData,
           let connectData = jsonData["Connect"] as? [String: Any] {
            for (key, value) in connectData {
                if tealeafKeys.contains(key) {
                    EOApplicationHelper.sharedInstance().setConfigItem(key, value: value, forModuleName: kTLFCoreModule)
                } else if eocoreKeys.contains(key) {
                    EOApplicationHelper.sharedInstance().setConfigItem(key, value: value, forModuleName: kEOCoreModule)
                } else if key == "layoutConfig",
                          let layoutConfig = value as? [String: Any] {
                    if let autoLayout = layoutConfig["AutoLayout"] {
                        EOApplicationHelper.sharedInstance().setConfigItem("AutoLayout", value: autoLayout, forModuleName: kTLFCoreModule)
                    }
                    if let appendMapIds = layoutConfig["AppendMapIds"] {
                        EOApplicationHelper.sharedInstance().setConfigItem("AppendMapIds", value: appendMapIds, forModuleName: kTLFCoreModule)
                    }
                }
            }
        }
    }
    
    /// 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 {
        let result = EOApplicationHelper().setConfigItem(key, value: value, forModuleName: moduleName)
        return result
    }
    
    /// 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 {
        let result = EOApplicationHelper().setConfigItem(key, value: value, forModuleName: moduleName)
        return result
    }
    
    /// 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 {
        let result = EOApplicationHelper().setConfigItem(key, value: value, forModuleName: moduleName)
        return result
    }
  
  
    /// 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_String_Double_Bool, moduleName: String) throws -> Bool {
      var result = EOApplicationHelper().setConfigItem(key, value: convertVariantToAny(value), forModuleName: moduleName)
      return true
    }
    
    /// 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 {
      var result = EOApplicationHelper().getBOOLconfigItem(forKey: key, withDefault: theDefault, forModuleName: moduleName)
      return result
    }
    
    /// 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 -> String? {
        var result = EOApplicationHelper().getStringItem(forKey: key, withDefault: theDefault, forModuleName: moduleName)
        return result
    }
    
    /// 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 {
        let result = EOApplicationHelper().getNumberItem(forKey: key, withDefault: theDefault as NSNumber, forModuleName: moduleName) as! Double
        return result
    }
    
    /// 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_String_Double_Bool>, 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_String_Double_Bool>, 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
      DispatchQueue.main.async {
        let view:UIView? = nil
        let result = ConnectCustomEvent().logClick(view, controlId: controlId, data: nil)
      }
      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: String?) throws -> Bool {
        let view:UIView? = nil
        var data: [String: Any] = [:]
        
        if let unwrappedText = text {
            data["text"] = unwrappedText
        }
        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().resume(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: String?, referrer: String?) throws -> Bool {
        let cllasss = logicalPageName == nil ? "ReactNative" : "ReactNative_\(logicalPageName)"
        let result = ConnectCustomEvent().logScreenViewContext(logicalPageName, withClass: cllasss, applicationContext: ConnectScreenViewType.load, referrer: referrer)
        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: String?, referrer: String?) throws -> Bool {
        let cllasss = logicalPageName == nil ? "ReactNative" : "ReactNative_\(logicalPageName)"
        let result = ConnectCustomEvent().logScreenViewContext(logicalPageName, withClass: cllasss, applicationContext: ConnectScreenViewType.unload, referrer: referrer)
        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
    }
    
    func testModuleName(moduleName: String) throws -> String {
        if (moduleName.caseInsensitiveCompare("Connect") == .orderedSame) {
            return "TLFCoreModule"
        }
        return moduleName
    }
  
    func convertToAnyDictionary(input: [String: Variant_String_Double_Bool]) -> [String: Any] {
        var result: [String: Any] = [:]
        
        for (key, value) in input {
            switch value {
            case .first(let stringValue):
                result[key] = stringValue
            case .second(let doubleValue):
                result[key] = doubleValue
            case .third(let boolValue):
                result[key] = boolValue
            }
        }
        
        return result
    }
  
    func convertVariantToAny(_ variant: Variant_String_Double_Bool) -> Any {
        switch variant {
        case .first(let stringValue):
            return stringValue
        case .second(let doubleValue):
            return doubleValue
        case .third(let boolValue):
            return boolValue
        }
    }
    
//    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")
    }
}
