#import "Topon.h"

#import <UIKit/UIKit.h>
#import <AnyThinkBanner/AnyThinkBanner.h>
#import <AnyThinkInterstitial/AnyThinkInterstitial.h>
#import <AnyThinkNative/AnyThinkNative.h>
#import <AnyThinkRewardedVideo/AnyThinkRewardedVideo.h>
#import <AnyThinkSDK/AnyThinkSDK.h>
#import <AnyThinkSplash/AnyThinkSplash.h>
#import <React/RCTLog.h>
#import <React/RCTUIManager.h>
#import <React/RCTUtils.h>
#import <objc/message.h>

#import "NSDictionary+String.h"
#import "NSJSONSerialization+String.h"
#import "ToponNativeAdView.h"

static NSString *const kToponRewardedLoaded = @"ATRewardedVideoLoaded";
static NSString *const kToponRewardedLoadFail = @"ATRewardedVideoLoadFail";
static NSString *const kToponRewardedPlayStart = @"ATRewardedVideoPlayStart";
static NSString *const kToponRewardedPlayEnd = @"ATRewardedVideoPlayEnd";
static NSString *const kToponRewardedPlayFail = @"ATRewardedVideoPlayFail";
static NSString *const kToponRewardedClose = @"ATRewardedVideoClose";
static NSString *const kToponRewardedClick = @"ATRewardedVideoClick";
static NSString *const kToponRewardedReward = @"ATRewardedVideoReward";

static NSString *const kToponInterstitialLoaded = @"ATInterstitialLoaded";
static NSString *const kToponInterstitialLoadFail = @"ATInterstitialLoadFail";
static NSString *const kToponInterstitialShow = @"ATInterstitialAdShow";
static NSString *const kToponInterstitialPlayStart = @"ATInterstitialPlayStart";
static NSString *const kToponInterstitialPlayEnd = @"ATInterstitialPlayEnd";
static NSString *const kToponInterstitialPlayFail = @"ATInterstitialPlayFail";
static NSString *const kToponInterstitialClose = @"ATInterstitialClose";
static NSString *const kToponInterstitialClick = @"ATInterstitialClick";

static NSString *const kToponBannerLoaded = @"ATBannerLoaded";
static NSString *const kToponBannerLoadFail = @"ATBannerLoadFail";
static NSString *const kToponBannerClose = @"ATBannerCloseButtonTapped";
static NSString *const kToponBannerClick = @"ATBannerClick";
static NSString *const kToponBannerShow = @"ATBannerShow";
static NSString *const kToponBannerRefresh = @"ATBannerRefresh";
static NSString *const kToponBannerRefreshFail = @"ATBannerRefreshFail";

static NSString *const kToponSplashLoaded = @"ATSplashLoaded";
static NSString *const kToponSplashLoadFail = @"ATSplashLoadFail";
static NSString *const kToponSplashTimeout = @"ATSplashTimeout";
static NSString *const kToponSplashShow = @"ATSplashShow";
static NSString *const kToponSplashClick = @"ATSplashClick";
static NSString *const kToponSplashClose = @"ATSplashClose";

static NSString *const kToponNativeLoaded = @"ATNativeLoaded";
static NSString *const kToponNativeLoadFail = @"ATNativeLoadFail";
static NSString *const kToponNativeShow = @"ATNativeShow";
static NSString *const kToponNativeClick = @"ATNativeClick";
static NSString *const kToponNativeClose = @"ATNativeClose";
static NSString *const kToponNativeVideoStart = @"ATNativeVideoStart";
static NSString *const kToponNativeVideoEnd = @"ATNativeVideoEnd";
static NSString *const kToponNativeVideoProgress = @"ATNativeVideoProgress";

static NSString *const kToponBannerPositionTop = @"top";
static NSString *const kToponBannerPositionBottom = @"bottom";
static NSString *const kToponBannerAdaptiveWidthKey = @"adaptive_width";
static NSString *const kToponBannerAdaptiveOrientationKey = @"adaptive_orientation";

static NSString *const kToponInterstitialUseRewardedAsInterstitialKey = @"UseRewardedVideoAsInterstitial";

static NSDictionary *ToponParseJSONDictionary(NSString *jsonString) {
  if (jsonString.length == 0) {
    return nil;
  }
  id value = [NSJSONSerialization topon_JSONObjectWithString:jsonString
                                                    options:NSJSONReadingAllowFragments
                                                      error:nil];
  if ([value isKindOfClass:[NSDictionary class]]) {
    return (NSDictionary *)value;
  }
  return nil;
}

static UIViewController *ToponCurrentViewController(void) {
  UIViewController *controller = RCTPresentedViewController();
  if (controller != nil) {
    return controller;
  }
  UIWindow *keyWindow = nil;
  if (@available(iOS 13.0, *)) {
    for (UIWindowScene *scene in UIApplication.sharedApplication.connectedScenes) {
      if (scene.activationState == UISceneActivationStateForegroundActive) {
        for (UIWindow *window in scene.windows) {
          if (window.isKeyWindow) {
            keyWindow = window;
            break;
          }
        }
      }
      if (keyWindow != nil) {
        break;
      }
    }
  }
  keyWindow = keyWindow ?: UIApplication.sharedApplication.keyWindow;
  return keyWindow.rootViewController;
}

static UIView *ToponContainerView(void) {
  UIViewController *controller = ToponCurrentViewController();
  return controller.view ?: UIApplication.sharedApplication.keyWindow.rootViewController.view;
}

static UIEdgeInsets ToponSafeAreaInsets(void) {
  UIView *view = ToponContainerView();
  if (@available(iOS 11.0, *)) {
    return view.safeAreaInsets;
  }
  return UIEdgeInsetsZero;
}

static CGRect ToponRectFromJSONString(NSString *rectJson) {
  CGRect rect = CGRectZero;
  NSDictionary *rectDict = ToponParseJSONDictionary(rectJson);
  if ([rectDict isKindOfClass:[NSDictionary class]]) {
    BOOL usesPixel = [rectDict[@"usesPixel"] boolValue];
    CGFloat scale = usesPixel ? UIScreen.mainScreen.nativeScale : 1.0f;
    rect = CGRectMake(
      [rectDict[@"x"] doubleValue] / scale,
      [rectDict[@"y"] doubleValue] / scale,
      [rectDict[@"width"] doubleValue] / scale,
      [rectDict[@"height"] doubleValue] / scale
    );
  }
  return rect;
}

static NSDictionary *ToponBannerExtraFromJSONString(NSString *settingsJson) {
  NSMutableDictionary *extra = [NSMutableDictionary dictionary];
  NSDictionary *settings = ToponParseJSONDictionary(settingsJson);
  if (![settings isKindOfClass:[NSDictionary class]]) {
    return extra.count > 0 ? extra : nil;
  }
  BOOL usesPixel = [settings[@"usesPixel"] boolValue];
  CGFloat scale = usesPixel ? UIScreen.mainScreen.nativeScale : 1.0f;
  NSNumber *width = settings[@"width"];
  NSNumber *height = settings[@"height"];
  if (width != nil && height != nil) {
    CGSize size = CGSizeMake(width.doubleValue / scale, height.doubleValue / scale);
    extra[kATAdLoadingExtraBannerAdSizeKey] = [NSValue valueWithCGSize:size];
  }
  NSNumber *adaptiveWidth = settings[kToponBannerAdaptiveWidthKey];
  NSNumber *adaptiveOrientation = settings[kToponBannerAdaptiveOrientationKey];
  if (adaptiveWidth != nil && adaptiveOrientation != nil) {
    // Reserved for AdMob adaptive banner; actual size calculation can be filled when needed.
  }
  if (extra[kATAdLoadingExtraBannerAdSizeKey] == nil) {
    extra[kATAdLoadingExtraBannerAdSizeKey] = [NSValue valueWithCGSize:CGSizeMake(320.0, 50.0)];
  }
  return extra;
}

static void ToponApplyBannerScenario(ATBannerView *bannerView, NSString *scenario) {
  if (scenario.length == 0 || bannerView == nil) {
    return;
  }
  SEL selector = NSSelectorFromString(@"setScenario:");
  if ([bannerView respondsToSelector:selector]) {
    void (*func)(id, SEL, NSString *) = (void (*)(id, SEL, NSString *))[bannerView methodForSelector:selector];
    func(bannerView, selector, scenario);
  }
}

static NSString *ToponJSONStringFromAdStatus(ATCheckLoadModel *model) {
  if (model == nil) {
    return @"";
  }
  NSMutableDictionary *status = [NSMutableDictionary dictionary];
  status[@"isLoading"] = @(model.isLoading);
  status[@"isReady"] = @(model.isReady);
  if (model.adOfferInfo != nil) {
    status[@"adInfo"] = model.adOfferInfo;
  }
  return [status topon_adInfoJSONString];
}

static NSDictionary *ToponNativeMaterialToDictionary(ATNativeAdOffer *offer) {
  if (offer == nil || offer.nativeAd == nil) {
    return @{};
  }
  ATNativeAd *nativeAd = offer.nativeAd;
  NSMutableDictionary *material = [NSMutableDictionary dictionary];
  if (nativeAd.title.length > 0) {
    material[@"title"] = nativeAd.title;
  }
  if (nativeAd.mainText.length > 0) {
    material[@"desc"] = nativeAd.mainText;
  }
  if (nativeAd.ctaText.length > 0) {
    material[@"cta"] = nativeAd.ctaText;
  }
  if (nativeAd.iconUrl.length > 0) {
    material[@"iconUrl"] = nativeAd.iconUrl;
  }
  if (nativeAd.imageUrl.length > 0) {
    material[@"mainImageUrl"] = nativeAd.imageUrl;
  }
  if (nativeAd.advertiser.length > 0) {
    material[@"advertiser"] = nativeAd.advertiser;
  }
  if (nativeAd.rating != nil) {
    material[@"rating"] = nativeAd.rating;
  }
  return material;
}

@interface Topon () <ATRewardedVideoDelegate, ATInterstitialDelegate, ATBannerDelegate, ATSplashDelegate, ATNativeADDelegate>
@property(nonatomic, strong) NSMutableDictionary<NSString *, ATBannerView *> *bannerViews;
@property(nonatomic, strong) NSMutableSet<NSString *> *rewardedPlacements;
@property(nonatomic, strong) NSMutableSet<NSString *> *interstitialPlacements;
@property(nonatomic, strong) NSMutableSet<NSString *> *bannerPlacements;
@property(nonatomic, strong) NSMutableSet<NSString *> *splashPlacements;
@property(nonatomic, strong) NSMutableSet<NSString *> *nativePlacements;
@property(nonatomic, strong) NSMutableDictionary<NSString *, ATNativeADView *> *nativeAdViews;
@property(nonatomic, strong) NSMutableDictionary<NSString *, UIView *> *nativeParentViews;
@property(nonatomic, assign) NSInteger listenerCount;
@end

@implementation Topon

- (instancetype)init {
  self = [super init];
  if (self != nil) {
    _bannerViews = [NSMutableDictionary dictionary];
    _rewardedPlacements = [NSMutableSet set];
    _interstitialPlacements = [NSMutableSet set];
    _bannerPlacements = [NSMutableSet set];
    _splashPlacements = [NSMutableSet set];
    _nativePlacements = [NSMutableSet set];
    _nativeAdViews = [NSMutableDictionary dictionary];
    _nativeParentViews = [NSMutableDictionary dictionary];
  }
  return self;
}

+ (BOOL)requiresMainQueueSetup {
  return YES;
}

- (dispatch_queue_t)methodQueue {
  return dispatch_get_main_queue();
}

- (NSArray<NSString *> *)supportedEvents {
  return @[
    kToponRewardedLoaded,
    kToponRewardedLoadFail,
    kToponRewardedPlayStart,
    kToponRewardedPlayEnd,
    kToponRewardedPlayFail,
    kToponRewardedClose,
    kToponRewardedClick,
    kToponRewardedReward,
    kToponInterstitialLoaded,
    kToponInterstitialLoadFail,
    kToponInterstitialShow,
    kToponInterstitialPlayStart,
    kToponInterstitialPlayEnd,
    kToponInterstitialPlayFail,
    kToponInterstitialClose,
    kToponInterstitialClick,
    kToponBannerLoaded,
    kToponBannerLoadFail,
    kToponBannerClose,
    kToponBannerClick,
    kToponBannerShow,
    kToponBannerRefresh,
    kToponBannerRefreshFail,
    kToponSplashLoaded,
    kToponSplashLoadFail,
    kToponSplashTimeout,
    kToponSplashShow,
    kToponSplashClick,
    kToponSplashClose,
    kToponNativeLoaded,
    kToponNativeLoadFail,
    kToponNativeShow,
    kToponNativeClick,
    kToponNativeClose,
    kToponNativeVideoStart,
    kToponNativeVideoEnd,
    kToponNativeVideoProgress
  ];
}

- (void)addListener:(NSString *)eventType {
  [super addListener:eventType];
  self.listenerCount += 1;
}

- (void)removeListeners:(double)count {
  [super removeListeners:count];
  self.listenerCount = MAX(0, self.listenerCount - (NSInteger)count);
}

- (void)emitEvent:(NSString *)eventName body:(NSDictionary *)body {
  if (self.listenerCount <= 0 || eventName.length == 0) {
    return;
  }
  [self sendEventWithName:eventName body:body ?: @{}];
}

- (void)emitEvent:(NSString *)eventName
      placementId:(NSString *)placementId
            extra:(NSDictionary *)extra
            error:(NSString *)errorMessage {
  NSMutableDictionary *payload = [NSMutableDictionary dictionary];
  payload[kToponCallbackPlacementIdKey] = placementId ?: @"";
  NSString *extraString = [extra topon_jsonString];
  if (extraString.length > 0) {
    payload[kToponCallbackExtraKey] = extraString;
  }
  if (errorMessage.length > 0) {
    payload[kToponCallbackErrorKey] = errorMessage;
  }
  [self emitEvent:eventName body:payload];
}

- (void)emitNativeEvent:(NSString *)eventName
            placementId:(NSString *)placementId
                 adInfo:(NSDictionary *)adInfo
             adMaterial:(NSDictionary *)adMaterial
                  error:(NSString *)errorMessage {
  NSMutableDictionary *payload = [NSMutableDictionary dictionary];
  payload[kToponCallbackPlacementIdKey] = placementId ?: @"";
  NSString *extraString = [adInfo topon_jsonString];
  if (extraString.length > 0) {
    payload[kToponCallbackExtraKey] = extraString;
  }
  NSString *materialString = [adMaterial topon_jsonString];
  if (materialString.length > 0) {
    payload[@"adMaterial"] = materialString;
  }
  if (errorMessage.length > 0) {
    payload[kToponCallbackErrorKey] = errorMessage;
  }
  [self emitEvent:eventName body:payload];
}

#pragma mark - SDK level APIs

- (void)init:(NSString *)appId appKey:(NSString *)appKey {
  if (![appId isKindOfClass:[NSString class]] || ![appKey isKindOfClass:[NSString class]]) {
    RCTLogError(@"[Topon] Invalid appId/appKey.");
    return;
  }
  NSError *error = nil;
  BOOL success = [[ATAPI sharedInstance] startWithAppID:appId appKey:appKey error:&error];
  if (!success && error != nil) {
    RCTLogError(@"[Topon] Failed to init SDK: %@", error);
  }
}

- (void)getSDKVersionName:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
  resolve([[ATAPI sharedInstance] version] ?: @"");
}

- (void)isCnSDK:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
  BOOL isCn = NO;
  ATAPI *api = [ATAPI sharedInstance];
  SEL selector = NSSelectorFromString(@"isCnSDK");
  if ([api respondsToSelector:selector]) {
    BOOL (*func)(id, SEL) = (BOOL (*)(id, SEL))[api methodForSelector:selector];
    isCn = func(api, selector);
  } else {
    selector = NSSelectorFromString(@"isCN");
    if ([api respondsToSelector:selector]) {
      BOOL (*func)(id, SEL) = (BOOL (*)(id, SEL))[api methodForSelector:selector];
      isCn = func(api, selector);
    } else if ([[api class] respondsToSelector:selector]) {
      BOOL (*func)(id, SEL) = (BOOL (*)(id, SEL))[[api class] methodForSelector:selector];
      isCn = func([api class], selector);
    }
  }
  if (!isCn) {
    @try {
      id area = [api valueForKey:@"area"];
      if ([area isKindOfClass:[NSString class]]) {
        NSString *areaString = [(NSString *)area lowercaseString];
        isCn = [areaString containsString:@"cn"];
      }
    } @catch (__unused NSException *exception) {
    }
  }
  resolve(@(isCn));
}

- (void)setExcludeMyOfferPkgList:(NSArray *)packages {
  if (![packages isKindOfClass:[NSArray class]]) {
    return;
  }
  [[ATAPI sharedInstance] setExludeAppleIdArray:packages];
}

- (void)initCustomMap:(NSString *)customMapJson {
  NSDictionary *customData = ToponParseJSONDictionary(customMapJson);
  if ([customData isKindOfClass:[NSDictionary class]]) {
    [[ATAPI sharedInstance] setCustomData:customData];
  }
}

- (void)setPlacementCustomMap:(NSString *)placementId customMapJson:(NSString *)customMapJson {
  NSDictionary *customData = ToponParseJSONDictionary(customMapJson);
  if (placementId.length == 0 || ![customData isKindOfClass:[NSDictionary class]]) {
    return;
  }
  [[ATAPI sharedInstance] setCustomData:customData forPlacementID:placementId];
}

- (void)setGDPRLevel:(double)level {
  NSInteger gdprLevel = (NSInteger)level;
  [[ATAPI sharedInstance] setDataConsentSet:2 - gdprLevel consentString:nil];
}

- (void)getGDPRLevel:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
  resolve(@(2 - [[ATAPI sharedInstance] dataConsentSet]));
}

- (void)getUserLocation:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
  [[ATAPI sharedInstance] getUserLocationWithCallback:^(ATUserLocation location) {
    NSInteger result = (location == 1) ? 1 : 2;
    resolve(@(result));
  }];
}

- (void)showGDPRAuth {
  UIViewController *controller = ToponCurrentViewController();
  if (controller == nil) {
    RCTLogWarn(@"[Topon] Unable to present GDPR auth without root view controller.");
    return;
  }
  [[ATAPI sharedInstance] presentDataConsentDialogInViewController:controller
                                        loadingFailureCallback:^(NSError *error) {
                                          RCTLogError(@"[Topon] Failed to load GDPR dialog: %@", error);
                                        }
                                               dismissalCallback:^{}];
}

- (void)showGDPRConsentDialog:(NSString *)appId
                      resolve:(RCTPromiseResolveBlock)resolve
                       reject:(RCTPromiseRejectBlock)reject {
  [self presentGDPRConsentDialogWithResolve:resolve reject:reject];
}

- (void)showGDPRConsentSecondDialog:(NSString *)appId
                            resolve:(RCTPromiseResolveBlock)resolve
                             reject:(RCTPromiseRejectBlock)reject {
  [self presentGDPRConsentDialogWithResolve:resolve reject:reject];
}

- (void)setUMPTestDeviceId:(NSString *)deviceId {
  RCTLogWarn(@"[Topon] setUMPTestDeviceId is Android-only.");
}

- (void)presentGDPRConsentDialogWithResolve:(RCTPromiseResolveBlock)resolve
                                     reject:(RCTPromiseRejectBlock)reject {
  __block BOOL settled = NO;
  UIViewController *controller = ToponCurrentViewController();
  if (controller == nil) {
    settled = YES;
    reject(@"NO_VIEW_CONTROLLER", @"Unable to present GDPR consent dialog.", nil);
    return;
  }
  [[ATAPI sharedInstance] presentDataConsentDialogInViewController:controller
                                        loadingFailureCallback:^(NSError *error) {
                                          if (settled) {
                                            return;
                                          }
                                          settled = YES;
                                          reject(@"GDPR_CONSENT_ERROR", @"Failed to load GDPR dialog.", error);
                                        }
                                               dismissalCallback:^{
                                                 if (settled) {
                                                   return;
                                                 }
                                                 settled = YES;
                                                 NSDictionary *payload = @{
                                                   @"gdprLevel": @(2 - [[ATAPI sharedInstance] dataConsentSet])
                                                 };
                                                 resolve([payload topon_jsonString]);
                                               }];
}

- (void)setLogDebug:(BOOL)isDebug {
  [ATAPI setLogEnabled:isDebug];
}

- (void)deniedUploadDeviceInfo:(NSArray *)keys {
  if (![keys isKindOfClass:[NSArray class]]) {
    return;
  }
  [[ATAPI sharedInstance] setDeniedUploadInfoArray:keys];
}

#pragma mark - Rewarded Video

- (void)rewardedLoadAd:(NSString *)placementId settingsJson:(NSString *)settingsJson {
  if (placementId.length > 0) {
    [self.rewardedPlacements addObject:placementId];
  }
  NSMutableDictionary *extra = nil;
  NSDictionary *settings = ToponParseJSONDictionary(settingsJson);
  if ([settings isKindOfClass:[NSDictionary class]]) {
    extra = [settings mutableCopy];
    if (![extra[kATAdLoadingExtraMediaExtraKey] isKindOfClass:[NSDictionary class]]) {
      [extra removeObjectForKey:kATAdLoadingExtraMediaExtraKey];
    }
  }
  [[ATAdManager sharedManager] loadADWithPlacementID:placementId
                                               extra:extra
                                            delegate:self];
}

- (void)rewardedShowAd:(NSString *)placementId {
  [self rewardedShowAdInScenario:placementId scenario:nil];
}

- (void)rewardedShowAdInScenario:(NSString *)placementId scenario:(NSString *)scenario {
  UIViewController *controller = ToponCurrentViewController();
  if (controller == nil) {
    [self emitEvent:kToponRewardedPlayFail
        placementId:placementId
              extra:nil
              error:@"No active UIViewController to present rewarded video."];
    return;
  }
  if (scenario.length > 0) {
    [[ATAdManager sharedManager] showRewardedVideoWithPlacementID:placementId
                                                            scene:scenario
                                               inViewController:controller
                                                       delegate:self];
  } else {
    [[ATAdManager sharedManager] showRewardedVideoWithPlacementID:placementId
                                               inViewController:controller
                                                       delegate:self];
  }
}

- (void)rewardedHasAdReady:(NSString *)placementId
                   resolve:(RCTPromiseResolveBlock)resolve
                    reject:(RCTPromiseRejectBlock)reject {
  BOOL ready = [[ATAdManager sharedManager] rewardedVideoReadyForPlacementID:placementId];
  resolve(@(ready));
}

- (void)rewardedCheckAdStatus:(NSString *)placementId
                      resolve:(RCTPromiseResolveBlock)resolve
                       reject:(RCTPromiseRejectBlock)reject {
  ATCheckLoadModel *model = [[ATAdManager sharedManager] checkRewardedVideoLoadStatusForPlacementID:placementId];
  resolve(ToponJSONStringFromAdStatus(model));
}

#pragma mark ATRewardedVideoDelegate

- (void)didFailToLoadADWithPlacementID:(NSString *)placementID error:(NSError *)error {
  NSString *message = error.localizedDescription ?: @"";
  if ([self.rewardedPlacements containsObject:placementID]) {
    [self emitEvent:kToponRewardedLoadFail placementId:placementID extra:nil error:message];
  } else if ([self.interstitialPlacements containsObject:placementID]) {
    [self emitEvent:kToponInterstitialLoadFail placementId:placementID extra:nil error:message];
  } else if ([self.bannerPlacements containsObject:placementID]) {
    [self emitEvent:kToponBannerLoadFail placementId:placementID extra:nil error:message];
  } else if ([self.splashPlacements containsObject:placementID]) {
    [self emitEvent:kToponSplashLoadFail placementId:placementID extra:nil error:message];
  } else if ([self.nativePlacements containsObject:placementID]) {
    [self emitEvent:kToponNativeLoadFail placementId:placementID extra:nil error:message];
  }
}

- (void)didFinishLoadingADWithPlacementID:(NSString *)placementID {
  if ([self.rewardedPlacements containsObject:placementID]) {
    [self emitEvent:kToponRewardedLoaded placementId:placementID extra:nil error:nil];
  } else if ([self.interstitialPlacements containsObject:placementID]) {
    [self emitEvent:kToponInterstitialLoaded placementId:placementID extra:nil error:nil];
  } else if ([self.bannerPlacements containsObject:placementID]) {
    [self emitEvent:kToponBannerLoaded placementId:placementID extra:nil error:nil];
  } else if ([self.nativePlacements containsObject:placementID]) {
    [self emitEvent:kToponNativeLoaded placementId:placementID extra:nil error:nil];
  }
}

- (void)rewardedVideoDidStartPlayingForPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponRewardedPlayStart placementId:placementID extra:extra error:nil];
}

- (void)rewardedVideoDidEndPlayingForPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponRewardedPlayEnd placementId:placementID extra:extra error:nil];
}

- (void)rewardedVideoDidFailToPlayForPlacementID:(NSString *)placementID
                                           error:(NSError *)error
                                           extra:(NSDictionary *)extra {
  [self emitEvent:kToponRewardedPlayFail
      placementId:placementID
            extra:extra
            error:error.localizedDescription];
}

- (void)rewardedVideoDidCloseForPlacementID:(NSString *)placementID
                                   rewarded:(BOOL)rewarded
                                      extra:(NSDictionary *)extra {
  [self emitEvent:kToponRewardedClose placementId:placementID extra:extra error:nil];
}

- (void)rewardedVideoDidClickForPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponRewardedClick placementId:placementID extra:extra error:nil];
}

- (void)rewardedVideoDidRewardSuccessForPlacemenID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponRewardedReward placementId:placementID extra:extra error:nil];
}

#pragma mark - Interstitial

- (void)interstitialLoadAd:(NSString *)placementId settingsJson:(NSString *)settingsJson {
  if (placementId.length > 0) {
    [self.interstitialPlacements addObject:placementId];
  }
  NSDictionary *settings = ToponParseJSONDictionary(settingsJson);
  NSDictionary *extra = nil;
  NSNumber *useRewarded = settings[kToponInterstitialUseRewardedAsInterstitialKey];
  if ([useRewarded isKindOfClass:[NSNumber class]]) {
    extra = @{kATInterstitialExtraUsesRewardedVideo: @([useRewarded boolValue])};
  }
  [[ATAdManager sharedManager] loadADWithPlacementID:placementId
                                               extra:extra
                                            delegate:self];
}

- (void)interstitialShowAd:(NSString *)placementId {
  [self interstitialShowAdInScenario:placementId scenario:nil];
}

- (void)interstitialShowAdInScenario:(NSString *)placementId scenario:(NSString *)scenario {
  UIViewController *controller = ToponCurrentViewController();
  if (controller == nil) {
    [self emitEvent:kToponInterstitialPlayFail
        placementId:placementId
              extra:nil
              error:@"No active UIViewController to present interstitial."];
    return;
  }
  if (scenario.length > 0) {
    [[ATAdManager sharedManager] showInterstitialWithPlacementID:placementId
                                                           scene:scenario
                                              inViewController:controller
                                                      delegate:self];
  } else {
    [[ATAdManager sharedManager] showInterstitialWithPlacementID:placementId
                                              inViewController:controller
                                                      delegate:self];
  }
}

- (void)interstitialHasAdReady:(NSString *)placementId
                       resolve:(RCTPromiseResolveBlock)resolve
                        reject:(RCTPromiseRejectBlock)reject {
  BOOL ready = [[ATAdManager sharedManager] interstitialReadyForPlacementID:placementId];
  resolve(@(ready));
}

- (void)interstitialCheckAdStatus:(NSString *)placementId
                          resolve:(RCTPromiseResolveBlock)resolve
                           reject:(RCTPromiseRejectBlock)reject {
  ATCheckLoadModel *model = [[ATAdManager sharedManager] checkInterstitialLoadStatusForPlacementID:placementId];
  resolve(ToponJSONStringFromAdStatus(model));
}

#pragma mark ATInterstitialDelegate

- (void)interstitialDidShowForPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponInterstitialShow placementId:placementID extra:extra error:nil];
}

- (void)interstitialDidClickForPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponInterstitialClick placementId:placementID extra:extra error:nil];
}

- (void)interstitialDidCloseForPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponInterstitialClose placementId:placementID extra:extra error:nil];
}

- (void)interstitialDidStartPlayingVideoForPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponInterstitialPlayStart placementId:placementID extra:extra error:nil];
}

- (void)interstitialDidEndPlayingVideoForPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponInterstitialPlayEnd placementId:placementID extra:extra error:nil];
}

- (void)interstitialDidFailToPlayVideoForPlacementID:(NSString *)placementID
                                               error:(NSError *)error
                                               extra:(NSDictionary *)extra {
  [self emitEvent:kToponInterstitialPlayFail placementId:placementID extra:extra error:error.localizedDescription];
}

- (void)interstitialFailedToShowForPlacementID:(NSString *)placementID
                                         error:(NSError *)error
                                         extra:(NSDictionary *)extra {
  [self emitEvent:kToponInterstitialPlayFail placementId:placementID extra:extra error:error.localizedDescription];
}

#pragma mark - Banner

- (void)bannerLoadAd:(NSString *)placementId settingsJson:(NSString *)settingsJson {
  if (placementId.length > 0) {
    [self.bannerPlacements addObject:placementId];
  }
  NSDictionary *extra = ToponBannerExtraFromJSONString(settingsJson);
  [[ATAdManager sharedManager] loadADWithPlacementID:placementId
                                               extra:extra
                                            delegate:self];
}

- (void)bannerShowAdInRectangle:(NSString *)placementId rectJson:(NSString *)rectJson {
  [self bannerShowAdInRectangleAndScenario:placementId rectJson:rectJson scenario:nil];
}

- (void)bannerShowAdInPosition:(NSString *)placementId position:(NSString *)position {
  [self bannerShowAdInPositionAndScenario:placementId position:position scenario:nil];
}

- (void)bannerShowAdInRectangleAndScenario:(NSString *)placementId
                                  rectJson:(NSString *)rectJson
                                  scenario:(NSString *)scenario {
  dispatch_async(dispatch_get_main_queue(), ^{
    ATBannerView *bannerView = [[ATAdManager sharedManager] retrieveBannerViewForPlacementID:placementId];
    if (bannerView == nil) {
      RCTLogWarn(@"[Topon] Banner view is nil for placement %@", placementId);
      return;
    }
    UIView *container = ToponContainerView();
    if (container == nil) {
      RCTLogWarn(@"[Topon] Unable to find a container view for banner placement %@", placementId);
      return;
    }
    ToponApplyBannerScenario(bannerView, scenario);
    bannerView.delegate = self;
    bannerView.frame = ToponRectFromJSONString(rectJson);
    [self.bannerViews[placementId] removeFromSuperview];
    self.bannerViews[placementId] = bannerView;
    [container addSubview:bannerView];
  });
}

- (void)bannerShowAdInPositionAndScenario:(NSString *)placementId
                                 position:(NSString *)position
                                 scenario:(NSString *)scenario {
  dispatch_async(dispatch_get_main_queue(), ^{
    ATBannerView *bannerView = [[ATAdManager sharedManager] retrieveBannerViewForPlacementID:placementId];
    if (bannerView == nil) {
      RCTLogWarn(@"[Topon] Banner view is nil for placement %@", placementId);
      return;
    }
    UIView *container = ToponContainerView();
    if (container == nil) {
      RCTLogWarn(@"[Topon] Unable to find a container view for banner placement %@", placementId);
      return;
    }
    ToponApplyBannerScenario(bannerView, scenario);
    bannerView.delegate = self;
    CGSize viewSize = bannerView.bounds.size;
    CGRect bounds = container.bounds;
    CGFloat x = (CGRectGetWidth(bounds) - viewSize.width) / 2.0;
    UIEdgeInsets safeInsets = ToponSafeAreaInsets();
    CGFloat y = safeInsets.bottom;
    if ([position isEqualToString:kToponBannerPositionTop]) {
      y = safeInsets.top;
    } else if ([position isEqualToString:kToponBannerPositionBottom]) {
      y = CGRectGetHeight(bounds) - safeInsets.bottom - viewSize.height;
    } else {
      y = CGRectGetHeight(bounds) - safeInsets.bottom - viewSize.height;
    }
    bannerView.frame = CGRectMake(x, y, viewSize.width, viewSize.height);
    [self.bannerViews[placementId] removeFromSuperview];
    self.bannerViews[placementId] = bannerView;
    [container addSubview:bannerView];
  });
}

- (void)bannerHideAd:(NSString *)placementId {
  dispatch_async(dispatch_get_main_queue(), ^{
    self.bannerViews[placementId].hidden = YES;
  });
}

- (void)bannerReShowAd:(NSString *)placementId {
  dispatch_async(dispatch_get_main_queue(), ^{
    self.bannerViews[placementId].hidden = NO;
  });
}

- (void)bannerRemoveAd:(NSString *)placementId {
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.bannerViews[placementId] removeFromSuperview];
    [self.bannerViews removeObjectForKey:placementId];
  });
}

- (void)bannerHasAdReady:(NSString *)placementId
                 resolve:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject {
  BOOL ready = [[ATAdManager sharedManager] bannerAdReadyForPlacementID:placementId];
  resolve(@(ready));
}

- (void)bannerCheckAdStatus:(NSString *)placementId
                    resolve:(RCTPromiseResolveBlock)resolve
                     reject:(RCTPromiseRejectBlock)reject {
  ATCheckLoadModel *model = [[ATAdManager sharedManager] checkBannerLoadStatusForPlacementID:placementId];
  resolve(ToponJSONStringFromAdStatus(model));
}

#pragma mark ATSplashDelegate

- (void)didFinishLoadingSplashADWithPlacementID:(NSString *)placementID isTimeout:(BOOL)isTimeout {
  NSDictionary *extra = @{@"isTimeout": @(isTimeout)};
  [self emitEvent:kToponSplashLoaded placementId:placementID extra:extra error:nil];
}

- (void)didTimeoutLoadingSplashADWithPlacementID:(NSString *)placementID {
  [self emitEvent:kToponSplashTimeout placementId:placementID extra:nil error:nil];
}

- (void)splashDidShowForPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponSplashShow placementId:placementID extra:extra error:nil];
}

- (void)splashDidClickForPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponSplashClick placementId:placementID extra:extra error:nil];
}

- (void)splashDidCloseForPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponSplashClose placementId:placementID extra:extra error:nil];
}

- (void)splashDidShowFailedForPlacementID:(NSString *)placementID
                                    error:(NSError *)error
                                    extra:(NSDictionary *)extra {
  [self emitEvent:kToponSplashLoadFail placementId:placementID extra:extra error:error.localizedDescription];
}

#pragma mark ATNativeADDelegate

- (void)didShowNativeAdInAdView:(ATNativeADView *)adView placementID:(NSString *)placementID extra:(NSDictionary *)extra {
  NSDictionary *material = ToponNativeMaterialToDictionary([[ATAdManager sharedManager] getNativeAdOfferWithPlacementID:placementID]);
  [self emitNativeEvent:kToponNativeShow placementId:placementID adInfo:extra adMaterial:material error:nil];
}

- (void)didClickNativeAdInAdView:(ATNativeADView *)adView placementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitNativeEvent:kToponNativeClick placementId:placementID adInfo:extra adMaterial:nil error:nil];
}

- (void)didTapCloseButtonInAdView:(ATNativeADView *)adView placementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitNativeEvent:kToponNativeClose placementId:placementID adInfo:extra adMaterial:nil error:nil];
}

- (void)didStartPlayingVideoInAdView:(ATNativeADView *)adView placementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitNativeEvent:kToponNativeVideoStart placementId:placementID adInfo:extra adMaterial:nil error:nil];
}

- (void)didEndPlayingVideoInAdView:(ATNativeADView *)adView placementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitNativeEvent:kToponNativeVideoEnd placementId:placementID adInfo:extra adMaterial:nil error:nil];
}

#pragma mark - Splash

- (void)splashLoadAd:(NSString *)placementId settingsJson:(NSString *)settingsJson {
  if (placementId.length > 0) {
    [self.splashPlacements addObject:placementId];
  }
  NSDictionary *settings = ToponParseJSONDictionary(settingsJson);
  NSMutableDictionary *extra = settings != nil ? [settings mutableCopy] : [NSMutableDictionary dictionary];
  NSNumber *timeoutMs = settings[@"tolerateTimeout"];
  if ([timeoutMs isKindOfClass:[NSNumber class]]) {
    [extra removeObjectForKey:@"tolerateTimeout"];
    extra[kATSplashExtraTolerateTimeoutKey] = @([timeoutMs floatValue] * 0.001f);
  }
  [[ATAdManager sharedManager] loadADWithPlacementID:placementId extra:extra delegate:self];
}

- (void)splashShowAd:(NSString *)placementId {
  [self splashShowAdInScenario:placementId scenario:nil];
}

- (void)splashShowAdInScenario:(NSString *)placementId scenario:(NSString *)scenario {
  UIWindow *window = UIApplication.sharedApplication.delegate.window;
  UIViewController *controller = ToponCurrentViewController();
  if (window == nil || controller == nil) {
    [self emitEvent:kToponSplashLoadFail
        placementId:placementId
              extra:nil
              error:@"No active window/controller to present splash."];
    return;
  }
  if (scenario.length > 0) {
    [[ATAdManager sharedManager] entrySplashScenarioWithPlacementID:placementId scene:scenario];
  }
  ATShowConfig *config =
    scenario.length > 0 ? [[ATShowConfig alloc] initWithScene:scenario showCustomExt:nil] : [ATShowConfig new];
  [[ATAdManager sharedManager] showSplashWithPlacementID:placementId
                                                  config:config
                                                  window:window
                                       inViewController:controller
                                                   extra:nil
                                                delegate:self];
}

- (void)splashHasAdReady:(NSString *)placementId
                 resolve:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject {
  BOOL ready = [[ATAdManager sharedManager] splashReadyForPlacementID:placementId];
  resolve(@(ready));
}

- (void)splashCheckAdStatus:(NSString *)placementId
                    resolve:(RCTPromiseResolveBlock)resolve
                     reject:(RCTPromiseRejectBlock)reject {
  ATCheckLoadModel *model = [[ATAdManager sharedManager] checkSplashLoadStatusForPlacementID:placementId];
  resolve(ToponJSONStringFromAdStatus(model));
}

#pragma mark - Native

- (void)nativeLoadAd:(NSString *)placementId settingsJson:(NSString *)settingsJson {
  if (placementId.length > 0) {
    [self.nativePlacements addObject:placementId];
  }
  NSDictionary *settings = ToponParseJSONDictionary(settingsJson);
  NSMutableDictionary *extra = settings != nil ? [settings mutableCopy] : [NSMutableDictionary dictionary];
  NSNumber *width = settings[@"width"];
  NSNumber *height = settings[@"height"];
  BOOL usesPixel = [settings[@"usesPixel"] boolValue];
  if (width != nil && height != nil) {
    CGFloat scale = usesPixel ? UIScreen.mainScreen.nativeScale : 1.0f;
    CGSize adSize = CGSizeMake(width.doubleValue / scale, height.doubleValue / scale);
    extra[kATExtraInfoNativeAdSizeKey] = [NSValue valueWithCGSize:adSize];
  }
  if ([settings[@"adaptive_height"] boolValue]) {
    extra[kATNativeAdSizeToFitKey] = @(YES);
  }
  [[ATAdManager sharedManager] loadADWithPlacementID:placementId extra:extra delegate:self];
}

- (void)nativeShowAd:(NSString *)placementId viewTagsJson:(NSString *)viewTagsJson {
  [self nativeShowAdInScenario:placementId viewTagsJson:viewTagsJson scenario:nil];
}

- (void)nativeShowAdInScenario:(NSString *)placementId
                   viewTagsJson:(NSString *)viewTagsJson
                       scenario:(NSString *)scenario {
  NSDictionary *viewTags = ToponParseJSONDictionary(viewTagsJson);
  NSNumber *parentTag = viewTags[@"parent"];
  if (![parentTag isKindOfClass:[NSNumber class]]) {
    [self emitEvent:kToponNativeLoadFail
        placementId:placementId
              extra:nil
              error:@"Native parent view tag missing"];
    return;
  }
  ToponNativeAdView *parentView = (ToponNativeAdView *)[self.bridge.uiManager viewForReactTag:parentTag];
  if (![parentView isKindOfClass:[ToponNativeAdView class]]) {
    [self emitEvent:kToponNativeLoadFail
        placementId:placementId
              extra:nil
              error:@"Parent view is not ToponNativeAdView"];
    return;
  }
  if (scenario.length > 0) {
    [[ATAdManager sharedManager] entryNativeScenarioWithPlacementID:placementId scene:scenario];
  }
  ATNativeAdOffer *offer =
    scenario.length > 0
      ? [[ATAdManager sharedManager] getNativeAdOfferWithPlacementID:placementId scene:scenario]
      : [[ATAdManager sharedManager] getNativeAdOfferWithPlacementID:placementId];
  if (offer == nil) {
    [self emitEvent:kToponNativeLoadFail
        placementId:placementId
              extra:nil
              error:@"No cached native offer"];
    return;
  }

  ATNativeADConfiguration *config = [[ATNativeADConfiguration alloc] init];
  config.ADFrame = parentView.bounds;
  config.rootViewController = ToponCurrentViewController();
  config.delegate = self;
  UIView *contentView = parentView.contentView;
  UIView *mainImageView = [self viewForTag:viewTags[@"mainImage"]];
  UIView *adLogoView = [self viewForTag:viewTags[@"adLogo"]];
  if (mainImageView != nil) {
    config.mediaViewFrame = [mainImageView convertRect:mainImageView.bounds toView:contentView];
  }
  if (adLogoView != nil) {
    config.logoViewFrame = [adLogoView convertRect:adLogoView.bounds toView:contentView];
  }

  ATNativeADView *adView = [[ATNativeADView alloc] initWithConfiguration:config
                                                             currentOffer:offer
                                                              placementID:placementId];
  [self registerNativeClickableViews:adView viewTags:viewTags];
  [self prepareNativeAdView:adView viewTags:viewTags];

  if (contentView.superview != nil && contentView.superview != adView) {
    [contentView removeFromSuperview];
  }
  [offer rendererWithConfiguration:config selfRenderView:contentView nativeADView:adView];
  if (contentView.superview == nil) {
    [adView addSubview:contentView];
  }
  adView.frame = parentView.bounds;
  adView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

  [self.nativeAdViews[placementId] removeFromSuperview];
  [parentView addSubview:adView];
  self.nativeAdViews[placementId] = adView;
  self.nativeParentViews[placementId] = parentView;
}

- (void)nativeHasAdReady:(NSString *)placementId
                 resolve:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject {
  BOOL ready = [[ATAdManager sharedManager] nativeAdReadyForPlacementID:placementId];
  resolve(@(ready));
}

- (void)nativeCheckAdStatus:(NSString *)placementId
                    resolve:(RCTPromiseResolveBlock)resolve
                     reject:(RCTPromiseRejectBlock)reject {
  ATCheckLoadModel *model = [[ATAdManager sharedManager] checkNativeLoadStatusForPlacementID:placementId];
  resolve(ToponJSONStringFromAdStatus(model));
}

- (void)nativeGetAdMaterial:(NSString *)placementId
                    resolve:(RCTPromiseResolveBlock)resolve
                     reject:(RCTPromiseRejectBlock)reject {
  ATNativeAdOffer *offer = [[ATAdManager sharedManager] getNativeAdOfferWithPlacementID:placementId];
  resolve([ToponNativeMaterialToDictionary(offer) topon_jsonString]);
}

- (void)nativeRemoveAd:(NSString *)placementId {
  UIView *parentView = self.nativeParentViews[placementId];
  ATNativeADView *adView = self.nativeAdViews[placementId];
  if ([parentView isKindOfClass:[ToponNativeAdView class]] && adView != nil) {
    ToponNativeAdView *nativeView = (ToponNativeAdView *)parentView;
    UIView *contentView = nativeView.contentView;
    [contentView removeFromSuperview];
    [adView removeFromSuperview];
    [nativeView addSubview:contentView];
  }
  [self.nativeAdViews removeObjectForKey:placementId];
  [self.nativeParentViews removeObjectForKey:placementId];
}

- (UIView *)viewForTag:(id)tagValue {
  if (![tagValue isKindOfClass:[NSNumber class]]) {
    return nil;
  }
  return [self.bridge.uiManager viewForReactTag:(NSNumber *)tagValue];
}

- (void)registerNativeClickableViews:(ATNativeADView *)adView viewTags:(NSDictionary *)viewTags {
  NSMutableArray<UIView *> *clickViews = [NSMutableArray array];
  NSArray<NSString *> *keys = @[ @"title", @"desc", @"cta", @"icon", @"mainImage", @"adLogo" ];
  for (NSString *key in keys) {
    UIView *view = [self viewForTag:viewTags[key]];
    if (view != nil) {
      [clickViews addObject:view];
    }
  }
  if (clickViews.count > 0) {
    [adView registerClickableViewArray:clickViews];
  }
}

- (void)prepareNativeAdView:(ATNativeADView *)adView viewTags:(NSDictionary *)viewTags {
  UILabel *titleLabel = (UILabel *)[self viewForTag:viewTags[@"title"]];
  UILabel *descLabel = (UILabel *)[self viewForTag:viewTags[@"desc"]];
  UILabel *ctaLabel = (UILabel *)[self viewForTag:viewTags[@"cta"]];
  UIImageView *iconView = (UIImageView *)[self viewForTag:viewTags[@"icon"]];
  UIImageView *mainImageView = (UIImageView *)[self viewForTag:viewTags[@"mainImage"]];
  UIView *mediaView = [self viewForTag:viewTags[@"mainImage"]];
  UIButton *dislikeButton = (UIButton *)[self viewForTag:viewTags[@"dislike"]];

  ATNativePrepareInfo *info = [ATNativePrepareInfo loadPrepareInfo:^(
    ATNativePrepareInfo * _Nonnull prepareInfo) {
      if ([titleLabel isKindOfClass:[UILabel class]]) {
        prepareInfo.titleLabel = titleLabel;
      }
      if ([descLabel isKindOfClass:[UILabel class]]) {
        prepareInfo.textLabel = descLabel;
      }
      if ([ctaLabel isKindOfClass:[UILabel class]]) {
        prepareInfo.ctaLabel = ctaLabel;
      }
      if ([iconView isKindOfClass:[UIImageView class]]) {
        prepareInfo.iconImageView = iconView;
      }
      if ([mainImageView isKindOfClass:[UIImageView class]]) {
        prepareInfo.mainImageView = mainImageView;
      }
      if (mediaView != nil) {
        prepareInfo.mediaView = mediaView;
      }
      if ([dislikeButton isKindOfClass:[UIButton class]]) {
        prepareInfo.dislikeButton = dislikeButton;
      }
    }];
  [adView prepareWithNativePrepareInfo:info];
}

#pragma mark ATBannerDelegate

- (void)bannerView:(ATBannerView *)bannerView didClickWithPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponBannerClick placementId:placementID extra:extra error:nil];
}

- (void)bannerView:(ATBannerView *)bannerView didShowAdWithPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponBannerShow placementId:placementID extra:extra error:nil];
}

- (void)bannerView:(ATBannerView *)bannerView didTapCloseButtonWithPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponBannerClose placementId:placementID extra:extra error:nil];
}

- (void)bannerView:(ATBannerView *)bannerView didAutoRefreshWithPlacement:(NSString *)placementID extra:(NSDictionary *)extra {
  [self emitEvent:kToponBannerRefresh placementId:placementID extra:extra error:nil];
}

- (void)bannerView:(ATBannerView *)bannerView failedToAutoRefreshWithPlacementID:(NSString *)placementID error:(NSError *)error {
  [self emitEvent:kToponBannerRefreshFail placementId:placementID extra:nil error:error.localizedDescription];
}

#pragma mark - Turbo module bootstrap

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
  (const facebook::react::ObjCTurboModule::InitParams &)params {
  return std::make_shared<facebook::react::NativeToponSpecJSI>(params);
}

+ (NSString *)moduleName {
  return @"Topon";
}

@end
