#import "ATReactNativeBridge.h"
#import "ATConfiguration.h"
#import "ATInitManager.h"
#import "ATRewardVideoManager.h"
#import "ATInterstitialManager.h"
#import "ATSplashAdManager.h"
#import "ATBannerAdManager.h"
#import "ATBannerTool.h"
#import "ATNativeManager.h"
#import "ATPlatformNativeManager.h"
#import "ATNativeTool.h"
#import "ATCommonTool.h"
// RN 0.85: RCTCallableJSModules class and protocol attributes are declared in RCTBridgeModule.h;
// This header has been introduced through RNATBridgeSpec.h, only RCTEventDispatcher (for fallback) is needed below.
#import <React/RCTBridgeModule.h>
#import <React/RCTEventDispatcher.h>
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTComponentViewProtocol.h>
#import <React/RCTComponentViewFactory.h>
#endif

// Fabric ComponentView registration points (RN 0.85, new architecture only):
#ifdef RCT_NEW_ARCH_ENABLED
// - RCTFabricComponentsProvider(name) of RN 0.85 is a hard-coded map, **only checks RN built-in components**;
// No longer use dlsym to find `<ComponentName>Cls` as in the old version.
// - Third-party components must explicitly call [RCTComponentViewFactory registerComponentViewClass:] to be
// Fabric runtime is found, otherwise the JS side <ATBannerView>/<ATNativeADView> will report "Unimplemented component".
// - At the same time, we still need to solve the static library dead-strip problem: (void) two Cls() functions in init,
// Force the linker to keep ATRNBannerView.o / ATRNNativeAdView.o into the final binary.
extern "C" Class<RCTComponentViewProtocol> ATBannerViewCls(void);
extern "C" Class<RCTComponentViewProtocol> ATNativeADViewCls(void);
#endif

/**
* RN→iOS bridge entry (dual architecture: old architecture RCTBridgeModule + new architecture TurboModule Spec).
 *
* Business forwarding:
* - init family → ATInitManager class method
* - Incentive / Interstitial / Open screen / Banner / Native → Hold the singleton Manager through lazy ivar in Bridge and forward the instance method
 *
* Events returned to JS (onAdLoaded / onAdFailed / etc.) are passed by each Delegate
* `[SendEventManager sendMethod:arguments:result:]` (ATSendSignalManager shim)
* Forward to [ATReactNativeEventEmitter sharedInstance] → sink → callableJSModules;
* Bridge itself does not directly send events, but only prints the `iOS: <method> <args>` log on the first line of each spec method.
 */
@interface ATReactNativeBridge () {
    ATRewardVideoManager *_rewardMgr;
    ATInterstitialManager *_interstitialMgr;
    ATSplashAdManager *_splashMgr;
    ATBannerAdManager *_bannerMgr;
ATNativeManager *_nativeMgr; // self-render
ATPlatformNativeManager *_platformNativeMgr; // Template (Express); distributed by settings.isNativeShowType=NO
// Record the ad type of each placement when loadingNativeAd (YES=Expresstemplate, NO=self-rendering).
//For getNativeAdMaterial to look up the table and return isExpress without consuming offer pool of ATAdManager.
    NSMutableDictionary<NSString *, NSNumber *> *_nativeExpressMap;
// adId self-increasing counter (main thread access, no lock required)
    NSUInteger _nativeAdIdCounter;
}
@end

@implementation ATReactNativeBridge

RCT_EXPORT_MODULE();

// bridge / callableJSModules in the RCTBridgeModule protocol is @optional property,
// Must be explicitly @synthesize for the RN runtime to inject the instance.
@synthesize bridge = _bridge;
@synthesize callableJSModules = _callableJSModules;

#pragma mark - Threading (aligned with Flutter MethodChannel main-thread behavior; md §5.1 / phase-2e Task7)

// AnyThinkSDK has a large number of APIs (GDPR consent dialog, Banner / Native view operations, UI controller taking root, etc.)
// Force the main thread. requiresMainQueueSetup allows RN to instantiate the module on the main thread;
// methodQueue switches all spec method scheduling to the main queue to avoid single method dispatch_async tangents.
+ (BOOL)requiresMainQueueSetup {
    return YES;
}

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

#pragma mark - Lifecycle / EventSink registration

- (instancetype)init {
    if ((self = [super init])) {
// Register yourself as the downstream channel of EventEmitter. Delegate key
        // [SendEventManager sendMethod:arguments:result:]
// → ATSendSignalManager gasket
        //   → [ATReactNativeEventEmitter sharedInstance]
// → sink (this example) → RCTCallableJSModules → JS
        [[ATReactNativeEventEmitter sharedInstance] setSink:self];

        _nativeExpressMap = [NSMutableDictionary dictionary];
        _nativeAdIdCounter = 0;

// Explicitly register Fabric ComponentView to the factory (RN 0.85 does not use dlsym, see the top comment for details).
// These two times of (void)Cls() also serve as strong references to prevent ATRNBanerView.o / ATRNNativeAdView.o from being dead-striped.
#ifdef RCT_NEW_ARCH_ENABLED
        Class bannerCls = ATBannerViewCls();
        Class nativeCls = ATNativeADViewCls();
        RCTComponentViewFactory *factory = [RCTComponentViewFactory currentComponentViewFactory];
        if (bannerCls) {
            [factory registerComponentViewClass:bannerCls];
            ATLog(@"ATReactNativeBridge.init registered Fabric component ATBannerView (%@)", bannerCls);
        }
        if (nativeCls) {
            [factory registerComponentViewClass:nativeCls];
            ATLog(@"ATReactNativeBridge.init registered Fabric component ATNativeADView (%@)", nativeCls);
        }
#endif
    }
    return self;
}

#pragma mark - ATReactNativeEventSink

- (void)emitEventName:(NSString *)name body:(id)body {
    ATLog(@"emitEventName name=%@ body=%@", name, body);
// New architecture recommendation: adjust JS side RCTDeviceEventEmitter.emit through callableJSModules
    if (_callableJSModules != nil) {
        [_callableJSModules invokeModule:@"RCTDeviceEventEmitter"
                                  method:@"emit"
                                withArgs:@[name, body ?: @{}]];
        return;
    }
// Downgrade: old architecture / bridge is still there
    if (_bridge != nil) {
        [_bridge.eventDispatcher sendDeviceEventWithName:name body:body];
        return;
    }
    ATLog(@"emitEventName no JS transport (callableJSModules & bridge both nil), drop event=%@", name);
}

#pragma mark - Lazy single-instance Managers

- (ATRewardVideoManager *)rewardMgr {
    if (!_rewardMgr) _rewardMgr = [[ATRewardVideoManager alloc] init];
    return _rewardMgr;
}

- (ATInterstitialManager *)interstitialMgr {
    if (!_interstitialMgr) _interstitialMgr = [[ATInterstitialManager alloc] init];
    return _interstitialMgr;
}

- (ATSplashAdManager *)splashMgr {
    if (!_splashMgr) _splashMgr = [[ATSplashAdManager alloc] init];
    return _splashMgr;
}

- (ATBannerAdManager *)bannerMgr {
    if (!_bannerMgr) _bannerMgr = [[ATBannerAdManager alloc] init];
    return _bannerMgr;
}

- (ATNativeManager *)nativeMgr {
// Use sharedManager to share the same instance - it also takes care of SDK loading delegate (loadADWithplacementID:delegate:)
// and display the role of delegate (ATNativeADConfiguration.delegate).
// Not sharing the same instance will cause UIComponent to be lost when attachIfNeeded is used. config.delegate=nil → Display/click callbacks are lost.
    if (!_nativeMgr) _nativeMgr = [ATNativeManager sharedManager];
    return _nativeMgr;
}

- (ATPlatformNativeManager *)platformNativeMgr {
    if (!_platformNativeMgr) _platformNativeMgr = [[ATPlatformNativeManager alloc] init];
    return _platformNativeMgr;
}

#pragma mark - Init / version / SDK lifecycle

RCT_EXPORT_METHOD(initAnyThinkSDK:(NSString *)appId appKey:(NSString *)appKey)
{
    ATLog(@"initAnyThinkSDK appId=%@", appId);
    [ATInitManager initAnyThinkSDKAppID:appId
                              appKeyStr:appKey
                           requestError:^(NSError * _Nullable error) {
        if (error) {
            ATLog(@"initAnyThinkSDK error=%@", error);
        }
    }];
}

RCT_EXPORT_METHOD(start)
{
    ATLog(@"start (noop, init already includes startWithAppID)");
}

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getSDKVersionName)
{
    NSString *v = [ATInitManager getSDKVersionName] ?: @"";
    ATLog(@"getSDKVersionName version=%@", v);
    return v;
}

#pragma mark - Logging / channel / custom params

RCT_EXPORT_METHOD(setLogEnabled:(BOOL)enabled)
{
    ATLog(@"setLogEnabled enabled=%d", enabled);
    [ATInitManager setLogEnabled:enabled];
}

RCT_EXPORT_METHOD(setChannelStr:(NSString *)channel)
{
    ATLog(@"setChannelStr channel=%@", channel);
    [ATInitManager setChannelStr:channel];
}

RCT_EXPORT_METHOD(setCustomDataDic:(NSDictionary *)customMap)
{
    ATLog(@"setCustomDataDic customMap=%@", customMap);
    [ATInitManager setCustomDataDic:customMap];
}

RCT_EXPORT_METHOD(setPlacementCustomData:(NSString *)placementId customMap:(NSDictionary *)customMap)
{
    ATLog(@"setPlacementCustomData placementId=%@ customMap=%@", placementId, customMap);
    [ATInitManager setPlacementCustomData:customMap placementIDStr:placementId];
}

#pragma mark - GDPR / privacy

RCT_EXPORT_METHOD(getGDPRLevel:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"getGDPRLevel");
    NSString *s = [ATInitManager getGDPRLevel];
// Agree on number mapping with RN: 0=Unknown, 1=Personalized, 2=Nonpersonalized
    NSNumber *n = [s isEqualToString:@"ATDataConsentSetPersonalized"] ? @1
                : [s isEqualToString:@"ATDataConsentSetNonpersonalized"] ? @2
                : @0;
    resolve(n);
}

RCT_EXPORT_METHOD(setDataConsentSet:(double)level)
{
    ATLog(@"setDataConsentSet level=%f", level);
    NSString *s = nil;
    if (level == 1) {
        s = @"ATDataConsentSetPersonalized";
    } else if (level == 2) {
        s = @"ATDataConsentSetNonpersonalized";
    } else {
        s = @"ATDataConsentSetUnknown";
    }
    [ATInitManager setDataConsentSet:s];
}

RCT_EXPORT_METHOD(showGDPRConsentDialog:(NSString *)appId)
{
    ATLog(@"showGDPRConsentDialog appId=%@", appId);
    [ATInitManager showGDPRConsentDialogWithAppId:appId];
}

#pragma mark - Filter / strategy / debug

RCT_EXPORT_METHOD(putFilter:(NSString *)placementId filterSpec:(NSDictionary *)filterSpec)
{
    ATLog(@"putFilter placementId=%@ filterSpec=%@", placementId, filterSpec);
    NSString *json = nil;
    if ([filterSpec isKindOfClass:[NSDictionary class]] && filterSpec.count > 0) {
        NSError *e = nil;
        NSData *data = [NSJSONSerialization dataWithJSONObject:filterSpec options:0 error:&e];
        if (!e && data) {
            json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        }
    }
    [ATInitManager putFilter:placementId filterJson:json];
}

RCT_EXPORT_METHOD(removeFilters)
{
    ATLog(@"removeFilters");
    [ATInitManager removeFilters];
}

RCT_EXPORT_METHOD(removeFilterWithPlacementId:(NSString *)placementId)
{
    ATLog(@"removeFilterWithPlacementId placementId=%@", placementId);
    [ATInitManager removeFilterWithPlacementId:placementId];
}

RCT_EXPORT_METHOD(setAdSourcePrivacyPolicy:(NSString *)policyJson)
{
    ATLog(@"setAdSourcePrivacyPolicy policyJson=%@", policyJson);
    [ATInitManager setAdSourcePrivacyPolicy:policyJson];
}

RCT_EXPORT_METHOD(showDebuggerUI:(NSString *)debugKey)
{
    ATLog(@"showDebuggerUI");
    [ATInitManager showDebuggerUI:debugKey];
}

#pragma mark - GDPR extensions / SDK privacy policy

RCT_EXPORT_METHOD(showGDPRConsentSecondDialog:(NSString *)appId)
{
    ATLog(@"showGDPRConsentSecondDialog appId=%@", appId);
    [ATInitManager showGDPRConsentSecondDialogWithAppId:appId];
}

RCT_EXPORT_METHOD(showGdprAuth)
{
    ATLog(@"showGdprAuth");
    [ATInitManager showGDPRAuth];
}

RCT_EXPORT_METHOD(checkIsEuTraffic:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"checkIsEuTraffic");
    [ATInitManager checkIsEuTrafficWithAppId:nil completion:^(BOOL isEuTraffic) {
        resolve(@(isEuTraffic));
        ATLog(@"checkIsEuTraffic: %d", isEuTraffic);
    }];
}

RCT_EXPORT_METHOD(isEUTraffic:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"isEUTraffic");
    resolve(@([ATInitManager isEUTraffic]));
}

RCT_EXPORT_METHOD(deniedUploadDeviceInfo:(NSArray *)deviceInfoKeys)
{
    ATLog(@"deniedUploadDeviceInfo deviceInfoKeys=%@", deviceInfoKeys);
    [ATInitManager setDeniedUploadInfoArray:deviceInfoKeys ?: @[]];
}

RCT_EXPORT_METHOD(setPersonalizedAdStatus:(double)status)
{
    ATLog(@"setPersonalizedAdStatus status=%f", status);
    [ATInitManager setPersonalizedAdStatus:(NSInteger)status];
}

RCT_EXPORT_METHOD(setFilterAdSourceIdList:(NSString *)placementId adSourceIdList:(NSArray *)list)
{
    ATLog(@"setFilterAdSourceIdList placementId=%@ adSourceIdList=%@", placementId, list);
    [ATInitManager setFilterAdSourceIdList:placementId list:list ?: @[]];
}

RCT_EXPORT_METHOD(setFilterNetworkFirmIdList:(NSString *)placementId networkFirmIdList:(NSArray *)list)
{
    ATLog(@"setFilterNetworkFirmIdList placementId=%@ networkFirmIdList=%@", placementId, list);
    [ATInitManager setFilterNetworkFirmIdList:placementId list:list ?: @[]];
}

RCT_EXPORT_METHOD(setForbidNetworkFirmIdList:(NSArray *)list)
{
    ATLog(@"setForbidNetworkFirmIdList networkFirmIdList=%@", list);
    [ATInitManager setForbidNetworkFirmIdList:list ?: @[]];
}

RCT_EXPORT_METHOD(setForbidShowNetworkFirmIdList:(NSString *)placementId networkFirmIdList:(NSArray *)list)
{
    ATLog(@"setForbidShowNetworkFirmIdList placementId=%@ networkFirmIdList=%@", placementId, list);
    [ATInitManager setForbidShowNetworkFirmIdList:placementId list:list ?: @[]];
}

RCT_EXPORT_METHOD(setAllowedShowNetworkFirmIdList:(NSString *)placementId networkFirmIdList:(NSArray *)list)
{
    ATLog(@"setAllowedShowNetworkFirmIdList placementId=%@ networkFirmIdList=%@", placementId, list);
    [ATInitManager setAllowedShowNetworkFirmIdList:placementId list:list ?: @[]];
}

RCT_EXPORT_METHOD(setRiskFilterNetworkFirmIdList:(double)risk networkFirmIdList:(NSArray *)list)
{
    ATLog(@"setRiskFilterNetworkFirmIdList risk=%f networkFirmIdList=%@", risk, list);
    [ATInitManager setRiskFilterNetworkFirmIdList:(NSInteger)risk list:list ?: @[]];
}

RCT_EXPORT_METHOD(getArea:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"getArea");
    [ATInitManager getAreaWithCompletion:^(NSString *areaCode) {
        resolve(areaCode ?: @"");
    }];
}

RCT_EXPORT_METHOD(isCnSDK:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"isCnSDK");
    resolve(@([ATInitManager isCnSDK]));
}

RCT_EXPORT_METHOD(setLocation:(NSDictionary *)location)
{
    ATLog(@"setLocation location=%@", location);
    [ATInitManager setLocationWithDictionary:location ?: @{}];
}

RCT_EXPORT_METHOD(setDeviceInformationData:(NSDictionary *)data)
{
    ATLog(@"setDeviceInformationData data=%@", data);
    [ATInitManager setDeviceInformationData:data ?: @{}];
}

RCT_EXPORT_METHOD(integrationChecking)
{
    ATLog(@"integrationChecking");
    [ATInitManager integrationChecking];
}

RCT_EXPORT_METHOD(isNetworkLogDebug:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"isNetworkLogDebug");
    resolve(@([ATInitManager isNetworkLogDebug]));
}

RCT_EXPORT_METHOD(setChannelSource:(double)channelFrom)
{
    ATLog(@"setChannelSource channelFrom=%f", channelFrom);
    [ATInitManager setChannelSource:(NSInteger)channelFrom];
}

RCT_EXPORT_METHOD(setLocalStrategyAssetPath:(NSString *)path)
{
    ATLog(@"setLocalStrategyAssetPath path=%@", path);
    [ATInitManager setPresetPlacementConfigPath:path];
}

RCT_EXPORT_METHOD(setSharedPlacementConfig:(NSDictionary *)config)
{
    ATLog(@"setSharedPlacementConfig config=%@", config);
    [ATInitManager setSharedPlacementConfig:config ?: @{}];
}

#pragma mark - Rewarded video
// Pay attention to the transplant pit: call rewardedVideoDidFailToPlay (in ATRewardVideoDelegate) when playback fails. Do not copy FailToLoad.

RCT_EXPORT_METHOD(loadRewardedVideo:(NSString *)placementID settings:(NSDictionary *)settings)
{
    ATLog(@"loadRewardedVideo placementID=%@ settings=%@", placementID, settings);
    [self.rewardMgr loadRewardedVideo:placementID extraDic:settings ?: @{}];
}

RCT_EXPORT_METHOD(showRewardedVideo:(NSString *)placementID
                 scenario:(NSString *)scenario
                  resolve:(RCTPromiseResolveBlock)resolve
                   reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"showRewardedVideo placementID=%@ scenario=%@", placementID, scenario);
    if (scenario.length > 0) {
        [self.rewardMgr showRewardedVideo:placementID sceneID:scenario];
    } else {
        [self.rewardMgr showRewardedVideo:placementID];
    }
    resolve(nil);
}

RCT_EXPORT_METHOD(showRewardedVideoWithConfig:(NSString *)placementID
                         showConfig:(NSDictionary *)showConfig
                            resolve:(RCTPromiseResolveBlock)resolve
                             reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"showRewardedVideoWithConfig placementID=%@ showConfig=%@", placementID, showConfig);
    NSString *scene = showConfig[@"Scenario"] ?: showConfig[@"scenarioId"] ?: @"";
    NSString *ext = showConfig[@"showCustomExt"] ?: showConfig[@"customExt"] ?: @"";
    [self.rewardMgr showRewardedVideoWithShowConfig:placementID sceneID:scene showCustomExt:ext];
    resolve(nil);
}

RCT_EXPORT_METHOD(rewardedVideoReady:(NSString *)placementID
                   resolve:(RCTPromiseResolveBlock)resolve
                    reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"rewardedVideoReady placementID=%@", placementID);
    resolve(@([self.rewardMgr rewardedVideoReady:placementID]));
}

RCT_EXPORT_METHOD(checkRewardedVideoLoadStatus:(NSString *)placementID
                             resolve:(RCTPromiseResolveBlock)resolve
                              reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"checkRewardedVideoLoadStatus placementID=%@", placementID);
    NSDictionary *status = [self.rewardMgr checkRewardedVideoLoadStatus:placementID];
    resolve(status ?: @{@"isLoading": @(NO), @"isReady": @(NO)});
}

RCT_EXPORT_METHOD(getRewardedVideoValidAds:(NSString *)placementID
                         resolve:(RCTPromiseResolveBlock)resolve
                          reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"getRewardedVideoValidAds placementID=%@", placementID);
    NSString *json = [self.rewardMgr getRewardedVideoValidAds:placementID];
    id parsed = nil;
    if (json.length > 0) {
        parsed = [NSJSONSerialization JSONObjectWithData:[json dataUsingEncoding:NSUTF8StringEncoding]
                                                 options:0 error:nil];
    }
    resolve(parsed ?: @[]);
}

RCT_EXPORT_METHOD(entryRewardedVideoScenario:(NSString *)placementID scenario:(NSString *)scenario tkExtra:(NSDictionary *)tkExtra)
{
    ATLog(@"entryRewardedVideoScenario placementID=%@ scenario=%@ tkExtra=%@", placementID, scenario, tkExtra);
    [self.rewardMgr entryScenarioWithPlacementID:placementID sceneID:scenario];
}

RCT_EXPORT_METHOD(setRewardedVideoLocalExtra:(NSString *)placementID map:(NSDictionary *)map)
{
    ATLog(@"setRewardedVideoLocalExtra placementID=%@ map=%@ (pending Manager API)", placementID, map);
}

RCT_EXPORT_METHOD(setRewardedVideoTKExtra:(NSString *)placementID map:(NSDictionary *)map)
{
    ATLog(@"setRewardedVideoTKExtra placementID=%@ map=%@ (pending Manager API)", placementID, map);
}

RCT_EXPORT_METHOD(addAutoLoadRewardedVideo:(NSString *)placementID settings:(NSDictionary *)settings)
{
    ATLog(@"addAutoLoadRewardedVideo placementID=%@ settings=%@", placementID, settings);
    [self.rewardMgr autoLoadRewardedVideo:placementID];
    if ([settings isKindOfClass:[NSDictionary class]] && settings.count > 0) {
        [self.rewardMgr autoLoadRewardedVideoSetLocalExtra:placementID extraDic:settings];
    }
}

RCT_EXPORT_METHOD(removeAutoLoadRewardedVideo:(NSString *)placementID)
{
    ATLog(@"removeAutoLoadRewardedVideo placementID=%@", placementID);
    [self.rewardMgr cancelAutoLoadRewardedVideo:placementID];
}

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(autoLoadRewardedVideoReady:(NSString *)placementID)
{
    ATLog(@"autoLoadRewardedVideoReady placementID=%@", placementID);
    return @([self.rewardMgr rewardedVideoReady:placementID]);
}

RCT_EXPORT_METHOD(showAutoLoadRewardedVideo:(NSString *)placementID
                         scenario:(NSString *)scenario
                          resolve:(RCTPromiseResolveBlock)resolve
                           reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"showAutoLoadRewardedVideo placementID=%@ scenario=%@", placementID, scenario);
    NSDictionary *configDict = [ATCommonTool dictionaryWithJsonString:scenario];
    if ([configDict isKindOfClass:[NSDictionary class]] && configDict.count > 0) {
        NSString *scene = configDict[@"scenarioId"] ?: configDict[@"Scenario"] ?: @"";
        NSString *ext = configDict[@"showCustomExt"] ?: configDict[@"customExt"] ?: @"";
        [self.rewardMgr showAutoLoadRewardedVideoAD:placementID sceneID:scene showCustomExt:ext];
    } else {
        [self.rewardMgr showAutoLoadRewardedVideoAD:placementID sceneID:scenario ?: @""];
    }
    resolve(nil);
}

RCT_EXPORT_METHOD(showAutoLoadRewardedVideoWithConfig:(NSString *)placementID
                         showConfig:(NSDictionary *)showConfig
                            resolve:(RCTPromiseResolveBlock)resolve
                             reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"showAutoLoadRewardedVideoWithConfig placementID=%@ showConfig=%@", placementID, showConfig);
    NSString *scene = showConfig[@"Scenario"] ?: showConfig[@"scenarioId"] ?: @"";
    NSString *ext = showConfig[@"showCustomExt"] ?: showConfig[@"customExt"] ?: @"";
    [self.rewardMgr showAutoLoadRewardedVideoAD:placementID sceneID:scene showCustomExt:ext];
    resolve(nil);
}

#pragma mark - Interstitial
// Note: Playback failure callback interstitialDidFailToPlayVideo (in ATInterstitialDelegate).

RCT_EXPORT_METHOD(loadInterstitial:(NSString *)placementID settings:(NSDictionary *)settings)
{
    ATLog(@"loadInterstitial placementID=%@ settings=%@", placementID, settings);
    [self.interstitialMgr loadInterstitialAd:placementID extraDic:settings ?: @{}];
}

RCT_EXPORT_METHOD(showInterstitial:(NSString *)placementID
                scenario:(NSString *)scenario
                 resolve:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"showInterstitial placementID=%@ scenario=%@", placementID, scenario);
    if (scenario.length > 0) {
        [self.interstitialMgr showInterstitialAd:placementID sceneID:scenario];
    } else {
        [self.interstitialMgr showInterstitialAd:placementID];
    }
    resolve(nil);
}

RCT_EXPORT_METHOD(showInterstitialWithConfig:(NSString *)placementID
                        showConfig:(NSDictionary *)showConfig
                           resolve:(RCTPromiseResolveBlock)resolve
                            reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"showInterstitialWithConfig placementID=%@ showConfig=%@", placementID, showConfig);
    NSString *scene = showConfig[@"Scenario"] ?: showConfig[@"scenarioId"] ?: @"";
    NSString *ext = showConfig[@"showCustomExt"] ?: showConfig[@"customExt"] ?: @"";
    [self.interstitialMgr showInterstitialAdWithShowConfig:placementID sceneID:scene showCustomExt:ext];
    resolve(nil);
}

RCT_EXPORT_METHOD(interstitialReady:(NSString *)placementID
                  resolve:(RCTPromiseResolveBlock)resolve
                   reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"interstitialReady placementID=%@", placementID);
    resolve(@([self.interstitialMgr hasInterstitialAdReady:placementID]));
}

RCT_EXPORT_METHOD(checkInterstitialLoadStatus:(NSString *)placementID
                            resolve:(RCTPromiseResolveBlock)resolve
                             reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"checkInterstitialLoadStatus placementID=%@", placementID);
    NSDictionary *status = [self.interstitialMgr checkInterstitialLoadStatus:placementID];
    resolve(status ?: @{@"isLoading": @(NO), @"isReady": @(NO)});
}

RCT_EXPORT_METHOD(getInterstitialValidAds:(NSString *)placementID
                        resolve:(RCTPromiseResolveBlock)resolve
                         reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"getInterstitialValidAds placementID=%@", placementID);
    NSString *json = [self.interstitialMgr getInterstitialValidAds:placementID];
    id parsed = nil;
    if (json.length > 0) {
        parsed = [NSJSONSerialization JSONObjectWithData:[json dataUsingEncoding:NSUTF8StringEncoding]
                                                 options:0 error:nil];
    }
    resolve(parsed ?: @[]);
}

RCT_EXPORT_METHOD(entryInterstitialScenario:(NSString *)placementID scenario:(NSString *)scenario tkExtra:(NSDictionary *)tkExtra)
{
    ATLog(@"entryInterstitialScenario placementID=%@ scenario=%@ tkExtra=%@", placementID, scenario, tkExtra);
    [self.interstitialMgr entryScenarioWithPlacementID:placementID sceneID:scenario];
}

RCT_EXPORT_METHOD(setInterstitialLocalExtra:(NSString *)placementID map:(NSDictionary *)map)
{
    ATLog(@"setInterstitialLocalExtra placementID=%@ map=%@ (pending Manager API)", placementID, map);
}

RCT_EXPORT_METHOD(setInterstitialTKExtra:(NSString *)placementID map:(NSDictionary *)map)
{
    ATLog(@"setInterstitialTKExtra placementID=%@ map=%@ (pending Manager API)", placementID, map);
}

RCT_EXPORT_METHOD(addAutoLoadInterstitial:(NSString *)placementID settings:(NSDictionary *)settings)
{
    ATLog(@"addAutoLoadInterstitial placementID=%@ settings=%@", placementID, settings);
    [self.interstitialMgr autoLoadInterstitialAD:placementID];
    if ([settings isKindOfClass:[NSDictionary class]] && settings.count > 0) {
        [self.interstitialMgr autoLoadInterstitialADSetLocalExtra:placementID extraDic:settings];
    }
}

RCT_EXPORT_METHOD(removeAutoLoadInterstitial:(NSString *)placementID)
{
    ATLog(@"removeAutoLoadInterstitial placementID=%@", placementID);
    [self.interstitialMgr cancelAutoLoadInterstitialAD:placementID];
}

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(autoLoadInterstitialReady:(NSString *)placementID)
{
    ATLog(@"autoLoadInterstitialReady placementID=%@", placementID);
    return @([self.interstitialMgr hasInterstitialAdReady:placementID]);
}

RCT_EXPORT_METHOD(showAutoLoadInterstitial:(NSString *)placementID
                        scenario:(NSString *)scenario
                         resolve:(RCTPromiseResolveBlock)resolve
                          reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"showAutoLoadInterstitial placementID=%@ scenario=%@", placementID, scenario);
    NSDictionary *configDict = [ATCommonTool dictionaryWithJsonString:scenario];
    if ([configDict isKindOfClass:[NSDictionary class]] && configDict.count > 0) {
        NSString *scene = configDict[@"scenarioId"] ?: configDict[@"Scenario"] ?: @"";
        NSString *ext = configDict[@"showCustomExt"] ?: configDict[@"customExt"] ?: @"";
        [self.interstitialMgr showAutoLoadInterstitialADWithPlacementID:placementID sceneID:scene showCustomExt:ext];
    } else {
        [self.interstitialMgr showAutoLoadInterstitialADWithPlacementID:placementID sceneID:scenario ?: @""];
    }
    resolve(nil);
}

RCT_EXPORT_METHOD(showAutoLoadInterstitialWithConfig:(NSString *)placementID
                         showConfig:(NSDictionary *)showConfig
                            resolve:(RCTPromiseResolveBlock)resolve
                             reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"showAutoLoadInterstitialWithConfig placementID=%@ showConfig=%@", placementID, showConfig);
    NSString *scene = showConfig[@"Scenario"] ?: showConfig[@"scenarioId"] ?: @"";
    NSString *ext = showConfig[@"showCustomExt"] ?: showConfig[@"customExt"] ?: @"";
    [self.interstitialMgr showAutoLoadInterstitialADWithPlacementID:placementID sceneID:scene showCustomExt:ext];
    resolve(nil);
}

#pragma mark - Splash

RCT_EXPORT_METHOD(loadSplash:(NSString *)placementID settings:(NSDictionary *)settings)
{
    ATLog(@"loadSplash placementID=%@ settings=%@", placementID, settings);
    [self.splashMgr loadSplashAd:placementID extraDic:settings ?: @{}];
}

RCT_EXPORT_METHOD(showSplash:(NSString *)placementID
          scenario:(NSString *)scenario
           resolve:(RCTPromiseResolveBlock)resolve
            reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"showSplash placementID=%@ scenario=%@", placementID, scenario);
    if (scenario.length > 0) {
        [self.splashMgr showSplashAd:placementID sceneID:scenario];
    } else {
        [self.splashMgr showSplashAd:placementID];
    }
    resolve(nil);
}

RCT_EXPORT_METHOD(showSplashWithConfig:(NSString *)placementID
                  showConfig:(NSDictionary *)showConfig
                     resolve:(RCTPromiseResolveBlock)resolve
                      reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"showSplashWithConfig placementID=%@ showConfig=%@", placementID, showConfig);
    NSString *scene = showConfig[@"Scenario"] ?: showConfig[@"scenarioId"] ?: showConfig[@"scenario"] ?: showConfig[@"sceneID"] ?: @"";
    NSString *ext = showConfig[@"showCustomExt"] ?: showConfig[@"customExt"] ?: @"";
    [self.splashMgr showSplashAdWithShowConfig:placementID sceneID:scene showCustomExt:ext];
    resolve(nil);
}

RCT_EXPORT_METHOD(splashReady:(NSString *)placementID
            resolve:(RCTPromiseResolveBlock)resolve
             reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"splashReady placementID=%@", placementID);
    resolve(@([self.splashMgr splashAdReady:placementID]));
}

RCT_EXPORT_METHOD(checkSplashLoadStatus:(NSString *)placementID
                      resolve:(RCTPromiseResolveBlock)resolve
                       reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"checkSplashLoadStatus placementID=%@", placementID);
    NSDictionary *status = [self.splashMgr checkSplashAdLoadStatus:placementID];
    resolve(status ?: @{@"isLoading": @(NO), @"isReady": @(NO)});
}

RCT_EXPORT_METHOD(getSplashValidAds:(NSString *)placementID
                  resolve:(RCTPromiseResolveBlock)resolve
                   reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"getSplashValidAds placementID=%@", placementID);
    NSString *json = [self.splashMgr getSplashAdValidAds:placementID];
    id parsed = nil;
    if (json.length > 0) {
        parsed = [NSJSONSerialization JSONObjectWithData:[json dataUsingEncoding:NSUTF8StringEncoding]
                                                 options:0 error:nil];
    }
    resolve(parsed ?: @[]);
}

RCT_EXPORT_METHOD(entrySplashScenario:(NSString *)placementID scenario:(NSString *)scenario tkExtra:(NSDictionary *)tkExtra)
{
    ATLog(@"entrySplashScenario placementID=%@ scenario=%@ tkExtra=%@", placementID, scenario, tkExtra);
    [self.splashMgr entryScenarioWithPlacementID:placementID sceneID:scenario];
}

RCT_EXPORT_METHOD(setSplashLocalExtra:(NSString *)placementID map:(NSDictionary *)map)
{
    ATLog(@"setSplashLocalExtra placementID=%@ map=%@ (pending Manager API)", placementID, map);
}

RCT_EXPORT_METHOD(setSplashTKExtra:(NSString *)placementID map:(NSDictionary *)map)
{
    ATLog(@"setSplashTKExtra placementID=%@ map=%@ (pending Manager API)", placementID, map);
}

#pragma mark - Banner
//Imperative part; embedded view (show in rect / position / hide / afreshShow) goes Fabric ComponentView,
// Not exposed in Bridge - but this entry still provides command interfaces such as load/destroy/entryScenario.

RCT_EXPORT_METHOD(loadBannerAd:(NSString *)placementID settings:(NSDictionary *)settings)
{
    ATLog(@"loadBannerAd placementID=%@ settings=%@", placementID, settings);
    [self.bannerMgr loadBannerWith:placementID extraDic:settings ?: @{}];
}

RCT_EXPORT_METHOD(checkBannerLoadStatus:(NSString *)placementID
                      resolve:(RCTPromiseResolveBlock)resolve
                       reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"checkBannerLoadStatus placementID=%@", placementID);
    NSDictionary *status = [self.bannerMgr checkBannerLoadStatus:placementID];
    resolve(status ?: @{@"isLoading": @(NO), @"isReady": @(NO)});
}

RCT_EXPORT_METHOD(getBannerValidAds:(NSString *)placementID
                  resolve:(RCTPromiseResolveBlock)resolve
                   reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"getBannerValidAds placementID=%@", placementID);
    NSString *json = [self.bannerMgr getBannerValidAds:placementID];
    id parsed = nil;
    if (json.length > 0) {
        parsed = [NSJSONSerialization JSONObjectWithData:[json dataUsingEncoding:NSUTF8StringEncoding]
                                                 options:0 error:nil];
    }
    resolve(parsed ?: @[]);
}

RCT_EXPORT_METHOD(entryBannerScenario:(NSString *)placementID scenario:(NSString *)scenario tkExtra:(NSDictionary *)tkExtra)
{
    ATLog(@"entryBannerScenario placementID=%@ scenario=%@ tkExtra=%@", placementID, scenario, tkExtra);
    [self.bannerMgr entryScenarioWithPlacementID:placementID sceneID:scenario];
}

RCT_EXPORT_METHOD(setBannerShowConfig:(NSString *)placementID map:(NSDictionary *)map)
{
    ATLog(@"setBannerShowConfig placementID=%@ map=%@", placementID, map);
    [ATBannerTool setShowConfigMap:map forPlacementID:placementID];
}

RCT_EXPORT_METHOD(setBannerLocalExtra:(NSString *)placementID map:(NSDictionary *)map)
{
    ATLog(@"setBannerLocalExtra placementID=%@ map=%@", placementID, map);
    [ATBannerTool setLocalExtraMap:map forPlacementID:placementID];
}

RCT_EXPORT_METHOD(setBannerTKExtra:(NSString *)placementID map:(NSDictionary *)map)
{
    ATLog(@"setBannerTKExtra placementID=%@ map=%@ (pending Manager API)", placementID, map);
}

RCT_EXPORT_METHOD(destroyBanner:(NSString *)placementID)
{
    ATLog(@"destroyBanner placementID=%@", placementID);
    [self.bannerMgr destroyBannerAd:placementID];
}

#pragma mark - Native feed
// Imperative part; self-rendering / Express embedded view goes Fabric ComponentView.

RCT_EXPORT_METHOD(loadNativeAd:(NSString *)placementID settings:(NSDictionary *)settings)
{
    ATLog(@"loadNativeAd placementID=%@ settings=%@", placementID, settings);
    NSDictionary *extra = ([settings isKindOfClass:[NSDictionary class]]) ? settings : @{};

// Reference Flutter ATFAdManger+NativeAd.m: default YES (self-rendering ATNativeManager);
// Explicitly pass isNativeShowType=NO to use the template (ATPlatformNativeManager).
    BOOL isNativeShowType = YES;
    id v = extra[@"isNativeShowType"];
    if (v != nil && [v respondsToSelector:@selector(boolValue)] && [v boolValue] == NO) {
        isNativeShowType = NO;
    }
    
    if (isNativeShowType) {
        ATLog(@"loadNativeAd → ATNativeManager (selfRender) placementID=%@", placementID);
_nativeExpressMap[placementID] = @(NO); // Self-rendering
        [self.nativeMgr loadNativeWith:placementID extraDic:extra];
    } else {
        ATLog(@"loadNativeAd → ATPlatformNativeManager (express) placementID=%@", placementID);
_nativeExpressMap[placementID] = @(YES); // Template (Express)
        [self.platformNativeMgr loadNativeWith:placementID extraDic:extra];
    }
}

RCT_EXPORT_METHOD(getNativeAd:(NSString *)placementID
            resolve:(RCTPromiseResolveBlock)resolve
             reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"getNativeAd placementID=%@", placementID);
    BOOL hasAd = [self.nativeMgr hasNativeAdReady:placementID];
    if (hasAd) {
// Generate cross-bridge object reference adId (= equivalent replacement for Android NativeAd object reference).
// Format: placementID + "#" + self-increasing serial number, simple and readable, no conflict.
        _nativeAdIdCounter++;
        NSString *adId = [NSString stringWithFormat:@"%@#%lu", placementID, (unsigned long)_nativeAdIdCounter];
        BOOL isExpress = [_nativeExpressMap[placementID] boolValue];
        ATLog(@"getNativeAd hasAd=YES adId=%@ isExpress=%d", adId, isExpress);
        resolve(@{@"hasAd": @(YES), @"adId": adId, @"isExpress": @(isExpress)});
    } else {
        resolve([NSNull null]);
    }
}

RCT_EXPORT_METHOD(prepareNativeAd:(NSString *)placementID prepareInfo:(NSDictionary *)prepareInfo)
{
    ATLog(@"prepareNativeAd placementID=%@ prepareInfo=%@ (pending Manager API)", placementID, prepareInfo);
}

RCT_EXPORT_METHOD(checkNativeLoadStatus:(NSString *)placementID
                      resolve:(RCTPromiseResolveBlock)resolve
                       reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"checkNativeLoadStatus placementID=%@", placementID); 
  NSDictionary *status = [self.nativeMgr checkNativeLoadStatus:placementID];
  resolve(status ?: @{@"isLoading": @(NO), @"isReady": @(NO)});
}

RCT_EXPORT_METHOD(getNativeValidAds:(NSString *)placementID
                  resolve:(RCTPromiseResolveBlock)resolve
                   reject:(RCTPromiseRejectBlock)reject)
{
    ATLog(@"getNativeValidAds placementID=%@ (Manager has no validAds query; returning empty)", placementID);
    resolve(@[]);
}

RCT_EXPORT_METHOD(getNativeAdMaterial:(NSString *)placementID
                      adId:(NSString *)adId
                   resolve:(RCTPromiseResolveBlock)resolve
                    reject:(RCTPromiseRejectBlock)reject)
{
// Check _nativeExpressMap and return isExpress, do not adjust getNativeAdOfferWithplacementID (will consume offer pool).
// ATNativeUIComponent.attachIfNeeded is the only entry that actually consumes offer and renders it.
    BOOL isExpress = [_nativeExpressMap[placementID] boolValue];
    ATLog(@"getNativeAdMaterial placementID=%@ adId=%@ isExpress=%d", placementID, adId, isExpress);
    resolve(@{@"isExpress": @(isExpress)});
}

RCT_EXPORT_METHOD(entryNativeScenario:(NSString *)placementID scenario:(NSString *)scenario tkExtra:(NSDictionary *)tkExtra)
{
    ATLog(@"entryNativeScenario placementID=%@ scenario=%@ tkExtra=%@", placementID, scenario, tkExtra);
    [self.nativeMgr entryScenarioWithPlacementID:placementID sceneID:scenario];
}

RCT_EXPORT_METHOD(setNativeShowConfig:(NSString *)placementID map:(NSDictionary *)map)
{
    ATLog(@"setNativeShowConfig placementID=%@ map=%@", placementID, map);
    [ATNativeTool setShowConfigMap:map forPlacementID:placementID];
}

RCT_EXPORT_METHOD(setNativeLocalExtra:(NSString *)placementID map:(NSDictionary *)map)
{
    ATLog(@"setNativeLocalExtra placementID=%@ map=%@", placementID, map);
    [ATNativeTool setLocalExtraMap:map forPlacementID:placementID];
}

RCT_EXPORT_METHOD(setNativeTKExtra:(NSString *)placementID map:(NSDictionary *)map)
{
    ATLog(@"setNativeTKExtra placementID=%@ map=%@ (pending Manager API)", placementID, map);
}

RCT_EXPORT_METHOD(destroyNativeAd:(NSString *)placementID adId:(NSString *)adId)
{
    ATLog(@"destroyNativeAd placementID=%@ adId=%@", placementID, adId);
    [self.nativeMgr destroyNativeAd:placementID adId:adId ?: @""];
}

RCT_EXPORT_METHOD(nativeAdOnResume:(NSString *)placementID adId:(NSString *)adId)
{
    ATLog(@"nativeAdOnResume placementID=%@ adId=%@ (stub)", placementID, adId);
}

RCT_EXPORT_METHOD(nativeAdOnPause:(NSString *)placementID adId:(NSString *)adId)
{
    ATLog(@"nativeAdOnPause placementID=%@ adId=%@ (stub)", placementID, adId);
}

#pragma mark - NativeEventEmitter required

RCT_EXPORT_METHOD(addListener:(NSString *)eventName)
{
    ATLog(@"addListener eventName=%@", eventName);
}

RCT_EXPORT_METHOD(removeListeners:(double)count)
{
    ATLog(@"removeListeners count=%f", count);
}

#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
    (const facebook::react::ObjCTurboModule::InitParams &)params
{
    return std::make_shared<facebook::react::NativeATBridgeSpecJSI>(params);
}
#endif

@end
