//
//  RPRReproReactBridge.h
//  RPRReproReactBridge
//
//  Created by Markus Wanke on 2018/08/08.
//  Copyright © 2018 Repro Inc. All rights reserved.
//


#import "RPRReproReactBridge.h"
#import <Repro/Repro.h>
#import <Repro/RPRRemoteConfig.h>
#import <Repro/RPRNewsFeedEntry.h>
#import <Repro/RPRUserProfileTypes.h>

// ReactNative Version >= 0.41
#if __has_include(<React/RCTConvert.h>)
#import <React/RCTConvert.h>

// ReactNative Version < 0.41
#elif __has_include("RCTConvert.h")
#import "RCTConvert.h"

// if ReactNative is a cocoapod and the project has swift enabled
#elif __has_include("React/RCTConvert.h")
#import "React/RCTConvert.h"

#else
#error "Can't find RCTConvert.h anywhere."
#endif




// ReactNative Version >= 0.41
#if __has_include(<React/RCTVersion.h>)
#import <React/RCTVersion.h>

// ReactNative Version < 0.41
#elif __has_include("RCTVersion.h")
#import "RCTVersion.h"

// if ReactNative is a cocoapod and the project has swift enabled
#elif __has_include("React/RCTVersion.h")
#import "React/RCTVersion.h"

#else
    #define RPR_CANT_DETERMINE_RN_VERSION 1
#endif



static NSObject*
safe_value(id value)
{
    return (value != nil) ? value : [NSNull null];
}

@implementation RCTConvert (RPRLogLevel)
    RCT_ENUM_CONVERTER(RPRLogLevel, (@{
        @"LOGLEVEL_DEBUG"  : @(RPRLogLevelDebug),
        @"LOGLEVEL_INFO"   : @(RPRLogLevelInfo),
        @"LOGLEVEL_WARN"   : @(RPRLogLevelWarn),
        @"LOGLEVEL_ERROR"  : @(RPRLogLevelError),
        @"LOGLEVEL_NONE"   : @(RPRLogLevelNone)}),
    RPRLogLevelNone, integerValue)
@end

@implementation RCTConvert (RPRUserProfileGender)
    RCT_ENUM_CONVERTER(RPRUserProfileGender, (@{
        @"GENDER_OTHER"    : @(RPRUserProfileGenderOther),
        @"GENDER_MALE"     : @(RPRUserProfileGenderMale),
        @"GENDER_FEMALE"   : @(RPRUserProfileGenderFemale)}),
    RPRUserProfileGenderOther, integerValue)
@end

@implementation RCTConvert (RPRRemoteConfigFetchStatus)
    RCT_ENUM_CONVERTER(RPRRemoteConfigFetchStatus, (@{
        @"REMOTE_CONFIG_SUCCESS"              : @(RPRRemoteConfigFetchStatusSuccess),
        @"REMOTE_CONFIG_TIMEOUT_REACHED"      : @(RPRRemoteConfigFetchStatusTimeoutReached),
        @"REMOTE_CONFIG_ALREADY_FETCHED"      : @(RPRRemoteConfigFetchStatusAlreadyFetched)}),
    RPRRemoteConfigFetchStatusTimeoutReached, integerValue)
@end

@implementation RCTConvert (RPRCampaignType)
    RCT_ENUM_CONVERTER(RPRCampaignType, (@{
        @"CAMPAIGN_TYPE_UNKNOWN"              : @(RPRCampaignTypeUnknown),
        @"CAMPAIGN_TYPE_PUSH_NOTIFICATION"    : @(RPRCampaignTypePushNotification),
        @"CAMPAIGN_TYPE_IN_APP_MESSAGE"       : @(RPRCampaignTypeInAppMessage),
        @"CAMPAIGN_TYPE_WEB_MESSAGE"          : @(RPRCampaignTypeWebMessage)}),
    RPRCampaignTypePushNotification, integerValue)
@end

@implementation RCTConvert (RPRUserProfilePrefecture)
    RCT_ENUM_CONVERTER(RPRUserProfilePrefecture, (@{
        @"PREFECTURE_HOKKAIDO" : @(RPRUserProfilePrefectureHokkaido),
        @"PREFECTURE_AOMORI" : @(RPRUserProfilePrefectureAomori),
        @"PREFECTURE_IWATE" : @(RPRUserProfilePrefectureIwate),
        @"PREFECTURE_MIYAGI" : @(RPRUserProfilePrefectureMiyagi),
        @"PREFECTURE_AKITA" : @(RPRUserProfilePrefectureAkita),
        @"PREFECTURE_YAMAGATA" : @(RPRUserProfilePrefectureYamagata),
        @"PREFECTURE_FUKUSHIMA" : @(RPRUserProfilePrefectureFukushima),
        @"PREFECTURE_IBARAKI" : @(RPRUserProfilePrefectureIbaraki),
        @"PREFECTURE_TOCHIGI" : @(RPRUserProfilePrefectureTochigi),
        @"PREFECTURE_GUNMA" : @(RPRUserProfilePrefectureGunma),
        @"PREFECTURE_SAITAMA" : @(RPRUserProfilePrefectureSaitama),
        @"PREFECTURE_CHIBA" : @(RPRUserProfilePrefectureChiba),
        @"PREFECTURE_TOKYO" : @(RPRUserProfilePrefectureTokyo),
        @"PREFECTURE_KANAGAWA" : @(RPRUserProfilePrefectureKanagawa),
        @"PREFECTURE_NIIGATA" : @(RPRUserProfilePrefectureNiigata),
        @"PREFECTURE_TOYAMA" : @(RPRUserProfilePrefectureToyama),
        @"PREFECTURE_ISHIKAWA" : @(RPRUserProfilePrefectureIshikawa),
        @"PREFECTURE_FUKUI" : @(RPRUserProfilePrefectureFukui),
        @"PREFECTURE_YAMANASHI" : @(RPRUserProfilePrefectureYamanashi),
        @"PREFECTURE_NAGANO" : @(RPRUserProfilePrefectureNagano),
        @"PREFECTURE_GIFU" : @(RPRUserProfilePrefectureGifu),
        @"PREFECTURE_SHIZUOKA" : @(RPRUserProfilePrefectureShizuoka),
        @"PREFECTURE_AICHI" : @(RPRUserProfilePrefectureAichi),
        @"PREFECTURE_MIE" : @(RPRUserProfilePrefectureMie),
        @"PREFECTURE_SHIGA" : @(RPRUserProfilePrefectureShiga),
        @"PREFECTURE_KYOTO" : @(RPRUserProfilePrefectureKyoto),
        @"PREFECTURE_OSAKA" : @(RPRUserProfilePrefectureOsaka),
        @"PREFECTURE_HYOGO" : @(RPRUserProfilePrefectureHyogo),
        @"PREFECTURE_NARA" : @(RPRUserProfilePrefectureNara),
        @"PREFECTURE_WAKAYAMA" : @(RPRUserProfilePrefectureWakayama),
        @"PREFECTURE_TOTTORI" : @(RPRUserProfilePrefectureTottori),
        @"PREFECTURE_SHIMANE" : @(RPRUserProfilePrefectureShimane),
        @"PREFECTURE_OKAYAMA" : @(RPRUserProfilePrefectureOkayama),
        @"PREFECTURE_HIROSHIMA" : @(RPRUserProfilePrefectureHiroshima),
        @"PREFECTURE_YAMAGUCHI" : @(RPRUserProfilePrefectureYamaguchi),
        @"PREFECTURE_TOKUSHIMA" : @(RPRUserProfilePrefectureTokushima),
        @"PREFECTURE_KAGAWA" : @(RPRUserProfilePrefectureKagawa),
        @"PREFECTURE_EHIME" : @(RPRUserProfilePrefectureEhime),
        @"PREFECTURE_KOCHI" : @(RPRUserProfilePrefectureKochi),
        @"PREFECTURE_FUKUOKA" : @(RPRUserProfilePrefectureFukuoka),
        @"PREFECTURE_SAGA" : @(RPRUserProfilePrefectureSaga),
        @"PREFECTURE_NAGASAKI" : @(RPRUserProfilePrefectureNagasaki),
        @"PREFECTURE_KUMAMOTO" : @(RPRUserProfilePrefectureKumamoto),
        @"PREFECTURE_OITA" : @(RPRUserProfilePrefectureOita),
        @"PREFECTURE_MIYAZAKI" : @(RPRUserProfilePrefectureMiyazaki),
        @"PREFECTURE_KAGOSHIMA" : @(RPRUserProfilePrefectureKagoshima),
        @"PREFECTURE_OKINAWA" : @(RPRUserProfilePrefectureOkinawa)}),
    RPRUserProfilePrefectureHokkaido, integerValue)
@end


@interface RPRRemoteConfigBridge ()

@property (nonatomic) RPRRemoteConfig* remoteConfig;

@end


@implementation RPRRemoteConfigBridge

RCT_EXPORT_MODULE(RemoteConfig)

- (NSDictionary *)constantsToExport
{
    return @{
        @"REMOTE_CONFIG_SUCCESS"            : @(RPRRemoteConfigFetchStatusSuccess),
        @"REMOTE_CONFIG_TIMEOUT_REACHED"    : @(RPRRemoteConfigFetchStatusTimeoutReached),
        @"REMOTE_CONFIG_ALREADY_FETCHED"    : @(RPRRemoteConfigFetchStatusAlreadyFetched),
    };
};

+ (BOOL)requiresMainQueueSetup
{
    return YES;  // needed because we override `constantsToExport`
}

- (NSDictionary *)toDictionary:(NSDictionary<NSString *, RPRRemoteConfigValue *> *)values
{
    NSMutableDictionary *dict = [NSMutableDictionary.alloc initWithCapacity:values.count];

    for (NSString* key in values.allKeys) {
        RPRRemoteConfigValue* value = values[key];
        if (value.stringValue) {
            [dict setObject:value.stringValue forKey:key];
        }
    }

    return [NSDictionary.alloc initWithDictionary: dict];
}

/// You may not need this method because the default behavior is:
///    - After a call to `Repro.setup()`, automatically fetch every time the app will enter foreground
///    - Run `activateFetched` as soon as a response was received.
///
/// If you need a completionHandler to ensure the remoteConfig gets activated at a certain point in time
/// or want to validate remote config functionality while development, you should use this method. You can
/// only set one completionHandler at a time. Also only one fetch per app foreground/background cycle is
/// permitted. Therefore you should call this when your app comes to foreground, preferably
/// from `applicationWillEnterForeground`.
///
/// If the completionHandler handler is called with status `RPRRemoteConfigFetchStatusSuccess`, you should
/// proceed with calling `activateFetched` in the completionHandler or after the completionHandler has
/// been executed.
///
/// After `activateFetched` has been called, new remote config values are available. This completionHandler
/// is always guaranteed to be called on the main thread. The callback will be invalidated and not executed
/// if the app goes to background or the end-user OptsOut via the OptIn/OptOut API.
RCT_EXPORT_METHOD(fetch:(double)timeout completionHandler:(RCTResponseSenderBlock)callback)
{
    double timeoutSecs = timeout * 0.001;
    [[Repro remoteConfig] fetchWithTimeout:timeoutSecs completionHandler:^(RPRRemoteConfigFetchStatus status) {
        callback(@[@(status)]);
    }];
}

/// This is only needed if you use `fetchWithTimeout:completionHandler:`. See above.
/// Returns YES if a previously fetched remote config has replaced the current remote config.
RCT_EXPORT_METHOD(activateFetched)
{
    [[Repro remoteConfig] activateFetched];
}

/// Set local defaults for remote config queries via dictionary.
RCT_EXPORT_METHOD(setDefaultsFromDictionary:(NSDictionary<NSString *, id> *) json)
{
    [[Repro remoteConfig] setDefaultsFromDictionary: json];
}

/// Set local defaults for remote config queries via a json string.
RCT_EXPORT_METHOD(setDefaultsFromJsonString:(NSString *)string)
{
    [[Repro remoteConfig] setDefaultsFromJsonString: string];
}

/// Access to remote config values.
RCT_EXPORT_METHOD(getValue:(NSString*)key callback:(RCTResponseSenderBlock)callback)
{
    callback(@[safe_value([[[Repro remoteConfig] valueForKey: key] stringValue])]);
}

/// Return a dictonary with all key value pairs.
RCT_EXPORT_METHOD(getAllValues:(RCTResponseSenderBlock)callback)
{
    callback(@[safe_value([self toDictionary: [[Repro remoteConfig] allValues]])]);
}

/// Return a dictonary with all key value pairs for a given prefix. Pass `nil` or an empty string to get all values.
RCT_EXPORT_METHOD(getAllValuesWithPrefix:(NSString*)key callback:(RCTResponseSenderBlock)callback)
{
    callback(@[safe_value([self toDictionary:[[Repro remoteConfig] allValuesWithPrefix: key]])]);
}

/// Returns the local default value for a key.
RCT_EXPORT_METHOD(getLocalDefaultValue:(NSString*)key callback:(RCTResponseSenderBlock)callback)
{
    callback(@[safe_value([[[Repro remoteConfig] localDefaultValueForKey: key] stringValue])]);
}

/// Reset all data. Local config & remote Config. Should only be used while in development.
RCT_EXPORT_METHOD(forceReset)
{
    [[Repro remoteConfig] forceReset];
}


@end




@interface Repro (NonPublicApi)
+ (void)_passRuntimeValues:(nonnull NSDictionary<NSString *, NSString *> *)values;
+ (BOOL)_webviewJavaScriptOpenUrlHandler:(NSString *)url;
@end




#ifndef RPR_CANT_DETERMINE_RN_VERSION
static NSString *get_react_native_version()
{
// see: https://github.com/facebook/react-native/commit/30469ed00170a74743d2ba5aadce61aae742715c#
#ifdef RCT_REACT_NATIVE_VERSION
    NSDictionary *vermap = RCT_REACT_NATIVE_VERSION;
    return [NSString stringWithFormat:@"%@.%@.%@",
            vermap[@"major"],
            vermap[@"minor"],
            vermap[@"patch"]];

#else
    NSDictionary *vermap = RCTGetReactNativeVersion();
    return [NSString stringWithFormat:@"%@.%@.%@",
            vermap[RCTVersionMajor],
            vermap[RCTVersionMinor],
            vermap[RCTVersionPatch]];

#endif
}
#endif


@implementation RPRReproReactBridge

- (instancetype)init
{
    self = [super init];

    if ([Repro respondsToSelector:@selector(_passRuntimeValues:)]) {
         [Repro _passRuntimeValues:@{
             @"sub_sdk_platform":            @"react_native",
#ifndef RPR_CANT_DETERMINE_RN_VERSION
             @"sub_sdk_platform_version":    get_react_native_version(),
#endif
             @"sub_sdk_bridge_version":      [NSString stringWithUTF8String:REPRO_REACT_NATIVE_BRIDGE_VERSION],
         }];
    }

    return self;
}


RCT_EXPORT_MODULE(Repro)

- (NSDictionary *)constantsToExport
{
    return @{
        @"LOGLEVEL_DEBUG"  : @(RPRLogLevelDebug),
        @"LOGLEVEL_INFO"   : @(RPRLogLevelInfo),
        @"LOGLEVEL_WARN"   : @(RPRLogLevelWarn),
        @"LOGLEVEL_ERROR"  : @(RPRLogLevelError),
        @"LOGLEVEL_NONE"   : @(RPRLogLevelNone),

        @"GENDER_OTHER"    : @(RPRUserProfileGenderOther),
        @"GENDER_MALE"     : @(RPRUserProfileGenderMale),
        @"GENDER_FEMALE"   : @(RPRUserProfileGenderFemale),

        @"REMOTE_CONFIG_SUCCESS"            : @(RPRRemoteConfigFetchStatusSuccess),
        @"REMOTE_CONFIG_TIMEOUT_REACHED"    : @(RPRRemoteConfigFetchStatusTimeoutReached),
        @"REMOTE_CONFIG_ALREADY_FETCHED"    : @(RPRRemoteConfigFetchStatusAlreadyFetched),

        @"CAMPAIGN_TYPE_UNKNOWN"            : @(RPRCampaignTypeUnknown),
        @"CAMPAIGN_TYPE_PUSH_NOTIFICATION"  : @(RPRCampaignTypePushNotification),
        @"CAMPAIGN_TYPE_IN_APP_MESSAGE"     : @(RPRCampaignTypeInAppMessage),
        @"CAMPAIGN_TYPE_WEB_MESSAGE"        : @(RPRCampaignTypeWebMessage),
        @"CAMPAIGN_TYPE_ALL"        : @(RPRCampaignTypeAll),

        @"PREFECTURE_HOKKAIDO" : @(RPRUserProfilePrefectureHokkaido),
        @"PREFECTURE_AOMORI" : @(RPRUserProfilePrefectureAomori),
        @"PREFECTURE_IWATE" : @(RPRUserProfilePrefectureIwate),
        @"PREFECTURE_MIYAGI" : @(RPRUserProfilePrefectureMiyagi),
        @"PREFECTURE_AKITA" : @(RPRUserProfilePrefectureAkita),
        @"PREFECTURE_YAMAGATA" : @(RPRUserProfilePrefectureYamagata),
        @"PREFECTURE_FUKUSHIMA" : @(RPRUserProfilePrefectureFukushima),
        @"PREFECTURE_IBARAKI" : @(RPRUserProfilePrefectureIbaraki),
        @"PREFECTURE_TOCHIGI" : @(RPRUserProfilePrefectureTochigi),
        @"PREFECTURE_GUNMA" : @(RPRUserProfilePrefectureGunma),
        @"PREFECTURE_SAITAMA" : @(RPRUserProfilePrefectureSaitama),
        @"PREFECTURE_CHIBA" : @(RPRUserProfilePrefectureChiba),
        @"PREFECTURE_TOKYO" : @(RPRUserProfilePrefectureTokyo),
        @"PREFECTURE_KANAGAWA" : @(RPRUserProfilePrefectureKanagawa),
        @"PREFECTURE_NIIGATA" : @(RPRUserProfilePrefectureNiigata),
        @"PREFECTURE_TOYAMA" : @(RPRUserProfilePrefectureToyama),
        @"PREFECTURE_ISHIKAWA" : @(RPRUserProfilePrefectureIshikawa),
        @"PREFECTURE_FUKUI" : @(RPRUserProfilePrefectureFukui),
        @"PREFECTURE_YAMANASHI" : @(RPRUserProfilePrefectureYamanashi),
        @"PREFECTURE_NAGANO" : @(RPRUserProfilePrefectureNagano),
        @"PREFECTURE_GIFU" : @(RPRUserProfilePrefectureGifu),
        @"PREFECTURE_SHIZUOKA" : @(RPRUserProfilePrefectureShizuoka),
        @"PREFECTURE_AICHI" : @(RPRUserProfilePrefectureAichi),
        @"PREFECTURE_MIE" : @(RPRUserProfilePrefectureMie),
        @"PREFECTURE_SHIGA" : @(RPRUserProfilePrefectureShiga),
        @"PREFECTURE_KYOTO" : @(RPRUserProfilePrefectureKyoto),
        @"PREFECTURE_OSAKA" : @(RPRUserProfilePrefectureOsaka),
        @"PREFECTURE_HYOGO" : @(RPRUserProfilePrefectureHyogo),
        @"PREFECTURE_NARA" : @(RPRUserProfilePrefectureNara),
        @"PREFECTURE_WAKAYAMA" : @(RPRUserProfilePrefectureWakayama),
        @"PREFECTURE_TOTTORI" : @(RPRUserProfilePrefectureTottori),
        @"PREFECTURE_SHIMANE" : @(RPRUserProfilePrefectureShimane),
        @"PREFECTURE_OKAYAMA" : @(RPRUserProfilePrefectureOkayama),
        @"PREFECTURE_HIROSHIMA" : @(RPRUserProfilePrefectureHiroshima),
        @"PREFECTURE_YAMAGUCHI" : @(RPRUserProfilePrefectureYamaguchi),
        @"PREFECTURE_TOKUSHIMA" : @(RPRUserProfilePrefectureTokushima),
        @"PREFECTURE_KAGAWA" : @(RPRUserProfilePrefectureKagawa),
        @"PREFECTURE_EHIME" : @(RPRUserProfilePrefectureEhime),
        @"PREFECTURE_KOCHI" : @(RPRUserProfilePrefectureKochi),
        @"PREFECTURE_FUKUOKA" : @(RPRUserProfilePrefectureFukuoka),
        @"PREFECTURE_SAGA" : @(RPRUserProfilePrefectureSaga),
        @"PREFECTURE_NAGASAKI" : @(RPRUserProfilePrefectureNagasaki),
        @"PREFECTURE_KUMAMOTO" : @(RPRUserProfilePrefectureKumamoto),
        @"PREFECTURE_OITA" : @(RPRUserProfilePrefectureOita),
        @"PREFECTURE_MIYAZAKI" : @(RPRUserProfilePrefectureMiyazaki),
        @"PREFECTURE_KAGOSHIMA" : @(RPRUserProfilePrefectureKagoshima),
        @"PREFECTURE_OKINAWA" : @(RPRUserProfilePrefectureOkinawa)
    };
};

+ (BOOL)requiresMainQueueSetup
{
    return YES;  // needed because we override `constantsToExport`
}


// Opt In / Opt Out

RCT_EXPORT_METHOD(optIn:(BOOL)endUserOptedIn)
{
    [Repro optIn:endUserOptedIn];
}



// User profile

RCT_EXPORT_METHOD(setUserID:(NSString *)userID)
{
    [Repro setUserID:userID];
}

RCT_EXPORT_METHOD(getUserID:(RCTResponseSenderBlock)callback)
{
    callback(@[[NSNull null], safe_value([Repro getUserID])]);
}

RCT_EXPORT_METHOD(getDeviceID:(RCTResponseSenderBlock)callback)
{
    callback(@[[NSNull null], safe_value([Repro getDeviceID])]);
}

RCT_EXPORT_METHOD(setStringUserProfile:(NSString *)key value:(NSString *)value)
{
    [Repro setStringUserProfile:value forKey:key];
}

RCT_EXPORT_METHOD(setIntUserProfile:(NSString *)key value:(NSInteger)value)
{
    [Repro setIntUserProfile:value forKey:key];
}

RCT_EXPORT_METHOD(setDoubleUserProfile:(NSString *)key value:(double)value)
{
    [Repro setDoubleUserProfile:value forKey:key];
}

RCT_EXPORT_METHOD(setDateUserProfile:(NSString *)key value:(NSDate *)value)
{
    [Repro setDateUserProfile:value forKey:key];
}

RCT_EXPORT_METHOD(setUserGender:(RPRUserProfileGender)value)
{
    [Repro setUserGender:value];
}

RCT_EXPORT_METHOD(setUserEmailAddress:(NSString *)value)
{
    [Repro setUserEmailAddress:value];
}

RCT_EXPORT_METHOD(setUserResidencePrefecture:(RPRUserProfilePrefecture)value)
{
    [Repro setUserResidencePrefecture:value];
}

RCT_EXPORT_METHOD(setUserDateOfBirth:(NSDate *)value)
{
    [Repro setUserDateOfBirth:value];
}

RCT_EXPORT_METHOD(setUserAge:(NSInteger)value)
{
    [Repro setUserAge:value];
}


// Custom event tracking

RCT_EXPORT_METHOD(track:(NSString *)eventName properties:(NSDictionary *)props)
{
    [Repro track:eventName properties:[RCTConvert NSDictionary:props]];
}



// Standard event tracking

RCT_EXPORT_METHOD(trackViewContent:(NSString *)contentID properties:(NSDictionary *)props)
{
    RPRViewContentProperties *properties = [[RPRViewContentProperties alloc] init];

    NSObject *val = nil;
    if ((val = props[@"value"]))             { properties.value            = [RCTConvert double:val];    }
    if ((val = props[@"currency"]))          { properties.currency         = [RCTConvert NSString:val];  }
    if ((val = props[@"content_category"]))  { properties.contentCategory  = [RCTConvert NSString:val];  }
    if ((val = props[@"content_name"]))      { properties.contentName      = [RCTConvert NSString:val];  }
    if ((val = props[@"extras"]))            { properties.extras       = [RCTConvert NSDictionary:val];  }

    [Repro trackViewContent:contentID properties:properties];
}

RCT_EXPORT_METHOD(trackSearch:(NSDictionary *)props)
{
    RPRSearchProperties *properties = [[RPRSearchProperties alloc] init];

    NSObject *val = nil;
    if ((val = props[@"value"]))             { properties.value            = [RCTConvert double:val];    }
    if ((val = props[@"currency"]))          { properties.currency         = [RCTConvert NSString:val];  }
    if ((val = props[@"content_category"]))  { properties.contentCategory  = [RCTConvert NSString:val];  }
    if ((val = props[@"content_id"]))        { properties.contentID        = [RCTConvert NSString:val];  }
    if ((val = props[@"search_string"]))     { properties.searchString     = [RCTConvert NSString:val];  }
    if ((val = props[@"extras"]))            { properties.extras       = [RCTConvert NSDictionary:val];  }

    [Repro trackSearch:properties];
}



RCT_EXPORT_METHOD(trackAddToCart:(NSString *)contentID properties:(NSDictionary *)props)
{
    RPRAddToCartProperties *properties = [[RPRAddToCartProperties alloc] init];

    NSObject *val = nil;
    if ((val = props[@"value"]))             { properties.value            = [RCTConvert double:val];    }
    if ((val = props[@"currency"]))          { properties.currency         = [RCTConvert NSString:val];  }
    if ((val = props[@"content_category"]))  { properties.contentCategory  = [RCTConvert NSString:val];  }
    if ((val = props[@"content_name"]))      { properties.contentName      = [RCTConvert NSString:val];  }
    if ((val = props[@"extras"]))            { properties.extras       = [RCTConvert NSDictionary:val];  }

    [Repro trackAddToCart:contentID properties:properties];
}


RCT_EXPORT_METHOD(trackAddToWishlist:(NSDictionary *)props)
{
    RPRAddToWishlistProperties *properties = [[RPRAddToWishlistProperties alloc] init];

    NSObject *val = nil;
    if ((val = props[@"value"]))             { properties.value            = [RCTConvert double:val];    }
    if ((val = props[@"currency"]))          { properties.currency         = [RCTConvert NSString:val];  }
    if ((val = props[@"content_category"]))  { properties.contentCategory  = [RCTConvert NSString:val];  }
    if ((val = props[@"content_id"]))        { properties.contentID        = [RCTConvert NSString:val];  }
    if ((val = props[@"content_name"]))      { properties.contentName      = [RCTConvert NSString:val];  }
    if ((val = props[@"extras"]))            { properties.extras       = [RCTConvert NSDictionary:val];  }

    [Repro trackAddToWishlist:properties];
}

RCT_EXPORT_METHOD(trackInitiateCheckout:(NSDictionary *)props)
{
    RPRInitiateCheckoutProperties *properties = [[RPRInitiateCheckoutProperties alloc] init];

    NSObject *val = nil;
    if ((val = props[@"value"]))             { properties.value            = [RCTConvert double:val];    }
    if ((val = props[@"currency"]))          { properties.currency         = [RCTConvert NSString:val];  }
    if ((val = props[@"content_category"]))  { properties.contentCategory  = [RCTConvert NSString:val];  }
    if ((val = props[@"content_id"]))        { properties.contentID        = [RCTConvert NSString:val];  }
    if ((val = props[@"content_name"]))      { properties.contentName      = [RCTConvert NSString:val];  }
    if ((val = props[@"num_items"]))         { properties.numItems         = [RCTConvert NSInteger:val]; }
    if ((val = props[@"extras"]))            { properties.extras       = [RCTConvert NSDictionary:val];  }

    [Repro trackInitiateCheckout:properties];
}

RCT_EXPORT_METHOD(trackAddPaymentInfo:(NSDictionary *)props)
{
    RPRAddPaymentInfoProperties *properties = [[RPRAddPaymentInfoProperties alloc] init];

    NSObject *val = nil;
    if ((val = props[@"value"]))             { properties.value            = [RCTConvert double:val];    }
    if ((val = props[@"currency"]))          { properties.currency         = [RCTConvert NSString:val];  }
    if ((val = props[@"content_category"]))  { properties.contentCategory  = [RCTConvert NSString:val];  }
    if ((val = props[@"content_id"]))        { properties.contentID        = [RCTConvert NSString:val];  }
    if ((val = props[@"extras"]))            { properties.extras       = [RCTConvert NSDictionary:val];  }

    [Repro trackAddPaymentInfo:properties];
}

RCT_EXPORT_METHOD(trackPurchase:(NSString *)contentID value:(double)value currency:(NSString *)currency properties:(NSDictionary *)props)
{
    RPRPurchaseProperties *properties = [[RPRPurchaseProperties alloc] init];

    NSObject *val = nil;
    if ((val = props[@"content_category"]))  { properties.contentCategory  = [RCTConvert NSString:val];  }
    if ((val = props[@"content_name"]))      { properties.contentName      = [RCTConvert NSString:val];  }
    if ((val = props[@"num_items"]))         { properties.numItems         = [RCTConvert NSInteger:val]; }
    if ((val = props[@"extras"]))            { properties.extras       = [RCTConvert NSDictionary:val];  }

    [Repro trackPurchase:contentID value:value currency:currency properties:properties];
}

RCT_EXPORT_METHOD(trackShare:(NSDictionary *)props)
{
    RPRShareProperties *properties = [[RPRShareProperties alloc] init];

    NSObject *val = nil;
    if ((val = props[@"service_name"]))      { properties.serviceName      = [RCTConvert NSString:val];  }
    if ((val = props[@"content_category"]))  { properties.contentCategory  = [RCTConvert NSString:val];  }
    if ((val = props[@"content_id"]))        { properties.contentID        = [RCTConvert NSString:val];  }
    if ((val = props[@"content_name"]))      { properties.contentName      = [RCTConvert NSString:val];  }
    if ((val = props[@"extras"]))            { properties.extras       = [RCTConvert NSDictionary:val];  }

    [Repro trackShare:properties];
}

RCT_EXPORT_METHOD(trackLead:(NSDictionary *)props)
{
    RPRLeadProperties *properties = [[RPRLeadProperties alloc] init];

    NSObject *val = nil;
    if ((val = props[@"value"]))             { properties.value            = [RCTConvert double:val];    }
    if ((val = props[@"currency"]))          { properties.currency         = [RCTConvert NSString:val];  }
    if ((val = props[@"content_category"]))  { properties.contentCategory  = [RCTConvert NSString:val];  }
    if ((val = props[@"content_name"]))      { properties.contentName      = [RCTConvert NSString:val];  }
    if ((val = props[@"extras"]))            { properties.extras       = [RCTConvert NSDictionary:val];  }

    [Repro trackLead:properties];
}

RCT_EXPORT_METHOD(trackCompleteRegistration:(NSDictionary *)props)
{
    RPRCompleteRegistrationProperties *properties = [[RPRCompleteRegistrationProperties alloc] init];

    NSObject *val = nil;
    if ((val = props[@"value"]))             { properties.value            = [RCTConvert double:val];    }
    if ((val = props[@"currency"]))          { properties.currency         = [RCTConvert NSString:val];  }
    if ((val = props[@"status"]))            { properties.status           = [RCTConvert NSString:val];  }
    if ((val = props[@"content_name"]))      { properties.contentName      = [RCTConvert NSString:val];  }
    if ((val = props[@"extras"]))            { properties.extras       = [RCTConvert NSDictionary:val];  }


    [Repro trackCompleteRegistration:properties];
}

// Other


RCT_EXPORT_METHOD(_handleWebViewUrl:(NSString *)webViewRequestUrl)
{
    // NOTE: use bool return value when changing to new react native arch
    [Repro _webviewJavaScriptOpenUrlHandler:webViewRequestUrl];
}

RCT_EXPORT_METHOD(setLogLevel:(RPRLogLevel)level)
{
    [Repro setLogLevel:level];
}

RCT_EXPORT_METHOD(setPushDeviceTokenString:(NSString *)deviceTokenHexString)
{
    [Repro setPushDeviceTokenString:deviceTokenHexString];
}

RCT_EXPORT_METHOD(setPushToken:(NSString *)deviceTokenHexString)
{
    [Repro setPushDeviceTokenString:deviceTokenHexString];
}

// NOTE: Should better be called native from AppDelegate::didFinishLaunchingWithOptions
RCT_EXPORT_METHOD(setup:(NSString *)token)
{
    [Repro setup:token];
}

RCT_EXPORT_METHOD(enableInAppMessagesOnForegroundTransition)
{
    [Repro enableInAppMessagesOnForegroundTransition];
}

// NOTE: Should better be called native from AppDelegate::didFinishLaunchingWithOptions if needed
RCT_EXPORT_METHOD(disableInAppMessagesOnForegroundTransition)
{
    [Repro disableInAppMessagesOnForegroundTransition];
}

RCT_EXPORT_METHOD(getNewsFeeds:(uint64_t)limit campaignType:(nonnull NSNumber *)type callback:(RCTResponseSenderBlock)callback)
{
    NSError *error = nil;
    RPRCampaignType campaignType = RPRCampaignTypeUnknown;

    if (type) {
        if ([type isEqualToNumber:@(RPRCampaignTypePushNotification)]) {
            campaignType = RPRCampaignTypePushNotification;
        } else if ([type isEqualToNumber:@(RPRCampaignTypeInAppMessage)]) {
            campaignType = RPRCampaignTypeInAppMessage;
        } else if ([type isEqualToNumber:@(RPRCampaignTypeWebMessage)]) {
            campaignType = RPRCampaignTypeWebMessage;
        } else if ([type isEqualToNumber:@(RPRCampaignTypeAll)]) {
            campaignType = RPRCampaignTypeAll;
        }
    }

    NSArray<RPRNewsFeedEntry *> * entries = [Repro getNewsFeeds:limit campaignType:campaignType error:&error];

    if (error) {
        NSString *errString = [error localizedDescription];
        callback(@[@{ @"message": errString }, [NSNull null]]);
        return;
    }

    NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
    formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZZZ";
    formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
    formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
    formatter.calendar = [NSCalendar.alloc initWithCalendarIdentifier:NSCalendarIdentifierGregorian];

    NSMutableArray *entryJsons = [NSMutableArray.alloc initWithCapacity:entries.count];

    for(RPRNewsFeedEntry *entry in entries) {

        NSString *formattedDateString = [formatter stringFromDate:entry.deliveredAt];

        NSDictionary *entryJson = @{
            @"newsfeed_id": @(entry.ID),
            @"device_id": entry.deviceID,
            @"title": entry.title,
            @"summary": entry.summary,
            @"body": entry.body,
            @"campaign_type": @(entry.campaignType),
            @"shown": @(entry.shown),
            @"read": @(entry.read),
            @"delivered_at": formattedDateString,
            @"link_url": entry.linkUrl ? [entry.linkUrl absoluteString] : @"",
            @"link_url_string": entry.linkUrlString,
            @"image_url": entry.imageUrl ? [entry.imageUrl absoluteString] : @"",
            @"image_url_string": entry.imageUrlString,
        };

        [entryJsons addObject:entryJson];
    }

    callback(@[[NSNull null], safe_value(entryJsons)]);
}

RCT_EXPORT_METHOD(getNewsFeedsFor:(uint64_t)limit offsetID:(uint64_t)offsetID campaignType:(nonnull NSNumber *)type callback:(RCTResponseSenderBlock)callback)
{
    NSError *error = nil;
    RPRCampaignType campaignType = RPRCampaignTypeUnknown;

    if (type) {
        if ([type isEqualToNumber:@(RPRCampaignTypePushNotification)]) {
            campaignType = RPRCampaignTypePushNotification;
        } else if ([type isEqualToNumber:@(RPRCampaignTypeInAppMessage)]) {
            campaignType = RPRCampaignTypeInAppMessage;
        } else if ([type isEqualToNumber:@(RPRCampaignTypeWebMessage)]) {
            campaignType = RPRCampaignTypeWebMessage;
        } else if ([type isEqualToNumber:@(RPRCampaignTypeAll)]) {
            campaignType = RPRCampaignTypeAll;
        }
    }

    NSArray<RPRNewsFeedEntry *> *entries = [Repro getNewsFeeds:limit offsetID:offsetID campaignType:campaignType error:&error];

    if (error) {
        NSString *errString = [error localizedDescription];
        callback(@[@{ @"message": errString }, [NSNull null]]);
        return;
    }

    NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
    formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZZZ";
    formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];
    formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
    formatter.calendar = [NSCalendar.alloc initWithCalendarIdentifier:NSCalendarIdentifierGregorian];

    NSMutableArray *entryJsons = [NSMutableArray.alloc initWithCapacity:entries.count];

    for(RPRNewsFeedEntry *entry in entries) {

        NSString *formattedDateString = [formatter stringFromDate:entry.deliveredAt];

        NSDictionary *entryJson = @{
            @"newsfeed_id": @(entry.ID),
            @"device_id": entry.deviceID,
            @"title": entry.title,
            @"summary": entry.summary,
            @"body": entry.body,
            @"campaign_type": @(entry.campaignType),
            @"shown": @(entry.shown),
            @"read": @(entry.read),
            @"delivered_at": formattedDateString,
            @"link_url": entry.linkUrl ? [entry.linkUrl absoluteString] : @"",
            @"link_url_string": entry.linkUrlString,
            @"image_url": entry.imageUrl ? [entry.imageUrl absoluteString] : @"",
            @"image_url_string": entry.imageUrlString,
        };

        [entryJsons addObject:entryJson];
    }

    callback(@[[NSNull null], safe_value(entryJsons)]);
}

RCT_EXPORT_METHOD(updateNewsFeeds:(NSArray<NSDictionary *> *)entryJsons callback:(RCTResponseSenderBlock)callback)
{
    NSMutableArray<RPRNewsFeedEntry *> *entries = [NSMutableArray.alloc initWithCapacity:entryJsons.count];

    for(NSDictionary *entryJson in entryJsons) {
        RPRNewsFeedEntry *entry = [RPRNewsFeedEntry.alloc initWithDictionary: entryJson];
        [entries addObject:entry];
    }

    NSError *error = nil;
    [Repro updateNewsFeeds:entries error:&error];

    if (error) {
        NSString *errString = [error localizedDescription];
        callback(@[@{ @"message": errString }]);
        return;
    }

    callback(@[[NSNull null]]);
}

RCT_EXPORT_METHOD(setSilverEggProdKey:(NSString *)silverEggProdKey)
{
    if (silverEggProdKey == nil) {
        return;
    }

    [Repro setSilverEggProdKey:silverEggProdKey];
}


RCT_EXPORT_METHOD(setSilverEggCookie:(NSString *)silverEggCookie)
{
    if (silverEggCookie == nil) {
        return;
    }

    [Repro setSilverEggCookie:silverEggCookie];
}

RCT_EXPORT_METHOD(linkLineID:(NSString *)lineUserId lineChannelID:(NSString *)lineChannelId)
{
    [Repro linkLineID:lineUserId lineChannelID:lineChannelId];
}

RCT_EXPORT_METHOD(unlinkLineID:(NSString *)lineUserId lineChannelID:(NSString *)lineChannelId)
{
    [Repro unlinkLineID:lineUserId lineChannelID:lineChannelId];
}

@end



@implementation RPREventEmitterOpenUrl

RCT_EXPORT_MODULE()

- (NSArray<NSString*> *)supportedEvents
{
    return @[@"__RPR__openUrlCallback"];
}

- (void)startObserving
{
    [Repro setOpenUrlCallback:^(NSURL * _Nonnull url) {
        [self sendEventWithName:@"__RPR__openUrlCallback" body:url.absoluteString];
    }];
}

- (void)stopObserving
{
    [Repro setOpenUrlCallback:nil];
}

@end



