#import "UserLeapBindings.h"
#import <React/RCTUtils.h>
// Legacy Paper includes gated on RCT_REMOVE_LEGACY_ARCH: these classes are removed on RN 0.85+.
#ifndef RCT_REMOVE_LEGACY_ARCH
#import <React/RCTTextView.h>
#import <React/RCTUIManager.h>
#import <React/RCTTextShadowView.h>
#import <React/RCTShadowView.h>
#import <React/RCTRawTextShadowView.h>
#import <React/RCTVirtualTextShadowView.h>
#import <React/RCTUIManagerUtils.h>
#endif
#import <React/RCTView.h>

// The codegen spec header is Obj-C++ only, which is why this file is `.mm`.
#import <RNUserLeapBindingsSpec/RNUserLeapBindingsSpec.h>

@implementation UserLeapBindings {
    BOOL _alive;
}

+ (BOOL)requiresMainQueueSetup
{
    return YES;
}

- (instancetype)init
{
    if (self = [super init]) {
        _alive = YES;
        // emitOnSprigEvent is a safe no-op when JS has no listener, so eager subscription is fine.
        [self subscribeToSdkEvents];
    }
    return self;
}

- (void)dealloc
{
    _alive = NO;
    [self unsubscribeFromSdkEvents];
}

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

- (NSDictionary *)parseSurveyResult:(SprigSurveyResult *)result {
    SurveyState state = [result surveyState];
    NSString *surveyStateString = [self getSurveyState:state];
    NSInteger surveyId = [result surveyId];
    return @{@"surveyState": surveyStateString, @"surveyId": @(surveyId)};
}

- (NSString *)getSurveyState:(SurveyState)surveyState
{
    NSString *surveyStateBinding = @"NO_SURVEY";
    switch(surveyState) {
        case SurveyStateDisabled:
            surveyStateBinding = @"DISABLED";
            break;
        case SurveyStateReady:
            surveyStateBinding = @"READY";
            break;
        case SurveyStateNoSurvey:
            surveyStateBinding = @"NO_SURVEY";
            break;
        case SurveyStatePreviousSurveyReady:
            surveyStateBinding = @"PREVIOUS_SURVEY_READY";
            break;
    }
    return surveyStateBinding;
}

RCT_EXPORT_MODULE()

- (NSNumber *)visitorIdentifier
{
    // No @() boxing: @() expects a primitive C type, and the SDK already returns NSNumber *.
    return [[UserLeap shared] visitorIdentifier];
}

- (NSString *)visitorIdentifierString
{
    return [[UserLeap shared] visitorIdentifierString];
}

- (NSString *)getSdkVersion
{
    // Renamed from `sdkVersion` to `getSdkVersion` so iOS and Android share one spec method name.
    return [[UserLeap shared] sdkVersion];
}

- (void)configure:(NSString *)environmentId configuration:(NSDictionary *)configuration
{
    [[UserLeap shared] configureWithEnvironment:environmentId configuration:configuration];
    [[UserLeap shared] _passWithRnExtractor:self];
}

- (void)setPreviewKey:(NSString *)previewKey
{
    [[UserLeap shared] setPreviewKey:previewKey];
}

- (void)setUserIdentifier:(NSString *)identifier
{
    [[UserLeap shared] setUserIdentifier:identifier];
}

- (void)setEmailAddress:(NSString *)emailAddress
{
    [[UserLeap shared] setEmailAddress:emailAddress];
}

- (void)logout
{
    [[UserLeap shared] logout];
}

- (void)setVisitorAttribute:(NSString *)key value:(NSString *)value
{
    [[UserLeap shared] setVisitorAttributeWithKey:key value:value];
}

- (void)setVisitorAttributes:(NSDictionary *)attributes
{
    [[UserLeap shared] setVisitorAttributes:attributes];
}

- (void)removeVisitorAttributes:(NSArray *)keys
{
    [[UserLeap shared] removeVisitorAttributes:keys];
}

- (void)setVisitorAttributesAndIdentify:(NSDictionary *)attributes
                                 userId:(NSString *)userId
                     partnerAnonymousId:(NSString *)partnerAnonymousId
{
    [[UserLeap shared] setVisitorAttributes:attributes userId:userId partnerAnonymousId:partnerAnonymousId];
}

- (void)presentSurvey
{
    [[UserLeap shared] presentSurveyFrom:RCTPresentedViewController()];
}

- (void)dismissActiveSurvey
{
    [[UserLeap shared] dismissActiveSurvey];
}

- (void)pauseDisplayingSurveys
{
    [[UserLeap shared] pauseDisplayingSurveys];
}

- (void)unpauseDisplayingSurveys
{
    [[UserLeap shared] unpauseDisplayingSurveys];
}

- (void)trackAndPresent:(NSString *)eventName
{
    [[UserLeap shared] trackAndPresentWithEventName:eventName from:RCTPresentedViewController()];
}

- (void)trackIdentifyAndPresent:(NSString *)eventName
                         userId:(NSString *)userId
             partnerAnonymousId:(NSString *)partnerAnonymousId
{
    [[UserLeap shared] trackAndPresentWithEventName:eventName userId:userId partnerAnonymousId:partnerAnonymousId from:RCTPresentedViewController()];
}

- (void)overrideUserInterfaceMode:(double)mode
{
    // Explicit enum cast required: Obj-C++ won't implicitly convert numeric → enum.
    [[UserLeap shared] overrideUserInterfaceModeWithMode:(enum SprigUserInterfaceMode)(NSInteger)mode];
}

- (void)trackEvent:(NSString *)eventName
surveyResultCallback:(RCTResponseSenderBlock)surveyResultCallback
{
    EventPayload *payload = [[EventPayload alloc] initWithEventName:eventName
                                                             userId:nil
                                                 partnerAnonymousId:nil
                                                         properties:nil
                                                            handler:nil
                                                      resultHandler:^(SprigSurveyResult * result) {
        if (surveyResultCallback != nil) {
            surveyResultCallback(@[[self parseSurveyResult:result]]);
        }
    }];
    [[UserLeap shared] trackWithPayload:payload];
}

- (void)trackEventWithProperties:(NSString *)eventName
                          userId:(NSString *)userId
              partnerAnonymousId:(NSString *)partnerAnonymousId
                      properties:(NSDictionary *)properties
            surveyResultCallback:(RCTResponseSenderBlock)surveyResultCallback
{
    EventPayload *payload = [[EventPayload alloc] initWithEventName:eventName
                                                             userId:userId
                                                 partnerAnonymousId:partnerAnonymousId
                                                         properties:properties
                                                            handler:nil
                                                      resultHandler:^(SprigSurveyResult * result) {
        if (surveyResultCallback != nil) {
            surveyResultCallback(@[[self parseSurveyResult:result]]);
        }
    }];
    [[UserLeap shared] trackWithPayload:payload];
}

- (void)trackEventAndIdentify:(NSString *)eventName
                       userId:(NSString *)userId
           partnerAnonymousId:(NSString *)partnerAnonymousId
         surveyResultCallback:(RCTResponseSenderBlock)surveyResultCallback
{
    EventPayload *payload = [[EventPayload alloc] initWithEventName:eventName
                                                             userId:userId
                                                 partnerAnonymousId:partnerAnonymousId
                                                         properties:nil
                                                            handler:nil
                                                      resultHandler:^(SprigSurveyResult * result) {
        if (surveyResultCallback != nil) {
            surveyResultCallback(@[[self parseSurveyResult:result]]);
        }
    }];
    [[UserLeap shared] trackWithPayload:payload];
}

- (void)track:(NSString *)eventName
surveyStateCallback:(RCTResponseSenderBlock)surveyStateCallback
{
    if (surveyStateCallback != nil) {
        [[UserLeap shared] trackWithEventName:eventName handler:^(enum SurveyState surveyState) {
            surveyStateCallback(@[[self getSurveyState:surveyState]]);
        }];
    } else {
        [[UserLeap shared] trackWithEventName:eventName handler:nil];
    }
}

- (void)trackWithProperties:(NSString *)eventName
                     userId:(NSString *)userId
         partnerAnonymousId:(NSString *)partnerAnonymousId
                 properties:(NSDictionary *)properties
        surveyStateCallback:(RCTResponseSenderBlock)surveyStateCallback
{
    [[UserLeap shared] trackWithEventName:eventName
                                   userId:userId
                       partnerAnonymousId:partnerAnonymousId
                               properties:properties
                                  handler:^(enum SurveyState surveyState) {
        if (surveyStateCallback != nil) {
            surveyStateCallback(@[[self getSurveyState:surveyState]]);
        }
    }];
}

- (void)trackAndIdentify:(NSString *)eventName
                  userId:(NSString *)userId
      partnerAnonymousId:(NSString *)partnerAnonymousId
     surveyStateCallback:(RCTResponseSenderBlock)surveyStateCallback
{
    if (surveyStateCallback != nil) {
        [[UserLeap shared] trackWithEventName:eventName
                                       userId:userId
                           partnerAnonymousId:partnerAnonymousId
                                      handler:^(enum SurveyState surveyState) {
            surveyStateCallback(@[[self getSurveyState:surveyState]]);
        }];
    } else {
        [[UserLeap shared] trackWithEventName:eventName
                                       userId:userId
                           partnerAnonymousId:partnerAnonymousId
                                      handler:nil];
    }
}

- (_SGRNTextProperties *)textPropertiesFromView:(UIView * _Nonnull)passedView {

#ifndef RCT_REMOVE_LEGACY_ARCH
    if ([passedView isKindOfClass:[RCTTextView class]]) {
        RCTUIManager* uiManager = [self.bridge moduleForClass:[RCTUIManager class]];
        RCTTextView *textView = (RCTTextView *)passedView;
        __block _SGRNTextProperties *textProperties = [[_SGRNTextProperties alloc] init];
        dispatch_sync(RCTGetUIManagerQueue(), ^{
            RCTShadowView *shadowView = [uiManager shadowViewForReactTag:textView.reactTag];
            if ([shadowView isKindOfClass:[RCTTextShadowView class]]) {
                RCTTextShadowView *textShadowView = (RCTTextShadowView *)shadowView;
                NSString *text = [self extractText: textShadowView.reactSubviews];
                if (text.length == 0) return;
                textProperties.text = text;
                textProperties.color = textShadowView.textAttributes.foregroundColor;
                textProperties.alignment = textShadowView.textAttributes.alignment;
                textProperties.font = textShadowView.textAttributes.effectiveFont;
            }
        });
        if (textProperties.text.length == 0) return nil;
        return textProperties;
    }
#endif

    Class ParagraphComponentView = NSClassFromString(@"RCTParagraphComponentView");
    if (ParagraphComponentView && [passedView isKindOfClass:ParagraphComponentView]) {
        NSString *text = [self extractTextFromParagraphComponentView:passedView];
        if (text.length == 0) return nil;
        _SGRNTextProperties *textProperties = [[_SGRNTextProperties alloc] init];
        textProperties.text = text;
        if ([passedView respondsToSelector:@selector(attributedText)]) {
            NSAttributedString *attrText = [passedView performSelector:@selector(attributedText)];
            if (attrText.length > 0) {
                NSDictionary *attrs = [attrText attributesAtIndex:0 effectiveRange:nil];
                if (!textProperties.color && attrs[NSForegroundColorAttributeName]) {
                    textProperties.color = attrs[NSForegroundColorAttributeName];
                }
                if (!textProperties.font && attrs[NSFontAttributeName]) {
                    textProperties.font = attrs[NSFontAttributeName];
                }
                NSParagraphStyle *style = attrs[NSParagraphStyleAttributeName];
                if (style) {
                    textProperties.alignment = style.alignment;
                }
            }
        }
        return textProperties;
    }
    return nil;
}

- (_SGRNViewProperties * _Nullable)propertiesFromView:(UIView * _Nonnull)passedView {
    if ([passedView isKindOfClass:[RCTView class]]) {
        RCTView *rView = (RCTView *) passedView;
        if (rView.borderWidth > 0) {
            _SGRNViewProperties *props = [[_SGRNViewProperties alloc] init];
            props.borderColor = rView.borderColor;
            props.borderWidth = rView.borderWidth;
            return props;
        }
    }
    return nil;
}

#ifndef RCT_REMOVE_LEGACY_ARCH
- (NSString * _Nullable)extractText:(NSArray<RCTShadowView *> * _Nonnull) subviews {
    NSMutableString *text = [[NSMutableString alloc] init];
    for (RCTShadowView *subview in subviews) {
        if ([subview isKindOfClass:[RCTRawTextShadowView class]]) {
            [text appendString:((RCTRawTextShadowView *)subview).text];
        }
        if ([subview isKindOfClass:[RCTVirtualTextShadowView class]]) {
            // Append, don't early-return: returning here would drop text already collected.
            NSString *nested = [self extractText: ((RCTVirtualTextShadowView *)subview).reactSubviews];
            if (nested) [text appendString:nested];
        }
    }
    return text;
}
#endif

- (NSString *)extractTextFromParagraphComponentView:(UIView *)view {
    if ([view respondsToSelector:@selector(attributedText)]) {
        NSAttributedString *attrText = [view performSelector:@selector(attributedText)];
        return attrText.string;
    }
    if ([view respondsToSelector:@selector(text)]) {
        NSString *text = [view performSelector:@selector(text)];
        return text;
    }
    NSMutableString *result = [NSMutableString string];
    for (UIView *subview in view.subviews) {
        NSString *subText = [self extractTextFromParagraphComponentView:subview];
        if (subText) [result appendString:subText];
    }
    return result;
}

- (NSArray<NSString *> *)getSupportedEventNames
{
    NSMutableArray<NSString *> *events = [NSMutableArray array];
    for (NSString *name in [LifecycleEventUtil allNameStrings]) {
        if ([name isEqualToString:@"unknown"]) continue;
        [events addObject:name];
    }
    return events;
}

- (NSDictionary *)normalizePayloadForEvent:(NSString *)eventName
                                       data:(NSDictionary<NSString *, id> *)data
{
    if (![eventName isEqualToString:@"loggingEvent"]) {
        return data ?: @{};
    }

    NSString *raw = data[@"message"];
    NSString *message = @"";
    if ([raw isKindOfClass:[NSString class]]) {
        NSString *prefix = @"Sprig: ";
        message = [raw hasPrefix:prefix] ? [raw substringFromIndex:prefix.length] : raw;
    }

    return @{
        @"type": @"loggingEvent",
        @"message": message,
        @"level": @"info",
    };
}

- (NSString *)jsonStringFromDictionary:(NSDictionary *)dict
{
    if (![NSJSONSerialization isValidJSONObject:dict]) {
        return @"{}";
    }
    NSError *error = nil;
    NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:0 error:&error];
    if (!data || error) {
        return @"{}";
    }
    return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: @"{}";
}

- (void)subscribeToSdkEvents
{
    __weak __typeof(self) weakSelf = self;
    UserLeap *sdk = [UserLeap shared];

    for (NSNumber *raw in [LifecycleEventUtil all]) {
        LifecycleEvent eventType = (LifecycleEvent)raw.integerValue;
        NSString *name = [LifecycleEventUtil stringName:eventType];
        if ([name isEqualToString:@"unknown"]) continue;

        [sdk registerEventListenerFor:eventType listener:^(NSDictionary<NSString *,id> * _Nonnull data) {
            __strong __typeof(weakSelf) strongSelf = weakSelf;
            // Weak capture + _alive guard: a callback firing during teardown must not touch a dead module.
            if (!strongSelf || !strongSelf->_alive) return;
            NSDictionary *payload = [strongSelf normalizePayloadForEvent:name data:data];
            NSString *json = [strongSelf jsonStringFromDictionary:payload];
            [strongSelf emitOnSprigEvent:@{@"name": name, @"json": json}];
        }];
    }
}

- (void)unsubscribeFromSdkEvents
{
    UserLeap *sdk = [UserLeap shared];
    for (NSNumber *raw in [LifecycleEventUtil all]) {
        LifecycleEvent eventType = (LifecycleEvent)raw.integerValue;
        [sdk unregisterAllEventListenersFor:eventType];
    }
}

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

@end
