//
//  RNConvivaAppAnalytics.m
//
//  Copyright (c) 2020-2023 Snowplow Analytics Ltd. All rights reserved.
//
//  This program is licensed to you under the Apache License Version 2.0,
//  and you may not use this file except in compliance with the Apache License
//  Version 2.0. You may obtain a copy of the Apache License Version 2.0 at
//  http://www.apache.org/licenses/LICENSE-2.0.
//
//  Unless required by applicable law or agreed to in writing,
//  software distributed under the Apache License Version 2.0 is distributed on
//  an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
//  express or implied. See the Apache License Version 2.0 for the specific
//  language governing permissions and limitations there under.
//
//  Copyright: Copyright (c) 2023 Snowplow Analytics Ltd
//  License: Apache License Version 2.0
//

#import "RNConvivaAppAnalytics.h"
#import "RNConfigUtils.h"
#import "RNUtilities.h"
#import "NSDictionary+RNCAT_TypeMethods.h"

#import <ConvivaAppAnalytics/CATAppAnalytics.h>
#import <ConvivaAppAnalytics/CATNetworkConfiguration.h>
#import <ConvivaAppAnalytics/CATNetworkConnection.h>
#import <ConvivaAppAnalytics/CATTrackerConfiguration.h>
#import <ConvivaAppAnalytics/CATSessionConfiguration.h>
#import <ConvivaAppAnalytics/CATEmitterConfiguration.h>
#import <ConvivaAppAnalytics/CATSubjectConfiguration.h>
#import <ConvivaAppAnalytics/CATGDPRConfiguration.h>
#import <ConvivaAppAnalytics/CATGlobalContextsConfiguration.h>
#if !TARGET_OS_TV
#import <ConvivaAppAnalytics/CATClientIdSyncConfiguration.h>
#endif
#import <ConvivaAppAnalytics/CATGlobalContext.h>
#import <ConvivaAppAnalytics/CATSelfDescribingJson.h>
#import <ConvivaAppAnalytics/CATSelfDescribing.h>
#import <ConvivaAppAnalytics/CATScreenView.h>
#import <ConvivaAppAnalytics/CATPageView.h>
#import <ConvivaAppAnalytics/CATTiming.h>
#import <ConvivaAppAnalytics/CATConsentGranted.h>
#import <ConvivaAppAnalytics/CATConsentWithdrawn.h>
#import <ConvivaAppAnalytics/CATStructured.h>
#import <ConvivaAppAnalytics/CATEcommerceItem.h>
#import <ConvivaAppAnalytics/CATEcommerce.h>
#import <ConvivaAppAnalytics/CATDeepLinkReceived.h>
#import <ConvivaAppAnalytics/CATMessageNotification.h>
#import <ConvivaAppAnalytics/CATRevenueEvent.h>
#import <ConvivaAppAnalytics/CATRevenueEventItem.h>
#import <Foundation/NSObject.h>

@implementation RNConvivaTracker

RCT_EXPORT_MODULE()

RCT_EXPORT_METHOD(createTracker:
                  (NSDictionary *)argmap
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {

    NSString *trackerNs = @"CAT";[argmap objectForKey:@"namespace"];
    NSString *appName = [argmap objectForKey:@"appName"];
    NSString *customerKey = [argmap objectForKey:@"customerKey"];
    NSDictionary *networkConfig =[argmap objectForKey:@"networkConfig"];

    CATNetworkConfiguration *networkConfiguration = nil;
    if(nil != networkConfig){
        
        // NetworkConfiguration
        NSString *method = [networkConfig rncat_stringForKey:@"method" defaultValue:nil];
        CATHttpMethod httpMethod = CATHttpMethodPost;
        
        if(0 < [method length]){
            httpMethod = [@"get" isEqualToString:method] ? CATHttpMethodGet : CATHttpMethodPost;
        }
        
        NSString *endpoint = networkConfig[@"endpoint"];
        
        if(0 < [endpoint length]) {
            networkConfiguration = [[CATNetworkConfiguration alloc] initWithEndpoint:networkConfig[@"endpoint"] method:httpMethod];
        }
        
        NSString *customPostPath = [networkConfig rncat_stringForKey:@"customPostPath" defaultValue:nil];
        if (0 < [customPostPath length]) {
            networkConfiguration.customPostPath = customPostPath;
        }
        
        NSObject *requestHeaders = [networkConfig objectForKey:@"requestHeaders"];
        if (requestHeaders != nil && [requestHeaders isKindOfClass:NSDictionary.class]) {
            networkConfiguration.requestHeaders = (NSDictionary *)requestHeaders;
        }
    }

    // Configurations
    NSMutableArray *controllers = [NSMutableArray array];

    // TrackerConfiguration
    NSObject *trackerArg = [argmap objectForKey:@"trackerConfig"];
    if (trackerArg != nil && [trackerArg isKindOfClass:NSDictionary.class]) {
        CATTrackerConfiguration *trackerConfiguration = [RNConfigUtils mkTrackerConfig:(NSDictionary *)trackerArg];
        [controllers addObject:trackerConfiguration];
    }
    else{
        CATTrackerConfiguration *trackerConfiguration = [RNConfigUtils mkDefaultTrackerConfig];
        [controllers addObject:trackerConfiguration];
    }

    // SessionConfiguration
    NSObject *sessionArg = [argmap objectForKey:@"sessionConfig"];
    if (sessionArg != nil && [sessionArg isKindOfClass:NSDictionary.class]) {
        CATSessionConfiguration *sessionConfiguration = [RNConfigUtils mkSessionConfig:(NSDictionary *)sessionArg];
        [controllers addObject:sessionConfiguration];
    }

    // EmitterConfiguration
    NSObject *emitterArg = [argmap objectForKey:@"emitterConfig"];
    if (emitterArg !=nil && [emitterArg isKindOfClass:NSDictionary.class]) {
        CATEmitterConfiguration *emitterConfiguration = [RNConfigUtils mkEmitterConfig:(NSDictionary *)emitterArg];
        [controllers addObject:emitterConfiguration];
    }

    // SubjectConfiguration
    NSObject *subjectArg = [argmap objectForKey:@"subjectConfig"];
    if (subjectArg != nil && [subjectArg isKindOfClass:NSDictionary.class]) {
        CATSubjectConfiguration *subjectConfiguration = [RNConfigUtils mkSubjectConfig:(NSDictionary *)subjectArg];
        [controllers addObject:subjectConfiguration];
    }

    // GdprConfiguration
    NSObject *gdprArg = [argmap objectForKey:@"gdprConfig"];
    if (gdprArg != nil && [gdprArg isKindOfClass:NSDictionary.class]) {
        CATGDPRConfiguration *gdprConfiguration = [RNConfigUtils mkGdprConfig:(NSDictionary *)gdprArg];
        [controllers addObject:gdprConfiguration];
    }

    // GConfiguration
    NSObject *gcArg = [argmap objectForKey:@"gcConfig"];
    if (gcArg != nil && [gcArg isKindOfClass:NSArray.class]) {
        CATGlobalContextsConfiguration *gcConfiguration = [RNConfigUtils mkGCConfig:(NSArray *)gcArg];
        [controllers addObject:gcConfiguration];
    }

    // ClidSyncConfiguration
    // The factory is exception-safe and always returns a non-nil object, but
    // we still gate on dictionary type here so we only enqueue a controller
    // when JS actually supplied `clidSyncConfig` — leaving the SDK on its
    // own defaults otherwise.
    // tvOS: the underlying SDK gates CATClientIdSyncConfiguration to
    // SNOWPLOW_TARGET_IOS (i.e. the type does not exist on tvOS), so the
    // entire block is compiled out there to keep the wrapper tvOS-buildable.
    // JS may still send `clidSyncConfig` on tvOS — it is silently dropped
    // here, which mirrors the SDK contract that the feature is iOS-only.
#if !TARGET_OS_TV
    NSObject *clidSyncArg = [argmap objectForKey:@"clidSyncConfig"];
    if (clidSyncArg != nil && [clidSyncArg isKindOfClass:NSDictionary.class]) {
        @try {
            CATClientIdSyncConfiguration *clidSyncConfiguration =
                [RNConfigUtils mkClidSyncConfig:(NSDictionary *)clidSyncArg];
            if (clidSyncConfiguration != nil) {
                [controllers addObject:clidSyncConfiguration];
            }
        } @catch (NSException *exception) {
            // Belt-and-braces: even though mkClidSyncConfig: catches its own
            // parsing exceptions, we wrap the call site too so any unexpected
            // failure here never aborts createTracker:. The tracker will still
            // be created using whatever configurations are already queued.
            NSLog(@"[RNConvivaAppAnalytics] createTracker: ignoring exception while building clidSyncConfig: %@", exception);
        }
    }
#endif

#if !TARGET_OS_TV
    // SessionReplayConfiguration - screen capture is not available on tvOS;
    // guard mirrors the clidSyncConfig pattern above.
    NSObject *replayArg = [argmap objectForKey:@"sessionReplayConfig"];
    if (replayArg != nil && [replayArg isKindOfClass:NSDictionary.class]) {
        @try {
            CATSessionReplayConfiguration *replayConfiguration = [RNConfigUtils mkSessionReplayConfig:(NSDictionary *)replayArg];
            if (replayConfiguration != nil) {
                [controllers addObject:replayConfiguration];
            }
        } @catch (NSException *exception) {
            // mkSessionReplayConfig: does not catch exceptions internally; this
            // try/catch ensures any unexpected failure never aborts createTracker:.
            // The tracker will still be created using whatever configurations
            // are already queued.
            NSLog(@"[RNConvivaAppAnalytics] createTracker: ignoring exception while building sessionReplayConfig: %@", exception);
        }
    }
#endif

    id<CATTrackerController> tracker = nil;
    if(nil != networkConfiguration){
        tracker = [CATAppAnalytics createTrackerWithCustomerKey:customerKey
                                                        appName:appName
                                                        network:networkConfiguration
                                                 configurations:controllers];
    }
    else{
        tracker = [CATAppAnalytics createTrackerWithCustomerKey:customerKey
                                                        appName:appName
                                                 configurations:controllers];
    }
    
    if (tracker) {
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker initialization failed", error);
    }
}

RCT_EXPORT_METHOD(removeTracker: (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];
    BOOL removed = [CATAppAnalytics removeTracker:trackerController];
    resolve(@(removed));
}

RCT_EXPORT_METHOD(removeAllTrackers: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
    [CATAppAnalytics removeAllTrackers];
    resolve(@YES);
}

RCT_EXPORT_METHOD(cleanup: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
    [CATAppAnalytics cleanup];
    resolve(@YES);
}

RCT_EXPORT_METHOD(trackSelfDescribingEvent:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSDictionary *argmap = [details objectForKey:@"eventData"];
        NSArray<NSDictionary *> *contexts = [details objectForKey:@"contexts"];

        CATSelfDescribingJson *eventData = [[CATSelfDescribingJson alloc] initWithSchema:(NSString *)[argmap objectForKey:@"schema"]
                                                                         andDictionary:(NSDictionary *)[argmap objectForKey:@"data"]];

        CATSelfDescribing *event = [[CATSelfDescribing alloc] initWithEventData:eventData];
        [event contexts:[RNUtilities mkSDJArray:contexts]];
        [trackerController track:event];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(trackStructuredEvent:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSDictionary *argmap = [details objectForKey:@"eventData"];
        NSArray<NSDictionary *> *contexts = [details objectForKey:@"contexts"];

        NSString *category = [argmap rncat_stringForKey:@"category" defaultValue:nil];
        NSString *action = [argmap rncat_stringForKey:@"action" defaultValue:nil];
        CATStructured *event = [[CATStructured alloc] initWithCategory:category
                                                              action:action];
        NSString *label = [argmap rncat_stringForKey:@"label" defaultValue:nil];
        if (label) {
            event.label = label;
        }
        NSString *property = [argmap rncat_stringForKey:@"property" defaultValue:nil];
        if (property) {
            event.property = property;
        }
        NSNumber *value = [argmap rncat_numberForKey:@"value" defaultValue:nil];
        if (label) {
            event.value = value;
        }

        [event contexts:[RNUtilities mkSDJArray:contexts]];
        [trackerController track:event];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(trackScreenViewEvent:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        
        BOOL trackScreenView = NO;
        if([(NSObject*)trackerController respondsToSelector:@selector(screenViewAutotracking)]) {
            trackScreenView = [(NSObject*)trackerController performSelector:@selector(screenViewAutotracking)];
        }
        
        if(!trackScreenView) {
            resolve(@YES);
            return;
        }

        NSDictionary *argmap = [details objectForKey:@"eventData"];
        NSArray<NSDictionary *> *contexts = [details objectForKey:@"contexts"];

        NSString *screenName = [argmap rncat_stringForKey:@"name" defaultValue:nil];
        NSString *screenId = [argmap rncat_stringForKey:@"id" defaultValue:nil];
        NSUUID *screenUuid = [[NSUUID alloc] initWithUUIDString:screenId];
        if (screenId != nil && screenUuid == nil) {
            NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
            reject(@"ERROR", @"screenId has to be a valid UUID string", error);
        }
        CATScreenView *event = [[CATScreenView alloc] initWithName:screenName
                                                        screenId:screenUuid];

        NSString *type = [argmap rncat_stringForKey:@"type" defaultValue:nil];
        if (type) {
            event.type = type;
        }
        NSString *previousName = [argmap rncat_stringForKey:@"previousName" defaultValue:nil];
        if (previousName) {
            event.previousName = previousName;
        }
        NSString *previousId = [argmap rncat_stringForKey:@"previousId" defaultValue:nil];
        if (previousId) {
            event.previousId = previousId;
        }
        NSString *previousType = [argmap rncat_stringForKey:@"previousType" defaultValue:nil];
        if (previousType) {
            event.previousType = previousType;
        }
        NSString *transitionType = [argmap rncat_stringForKey:@"transitionType" defaultValue:nil];
        if (transitionType) {
            event.transitionType = transitionType;
        }

        [event contexts:[RNUtilities mkSDJArray:contexts]];
        [trackerController track:event];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(trackPageView:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSDictionary *argmap = [details objectForKey:@"eventData"];
        NSArray<NSDictionary *> *contexts = [details objectForKey:@"contexts"];

        NSString *pageUrl = [argmap rncat_stringForKey:@"pageUrl" defaultValue:nil];
        CATPageView *event = [[CATPageView alloc] initWithPageUrl:pageUrl];

        NSString *pageTitle = [argmap rncat_stringForKey:@"pageTitle" defaultValue:nil];
        if (pageTitle) {
            event.pageTitle = pageTitle;
        }
        NSString *referrer = [argmap rncat_stringForKey:@"referrer" defaultValue:nil];
        if (referrer) {
            event.referrer = referrer;
        }

        [event contexts:[RNUtilities mkSDJArray:contexts]];
        [trackerController track:event];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(trackTimingEvent:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSDictionary *argmap = [details objectForKey:@"eventData"];
        NSArray<NSDictionary *> *contexts = [details objectForKey:@"contexts"];

        NSString *category = [argmap rncat_stringForKey:@"category" defaultValue:nil];
        NSString *variable = [argmap rncat_stringForKey:@"variable" defaultValue:nil];
        NSNumber *timing = [argmap rncat_numberForKey:@"timing" defaultValue:nil];
        CATTiming *event = [[CATTiming alloc] initWithCategory:category
                                                    variable:variable
                                                      timing:timing];
        NSString *label = [argmap rncat_stringForKey:@"label" defaultValue:nil];
        if (label) {
            event.label = label;
        }

        [event contexts:[RNUtilities mkSDJArray:contexts]];
        [trackerController track:event];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(trackConsentGrantedEvent:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSDictionary *argmap = [details objectForKey:@"eventData"];
        NSArray<NSDictionary *> *contexts = [details objectForKey:@"contexts"];

        NSString *expiry = [argmap rncat_stringForKey:@"expiry" defaultValue:nil];
        NSString *documentId = [argmap rncat_stringForKey:@"documentId" defaultValue:nil];
        NSString *version = [argmap rncat_stringForKey:@"version" defaultValue:nil];
        CATConsentGranted *event = [[CATConsentGranted alloc] initWithExpiry:expiry
                                                                documentId:documentId
                                                                   version:version];

        NSString *name = [argmap rncat_stringForKey:@"name" defaultValue:nil];
        if (name) {
            event.name = name;
        }
        NSString *documentDescription = [argmap rncat_stringForKey:@"documentDescription" defaultValue:nil];
        if (documentDescription) {
            event.documentDescription = documentDescription;
        }

        [event contexts:[RNUtilities mkSDJArray:contexts]];
        [trackerController track:event];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(trackConsentWithdrawnEvent:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSDictionary *argmap = [details objectForKey:@"eventData"];
        NSArray<NSDictionary *> *contexts = [details objectForKey:@"contexts"];

        CATConsentWithdrawn *event = [CATConsentWithdrawn new];

        BOOL all = [argmap rncat_boolForKey:@"all" defaultValue:nil];
        event.all = all;
        NSString *documentId = [argmap rncat_stringForKey:@"documentId" defaultValue:nil];
        event.documentId = documentId;
        NSString *version = [argmap rncat_stringForKey:@"version" defaultValue:nil];
        event.version = version;
        NSString *name = [argmap rncat_stringForKey:@"name" defaultValue:nil];
        if (name) {
            event.name = name;
        }
        NSString *documentDescription = [argmap rncat_stringForKey:@"documentDescription" defaultValue:nil];
        if (documentDescription) {
            event.documentDescription = documentDescription;
        }

        [event contexts:[RNUtilities mkSDJArray:contexts]];
        [trackerController track:event];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(trackEcommerceTransactionEvent:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSDictionary *argmap = [details objectForKey:@"eventData"];
        NSArray<NSDictionary *> *contexts = [details objectForKey:@"contexts"];

        NSString *orderId = [argmap rncat_stringForKey:@"orderId" defaultValue:nil];
        NSNumber *totalValue = [argmap rncat_numberForKey:@"totalValue" defaultValue:nil];
        NSArray *items = [argmap objectForKey:@"items"];

        NSMutableArray *transItems = [NSMutableArray new];
        for (NSDictionary* item in items) {
            NSString *sku = [item rncat_stringForKey:@"sku" defaultValue:nil];
            NSNumber *price = [item rncat_numberForKey:@"price" defaultValue:nil];
            NSNumber *quantity = [item rncat_numberForKey:@"quantity" defaultValue:nil];
            CATEcommerceItem *ecomItem = [[CATEcommerceItem alloc] initWithSku:sku
                                                                       price:price
                                                                    quantity:quantity];

            NSString *name = [argmap rncat_stringForKey:@"name" defaultValue:nil];
            if (name) {
                ecomItem.name = name;
            }
            NSString *category = [argmap rncat_stringForKey:@"category" defaultValue:nil];
            if (category) {
                ecomItem.category = category;
            }
            NSString *currency = [argmap rncat_stringForKey:@"currency" defaultValue:nil];
            if (currency) {
                ecomItem.currency = currency;
            }

            [transItems addObject:ecomItem];
        }

        CATEcommerce *event = [[CATEcommerce alloc] initWithOrderId:orderId
                                                       totalValue:totalValue
                                                            items:(NSArray<CATEcommerceItem *> *)transItems];
        [event contexts:[RNUtilities mkSDJArray:contexts]];
        [trackerController track:event];
        resolve(@YES);

    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(trackDeepLinkReceivedEvent:
    (NSDictionary *)details
            resolver:(RCTPromiseResolveBlock)resolve
            rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSDictionary *argmap = [details objectForKey:@"eventData"];
        NSArray<NSDictionary *> *contexts = [details objectForKey:@"contexts"];

        NSString *url = [argmap rncat_stringForKey:@"url" defaultValue:nil];
        CATDeepLinkReceived *event = [[CATDeepLinkReceived alloc] initWithUrl:url];

        NSString *referrer = [argmap rncat_stringForKey:@"referrer" defaultValue:nil];
        if (referrer) {
            event.referrer = referrer;
        }

        [event contexts:[RNUtilities mkSDJArray:contexts]];
        [trackerController track:event];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(trackMessageNotificationEvent:
    (NSDictionary *)details
            resolver:(RCTPromiseResolveBlock)resolve
            rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSDictionary *argmap = [details objectForKey:@"eventData"];
        NSArray<NSDictionary *> *contexts = [details objectForKey:@"contexts"];

        NSString *title = [argmap rncat_stringForKey:@"title" defaultValue:nil];
        NSString *body = [argmap rncat_stringForKey:@"body" defaultValue:nil];
        NSString *triggerStr = [argmap rncat_stringForKey:@"trigger" defaultValue:nil];
        CATMessageNotificationTrigger trigger;
        if ([triggerStr isEqualToString:@"push"]) {
            trigger = CATMessageNotificationTriggerPush;
        } else if ([triggerStr isEqualToString:@"location"]) {
            trigger = CATMessageNotificationTriggerLocation;
        } else if ([triggerStr isEqualToString:@"calendar"]) {
            trigger = CATMessageNotificationTriggerCalendar;
        } else if ([triggerStr isEqualToString:@"timeInterval"]) {
            trigger = CATMessageNotificationTriggerTimeInterval;
        } else {
            trigger = CATMessageNotificationTriggerOther;
        }
        CATMessageNotification *event = [[CATMessageNotification alloc] initWithTitle: title
                                                                               body: body
                                                                            trigger: trigger];

        NSString *action = [argmap rncat_stringForKey:@"action" defaultValue:nil];
        if (action) {
            event.action = action;
        }
        NSArray *attachmentsMap = [argmap objectForKey:@"attachments"];
        if (attachmentsMap) {
            NSMutableArray *attachments = [NSMutableArray new];
            for (NSDictionary* attachmentMap in attachmentsMap) {
                NSString *identifier = [attachmentMap rncat_stringForKey:@"identifier" defaultValue:nil];
                NSString *type = [attachmentMap rncat_stringForKey:@"type" defaultValue:nil];
                NSString *url = [attachmentMap rncat_stringForKey:@"url" defaultValue:nil];
                CATMessageNotificationAttachment *attachment = [[CATMessageNotificationAttachment alloc] initWithIdentifier:identifier
                                                                                                                     type:type
                                                                                                                      url:url];
                [attachments addObject:attachment];
            }
            event.attachments = attachments;
        }
        NSArray<NSString *> *bodyLocArgs = [argmap objectForKey:@"bodyLocArgs"];
        if (bodyLocArgs) {
            event.bodyLocArgs = bodyLocArgs;
        }
        NSString *bodyLocKey = [argmap rncat_stringForKey:@"bodyLocKey" defaultValue:nil];
        if (bodyLocKey) {
            event.bodyLocKey = bodyLocKey;
        }
        NSString *category = [argmap rncat_stringForKey:@"category" defaultValue:nil];
        if (category) {
            event.category = category;
        }
        NSNumber *contentAvailable = [argmap rncat_numberForKey:@"contentAvailable" defaultValue:nil];
        if (contentAvailable != nil) {
            event.contentAvailable = contentAvailable;
        }
        NSString *group = [argmap rncat_stringForKey:@"group" defaultValue:nil];
        if (group) {
            event.group = group;
        }
        NSString *icon = [argmap rncat_stringForKey:@"icon" defaultValue:nil];
        if (icon) {
            event.icon = icon;
        }
        NSNumber *notificationCount = [argmap rncat_numberForKey:@"notificationCount" defaultValue:nil];
        if (notificationCount) {
            event.notificationCount = notificationCount;
        }
        NSString *notificationTimestamp = [argmap rncat_stringForKey:@"notificationTimestamp" defaultValue:nil];
        if (notificationTimestamp) {
            event.notificationTimestamp = notificationTimestamp;
        }
        NSString *sound = [argmap rncat_stringForKey:@"sound" defaultValue:nil];
        if (sound) {
            event.sound = sound;
        }
        NSString *subtitle = [argmap rncat_stringForKey:@"subtitle" defaultValue:nil];
        if (subtitle) {
            event.subtitle = subtitle;
        }
        NSString *tag = [argmap rncat_stringForKey:@"tag" defaultValue:nil];
        if (tag) {
            event.tag = tag;
        }
        NSString *threadIdentifier = [argmap rncat_stringForKey:@"threadIdentifier" defaultValue:nil];
        if (threadIdentifier) {
            event.threadIdentifier = threadIdentifier;
        }
        NSArray<NSString *> *titleLocArgs = [argmap objectForKey:@"titleLocArgs"];
        if (titleLocArgs) {
            event.titleLocArgs = titleLocArgs;
        }
        NSString *titleLocKey = [argmap rncat_stringForKey:@"titleLocKey" defaultValue:nil];
        if (titleLocKey) {
            event.titleLocKey = titleLocKey;
        }

        [event contexts:[RNUtilities mkSDJArray:contexts]];
        [trackerController track:event];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(trackCustomEvent:
    (NSDictionary *)details
            resolver:(RCTPromiseResolveBlock)resolve
            rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
   
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSString *eventName = [details objectForKey:@"eventName"];
        NSDictionary *eventData = [details objectForKey:@"eventData"];

        if(0 < eventName.length){
            [trackerController trackCustomEvent:eventName eventData:eventData];
        }
        
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(trackRevenueEvent:
    (NSDictionary *)details
            resolver:(RCTPromiseResolveBlock)resolve
            rejecter:(RCTPromiseRejectBlock)reject) {

    NSString *namespace = [details objectForKey:@"tracker"];

    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSDictionary *argmap = [details objectForKey:@"eventData"];

        NSNumber *totalOrderAmount = [argmap objectForKey:@"totalOrderAmount"];
        NSString *transactionId = [argmap objectForKey:@"transactionId"];
        NSString *currency = [argmap objectForKey:@"currency"];
        
        CATRevenueEvent *event = [[CATRevenueEvent alloc]
                                  initWithTotalOrderAmount:totalOrderAmount
                                  transactionId:transactionId
                                  currency:currency];

        if ([argmap objectForKey:@"taxAmount"] && ![[argmap objectForKey:@"taxAmount"] isEqual:[NSNull null]]) {
            event.taxAmount = [argmap objectForKey:@"taxAmount"];
        }
        if ([argmap objectForKey:@"shippingCost"] && ![[argmap objectForKey:@"shippingCost"] isEqual:[NSNull null]]) {
            event.shippingCost = [argmap objectForKey:@"shippingCost"];
        }
        if ([argmap objectForKey:@"discount"] && ![[argmap objectForKey:@"discount"] isEqual:[NSNull null]]) {
            event.discount = [argmap objectForKey:@"discount"];
        }
        if ([argmap objectForKey:@"cartSize"] && ![[argmap objectForKey:@"cartSize"] isEqual:[NSNull null]]) {
            event.cartSize = [argmap objectForKey:@"cartSize"];
        }
        if ([argmap objectForKey:@"paymentMethod"] && ![[argmap objectForKey:@"paymentMethod"] isEqual:[NSNull null]]) {
            event.paymentMethod = [argmap objectForKey:@"paymentMethod"];
        }
        if ([argmap objectForKey:@"paymentProvider"] && ![[argmap objectForKey:@"paymentProvider"] isEqual:[NSNull null]]) {
            event.paymentProvider = [argmap objectForKey:@"paymentProvider"];
        }
        if ([argmap objectForKey:@"extraMetadata"] && ![[argmap objectForKey:@"extraMetadata"] isEqual:[NSNull null]]) {
            event.extraMetadata = [argmap objectForKey:@"extraMetadata"];
        }

        NSArray *itemsArray = [argmap objectForKey:@"items"];
        if (itemsArray && ![itemsArray isEqual:[NSNull null]] && [itemsArray isKindOfClass:[NSArray class]]) {
            NSMutableArray<CATRevenueEventItem *> *items = [NSMutableArray array];
            for (NSDictionary *itemDict in itemsArray) {
                if (![itemDict isKindOfClass:[NSDictionary class]]) continue;

                CATRevenueEventItem *item = [[CATRevenueEventItem alloc] init];

                if ([itemDict objectForKey:@"productId"] && ![[itemDict objectForKey:@"productId"] isEqual:[NSNull null]]) {
                    item.productId = [itemDict objectForKey:@"productId"];
                }
                if ([itemDict objectForKey:@"name"] && ![[itemDict objectForKey:@"name"] isEqual:[NSNull null]]) {
                    item.name = [itemDict objectForKey:@"name"];
                }
                if ([itemDict objectForKey:@"sku"] && ![[itemDict objectForKey:@"sku"] isEqual:[NSNull null]]) {
                    item.sku = [itemDict objectForKey:@"sku"];
                }
                if ([itemDict objectForKey:@"category"] && ![[itemDict objectForKey:@"category"] isEqual:[NSNull null]]) {
                    item.category = [itemDict objectForKey:@"category"];
                }
                if ([itemDict objectForKey:@"unitPrice"] && ![[itemDict objectForKey:@"unitPrice"] isEqual:[NSNull null]]) {
                    item.unitPrice = [itemDict objectForKey:@"unitPrice"];
                }
                if ([itemDict objectForKey:@"quantity"] && ![[itemDict objectForKey:@"quantity"] isEqual:[NSNull null]]) {
                    item.quantity = [itemDict objectForKey:@"quantity"];
                }
                if ([itemDict objectForKey:@"discount"] && ![[itemDict objectForKey:@"discount"] isEqual:[NSNull null]]) {
                    item.discount = [itemDict objectForKey:@"discount"];
                }
                if ([itemDict objectForKey:@"brand"] && ![[itemDict objectForKey:@"brand"] isEqual:[NSNull null]]) {
                    item.brand = [itemDict objectForKey:@"brand"];
                }
                if ([itemDict objectForKey:@"variant"] && ![[itemDict objectForKey:@"variant"] isEqual:[NSNull null]]) {
                    item.variant = [itemDict objectForKey:@"variant"];
                }
                if ([itemDict objectForKey:@"extraMetadata"] && ![[itemDict objectForKey:@"extraMetadata"] isEqual:[NSNull null]]) {
                    item.extraMetadata = [itemDict objectForKey:@"extraMetadata"];
                }

                [items addObject:item];
            }
            event.items = items;
        }

        [trackerController trackRevenueEvent:event];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(setCustomTags:
    (NSDictionary *)details
            resolver:(RCTPromiseResolveBlock)resolve
            rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
   
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSDictionary *tags = [details objectForKey:@"tags"];

        if(0 < tags.count){
            [trackerController setCustomTags:tags];
        }
        
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(setCustomTagsWithCategory:
    (NSDictionary *)details
            resolver:(RCTPromiseResolveBlock)resolve
            rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
   
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSString *category = [details objectForKey:@"category"];
        NSDictionary *tags = [details objectForKey:@"tags"];

        if(0 < category.length && 0 < tags.count){
            [trackerController setCustomTags:category tags:tags];
        }
        
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(clearCustomTags:
    (NSDictionary *)details
            resolver:(RCTPromiseResolveBlock)resolve
            rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
   
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSArray *tags = [details objectForKey:@"tagKeys"];

        if(0 < tags.count){
            [trackerController clearCustomTags:tags];
        }
        
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(clearAllCustomTags:
    (NSDictionary *)details
            resolver:(RCTPromiseResolveBlock)resolve
            rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
   
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {

        [trackerController clearCustomTags];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(removeGlobalContexts:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSString *tag = [details objectForKey:@"removeTag"];
        [[trackerController globalContexts] removeWithTag:tag];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(addGlobalContexts:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSDictionary *gcArg = [details objectForKey:@"addGlobalContext"];
        NSString *tag = [gcArg objectForKey:@"tag"];
        NSArray *globalContexts = [gcArg objectForKey:@"globalContexts"];

        NSArray *staticContexts = [[RNUtilities mkSDJArray:globalContexts] mutableCopy];
        CATGlobalContext *gcStatic = [[CATGlobalContext alloc] initWithStaticContexts:staticContexts];

        [[trackerController globalContexts] addWithTag:tag contextGenerator:gcStatic];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(setUserId:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSString *newUid = [details rncat_stringForKey:@"userId" defaultValue:nil];
        [trackerController.subject setUserId:newUid];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(setNetworkUserId:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSString *newNuid = [details rncat_stringForKey:@"networkUserId" defaultValue:nil];
        [trackerController.subject setNetworkUserId:newNuid];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(setDomainUserId:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSString *newDuid = [details rncat_stringForKey:@"domainUserId" defaultValue:nil];
        [trackerController.subject setDomainUserId:newDuid];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(setIpAddress:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSString *newIp = [details rncat_stringForKey:@"ipAddress" defaultValue:nil];
        [trackerController.subject setIpAddress:newIp];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(setUseragent:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSString *newUagent = [details rncat_stringForKey:@"useragent" defaultValue:nil];
        [trackerController.subject setUseragent:newUagent];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(setTimezone:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSString *newTz = [details rncat_stringForKey:@"timezone" defaultValue:nil];
        [trackerController.subject setTimezone:newTz];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(setLanguage:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSString *newLang = [details rncat_stringForKey:@"language" defaultValue:nil];
        [trackerController.subject setLanguage:newLang];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(setScreenResolution:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSObject *newRes = [details objectForKey:@"screenResolution"];
        if (newRes == [NSNull null]) {
            [trackerController.subject setScreenResolution:nil];
        } else {
            NSNumber *resWidth = [(NSArray *)newRes objectAtIndex:0];
            NSNumber *resHeight = [(NSArray *)newRes objectAtIndex:1];
            CISSize *resSize = [[CISSize alloc] initWithWidth:[resWidth integerValue] height:[resHeight integerValue]];
            [trackerController.subject setScreenResolution:resSize];
        }
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(setScreenViewport:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSObject *newView = [details objectForKey:@"screenViewport"];
        if (newView == [NSNull null]) {
            [trackerController.subject setScreenViewPort:nil];
        } else {
            NSNumber *vpWidth = [(NSArray *)newView objectAtIndex:0];
            NSNumber *vpHeight = [(NSArray *)newView objectAtIndex:1];
            CISSize *vpSize = [[CISSize alloc] initWithWidth:[vpWidth integerValue] height:[vpHeight integerValue]];
            [trackerController.subject setScreenViewPort:vpSize];
        }
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(setColorDepth:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSNumber *newColorD = [details rncat_numberForKey:@"colorDepth" defaultValue:nil];
        [trackerController.subject setColorDepth:newColorD];
        resolve(@YES);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(getSessionUserId:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSString *suid = [trackerController.session userId];
        resolve(suid);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(getSessionId:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSString *sid = [trackerController.session sessionId];
        resolve(sid);
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(getSessionIndex:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    NSString *namespace = [details objectForKey:@"tracker"];
    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
        NSInteger sidx = [trackerController.session sessionIndex];
        resolve(@(sidx));
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(getIsInBackground:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
//    NSString *namespace = [details objectForKey:@"tracker"];
//    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];
//
//    if (trackerController != nil) {
//        BOOL isInBg = [trackerController.session isInBackground];
//        resolve(@(isInBg));
//    } else {
//        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
//        reject(@"ERROR", @"tracker with given namespace not found", error);
//    }
    
    @try {
        resolve(@(YES));
    } @catch (NSException *exception) {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(getBackgroundIndex:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
//    NSString *namespace = [details objectForKey:@"tracker"];
//    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];
//
//    if (trackerController != nil) {
//        NSInteger bgIdx = [trackerController.session backgroundIndex];
//        resolve(@(bgIdx));
//    } else {
//        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
//        reject(@"ERROR", @"tracker with given namespace not found", error);
//    }
    @try {
        resolve(@(YES));
    } @catch (NSException *exception) {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(getForegroundIndex:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
//    NSString *namespace = [details objectForKey:@"tracker"];
//    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];
//
//    if (trackerController != nil) {
//        NSInteger fgIdx = [trackerController.session foregroundIndex];
//        resolve(@(fgIdx));
//    } else {
//        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
//        reject(@"ERROR", @"tracker with given namespace not found", error);
//    }
    @try {
        resolve(@(YES));
    } @catch (NSException *exception) {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

RCT_EXPORT_METHOD(getClientId: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
    
    CATAppAnalytics *convivaAppAnalytics = [CATAppAnalytics sharedInstance];

    if (convivaAppAnalytics != nil) {
        NSString *clientId = [convivaAppAnalytics getClientId];
        if (clientId != nil && clientId.length > 0) {
            resolve(clientId);
        }
        else {
            NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
            reject(@"ERROR", @"ClientId not found", error);
        }
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"CATAppAnalytics not found", error);
    }
}

RCT_EXPORT_METHOD(setClientId:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    CATAppAnalytics *convivaAppAnalytics = [CATAppAnalytics sharedInstance];

    if (convivaAppAnalytics != nil) {
        NSString *newClientId = [details rncat_stringForKey:@"clientId" defaultValue:nil];
        BOOL isClientIdSet = NO;
        if (newClientId != nil && newClientId.length > 0) {
             isClientIdSet = [convivaAppAnalytics setClientId: newClientId];
        }
        resolve(@(isClientIdSet));
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

- (nullable id<CATTrackerController>)trackerByNamespace:(NSString *)namespace {
    return [namespace isEqual:[NSNull null]] ? [CATAppAnalytics defaultTracker] : [CATAppAnalytics trackerByNamespace:namespace];
}

// -----------------------------------------------------------------------------
// JS → native JS-bundle-identity entry point.
//
// Forwards the JS bundle identity map (jsBundleSource / jsBundleId /
// jsBundleChannel / jsBundleLabel / jsEngine) to the native SDK so it is
// attached to the Conviva app-context entity on every subsequent event.
//
// The underlying `-setJsBundleInfo:` is a newer addition to CATTrackerController
// and may be absent in older ConvivaAppAnalytics versions. To stay BOTH
// compile-safe (no hard dependency on the selector being declared) AND
// runtime-safe (no crash on older SDKs), it is dispatched dynamically and only
// invoked when the controller actually responds to it. D7: never throws.
// -----------------------------------------------------------------------------
RCT_EXPORT_METHOD(setJsBundleInfo:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    @try {
        NSString *namespace = [details objectForKey:@"tracker"];
        id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

        if (trackerController != nil) {
            NSDictionary *info = [details objectForKey:@"info"];
            SEL selector = NSSelectorFromString(@"setJsBundleInfo:");
            if (info != nil && [(NSObject *)trackerController respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                [(NSObject *)trackerController performSelector:selector withObject:info];
#pragma clang diagnostic pop
            }
            resolve(@YES);
        } else {
            NSError *error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
            reject(@"ERROR", @"tracker with given namespace not found", error);
        }
    } @catch (NSException *exception) {
        NSError *error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", exception.reason ?: @"setJsBundleInfo failed", error);
    }
}

// -----------------------------------------------------------------------------
// JS → native error-reporting entry point.
//
// Receives a single flat JSON string produced by the JS-side NativeBridgeAdapter
// and handles NON-FATAL JS errors only:
//
//  - isFatal=false -> trackCustomEvent("conviva_non_fatal_error") with
//    descriptive keys. Dedicated metric surface; does not affect crash-rate.
//
// Fatal JS errors are NOT handled here. They are owned by the native iOS SDK's
// CATUnCaughtExceptionHandler, which detects RCTFatalException by name and emits
// the fatal sp/ae/1-0-2 with programmingLanguage=JAVASCRIPT — mirroring the
// Android native ExceptionHandler. The JS GlobalErrorHandlerPlugin gates fatal
// dispatch off on iOS, so reporting a fatal from this bridge would double-report
// against the native handler. See plan §7 / §7A.1 / §7A.4-B (D11 / D15).
//
// Web / Horizontal JS errors remain conviva_application_error — unchanged.
//
// D4-fix: thread-safe — no extra dispatch_queue needed.
// D7: this method MUST NOT throw — every failure is swallowed silently.
// -----------------------------------------------------------------------------
RCT_EXPORT_METHOD(reportJsError:(NSString *)payloadJson) {
    @try {
        if (payloadJson == nil || payloadJson.length == 0) {
            return;
        }

        NSError *parseError = nil;
        NSData *data = [payloadJson dataUsingEncoding:NSUTF8StringEncoding];
        id parsed = [NSJSONSerialization JSONObjectWithData:data
                                                    options:0
                                                      error:&parseError];
        if (parseError || ![parsed isKindOfClass:[NSDictionary class]]) {
            return; // malformed payload — fail silent (D7)
        }
        NSDictionary *json = (NSDictionary *)parsed;

        BOOL isFatal = [json[@"isFatal"] boolValue];
        id<CATTrackerController> tracker = [CATAppAnalytics defaultTracker];
        if (tracker == nil) {
            return;
        }

        // Fatal JS errors are owned by the native iOS SDK (CATUnCaughtExceptionHandler
        // detects RCTFatalException and emits the fatal sp/ae/1-0-2). The bridge
        // handles non-fatals only to avoid double-reporting. See plan §7A.4-B.
        if (!isFatal) {
            // Non-fatal JS error — custom event with descriptive keys.
            NSMutableDictionary *eventData = [NSMutableDictionary dictionary];
            eventData[@"message"]        = json[@"message"]        ?: @"";
            eventData[@"errorType"]      = json[@"errorType"]      ?: @"";
            eventData[@"stackTrace"]     = json[@"stackTrace"]     ?: @"";
            eventData[@"timestamp"]      = json[@"timestamp"]      ?: @0;
            eventData[@"isFatal"]        = @NO;
            eventData[@"isHandled"]      = json[@"isHandled"]      ?: @YES;
            eventData[@"errorSource"]    = json[@"errorSource"]    ?: @"";
            eventData[@"severity"]       = json[@"severity"]       ?: @"";
            eventData[@"componentStack"] = json[@"componentStack"] ?: @"";
            eventData[@"jsEngine"]       = json[@"jsEngine"]       ?: @"unknown";

            if (json[@"lineNumber"] && json[@"lineNumber"] != [NSNull null]) {
                eventData[@"lineNumber"] = json[@"lineNumber"];
            }
            if (json[@"lineColumn"] && json[@"lineColumn"] != [NSNull null]) {
                eventData[@"lineColumn"] = json[@"lineColumn"];
            }
            if (json[@"fileName"] && json[@"fileName"] != [NSNull null]) {
                eventData[@"fileName"] = json[@"fileName"];
            }
            if (json[@"bundleId"] && json[@"bundleId"] != [NSNull null]) {
                eventData[@"bundleId"] = json[@"bundleId"];
            }

            // Consumer attributes — all keys not in the known set.
            NSSet *knownSet = [NSSet setWithArray:@[
                @"timestamp", @"message", @"errorType", @"stackTrace", @"isFatal",
                @"isHandled", @"errorSource", @"severity", @"componentStack",
                @"lineNumber", @"lineColumn", @"fileName", @"bundleId", @"jsEngine"
            ]];
            for (NSString *key in json) {
                if (![knownSet containsObject:key]) {
                    id value = json[key];
                    if (value != nil && value != [NSNull null]) {
                        eventData[key] = value;
                    }
                }
            }

            [tracker trackCustomEvent:@"conviva_non_fatal_error" eventData:[eventData copy]];
        }
    } @catch (NSException *ignored) {
        // D7: error-reporting path must never throw.
    }
}

// -----------------------------------------------------------------------------
// JS → native remote-config pull.
//
// Returns the verbatim last-applied remote-config JSON (or null) so the JS
// RemoteConfigSync can parse error-tracking fields (exceptionAutotracking /
// collectionRateLimit) that the native SDK does not expose as typed getters.
// Mirrors the Android bridge's getRemoteConfig →
// ConvivaAppAnalytics.getAppliedRemoteConfigJson().
//
// The underlying +[CATAppAnalytics appliedRemoteConfigJson] is a newer addition
// and may be absent in older ConvivaAppAnalytics versions, so it is dispatched
// dynamically (respondsToSelector:) — both compile-safe (no hard dependency on
// the selector being declared) AND runtime-safe (no crash on older SDKs). When
// the API is unavailable the method resolves null so JS falls back to its
// defaults. D7: never throws.
// -----------------------------------------------------------------------------
RCT_EXPORT_METHOD(getRemoteConfig:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    @try {
        NSString *configJson = nil;
        SEL selector = NSSelectorFromString(@"appliedRemoteConfigJson");
        if ([CATAppAnalytics respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            id result = [CATAppAnalytics performSelector:selector];
#pragma clang diagnostic pop
            if ([result isKindOfClass:[NSString class]]) {
                configJson = (NSString *)result;
            }
        }
        // Resolves null when the native API is unavailable or no config has been
        // applied yet — JS RemoteConfigSync then keeps its current/default config.
        resolve(configJson);
    } @catch (NSException *exception) {
        // D7 — resolve null so JS uses its defaults; never reject hard.
        resolve(nil);
    }
}

RCT_EXPORT_METHOD(trackClickEvent:
                  (NSDictionary *)details
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject) {
    
    NSString *namespace = [details objectForKey:@"tracker"];

    id<CATTrackerController> trackerController = [self trackerByNamespace:namespace];

    if (trackerController != nil) {
       
        NSDictionary *eventData = [details objectForKey:@"eventData"];
        
        if(nil != eventData && 0 < eventData.count){
            
            NSMutableDictionary *data = [NSMutableDictionary new];
            
            NSString *elementType = eventData[@"elementType"] ?: @"";
            if(0 < elementType.length){
                data[@"elementType"] = elementType;
            }
            
            NSString *elementId = eventData[@"elementId"] ?: @"";
            if(0 < elementId.length){
                data[@"elementId"] = elementId;
            }
            
            NSString *elementName = eventData[@"elementName"] ?: @"";
            if(0 < elementName.length){
                data[@"elementName"] = elementName;
            }
            
            NSString *elementClasses = eventData[@"elementClasses"] ?: @"";
            if(0 < elementClasses.length){
                data[@"elementClasses"] = elementClasses;
            }
            
            NSString *elementText = eventData[@"elementText"]  ?: @"";
            if(0 < elementText.length){
                data[@"elementText"] = elementText;
            }
            
            NSString *elementValue = eventData[@"elementValue"] ?: @"";
            if(0 < elementValue.length){
                data[@"elementValue"] = elementValue;
            }
            
            [[NSNotificationCenter defaultCenter] postNotificationName:@"kCATNotificationButtonClick" object:nil userInfo:data];
        }
        
        resolve(@(YES));
    } else {
        NSError* error = [NSError errorWithDomain:@"ConvivaAppAnalytics" code:200 userInfo:nil];
        reject(@"ERROR", @"tracker with given namespace not found", error);
    }
}

#if !TARGET_OS_TV
RCT_EXPORT_METHOD(startReplay: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
    @try {
        [CATAppAnalytics startReplay];
        resolve(@(YES));
    } @catch (NSException *exception) {
        reject(@"ERROR", exception.reason, nil);
    }
}

RCT_EXPORT_METHOD(stopReplay: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) {
    @try {
        [CATAppAnalytics stopReplay];
        resolve(@(YES));
    } @catch (NSException *exception) {
        reject(@"ERROR", exception.reason, nil);
    }
}
#endif

@end
