#import "RNHapticFeedback.h"
#import <CoreHaptics/CoreHaptics.h>
#import <AudioToolbox/AudioToolbox.h>

@interface RNHapticFeedback()
@property (nonatomic, strong) CHHapticEngine *engine;
@property (nonatomic, strong) id<CHHapticPatternPlayer> hapticPlayer;
@end

@implementation RNHapticFeedback
@synthesize bridge = _bridge;

- (void)setBridge:(RCTBridge *)bridge
{
    _bridge = bridge;
    [self initEngine]; // pre-warm on module init to eliminate first-call latency
}

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

RCT_EXPORT_MODULE();

// MARK: - Engine lifecycle

- (void)initEngine
{
    if (_engine) return;

    NSError *error = nil;
    _engine = [[CHHapticEngine alloc] initAndReturnError:&error];
    if (error || !_engine) {
        _engine = nil;
        return;
    }

    __weak __typeof__(self) weakSelf = self;
    _engine.stoppedHandler = ^(CHHapticEngineStoppedReason reason) {
        __strong __typeof__(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            dispatch_async(dispatch_get_main_queue(), ^{
                strongSelf.engine = nil;
                strongSelf.hapticPlayer = nil;
            });
        }
    };

    _engine.resetHandler = ^{
        __strong __typeof__(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            dispatch_async(dispatch_get_main_queue(), ^{
                NSError *startError = nil;
                [strongSelf.engine startAndReturnError:&startError];
            });
        }
    };

    [_engine startAndReturnError:&error];
    if (error) {
        _engine = nil;
    }
}

// MARK: - Helpers

- (CHHapticEvent *)makeTransientEvent:(NSTimeInterval)time
                            intensity:(float)intensity
                            sharpness:(float)sharpness
{
    CHHapticEventParameter *intensityParam = [[CHHapticEventParameter alloc]
        initWithParameterID:CHHapticEventParameterIDHapticIntensity
        value:intensity];
    CHHapticEventParameter *sharpnessParam = [[CHHapticEventParameter alloc]
        initWithParameterID:CHHapticEventParameterIDHapticSharpness
        value:sharpness];
    return [[CHHapticEvent alloc]
        initWithEventType:CHHapticEventTypeHapticTransient
        parameters:@[intensityParam, sharpnessParam]
        relativeTime:time];
}

- (NSArray<CHHapticEvent *> *)eventsForType:(NSString *)type
{
    if ([type isEqual:@"impactLight"]) {
        return @[[self makeTransientEvent:0 intensity:0.3 sharpness:0.3]];
    } else if ([type isEqual:@"impactMedium"]) {
        return @[[self makeTransientEvent:0 intensity:0.6 sharpness:0.6]];
    } else if ([type isEqual:@"impactHeavy"]) {
        return @[[self makeTransientEvent:0 intensity:1.0 sharpness:0.8]];
    } else if ([type isEqual:@"rigid"]) {
        return @[[self makeTransientEvent:0 intensity:0.8 sharpness:1.0]];
    } else if ([type isEqual:@"soft"]) {
        return @[[self makeTransientEvent:0 intensity:0.3 sharpness:0.1]];
    } else if ([type isEqual:@"notificationSuccess"]) {
        return @[
            [self makeTransientEvent:0    intensity:0.4 sharpness:0.4],
            [self makeTransientEvent:0.1  intensity:0.8 sharpness:0.6],
        ];
    } else if ([type isEqual:@"notificationWarning"]) {
        return @[
            [self makeTransientEvent:0    intensity:0.6 sharpness:0.5],
            [self makeTransientEvent:0.15 intensity:0.8 sharpness:0.7],
        ];
    } else if ([type isEqual:@"notificationError"]) {
        return @[
            [self makeTransientEvent:0    intensity:1.0 sharpness:0.6],
            [self makeTransientEvent:0.1  intensity:0.6 sharpness:0.4],
            [self makeTransientEvent:0.2  intensity:0.8 sharpness:0.5],
        ];
    } else if ([type isEqual:@"confirm"]) {
        return @[
            [self makeTransientEvent:0    intensity:0.5 sharpness:0.5],
            [self makeTransientEvent:0.1  intensity:1.0 sharpness:0.7],
        ];
    } else if ([type isEqual:@"reject"]) {
        return @[
            [self makeTransientEvent:0    intensity:1.0 sharpness:0.7],
            [self makeTransientEvent:0.1  intensity:0.7 sharpness:0.5],
            [self makeTransientEvent:0.2  intensity:0.4 sharpness:0.3],
        ];
    } else if ([type isEqual:@"gestureStart"]) {
        return @[[self makeTransientEvent:0 intensity:0.3 sharpness:0.6]];
    } else if ([type isEqual:@"gestureEnd"]) {
        return @[[self makeTransientEvent:0 intensity:0.5 sharpness:0.6]];
    } else if ([type isEqual:@"segmentTick"] || [type isEqual:@"segmentFrequentTick"]) {
        return @[[self makeTransientEvent:0 intensity:0.2 sharpness:0.9]];
    } else if ([type isEqual:@"toggleOn"]) {
        return @[
            [self makeTransientEvent:0    intensity:0.3 sharpness:0.4],
            [self makeTransientEvent:0.08 intensity:0.6 sharpness:0.6],
        ];
    } else if ([type isEqual:@"toggleOff"]) {
        return @[
            [self makeTransientEvent:0    intensity:0.6 sharpness:0.6],
            [self makeTransientEvent:0.08 intensity:0.3 sharpness:0.4],
        ];
    } else if ([type isEqual:@"clockTick"]) {
        return @[[self makeTransientEvent:0 intensity:0.25 sharpness:0.9]];
    } else if ([type isEqual:@"contextClick"]) {
        return @[[self makeTransientEvent:0 intensity:0.4 sharpness:0.5]];
    } else if ([type isEqual:@"keyboardPress"] || [type isEqual:@"virtualKey"]) {
        return @[[self makeTransientEvent:0 intensity:0.3 sharpness:0.7]];
    } else if ([type isEqual:@"keyboardRelease"] || [type isEqual:@"virtualKeyRelease"]) {
        return @[[self makeTransientEvent:0 intensity:0.2 sharpness:0.5]];
    } else if ([type isEqual:@"keyboardTap"]) {
        return @[[self makeTransientEvent:0 intensity:0.3 sharpness:0.7]];
    } else if ([type isEqual:@"longPress"]) {
        return @[[self makeTransientEvent:0 intensity:0.7 sharpness:0.3]];
    } else if ([type isEqual:@"textHandleMove"]) {
        return @[[self makeTransientEvent:0 intensity:0.15 sharpness:0.4]];
    } else if ([type isEqual:@"effectClick"]) {
        return @[[self makeTransientEvent:0 intensity:0.5 sharpness:0.6]];
    } else if ([type isEqual:@"effectDoubleClick"]) {
        return @[
            [self makeTransientEvent:0    intensity:0.5 sharpness:0.6],
            [self makeTransientEvent:0.05 intensity:0.5 sharpness:0.6],
        ];
    } else if ([type isEqual:@"effectHeavyClick"]) {
        return @[[self makeTransientEvent:0 intensity:1.0 sharpness:0.7]];
    } else if ([type isEqual:@"effectTick"]) {
        return @[[self makeTransientEvent:0 intensity:0.3 sharpness:0.8]];
    } else if ([type isEqual:@"dragStart"]) {
        return @[[self makeTransientEvent:0 intensity:0.3 sharpness:0.6]];
    } else if ([type isEqual:@"gestureThresholdActivate"]) {
        return @[[self makeTransientEvent:0 intensity:0.5 sharpness:0.7]];
    } else if ([type isEqual:@"gestureThresholdDeactivate"]) {
        return @[[self makeTransientEvent:0 intensity:0.2 sharpness:0.4]];
    } else if ([type isEqual:@"noHaptics"]) {
        return @[]; // explicit no-op
    } else {
        // selection and any unrecognised type
        return @[[self makeTransientEvent:0 intensity:0.2 sharpness:0.5]];
    }
}

// MARK: - UIKit fallback (Taptic Engine devices without Core Haptics, e.g. iPhone 6s/7/SE 1st gen)

- (void)playUIKitHaptic:(NSString *)type
{
    if ([type isEqual:@"notificationSuccess"]) {
        UINotificationFeedbackGenerator *g = [UINotificationFeedbackGenerator new];
        [g notificationOccurred:UINotificationFeedbackTypeSuccess];
    } else if ([type isEqual:@"notificationWarning"]) {
        UINotificationFeedbackGenerator *g = [UINotificationFeedbackGenerator new];
        [g notificationOccurred:UINotificationFeedbackTypeWarning];
    } else if ([type isEqual:@"notificationError"] || [type isEqual:@"reject"]) {
        UINotificationFeedbackGenerator *g = [UINotificationFeedbackGenerator new];
        [g notificationOccurred:UINotificationFeedbackTypeError];
    } else if ([type isEqual:@"impactLight"] || [type isEqual:@"soft"]
               || [type isEqual:@"effectTick"] || [type isEqual:@"clockTick"]
               || [type isEqual:@"gestureStart"] || [type isEqual:@"segmentTick"]
               || [type isEqual:@"segmentFrequentTick"] || [type isEqual:@"textHandleMove"]) {
        UIImpactFeedbackGenerator *g = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
        [g impactOccurred];
    } else if ([type isEqual:@"impactHeavy"] || [type isEqual:@"rigid"]
               || [type isEqual:@"effectHeavyClick"] || [type isEqual:@"longPress"]) {
        UIImpactFeedbackGenerator *g = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleHeavy];
        [g impactOccurred];
    } else if ([type isEqual:@"selection"] || [type isEqual:@"keyboardPress"]
               || [type isEqual:@"keyboardRelease"] || [type isEqual:@"keyboardTap"]
               || [type isEqual:@"virtualKey"] || [type isEqual:@"virtualKeyRelease"]
               || [type isEqual:@"gestureEnd"] || [type isEqual:@"contextClick"]) {
        UISelectionFeedbackGenerator *g = [UISelectionFeedbackGenerator new];
        [g selectionChanged];
    } else if ([type isEqual:@"noHaptics"]) {
        // explicit no-op
    } else {
        // impactMedium, confirm, toggleOn, toggleOff, effectClick, effectDoubleClick,
        // effectHeavyClick, dragStart, gestureThresholdActivate, gestureThresholdDeactivate
        // and any unrecognised type
        UIImpactFeedbackGenerator *g = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleMedium];
        [g impactOccurred];
    }
}

- (void)playEvents:(NSArray<CHHapticEvent *> *)events
{
    if (events.count == 0) return;
    [self initEngine];
    if (!_engine) return;

    NSError *error = nil;
    CHHapticPattern *pattern = [[CHHapticPattern alloc]
        initWithEvents:events parameters:@[] error:&error];
    if (error) return;

    id<CHHapticPatternPlayer> player = [_engine createPlayerWithPattern:pattern error:&error];
    if (error) return;

    _hapticPlayer = player;
    [player startAtTime:CHHapticTimeImmediate error:&error];
}

// MARK: - Exported methods

#ifdef RCT_NEW_ARCH_ENABLED
RCT_EXPORT_METHOD(trigger:(NSString *)type options:(JS::NativeHapticFeedback::SpecTriggerOptions&)options)
{
    BOOL enableVibrateFallback = options.enableVibrateFallback().value();
#else
RCT_EXPORT_METHOD(trigger:(NSString *)type options:(NSDictionary *)options)
{
    BOOL enableVibrateFallback = [[options objectForKey:@"enableVibrateFallback"] boolValue];
#endif
    if ([CHHapticEngine capabilitiesForHardware].supportsHaptics) {
        // Best path: Core Haptics — per-type patterns on iPhone 8+ / iPad Pro
        [self playEvents:[self eventsForType:type]];
        return;
    }
    // Intermediate fallback: UIKit feedback generators — per-type on devices with
    // a Taptic Engine but without Core Haptics (iPhone 6s, 7, SE 1st gen on iOS 13+).
    // Silent no-op on devices with no Taptic Engine at all (e.g. iPod touch).
    // Note: enableVibrateFallback is NOT triggered here. On iOS 13+ all iPhones
    // capable of running this SDK have a Taptic Engine, so UIKit already handles them.
    // Adding AudioServicesPlaySystemSound here would cause a double-fire on 6s/7.
    [self playUIKitHaptic:type];
    (void)enableVibrateFallback; // consumed; not used on UIKit path
}

RCT_EXPORT_METHOD(stop)
{
    if (_hapticPlayer) {
        NSError *error = nil;
        [_hapticPlayer cancelAndReturnError:&error];
        _hapticPlayer = nil;
    }
    if (_engine) {
        [_engine stopWithCompletionHandler:nil];
        _engine = nil;
    }
}

#ifdef RCT_NEW_ARCH_ENABLED
- (NSNumber *)isSupported
{
    return @([CHHapticEngine capabilitiesForHardware].supportsHaptics);
}
#else
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, isSupported)
{
    return @([CHHapticEngine capabilitiesForHardware].supportsHaptics);
}
#endif

#ifdef RCT_NEW_ARCH_ENABLED
RCT_EXPORT_METHOD(triggerPattern:(NSArray *)events options:(JS::NativeHapticFeedback::SpecTriggerPatternOptions&)options)
{
    BOOL enableVibrateFallback = options.enableVibrateFallback().value();
#else
RCT_EXPORT_METHOD(triggerPattern:(NSArray *)events options:(NSDictionary *)options)
{
    BOOL enableVibrateFallback = [[options objectForKey:@"enableVibrateFallback"] boolValue];
#endif
    if (![CHHapticEngine capabilitiesForHardware].supportsHaptics) {
        if (enableVibrateFallback) {
            AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
        }
        return;
    }

    NSMutableArray<CHHapticEvent *> *hapticEvents = [NSMutableArray array];
    for (NSDictionary *evt in events) {
        NSTimeInterval time = [evt[@"time"] doubleValue] / 1000.0;
        float intensity = evt[@"intensity"] ? [evt[@"intensity"] floatValue] : 0.5f;
        float sharpness = evt[@"sharpness"] ? [evt[@"sharpness"] floatValue] : 0.5f;
        NSString *type = evt[@"type"] ?: @"transient";

        CHHapticEventParameter *intensityParam = [[CHHapticEventParameter alloc]
            initWithParameterID:CHHapticEventParameterIDHapticIntensity value:intensity];
        CHHapticEventParameter *sharpnessParam = [[CHHapticEventParameter alloc]
            initWithParameterID:CHHapticEventParameterIDHapticSharpness value:sharpness];

        CHHapticEvent *hapticEvent;
        if ([type isEqual:@"continuous"]) {
            NSTimeInterval duration = evt[@"duration"] ? [evt[@"duration"] doubleValue] / 1000.0 : 0.1;
            hapticEvent = [[CHHapticEvent alloc]
                initWithEventType:CHHapticEventTypeHapticContinuous
                parameters:@[intensityParam, sharpnessParam]
                relativeTime:time
                duration:duration];
        } else {
            hapticEvent = [[CHHapticEvent alloc]
                initWithEventType:CHHapticEventTypeHapticTransient
                parameters:@[intensityParam, sharpnessParam]
                relativeTime:time];
        }
        [hapticEvents addObject:hapticEvent];
    }

    if (hapticEvents.count > 0) {
        [self playEvents:hapticEvents];
    }
}

#ifdef RCT_NEW_ARCH_ENABLED
RCT_EXPORT_METHOD(playAHAP:(NSString *)fileName
                  resolve:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject)
#else
RCT_EXPORT_METHOD(playAHAP:(NSString *)fileName
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
#endif
{
    [self initEngine];
    if (!_engine) {
        reject(@"engine_unavailable", @"CHHapticEngine could not be initialised", nil);
        return;
    }

    // Search in <bundle>/haptics/ subdirectory first, then bundle root
    NSString *fullPath = [[NSBundle mainBundle] pathForResource:fileName
                                                         ofType:nil
                                                    inDirectory:@"haptics"];
    if (!fullPath) {
        fullPath = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
    }
    NSURL *fileURL = fullPath ? [NSURL fileURLWithPath:fullPath] : nil;

    if (!fileURL) {
        reject(@"file_not_found", [NSString stringWithFormat:@"AHAP file not found: %@", fileName], nil);
        return;
    }

    NSError *error = nil;
    [_engine playPatternFromURL:fileURL error:&error];
    if (error) {
        reject(@"playback_error", error.localizedDescription, error);
    } else {
        resolve(nil);
    }
}

#ifdef RCT_NEW_ARCH_ENABLED
RCT_EXPORT_METHOD(getSystemHapticStatus:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject)
#else
RCT_EXPORT_METHOD(getSystemHapticStatus:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
#endif
{
    BOOL vibrationEnabled = [CHHapticEngine capabilitiesForHardware].supportsHaptics;
    resolve(@{
        @"vibrationEnabled": @(vibrationEnabled),
        @"ringerMode": [NSNull null],
    });
}

// MARK: - New arch TurboModule

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

@end
