//
//  Copyright (c) 2017-2021 Slang Labs Private Limited. All rights reserved.
//

import Foundation
import SlangFitnessAssistant

@objc(RNSlangFitnessAssistant)
class RNSlangFitnessAssistant: RCTEventEmitter {
    
    private var foodLoggingUserJourney:FoodLoggingUserJourney?
    private var sugarLoggingUserJourney:SugarLoggingUserJourney?
    private var navigationUserJourney:NavigationUserJourney?
    private var weightLoggingUserJourney:WeightLoggingUserJourney?

    private let configAssistantId = "assistantId"
    private let configAPIKey = "apiKey"
    private let configDefaultLocale = "defaultLocale"
    private let configRequestedLocales = "requestedLocales"
    private let configTriggerPosition = "triggerPosition"
    private let configCustomTriggerEnable = "enableCustomTrigger"
    private let configStrictModeEnable = "enableStrictMode"
    private let configOnboardingInfoTitle = "onBoardingInfoTitle"
    private let configOnboardingInfoDescription = "onBoardingInfoDescription"
    private let configDefaultSubdomain = "defaultSubdomain"
    private let configBrandColor = "brandColor"
    private let configDefaultUserJourney = "defaultUserJourney"
    private let configEnvironment = "environment"
    private let configOnboardingIdentifier = "onboardingIdentifier"
    private let configOnboardingInfoTitleSize = "onboardingInfoTitleSize"
    private let configOnboardingPrimaryTitle = "onboardingPrimaryTitle"
    private let configOnboardingPrimaryDescription = "onboardingPrimaryDescription"
    private let configAutomaticAppearenceLimit = "automaticAppearanceLimit"
    private let configCoachmarkVisibility = "coachmarkVisibility"
    private let configDisableOnboardingLocaleSelection = "disableOnboardingLocaleSelection"
    private let configDisableSurfaceLocaleSelection = "disableSurfaceLocaleSelection"
    private let configDisableCoachmark = "disableCoachMark"
    private let configInfoTextColor = "infoTextColor"
    private let configAccentColor = "accentColor"
    
    override init() {
        super.init()
    }
    
    public enum EventName: String, CaseIterable {
        case onAssistantInitSuccess
        case onAssistantInitFailure
        case onAssistantError
        case onAssistantInvoked
        case onAssistantClosed
        case onAssistantLocaleChanged
        case onUnrecognisedUtterance
        case onUtteranceDetected
        case onOnboardingSuccess
        case onOnboardingFailure
        case onMicPermissionDenied
        case onMicPermissionGranted

        case onFoodLog
        case onSugarLog
        case onWeightLog
        case onNavigation
    }
    
    override func supportedEvents() -> [String]! {
        var allEventNames: [String] = []
        for event in EventName.allCases{
            allEventNames.append(event.rawValue)
        }
        return allEventNames
    }
    
    @objc func initialize(_ initializeData: Dictionary<String, Any>) {
        
        var locales = Set<Locale>()
        locales.insert(Locale(identifier: "en-IN"))
        locales.insert(Locale(identifier: "hi-IN"))
        
        configOptions = initializeData
        
        DispatchQueue.main.async {
            let configBuilder = AssistantConfiguration.Builder()
                .setAssistantId(getStringConfig(self.configAssistantId))
                .setAPIKey(getStringConfig(self.configAPIKey))
                .setDefaultLocale(getDefaultLocale(getStringConfig(self.configDefaultLocale)))
                .setRequestedLocales(getRequestedLocales(getStringListConfig(self.configRequestedLocales)))
                .setUIPosition(getAssistantUIConfig(self.configTriggerPosition))
                .setDefaultSubDomain(with: getAssistantDefaultSubdomainConfig(self.configDefaultSubdomain))
                .enableCustomTrigger(getBooleanConfig(self.configCustomTriggerEnable))
                .enableStrictIntentMode(getBooleanConfig(self.configStrictModeEnable))
                .setAutomaticAppearanceLimit(getIntConfig(self.configAutomaticAppearenceLimit))
                .setCoachmarkVisibility(getBooleanConfig(self.configCoachmarkVisibility, defaultValue: true))
                .disableOnboardingLocaleSelection(getBooleanConfig(self.configDisableOnboardingLocaleSelection))
                .disableSurfaceLocaleSelection(getBooleanConfig(self.configDisableSurfaceLocaleSelection))
                .setCoachmarkVisibility(!getBooleanConfig(self.configDisableCoachmark))
                .setEnvironment(with: getEnvironmentFromString(self.configEnvironment))

            if let onboardingInfoTitle = getLocaleStringMapConfig(self.configOnboardingInfoTitle) {
                _ = configBuilder.setOnboardingInfoTitle(onboardingInfoTitle)
            }

            if let onboardingInfoDescription = getLocaleStringMapConfig(self.configOnboardingInfoDescription) {
                _ = configBuilder.setOnboardingInfoDescription(onboardingInfoDescription)
            }

            if let onboardingPrimaryTitle = getLocaleStringMapConfig(self.configOnboardingPrimaryTitle) {
                _ = configBuilder.setOnboardingPrimaryTitle(onboardingPrimaryTitle)
            }

            if let onboardingPrimaryDescription = getLocaleStringMapConfig(self.configOnboardingPrimaryDescription) {
                _ = configBuilder.setOnboardingPrimaryDescription(onboardingPrimaryDescription)
            }

            if let infoTextColor = convertHexToColor(hex: getStringConfig(self.configInfoTextColor)) {
                _ = configBuilder.setInfoTextColor(infoTextColor)
            }

            if let accentColor = convertHexToColor(hex: getStringConfig(self.configAccentColor)) {
                _ = configBuilder.setAccentColor(accentColor)
            }

            if let brandColor = convertHexToColor(hex: getStringConfig(self.configBrandColor)) {
                _ = configBuilder.setBrandColor(brandColor)
            }
            
            if let defaultUserJourney = getAssistantDefaultUserJourneyConfig(self.configDefaultUserJourney) {
                _ = configBuilder.setDefaultUserJourney(with: defaultUserJourney)
            }

            SlangFitnessAssistant.initialize(with: configBuilder.build())
        }
        
    }
    
    @objc func setLifecycleObserver() {
        DispatchQueue.main.async {
            SlangFitnessAssistant.setLifecycleObserver(with: self)
        }
    }
    
    @objc func setAction() {
        DispatchQueue.main.async {
            SlangFitnessAssistant.setAction(with: self)
        }
    }
    
    @objc func startConversation(_ userJourney:String) {
        DispatchQueue.main.async {
            guard let viewController
                    = UIApplication.shared.keyWindow?.rootViewController else {return}
            SlangFitnessAssistant
                .startConversation(
                    getAssistantDefaultUserJourney(userJourney),
                    viewController: viewController
                )
        }
    }
    
    @objc func cancelSession() {
        DispatchQueue.main.async {
            SlangFitnessAssistant.cancelSession()
        }
    }
    
    @objc func hideTrigger() {
        DispatchQueue.main.async {
            SlangFitnessAssistant.getUI().hideTrigger()
        }
    }
    
    @objc func showTrigger() {
       DispatchQueue.main.async {
            guard let viewController
                    = UIApplication.shared.keyWindow?.rootViewController else {return}
            SlangFitnessAssistant.getUI().showTrigger(in: viewController)
        }
    }
    
    @objc func prepareActiveFoodLoggingUserJourney() {
       DispatchQueue.main.async {
           self.foodLoggingUserJourney = SlangFitnessAssistant.getActiveFoodLoggingUserJourney()
        }
    }
    @objc func prepareActiveSugarLoggingUserJourney() {
        //TODO : Fix once prepareActiveSugarLoggingUserJourney is completed.
    }
    
    @objc func setUserProperties(_ userId:String, userProperties: Dictionary<String, Any>) {
        //TODO : Fix once setUserProperties is completed.
        DispatchQueue.main.async {
        }
    }
    
    @objc func trackCustomEvent(_ eventName:String, eventMetadata: Dictionary<String, Any>) {
        DispatchQueue.main.async {
            do {
                try SlangFitnessAssistant
                    .trackCustomEvent(
                        eventName,
                        eventMedata: convertToStringMap(eventMetadata)
                    )
            }
            catch {
                self.sendAssistantError(error)
            }
        }
    }
    
    @objc func notifyFoodLoggingActionCompleted(
        _ foodLoggingAppState:String,
        foodLoggingAppStateCondition: String
    ) {
        print("notifyFoodLoggingActionCompleted: " + foodLoggingAppState)
        guard let foodLoggingUserJourney = foodLoggingUserJourney else {
            return
        }
        do {
            if foodLoggingAppState.lowercased() == "unsupported" {
                try foodLoggingUserJourney.notifyAppState(with: .unsupported)
                return
            }
            else if foodLoggingAppState.lowercased() == "waiting" {
                return
            }
            switch foodLoggingAppStateCondition {
            case "FAILURE" :
                foodLoggingUserJourney.setFailure()
                try foodLoggingUserJourney.notifyAppState(with: .foodLog)
            case "MEAL_TYPE_NOT_SPECIFIED" :
                foodLoggingUserJourney.setMealTypeRequired()
                try foodLoggingUserJourney.notifyAppState(with: .foodLog)
            case "DATE_NOT_SPECIFIED" :
                foodLoggingUserJourney.setDateRequired()
                try foodLoggingUserJourney.notifyAppState(with: .foodLog)
            case "INVALID_DATE_SPECIFIED" :
                foodLoggingUserJourney.setDateInvalid()
                try foodLoggingUserJourney.notifyAppState(with: .foodLog)
            case "TIME_NOT_SPECIFIED" :
                foodLoggingUserJourney.setTimeRequired()
                try foodLoggingUserJourney.notifyAppState(with: .foodLog)
            case "FOODS_NOT_SPECIFIED" :
                foodLoggingUserJourney.setFoodsRequired()
                try foodLoggingUserJourney.notifyAppState(with: .foodLog)
            case "FOODS_LIST_INCOMPLETE" :
                foodLoggingUserJourney.setFoodsIncomplete()
                try foodLoggingUserJourney.notifyAppState(with: .foodLog)
            case "FOODS_FOOD_NAME_DISAMBIGUATE" :
                foodLoggingUserJourney.setDisambiguateFoodName()
                try foodLoggingUserJourney.notifyAppState(with: .foodLog)
            case "CONFIRMATION_BAILOUT" :
                foodLoggingUserJourney.setConfirmationBailout()
                try foodLoggingUserJourney.notifyAppState(with: .foodLog)
            case "CONFIRMATION_REQUIRED" :
                foodLoggingUserJourney.setConfirmationRequired()
                try foodLoggingUserJourney.notifyAppState(with: .foodLog)
            case "VERIFICATION_REQUIRED" :
                foodLoggingUserJourney.setVerificationRequired()
                try foodLoggingUserJourney.notifyAppState(with: .foodLog)
            default:
                foodLoggingUserJourney.setSuccess()
                try foodLoggingUserJourney.notifyAppState(with: .foodLog)
            }
        }
        catch {
            sendAssistantError(error)
        }
    }
    
    @objc func notifySugarLoggingActionCompleted(
        _ sugarLoggingAppState:String,
        sugarLoggingAppStateCondition: String
    ) {
        print("notifySugarLoggingActionCompleted: " + sugarLoggingAppState)
        guard let sugarLoggingUserJourney = sugarLoggingUserJourney else {
            return
        }
        do {
            if sugarLoggingAppState.lowercased() == "unsupported" {
                try sugarLoggingUserJourney.notifyAppState(with: .unsupported)
                return
            }
            else if sugarLoggingAppState.lowercased() == "waiting" {
                return
            }
            switch sugarLoggingAppStateCondition {
            case "FAILURE" :
                sugarLoggingUserJourney.setFailure()
                try sugarLoggingUserJourney.notifyAppState(with: .sugarLog)
            case "SUGAR_LEVEL_REQUIRED" :
                sugarLoggingUserJourney.setSugarLevelRequired()
                try sugarLoggingUserJourney.notifyAppState(with: .sugarLog)
            case "DATE_REQUIRED" :
                sugarLoggingUserJourney.setDateRequired()
                try sugarLoggingUserJourney.notifyAppState(with: .sugarLog)
            case "INVALID_DATE_SPECIFIED" :
                sugarLoggingUserJourney.setDateInvalid()
                try sugarLoggingUserJourney.notifyAppState(with: .sugarLog)
            case "TIME_REQUIRED" :
                sugarLoggingUserJourney.setTimeRequired()
                try sugarLoggingUserJourney.notifyAppState(with: .sugarLog)
            case "CONFIRMATION_BAILOUT" :
                sugarLoggingUserJourney.setConfirmationBailout()
                try sugarLoggingUserJourney.notifyAppState(with: .sugarLog)
            case "CONFIRMATION_REQUIRED" :
                sugarLoggingUserJourney.setConfirmationRequired()
                try sugarLoggingUserJourney.notifyAppState(with: .sugarLog)
            case "VERIFICATION_REQUIRED" :
                sugarLoggingUserJourney.setVerificationRequired()
                try sugarLoggingUserJourney.notifyAppState(with: .sugarLog)
            default:
                sugarLoggingUserJourney.setSuccess()
                try sugarLoggingUserJourney.notifyAppState(with: .sugarLog)
            }
        }
        catch {
            sendAssistantError(error)
        }
    }
    
    @objc func notifyWeightLoggingActionCompleted(
        _ weightLoggingAppState:String,
        weightLoggingAppStateCondition: String
    ) {
        print("notifyWeightLoggingActionCompleted: " + weightLoggingAppState)
        guard let weightLoggingUserJourney = weightLoggingUserJourney else {
            return
        }
        do {
            if weightLoggingAppState.lowercased() == "unsupported" {
                try weightLoggingUserJourney.notifyAppState(with: .unsupported)
                return
            }
            else if weightLoggingAppState.lowercased() == "waiting" {
                return
            }
            switch weightLoggingAppStateCondition {
            case "FAILURE" :
                weightLoggingUserJourney.setFailure()
                try weightLoggingUserJourney.notifyAppState(with: .weightLog)
            case "WEIGHT_REQUIRED" :
                weightLoggingUserJourney.setWeightRequired()
                try weightLoggingUserJourney.notifyAppState(with: .weightLog)
            case "DATE_REQUIRED" :
                weightLoggingUserJourney.setDateRequired()
                try weightLoggingUserJourney.notifyAppState(with: .weightLog)
            case "INVALID_DATE_SPECIFIED" :
                weightLoggingUserJourney.setDateInvalid()
                try weightLoggingUserJourney.notifyAppState(with: .weightLog)
            case "TIME_REQUIRED" :
                weightLoggingUserJourney.setTimeRequired()
                try weightLoggingUserJourney.notifyAppState(with: .weightLog)
            case "CONFIRMATION_BAILOUT" :
                weightLoggingUserJourney.setConfirmationBailout()
                try weightLoggingUserJourney.notifyAppState(with: .weightLog)
            case "CONFIRMATION_REQUIRED" :
                weightLoggingUserJourney.setConfirmationRequired()
                try weightLoggingUserJourney.notifyAppState(with: .weightLog)
            case "VERIFICATION_REQUIRED" :
                weightLoggingUserJourney.setVerificationRequired()
                try weightLoggingUserJourney.notifyAppState(with: .weightLog)
            default:
                weightLoggingUserJourney.setSuccess()
                try weightLoggingUserJourney.notifyAppState(with: .weightLog)
            }
        }
        catch {
            sendAssistantError(error)
        }
    }
    
    @objc func setBackgroundColorGradient(_ colors: Array<Any>) {
        //TODO : Depreacated, move to assitant config
        DispatchQueue.main.async {
            var params = Dictionary<String, String>()
            params["type"] = "Useage of deprecated method setBackgroundColorGradient"
            self.sendEvent(.onAssistantError, eventInfo: params)
        }
    }
    
    @objc func setWaveColorGradient(_ colors: Array<Any>) {
        DispatchQueue.main.async {
            var colorValues = [UIColor]()
            for colorValue in colors {
                if let colorString = colorValue as? String,
                    let colorValue = convertHexToColor(hex: colorString) {
                    colorValues.append(colorValue)
                }
            }
            if colorValues.count > 0 {
                SlangFitnessAssistant.getUI().setWaveColorGradient(with: colorValues)
            }
        }
    }
    
    @objc func setTextColor(_ color: String) {
        //TODO : Depreacated, move to assitant config
        DispatchQueue.main.async {
            var params = Dictionary<String, String>()
            params["type"] = "Useage of deprecated method setTextColor"
            self.sendEvent(.onAssistantError, eventInfo: params)
        }
    }
    
    @objc func setSecondaryTextColor(_ color: String) {
        //TODO : Depreacated, move to assitant config
        DispatchQueue.main.async {
            var params = Dictionary<String, String>()
            params["type"] = "Useage of deprecated method setSecondaryTextColor"
            self.sendEvent(.onAssistantError, eventInfo: params)
        }
    }
    
    @objc func setTextHighlightColor(_ color: String) {
        //TODO : Depreacated, move to assitant config
        DispatchQueue.main.async {
            var params = Dictionary<String, String>()
            params["type"] = "Useage of deprecated method setTextHighlightColor"
            self.sendEvent(.onAssistantError, eventInfo: params)
        }
    }
    
    @objc func setSettingsButtonBackgroundColor(_ color: String) {
        DispatchQueue.main.async {
            if let colorValue = convertHexToColor(hex: color) {
                SlangFitnessAssistant.getUI().setSettingsButtonBackgroundColor(with: colorValue)
            }
        }
    }
    
    @objc func setControlButtonBackgroundColor(_ color: String) {
        DispatchQueue.main.async {
            if let colorValue = convertHexToColor(hex: color) {
                SlangFitnessAssistant.getUI().setControlButtonBackgroundColor(with: colorValue)
            }
        }
    }
    
    @objc func setTheme(_ theme: String) {
        DispatchQueue.main.async {
            var uiMode = AssistantUI.UITheme.light
            if theme.lowercased() == "dark" {
                uiMode = .dark
            }
            SlangFitnessAssistant.getUI().setUITheme(with: uiMode)
        }
    }
    
    @objc func setMode(_ mode: String) {
        DispatchQueue.main.async {
            var uiMode = AssistantUI.UITheme.light
            if mode.lowercased() == "dark" {
                uiMode = .dark
            }
            SlangFitnessAssistant.getUI().setUITheme(with: uiMode)
        }
    }
    
    @objc func disableLocaleSelection(_ disable: Bool) {
        //TODO : Depreacated, move to assitant config
        DispatchQueue.main.async {
            var params = Dictionary<String, String>()
            params["type"] = "Useage of deprecated method disableLocaleSelection"
            self.sendEvent(.onAssistantError, eventInfo: params)
        }
    }
    
    @objc func setWaveVelocity(_ velocity: Float) {
        //TODO : Fix once setWaveVelocity is completed.
        DispatchQueue.main.async {
        }
    }
    
    @objc func setTriggerSize(_ width: NSNumber, height: NSNumber) {
        //TODO : Depreacated, move to assitant config
        DispatchQueue.main.async {
            var params = Dictionary<String, String>()
            params["type"] = "Useage of deprecated method setTriggerSize"
            self.sendEvent(.onAssistantError, eventInfo: params)
        }
    }
    
    
    @objc func setAppDefaultUserJourney(_ userJourney: String) {
        DispatchQueue.main.async {
            let userJourney = getAssistantDefaultUserJourney(userJourney)
            SlangFitnessAssistant.setAppDefaultUserJourney(with: userJourney)
        }
    }
    
    @objc func setAppDefaultSubdomain(_ subdomain: String) {
        DispatchQueue.main.async {
            let subdomain = getAssistantDefaultSubdomainConfig(subdomain)
            SlangFitnessAssistant.setAppDefaultSubDomain(with: subdomain)
        }
    }
    
    @objc func setTriggerImageResource(_ urlString: String) {
        //TODO : Fix once setTriggerImageResource is completed.
        DispatchQueue.main.async {
        }
    }
    
    @objc func setDisambiguationIndexInContext(_ disambiguationIndex: NSNumber) {
        DispatchQueue.main.async {
            FoodLoggingUserJourney.getContext().setDisambiguationIndex(disambiguationIndex.intValue)
        }
    }
    
    @objc func setFoodInfoAtIndexInContext(_ disambiguationIndex: NSNumber, foodInfo: Dictionary<String, Any>) {
        DispatchQueue.main.async {
            if let foodInfo = mapFoodInfo(foodInfo) {
                FoodLoggingUserJourney.getContext()
                    .setFoodInfoAtIndex(
                        disambiguationIndex.intValue,
                        food: foodInfo
                    )
            }
        }
    }
    
    private func sendAssistantError(_ error: Error) {
        var params = Dictionary<String, String>()
        params["type"] = error.localizedDescription
        sendEvent(.onAssistantError, eventInfo: params)
    }
    
    private func sendEvent(_ eventName:EventName, eventInfo: [String:Any]) {
        self.sendEvent(withName: eventName.rawValue, body: eventInfo)
    }
    
}

extension RNSlangFitnessAssistant: LifecycleObserver {
    
    func onAssistantInitSuccess() {
        let params = [String:Any]()
        sendEvent(.onAssistantInitSuccess, eventInfo: params)
    }
    
    func onAssistantInitFailure(with description: String) {
        var params = [String:Any]()
        params["type"] = description
        sendEvent(.onAssistantInitFailure, eventInfo: params)
    }
    
    func onAssistantInvoked() {
        let params = [String:Any]()
        sendEvent(.onAssistantInvoked, eventInfo: params)
    }
    
    func onAssistantClosed(with cancelled: Bool) {
        var params = [String:Any]()
        params["isCancelled"] = cancelled
        sendEvent(.onAssistantClosed, eventInfo: params)
    }
    
    func onAssistantLocaleChanged(for locale: Locale) {
        var params = [String:Any]()
        params["country"] = locale.regionCode
        params["language"] = locale.languageCode
        sendEvent(.onAssistantLocaleChanged, eventInfo: params)
    }
    
    func onUnrecognisedUtterance(for utterance: String?) {
        var params = [String:Any]()
        params["utterance"] = utterance
        sendEvent(.onUnrecognisedUtterance, eventInfo: params)
    }
    
    func onUtteranceDetected(for utterance: String?) {
        var params = [String:Any]()
        params["utterance"] = utterance
        sendEvent(.onUtteranceDetected, eventInfo: params)
    }
    
    func onOnboardingSuccess() {
        let params = [String:Any]()
        sendEvent(.onOnboardingSuccess, eventInfo: params)
    }
    
    func onOnboardingFailure() {
        let params = [String:Any]()
        sendEvent(.onOnboardingFailure, eventInfo: params)
    }
    
}

extension RNSlangFitnessAssistant: SlangFitnessAssistantAction {
    
    func onFoodLog(with foodInfo: FoodInfo, foodLoggingUserJourney: FoodLoggingUserJourney) -> FoodLoggingUserJourney.AppState {
        self.foodLoggingUserJourney = foodLoggingUserJourney
        let foodInfoMap = mapFoodInfo(foodInfo)
        sendEvent(.onFoodLog, eventInfo: foodInfoMap)
        
        return FoodLoggingUserJourney.AppState.waiting
    }
    
    func onNavigation(with navigationInfo: NavigationInfo, navigationUserJourney: NavigationUserJourney) -> NavigationUserJourney.AppState {
        self.navigationUserJourney = navigationUserJourney
        return NavigationUserJourney.AppState.unsupported
    }
    
    func onSugarLog(with sugarInfo: SugarInfo, sugarLoggingUserJourney: SugarLoggingUserJourney) -> SugarLoggingUserJourney.AppState {
        self.sugarLoggingUserJourney = sugarLoggingUserJourney
        let sugarInfoMap = mapSugarInfo(sugarInfo)
        sendEvent(.onSugarLog, eventInfo: sugarInfoMap)
        
        return SugarLoggingUserJourney.AppState.waiting
    }
    
    func onWeightLog(with weightInfo: WeightInfo, weightLoggingUserJourney: WeightLoggingUserJourney) -> WeightLoggingUserJourney.AppState {
        self.weightLoggingUserJourney = weightLoggingUserJourney
        let weightInfoMap = mapWeightInfo(weightInfo)
        sendEvent(.onWeightLog, eventInfo: weightInfoMap)
        
        return WeightLoggingUserJourney.AppState.waiting
    }
    
    func onAssistantError(with error: AssistantError) {
        var params = [String:Any]()
        params["type"] = error.localizedDescription
        sendEvent(.onAssistantError, eventInfo: params)
    }
    
}
