#import "RNSTabsBottomAccessoryHelper.h"
#import "RNSTabsBottomAccessoryShadowStateProxy.h"

#if RNS_TABS_BOTTOM_ACCESSORY_AVAILABLE

#import <React/RCTAssert.h>
#import <cxxreact/ReactNativeVersion.h>

namespace react = facebook::react;

static void *RNSTabsBottomAccessoryNativeWrapperViewContext = &RNSTabsBottomAccessoryNativeWrapperViewContext;

@implementation RNSTabsBottomAccessoryHelper {
  RNSTabsBottomAccessoryComponentView *__weak _bottomAccessoryView;
  UIView *__weak _observedNativeWrapperView;

#if REACT_NATIVE_VERSION_MINOR < 82
  BOOL _initialStateUpdateSent;
  CADisplayLink *_displayLink;
#else // REACT_NATIVE_VERSION_MINOR < 82
  RNSTabsBottomAccessoryContentComponentView *__weak _regularContentView;
  RNSTabsBottomAccessoryContentComponentView *__weak _inlineContentView;
#endif // REACT_NATIVE_VERSION_MINOR < 82

  id<UITraitChangeRegistration> _traitChangeRegistration;
}

- (instancetype)initWithBottomAccessoryView:(RNSTabsBottomAccessoryComponentView *)bottomAccessoryView
{
  if (self = [super init]) {
    _bottomAccessoryView = bottomAccessoryView;
    [self initState];
    _traitChangeRegistration = [self registerForAccessoryEnvironmentChanges];
  }

  return self;
}

- (void)initState
{
  _observedNativeWrapperView = nil;
#if REACT_NATIVE_VERSION_MINOR < 82
  _initialStateUpdateSent = NO;
  _displayLink = nil;
#else // REACT_NATIVE_VERSION_MINOR < 82
  _regularContentView = nil;
  _inlineContentView = nil;
#endif // REACT_NATIVE_VERSION_MINOR < 82
}

#pragma mark - Content view switching workaround

#if REACT_NATIVE_VERSION_MINOR >= 82

- (BOOL)isContentViewSwitchingWorkaroundActive
{
  return _regularContentView != nil && _inlineContentView != nil;
}

- (void)setContentView:(RNSTabsBottomAccessoryContentComponentView *)contentView
        forEnvironment:(RNSTabsBottomAccessoryEnvironment)environment
{
  switch (environment) {
    case RNSTabsBottomAccessoryEnvironmentRegular:
      _regularContentView = contentView;
      break;

    case RNSTabsBottomAccessoryEnvironmentInline:
      _inlineContentView = contentView;
      break;

    default:
      RCTLogError(@"[RNScreens] Unsupported TabsBottomAccessory environment");
  }

  [self handleContentViewVisibilityForEnvironmentIfNeeded];
}

- (void)handleContentViewVisibilityForEnvironmentIfNeeded
{
  if (!self.isContentViewSwitchingWorkaroundActive) {
    return;
  }

  switch (self->_bottomAccessoryView.traitCollection.tabAccessoryEnvironment) {
    case UITabAccessoryEnvironmentInline:
      _regularContentView.layer.opacity = 0.0;
      _inlineContentView.layer.opacity = 1.0;
      break;
    default:
      _regularContentView.layer.opacity = 1.0;
      _inlineContentView.layer.opacity = 0.0;
      break;
  }
}

#endif // REACT_NATIVE_VERSION_MINOR >= 82

#pragma mark - Observing environment changes

- (id<UITraitChangeRegistration>)registerForAccessoryEnvironmentChanges
{
  return [_bottomAccessoryView
      registerForTraitChanges:@[ [UITraitTabAccessoryEnvironment class] ]
                  withHandler:^(__kindof id<UITraitEnvironment>, UITraitCollection *previousTraitCollection) {
                    UITabAccessoryEnvironment environment =
                        self->_bottomAccessoryView.traitCollection.tabAccessoryEnvironment;
                    [self->_bottomAccessoryView.reactEventEmitter emitOnEnvironmentChangeIfNeeded:environment];
#if REACT_NATIVE_VERSION_MINOR >= 82
                    [self handleContentViewVisibilityForEnvironmentIfNeeded];
#endif // REACT_NATIVE_VERSION_MINOR >= 82
                  }];
}

#pragma mark - Observing frame changes

- (void)unregisterForAccessoryFrameChanges
{
  UIView *observedNativeWrapperView = _observedNativeWrapperView;
  if (observedNativeWrapperView == nil) {
    return;
  }

  [observedNativeWrapperView removeObserver:self
                                 forKeyPath:@"center"
                                    context:RNSTabsBottomAccessoryNativeWrapperViewContext];
  _observedNativeWrapperView = nil;
}

- (void)registerForAccessoryFrameChanges
{
  UIView *nativeWrapperView = self.nativeWrapperView;
  if (_observedNativeWrapperView == nativeWrapperView) {
    return;
  }

  [self unregisterForAccessoryFrameChanges];
  [nativeWrapperView addObserver:self
                      forKeyPath:@"center"
                         options:NSKeyValueObservingOptionInitial
                         context:RNSTabsBottomAccessoryNativeWrapperViewContext];
  _observedNativeWrapperView = nativeWrapperView;
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
  if (context == RNSTabsBottomAccessoryNativeWrapperViewContext) {
    [self notifyWrapperViewFrameHasChanged];
  } else {
    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  }
}

- (UIView *)nativeWrapperView
{
  RCTAssert(
      _bottomAccessoryView.superview.superview != nil,
      @"[RNScreens] RNSTabsBottomAccessoryComponentView must be the set as bottom accessory.");
  return _bottomAccessoryView.superview.superview;
}

- (void)notifyWrapperViewFrameHasChanged
{
#if REACT_NATIVE_VERSION_MINOR < 82
  // Make sure that bottom accessory's size is sent to ShadowNode as soon as possible.
  // We set origin to (0,0) because initially self.nativeWrapperView's origin is incorrect.
  // We want the enable the display link as well so that it takes over later with correct origin.
  if (!_initialStateUpdateSent) {
    CGRect frame = CGRectMake(0, 0, self.nativeWrapperView.frame.size.width, self.nativeWrapperView.frame.size.height);
    [_bottomAccessoryView.shadowStateProxy updateShadowStateWithFrame:frame];
    _initialStateUpdateSent = YES;
  }

  if (_displayLink == nil) {
    [self setupDisplayLink];
  }
#else // REACT_NATIVE_VERSION_MINOR < 82
  // We use self.nativeWrapperView because it has both the size and the origin
  // that we want to send to the ShadowNode.
  [_bottomAccessoryView.shadowStateProxy updateShadowStateWithFrame:self.nativeWrapperView.frame];
#endif // REACT_NATIVE_VERSION_MINOR < 82
}

#if REACT_NATIVE_VERSION_MINOR < 82
- (void)setupDisplayLink
{
  _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
  [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

- (void)handleDisplayLink:(CADisplayLink *)sender
{
  // We use self.nativeWrapperView because it has both the size and the origin
  // that we want to send to the ShadowNode.
  CGRect presentationFrame = self.nativeWrapperView.layer.presentationLayer.frame;
  if (CGRectEqualToRect(presentationFrame, CGRectZero)) {
    return;
  }

  [_bottomAccessoryView.shadowStateProxy updateShadowStateWithFrame:presentationFrame];

  // self.nativeWrapperView.frame is set to final value at the beginning of the transition.
  // When frame from presentation layer matches self.nativeWrapperView.frame, it indicates that
  // the transition is over and we can disable the display link.
  if (CGRectEqualToRect(presentationFrame, self.nativeWrapperView.frame)) {
    [self invalidateDisplayLink];
  }
}

- (void)invalidateDisplayLink
{
  [_displayLink invalidate];
  _displayLink = nil;
}
#endif // REACT_NATIVE_VERSION_MINOR < 82

#pragma mark - Invalidation

- (void)invalidate
{
  [_bottomAccessoryView unregisterForTraitChanges:_traitChangeRegistration];
  _traitChangeRegistration = nil;
  [self unregisterForAccessoryFrameChanges];
  _bottomAccessoryView = nil;
#if REACT_NATIVE_VERSION_MINOR < 82
  [self invalidateDisplayLink];
#else // REACT_NATIVE_VERSION_MINOR < 82
  _regularContentView = nil;
  _inlineContentView = nil;
#endif // REACT_NATIVE_VERSION_MINOR < 82
}

@end

#endif // RNS_TABS_BOTTOM_ACCESSORY_AVAILABLE
