/*
 * Copyright © Dynamsoft Corporation. All rights reserved.
 */

#import "RNDynamsoftImageSourceAdapter.h"
#import <React/RCTBridge+Private.h>
#import <React/RCTUtils.h>
#import <jsi/jsi.h>
#import <sys/utsname.h>
#import "YeetJSIUtils.h"
#import <DynamsoftCaptureVisionBundle/DynamsoftCaptureVisionBundle.h>
#import "DSImageData+HostObject.h"

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

@interface RNDynamsoftImageSourceAdapter ()

@end

@implementation RNDynamsoftImageSourceAdapter

RCT_EXPORT_MODULE(DynamsoftImageSourceAdapterModule)

- (instancetype)init
{
    self = [super init];
    if (self) {
        
    }
    return self;
}
 
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(createDefaultNativeISA) {
    DSImageSourceAdapter *adapter = [[DSImageSourceAdapter alloc] init];
    NSString *key = [NSNumber numberWithUnsignedInteger:adapter.hash].stringValue;
    [self.isaDictionary setValue:adapter forKey:key];
    return key;
}

RCT_EXPORT_METHOD(destroy:(NSString *)key) {
    [self.isaDictionary removeObjectForKey:key];
}

RCT_EXPORT_METHOD(clearBuffer:(NSString *)key) {
    [[self.isaDictionary valueForKey:key] clearBuffer];
}

RCT_EXPORT_METHOD(getImageCount:(NSString *)key
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    resolve(@([self.isaDictionary valueForKey:key].imageCount));
}

RCT_EXPORT_METHOD(isBufferEmpty:(NSString *)key
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    resolve(@([self.isaDictionary valueForKey:key].isBufferEmpty));
}

RCT_EXPORT_METHOD(getColourChannelUsageType:(NSString *)key
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    resolve(@([self.isaDictionary valueForKey:key].colourChannelUsageType));
}

RCT_EXPORT_METHOD(setColourChannelUsageType:(NSString *)key
                  type:(NSInteger)type
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    [self.isaDictionary valueForKey:key].colourChannelUsageType = (DSColourChannelUsageType)type;
    resolve(@(true));
}

RCT_EXPORT_METHOD(getMaximumImageCount:(NSString *)key
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    resolve(@([self.isaDictionary valueForKey:key].maxImageCount));
}

RCT_EXPORT_METHOD(setMaximumImageCount:(NSString *)key
                  count:(NSInteger)count
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    [self.isaDictionary valueForKey:key].maxImageCount = count;
    resolve(@(true));
}

RCT_EXPORT_METHOD(setNextImageToReturn:(NSString *)key
                  imageId:(NSInteger)imageId
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    [[self.isaDictionary valueForKey:key] setNextImageToReturn:imageId keepInBuffer:true];
    resolve(@(true));
}

RCT_EXPORT_METHOD(startFetching:(NSString *)key) {
    [[self.isaDictionary valueForKey:key] startFetching];
}

RCT_EXPORT_METHOD(stopFetching:(NSString *)key) {
    [[self.isaDictionary valueForKey:key] stopFetching];
}

RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(hasImage:(NSString *)key imageId:(NSInteger)imageId)  {
    return @([[self.isaDictionary valueForKey:key] hasImage:imageId]);
}

#pragma mark - Private
- (NSMutableDictionary<NSString *,DSImageSourceAdapter *> *)isaDictionary {
    if (!_isaDictionary) {
        _isaDictionary = [[NSMutableDictionary alloc] init];
    }
    return _isaDictionary;
}

- (void)addImage:(DSImageData *)data toISA:(NSString *)key {
    [[self.isaDictionary valueForKey:key] addImageToBuffer:data];
}

- (DSImageData *)imageDataFromISA:(NSString *)key {
    return [[self.isaDictionary valueForKey:key] getImage];
}

#pragma mark - Global JSI
// --- RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) ---
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install)
{
    RCTBridge* bridge = [RCTBridge currentBridge];
    RCTCxxBridge* cxxBridge = (RCTCxxBridge*)bridge;
    if (cxxBridge == nil) {
        return @false;
    }
    auto jsiRuntime = (jsi::Runtime*) cxxBridge.runtime;
    if (jsiRuntime == nil) {
        return @false;
    }
    
    install(*(facebook::jsi::Runtime *)jsiRuntime, self);
    return @true;
}

// --- Helper Functions and Lambdas ---
static void installHostFunction(
    jsi::Runtime &jsiRuntime,
    const char* name,
    size_t argCount,
    HostFunctionType hostFunction) {
    
    auto func = Function::createFromHostFunction(
        jsiRuntime,
        PropNameID::forAscii(jsiRuntime, name),
        argCount,
        std::move(hostFunction)
    );
    jsiRuntime.global().setProperty(jsiRuntime, name, std::move(func));
}

// --- JSI Installation Logic ---
static void install(jsi::Runtime &jsiRuntime, RNDynamsoftImageSourceAdapter *rnAdapter) {
    using namespace jsi;

    // Helper Lambda: Extract ImageDataHostObject from JSI Value
    auto getHostImageData = [&](Runtime &runtime, const Value &value) -> std::optional<ImageDataHostObject> {
        // Optimization: isObject() check does not require runtime
        if (!value.isObject()) return std::nullopt;
        auto obj = value.getObject(runtime);
        // isHostObject<> requires runtime
        if (!obj.isHostObject<ImageDataHostObject>(runtime)) return std::nullopt;
        return ImageDataHostObject::getHOFromObject(runtime, obj);
    };

    // Helper Lambda: Create JSI Host Object from DSImageData and return
    auto createJsiHostObject = [&](Runtime &runtime, DSImageData *imageData) -> Value {
        if (!imageData) return Value::null();
        auto host = [imageData hostObject];
        return Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(host));
    };
    
    // ---------------------- isaGetImageData ----------------------
    // isaGetImageData(key)
    installHostFunction(jsiRuntime, "isaGetImageData", 1,
        [rnAdapter, createJsiHostObject](Runtime &runtime, const Value &, const Value *arguments, size_t count) -> Value {
            // 🔍 Argument Validation: Check count and key type (isString() no runtime)
            if (count != 1 || !arguments[0].isString()) return Value::null();

            NSString *key = convertJSIStringToNSString(runtime, arguments[0].getString(runtime));
            DSImageData *imageData = [rnAdapter imageDataFromISA:key];
            
            return createJsiHostObject(runtime, imageData);
        }
    );
    
    // ---------------------- isaAddImageToBuffer ----------------------
    // isaAddImageToBuffer(key, imageData)
    installHostFunction(jsiRuntime, "isaAddImageToBuffer", 2,
        [rnAdapter, getHostImageData](Runtime &runtime, const Value &, const Value *arguments, size_t count) -> Value {
            // 🔍 Argument Validation: Check count and key type (isString() no runtime)
            if (count != 2 || !arguments[0].isString() || !arguments[1].isObject()) return Value(false);

            auto hostOpt = getHostImageData(runtime, arguments[1]);
            if (!hostOpt) return Value(false);
            
            NSString *key = convertJSIStringToNSString(runtime, arguments[0].getString(runtime));
            DSImageData *data = [DSImageData imageDataFromHostObject:*hostOpt];
            
            [rnAdapter addImage:data toISA:key];
            return Value(true);
        }
    );
    
    // ---------------------- imageDataToBase64 ----------------------
    // imageDataToBase64(imageData)
    installHostFunction(jsiRuntime, "imageDataToBase64", 1,
        [getHostImageData](Runtime &runtime, const Value &, const Value *arguments, size_t count) -> Value {
            // 🔍 Argument Validation: Check count and image object type (isObject() no runtime)
            if (count != 1 || !arguments[0].isObject()) return Value(false);
            
            auto hostOpt = getHostImageData(runtime, arguments[0]);
            if (!hostOpt) return Value(false);

            DSImageData *imageData = [DSImageData imageDataFromHostObject:*hostOpt];
            
            NSError *error;
            // Native code to convert DSImageData to Base64 (using JPEG representation)
            UIImage *image = [imageData toUIImage:&error];
            if (!error && image) {
                // Compression quality set to 1.0 (highest quality)
                NSData *data = UIImageJPEGRepresentation(image, 1.0);
                NSString *base64String = [data base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
                
                // convertNSStringToJSIString is assumed to be defined elsewhere
                return convertNSStringToJSIString(runtime, base64String);
            }
            
            return Value(false);
        }
    );
}

@end
