#import "RCTDefaultReactNativeFactoryDelegate.h"
#import "DgisMapView.h"
#import "MarkerView.h"
#import <Dgis-Swift.h>

#import <React/RCTConversions.h>
#import <React/RCTFabricComponentsPlugins.h>

#import <react/renderer/components/RNDgisMapSpec/ComponentDescriptors.h>
#import <react/renderer/components/RNDgisMapSpec/EventEmitters.h>
#import <react/renderer/components/RNDgisMapSpec/Props.h>
#import <react/renderer/components/RNDgisMapSpec/RCTComponentViewHelpers.h>

using namespace facebook::react;

static const float DEFAULT_ANIMATION_DURATION = 0.3f;

@interface DgisMapView () <RCTDgisMapViewViewProtocol>
@end

@implementation DgisMapView {
    UIView *_mapView;
    DGisMapViewBridge *_mapBridge;
    NSMutableArray<MarkerView *> *_markers;
    BOOL _mapReady;
    double _pendingCenterLatitude;
    double _pendingCenterLongitude;
    double _pendingZoom;
    BOOL _hasPendingCenter;
}

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

- (DGisMapViewBridge *)mapBridge {
    return _mapBridge;
}

- (UIView *)mapView {
    return _mapView;
}

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        static const auto defaultProps = std::make_shared<const DgisMapViewProps>();
        _props = defaultProps;
        _markers = [[NSMutableArray alloc] init];
        _mapReady = NO;
        _hasPendingCenter = NO;
        _centerLatitude = 0.0;
        _centerLongitude = 0.0;
        _centerZoom = 1.0;
        [self setupMap];
    }
    return self;
}

- (void)setupMap {
    if (![[DGisBridge shared] isInitialized]) {
        return;
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        DGisMapViewBridge *bridge = [[DGisBridge shared] createMapViewWithCenterLat:self->_centerLatitude centerLng:self->_centerLongitude zoom:self->_centerZoom];
        if (!bridge) return;
        self->_mapBridge = bridge;
        self->_mapView = bridge.view;
        UIView *view = self->_mapView;
        [self addSubview:view];
        view.translatesAutoresizingMaskIntoConstraints = NO;
        [NSLayoutConstraint activateConstraints:@[
            [view.topAnchor constraintEqualToAnchor:self.topAnchor],
            [view.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
            [view.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
            [view.bottomAnchor constraintEqualToAnchor:self.bottomAnchor]
        ]];
        [self setupMapCallbacks];
        self->_mapReady = YES;
        if (self->_hasPendingCenter) {
            [self->_mapBridge setCenterWithLat:self->_pendingCenterLatitude lng:self->_pendingCenterLongitude zoom:self->_pendingZoom animated:NO];
            self->_hasPendingCenter = NO;
        }
        for (MarkerView *mv in self->_markers) {
            [mv initializeMarker];
        }
        [self emitMapLoadedEvent];
    });
}

- (void)setupMapCallbacks {
    if (!_mapBridge) return;
    __weak DgisMapView *weakSelf = self;
    [_mapBridge addObjectTappedCallbackWithCallback:^(NSString * _Nullable markerId, double lat, double lng) {
        DgisMapView *strongSelf = weakSelf;
        if (!strongSelf) return;
        if (markerId.length > 0) {
            for (MarkerView *mv in strongSelf->_markers) {
                if ([mv.markerId isEqualToString:markerId]) {
                    [mv emitPressEvent];
                    return;
                }
            }
        }
        [strongSelf emitTapEventWithLat:lat lng:lng];
    }];
    [_mapBridge addObjectLongPressCallbackWithCallback:^(double lat, double lng) {
        DgisMapView *strongSelf = weakSelf;
        if (strongSelf) [strongSelf emitLongTouchEventWithLat:lat lng:lng];
    }];
}

- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps {
    const auto &oldViewProps = *std::static_pointer_cast<DgisMapViewProps const>(_props);
    const auto &newViewProps = *std::static_pointer_cast<DgisMapViewProps const>(props);
    if (oldViewProps.center.latitude != newViewProps.center.latitude ||
        oldViewProps.center.longitude != newViewProps.center.longitude ||
        oldViewProps.center.zoom != newViewProps.center.zoom) {
        double zoom = newViewProps.center.zoom > 0 ? newViewProps.center.zoom : _centerZoom;
        [self setCenter:newViewProps.center.latitude longitude:newViewProps.center.longitude zoom:zoom animated:NO];
    }
    [super updateProps:props oldProps:oldProps];
}

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

#pragma mark - RCTDgisMapViewViewProtocol

- (void)fitAllMarkers:(NSArray *)args {
    double durationMs = DEFAULT_ANIMATION_DURATION * 1000;
    if (args.count > 0 && [args[0] isKindOfClass:[NSDictionary class]]) {
        NSNumber *d = [(NSDictionary *)args[0] objectForKey:@"duration"];
        if (d != nil) durationMs = [d doubleValue];
    }
    [self fitAllMarkersWithDuration:durationMs];
}

- (void)setZoom:(NSArray *)args {
    if (!args.count || ![args[0] isKindOfClass:[NSDictionary class]]) return;
    NSDictionary *params = (NSDictionary *)args[0];
    double zoom = [params[@"zoom"] doubleValue];
    double duration = params[@"duration"] ? [params[@"duration"] doubleValue] : DEFAULT_ANIMATION_DURATION * 1000;
    [self setZoom:zoom duration:duration];
}

- (void)zoomIn { [self zoomInAction]; }
- (void)zoomOut { [self zoomOutAction]; }

#pragma mark - Public Methods

- (void)setCenter:(double)latitude longitude:(double)longitude zoom:(double)zoom animated:(BOOL)animated {
    _centerLatitude = latitude;
    _centerLongitude = longitude;
    if (zoom > 0) _centerZoom = zoom;
    if (_mapReady && _mapBridge) {
        [_mapBridge setCenterWithLat:latitude lng:longitude zoom:_centerZoom animated:animated];
    } else {
        _pendingCenterLatitude = latitude;
        _pendingCenterLongitude = longitude;
        _pendingZoom = _centerZoom;
        _hasPendingCenter = YES;
    }
}

- (void)fitAllMarkersWithDuration:(double)duration {
    if (!_mapBridge || _markers.count == 0) return;
    NSMutableArray *positions = [NSMutableArray arrayWithCapacity:_markers.count];
    for (MarkerView *mv in _markers) {
        if (mv.markerId) {
            [positions addObject:@[@(mv.latitude), @(mv.longitude)]];
        }
    }
    if (positions.count == 0) return;
    [_mapBridge fitAllMarkersWithPositions:positions durationMs:duration];
}

- (void)setZoom:(double)zoom duration:(double)duration {
    if (!_mapBridge) return;
    [_mapBridge setZoomWithZoom:zoom durationMs:duration];
}

- (void)zoomInAction {
    if (_mapBridge) [_mapBridge zoomIn];
}

- (void)zoomOutAction {
    if (_mapBridge) [_mapBridge zoomOut];
}

#pragma mark - Child Management

- (void)addMarkerView:(UIView *)view {
    if (![view isKindOfClass:[MarkerView class]]) return;
    MarkerView *markerView = (MarkerView *)view;
    markerView.dgisMapView = self;
    [_markers addObject:markerView];
    [markerView initializeMarker];
}

- (void)removeMarkerViewAtIndex:(NSInteger)index {
    if (index < 0 || (NSInteger)_markers.count <= index) return;
    MarkerView *markerView = _markers[index];
    if (markerView.markerBridge) {
        [markerView.markerBridge remove];
    }
    [_markers removeObjectAtIndex:index];
}

- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
    [self addMarkerView:childComponentView];
    [super mountChildComponentView:childComponentView index:index];
}

- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index {
    [self removeMarkerViewAtIndex:index];
    [super unmountChildComponentView:childComponentView index:index];
}

#pragma mark - Event Emission

- (void)emitMapLoadedEvent {
    if (_eventEmitter) {
        std::dynamic_pointer_cast<const DgisMapViewEventEmitter>(_eventEmitter)->onMapLoaded({});
    }
}

- (void)emitTapEventWithLat:(double)lat lng:(double)lng {
    if (_eventEmitter) {
        std::dynamic_pointer_cast<const DgisMapViewEventEmitter>(_eventEmitter)->onTap({ .latitude = lat, .longitude = lng });
    }
}

- (void)emitLongTouchEventWithLat:(double)lat lng:(double)lng {
    if (_eventEmitter) {
        std::dynamic_pointer_cast<const DgisMapViewEventEmitter>(_eventEmitter)->onLongTouch({ .latitude = lat, .longitude = lng });
    }
}

#pragma mark - Cleanup

- (void)prepareForRecycle {
    [super prepareForRecycle];
    [_markers removeAllObjects];
    if (_mapView) {
        [_mapView removeFromSuperview];
    }
    _mapView = nil;
    _mapBridge = nil;
    _mapReady = NO;
    _hasPendingCenter = NO;
}

@end

Class<RCTComponentViewProtocol> DgisMapViewCls(void) {
    return DgisMapView.class;
}
