// MARK: - Component Type for Fabric

typedef NS_ENUM(NSInteger, FabricComponentType) {
    FabricComponentTypeCard = 0,
    FabricComponentTypeFlow = 1,
    FabricComponentTypeApplePay = 2
};

#define DECLARE_FABRIC_COMPONENT( \
  COMPONENT_NAME, \
  COMPONENT_TYPE, \
  PROPS_TYPE, \
  CONFIG_STRUCT_TYPE, \
  EVENT_EMITTER_TYPE, \
  DESCRIPTOR_TYPE, \
  CONFIG_CONVERSION_BLOCK \
) \
\
@implementation COMPONENT_NAME { \
  UIView *_view; \
  UIViewController *_hostingController; \
  NSDictionary *_config; \
  BOOL _componentCreated; \
  BOOL _initialPropsReceived; \
  FabricComponentType _componentType; \
} \
\
+ (ComponentDescriptorProvider)componentDescriptorProvider \
{ \
  return concreteComponentDescriptorProvider<DESCRIPTOR_TYPE>(); \
} \
\
- (instancetype)init \
{ \
  if (self = [super init]) { \
    _componentType = COMPONENT_TYPE; \
    _view = [[UIView alloc] initWithFrame:self.bounds]; \
    _componentCreated = NO; \
    _initialPropsReceived = NO; \
    [self addSubview:_view]; \
  } \
  return self; \
} \
\
- (void)layoutSubviews \
{ \
  [super layoutSubviews]; \
  _view.frame = self.bounds; \
  if (_hostingController && _componentCreated) { \
    _hostingController.view.frame = _view.bounds; \
  } \
} \
\
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps \
{ \
  const auto &newViewProps = *std::static_pointer_cast<PROPS_TYPE const>(props); \
  if (!_initialPropsReceived) { \
    _initialPropsReceived = YES; \
    _config = [self configStructToDict:newViewProps.config]; \
    [super updateProps:props oldProps:oldProps]; \
    [self createComponent]; \
    return; \
  } \
} \
\
- (void)prepareForRecycle \
{ \
  [super prepareForRecycle]; \
  [self cleanupComponent]; \
  _componentCreated = NO; \
  _initialPropsReceived = NO; \
  _config = nil; \
} \
\
- (void)cleanupComponent \
{ \
  if (_hostingController) { \
    [[CheckoutManager sharedInstance] cleanupComponentWithViewController:_hostingController]; \
    [_hostingController.view removeFromSuperview]; \
    _hostingController = nil; \
  } \
} \
\
- (void)createComponent \
{ \
  if (_componentCreated) { \
    return; \
  } \
  \
  _componentCreated = YES; \
  \
  NSDictionary *configToUse = _config ? _config : @{}; \
  \
  CheckoutComponentType swiftComponentType = (CheckoutComponentType)_componentType; \
  \
  [[CheckoutManager sharedInstance] createComponentWithType:swiftComponentType \
                                                     config:configToUse \
                                        onDimensionsChanged:^(CGSize size) { \
    [self handleDimensionChange:size]; \
  } \
                                                 completion:^(UIViewController * _Nullable controller, \
                                                             NSError * _Nullable error) { \
    if (controller != nil) { \
      dispatch_async(dispatch_get_main_queue(), ^{ \
        self->_hostingController = controller; \
        \
        UIView *checkoutView = controller.view; \
        \
        checkoutView.frame = self->_view.bounds; \
        checkoutView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; \
        \
        [self->_view addSubview:checkoutView]; \
      }); \
    } else { \
      self->_componentCreated = NO; \
    } \
  }]; \
} \
\
- (void)handleDimensionChange:(CGSize)size { \
  if (_eventEmitter != nullptr) { \
    auto eventEmitter = std::dynamic_pointer_cast<const facebook::react::EVENT_EMITTER_TYPE>(_eventEmitter); \
    if (eventEmitter) { \
      facebook::react::EVENT_EMITTER_TYPE::OnDimensionsChanged dims{ \
        .width = size.width, \
        .height = size.height \
      }; \
      eventEmitter->onDimensionsChanged(dims); \
    } \
  } \
} \
\
- (NSDictionary *)configStructToDict:(const CONFIG_STRUCT_TYPE &)configStruct \
{ \
  CONFIG_CONVERSION_BLOCK \
} \
\
- (void)submit \
{ \
  \
  if (_hostingController && _componentCreated) { \
    [[CheckoutManager sharedInstance] submitComponentWithViewController:_hostingController]; \
  } \
} \
\
- (void)tokenize \
{ \
  \
  if (_hostingController && _componentCreated) { \
    [[CheckoutManager sharedInstance] tokenizeComponentWithViewController:_hostingController]; \
  } \
} \
- (void)update:(NSInteger)amount currency:(NSString *)currency \
{ \
  if (_hostingController && _componentCreated) { \
    [[CheckoutManager sharedInstance] updateComponentWithViewController:_hostingController \
                                                               amount:amount \
                                                             currency:currency]; \
  } \
} \
\
- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args \
{ \
  if ([commandName isEqualToString:@"submit"]) { \
    [self submit]; \
  } else if ([commandName isEqualToString:@"tokenize"]) { \
    [self tokenize]; \
  } else if ([commandName isEqualToString:@"update"]) { \
    if (args.count > 0) { \
        NSInteger amount = [args[0] integerValue]; \
        NSString *currency = nil; \
        if (args.count > 1 && [args[1] isKindOfClass:[NSString class]]) { \
            currency = args[1]; \
        } \
        [self update:amount currency:currency]; \
    }\
  } \
} \
\
- (void)dealloc \
{ \
  [self cleanupComponent]; \
} \
\
Class<RCTComponentViewProtocol> COMPONENT_NAME##Cls(void) \
{ \
  return COMPONENT_NAME.class; \
} \
\
@end

// MARK: - Convenience Macros

#define DECLARE_FABRIC_CARD_CONFIG \
  NSMutableDictionary *dict = [NSMutableDictionary dictionary]; \
  dict[@"showPayButton"] = @(configStruct.showPayButton); \
  if (!configStruct.paymentButtonAction.empty()) { \
    dict[@"paymentButtonAction"] = [NSString stringWithUTF8String:configStruct.paymentButtonAction.c_str()]; \
  } \
  return [dict copy];

#define DECLARE_FABRIC_EMPTY_CONFIG \
  return @{};
