#import <Foundation/Foundation.h>
#import "RNCPagerViewComponentView.h"
#import <react/renderer/components/pagerview/ComponentDescriptors.h>
#import <react/renderer/components/pagerview/EventEmitters.h>
#import <react/renderer/components/pagerview/Props.h>
#import <react/renderer/components/pagerview/RCTComponentViewHelpers.h>

#import "RCTFabricComponentsPlugins.h"
#import "React/RCTConversions.h"

#import "RCTOnPageScrollEvent.h"

using namespace facebook::react;


@interface RNCPagerViewComponentView () <RCTRNCViewPagerViewProtocol, UIPageViewControllerDataSource, UIPageViewControllerDelegate, UIScrollViewDelegate, UIGestureRecognizerDelegate>

@property(nonatomic, strong) UIPageViewController *nativePageViewController;
@property(nonatomic, strong) NSMutableArray<UIViewController *> *nativeChildrenViewControllers;

@end

@implementation RNCPagerViewComponentView {
    LayoutMetrics _layoutMetrics;
    LayoutMetrics _oldLayoutMetrics;
    UIScrollView *scrollView;
    BOOL transitioning;
    NSInteger _currentIndex;
    NSInteger _destinationIndex;
    BOOL _overdrag;
    NSString *_layoutDirection;
    BOOL _scrollEnabled;
    // _isBeingRecycled: synchronous guard, only true during prepareForRecycle execution
    BOOL _isBeingRecycled;
    // _generation: async guard for recycle-and-reuse, incremented in prepareForRecycle
    NSUInteger _generation;
    // _transitionId: async guard for superseded transitions, incremented each setPagerViewControllers call
    NSUInteger _transitionId;
    // _pendingGoToIndex: stores the latest blocked animated transition target, drained after current transition completes
    NSInteger _pendingGoToIndex;
    // _nestedScrollEnabled: when YES, this inner PagerView coordinates gestures with an outer PagerView
    BOOL _nestedScrollEnabled;
    NSHashTable<id<RNCPagerViewNestedOverlayObserver>> *_nestedOverlayObservers;
    // _blockerGesture: gate gesture that blocks inner scroll at page boundaries when nested
    UIPanGestureRecognizer *_blockerGesture;
}

- (void)notifyNestedOverlayObservers {
    UIView *activePage = _currentIndex >= 0 &&
        _currentIndex < _nativeChildrenViewControllers.count
        ? _nativeChildrenViewControllers[_currentIndex].view : nil;
    for (id<RNCPagerViewNestedOverlayObserver> observer in _nestedOverlayObservers) {
        BOOL active = activePage != nil &&
            [(UIView *)observer isDescendantOfView:activePage];
        [observer outerPagerDidUpdateOverlayOwner:active transitioning:transitioning || scrollView.dragging || scrollView.decelerating];
    }
}

- (void)addNestedOverlayObserver:(id<RNCPagerViewNestedOverlayObserver>)observer {
    if (_nestedOverlayObservers == nil) _nestedOverlayObservers = [NSHashTable weakObjectsHashTable];
    [_nestedOverlayObservers addObject:observer];
    [self notifyNestedOverlayObservers];
}

- (void)removeNestedOverlayObserver:(id<RNCPagerViewNestedOverlayObserver>)observer {
    [_nestedOverlayObservers removeObject:observer];
}

// Needed because of this: https://github.com/facebook/react-native/pull/37274
+ (void)load
{
  [super load];
}

- (void)initializeNativePageViewController {
    const auto &viewProps = *std::static_pointer_cast<const RNCViewPagerProps>(_props);
    NSDictionary *options = @{ UIPageViewControllerOptionInterPageSpacingKey: @(viewProps.pageMargin) };
    UIPageViewControllerNavigationOrientation orientation = UIPageViewControllerNavigationOrientationHorizontal;
    switch (viewProps.orientation) {
        case RNCViewPagerOrientation::Horizontal:
            orientation = UIPageViewControllerNavigationOrientationHorizontal;
            break;
        case RNCViewPagerOrientation::Vertical:
            orientation = UIPageViewControllerNavigationOrientationVertical;
            break;
    }
    _nativePageViewController = [[UIPageViewController alloc]
                                 initWithTransitionStyle: UIPageViewControllerTransitionStyleScroll
                                 navigationOrientation:orientation
                                 options:options];
    _nativePageViewController.dataSource = self;
    _nativePageViewController.delegate = self;
    _nativePageViewController.view.frame = self.frame;
    self.contentView = _nativePageViewController.view;
    
    for (UIView *subview in _nativePageViewController.view.subviews) {
        if([subview isKindOfClass:UIScrollView.class]){
            ((UIScrollView *)subview).delegate = self;
            ((UIScrollView *)subview).delaysContentTouches = NO;
            scrollView = (UIScrollView *)subview;
        }
    }

    [self applyScrollEnabled];
    [self applyNestedScrollBlocker];
}

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        static const auto defaultProps = std::make_shared<const RNCViewPagerProps>();
        _props = defaultProps;
        _nativeChildrenViewControllers = [[NSMutableArray alloc] init];
        _currentIndex = -1;
        _destinationIndex = -1;
        _layoutDirection = @"ltr";
        _overdrag = NO;
        _scrollEnabled = YES;
        _isBeingRecycled = NO;
        _pendingGoToIndex = -1;
        _nestedScrollEnabled = NO;
    }
    
    return self;
}

- (void)willMoveToSuperview:(UIView *)newSuperview {
    if (newSuperview != nil) {
        [self initializeNativePageViewController];
        [self goTo:_currentIndex animated:NO];
    }
}


#pragma mark - React API

- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
    if (_isBeingRecycled) {
        return;
    }
    UIViewController *vc = [UIViewController new];
    [vc.view addSubview:childComponentView];
    [_nativeChildrenViewControllers insertObject:vc atIndex:index];
    [self goTo:_currentIndex animated:NO];
}

- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
    if (_isBeingRecycled) {
        return;
    }

    if (index >= _nativeChildrenViewControllers.count) {
        return;
    }

    [childComponentView removeFromSuperview];
    [_nativeChildrenViewControllers removeObjectAtIndex:index];
 
    NSInteger maxPage = _nativeChildrenViewControllers.count - 1;
    
    if (_currentIndex >= maxPage) {
        if (maxPage >= 0) {
            [self goTo:maxPage animated:NO];
        }
    }
}


-(void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics {
    _oldLayoutMetrics = oldLayoutMetrics;
    _layoutMetrics = layoutMetrics;
  
    if (transitioning) {
      return;
    }
  
    [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:_oldLayoutMetrics];
}


-(void)prepareForRecycle {
    _isBeingRecycled = YES;
    _generation++;
    [_nestedOverlayObservers removeAllObjects];

    // Cancel any ongoing transitions
    if (_nativePageViewController != nil && transitioning) {
        transitioning = NO;
    }

    // Clear view controllers before recycling
    [_nativeChildrenViewControllers removeAllObjects];

    // Remove blocker gesture before recycling
    if (_blockerGesture != nil) {
        [self removeGestureRecognizer:_blockerGesture];
        _blockerGesture = nil;
    }

    [super prepareForRecycle];
    _nativePageViewController = nil;
    _currentIndex = -1;
    _scrollEnabled = YES;
    _pendingGoToIndex = -1;
    _nestedScrollEnabled = NO;
    _isBeingRecycled = NO;
}

- (void)shouldDismissKeyboard:(RNCViewPagerKeyboardDismissMode)dismissKeyboard {
#if !TARGET_OS_VISION
    UIScrollViewKeyboardDismissMode dismissKeyboardMode = UIScrollViewKeyboardDismissModeNone;
    switch (dismissKeyboard) {
        case RNCViewPagerKeyboardDismissMode::None:
            dismissKeyboardMode = UIScrollViewKeyboardDismissModeNone;
            break;
        case RNCViewPagerKeyboardDismissMode::OnDrag:
            dismissKeyboardMode = UIScrollViewKeyboardDismissModeOnDrag;
            break;
    }
    scrollView.keyboardDismissMode = dismissKeyboardMode;
#endif
}

- (void)applyScrollEnabled {
  scrollView.scrollEnabled = _scrollEnabled;
}


- (void)updateProps:(const facebook::react::Props::Shared &)props oldProps:(const facebook::react::Props::Shared &)oldProps{
    const auto &oldScreenProps = *std::static_pointer_cast<const RNCViewPagerProps>(_props);
    const auto &newScreenProps = *std::static_pointer_cast<const RNCViewPagerProps>(props);
    
    // change index only once
    if (_currentIndex == -1) {
        _currentIndex = newScreenProps.initialPage;
        [self shouldDismissKeyboard: newScreenProps.keyboardDismissMode];
    }
    
    const auto newLayoutDirectionStr = RCTNSStringFromString(toString(newScreenProps.layoutDirection));
    
    
    if (_layoutDirection != newLayoutDirectionStr) {
        _layoutDirection = newLayoutDirectionStr;
    }
    
    if (oldScreenProps.keyboardDismissMode != newScreenProps.keyboardDismissMode) {
        [self shouldDismissKeyboard: newScreenProps.keyboardDismissMode];
    }
    
    if (oldScreenProps.scrollEnabled != newScreenProps.scrollEnabled) {
        _scrollEnabled = newScreenProps.scrollEnabled;
        [self applyScrollEnabled];
    }
    
    if (newScreenProps.overdrag != _overdrag) {
        _overdrag = newScreenProps.overdrag;
    }

    if (oldScreenProps.nestedScrollEnabled != newScreenProps.nestedScrollEnabled) {
        _nestedScrollEnabled = newScreenProps.nestedScrollEnabled;
        [self applyNestedScrollBlocker];
    }

    [super updateProps:props oldProps:oldProps];
}


#pragma mark - Internal methods

- (void)disableSwipe {
    self.nativePageViewController.view.userInteractionEnabled = NO;
}

- (void)enableSwipe {
    self.nativePageViewController.view.userInteractionEnabled = YES;
}

- (void)goTo:(NSInteger)index animated:(BOOL)animated {
    NSInteger numberOfPages = _nativeChildrenViewControllers.count;
    
    // Check recycling state BEFORE disableSwipe to avoid state inconsistency
    if (_isBeingRecycled) {
#ifdef DEBUG
        NSLog(@"[PagerView] goTo blocked: view is being recycled (index=%ld)", (long)index);
#endif
        return;
    }

    [self disableSwipe];
    
    _destinationIndex = index;
    
    
    if (numberOfPages == 0 || index < 0 || index > numberOfPages - 1) {
        [self enableSwipe];
        return;
    }
    
    BOOL isForward = (index > _currentIndex && [self isLtrLayout]) || (index < _currentIndex && ![self isLtrLayout]);
    UIPageViewControllerNavigationDirection direction = isForward ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse;
    
    long diff = labs(index - _currentIndex);
    
    [self setPagerViewControllers:index
                        direction:direction
                         animated:diff == 0 ? NO : animated];
    
}

- (void)setPagerViewControllers:(NSInteger)index
                      direction:(UIPageViewControllerNavigationDirection)direction
                       animated:(BOOL)animated{
    if (_nativePageViewController == nil || _isBeingRecycled) {
#ifdef DEBUG
        NSLog(@"[PagerView] setPagerViewControllers blocked: pageVC=%@ recycled=%d index=%ld",
              _nativePageViewController ? @"EXISTS" : @"NIL", _isBeingRecycled, (long)index);
#endif
        [self enableSwipe];
        return;
    }
  
    // Validate index bounds
    if (index < 0 || index >= _nativeChildrenViewControllers.count) {
#ifdef DEBUG
        NSLog(@"[PagerView] setPagerViewControllers blocked: index %ld out of bounds (count=%lu)",
              (long)index, (unsigned long)_nativeChildrenViewControllers.count);
#endif
        [self enableSwipe];
        return;
    }

    // Validate view controller and its view
    UIViewController *targetVC = [_nativeChildrenViewControllers objectAtIndex:index];
    if (targetVC == nil || targetVC.view == nil) {
#ifdef DEBUG
        NSLog(@"[PagerView] setPagerViewControllers blocked: targetVC=%@ view=%@ index=%ld",
              targetVC ? @"EXISTS" : @"NIL", targetVC.view ? @"EXISTS" : @"NIL", (long)index);
#endif
        [self enableSwipe];
        return;
    }

    // Note: Do NOT check superview/window == nil here!
    // Target page views are expected to have nil superview/window before being added to pageViewController
    // Checking for nil superview/window would block all legitimate page transitions

    // Prevent calling setViewControllers during an ongoing animated transition.
    // UIPageViewController's internal _UIQueuingScrollView does not support
    // concurrent enqueued transitions and will crash with
    // "Duplicate states in queue" (NSInternalInconsistencyException).
    // Note: Non-animated transitions (animated=NO) are synchronous and bypass
    // the internal _UIQueuingScrollView queue, so they are safe during an ongoing transition.
    if (transitioning && animated) {
#ifdef DEBUG
        NSLog(@"[PagerView] setPagerViewControllers deferred: already transitioning (index=%ld)", (long)index);
#endif
        _pendingGoToIndex = index;
        [self enableSwipe];
        return;
    }

    // Clear any pending transition since we are now starting a fresh one
    _pendingGoToIndex = -1;

    transitioning = YES;
    [self notifyNestedOverlayObservers];

    __weak RNCPagerViewComponentView *weakSelf = self;
    NSUInteger capturedGeneration = _generation;
    _transitionId++;
    NSUInteger capturedTransitionId = _transitionId;
    [_nativePageViewController setViewControllers:@[targetVC]
                                        direction:direction
                                         animated:animated
                                       completion:^(BOOL finished) {
        __strong RNCPagerViewComponentView *strongSelf = weakSelf;
        if (strongSelf == nil) {
            return;
        }

        // View was recycled and possibly reused since this animation started
        if (strongSelf->_isBeingRecycled || strongSelf->_generation != capturedGeneration) {
            // Do NOT reset transitioning here — a new lifecycle owns that state
            return;
        }

        // A newer transition was started, this completion is stale
        if (strongSelf->_transitionId != capturedTransitionId) {
            // Do NOT reset transitioning — the newer transition owns that state
            return;
        }

        // Only reset transitioning when this completion corresponds to the current transition
        strongSelf->transitioning = NO;

        [strongSelf enableSwipe];
        strongSelf->_currentIndex = index;
        [strongSelf notifyNestedOverlayObservers];
        if (strongSelf->_eventEmitter != nullptr ) {
            const auto eventEmitter = [strongSelf pagerEventEmitter];
            if (eventEmitter) {
                int position = (int) index;
                eventEmitter->onPageSelected(RNCViewPagerEventEmitter::OnPageSelected{.position =  static_cast<double>(position)});
            }
        }
        [strongSelf updateLayoutMetrics:strongSelf->_layoutMetrics oldLayoutMetrics:strongSelf->_oldLayoutMetrics];

        // Drain pending transition that was blocked while this animation was in-flight
        NSInteger pending = strongSelf->_pendingGoToIndex;
        if (pending >= 0 && pending != index) {
            strongSelf->_pendingGoToIndex = -1;
            [strongSelf goTo:pending animated:YES];
        }
    }];
}


- (UIViewController *)nextControllerForController:(UIViewController *)controller
                                      inDirection:(UIPageViewControllerNavigationDirection)direction {
    NSUInteger numberOfPages = _nativeChildrenViewControllers.count;

    if (_isBeingRecycled || numberOfPages == 0) {
        return nil;
    }

    NSInteger index = [_nativeChildrenViewControllers indexOfObject:controller];
    
    if (index == NSNotFound) {
        return nil;
    }
    
    direction == UIPageViewControllerNavigationDirectionForward ? index++ : index--;
    
    if (index < 0 || (index > (numberOfPages - 1))) {
        return nil;
    }
    
    return [_nativeChildrenViewControllers objectAtIndex:index];
}

- (UIViewController *)currentlyDisplayed {
    return _nativePageViewController.viewControllers.firstObject;
}

#pragma mark - UIScrollViewDelegate

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    if (_isBeingRecycled) {
        return;
    }
    [self notifyNestedOverlayObservers];
    const auto eventEmitter = [self pagerEventEmitter];
    if (!eventEmitter) {
        return;
    }
    eventEmitter->onPageScrollStateChanged(RNCViewPagerEventEmitter::OnPageScrollStateChanged{.pageScrollState =  RNCViewPagerEventEmitter::OnPageScrollStateChangedPageScrollState::Dragging });
}

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
    if (_isBeingRecycled) {
        return;
    }
    const auto eventEmitter = [self pagerEventEmitter];
    if (!eventEmitter) {
        return;
    }
    eventEmitter->onPageScrollStateChanged(RNCViewPagerEventEmitter::OnPageScrollStateChanged{.pageScrollState =  RNCViewPagerEventEmitter::OnPageScrollStateChangedPageScrollState::Settling });
    
    if (!_overdrag) {
        NSInteger maxIndex = _nativeChildrenViewControllers.count - 1;
        BOOL isFirstPage = [self isLtrLayout] ? _currentIndex == 0 : _currentIndex == maxIndex;
        BOOL isLastPage = [self isLtrLayout] ? _currentIndex == maxIndex : _currentIndex == 0;
        CGFloat contentOffset = [self isHorizontal] ? scrollView.contentOffset.x : scrollView.contentOffset.y;
        CGFloat topBound = [self isHorizontal] ? scrollView.bounds.size.width : scrollView.bounds.size.height;
        
        if ((isFirstPage && contentOffset <= topBound) || (isLastPage && contentOffset >= topBound)) {
            CGPoint croppedOffset = [self isHorizontal] ? CGPointMake(topBound, 0) : CGPointMake(0, topBound);
            *targetContentOffset = croppedOffset;
            
            eventEmitter->onPageScrollStateChanged(RNCViewPagerEventEmitter::OnPageScrollStateChanged{.pageScrollState =  RNCViewPagerEventEmitter::OnPageScrollStateChangedPageScrollState::Idle });
        }
    }
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (!decelerate) [self notifyNestedOverlayObservers];
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    [self notifyNestedOverlayObservers];
    if (_isBeingRecycled) {
        return;
    }
    const auto eventEmitter = [self pagerEventEmitter];
    if (!eventEmitter) {
        return;
    }
    eventEmitter->onPageScrollStateChanged(RNCViewPagerEventEmitter::OnPageScrollStateChanged{.pageScrollState =  RNCViewPagerEventEmitter::OnPageScrollStateChangedPageScrollState::Idle });
}


- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (_isBeingRecycled) {
        return;
    }
    BOOL isHorizontal = [self isHorizontal];
    CGFloat contentOffset = isHorizontal ? scrollView.contentOffset.x : scrollView.contentOffset.y;
    CGFloat frameSize = isHorizontal ? scrollView.frame.size.width : scrollView.frame.size.height;
    
    if (frameSize == 0) {
        return;
    }

    float offset = (contentOffset - frameSize) / frameSize;
    float absoluteOffset = fabs(offset);
    NSInteger position = _currentIndex;
    
    BOOL isHorizontalRtl = [self isHorizontalRtlLayout];
    BOOL isAnimatingBackwards = isHorizontalRtl ? offset > 0.05f : offset < 0;
    BOOL isBeingMovedByNestedScrollView = !scrollView.isDragging && !scrollView.isTracking;
    if (scrollView.isDragging || isBeingMovedByNestedScrollView) {
        _destinationIndex = isAnimatingBackwards ? _currentIndex - 1 : _currentIndex + 1;
    }
    
    if (isAnimatingBackwards) {
        position = _destinationIndex;
        absoluteOffset = fmax(0, 1 - absoluteOffset);
    }
    
    if (!_overdrag) {
        NSInteger maxIndex = _nativeChildrenViewControllers.count - 1;
        NSInteger firstPageIndex = isHorizontalRtl ? maxIndex : 0;
        NSInteger lastPageIndex = isHorizontalRtl ? 0 : maxIndex;
        BOOL isFirstPage = _currentIndex == firstPageIndex;
        BOOL isLastPage = _currentIndex == lastPageIndex;
        CGFloat topBound = isHorizontal ? scrollView.bounds.size.width : scrollView.bounds.size.height;
        
        if ((isFirstPage && contentOffset <= topBound) || (isLastPage && contentOffset >= topBound)) {
            CGPoint croppedOffset = isHorizontal ? CGPointMake(topBound, 0) : CGPointMake(0, topBound);
            scrollView.contentOffset = croppedOffset;
            absoluteOffset = 0;
            position = isLastPage ? lastPageIndex : firstPageIndex;
        }
    }
    
    float interpolatedOffset = absoluteOffset * labs(_destinationIndex - _currentIndex);
    [self sendScrollEventsForPosition:position offset:interpolatedOffset];
}


#pragma mark - UIPageViewControllerDelegate

- (void)pageViewController:(UIPageViewController *)pageViewController
        didFinishAnimating:(BOOL)finished
   previousViewControllers:(nonnull NSArray<UIViewController *> *)previousViewControllers
       transitionCompleted:(BOOL)completed {
    if (_isBeingRecycled) {
        return;
    }
    if (completed) {
        UIViewController* currentVC = [self currentlyDisplayed];
        NSUInteger currentIndex = [_nativeChildrenViewControllers indexOfObject:currentVC];
        _currentIndex = currentIndex;
        [self notifyNestedOverlayObservers];
        int position = (int) currentIndex;
        const auto eventEmitter = [self pagerEventEmitter];
        if (eventEmitter) {
            eventEmitter->onPageSelected(RNCViewPagerEventEmitter::OnPageSelected{.position =  static_cast<double>(position)});
        }
    }
    if (!completed) [self notifyNestedOverlayObservers];
}

#pragma mark - UIPageViewControllerDataSource

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
       viewControllerAfterViewController:(UIViewController *)viewController {
    
    UIPageViewControllerNavigationDirection direction = [self isLtrLayout] ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse;
    return [self nextControllerForController:viewController inDirection:direction];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
      viewControllerBeforeViewController:(UIViewController *)viewController {
    UIPageViewControllerNavigationDirection direction = [self isLtrLayout] ? UIPageViewControllerNavigationDirectionReverse : UIPageViewControllerNavigationDirectionForward;
    return [self nextControllerForController:viewController inDirection:direction];
}

#pragma mark - Imperative methods exposed to React Native

- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
   RCTRNCViewPagerHandleCommand(self, commandName, args);
}

- (void)setPage:(NSInteger)index {
    [self goTo:index animated:YES];
}

- (void)setPageWithoutAnimation:(NSInteger)index {
    [self goTo:index animated:NO];
}

- (void)setScrollEnabledImperatively:(BOOL)scrollEnabled {
    _scrollEnabled = scrollEnabled;
    [self applyScrollEnabled];
}

#pragma mark - Nested Scroll Gesture Coordination

- (void)applyNestedScrollBlocker {
    if (scrollView == nil) return;

    if (_nestedScrollEnabled && _blockerGesture == nil) {
        _blockerGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(blockerGestureFired:)];
        _blockerGesture.delegate = self;
        // Cancel touches so the blocker never actually scrolls anything
        _blockerGesture.cancelsTouchesInView = NO;
        _blockerGesture.delaysTouchesBegan = NO;
        [self addGestureRecognizer:_blockerGesture];
        // Inner scroll view pan must wait for the blocker to fail before it can begin
        [scrollView.panGestureRecognizer requireGestureRecognizerToFail:_blockerGesture];
    } else if (!_nestedScrollEnabled && _blockerGesture != nil) {
        [self removeGestureRecognizer:_blockerGesture];
        _blockerGesture = nil;
    }
}

// The blocker gesture never actually does anything — it only serves as a gate
- (void)blockerGestureFired:(UIPanGestureRecognizer *)recognizer {
    // Reset through the public enabled API. UIGestureRecognizer.state is
    // read-only for clients; only recognizer subclasses may assign it.
    if (recognizer.state == UIGestureRecognizerStateBegan) {
        recognizer.enabled = NO;
        recognizer.enabled = YES;
    }
}

#pragma mark - UIGestureRecognizerDelegate

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if (gestureRecognizer != _blockerGesture) return YES;
    if (!_nestedScrollEnabled) return NO;
    if (![gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) return NO;

    UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;
    CGPoint velocity = [pan velocityInView:self];
    NSInteger pageCount = _nativeChildrenViewControllers.count;

    if (pageCount == 0) return NO;
    if (_currentIndex < 0 || _currentIndex >= pageCount) return NO;

    // Nested coordination is designed for horizontal inner/outer pagers only.
    // For vertical pagers, do not block so inner scrolling keeps working.
    BOOL isHorizontal = [self isHorizontal];
    if (!isHorizontal) return NO;

    // Only edge-block for gestures that are primarily horizontal.
    // This avoids accidental outer-tab switches on vertical/diagonal content scrolls.
    CGFloat absVelocityX = fabs(velocity.x);
    CGFloat absVelocityY = fabs(velocity.y);
    if (absVelocityX <= absVelocityY) return NO;
    if (absVelocityX < 1.0f) return NO;

    BOOL isLtr = [self isLtrLayout];
    NSInteger firstPageIndex = isLtr ? 0 : pageCount - 1;
    NSInteger lastPageIndex = isLtr ? pageCount - 1 : 0;

    // At first page, swiping backward → blocker begins → inner scroll blocked → outer takes over
    if (_currentIndex == firstPageIndex && velocity.x > 0) return YES;
    // At last page, swiping forward → blocker begins → inner scroll blocked → outer takes over
    if (_currentIndex == lastPageIndex && velocity.x < 0) return YES;

    // Not at boundary or swiping inward → blocker fails → inner scroll works normally
    return NO;
}

// Allow the blocker to be recognized simultaneously so it doesn't steal from vertical scrolls
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    if (gestureRecognizer == _blockerGesture) return YES;
    return NO;
}

#pragma mark - Helpers

- (BOOL)isHorizontalRtlLayout {
    return self.isHorizontal && !self.isLtrLayout;
}

- (BOOL)isHorizontal {
    return _nativePageViewController.navigationOrientation == UIPageViewControllerNavigationOrientationHorizontal;
}

- (BOOL)isLtrLayout {
    return [_layoutDirection isEqualToString: @"ltr"];
}

- (std::shared_ptr<const RNCViewPagerEventEmitter>)pagerEventEmitter
{
  if (!_eventEmitter) {
    return nullptr;
  }

  assert(std::dynamic_pointer_cast<const RNCViewPagerEventEmitter>(_eventEmitter));
  return std::static_pointer_cast<const RNCViewPagerEventEmitter>(_eventEmitter);
}

- (void)sendScrollEventsForPosition:(NSInteger)position offset:(CGFloat)offset {
    const auto eventEmitter = [self pagerEventEmitter];
    if (eventEmitter) {
        eventEmitter->onPageScroll(RNCViewPagerEventEmitter::OnPageScroll{
            .position = static_cast<double>(position),
            .offset = offset
        });
    }
  
    // This is temporary workaround to allow animations based on onPageScroll event
    // until Fabric implements proper NativeAnimationDriver,
    // see: https://github.com/facebook/react-native/blob/44f431b471c243c92284aa042d3807ba4d04af65/packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm#L59
    RCTOnPageScrollEvent *event = [[RCTOnPageScrollEvent alloc] initWithReactTag:@(self.tag)
                                                                        position:@(position)
                                                                          offset:@(offset)];
    NSDictionary *userInfo = @{@"event": event};
    [[NSNotificationCenter defaultCenter] postNotificationName:@"RCTNotifyEventDispatcherObserversOfEvent_DEPRECATED"
                                                        object:nil
                                                      userInfo:userInfo];
}

#pragma mark - RCTComponentViewProtocol

+ (ComponentDescriptorProvider)componentDescriptorProvider
{
    return concreteComponentDescriptorProvider<RNCViewPagerComponentDescriptor>();
}

@end

Class<RCTComponentViewProtocol> RNCViewPagerCls(void)
{
    return RNCPagerViewComponentView.class;
}
