#import "MMKVNative.h"
#import "YeetJSIUtils.h"

#import "SecureStorage.h"
#import <MMKV/MMKV.h>
#import <React/RCTBridge+Private.h>
#import <React/RCTUtils.h>
#import <jsi/jsi.h>

using namespace facebook;
using namespace jsi;
using namespace std;

@implementation MMKVNative
@synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;
NSString *rPath = @"";
NSMutableDictionary *mmkvInstances;
NSMutableDictionary *serviceNames;
SecureStorage *_secureStorage;

RCT_EXPORT_MODULE()

+ (BOOL)requiresMainQueueSetup {
    
    return YES;
}

- (instancetype)init
{
    self = [super init];
    RCTExecuteOnMainQueue(^{
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
                                                             NSUserDomainMask, YES);
        NSString *libraryPath = (NSString *)[paths firstObject];
        NSString *rootDir = [libraryPath stringByAppendingPathComponent:@"mmkv"];
        rPath = rootDir;
        _secureStorage = [[SecureStorage alloc] init];
        [MMKV initializeMMKV:rootDir];
    });
    
    
    return self;
}

MMKV *getInstance(NSString *ID) {
    if ([[mmkvInstances allKeys] containsObject:ID]) {
        MMKV *kv = [mmkvInstances objectForKey:ID];
        
        return kv;
    } else {
        return NULL;
    }
}

NSString *getServiceName(NSString *alias) {
    if ([[serviceNames allKeys] containsObject:alias]) {
        NSString *serviceName = [serviceNames objectForKey:alias];
        return serviceName;
    } else {
        return nil;
    }
}

void setServiceName(NSString *alias, NSString *serviceName) {
    [serviceNames setObject:serviceName forKey: alias];
}

// Installing JSI Bindings as done by
// https://github.com/mrousavy/react-native-mmkv
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install)
{
    RCTCxxBridge* cxxBridge = (RCTCxxBridge*)_bridge;
    if (cxxBridge == nil) {
        return @false;
    }

    auto jsiRuntime = (jsi::Runtime*) cxxBridge.runtime;
    if (jsiRuntime == nil) {
        return @false;
    }
    
    mmkvInstances = [NSMutableDictionary dictionary];
    serviceNames = [NSMutableDictionary dictionary];
    
    [self migrate];
    install(*(jsi::Runtime *)jsiRuntime);
    return @true;
}

MMKV *createInstance(NSString *ID, MMKVMode mode, NSString *key,
                     NSString *path) {
    
    MMKV *kv;
    
    if (![key isEqual:@""] && [path isEqual:@""]) {
        NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding];
        kv = [MMKV mmkvWithID:ID cryptKey:cryptKey mode:mode];
    } else if (![path isEqual:@""] && [key isEqual:@""]) {
        kv = [MMKV mmkvWithID:ID mode:mode];
    } else if (![path isEqual:@""] && ![key isEqual:@""]) {
        NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding];
        kv = [MMKV mmkvWithID:ID cryptKey:cryptKey mode:mode];
    } else {
        kv = [MMKV mmkvWithID:ID mode:mode];
    }
    [mmkvInstances setObject:kv forKey:ID];
    return kv;
}

void setIndex(MMKV *kv, NSString *type, NSString *key) {
    
    NSMutableArray *indexer = [NSMutableArray array];
    
    if ([kv containsKey:type]) {
        indexer = [kv getObjectOfClass:NSMutableArray.class forKey:type];
    }
    if (![indexer containsObject:key]) {
        [indexer addObject:key];
        [kv setObject:indexer forKey:type];
    }
}

NSMutableArray *getIndex(MMKV *kv, NSString *type) {
    
    NSMutableArray *indexer = [NSMutableArray array];
    
    if ([kv containsKey:type]) {
        return [kv getObjectOfClass:NSMutableArray.class forKey:type];
    } else {
        return indexer;
    }
}

void removeKeyFromIndexer(MMKV *kv, NSString *key) {
    
    NSMutableArray *index = getIndex(kv, @"stringIndex");
    
    if (index != NULL && [index containsObject:key]) {
        
        [index removeObject:key];
        [kv setObject:index forKey:@"stringIndex"];
        return;
    }
    
    index = getIndex(kv, @"numberIndex");
    
    if (index != NULL && [index containsObject:key]) {
        
        [index removeObject:key];
        [kv setObject:index forKey:@"numberIndex"];
        return;
    }
    
    index = getIndex(kv, @"boolIndex");
    
    if (index != NULL && [index containsObject:key]) {
        
        [index removeObject:key];
        [kv setObject:index forKey:@"boolIndex"];
        return;
    }
    
    index = getIndex(kv, @"mapIndex");
    
    if (index != NULL && [index containsObject:key]) {
        
        [index removeObject:key];
        [kv setObject:index forKey:@"mapIndex"];
        return;
    }
    
    index = getIndex(kv, @"arrayIndex");
    
    if (index != NULL && [index containsObject:key]) {
        
        [index removeObject:key];
        [kv setObject:index forKey:@"arrayIndex"];
        return;
    }
}

static void install(jsi::Runtime &jsiRuntime) {
    
    auto initializeMMKV = Function::createFromHostFunction(
                                                           jsiRuntime, PropNameID::forAscii(jsiRuntime, "initializeMMKV"), 0,
                                                           [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                              size_t count) -> Value {
        [MMKV initializeMMKV:rPath];
        return Value::undefined();
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "initializeMMKV",
                                    move(initializeMMKV));
    
    auto setupMMKVInstance = Function::createFromHostFunction(
                                                              jsiRuntime, PropNameID::forAscii(jsiRuntime, "setupMMKVInstance"), 4,
                                                              [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                                 size_t count) -> Value {
        NSString *ID = convertJSIStringToNSString(
                                                  runtime, arguments[0].getString(runtime));
        
        MMKVMode mode = (MMKVMode)(int)arguments[1].getNumber();
        NSString *cryptKey = convertJSIStringToNSString(
                                                        runtime, arguments[2].getString(runtime));
        
        NSString *path = convertJSIStringToNSString(
                                                    runtime, arguments[3].getString(runtime));
        
        createInstance(ID, mode, cryptKey, path);
        
        return Value(true);
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "setupMMKVInstance",
                                    move(setupMMKVInstance));

    auto setMMKVServiceName = Function::createFromHostFunction(jsiRuntime, PropNameID::forAscii(jsiRuntime, "setMMKVServiceName"), 2,
                                                                [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                                    size_t count) -> Value {
        NSString *alias = convertJSIStringToNSString(
                                                runtime, arguments[0].getString(runtime));
        NSString *serviceName = convertJSIStringToNSString(
                                                runtime, arguments[1].getString(runtime));
        setServiceName(alias, serviceName);
        // [_secureStorage setServiceName:serviceName];
        return Value::undefined();
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "setMMKVServiceName",
                                    move(setMMKVServiceName));

    
    auto setStringMMKV = Function::createFromHostFunction(
                                                          jsiRuntime, PropNameID::forAscii(jsiRuntime, "setStringMMKV"), 3,
                                                          [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                             size_t count) -> Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[2].getString(runtime)));
        
        if (!kv) {
            return Value::undefined();
        }
        
        NSString *key = convertJSIStringToNSString(
                                                   runtime, arguments[0].getString(runtime));
        
        setIndex(kv, @"stringIndex", key);
        
        [kv setString:convertJSIStringToNSString(
                                                 runtime, arguments[1].getString(runtime))
               forKey:key];
        
        return Value(true);
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "setStringMMKV",
                                    move(setStringMMKV));
    
    auto getStringMMKV = Function::createFromHostFunction(
                                                          jsiRuntime, PropNameID::forAscii(jsiRuntime, "getStringMMKV"), 2,
                                                          [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                             size_t count) -> Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[1].getString(runtime)));
        
        if (!kv) {
            return Value::undefined();
        }
        
        NSString *key = convertJSIStringToNSString(
                                                   runtime, arguments[0].getString(runtime));
        
        if ([kv containsKey:key]) {
            return Value(
                         convertNSStringToJSIString(runtime, [kv getStringForKey:key]));
        } else {
            return Value::null();
        }
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "getStringMMKV",
                                    move(getStringMMKV));
    
    auto setMapMMKV = Function::createFromHostFunction(
                                                       jsiRuntime, PropNameID::forAscii(jsiRuntime, "setMapMMKV"), 3,
                                                       [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                          size_t count) -> Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[2].getString(runtime)));
        
        if (!kv) {
            return Value::undefined();
        }
        
        NSString *key = convertJSIStringToNSString(
                                                   runtime, arguments[0].getString(runtime));
        
        setIndex(kv, @"mapIndex", key);
        
        [kv setString:convertJSIStringToNSString(
                                                 runtime, arguments[1].getString(runtime))
               forKey:key];
        
        return Value(true);
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "setMapMMKV", move(setMapMMKV));
    
    auto getMapMMKV = Function::createFromHostFunction(
                                                       jsiRuntime, PropNameID::forAscii(jsiRuntime, "getMapMMKV"), 2,
                                                       [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                          size_t count) -> Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[1].getString(runtime)));
        
        if (!kv) {
            return Value::undefined();
        }
        
        NSString *key = convertJSIStringToNSString(
                                                   runtime, arguments[0].getString(runtime));
        
        if ([kv containsKey:key]) {
            return Value(
                         convertNSStringToJSIString(runtime, [kv getStringForKey:key]));
        } else {
            return Value::null();
        }
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "getMapMMKV", move(getMapMMKV));
    
    auto setArrayMMKV = Function::createFromHostFunction(
                                                         jsiRuntime, PropNameID::forAscii(jsiRuntime, "setArrayMMKV"), 3,
                                                         [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                            size_t count) -> Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[2].getString(runtime)));
        
        if (!kv) {
            return Value::undefined();
        }
        
        NSString *key = convertJSIStringToNSString(
                                                   runtime, arguments[0].getString(runtime));
        
        setIndex(kv, @"arrayIndex", key);
        
        [kv setString:convertJSIStringToNSString(
                                                 runtime, arguments[1].getString(runtime))
               forKey:key];
        
        return Value(true);
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "setArrayMMKV",
                                    move(setArrayMMKV));
    
    auto getArrayMMKV = Function::createFromHostFunction(
                                                         jsiRuntime, PropNameID::forAscii(jsiRuntime, "getArrayMMKV"), 2,
                                                         [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                            size_t count) -> Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[1].getString(runtime)));
        
        if (!kv) {
            return Value::undefined();
        }
        
        NSString *key = convertJSIStringToNSString(
                                                   runtime, arguments[0].getString(runtime));
        
        if ([kv containsKey:key]) {
            return Value(
                         convertNSStringToJSIString(runtime, [kv getStringForKey:key]));
        } else {
            return Value::null();
        }
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "getArrayMMKV",
                                    move(getArrayMMKV));
    
    auto setNumberMMKV = Function::createFromHostFunction(
                                                          jsiRuntime, PropNameID::forAscii(jsiRuntime, "setNumberMMKV"), 3,
                                                          [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                             size_t count) -> Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[2].getString(runtime)));
        
        if (!kv) {
            return Value::undefined();
        }
        
        NSString *key = convertJSIStringToNSString(
                                                   runtime, arguments[0].getString(runtime));
        
        setIndex(kv, @"numberIndex", key);
        
        [kv setDouble:arguments[1].getNumber() forKey:key];
        
        return Value(true);
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "setNumberMMKV",
                                    move(setNumberMMKV));
    
    auto getNumberMMKV = Function::createFromHostFunction(
                                                          jsiRuntime, PropNameID::forAscii(jsiRuntime, "getNumberMMKV"), 2,
                                                          [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                             size_t count) -> Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[1].getString(runtime)));
        
        if (!kv) {
            return Value::undefined();
        }
        
        NSString *key = convertJSIStringToNSString(
                                                   runtime, arguments[0].getString(runtime));
        
        if ([kv containsKey:key]) {
            return Value([kv getDoubleForKey:key]);
        } else {
            return Value::null();
        }
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "getNumberMMKV",
                                    move(getNumberMMKV));
    
    auto setBoolMMKV = Function::createFromHostFunction(
                                                        jsiRuntime, PropNameID::forAscii(jsiRuntime, "setBoolMMKV"), 3,
                                                        [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                           size_t count) -> Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[2].getString(runtime)));
        
        if (!kv) {
            return Value::undefined();
        }
        
        NSString *key = convertJSIStringToNSString(
                                                   runtime, arguments[0].getString(runtime));
        
        setIndex(kv, @"boolIndex", key);
        
        [kv setBool:arguments[1].getBool() forKey:key];
        
        return Value(true);
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "setBoolMMKV", move(setBoolMMKV));
    
    auto getBoolMMKV = Function::createFromHostFunction(
                                                        jsiRuntime, PropNameID::forAscii(jsiRuntime, "getBoolMMKV"), 2,
                                                        [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                           size_t count) -> Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[1].getString(runtime)));
        
        if (!kv) {
            return Value::undefined();
        }
        
        NSString *key = convertJSIStringToNSString(
                                                   runtime, arguments[0].getString(runtime));
        
        if ([kv containsKey:key]) {
            return Value([kv getBoolForKey:key]);
        } else {
            return Value::null();
        }
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "getBoolMMKV", move(getBoolMMKV));
    
    auto removeValueMMKV = jsi::Function::createFromHostFunction(
                                                                 jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "removeValueMMKV"),
                                                                 2, // key
                                                                 [](jsi::Runtime &runtime, const jsi::Value &thisValue,
                                                                    const jsi::Value *arguments, size_t count) -> jsi::Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[1].getString(runtime)));
        
        if (!kv) {
            return Value::undefined();
        }
        
        NSString *key = convertJSIStringToNSString(
                                                   runtime, arguments[0].getString(runtime));
        
        removeKeyFromIndexer(kv, key);
        [kv removeValueForKey:key];
        
        return Value(true);
    });
    jsiRuntime.global().setProperty(jsiRuntime, "removeValueMMKV",
                                    std::move(removeValueMMKV));
    
    auto getAllKeysMMKV = jsi::Function::createFromHostFunction(
                                                                jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "getAllKeysMMKV"), 1,
                                                                [](jsi::Runtime &runtime, const jsi::Value &thisValue,
                                                                   const jsi::Value *arguments, size_t count) -> jsi::Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[0].getString(runtime)));
        
        if (!kv) {
            return Value::undefined();
        }
        
        NSArray *keys = [kv allKeys];
        
        return Value(convertNSArrayToJSIArray(runtime, keys));
    });
    jsiRuntime.global().setProperty(jsiRuntime, "getAllKeysMMKV",
                                    std::move(getAllKeysMMKV));
    
    auto getIndexMMKV = jsi::Function::createFromHostFunction(
                                                              jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "getIndexMMKV"), 2,
                                                              [](jsi::Runtime &runtime, const jsi::Value &thisValue,
                                                                 const jsi::Value *arguments, size_t count) -> jsi::Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[1].getString(runtime)));
        if (!kv) {
            return Value::undefined();
        }
        
        NSMutableArray *keys =
        getIndex(kv, convertJSIStringToNSString(
                                                runtime, arguments[0].getString(runtime)));
        return Value(convertNSArrayToJSIArray(runtime, keys));
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "getIndexMMKV",
                                    std::move(getIndexMMKV));
    
    auto containsKeyMMKV = jsi::Function::createFromHostFunction(
                                                                 jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "containsKeyMMKV"), 2,
                                                                 [](jsi::Runtime &runtime, const jsi::Value &thisValue,
                                                                    const jsi::Value *arguments,
                                                                    
                                                                    size_t count) -> jsi::Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[1].getString(runtime)));
        if (!kv) {
            return Value::undefined();
        }
        return Value(
                     [kv containsKey:convertJSIStringToNSString(
                                                                runtime, arguments[0].getString(runtime))]);
    });
    jsiRuntime.global().setProperty(jsiRuntime, "containsKeyMMKV",
                                    std::move(containsKeyMMKV));
    
    auto clearMMKV = jsi::Function::createFromHostFunction(
                                                           jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "clearMMKV"), 1,
                                                           [](jsi::Runtime &runtime, const jsi::Value &thisValue,
                                                              const jsi::Value *arguments,
                                                              
                                                              size_t count) -> jsi::Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[0].getString(runtime)));
        if (!kv) {
            return Value::undefined();
        }
        
        [kv clearAll];
        
        return Value(true);
    });
    jsiRuntime.global().setProperty(jsiRuntime, "clearMMKV",
                                    std::move(clearMMKV));

        auto clearMemoryCache = jsi::Function::createFromHostFunction(
                                                           jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "clearMemoryCache"), 1,
                                                           [](jsi::Runtime &runtime, const jsi::Value &thisValue,
                                                              const jsi::Value *arguments,
                                                              
                                                              size_t count) -> jsi::Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[0].getString(runtime)));
        if (!kv) {
            return Value::undefined();
        }
        
        [kv clearMemoryCache];
        
        return Value(true);
    });
    jsiRuntime.global().setProperty(jsiRuntime, "clearMemoryCache",
                                    std::move(clearMemoryCache));                                  
    
    auto encryptMMKV = jsi::Function::createFromHostFunction(
                                                             jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "encryptMMKV"), 2,
                                                             [](jsi::Runtime &runtime, const jsi::Value &thisValue,
                                                                const jsi::Value *arguments,
                                                                
                                                                size_t count) -> jsi::Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[1].getString(runtime)));
        if (!kv) {
            return Value::undefined();
        }
        
        NSString *key = convertJSIStringToNSString(
                                                   runtime, arguments[0].getString(runtime));
        
        NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding];
        [kv reKey:cryptKey];
        
        return Value(true);
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "encryptMMKV",
                                    std::move(encryptMMKV));
    
    auto decryptMMKV = jsi::Function::createFromHostFunction(
                                                             jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "decryptMMKV"), 2,
                                                             [](jsi::Runtime &runtime, const jsi::Value &thisValue,
                                                                const jsi::Value *arguments,
                                                                
                                                                size_t count) -> jsi::Value {
        MMKV *kv = getInstance(convertJSIStringToNSString(
                                                          runtime, arguments[1].getString(runtime)));
        if (!kv) {
            return Value::undefined();
        }
        
        [kv reKey:NULL];
        
        return Value(true);
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "decryptMMKV",
                                    std::move(decryptMMKV));
    
    // Secure Store
    
    auto setSecureKey = Function::createFromHostFunction(
                                                         jsiRuntime, PropNameID::forAscii(jsiRuntime, "setSecureKey"), 3,
                                                         [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                            size_t count) -> Value {
        NSString *alias = convertJSIStringToNSString(
                                                     runtime, arguments[0].getString(runtime));
        NSString *key = convertJSIStringToNSString(
                                                   runtime, arguments[1].getString(runtime));
        NSString *accValue = convertJSIStringToNSString(
                                                        runtime, arguments[2].getString(runtime));
                                                                [_secureStorage setServiceName: getServiceName(alias)];
        [_secureStorage setSecureKey:alias
                               value:key
                             options:@{@"accessible" : accValue}];
        
        return Value(true);
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "setSecureKey",
                                    move(setSecureKey));
    
    auto getSecureKey = Function::createFromHostFunction(
                                                         jsiRuntime, PropNameID::forAscii(jsiRuntime, "getSecureKey"), 3,
                                                         [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                            size_t count) -> Value {
        NSString *alias = convertJSIStringToNSString(
                                                     runtime, arguments[0].getString(runtime));
        
                                                                [_secureStorage setServiceName: getServiceName(alias)];
        return Value(convertNSStringToJSIString(
                                                runtime, [_secureStorage getSecureKey:alias]));
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "getSecureKey",
                                    move(getSecureKey));
    
    auto secureKeyExists = Function::createFromHostFunction(
                                                            jsiRuntime, PropNameID::forAscii(jsiRuntime, "secureKeyExists"), 3,
                                                            [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                               size_t count) -> Value {
        NSString *alias = convertJSIStringToNSString(
                                                     runtime, arguments[0].getString(runtime));
        
                                                                   [_secureStorage setServiceName: getServiceName(alias)];
        return Value([_secureStorage secureKeyExists:alias]);
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "secureKeyExists",
                                    move(secureKeyExists));
    
    auto removeSecureKey = Function::createFromHostFunction(
                                                            jsiRuntime, PropNameID::forAscii(jsiRuntime, "removeSecureKey"), 3,
                                                            [](Runtime &runtime, const Value &thisValue, const Value *arguments,
                                                               size_t count) -> Value {
        NSString *alias = convertJSIStringToNSString(
                                                     runtime, arguments[0].getString(runtime));
                                                                   [_secureStorage setServiceName: getServiceName(alias)];
        [_secureStorage removeSecureKey:alias];
        return Value(true);
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "removeSecureKey",
                                    move(removeSecureKey));
    
    // Secure Store End
}

- (void)migrate {
    MMKV *kv = [MMKV mmkvWithID:@"mmkvIdStore"];
    [mmkvInstances setObject:kv forKey:@"mmkvIdStore"];
    if ([kv containsKey:@"mmkvIdData"]) {
        NSMutableDictionary *oldStore =
        [kv getObjectOfClass:NSMutableDictionary.class forKey:@"mmkvIdData"];
        NSArray *keys = [oldStore allKeys];
        
        for (int i = 0; i < keys.count; i++) {
            NSString *storageKey = keys[i];
            NSMutableDictionary *entry = [oldStore objectForKey:storageKey];
            NSError *error;
            NSData *jsonData = [NSJSONSerialization dataWithJSONObject:entry
                                                               options:0
                                                                 error:&error];
            NSString *jsonString =
            [[NSString alloc] initWithData:jsonData
                                  encoding:NSUTF8StringEncoding];
            [kv setString:jsonString forKey:storageKey];
            
            if ([[entry valueForKey:@"encrypted"] boolValue]) {
                NSString *alias = [entry valueForKey:@"alias"];
                [_secureStorage setServiceName: getServiceName(alias)];
                if ([_secureStorage searchKeychainCopyMatchingExists:alias]) {
                    NSString *key = [_secureStorage searchKeychainCopyMatching:alias];
                    if (key != nil) {
                        NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding];
                        MMKV *kvv = [MMKV mmkvWithID:storageKey
                                            cryptKey:cryptKey
                                                mode:MMKVSingleProcess];
                        [self writeToJson:kvv];
                    }
                };
            } else {
                MMKV *kvv = [MMKV mmkvWithID:storageKey mode:MMKVSingleProcess];
                [self writeToJson:kvv];
            }
        }
        
        [kv removeValueForKey:@"mmkvIdData"];
    }
}

- (void)writeToJson:(MMKV *)kv {
    NSArray *mapIndex = getIndex(kv, @"mapIndex");
    if (mapIndex != nil) {
        for (NSString *key in mapIndex) {
            NSDictionary *data = [kv getObjectOfClass:NSDictionary.class forKey:key];
            if (data != nil) {
                NSError *error;
                NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data
                                                                   options:0
                                                                     error:&error];
                NSString *jsonString =
                [[NSString alloc] initWithData:jsonData
                                      encoding:NSUTF8StringEncoding];
                [kv setString:jsonString forKey:key];
            }
        }
    }
    
    NSArray *arrayIndex = getIndex(kv, @"arrayIndex");
    if (arrayIndex != nil) {
        for (NSString *key in arrayIndex) {
            NSDictionary *data =
            [kv getObjectOfClass:NSDictionary.class forKey:key];
            
            if (data != nil) {
                NSMutableArray *array = [data objectForKey:key];
                if (array != nil) {
                    NSError *error;
                    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:array
                                                                       options:0
                                                                         error:&error];
                    NSString *jsonString =
                    [[NSString alloc] initWithData:jsonData
                                          encoding:NSUTF8StringEncoding];
                    [kv setString:jsonString forKey:key];
                }
            }
        }
    }
    
    NSArray *intIndex =
    [kv getObjectOfClass:NSMutableArray.class forKey:@"intIndex"];
    if (intIndex != nil) {
        for (NSString *key in intIndex) {
            int64_t intVal = [kv getInt64ForKey:key];
            [kv setDouble:double(intVal) forKey:key];
        }
        [kv setObject:intIndex forKey:@"numberIndex"];
        [kv removeValueForKey:@"intIndex"];
    }
}

@end
