#import "RNSTabsScreenComponentView.h"
#import "NSString+RNSUtility.h"
#import "RNSConversions.h"
#import "RNSDefines.h"
#import "RNSLog.h"
#import "RNSSafeAreaViewNotifications.h"
#import "RNSScrollViewFinder.h"
#import "RNSScrollViewHelper.h"
#import "RNSTabBarAppearanceCoordinator.h"
#import "RNSTabBarController.h"

#if RCT_NEW_ARCH_ENABLED
#import <React/RCTConversions.h>
#import <React/RCTImageSource.h>
#import <react/renderer/components/rnscreens/ComponentDescriptors.h>
#import <react/renderer/components/rnscreens/EventEmitters.h>
#import <react/renderer/components/rnscreens/Props.h>
#import <react/renderer/components/rnscreens/RCTComponentViewHelpers.h>
#endif // RCT_NEW_ARCH_ENABLED

#if RCT_NEW_ARCH_ENABLED
namespace react = facebook::react;
#endif // RCT_NEW_ARCH_ENABLED

#pragma mark - View implementation

@implementation RNSTabsScreenComponentView {
  RNSTabsScreenViewController *_controller;
  RNSTabsHostComponentView *__weak _Nullable _reactSuperview;

  RNSTabsScreenEventEmitter *_Nonnull _reactEventEmitter;

  // We need this information to warn users about dynamic changes to behavior being currently unsupported.
  BOOL _isOverrideScrollViewContentInsetAdjustmentBehaviorSet;
  // Tracks that the first child was mounted before props were set.
  // The pending override will be applied once updateProps runs.
  BOOL _needsScrollViewBehaviorOverride;
#if !RCT_NEW_ARCH_ENABLED
  BOOL _tabItemNeedsAppearanceUpdate;
  BOOL _tabScreenOrientationNeedsUpdate;
  BOOL _tabBarItemNeedsRecreation;
  BOOL _tabBarItemNeedsUpdate;
  BOOL _scrollEdgeEffectsNeedUpdate;
#endif // !RCT_NEW_ARCH_ENABLED
}

- (instancetype)initWithFrame:(CGRect)frame
{
  if (self = [super initWithFrame:frame]) {
    [self initState];
  }
  return self;
}

- (void)initState
{
#if RCT_NEW_ARCH_ENABLED
  static const auto defaultProps = std::make_shared<const react::RNSTabsScreenProps>();
  _props = defaultProps;
#endif // RCT_NEW_ARCH_ENABLED

  _controller = [RNSTabsScreenViewController new];
  _controller.view = self;

  _reactSuperview = nil;
  _reactEventEmitter = [RNSTabsScreenEventEmitter new];

#if !RCT_NEW_ARCH_ENABLED
  _tabItemNeedsAppearanceUpdate = NO;
  _tabScreenOrientationNeedsUpdate = NO;
  _tabBarItemNeedsRecreation = NO;
  _tabBarItemNeedsUpdate = NO;
  _scrollEdgeEffectsNeedUpdate = NO;
#endif

  [self resetProps];
}

- (void)resetProps
{
  _isSelectedScreen = NO;
  _badgeValue = nil;
  _title = nil;
  _isTitleUndefined = YES;
  _orientation = RNSOrientationInherit;

  _standardAppearance = [UITabBarAppearance new];
  _scrollEdgeAppearance = nil;

  _shouldUseRepeatedTabSelectionPopToRootSpecialEffect = YES;
  _shouldUseRepeatedTabSelectionScrollToTopSpecialEffect = YES;

  _overrideScrollViewContentInsetAdjustmentBehavior = YES;
  _isOverrideScrollViewContentInsetAdjustmentBehaviorSet = NO;

  _iconType = RNSTabsIconTypeSfSymbol;

  _iconImageSource = nil;
  _iconResourceName = nil;

  _selectedIconImageSource = nil;
  _selectedIconResourceName = nil;

  _systemItem = RNSTabsScreenSystemItemNone;

  _userInterfaceStyle = UIUserInterfaceStyleUnspecified;
}

RNS_IGNORE_SUPER_CALL_BEGIN
- (nullable RNSTabsHostComponentView *)reactSuperview
{
  return _reactSuperview;
}
RNS_IGNORE_SUPER_CALL_END

#ifdef RCT_NEW_ARCH_ENABLED

#pragma mark - RNSViewControllerInvalidating

- (void)invalidateController
{
  _controller = nil;
}

- (BOOL)shouldInvalidateOnMutation:(const facebook::react::ShadowViewMutation &)mutation
{
  // For tabs, Host is responsible for invalidating children.
  return NO;
}

#else

#pragma mark - RCTInvalidating

- (void)invalidate
{
  _controller = nil;
}

#endif

#pragma mark - Events

- (nonnull RNSTabsScreenEventEmitter *)reactEventEmitter
{
  RCTAssert(_reactEventEmitter != nil, @"[RNScreens] Attempt to access uninitialized _reactEventEmitter");
  return _reactEventEmitter;
}

- (nullable RNSTabBarController *)findTabBarController
{
  return static_cast<RNSTabBarController *>(_controller.tabBarController);
}

#pragma mark - RNSScrollViewBehaviorOverriding

- (BOOL)shouldOverrideScrollViewContentInsetAdjustmentBehavior
{
  return self.overrideScrollViewContentInsetAdjustmentBehavior;
}

- (void)overrideScrollViewBehaviorInFirstDescendantChainIfNeeded
{
  if ([self shouldOverrideScrollViewContentInsetAdjustmentBehavior]) {
    [RNSScrollViewHelper overrideScrollViewBehaviorInFirstDescendantChainFrom:self];
  }
}

- (void)updateContentScrollViewEdgeEffectsIfExists
{
  [RNSScrollEdgeEffectApplicator applyToScrollView:[RNSScrollViewFinder findScrollViewInFirstDescendantChainFrom:self]
                                      withProvider:self];
}

#pragma mark - Prop update utils

- (void)createTabBarItem
{
  UITabBarItem *tabBarItem = nil;
  if (_systemItem != RNSTabsScreenSystemItemNone) {
    UITabBarSystemItem systemItem = rnscreens::conversion::RNSTabsScreenSystemItemToUITabBarSystemItem(_systemItem);
    tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:systemItem tag:0];
  } else {
    tabBarItem = [[UITabBarItem alloc] init];
  }

  _controller.tabBarItem = tabBarItem;
}

- (void)updateTabBarItem
{
  UITabBarItem *tabBarItem = _controller.tabBarItem;

  NSString *evaluatedTitle = _title;
  if (_title == nil && _systemItem != RNSTabsScreenSystemItemNone) {
    // Restore default system item title
    UITabBarSystemItem systemItem = rnscreens::conversion::RNSTabsScreenSystemItemToUITabBarSystemItem(_systemItem);
    evaluatedTitle = [[UITabBarItem alloc] initWithTabBarSystemItem:systemItem tag:0].title;
  }

  [self updateTabBarItemTitle:evaluatedTitle];

  if (![tabBarItem.badgeValue isEqualToString:_badgeValue]) {
    tabBarItem.badgeValue = _badgeValue;
  }
}

- (void)updateTabBarItemTitle:(NSString *)newTitle
{
  // Setting _controller.title updates also _controller.tabBarItem.title but only if there
  // is a change to _controller.title. After creating new tabBarItem, _controller.title
  // remains the same but _controller.tabBarItem.title is nil. For consistency, we always
  // update both.
  if (![_controller.tabBarItem.title isEqualToString:newTitle] || ![_controller.title isEqualToString:newTitle]) {
    _controller.title = newTitle;
    _controller.tabBarItem.title = newTitle;
  }
}

#pragma mark - RNSSafeAreaProviding

- (UIEdgeInsets)providerSafeAreaInsets
{
  return self.safeAreaInsets;
}

- (void)dispatchSafeAreaDidChangeNotification
{
  [NSNotificationCenter.defaultCenter postNotificationName:RNSSafeAreaDidChange object:self userInfo:nil];
}

#pragma mark - RNSSafeAreaProviding related methods

// TODO: register for UIKeyboard notifications

- (void)safeAreaInsetsDidChange
{
  [super safeAreaInsetsDidChange];
  [self dispatchSafeAreaDidChangeNotification];
}

#if RCT_NEW_ARCH_ENABLED
#pragma mark - RCTComponentViewProtocol

- (void)updateProps:(const facebook::react::Props::Shared &)props
           oldProps:(const facebook::react::Props::Shared &)oldProps
{
  const auto &oldComponentProps = *std::static_pointer_cast<const react::RNSTabsScreenProps>(_props);
  const auto &newComponentProps = *std::static_pointer_cast<const react::RNSTabsScreenProps>(props);

  bool tabItemNeedsAppearanceUpdate{false};
  bool tabScreenOrientationNeedsUpdate{false};
  bool tabBarItemNeedsRecreation{false};
  bool tabBarItemNeedsUpdate{false};
  bool scrollEdgeEffectsNeedUpdate{false};

  if (newComponentProps.title != oldComponentProps.title ||
      newComponentProps.isTitleUndefined != oldComponentProps.isTitleUndefined) {
    _isTitleUndefined = newComponentProps.isTitleUndefined;

    if (_isTitleUndefined) {
      _title = nil;
    } else {
      _title = RCTNSStringFromString(newComponentProps.title);
    }

    tabBarItemNeedsUpdate = YES;
  }

  if (newComponentProps.orientation != oldComponentProps.orientation) {
    _orientation = rnscreens::conversion::RNSOrientationFromRNSTabsScreenOrientation(newComponentProps.orientation);
    tabScreenOrientationNeedsUpdate = YES;
  }

  if (newComponentProps.tabKey != oldComponentProps.tabKey) {
    RCTAssert(!newComponentProps.tabKey.empty(), @"[RNScreens] tabKey must not be empty!");
    _tabKey = RCTNSStringFromString(newComponentProps.tabKey);
  }

  if (newComponentProps.isFocused != oldComponentProps.isFocused) {
    _isSelectedScreen = newComponentProps.isFocused;
    [_controller tabScreenFocusHasChanged];
  }

  if (newComponentProps.badgeValue != oldComponentProps.badgeValue) {
    _badgeValue = RCTNSStringFromStringNilIfEmpty(newComponentProps.badgeValue);
    tabBarItemNeedsUpdate = YES;
  }

  if (newComponentProps.tabBarItemTestID != oldComponentProps.tabBarItemTestID) {
    _tabItemTestID = RCTNSStringFromStringNilIfEmpty(newComponentProps.tabBarItemTestID);
    _tabBarItemNeedsA11yUpdate = YES;
  }

  if (newComponentProps.tabBarItemAccessibilityLabel != oldComponentProps.tabBarItemAccessibilityLabel) {
    _tabItemAccessibilityLabel = RCTNSStringFromStringNilIfEmpty(newComponentProps.tabBarItemAccessibilityLabel);
    _tabBarItemNeedsA11yUpdate = YES;
  }

  if (newComponentProps.standardAppearance != oldComponentProps.standardAppearance) {
    _standardAppearance = [UITabBarAppearance new];
    [RNSTabBarAppearanceCoordinator configureTabBarAppearance:_standardAppearance
                                          fromAppearanceProps:rnscreens::conversion::RNSConvertFollyDynamicToId(
                                                                  newComponentProps.standardAppearance)];
    tabItemNeedsAppearanceUpdate = YES;
  }

  if (newComponentProps.scrollEdgeAppearance != oldComponentProps.scrollEdgeAppearance) {
    if (newComponentProps.scrollEdgeAppearance.type() == folly::dynamic::OBJECT) {
      _scrollEdgeAppearance = [UITabBarAppearance new];
      [RNSTabBarAppearanceCoordinator configureTabBarAppearance:_scrollEdgeAppearance
                                            fromAppearanceProps:rnscreens::conversion::RNSConvertFollyDynamicToId(
                                                                    newComponentProps.scrollEdgeAppearance)];
    } else {
      _scrollEdgeAppearance = nil;
    }
    tabItemNeedsAppearanceUpdate = YES;
  }

  if (newComponentProps.iconType != oldComponentProps.iconType) {
    _iconType = rnscreens::conversion::RNSTabsIconTypeFromIcon(newComponentProps.iconType);
    tabItemNeedsAppearanceUpdate = YES;
  }

  if (newComponentProps.iconImageSource != oldComponentProps.iconImageSource) {
    _iconImageSource =
        rnscreens::conversion::RCTImageSourceFromImageSourceAndIconType(&newComponentProps.iconImageSource, _iconType);
    tabItemNeedsAppearanceUpdate = YES;
  }

  if (newComponentProps.iconResourceName != oldComponentProps.iconResourceName) {
    _iconResourceName = RCTNSStringFromStringNilIfEmpty(newComponentProps.iconResourceName);
    tabItemNeedsAppearanceUpdate = YES;
  }

  if (newComponentProps.selectedIconImageSource != oldComponentProps.selectedIconImageSource) {
    _selectedIconImageSource = rnscreens::conversion::RCTImageSourceFromImageSourceAndIconType(
        &newComponentProps.selectedIconImageSource, _iconType);
    tabItemNeedsAppearanceUpdate = YES;
  }

  if (newComponentProps.selectedIconResourceName != oldComponentProps.selectedIconResourceName) {
    _selectedIconResourceName = RCTNSStringFromStringNilIfEmpty(newComponentProps.selectedIconResourceName);
    tabItemNeedsAppearanceUpdate = YES;
  }

  if (newComponentProps.specialEffects.repeatedTabSelection.popToRoot !=
      oldComponentProps.specialEffects.repeatedTabSelection.popToRoot) {
    _shouldUseRepeatedTabSelectionPopToRootSpecialEffect =
        newComponentProps.specialEffects.repeatedTabSelection.popToRoot;
  }

  if (newComponentProps.specialEffects.repeatedTabSelection.scrollToTop !=
      oldComponentProps.specialEffects.repeatedTabSelection.scrollToTop) {
    _shouldUseRepeatedTabSelectionScrollToTopSpecialEffect =
        newComponentProps.specialEffects.repeatedTabSelection.scrollToTop;
  }

  if (newComponentProps.overrideScrollViewContentInsetAdjustmentBehavior !=
      oldComponentProps.overrideScrollViewContentInsetAdjustmentBehavior) {
    _overrideScrollViewContentInsetAdjustmentBehavior =
        newComponentProps.overrideScrollViewContentInsetAdjustmentBehavior;

    if (_isOverrideScrollViewContentInsetAdjustmentBehaviorSet) {
      RCTLogWarn(
          @"[RNScreens] changing overrideScrollViewContentInsetAdjustmentBehavior dynamically is currently unsupported");
    }
  }

  // This flag is set to YES when overrideScrollViewContentInsetAdjustmentBehavior prop
  // is assigned for the first time. This allows us to identify any subsequent changes to this prop,
  // enabling us to warn users that dynamic changes are not supported.
  _isOverrideScrollViewContentInsetAdjustmentBehaviorSet = YES;

  // Apply deferred scroll view override if a child was mounted before props arrived
  if (_needsScrollViewBehaviorOverride) {
    [self overrideScrollViewBehaviorInFirstDescendantChainIfNeeded];
    _needsScrollViewBehaviorOverride = NO;
  }

  if (newComponentProps.systemItem != oldComponentProps.systemItem) {
    _systemItem =
        rnscreens::conversion::RNSTabsScreenSystemItemFromReactRNSTabsScreenSystemItem(newComponentProps.systemItem);
    tabBarItemNeedsRecreation = YES;
  }

  if (newComponentProps.bottomScrollEdgeEffect != oldComponentProps.bottomScrollEdgeEffect) {
    [self setBottomScrollEdgeEffect:rnscreens::conversion::
                                        RNSTabsScrollEdgeEffectFromTabsScreenBottomScrollEdgeEffectCppEquivalent(
                                            newComponentProps.bottomScrollEdgeEffect)];
    scrollEdgeEffectsNeedUpdate = YES;
  }

  if (newComponentProps.leftScrollEdgeEffect != oldComponentProps.leftScrollEdgeEffect) {
    [self setLeftScrollEdgeEffect:rnscreens::conversion::
                                      RNSTabsScrollEdgeEffectFromTabsScreenLeftScrollEdgeEffectCppEquivalent(
                                          newComponentProps.leftScrollEdgeEffect)];
    scrollEdgeEffectsNeedUpdate = YES;
  }

  if (newComponentProps.rightScrollEdgeEffect != oldComponentProps.rightScrollEdgeEffect) {
    [self setRightScrollEdgeEffect:rnscreens::conversion::
                                       RNSTabsScrollEdgeEffectFromTabsScreenRightScrollEdgeEffectCppEquivalent(
                                           newComponentProps.rightScrollEdgeEffect)];
    scrollEdgeEffectsNeedUpdate = YES;
  }

  if (newComponentProps.topScrollEdgeEffect != oldComponentProps.topScrollEdgeEffect) {
    [self setTopScrollEdgeEffect:rnscreens::conversion::
                                     RNSTabsScrollEdgeEffectFromTabsScreenTopScrollEdgeEffectCppEquivalent(
                                         newComponentProps.topScrollEdgeEffect)];
    scrollEdgeEffectsNeedUpdate = YES;
  }

  if (newComponentProps.userInterfaceStyle != oldComponentProps.userInterfaceStyle) {
    _userInterfaceStyle =
        rnscreens::conversion::UIUserInterfaceStyleFromTabsScreenCppEquivalent(newComponentProps.userInterfaceStyle);
  }

  if (tabBarItemNeedsRecreation) {
    [self createTabBarItem];
    tabBarItemNeedsUpdate = YES;
    _tabBarItemNeedsA11yUpdate = YES;
  }

  if (tabBarItemNeedsUpdate) {
    [self updateTabBarItem];

    // Force appearance update to make sure correct image for tab bar item is used
    tabItemNeedsAppearanceUpdate = YES;
  }

  if (tabItemNeedsAppearanceUpdate) {
    [_controller tabItemAppearanceHasChanged];
  }

  if (tabScreenOrientationNeedsUpdate) {
    [_controller tabScreenOrientationHasChanged];
  }

  if (scrollEdgeEffectsNeedUpdate) {
    [self updateContentScrollViewEdgeEffectsIfExists];
    scrollEdgeEffectsNeedUpdate = NO;
  }

  [super updateProps:props oldProps:oldProps];
}

- (void)updateLayoutMetrics:(const facebook::react::LayoutMetrics &)layoutMetrics
           oldLayoutMetrics:(const facebook::react::LayoutMetrics &)oldLayoutMetrics
{
  RNSLog(
      @"TabScreen [%ld] updateLayoutMetrics: %@", self.tag, NSStringFromCGRect(RCTCGRectFromRect(layoutMetrics.frame)));
  [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
}

- (void)updateEventEmitter:(const facebook::react::EventEmitter::Shared &)eventEmitter
{
  [super updateEventEmitter:eventEmitter];
  [_reactEventEmitter
      updateEventEmitter:std::static_pointer_cast<const react::RNSTabsScreenEventEmitter>(eventEmitter)];
}

- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
  RNSLog(@"TabScreen [%ld] mount [%ld] at %ld", self.tag, childComponentView.tag, index);
  [super mountChildComponentView:childComponentView index:index];

  // overrideScrollViewBehavior and updateContentScrollViewEdgeEffects use first descendant chain
  // from screen to find ScrollView, that's why we're only interested in child mounted at index 0.
  if (index == 0) {
    // Before the props are set (first render) defer acting to after
    // we receive props, as user might have decided to disable this feature.
    if (_isOverrideScrollViewContentInsetAdjustmentBehaviorSet) {
      [self overrideScrollViewBehaviorInFirstDescendantChainIfNeeded];
    } else {
      _needsScrollViewBehaviorOverride = YES;
    }
    [self updateContentScrollViewEdgeEffectsIfExists];
  }
}

- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
  RNSLog(@"TabScreen [%ld] unmount [%ld] from %ld", self.tag, childComponentView.tag, index);
  [super unmountChildComponentView:childComponentView index:index];
}

+ (react::ComponentDescriptorProvider)componentDescriptorProvider
{
  return react::concreteComponentDescriptorProvider<react::RNSTabsScreenComponentDescriptor>();
}

+ (BOOL)shouldBeRecycled
{
  // There won't be tens of instances of this component usually & it's easier for now.
  // We could consider enabling it someday though.
  return NO;
}

#else

#pragma mark - LEGACY RCTComponent protocol

- (void)didSetProps:(NSArray<NSString *> *)changedProps
{
  [super didSetProps:changedProps];

  // This flag is set to YES when overrideScrollViewContentInsetAdjustmentBehavior prop
  // is assigned for the first time. This allows us to identify any subsequent changes to this prop,
  // enabling us to warn users that dynamic changes are not supported.
  // On Paper, setter for the prop may not be called (when it is undefined in JS).
  // Therefore we set the flag in didSetProps to make sure to handle this case as well.
  // didSetProps will always be called because tabKey prop is required.
  _isOverrideScrollViewContentInsetAdjustmentBehaviorSet = YES;

  if (_tabBarItemNeedsRecreation) {
    [self createTabBarItem];
    _tabBarItemNeedsRecreation = NO;

    _tabBarItemNeedsUpdate = YES;
    _tabBarItemNeedsA11yUpdate = YES;
  }

  if (_tabBarItemNeedsUpdate) {
    [self updateTabBarItem];
    _tabBarItemNeedsUpdate = NO;

    // Force appearance update to make sure correct image for tab bar item is used
    _tabItemNeedsAppearanceUpdate = YES;
  }

  if (_tabItemNeedsAppearanceUpdate) {
    [_controller tabItemAppearanceHasChanged];
    _tabItemNeedsAppearanceUpdate = NO;
  }

  if (_tabScreenOrientationNeedsUpdate) {
    [_controller tabScreenOrientationHasChanged];
    _tabScreenOrientationNeedsUpdate = NO;
  }

  if (_scrollEdgeEffectsNeedUpdate) {
    [self updateContentScrollViewEdgeEffectsIfExists];
    _scrollEdgeEffectsNeedUpdate = NO;
  }
}

#pragma mark - LEGACY prop setters

- (void)setIsSelectedScreen:(BOOL)isSelectedScreen
{
  if (_isSelectedScreen != isSelectedScreen) {
    _isSelectedScreen = isSelectedScreen;
    [_controller tabScreenFocusHasChanged];
  }
}

- (void)setTabKey:(NSString *)tabKey
{
  RCTAssert([NSString rnscreens_isBlankOrNull:tabKey] == NO, @"[RNScreens] tabKey must not be empty");
  _tabKey = tabKey;
}

- (void)setTitle:(NSString *)title
{
  _title = title;
  _isTitleUndefined = title == nil;
  _tabBarItemNeedsUpdate = YES;
}

- (void)setBadgeValue:(NSString *)badgeValue
{
  _badgeValue = [NSString rnscreens_stringOrNilIfBlank:badgeValue];
  _tabBarItemNeedsUpdate = YES;
}

- (void)setTabBarItemTestID:(NSString *)tabBarItemTestID
{
  _tabItemTestID = tabBarItemTestID;
  _tabBarItemNeedsA11yUpdate = YES;
}

- (void)setTabBarItemAccessibilityLabel:(NSString *)tabBarItemAccessibilityLabel
{
  _tabItemAccessibilityLabel = tabBarItemAccessibilityLabel;
  _tabBarItemNeedsA11yUpdate = YES;
}

- (void)setIconType:(RNSTabsIconType)iconType
{
  _iconType = iconType;
  _tabItemNeedsAppearanceUpdate = YES;
}

- (void)setIconImageSource:(RCTImageSource *)iconImageSource
{
  _iconImageSource = iconImageSource;
  _tabItemNeedsAppearanceUpdate = YES;
}

- (void)setIconResourceName:(NSString *)iconResourceName
{
  _iconResourceName = [NSString rnscreens_stringOrNilIfEmpty:iconResourceName];
  _tabItemNeedsAppearanceUpdate = YES;
}

- (void)setSelectedIconImageSource:(RCTImageSource *)selectedIconImageSource
{
  _selectedIconImageSource = selectedIconImageSource;
  _tabItemNeedsAppearanceUpdate = YES;
}

- (void)setSelectedIconResourceName:(NSString *)selectedIconResourceName
{
  _selectedIconResourceName = [NSString rnscreens_stringOrNilIfEmpty:selectedIconResourceName];
  _tabItemNeedsAppearanceUpdate = YES;
}

- (void)setBottomScrollEdgeEffect:(RNSScrollEdgeEffect)bottomScrollEdgeEffect
{
  _bottomScrollEdgeEffect = bottomScrollEdgeEffect;
  _scrollEdgeEffectsNeedUpdate = YES;
}

- (void)setLeftScrollEdgeEffect:(RNSScrollEdgeEffect)leftScrollEdgeEffect
{
  _leftScrollEdgeEffect = leftScrollEdgeEffect;
  _scrollEdgeEffectsNeedUpdate = YES;
}

- (void)setRightScrollEdgeEffect:(RNSScrollEdgeEffect)rightScrollEdgeEffect
{
  _rightScrollEdgeEffect = rightScrollEdgeEffect;
  _scrollEdgeEffectsNeedUpdate = YES;
}

- (void)setTopScrollEdgeEffect:(RNSScrollEdgeEffect)topScrollEdgeEffect
{
  _topScrollEdgeEffect = topScrollEdgeEffect;
  _scrollEdgeEffectsNeedUpdate = YES;
}

- (void)setOverrideScrollViewContentInsetAdjustmentBehavior:(BOOL)overrideScrollViewContentInsetAdjustmentBehavior
{
  _overrideScrollViewContentInsetAdjustmentBehavior = overrideScrollViewContentInsetAdjustmentBehavior;

  if (_isOverrideScrollViewContentInsetAdjustmentBehaviorSet) {
    RCTLogWarn(
        @"[RNScreens] changing overrideScrollViewContentInsetAdjustmentBehavior dynamically is currently unsupported");
  }

  // _isOverrideScrollViewContentInsetAdjustmentBehaviorSet flag is set in didSetProps to handle a case
  // when the prop is undefined in JS and default value is used instead of calling this setter.
}

- (void)setStandardAppearance:(NSDictionary *)standardAppearanceProps
{
  _standardAppearance = [UITabBarAppearance new];
  if (standardAppearanceProps != nil) {
    [RNSTabBarAppearanceCoordinator configureTabBarAppearance:_standardAppearance
                                          fromAppearanceProps:standardAppearanceProps];
  }
  _tabItemNeedsAppearanceUpdate = YES;
}

- (void)setScrollEdgeAppearance:(NSDictionary *)scrollEdgeAppearanceProps
{
  if (scrollEdgeAppearanceProps != nil) {
    _scrollEdgeAppearance = [UITabBarAppearance new];
    [RNSTabBarAppearanceCoordinator configureTabBarAppearance:_scrollEdgeAppearance
                                          fromAppearanceProps:scrollEdgeAppearanceProps];
  } else {
    _scrollEdgeAppearance = nil;
  }
  _tabItemNeedsAppearanceUpdate = YES;
}

// This is a Paper-only setter method that will be called by the mounting code.
// It allows us to store UITabBarMinimizeBehavior in the component while accepting a custom enum as input from JS.
- (void)setSystemItem:(RNSTabsScreenSystemItem)systemItem
{
  _systemItem = systemItem;
  _tabBarItemNeedsRecreation = YES;
}

- (void)setSpecialEffects:(NSDictionary *)specialEffects
{
  if (specialEffects == nil || specialEffects[@"repeatedTabSelection"] == nil ||
      ![specialEffects[@"repeatedTabSelection"] isKindOfClass:[NSDictionary class]]) {
    _shouldUseRepeatedTabSelectionPopToRootSpecialEffect = YES;
    _shouldUseRepeatedTabSelectionScrollToTopSpecialEffect = YES;
    return;
  }

  NSDictionary *repeatedTabSelection = specialEffects[@"repeatedTabSelection"];

  if (repeatedTabSelection[@"popToRoot"] != nil) {
    _shouldUseRepeatedTabSelectionPopToRootSpecialEffect = [RCTConvert BOOL:repeatedTabSelection[@"popToRoot"]];
  } else {
    _shouldUseRepeatedTabSelectionPopToRootSpecialEffect = YES;
  }

  if (repeatedTabSelection[@"scrollToTop"] != nil) {
    _shouldUseRepeatedTabSelectionScrollToTopSpecialEffect = [RCTConvert BOOL:repeatedTabSelection[@"scrollToTop"]];
  } else {
    _shouldUseRepeatedTabSelectionScrollToTopSpecialEffect = YES;
  }
}

- (void)setOrientation:(RNSOrientation)orientation
{
  _orientation = orientation;
  _tabScreenOrientationNeedsUpdate = YES;
}

- (void)setOnWillAppear:(RCTDirectEventBlock)onWillAppear
{
  [self.reactEventEmitter setOnWillAppear:onWillAppear];
}

- (void)setOnWillDisappear:(RCTDirectEventBlock)onWillDisappear
{
  [self.reactEventEmitter setOnWillDisappear:onWillDisappear];
}

- (void)setOnDidAppear:(RCTDirectEventBlock)onDidAppear
{
  [self.reactEventEmitter setOnDidAppear:onDidAppear];
}

- (void)setOnDidDisappear:(RCTDirectEventBlock)onDidDisappear
{
  [self.reactEventEmitter setOnDidDisappear:onDidDisappear];
}

#define RNS_FAILING_EVENT_GETTER(eventName)                                           \
  -(RCTDirectEventBlock)eventName                                                     \
  {                                                                                   \
    RCTAssert(NO, @"[RNScreens] Events should be emitted through reactEventEmitter"); \
    return nil;                                                                       \
  }

RNS_FAILING_EVENT_GETTER(onWillAppear);
RNS_FAILING_EVENT_GETTER(onDidAppear);
RNS_FAILING_EVENT_GETTER(onWillDisappear);
RNS_FAILING_EVENT_GETTER(onDidDisappear);

#undef RNS_FAILING_EVENT_GETTER

#endif // RCT_NEW_ARCH_ENABLED

@end

#if RCT_NEW_ARCH_ENABLED
#pragma mark - View class exposure

Class<RCTComponentViewProtocol> RNSTabsScreen(void)
{
  return RNSTabsScreenComponentView.class;
}
#endif // RCT_NEW_ARCH_ENABLED
