#import "YamapView.h"

#import <React/UIView+React.h>

#if TARGET_OS_SIMULATOR
#import <mach-o/arch.h>
#endif

#import "ClusteredYamapView.h"
#import "CircleView.h"
#import "MarkerView.h"
#import "PolygonView.h"
#import "PolylineView.h"
#import "../Util/RCTConvert+Yamap.mm"
#import "ImageCacheManager.h"

#import "../Util/NewArchUtils.h"

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

#import "RCTFabricComponentsPlugins.h"

using namespace facebook::react;

@interface YamapView () <YMKUserLocationObjectListener, YMKMapCameraListener, YMKMapLoadedListener, YMKTrafficDelegate, YMKClusterListener, YMKClusterTapListener, YMKMapObjectTapListener>

@end

#import <YandexMapsMobile/YMKMap.h>
#import <YandexMapsMobile/YMKMapKitFactory.h>
#import <YandexMapsMobile/YMKMapObjectCollection.h>
#import <YandexMapsMobile/YMKVisibleRegion.h>
#import <YandexMapsMobile/YMKLogoAlignment.h>
#import <YandexMapsMobile/YMKLogoPadding.h>
#import <YandexMapsMobile/YMKIconStyle.h>
#import <YandexMapsMobile/YMKMapLoadStatistics.h>
#import <YandexMapsMobile/YMKCluster.h>
#import <YandexMapsMobile/YMKClusterizedPlacemarkCollection.h>

@implementation YamapView {
    YMKMapView *mapView;
    NSMutableArray<UIView*> *_reactSubviews;
    YMKUserLocationView *userLocationView;
    UIImage *userLocationImage;
    NSNumber *userLocationImageScale;
    YMKUserLocationLayer *userLayer;
    YMKTrafficLayer *trafficLayer;
    UIColor *userLocationAccuracyFillColor;
    UIColor *userLocationAccuracyStrokeColor;
    float userLocationAccuracyStrokeWidth;
    Boolean initializedRegion;
    UIColor *clusterColor;
    UIImage* clusImage;
    CGFloat clusterWidth;
    CGFloat clusterHeight;
    UIColor* clusterTextColor;
    double clusterTextSize;
    double clusterTextYOffset;
    double clusterTextXOffset;
    NSMutableArray<YMKPlacemarkMapObject *> *clusterPlacemarks;
    YMKClusterizedPlacemarkCollection *clusterCollection;
    BOOL mapLoaded;
    BOOL hasImperativePlacemarks;
    NSMutableDictionary<NSValue *, NSNumber *> *imperativeIndexMap;
    NSInteger imperativePlacemarkCounter;
}

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
#if TARGET_OS_SIMULATOR
        const NXArchInfo *archInfo = NXGetLocalArchInfo();
        NSString *cpuArch = [NSString stringWithUTF8String:archInfo->description];
        mapView = [[YMKMapView alloc] initWithFrame:frame vulkanPreferred:[cpuArch hasPrefix:@"ARM64"]];
#else
        mapView = [[YMKMapView alloc] initWithFrame:frame];
#endif

        static const auto defaultProps = std::make_shared<const YamapViewProps>();
        _props = defaultProps;

        _reactSubviews = [[NSMutableArray alloc] init];
        userLocationImageScale = [NSNumber numberWithFloat:1.f];
        userLocationAccuracyFillColor = nil;
        userLocationAccuracyStrokeColor = nil;
        userLocationAccuracyStrokeWidth = 0.f;
        [mapView.mapWindow.map addCameraListenerWithCameraListener:self];
        [mapView.mapWindow.map addInputListenerWithInputListener:(id<YMKMapInputListener>) self];
        [mapView.mapWindow.map setMapLoadedListenerWithMapLoadedListener:self];
        initializedRegion = NO;
        clusterPlacemarks = [[NSMutableArray alloc] init];
        imperativeIndexMap = [[NSMutableDictionary alloc] init];
        imperativePlacemarkCounter = 0;
        clusterCollection = [mapView.mapWindow.map.mapObjects addClusterizedPlacemarkCollectionWithClusterListener:self];
        clusterColor = UIColor.redColor;
        mapLoaded = NO;
        clusterWidth = 32;
        clusterHeight = 32;
        clusterTextColor = UIColor.blackColor;
        clusterTextSize = 45;
        clusterTextYOffset = 0;
        clusterTextXOffset = 0;
        [self addSubview:mapView];
    }

    return self;
}

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

- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps {
    const auto &oldViewProps = *std::static_pointer_cast<YamapViewProps const>(_props);
    const auto &newViewProps = *std::static_pointer_cast<YamapViewProps const>(props);

    BOOL needUpdateUserIcon = false;

    if (![NewArchUtils yamapInitialRegionsEquals:oldViewProps.initialRegion initialRegion2:newViewProps.initialRegion]) {
        YMKPoint *center = [YMKPoint pointWithLatitude:newViewProps.initialRegion.lat longitude:newViewProps.initialRegion.lon];
        YMKCameraPosition *cameraPosition = [YMKCameraPosition cameraPositionWithTarget:center zoom:newViewProps.initialRegion.zoom azimuth:newViewProps.initialRegion.azimuth tilt:newViewProps.initialRegion.tilt];
        [mapView.mapWindow.map moveWithCameraPosition:cameraPosition];
    }

    if (oldViewProps.mapType != newViewProps.mapType) {
        mapView.mapWindow.map.mapType = [self mapTypeWithJsEnum:newViewProps.mapType];
    }

    if (oldViewProps.nightMode != newViewProps.nightMode) {
        [self setNightMode:newViewProps.nightMode];
    }

    if (oldViewProps.showUserPosition != newViewProps.showUserPosition) {
        [self setShowUserPosition:newViewProps.showUserPosition];
    }

    if (oldViewProps.userLocationAccuracyFillColor != newViewProps.userLocationAccuracyFillColor) {
        userLocationAccuracyFillColor = [RCTConvert UIColor:[NSNumber numberWithInt:newViewProps.userLocationAccuracyFillColor]];
        needUpdateUserIcon = true;
    }

    if (oldViewProps.userLocationAccuracyStrokeColor != newViewProps.userLocationAccuracyStrokeColor) {
        userLocationAccuracyStrokeColor = [RCTConvert UIColor:[NSNumber numberWithInt:newViewProps.userLocationAccuracyStrokeColor]];
        needUpdateUserIcon = true;
    }

    if (oldViewProps.userLocationAccuracyStrokeWidth != newViewProps.userLocationAccuracyStrokeWidth) {
        userLocationAccuracyStrokeWidth = newViewProps.userLocationAccuracyStrokeWidth;
        needUpdateUserIcon = true;
    }

    if (oldViewProps.userLocationIconScale != newViewProps.userLocationIconScale) {
        userLocationImageScale = [NSNumber numberWithFloat:newViewProps.userLocationIconScale];
        needUpdateUserIcon = true;
    }

    if (oldViewProps.userLocationIcon != newViewProps.userLocationIcon) {
        needUpdateUserIcon = false;
        [self setUserLocationIcon:[NSString stringWithCString:newViewProps.userLocationIcon.c_str() encoding:[NSString defaultCStringEncoding]]];
    }

    if (oldViewProps.mapStyle != newViewProps.mapStyle) {
        [mapView.mapWindow.map setMapStyleWithStyle:[NSString stringWithCString:newViewProps.mapStyle.c_str() encoding:[NSString defaultCStringEncoding]]];
    }

    if (oldViewProps.scrollGesturesDisabled != newViewProps.scrollGesturesDisabled) {
        mapView.mapWindow.map.scrollGesturesEnabled = !newViewProps.scrollGesturesDisabled;
    }

    if (oldViewProps.zoomGesturesDisabled != newViewProps.zoomGesturesDisabled) {
        mapView.mapWindow.map.zoomGesturesEnabled = !newViewProps.zoomGesturesDisabled;
    }

    if (oldViewProps.tiltGesturesDisabled != newViewProps.tiltGesturesDisabled) {
        mapView.mapWindow.map.tiltGesturesEnabled = !newViewProps.tiltGesturesDisabled;
    }

    if (oldViewProps.rotateGesturesDisabled != newViewProps.rotateGesturesDisabled) {
        mapView.mapWindow.map.rotateGesturesEnabled = !newViewProps.rotateGesturesDisabled;
    }

    if (oldViewProps.fastTapDisabled != newViewProps.fastTapDisabled) {
        mapView.mapWindow.map.fastTapEnabled = !newViewProps.fastTapDisabled;
    }

    if (oldViewProps.followUser != newViewProps.followUser) {
        [self setFollowUser:newViewProps.followUser];
    }

    if (oldViewProps.interactiveDisabled != newViewProps.interactiveDisabled) {
        [mapView setNoninteractive:newViewProps.interactiveDisabled];
    }

    if (oldViewProps.logoPosition.vertical != newViewProps.logoPosition.vertical || oldViewProps.logoPosition.horizontal != newViewProps.logoPosition.horizontal) {
        YMKLogoHorizontalAlignment horizontalAlignment = YMKLogoHorizontalAlignmentRight;
        YMKLogoVerticalAlignment verticalAlignment = YMKLogoVerticalAlignmentBottom;

        if (newViewProps.logoPosition.horizontal == YamapViewHorizontal::Left) {
            horizontalAlignment = YMKLogoHorizontalAlignmentLeft;
        } else if (newViewProps.logoPosition.horizontal == YamapViewHorizontal::Center) {
            horizontalAlignment = YMKLogoHorizontalAlignmentCenter;
        }

        if (newViewProps.logoPosition.vertical == YamapViewVertical::Top) {
            verticalAlignment = YMKLogoVerticalAlignmentTop;
        }

        [mapView.mapWindow.map.logo setAlignmentWithAlignment:[YMKLogoAlignment alignmentWithHorizontalAlignment:horizontalAlignment verticalAlignment:verticalAlignment]];
    }

    if (oldViewProps.logoPadding.vertical != newViewProps.logoPadding.vertical || oldViewProps.logoPadding.horizontal != newViewProps.logoPadding.horizontal) {
        [mapView.mapWindow.map.logo setPaddingWithPadding:[YMKLogoPadding paddingWithHorizontalPadding:newViewProps.logoPadding.horizontal verticalPadding:newViewProps.logoPadding.vertical]];
    }

    if (needUpdateUserIcon) {
        [self updateUserIcon];
    }

    _props = std::static_pointer_cast<const ViewProps>(props);
}

- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
    if (!_eventEmitter) {
        return;
    }

    if ([commandName isEqual:@"setCenter"]) {
        NSNumber *duration = args[0][0][@"duration"];
        NSNumber *animation = args[0][0][@"animation"];
        NSNumber *zoom = args[0][0][@"zoom"];
        NSNumber *azimuth = args[0][0][@"azimuth"];
        NSNumber *tilt = args[0][0][@"tilt"];
        YMKPoint *center = [RCTConvert YMKPoint:args[0][0][@"center"]];
        YMKCameraPosition *cameraPosition = [YMKCameraPosition cameraPositionWithTarget:center zoom:[zoom floatValue] azimuth:[azimuth floatValue] tilt:[tilt floatValue]];
        [self setCenter:cameraPosition withDuration:[duration floatValue] withAnimation:[animation intValue]];
    } else if ([commandName isEqual:@"fitMarkers"]) {
        NSArray *points = [RCTConvert YMKPointArray:args[0][0][@"points"]];
        NSNumber *duration = args[0][0][@"duration"];
        NSNumber *animation = args[0][0][@"animation"];
        [self fitMarkers:points duration:[duration floatValue] animation:[animation intValue]];
    } else if ([commandName isEqual:@"fitAllMarkers"]) {
        NSNumber *duration = args[0][0][@"duration"];
        NSNumber *animation = args[0][0][@"animation"];
        [self fitAllMarkers:[duration floatValue] animation:[animation intValue]];
    } else if ([commandName isEqual:@"setTrafficVisible"]) {
        BOOL isVisible = [args[0][0][@"isVisible"] boolValue];
        [self setTrafficVisible:isVisible];
    } else if ([commandName isEqual:@"setZoom"]) {
        NSNumber *zoom = args[0][0][@"zoom"];
        NSNumber *duration = args[0][0][@"duration"];
        NSNumber *animation = args[0][0][@"animation"];
        [self setZoom:[zoom floatValue] withDuration:[duration floatValue] withAnimation:[animation intValue]];
    } else if ([commandName isEqual:@"getCameraPosition"]) {
        std::string id = std::string([args[0][0][@"id"] UTF8String]);
        YMKCameraPosition *position = mapView.mapWindow.map.cameraPosition;

        if ([self isKindOfClass:[ClusteredYamapView class]]) {
            std::dynamic_pointer_cast<const ClusteredYamapViewEventEmitter>(_eventEmitter)->onCameraPositionReceived({
                .id = id,
                .point = {
                    .lat = position.target.latitude,
                    .lon = position.target.longitude,
                },
                .azimuth = position.azimuth,
                .reason = "APPLICATION",
                .tilt = position.tilt,
                .zoom = position.zoom
            });
        } else {
            std::dynamic_pointer_cast<const YamapViewEventEmitter>(_eventEmitter)->onCameraPositionReceived({
                .id = id,
                .point = {
                    .lat = position.target.latitude,
                    .lon = position.target.longitude,
                },
                .azimuth = position.azimuth,
                .reason = "APPLICATION",
                .tilt = position.tilt,
                .zoom = position.zoom
            });
        }
    } else if ([commandName isEqual:@"getVisibleRegion"]) {
        std::string id = std::string([args[0][0][@"id"] UTF8String]);
        YMKVisibleRegion *region = mapView.mapWindow.map.visibleRegion;

        if ([self isKindOfClass:[ClusteredYamapView class]]) {
            std::dynamic_pointer_cast<const ClusteredYamapViewEventEmitter>(_eventEmitter)->onVisibleRegionReceived({
                .id = id,
                .bottomLeft = {
                  .lat = region.bottomLeft.latitude,
                  .lon = region.bottomLeft.longitude
                },
                .bottomRight = {
                  .lat = region.bottomRight.latitude,
                  .lon = region.bottomRight.longitude
                },
                .topLeft = {
                  .lat = region.topLeft.latitude,
                  .lon = region.topLeft.longitude
                },
                .topRight = {
                  .lat = region.topRight.latitude,
                  .lon = region.topRight.longitude
                }
            });
        } else {
            std::dynamic_pointer_cast<const YamapViewEventEmitter>(_eventEmitter)->onVisibleRegionReceived({
                .id = id,
                .bottomLeft = {
                  .lat = region.bottomLeft.latitude,
                  .lon = region.bottomLeft.longitude
                },
                .bottomRight = {
                  .lat = region.bottomRight.latitude,
                  .lon = region.bottomRight.longitude
                },
                .topLeft = {
                  .lat = region.topLeft.latitude,
                  .lon = region.topLeft.longitude
                },
                .topRight = {
                  .lat = region.topRight.latitude,
                  .lon = region.topRight.longitude
                }
            });
        }
    } else if ([commandName isEqual:@"getScreenPoints"]) {
        std::string id = std::string([args[0][0][@"id"] UTF8String]);
        NSArray *worldPoints = args[0][0][@"points"];

        if ([self isKindOfClass:[ClusteredYamapView class]]) {
            std::vector<ClusteredYamapViewEventEmitter::OnWorldToScreenPointsReceivedScreenPoints> screenPoints;
            for (int i = 0; i < [worldPoints count]; ++i) {
                NSDictionary *worldPoint = [worldPoints objectAtIndex:i];
                YMKScreenPoint *screenPoint = [mapView.mapWindow worldToScreenWithWorldPoint:[RCTConvert YMKPoint:worldPoint]];
                screenPoints.push_back({
                    .x = screenPoint.x,
                    .y = screenPoint.y
                });
            }

            std::dynamic_pointer_cast<const ClusteredYamapViewEventEmitter>(_eventEmitter)->onWorldToScreenPointsReceived({
                .id = id,
                .screenPoints = screenPoints
            });
        } else {
            std::vector<YamapViewEventEmitter::OnWorldToScreenPointsReceivedScreenPoints> screenPoints;
            for (int i = 0; i < [worldPoints count]; ++i) {
                NSDictionary *worldPoint = [worldPoints objectAtIndex:i];
                YMKScreenPoint *screenPoint = [mapView.mapWindow worldToScreenWithWorldPoint:[RCTConvert YMKPoint:worldPoint]];
                screenPoints.push_back({
                    .x = screenPoint.x,
                    .y = screenPoint.y
                });
            }

            std::dynamic_pointer_cast<const YamapViewEventEmitter>(_eventEmitter)->onWorldToScreenPointsReceived({
                .id = id,
                .screenPoints = screenPoints
            });
        }
    } else if ([commandName isEqual:@"getWorldPoints"]) {
        std::string id = std::string([args[0][0][@"id"] UTF8String]);
        NSArray *screenPoints = args[0][0][@"points"];

        if ([self isKindOfClass:[ClusteredYamapView class]]) {
            std::vector<ClusteredYamapViewEventEmitter::OnScreenToWorldPointsReceivedWorldPoints> worldPoints;
            for (int i = 0; i < [screenPoints count]; ++i) {
                NSDictionary *screenPoint = [screenPoints objectAtIndex:i];
                YMKPoint *worldPoint = [mapView.mapWindow screenToWorldWithScreenPoint:[RCTConvert YMKScreenPoint:screenPoint]];
                worldPoints.push_back({
                    .lat = worldPoint.latitude,
                    .lon = worldPoint.longitude
                });
            }

            std::dynamic_pointer_cast<const ClusteredYamapViewEventEmitter>(_eventEmitter)->onScreenToWorldPointsReceived({
                .id = id,
                .worldPoints = worldPoints
            });
        } else {
            std::vector<YamapViewEventEmitter::OnScreenToWorldPointsReceivedWorldPoints> worldPoints;
            for (int i = 0; i < [screenPoints count]; ++i) {
                NSDictionary *screenPoint = [screenPoints objectAtIndex:i];
                YMKPoint *worldPoint = [mapView.mapWindow screenToWorldWithScreenPoint:[RCTConvert YMKScreenPoint:screenPoint]];
                worldPoints.push_back({
                    .lat = worldPoint.latitude,
                    .lon = worldPoint.longitude
                });
            }

            std::dynamic_pointer_cast<const YamapViewEventEmitter>(_eventEmitter)->onScreenToWorldPointsReceived({
                .id = id,
                .worldPoints = worldPoints
            });
        }
    } else if ([commandName isEqual:@"appendClusterMarkers"]) {
        NSArray *points = [RCTConvert YMKPointArray:args[0][0][@"points"]];
        NSString *iconSource = args[0][0][@"iconSource"];
        if ([iconSource isEqual:[NSNull null]]) {
            iconSource = nil;
        }
        id reclusterArg = args[0][0][@"recluster"];
        NSNumber *anchorX = args[0][0][@"anchorX"];
        NSNumber *anchorY = args[0][0][@"anchorY"];
        BOOL recluster = (reclusterArg == nil || [reclusterArg isEqual:[NSNull null]]) ? YES : [reclusterArg boolValue];
        if ([anchorX isEqual:[NSNull null]]) {
            anchorX = nil;
        }
        if ([anchorY isEqual:[NSNull null]]) {
            anchorY = nil;
        }
        [self appendClusterMarkers:points iconSource:iconSource anchorX:anchorX anchorY:anchorY recluster:recluster];
    } else if ([commandName isEqual:@"clearClusterMarkers"]) {
        [self clearClusterMarkers];
    }
}

- (void)prepareForRecycle
{
    [super prepareForRecycle];

    [self removeAllSections];
    [clusterPlacemarks removeAllObjects];
    [imperativeIndexMap removeAllObjects];
    imperativePlacemarkCounter = 0;
    hasImperativePlacemarks = NO;

    static const auto defaultProps = std::make_shared<const YamapViewProps>();
    _props = defaultProps;
}

- (YMKMapType)mapTypeWithJsEnum:(YamapViewMapType)jsMapType {
    if (jsMapType == YamapViewMapType::None) {
        return YMKMapTypeNone;
    } else if (jsMapType == YamapViewMapType::Raster) {
        return YMKMapTypeMap;
    }

    return YMKMapTypeVectorMap;
}

- (void)removeAllSections {
    [mapView.mapWindow.map.mapObjects clear];
}

// REF
- (void)setCenter:(YMKCameraPosition *)position withDuration:(float)duration withAnimation:(int)animation {
    if (duration > 0) {
        YMKAnimationType anim = animation == 0 ? YMKAnimationTypeSmooth : YMKAnimationTypeLinear;
        [mapView.mapWindow.map moveWithCameraPosition:position animation:[YMKAnimation animationWithType:anim duration: duration] cameraCallback:^(BOOL completed) {}];
    } else {
        [mapView.mapWindow.map moveWithCameraPosition:position];
    }
}

- (void)setZoom:(float)zoom withDuration:(float)duration withAnimation:(int)animation {
    YMKCameraPosition *prevPosition = mapView.mapWindow.map.cameraPosition;
    YMKCameraPosition *position = [YMKCameraPosition cameraPositionWithTarget:prevPosition.target zoom:zoom azimuth:prevPosition.azimuth tilt:prevPosition.tilt];
    [self setCenter:position withDuration:duration withAnimation:animation];
}

- (void)setMapType:(NSString *)type {
    if ([type isEqual:@"none"]) {
        mapView.mapWindow.map.mapType = YMKMapTypeNone;
    } else if ([type isEqual:@"raster"]) {
        mapView.mapWindow.map.mapType = YMKMapTypeMap;
    } else {
        mapView.mapWindow.map.mapType = YMKMapTypeVectorMap;
    }
}

- (void)setTrafficVisible:(BOOL)isVisible {
    YMKMapKit *inst = [YMKMapKit sharedInstance];

    if (trafficLayer == nil) {
        trafficLayer = [inst createTrafficLayerWithMapWindow:mapView.mapWindow];
    }

    if (isVisible) {
        [trafficLayer setTrafficVisibleWithOn:YES];
        [trafficLayer addTrafficListenerWithTrafficListener:self];
    } else {
        [trafficLayer setTrafficVisibleWithOn:NO];
        [trafficLayer removeTrafficListenerWithTrafficListener:self];
    }
}

- (void)onCameraPositionChangedWithMap:(nonnull YMKMap*)map
                        cameraPosition:(nonnull YMKCameraPosition*)cameraPosition
                    cameraUpdateReason:(YMKCameraUpdateReason)cameraUpdateReason
                              finished:(BOOL)finished {
    if (!_eventEmitter) {
        return;
    }

    if ([self isKindOfClass:[ClusteredYamapView class]]) {
        std::dynamic_pointer_cast<const ClusteredYamapViewEventEmitter>(_eventEmitter)->onCameraPositionChange({
            .point = {
                .lat = cameraPosition.target.latitude,
                .lon = cameraPosition.target.longitude,
            },
            .azimuth = cameraPosition.azimuth,
            .finished = finished,
            .reason = cameraUpdateReason == YMKCameraUpdateReasonGestures ? "GESTURES" : "APPLICATION",
            .tilt = cameraPosition.tilt,
            .zoom = cameraPosition.zoom,
        });
    } else {
        std::dynamic_pointer_cast<const YamapViewEventEmitter>(_eventEmitter)->onCameraPositionChange({
            .point = {
                .lat = cameraPosition.target.latitude,
                .lon = cameraPosition.target.longitude,
            },
            .azimuth = cameraPosition.azimuth,
            .finished = finished,
            .reason = cameraUpdateReason == YMKCameraUpdateReasonGestures ? "GESTURES" : "APPLICATION",
            .tilt = cameraPosition.tilt,
            .zoom = cameraPosition.zoom,
        });
    }

    if (finished) {
        if ([self isKindOfClass:[ClusteredYamapView class]]) {
            std::dynamic_pointer_cast<const ClusteredYamapViewEventEmitter>(_eventEmitter)->onCameraPositionChangeEnd({
                .point = {
                    .lat = cameraPosition.target.latitude,
                    .lon = cameraPosition.target.longitude,
                },
                .azimuth = cameraPosition.azimuth,
                .finished = finished,
                .reason = cameraUpdateReason == YMKCameraUpdateReasonGestures ? "GESTURES" : "APPLICATION",
                .tilt = cameraPosition.tilt,
                .zoom = cameraPosition.zoom,
            });
        } else {
            std::dynamic_pointer_cast<const YamapViewEventEmitter>(_eventEmitter)->onCameraPositionChangeEnd({
                .point = {
                    .lat = cameraPosition.target.latitude,
                    .lon = cameraPosition.target.longitude,
                },
                .azimuth = cameraPosition.azimuth,
                .finished = finished,
                .reason = cameraUpdateReason == YMKCameraUpdateReasonGestures ? "GESTURES" : "APPLICATION",
                .tilt = cameraPosition.tilt,
                .zoom = cameraPosition.zoom,
            });
        }
    }
}

- (void)setNightMode:(BOOL)nightMode {
    [mapView.mapWindow.map setNightModeEnabled:nightMode];
}

- (void)setShowUserPosition:(BOOL)listen {
    YMKMapKit *inst = [YMKMapKit sharedInstance];

    if (userLayer == nil) {
        userLayer = [inst createUserLocationLayerWithMapWindow:mapView.mapWindow];
    }

    if (listen) {
        [userLayer setVisibleWithOn:YES];
        [userLayer setObjectListenerWithObjectListener:self];
    } else {
        [userLayer setVisibleWithOn:NO];
        [userLayer setObjectListenerWithObjectListener:nil];
    }
}

- (void)setFollowUser:(BOOL)follow {
    if (userLayer == nil) {
        [self setShowUserPosition: follow];
    }

    if (follow) {
        [userLayer setAnchorWithAnchorNormal:CGPointMake(0.5 * mapView.mapWindow.width, 0.5 * mapView.mapWindow.height) anchorCourse:CGPointMake(0.5 * mapView.mapWindow.width, 0.83 * mapView.mapWindow.height )];
        [userLayer setAutoZoomEnabled:YES];
    } else {
        [userLayer setAutoZoomEnabled:NO];
        [userLayer resetAnchor];
    }
}

- (void)fitAllMarkers:(float)duration animation:(int)animation {
    NSMutableArray<YMKPoint *> *lastKnownMarkers = [[NSMutableArray alloc] init];

    for (int i = 0; i < [_reactSubviews count]; ++i) {
        UIView *view = [_reactSubviews objectAtIndex:i];

        if ([view isKindOfClass:[MarkerView class]]) {
            MarkerView *marker = (MarkerView *)view;
            [lastKnownMarkers addObject:[marker getPoint]];
        }
    }

    [self fitMarkers:lastKnownMarkers duration:duration animation:animation];
}

- (NSArray<YMKPoint *> *)mapPlacemarksToPoints:(NSArray<YMKPlacemarkMapObject *> *)placemarks {
    NSMutableArray<YMKPoint *> *points = [[NSMutableArray alloc] init];

    for (int i = 0; i < [placemarks count]; ++i) {
        [points addObject:[[placemarks objectAtIndex:i] geometry]];
    }

    return points;
}

- (YMKBoundingBox *)calculateBoundingBox:(NSArray<YMKPoint *> *) points {
    double minLon = [points[0] longitude], maxLon = [points[0] longitude];
    double minLat = [points[0] latitude], maxLat = [points[0] latitude];

    for (int i = 0; i < [points count]; i++) {
        if ([points[i] longitude] > maxLon) maxLon = [points[i] longitude];
        if ([points[i] longitude] < minLon) minLon = [points[i] longitude];
        if ([points[i] latitude] > maxLat) maxLat = [points[i] latitude];
        if ([points[i] latitude] < minLat) minLat = [points[i] latitude];
    }

    YMKPoint *southWest = [YMKPoint pointWithLatitude:minLat longitude:minLon];
    YMKPoint *northEast = [YMKPoint pointWithLatitude:maxLat longitude:maxLon];
    YMKBoundingBox *boundingBox = [YMKBoundingBox boundingBoxWithSouthWest:southWest northEast:northEast];
    return boundingBox;
}

- (void)fitMarkers:(NSArray<YMKPoint *> *)points duration:(float)duration animation:(int)animation {
    if ([points count] == 0) {
        return;
    }

    YMKAnimation *anim = [YMKAnimation animationWithType:animation == 0 ? YMKAnimationTypeSmooth : YMKAnimationTypeLinear duration:duration];

    if ([points count] == 1) {
        YMKPoint *center = [points objectAtIndex:0];
        [mapView.mapWindow.map moveWithCameraPosition:[YMKCameraPosition cameraPositionWithTarget:center zoom:15 azimuth:0 tilt:0] animation:anim cameraCallback:^(BOOL completed){}];
        return;
    }
    YMKCameraPosition *cameraPosition = [mapView.mapWindow.map cameraPositionWithGeometry:[YMKGeometry geometryWithBoundingBox:[self calculateBoundingBox:points]]];
    cameraPosition = [YMKCameraPosition cameraPositionWithTarget:cameraPosition.target zoom:cameraPosition.zoom - 0.8f azimuth:cameraPosition.azimuth tilt:cameraPosition.tilt];
    [mapView.mapWindow.map moveWithCameraPosition:cameraPosition animation:anim cameraCallback:^(BOOL completed){}];
}

- (void)setLogoPosition:(NSDictionary *)logoPosition {
    YMKLogoHorizontalAlignment horizontalAlignment = YMKLogoHorizontalAlignmentRight;
    YMKLogoVerticalAlignment verticalAlignment = YMKLogoVerticalAlignmentBottom;

    if ([[logoPosition valueForKey:@"horizontal"] isEqual:@"left"]) {
        horizontalAlignment = YMKLogoHorizontalAlignmentLeft;
    } else if ([[logoPosition valueForKey:@"horizontal"] isEqual:@"center"]) {
        horizontalAlignment = YMKLogoHorizontalAlignmentCenter;
    }

    if ([[logoPosition valueForKey:@"vertical"] isEqual:@"top"]) {
        verticalAlignment = YMKLogoVerticalAlignmentTop;
    }

    [mapView.mapWindow.map.logo setAlignmentWithAlignment:[YMKLogoAlignment alignmentWithHorizontalAlignment:horizontalAlignment verticalAlignment:verticalAlignment]];
}

- (void)setLogoPadding:(NSDictionary *)logoPadding {
    NSUInteger horizontalPadding = [logoPadding valueForKey:@"horizontal"] != nil ? [RCTConvert NSUInteger:logoPadding[@"horizontal"]] : 0;
    NSUInteger verticalPadding = [logoPadding valueForKey:@"vertical"] != nil ? [RCTConvert NSUInteger:logoPadding[@"vertical"]] : 0;

    YMKLogoPadding *padding = [YMKLogoPadding paddingWithHorizontalPadding:horizontalPadding verticalPadding:verticalPadding];
    [mapView.mapWindow.map.logo setPaddingWithPadding:padding];
}

// PROPS
- (void)setUserLocationIcon:(NSString *)source {
    [[ImageCacheManager instance] getWithSource:source completion:^(UIImage *image) {
        self->userLocationImage = image;
        [self updateUserIcon];
    }];
}

- (void)updateUserIcon {
    if (userLocationView == nil) {
        return;
    }

    if (userLocationImage) {
        YMKIconStyle *userIconStyle = [[YMKIconStyle alloc] init];
        [userIconStyle setScale:userLocationImageScale];

        [userLocationView.pin setIconWithImage:userLocationImage style:userIconStyle];
        [userLocationView.arrow setIconWithImage:userLocationImage style:userIconStyle];
    }

    YMKCircleMapObject* circle = userLocationView.accuracyCircle;

    if (userLocationAccuracyFillColor) {
        [circle setFillColor:userLocationAccuracyFillColor];
    }

    if (userLocationAccuracyStrokeColor) {
        [circle setStrokeColor:userLocationAccuracyStrokeColor];
    }

    [circle setStrokeWidth:userLocationAccuracyStrokeWidth];
}

- (void)onObjectAddedWithView:(nonnull YMKUserLocationView *)view {
    userLocationView = view;
    [self updateUserIcon];
}

- (void)onObjectRemovedWithView:(nonnull YMKUserLocationView *)view {
}

- (void)onObjectUpdatedWithView:(nonnull YMKUserLocationView *)view event:(nonnull YMKObjectEvent *)event {
    userLocationView = view;
    [self updateUserIcon];
}

- (void)onMapTapWithMap:(nonnull YMKMap *)map
                  point:(nonnull YMKPoint *)point {
    if (!_eventEmitter) {
        return;
    }

    if ([self isKindOfClass:[ClusteredYamapView class]]) {
        std::dynamic_pointer_cast<const ClusteredYamapViewEventEmitter>(_eventEmitter)->onMapPress({.lat = point.latitude, .lon = point.longitude});
    } else {
        std::dynamic_pointer_cast<const YamapViewEventEmitter>(_eventEmitter)->onMapPress({.lat = point.latitude, .lon = point.longitude});
    }
}

- (void)onMapLongTapWithMap:(nonnull YMKMap *)map
                      point:(nonnull YMKPoint *)point {
    if (!_eventEmitter) {
        return;
    }

    if ([self isKindOfClass:[ClusteredYamapView class]]) {
        std::dynamic_pointer_cast<const ClusteredYamapViewEventEmitter>(_eventEmitter)->onMapLongPress({.lat = point.latitude, .lon = point.longitude});
    } else {
        std::dynamic_pointer_cast<const YamapViewEventEmitter>(_eventEmitter)->onMapLongPress({.lat = point.latitude, .lon = point.longitude});
    }
}

- (void)insertSubview:(UIView *)subview atIndex:(NSInteger)index {
    if ([subview isKindOfClass:[PolygonView class]]) {
        YMKMapObjectCollection *objects = mapView.mapWindow.map.mapObjects;
        PolygonView *polygon = (PolygonView *) subview;
        YMKPolygonMapObject *obj = [objects addPolygonWithPolygon:[polygon getPolygon]];
        [polygon setMapObject:obj];
    } else if ([subview isKindOfClass:[PolylineView class]]) {
        YMKMapObjectCollection *objects = mapView.mapWindow.map.mapObjects;
        PolylineView *polyline = (PolylineView*) subview;
        YMKPolylineMapObject *obj = [objects addPolylineWithPolyline:[polyline getPolyline]];
        [polyline setMapObject:obj];
    } else if ([subview isKindOfClass:[MarkerView class]]) {
        MarkerView *marker = (MarkerView *) subview;
        BOOL inClusterHost = [self isKindOfClass:[ClusteredYamapView class]];
        if (inClusterHost && ![marker excludeFromCluster]) {
            if (index<[clusterPlacemarks count]) {
                [marker setClusterMapObject:[clusterPlacemarks objectAtIndex:index]];
            }
        } else {
            YMKMapObjectCollection *objects = mapView.mapWindow.map.mapObjects;
            YMKPlacemarkMapObject *obj = [objects addPlacemark];
            [obj setIconWithImage:[[UIImage alloc] init]];
            [obj setGeometry:[marker getPoint]];
            [marker setMapObject:obj];
        }
    } else if ([subview isKindOfClass:[CircleView class]]) {
        YMKMapObjectCollection *objects = mapView.mapWindow.map.mapObjects;
        CircleView *circle = (CircleView*) subview;
        YMKCircleMapObject *obj = [objects addCircleWithCircle:[circle getCircle]];
        [circle setMapObject:obj];
    } else {
        NSArray<id<RCTComponent>> *childSubviews = [subview reactSubviews];
        for (int i = 0; i < childSubviews.count; i++) {
            [self insertSubview:(UIView *)childSubviews[i] atIndex:index];
        }
    }

    [_reactSubviews insertObject:subview atIndex:index];
    [super insertSubview:subview atIndex:index];
}

- (void)removeReactSubview:(UIView *)subview {
    if ([subview isKindOfClass:[PolygonView class]]) {
        YMKMapObjectCollection *objects = mapView.mapWindow.map.mapObjects;
        PolygonView *polygon = (PolygonView *) subview;
        [objects removeWithMapObject:[polygon getMapObject]];
    } else if ([subview isKindOfClass:[PolylineView class]]) {
        YMKMapObjectCollection *objects = mapView.mapWindow.map.mapObjects;
        PolylineView *polyline = (PolylineView *) subview;
        [objects removeWithMapObject:[polyline getMapObject]];
    } else if ([subview isKindOfClass:[MarkerView class]]) {
        YMKMapObjectCollection *objects = mapView.mapWindow.map.mapObjects;
        MarkerView *marker = (MarkerView *) subview;
        YMKPlacemarkMapObject *mapObject = [marker getMapObject];
        BOOL inClusterHost = [self isKindOfClass:[ClusteredYamapView class]];
        if (inClusterHost && ![marker excludeFromCluster]) {
            [clusterPlacemarks removeObject:mapObject];
        } else if (mapObject != nil && [mapObject isValid]) {
            [objects removeWithMapObject:mapObject];
        }
    } else if ([subview isKindOfClass:[CircleView class]]) {
        YMKMapObjectCollection *objects = mapView.mapWindow.map.mapObjects;
        CircleView *circle = (CircleView *) subview;
        [objects removeWithMapObject:[circle getMapObject]];
    } else {
        NSArray<id<RCTComponent>> *childSubviews = [subview reactSubviews];
        for (int i = 0; i < childSubviews.count; i++) {
            [self removeReactSubview:(UIView *)childSubviews[i]];
        }
    }

    [_reactSubviews removeObject:subview];
    [super removeReactSubview: subview];
}

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

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

- (void)setClusterColor: (NSNumber*) color {
    clusterColor = [RCTConvert UIColor:color];
}

- (void)setClusterIcon:(NSString *)source points:(NSArray<YMKPoint *> * _Nullable)points {
    [[ImageCacheManager instance] getWithSource:source completion:^(UIImage *image) {
        self->clusImage = image;
        if (points != nil) {
            [self setClusteredMarkers:points];
        }
    }];
}

- (void)setClusterSize:(NSDictionary *)sizes {
    clusterWidth = [sizes valueForKey:@"width"] != nil ? [RCTConvert NSUInteger:sizes[@"width"]] : 0;
    clusterHeight = [sizes valueForKey:@"height"] != nil ? [RCTConvert NSUInteger:sizes[@"height"]] : 0;
}

- (void)setClusterTextColor:(NSNumber *)color {
    clusterTextColor = [RCTConvert UIColor:color];
}

- (void)setClusterTextSize:(double)size {
    clusterTextSize = size;
}

- (void)setClusterTextYOffset:(double)offset {
    clusterTextYOffset = offset;
}

- (void)setClusterTextXOffset:(double)offset {
    clusterTextXOffset = offset;
}

- (void)setClusteredMarkers:(NSArray<YMKPoint*>*) markers {
    // Empty prop arrives on every re-render when the consumer drives this
    // component imperatively — never clear in that case, it would wipe the
    // markers added via appendClusterMarkers.
    if ((markers == nil || [markers count] == 0) && hasImperativePlacemarks) return;
    [clusterPlacemarks removeAllObjects];
    [imperativeIndexMap removeAllObjects];
    imperativePlacemarkCounter = 0;
    if (![clusterCollection isValid]) {
        clusterCollection = [mapView.mapWindow.map.mapObjects addClusterizedPlacemarkCollectionWithClusterListener:self];
    }
    [clusterCollection clear];
    UIImage *image = [self clusterImage:[NSNumber numberWithFloat:[markers count]]];
    NSArray<YMKPlacemarkMapObject *>* newPlacemarks = [clusterCollection addPlacemarksWithPoints:markers image:image style:[YMKIconStyle new]];
    [clusterPlacemarks addObjectsFromArray:newPlacemarks];
    for (int i=0; i<[clusterPlacemarks count]; i++) {
        if (i<[_reactSubviews count]) {
            UIView *subview = [_reactSubviews objectAtIndex:i];
            if ([subview isKindOfClass:[MarkerView class]]) {
                MarkerView *marker = (MarkerView*) subview;
                [marker setClusterMapObject:[clusterPlacemarks objectAtIndex:i]];
            }
        }
    }
    hasImperativePlacemarks = NO;

    [self clusterPlacemarks];
}

- (void)appendClusterMarkers:(NSArray<YMKPoint*>*)points iconSource:(NSString*)iconSource anchorX:(NSNumber*)anchorX anchorY:(NSNumber*)anchorY recluster:(BOOL)recluster {
    if (![self isKindOfClass:[ClusteredYamapView class]]) return;
    if (points == nil || [points count] == 0) return;
    if (![clusterCollection isValid]) {
        clusterCollection = [mapView.mapWindow.map.mapObjects addClusterizedPlacemarkCollectionWithClusterListener:self];
    }

    __weak __typeof__(self) weakSelf = self;
    void (^addBlock)(UIImage *) = ^(UIImage *image) {
        __typeof__(self) strongSelf = weakSelf;
        if (!strongSelf) return;
        UIImage *effectiveImage = image ?: [strongSelf clusterImage:@(1)];
        YMKIconStyle *iconStyle = [YMKIconStyle new];
        if (anchorX != nil && anchorY != nil) {
            [iconStyle setAnchor:[NSValue valueWithCGPoint:CGPointMake(anchorX.doubleValue, anchorY.doubleValue)]];
        }
        NSArray<YMKPlacemarkMapObject *> *added = [strongSelf->clusterCollection addPlacemarksWithPoints:points image:effectiveImage style:iconStyle];
        [strongSelf->clusterPlacemarks addObjectsFromArray:added];
        for (YMKPlacemarkMapObject *pm in added) {
            NSValue *key = [NSValue valueWithNonretainedObject:pm];
            strongSelf->imperativeIndexMap[key] = @(strongSelf->imperativePlacemarkCounter++);
            [pm addTapListenerWithTapListener:strongSelf];
        }
        strongSelf->hasImperativePlacemarks = YES;
        if (recluster) {
            [strongSelf clusterPlacemarks];
        }
    };

    if (iconSource && [iconSource length] > 0) {
        [[ImageCacheManager instance] getWithSource:iconSource completion:^(UIImage *image) {
            addBlock(image);
        }];
    } else {
        addBlock(nil);
    }
}

- (void)clearClusterMarkers {
    if (![self isKindOfClass:[ClusteredYamapView class]]) return;
    [clusterPlacemarks removeAllObjects];
    [imperativeIndexMap removeAllObjects];
    imperativePlacemarkCounter = 0;
    if ([clusterCollection isValid]) {
        [clusterCollection clear];
    }
    hasImperativePlacemarks = NO;
}

-(UIImage*)clusterImage:(NSNumber*) clusterSize {
    NSString *text = [clusterSize stringValue];
    UIFont *font = [UIFont systemFontOfSize:clusterTextSize];
    CGSize size = [text sizeWithAttributes:@{NSFontAttributeName:font}];

    // This function returns a newImage, based on image, that has been:
    // - scaled to fit in (CGRect) rect
    // - and cropped within a circle of radius: rectWidth/2

    //Create the bitmap graphics context
    if(clusImage && clusterWidth != 0 && clusterHeight != 0) {
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(clusterWidth, clusterHeight), NO, 1.0);
        [clusImage drawInRect:CGRectMake(0, 0, clusterWidth, clusterHeight)];
        [text drawInRect:CGRectMake(clusterWidth / 2  - size.width/2 + clusterTextXOffset, clusterHeight / 2 - size.height/2 + clusterTextYOffset, size.width, size.height) withAttributes:@{NSFontAttributeName: font, NSForegroundColorAttributeName: clusterTextColor }];
    } else {
        float MARGIN_SIZE = 9;
        float STROKE_SIZE = 9;

        float textRadius = sqrt(size.height * size.height + size.width * size.width) / 2;
        float internalRadius = textRadius + MARGIN_SIZE;
        float externalRadius = internalRadius + STROKE_SIZE;

        UIGraphicsBeginImageContextWithOptions(CGSizeMake(externalRadius*2, externalRadius*2), NO, 1.0);
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSetFillColorWithColor(context, [clusterColor CGColor]);
        CGContextFillEllipseInRect(context, CGRectMake(0, 0, externalRadius*2, externalRadius*2));
        CGContextSetFillColorWithColor(context, [UIColor.whiteColor CGColor]);
        CGContextFillEllipseInRect(context, CGRectMake(STROKE_SIZE, STROKE_SIZE, internalRadius*2, internalRadius*2));
        [text drawInRect:CGRectMake(externalRadius - size.width/2, externalRadius - size.height/2, size.width, size.height) withAttributes:@{NSFontAttributeName: font, NSForegroundColorAttributeName: clusterTextColor }];
    }

    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return newImage;
}

- (void) clusterPlacemarks {
    if ([clusterPlacemarks count] > 0) {
        [clusterCollection clusterPlacemarksWithClusterRadius:50 minZoom:12];
    }
}

- (void)onMapLoadedWithStatistics:(YMKMapLoadStatistics*)statistics {
    if (!_eventEmitter) {
        return;
    }

    if ([self isKindOfClass:[ClusteredYamapView class]]) {
        std::dynamic_pointer_cast<const ClusteredYamapViewEventEmitter>(_eventEmitter)->onMapLoaded({
            .renderObjectCount = [[NSNumber numberWithLong:statistics.renderObjectCount] doubleValue],
            .curZoomModelsLoaded = statistics.curZoomModelsLoaded,
            .curZoomPlacemarksLoaded = statistics.curZoomPlacemarksLoaded,
            .curZoomLabelsLoaded = statistics.curZoomLabelsLoaded,
            .curZoomGeometryLoaded = statistics.curZoomGeometryLoaded,
            .tileMemoryUsage = [[NSNumber numberWithUnsignedLong:statistics.tileMemoryUsage] doubleValue],
            .delayedGeometryLoaded = statistics.delayedGeometryLoaded,
            .fullyAppeared = statistics.fullyAppeared,
            .fullyLoaded = statistics.fullyLoaded,
        });
    } else {
        std::dynamic_pointer_cast<const YamapViewEventEmitter>(_eventEmitter)->onMapLoaded({
            .renderObjectCount = [[NSNumber numberWithLong:statistics.renderObjectCount] doubleValue],
            .curZoomModelsLoaded = statistics.curZoomModelsLoaded,
            .curZoomPlacemarksLoaded = statistics.curZoomPlacemarksLoaded,
            .curZoomLabelsLoaded = statistics.curZoomLabelsLoaded,
            .curZoomGeometryLoaded = statistics.curZoomGeometryLoaded,
            .tileMemoryUsage = [[NSNumber numberWithUnsignedLong:statistics.tileMemoryUsage] doubleValue],
            .delayedGeometryLoaded = statistics.delayedGeometryLoaded,
            .fullyAppeared = statistics.fullyAppeared,
            .fullyLoaded = statistics.fullyLoaded,
        });
    }

    mapLoaded = YES;
}

- (void)setInteractiveDisabled:(BOOL)interactiveDisabled {
    [mapView setNoninteractive:interactiveDisabled];
}

- (void)onTrafficLoading {
}

- (void)onTrafficChangedWithTrafficLevel:(nullable YMKTrafficLevel *)trafficLevel {
}

- (void)onTrafficExpired {
}

- (YMKMapView *)getMapView {
    return mapView;
}

- (void)onClusterAddedWithCluster:(nonnull YMKCluster *)cluster {
    NSNumber *myNum = @([cluster size]);
    [[cluster appearance] setIconWithImage:[self clusterImage:myNum]];
    [cluster addClusterTapListenerWithClusterTapListener:self];
}

- (BOOL)onClusterTapWithCluster:(nonnull YMKCluster *)cluster {
    NSMutableArray<YMKPoint*>* lastKnownMarkers = [[NSMutableArray alloc] init];
    for (YMKPlacemarkMapObject *placemark in [cluster placemarks]) {
        [lastKnownMarkers addObject:[placemark geometry]];
    }
    [self fitMarkers:lastKnownMarkers duration:0.7 animation:0];
    return YES;
}

- (BOOL)onMapObjectTapWithMapObject:(nonnull YMKMapObject *)mapObject point:(nonnull YMKPoint *)point {
    if (![mapObject isKindOfClass:[YMKPlacemarkMapObject class]]) {
        return NO;
    }
    NSValue *key = [NSValue valueWithNonretainedObject:mapObject];
    NSNumber *idx = imperativeIndexMap[key];
    if (idx == nil) {
        return NO;
    }
    if (_eventEmitter && [self isKindOfClass:[ClusteredYamapView class]]) {
        std::dynamic_pointer_cast<const ClusteredYamapViewEventEmitter>(_eventEmitter)->onClusterPlacemarkPress({
            .lat = point.latitude,
            .lon = point.longitude,
            .index = [idx intValue],
        });
    }
    return YES;
}

@end
