#import "RNCarPlay.h"
#import <React/RCTConvert.h>
#import <React/RCTRootView.h>

@implementation RNCarPlay

@synthesize interfaceController;
@synthesize window;
@synthesize selectedResultBlock;
@synthesize isNowPlayingActive;

+ (void) connectWithInterfaceController:(CPInterfaceController*)interfaceController window:(CPWindow*)window {
    RNCPStore * store = [RNCPStore sharedManager];
    store.interfaceController = interfaceController;
    store.window = window;
    [store setConnected:true];

    RNCarPlay *cp = [RNCarPlay allocWithZone:nil];
    if (cp.bridge) {
        [cp sendEventWithName:@"didConnect" body:@{}];
    }
}

+ (void) disconnect {
    RNCarPlay *cp = [RNCarPlay allocWithZone:nil];
    RNCPStore *store = [RNCPStore sharedManager];
    [store setConnected:false];

    if (cp.bridge) {
        [cp sendEventWithName:@"didDisconnect" body:@{}];
    }
}

RCT_EXPORT_MODULE();

+ (id)allocWithZone:(NSZone *)zone {
    static RNCarPlay *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [super allocWithZone:zone];
    });
    return sharedInstance;
}

- (NSArray<NSString *> *)supportedEvents
{
    return @[
        @"didConnect",
        @"didDisconnect",
        // interface
        @"barButtonPressed",
        @"backButtonPressed",
        @"didAppear",
        @"didDisappear",
        @"willAppear",
        @"willDisappear",
        // grid
        @"gridButtonPressed",
        // list
        @"didSelectListItem",
        // tabbar
        @"didSelectTemplate",
        // nowPlaying
        @"albumArtistButtonTapped",
        @"upNextButtonTapped",
        @"playbackRateButtonTapped",
        @"imageButtonTapped",
    ];
}

- (dispatch_queue_t)methodQueue
{
    return dispatch_get_main_queue();
}


-(UIImage *)imageWithTint:(UIImage *)image andTintColor:(UIColor *)tintColor {
    UIImage *imageNew = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    UIImageView *imageView = [[UIImageView alloc] initWithImage:imageNew];
    imageView.tintColor = tintColor;
    UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 0.0);
    [imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return tintedImage;
}

- (UIImage *)imageWithSize:(UIImage *)image convertToSize:(CGSize)size {
    UIGraphicsBeginImageContext(size);
    [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
    UIImage *destImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return destImage;
}

RCT_EXPORT_METHOD(checkForConnection) {
    RNCPStore *store = [RNCPStore sharedManager];
    if ([store isConnected]) {
        [self sendEventWithName:@"didConnect" body:@{}];
    }
}


RCT_EXPORT_METHOD(createTemplate:(NSString *)templateId config:(NSDictionary*)config) {
    RNCPStore *store = [RNCPStore sharedManager];

    NSString *type = [RCTConvert NSString:config[@"type"]];
    NSString *title = [RCTConvert NSString:config[@"title"]];
    NSArray *leadingNavigationBarButtons = [self parseBarButtons:[RCTConvert NSArray:config[@"leadingNavigationBarButtons"]] templateId:templateId];
    NSArray *trailingNavigationBarButtons = [self parseBarButtons:[RCTConvert NSArray:config[@"trailingNavigationBarButtons"]] templateId:templateId];

    CPTemplate *template = [[CPTemplate alloc] init];

    if ([type isEqualToString:@"grid"]) {
        NSArray *buttons = [self parseGridButtons:[RCTConvert NSArray:config[@"buttons"]] templateId:templateId];
        CPGridTemplate *gridTemplate = [[CPGridTemplate alloc] initWithTitle:title gridButtons:buttons];
        [gridTemplate setLeadingNavigationBarButtons:leadingNavigationBarButtons];
        [gridTemplate setTrailingNavigationBarButtons:trailingNavigationBarButtons];
        template = gridTemplate;
    }
    else if ([type isEqualToString:@"list"]) {
        NSUInteger bytes = [title lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
        NSString *computedTitle = bytes > 36 ?
            [[self sliceString:title withByteLength:36] stringByAppendingString:@"..."] :
            title;
        CPListTemplate *listTemplate = [[CPListTemplate alloc] initWithTitle:computedTitle sections:[NSMutableArray array]];
        NSArray *sections = [self parseSections:listTemplate sections:[RCTConvert NSArray:config[@"sections"]]];
        [listTemplate updateSections:sections];
        [listTemplate setLeadingNavigationBarButtons:leadingNavigationBarButtons];
        [listTemplate setTrailingNavigationBarButtons:trailingNavigationBarButtons];
        NSString *backTitle = bytes > 36 ? @"" : @" 返回";
        CPBarButton *backButton = [[CPBarButton alloc] initWithTitle:backTitle handler:^(CPBarButton * _Nonnull barButton) {
            [self sendEventWithName:@"backButtonPressed" body:@{@"templateId":templateId}];
            [self popTemplate:false];
        }];
        [listTemplate setBackButton:backButton];
        if (config[@"emptyViewTitleVariants"]) {
            listTemplate.emptyViewTitleVariants = [RCTConvert NSArray:config[@"emptyViewTitleVariants"]];
        }
        if (config[@"emptyViewSubtitleVariants"]) {
            listTemplate.emptyViewSubtitleVariants = [RCTConvert NSArray:config[@"emptyViewSubtitleVariants"]];
        }
        template = listTemplate;
    } else if ([type isEqualToString:@"nowplaying"]) {
        CPNowPlayingTemplate *nowPlayingTemplate = [CPNowPlayingTemplate sharedTemplate];
        [nowPlayingTemplate setAlbumArtistButtonEnabled:[RCTConvert BOOL:config[@"albumArtistButton"]]];
        [nowPlayingTemplate setUpNextTitle:[RCTConvert NSString:config[@"upNextTitle"]]];
        [nowPlayingTemplate setUpNextButtonEnabled:[RCTConvert BOOL:config[@"upNextButton"]]];

        __weak RNCarPlay *weakSelf = self;

        NSMutableArray *nowPlayingButtons = [NSMutableArray array];
        if (config[@"imageButton"] != nil) {
            RNCPStore *store = [RNCPStore sharedManager];
            UIImage *imageButtonImage = [UIImage imageNamed:config[@"imageButton"] inBundle:[NSBundle mainBundle] compatibleWithTraitCollection:store.interfaceController.carTraitCollection];

            if (imageButtonImage != nil) {
                CPNowPlayingImageButton *imageButton = [[CPNowPlayingImageButton alloc] initWithImage:imageButtonImage handler:^(__kindof CPNowPlayingButton * _Nonnull button) {
                    if (!weakSelf) {
                        return;
                    }
                    [weakSelf sendEventWithName:@"imageButtonTapped" body:@{}];
                }];
                if (imageButton != nil) {
                    [nowPlayingButtons addObject:imageButton];
                }
            }
        }
        if (config[@"playbackRateButton"] && [RCTConvert BOOL:config[@"playbackRateButton"]]) {
            CPNowPlayingPlaybackRateButton *playbackRateButton = [[CPNowPlayingPlaybackRateButton alloc] initWithHandler:^(__kindof CPNowPlayingButton * _Nonnull button) {
                if (!weakSelf) {
                    return;
                }
                [weakSelf sendEventWithName:@"playbackRateButtonTapped" body:@{}];
            }];
            [nowPlayingButtons addObject:playbackRateButton];
        }
        [nowPlayingTemplate updateNowPlayingButtons:nowPlayingButtons];
        template = nowPlayingTemplate;
    } else if ([type isEqualToString:@"tabbar"]) {
        CPTabBarTemplate *tabBarTemplate = [[CPTabBarTemplate alloc] initWithTemplates:[self parseTemplatesFrom:config]];
        tabBarTemplate.delegate = self;
        template = tabBarTemplate;
    } else if ([type isEqualToString:@"alert"]) {
        NSMutableArray<CPAlertAction *> *actions = [NSMutableArray new];
        NSArray<NSDictionary*> *_actions = [RCTConvert NSDictionaryArray:config[@"actions"]];
        for (NSDictionary *_action in _actions) {
            CPAlertAction *action = [[CPAlertAction alloc] initWithTitle:[RCTConvert NSString:_action[@"title"]] style:[self parseAlertActionStyle:_action[@"style"]] handler:^(CPAlertAction *a) {
                [self sendEventWithName:@"actionButtonPressed" body:@{@"templateId":templateId, @"id": _action[@"id"] }];
            }];
            [actions addObject:action];
        }
        NSArray<NSString*>* titleVariants = [RCTConvert NSArray:config[@"titleVariants"]];
        CPAlertTemplate *alertTemplate = [[CPAlertTemplate alloc] initWithTitleVariants:titleVariants actions:actions];
        template = alertTemplate;
    }

    if (config[@"tabSystemItem"]) {
        template.tabSystemItem = [RCTConvert NSInteger:config[@"tabSystemItem"]];
    }
    if (config[@"tabSystemImg"]) {
        template.tabImage = [UIImage systemImageNamed:[RCTConvert NSString:config[@"tabSystemImg"]]];
    }
    if (config[@"tabImage"]) {
        template.tabImage = [RCTConvert UIImage:config[@"tabImage"]];
    }


    [template setUserInfo:@{ @"templateId": templateId }];
    [store setTemplate:templateId template:template];
}

RCT_EXPORT_METHOD(setRootTemplate:(NSString *)templateId animated:(BOOL)animated) {
    RNCPStore *store = [RNCPStore sharedManager];
    CPTemplate *template = [store findTemplateById:templateId];

    store.interfaceController.delegate = self;

    if (template) {
        [store.interfaceController setRootTemplate:template animated:animated completion:^(BOOL done, NSError * _Nullable err) {
            if (err != nil) {
                NSLog(@"error %@", err);
            }
        }];
    } else {
        NSLog(@"Failed to find template %@", template);
    }
}

RCT_EXPORT_METHOD(pushTemplate:(NSString *)templateId animated:(BOOL)animated) {
    RNCPStore *store = [RNCPStore sharedManager];
    CPTemplate *template = [store findTemplateById:templateId];
    if (template) {
        [store.interfaceController pushTemplate:template animated:animated completion:^(BOOL done, NSError * _Nullable err) {
            if (err != nil) {
                NSLog(@"error %@", err);
            }
        }];
    } else {
        NSLog(@"Failed to find template %@", template);
    }
}

RCT_EXPORT_METHOD(popToTemplate:(NSString *)templateId animated:(BOOL)animated) {
    RNCPStore *store = [RNCPStore sharedManager];
    CPTemplate *template = [store findTemplateById:templateId];
    if (template) {
        [store.interfaceController popToTemplate:template animated:animated completion:^(BOOL done, NSError * _Nullable err) {
            if (err != nil) {
                NSLog(@"error %@", err);
            }
        }];
    } else {
        NSLog(@"Failed to find template %@", template);
    }
}

RCT_EXPORT_METHOD(popToRootTemplate:(BOOL)animated) {
    RNCPStore *store = [RNCPStore sharedManager];
    [store.interfaceController popToRootTemplateAnimated:animated completion:^(BOOL done, NSError * _Nullable err) {
        NSLog(@"error %@", err);
        // noop
    }];
}

RCT_EXPORT_METHOD(popTemplate:(BOOL)animated) {
    RNCPStore *store = [RNCPStore sharedManager];
    [store.interfaceController popTemplateAnimated:animated completion:^(BOOL done, NSError * _Nullable err) {
        if (err != nil) {
            NSLog(@"error %@", err);
        }
        // noop
    }];
}

RCT_EXPORT_METHOD(presentTemplate:(NSString *)templateId animated:(BOOL)animated) {
    RNCPStore *store = [RNCPStore sharedManager];
    CPTemplate *template = [store findTemplateById:templateId];
    if (template) {
        [store.interfaceController presentTemplate:template animated:animated completion:^(BOOL done, NSError * _Nullable err) {
            if (err != nil) {
                NSLog(@"error %@", err);
            }
        }];
    } else {
        NSLog(@"Failed to find template %@", template);
    }
}

RCT_EXPORT_METHOD(dismissTemplate:(BOOL)animated) {
    RNCPStore *store = [RNCPStore sharedManager];
    [store.interfaceController dismissTemplateAnimated:animated];
}

RCT_EXPORT_METHOD(updatePlayingItem:(NSString*)templateId
                       sectionIndex:(nonnull NSNumber *)sectionIndex
                    nowPlayingIndex:(nonnull NSNumber *)nowPlayingIndex
                           resolver:(RCTPromiseResolveBlock)resolve
                           rejecter:(RCTPromiseRejectBlock)reject)
{
    RNCPStore *store = [RNCPStore sharedManager];
    NSInteger sectionIndexValue = [sectionIndex intValue];
    NSInteger nowPlayingIndexValue = [nowPlayingIndex intValue];
    CPTemplate *template = [store findTemplateById:templateId];
    if (template == nil) {
        resolve(@(-1));
        return;
    }
    CPListTemplate *listTemplate = (CPListTemplate *)template;
    CPListSection *section = nil;
    if (listTemplate.sectionCount > sectionIndexValue) {
        section = listTemplate.sections[sectionIndexValue];
        if (section == nil) {
            resolve(@(-1));
            return;
        }
    } else {
        resolve(@(-1));
        return;
    }
    NSUInteger index = [section.items indexOfObjectPassingTest:^BOOL(id<CPListTemplateItem> _Nonnull __strong item, NSUInteger idx, BOOL *stop) {
        if ([item isKindOfClass:[CPListImageRowItem class]]) {
            return NO;
        }
        CPListItem *it = (CPListItem *)item;
        if (it.playing) {
            if (nowPlayingIndexValue != idx) {
                [it setPlaying:NO];
            }
            return YES;
        }
        return NO;
    }];
    if (nowPlayingIndexValue != -1 && section.items.count > nowPlayingIndexValue && nowPlayingIndexValue >= 0) {
        CPListItem *i = (CPListItem *)(section.items[nowPlayingIndexValue]);
        if (i != nil) {
            [i setPlaying:YES];
        }
    }
    if (index == NSNotFound) {
        resolve(@(-1));
        return;
    }
    resolve(@(index));
}

RCT_EXPORT_METHOD(metadata:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    RNCPStore *store = [RNCPStore sharedManager];
    NSMutableDictionary *res = [[NSMutableDictionary alloc] init];
    [res setObject:@(CPListItem.maximumImageSize.width) forKey:@"maximumListItemImageWidth"];
    [res setObject:@(CPListItem.maximumImageSize.height) forKey:@"maximumListItemImageHeight"];
    [res setObject:@(CPListImageRowItem.maximumImageSize.width) forKey:@"maximumListImageRowItemImageWidth"];
    [res setObject:@(CPListImageRowItem.maximumImageSize.height) forKey:@"maximumListImageRowItemImageHeight"];
    [res setObject:@(CPMaximumNumberOfGridImages) forKey:@"maximumListGridImagesCount"];
    [res setObject:@(CPListTemplate.maximumItemCount) forKey:@"maximumListItemCount"];
    [res setObject:@(CPListTemplate.maximumSectionCount) forKey:@"maximumListSectionCount"];
    CGFloat displayScale = store.interfaceController.carTraitCollection.displayScale;
    [res setObject:(displayScale != 0 ? @(displayScale) : @(1)) forKey:@"displayScale"];
    resolve(res);
}

RCT_EXPORT_METHOD(updateListTemplate:(NSString*)templateId config:(NSDictionary*)config) {
    RNCPStore *store = [RNCPStore sharedManager];
    CPTemplate *template = [store findTemplateById:templateId];
    if (template && [template isKindOfClass:[CPListTemplate class]]) {
        CPListTemplate *listTemplate = (CPListTemplate *)template;
        if (config[@"leadingNavigationBarButtons"]) {
            NSArray *leadingNavigationBarButtons = [self parseBarButtons:[RCTConvert NSArray:config[@"leadingNavigationBarButtons"]] templateId:templateId];
            [listTemplate setLeadingNavigationBarButtons:leadingNavigationBarButtons];
        }
        if (config[@"trailingNavigationBarButtons"]) {
            NSArray *trailingNavigationBarButtons = [self parseBarButtons:[RCTConvert NSArray:config[@"trailingNavigationBarButtons"]] templateId:templateId];
            [listTemplate setTrailingNavigationBarButtons:trailingNavigationBarButtons];
        }
        if (config[@"emptyViewTitleVariants"]) {
            listTemplate.emptyViewTitleVariants = [RCTConvert NSArray:config[@"emptyViewTitleVariants"]];
        }
        if (config[@"emptyViewSubtitleVariants"]) {
            NSLog(@"%@", [RCTConvert NSArray:config[@"emptyViewSubtitleVariants"]]);
            listTemplate.emptyViewSubtitleVariants = [RCTConvert NSArray:config[@"emptyViewSubtitleVariants"]];
        }
    }
}

RCT_EXPORT_METHOD(updateNowPlayingTemplate:(NSDictionary*)config) {
    CPNowPlayingTemplate *nowPlayingTemplate = [CPNowPlayingTemplate sharedTemplate];
    if (config[@"albumArtistButton"] != nil) {
        [nowPlayingTemplate setAlbumArtistButtonEnabled:[RCTConvert BOOL:config[@"albumArtistButton"]]];
    }
    if (config[@"upNextTitle"] != nil) {
        [nowPlayingTemplate setUpNextTitle:[RCTConvert NSString:config[@"upNextTitle"]]];
    }
    if (config[@"upNextButton"] != nil) {
        [nowPlayingTemplate setUpNextButtonEnabled:[RCTConvert BOOL:config[@"upNextButton"]]];
    }
    __weak RNCarPlay *weakSelf = self;
    NSMutableArray *nowPlayingButtons = [NSMutableArray array];

    if (config[@"imageButton"] != nil) {
        RNCPStore *store = [RNCPStore sharedManager];
        UIImage *imageButtonImage = [UIImage imageNamed:config[@"imageButton"] inBundle:[NSBundle mainBundle] compatibleWithTraitCollection:store.interfaceController.carTraitCollection];

        if (imageButtonImage != nil) {
            CPNowPlayingImageButton *imageButton = [[CPNowPlayingImageButton alloc] initWithImage:imageButtonImage handler:^(__kindof CPNowPlayingButton * _Nonnull button) {
                if (!weakSelf) {
                    return;
                }
                [weakSelf sendEventWithName:@"imageButtonTapped" body:@{}];
            }];
            if (imageButton != nil) {
                [nowPlayingButtons addObject:imageButton];
            }
        }
    }
    if (config[@"playbackRateButton"] && [RCTConvert BOOL:config[@"playbackRateButton"]]) {
        CPNowPlayingPlaybackRateButton *playbackRateButton = [[CPNowPlayingPlaybackRateButton alloc] initWithHandler:^(__kindof CPNowPlayingButton * _Nonnull button) {
            if (!weakSelf) {
                return;
            }
            [weakSelf sendEventWithName:@"playbackRateButtonTapped" body:@{}];
        }];
        if (playbackRateButton != nil) {
            [nowPlayingButtons addObject:playbackRateButton];
        }
    }
    [nowPlayingTemplate updateNowPlayingButtons:nowPlayingButtons];
}

RCT_EXPORT_METHOD(updateTabBarTemplates:(NSString *)templateId templates:(NSDictionary*)config) {
    RNCPStore *store = [RNCPStore sharedManager];
    CPTemplate *template = [store findTemplateById:templateId];
    if (template) {
        CPTabBarTemplate *tabBarTemplate = (CPTabBarTemplate*) template;
        [tabBarTemplate updateTemplates:[self parseTemplatesFrom:config]];
    } else {
        NSLog(@"Failed to find template %@", template);
    }
}


RCT_EXPORT_METHOD(updateListTemplateSections:(NSString *)templateId sections:(NSArray*)sections) {
    RNCPStore *store = [RNCPStore sharedManager];
    CPTemplate *template = [store findTemplateById:templateId];
    if (template) {
        CPListTemplate *listTemplate = (CPListTemplate*) template;
        [listTemplate updateSections:[self parseSections:listTemplate sections:sections]];
    } else {
        NSLog(@"Failed to find template %@", template);
    }
}

RCT_EXPORT_METHOD(updateListTemplateItem:(NSString *)templateId config:(NSDictionary*)config) {
    RNCPStore *store = [RNCPStore sharedManager];
    CPTemplate *template = [store findTemplateById:templateId];
    if (template) {
        CPListTemplate *listTemplate = (CPListTemplate*) template;
        NSInteger sectionIndex = [RCTConvert NSInteger:config[@"sectionIndex"]];
        if (sectionIndex >= listTemplate.sections.count) {
            NSLog(@"Failed to update item at section %ld, sections size is %lu", (long)sectionIndex, (unsigned long)listTemplate.sections.count);
            return;
        }
        CPListSection *section = listTemplate.sections[sectionIndex];
        NSInteger index = [RCTConvert NSInteger:config[@"itemIndex"]];
        if (index >= section.items.count) {
            NSLog(@"Failed to update item at index %ld, section size is %lu", (long)index, (unsigned long)section.items.count);
            return;
        }
        CPListItem *item = (CPListItem *)section.items[index];
        if (config[@"imgUrl"]) {
            [item setImage:[
                [UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[RCTConvert NSString:config[@"imgUrl"]]]]]
            ];
        }
        if (config[@"image"]) {
            [item setImage:[UIImage imageNamed:config[@"image"]]];
        }
        if (config[@"text"]) {
            [item setText:[RCTConvert NSString:config[@"text"]]];
        }
        if (config[@"detailText"]) {
            [item setDetailText:[RCTConvert NSString:config[@"text"]]];
        }
        if (config[@"isPlaying"] != nil) {
            [item setPlaying:[RCTConvert BOOL:config[@"isPlaying"]]];
        }
        if (config[@"playbackProgress"] != nil) {
            [item setPlaybackProgress:[RCTConvert CGFloat:config[@"playbackProgress"]]];
        }
        if (config[@"playingIndicatorLocation"] != nil) {
            [item setPlayingIndicatorLocation:[RCTConvert NSInteger:config[@"playingIndicatorLocation"]]];
        }
        if (config[@"accessoryType"] != nil) {
            [item setAccessoryType:[RCTConvert NSInteger:config[@"accessoryType"]]];
        }
        if (config[@"isEnabled"] != nil) {
            if (@available(iOS 15.0, *)) {
                [item setEnabled:[RCTConvert BOOL:config[@"isEnabled"]]];
            } else {
                // Fallback on earlier versions
            }
        }


    } else {
        NSLog(@"Failed to find template %@", template);
    }
}

RCT_EXPORT_METHOD(enableNowPlaying:(BOOL)enable) {
    if (enable && !isNowPlayingActive) {
        [CPNowPlayingTemplate.sharedTemplate addObserver:self];
    } else if (!enable && isNowPlayingActive) {
        [CPNowPlayingTemplate.sharedTemplate removeObserver:self];
    }
}

RCT_EXPORT_METHOD(reactToSelectedResult:(BOOL)status) {
    if (self.selectedResultBlock) {
        self.selectedResultBlock();
        self.selectedResultBlock = nil;
    }
}

# pragma parsers

- (NSArray<__kindof CPTemplate*>*) parseTemplatesFrom:(NSDictionary*)config {
    RNCPStore *store = [RNCPStore sharedManager];
    NSMutableArray<__kindof CPTemplate*> *templates = [NSMutableArray new];
    NSArray<NSDictionary*> *tpls = [RCTConvert NSDictionaryArray:config[@"templates"]];
    for (NSDictionary *tpl in tpls) {
        CPTemplate *templ = [store findTemplateById:tpl[@"id"]];
        // @todo UITabSystemItem
        [templates addObject:templ];
    }
    return templates;
}

- (NSArray<CPBarButton*>*) parseBarButtons:(NSArray*)barButtons templateId:(NSString *)templateId {
    NSMutableArray *result = [NSMutableArray array];
    for (NSDictionary *barButton in barButtons) {
        CPBarButtonType _type;
        NSString *_id = [barButton objectForKey:@"id"];
        NSString *type = [barButton objectForKey:@"type"];
        if (type && [type isEqualToString:@"image"]) {
            _type = CPBarButtonTypeImage;
        } else {
            _type = CPBarButtonTypeText;
        }
        CPBarButton *_barButton = [[CPBarButton alloc] initWithType:_type handler:^(CPBarButton * _Nonnull barButton) {
            [self sendEventWithName:@"barButtonPressed" body:@{@"id": _id, @"templateId":templateId}];
        }];
        BOOL _disabled = [barButton objectForKey:@"disabled"];
        [_barButton setEnabled:!_disabled];

        if (_type == CPBarButtonTypeText) {
            NSString *_title = [barButton objectForKey:@"title"];
            [_barButton setTitle:_title];
        } else if (_type == CPBarButtonTypeImage) {
            UIImage *_image = [RCTConvert UIImage:[barButton objectForKey:@"image"]];
            [_barButton setImage:_image];
        }
        [result addObject:_barButton];
    }
    return result;
}

- (NSArray<CPListSection*>*)parseSections:(CPListTemplate *)listTemplate sections:(NSArray*)sections {
    NSMutableArray *result = [NSMutableArray array];
    int index = 0;
    for (NSDictionary *section in sections) {
        NSArray *items = [section objectForKey:@"items"];
        NSString *_sectionIndexTitle = [section objectForKey:@"sectionIndexTitle"];
        NSString *_header = [section objectForKey:@"header"];
        NSArray *_items = [self parseListItems:listTemplate items:items startIndex:index];
        CPListSection *_section = [[CPListSection alloc] initWithItems:_items header:_header sectionIndexTitle:_sectionIndexTitle];
        [result addObject:_section];
        int count = (int) [items count];
        index = index + count;
    }
    return result;
}

- (NSArray<CPListItem*>*)parseListItems:(CPListTemplate *)listTemplate items:(NSArray*)items startIndex:(int)startIndex {
    NSMutableArray *_items = [NSMutableArray array];
    int index = startIndex;
    for (NSDictionary *item in items) {
        NSString *_type = [item objectForKey:@"type"];
        if ([_type isEqualToString:@"text"]) {
            NSString *_detailText = [item objectForKey:@"detailText"];
            NSString *_text = [item objectForKey:@"text"];
            UIImage *_image = nil;
            if (item[@"imgUrl"]) {
                _image = [self createListItemPlaceholderImage];
            } else if (item[@"image"]) {
                _image = [UIImage imageNamed:item[@"image"]];
            }
            CPListItem *_item = [[CPListItem alloc] initWithText:_text detailText:_detailText image:_image];

            if (item[@"imgUrl"]) {
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                    NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[RCTConvert NSString:item[@"imgUrl"]]]];
                    dispatch_async(dispatch_get_main_queue(), ^{
                        UIImage *networkImage = [[UIImage alloc] initWithData:imgData];
                        if (_item != nil) {
                            [_item setImage:networkImage];
                        }
                    });
                });

            }

            if ([item objectForKey:@"isPlaying"] != nil) {
                [_item setPlaying:[RCTConvert BOOL:[item objectForKey:@"isPlaying"]]];
            }
            if ([item objectForKey:@"playbackProgress"] != nil) {
                [_item setPlaying:[RCTConvert CGFloat:[item objectForKey:@"playbackProgress"]]];
            }
            if ([item objectForKey:@"playingIndicatorLocation"] != nil) {
                [_item setPlayingIndicatorLocation:[RCTConvert NSInteger:[item objectForKey:@"playingIndicatorLocation"]]];
            }
            if ([item objectForKey:@"accessoryType"] != nil) {
                [_item setAccessoryType:[RCTConvert NSInteger:[item objectForKey:@"accessoryType"]]];
            }

            if ([item objectForKey:@"isEnabled"] != nil) {
                if (@available(iOS 15.0, *)) {
                    [_item setEnabled:[RCTConvert BOOL:[item objectForKey:@"isEnabled"]]];
                } else {
                    // Fallback on earlier versions
                }
            }

            [_item setUserInfo:@{ @"index": @(index) }];
            __weak RNCarPlay *weakSelf = self;
            _item.handler = ^(id <CPSelectableListItem> it,
                              dispatch_block_t completionBlock) {
                if (it == nil) {
                    completionBlock();
                    return;
                }
                NSNumber* index = [it.userInfo objectForKey:@"index"];
                [weakSelf sendTemplateEventWithName:listTemplate name:@"didSelectListItem" json:@{ @"index": index }];
                weakSelf.selectedResultBlock = completionBlock;
            };

            [_items addObject:_item];
        } else if ([_type isEqualToString:@"image"]) {
            NSMutableArray *_images = [NSMutableArray array];
            NSString *_text = [item objectForKey:@"text"];
            if (item[@"gridImageUrls"] != nil) {
                NSArray *_urls = [RCTConvert NSArray:item[@"gridImageUrls"]];
                for (id _url in _urls) {
                    UIImage *_image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[RCTConvert NSString:_url]]]];
                    [_images addObject:_image];
                }
            }
            CPListImageRowItem *_item = [[CPListImageRowItem alloc] initWithText:_text images:_images];

            if ([item objectForKey:@"isEnabled"] != nil) {
                if (@available(iOS 15.0, *)) {
                    [_item setEnabled:[RCTConvert BOOL:[item objectForKey:@"isEnabled"]]];
                } else {
                    // Fallback on earlier versions
                }
            }

            [_item setUserInfo:@{ @"index": @(index) }];
            __weak RNCarPlay *weakSelf = self;
            _item.handler = ^(id <CPSelectableListItem> it,
                              dispatch_block_t completionBlock) {
                if (it == nil) {
                    completionBlock();
                    return;
                }
                NSNumber* index = [it.userInfo objectForKey:@"index"];
                [weakSelf sendTemplateEventWithName:listTemplate name:@"didSelectListItem" json:@{ @"index": index }];
                weakSelf.selectedResultBlock = completionBlock;
            };

            [_items addObject:_item];
        }
        index = index + 1;
    }
    return _items;
}

- (NSArray<CPGridButton*>*)parseGridButtons:(NSArray*)buttons templateId:(NSString*)templateId {
    NSMutableArray *result = [NSMutableArray array];
    int index = 0;
    for (NSDictionary *button in buttons) {
        NSString *_id = [button objectForKey:@"id"];
        NSArray<NSString*> *_titleVariants = [button objectForKey:@"titleVariants"];
        UIImage *_image = [RCTConvert UIImage:[button objectForKey:@"image"]];
        CPGridButton *_button = [[CPGridButton alloc] initWithTitleVariants:_titleVariants image:_image handler:^(CPGridButton * _Nonnull barButton) {
            [self sendEventWithName:@"gridButtonPressed" body:@{@"id": _id, @"templateId":templateId, @"index": @(index) }];
        }];
        BOOL _disabled = [button objectForKey:@"disabled"];
        [_button setEnabled:!_disabled];
        [result addObject:_button];
        index = index + 1;
    }
    return result;
}

- (CPAlertAction*)parseAlertAction:(NSDictionary*)json body:(NSDictionary*)body {
    return [[CPAlertAction alloc] initWithTitle:[RCTConvert NSString:json[@"title"]] style:(CPAlertActionStyle) [RCTConvert NSUInteger:json[@"style"]] handler:^(CPAlertAction * _Nonnull action) {
        [self sendEventWithName:@"alertActionPressed" body:body];
    }];
}

- (UIImage *)scaleImage:(UIImage *)image toSize:(CGSize)newSize {
    UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
    [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}

- (UIImage *)createListItemPlaceholderImage {
    RNCPStore *store = [RNCPStore sharedManager];
    CGFloat displayScale = store.interfaceController.carTraitCollection.displayScale;
    CGRect rect = CGRectMake(0.0f, 0.0f, CPListItem.maximumImageSize.width * displayScale, CPListItem.maximumImageSize.height * displayScale);
    UIColor *color = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.16];
    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, rect);

    UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return theImage;
}

- (void)sendTemplateEventWithName:(CPTemplate *)template name:(NSString*)name {
    [self sendTemplateEventWithName:template name:name json:@{}];
}

- (void)sendTemplateEventWithName:(CPTemplate *)template name:(NSString*)name json:(NSDictionary*)json {
    NSMutableDictionary *body = [[NSMutableDictionary alloc] initWithDictionary:json];
    NSDictionary *userInfo = [template userInfo];
    if ([userInfo objectForKey:@"templateId"] != nil) {
        [body setObject:[userInfo objectForKey:@"templateId"] forKey:@"templateId"];
        [self sendEventWithName:name body:body];
    }
}

- (CPAlertActionStyle)parseAlertActionStyle:(NSString*) json {
    if ([json isEqualToString:@"cancel"]) {
        return CPAlertActionStyleCancel;
    } else if ([json isEqualToString:@"destructive"]) {
        return CPAlertActionStyleDestructive;
    }
    return CPAlertActionStyleDefault;
}

# pragma TabBarTemplate
- (void)tabBarTemplate:(CPTabBarTemplate *)tabBarTemplate didSelectTemplate:(__kindof CPTemplate *)selectedTemplate {
    NSString* selectedTemplateId = [[selectedTemplate userInfo] objectForKey:@"templateId"];
    [self sendTemplateEventWithName:tabBarTemplate name:@"didSelectTemplate" json:@{@"selectedTemplateId":selectedTemplateId}];
}

# pragma InterfaceController

- (void)templateDidAppear:(CPTemplate *)aTemplate animated:(BOOL)animated {
    [self sendTemplateEventWithName:aTemplate name:@"didAppear" json:@{ @"animated": @(animated) }];
}

- (void)templateDidDisappear:(CPTemplate *)aTemplate animated:(BOOL)animated {
    [self sendTemplateEventWithName:aTemplate name:@"didDisappear" json:@{ @"animated": @(animated) }];
}

- (void)templateWillAppear:(CPTemplate *)aTemplate animated:(BOOL)animated {
    [self sendTemplateEventWithName:aTemplate name:@"willAppear" json:@{ @"animated": @(animated) }];
}

- (void)templateWillDisappear:(CPTemplate *)aTemplate animated:(BOOL)animated {
    [self sendTemplateEventWithName:aTemplate name:@"willDisappear" json:@{ @"animated": @(animated) }];
}

# pragma NowPlaying

- (void)nowPlayingTemplateUpNextButtonTapped:(CPNowPlayingTemplate *)nowPlayingTemplate {
    [self sendEventWithName:@"upNextButtonTapped" body:@{}];
}

- (void)nowPlayingTemplateAlbumArtistButtonTapped:(CPNowPlayingTemplate *)nowPlayingTemplate {
    [self sendEventWithName:@"albumArtistButtonTapped" body:@{}];
}

- (NSString *)sliceString:(NSString *)originalString withByteLength:(NSUInteger)length
{
  NSData* originalData = [originalString dataUsingEncoding:NSUTF8StringEncoding];
  const char *originalBytes = originalData.bytes;

  //make sure to use a loop to get a not nil string.
  //because your certain length data may be not decode by NSString
  for (NSUInteger i = length; i > 0; i--) {
    @autoreleasepool {
      NSData *data = [NSData dataWithBytes:originalBytes length:i];
      NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
      if (string) {
        return string;
      }
    }
  }
  return @"";
}

@end
