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

#import "RNDynamsoftCaptureVisionRouter.h"
#import <DynamsoftCaptureVisionBundle/DynamsoftCaptureVisionBundle.h>
#import <React/RCTUIManager.h>
#import "RNDynamsoftImageSourceAdapter.h"
#import "RNDynamsoft+Json.h"
#import "RNDynamsoft+JSI.h"
#import <jsi/jsi.h>
#import <React/RCTBridge+Private.h>
#import "DSImageData+HostObject.h"
#import "YeetJSIUtils.h"

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

@interface RNDynamsoftCaptureVisionRouter () <DSCapturedResultReceiver>

@property (nonatomic, strong) DSCaptureVisionRouter *cvr;

@property (nonatomic, strong) NSMutableArray<DSImageData *> *deskewedImageDataArray;

@property (nonatomic, strong) NSMutableArray<DSImageData *> *enhancedImageDataArray;

@property (nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *receiveDictionary;

@end

@implementation RNDynamsoftCaptureVisionRouter {
    BOOL hasListeners;
    dispatch_semaphore_t semaphore;
}

RCT_EXPORT_MODULE(DynamsoftCaptureVisionRouterModule)

- (instancetype)init
{
    self = [super init];
    if (self) {
        _cvr = [[DSCaptureVisionRouter alloc] init];
        semaphore = dispatch_semaphore_create(0);
    }
    return self;
}

- (void)dealloc
{
    _cvr = nil;
}

RCT_EXPORT_METHOD(setGlobalIntraOpNumThreads:(NSInteger)intraOpNumThreads) {
    [DSCaptureVisionRouter setGlobalIntraOpNumThreads:intraOpNumThreads];
}

RCT_EXPORT_METHOD(clearDLModelBuffers) {
    [DSCaptureVisionRouter clearDLModelBuffers];
}

RCT_EXPORT_METHOD(switchCapturingTemplate:(NSString *)templateName
                  resolve:(RCTPromiseResolveBlock)resolve
                  rejecter: (RCTPromiseRejectBlock)reject)
{
    NSError *error = nil;
    [self.cvr switchCapturingTemplate:templateName error:&error];
    if (error) {
        NSString *code = [NSString stringWithFormat:@"%ld",(long)error.code];
        reject(code, error.localizedDescription, error);
    } else {
        resolve(@(true));
    }
}

RCT_EXPORT_METHOD(startCapturing:(NSString *)templateName
                  resolve:(RCTPromiseResolveBlock)resolve
                  rejecter: (RCTPromiseRejectBlock)reject)
{
    [self.cvr startCapturing:templateName completionHandler:^(BOOL isSuccess, NSError * _Nullable error) {
        if (!isSuccess && error) {
            NSString *code = [NSString stringWithFormat:@"%ld",(long)error.code];
            reject(code, error.localizedDescription, error);
        } else {
            self->semaphore = dispatch_semaphore_create(0);
            resolve(@(true));
        }
    }];
}

RCT_EXPORT_METHOD(stopCapturing:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    [self continueCRR];
    [self.cvr stopCapturing];
    resolve(@(true));
}

RCT_EXPORT_METHOD(setInput:(NSString *)key) {
    RNDynamsoftImageSourceAdapter *rnAdapter = [self.bridge moduleForClass:[RNDynamsoftImageSourceAdapter class]];
    DSImageSourceAdapter *isa = [rnAdapter.isaDictionary valueForKey:key];
    NSError *error;
    BOOL isSuccess = [self.cvr setInput:isa error:&error];
    if (!isSuccess && error) {
        __unused NSString *code = [NSString stringWithFormat:@"%ld",(long)error.code];
    } else {
        
    }
}

RCT_EXPORT_METHOD(getSimplifiedSettings:(NSString *)name
                  resolve:(RCTPromiseResolveBlock)resolve
                  rejecter: (RCTPromiseRejectBlock)reject)
{
    NSError *error;
    DSSimplifiedCaptureVisionSettings *settings = [self.cvr getSimplifiedSettings:name error:&error];
    if (error) {
        NSString *code = [NSString stringWithFormat:@"%ld",(long)error.code];
        reject(code, error.localizedDescription, error);
    } else {
        resolve([settings toJson]);
    }
}

RCT_EXPORT_METHOD(updateSettings:(NSDictionary *)dictionary
                  templateName:(NSString *)templateName
                  resolve:(RCTPromiseResolveBlock)resolve
                  rejecter: (RCTPromiseRejectBlock)reject)
{
    NSError *error;
    DSSimplifiedCaptureVisionSettings *settings = [self.cvr getSimplifiedSettings:templateName error:nil];
    [settings updateWithDicitionary:dictionary];
    BOOL isSuccess = [self.cvr updateSettings:templateName settings:settings error:&error];
    if (!isSuccess && error) {
        NSString *code = [NSString stringWithFormat:@"%ld",(long)error.code];
        reject(code, error.localizedDescription, error);
    } else {
        resolve(@(true));
    }
}

RCT_EXPORT_METHOD(resetSettings:(RCTPromiseResolveBlock)resolve
                  rejecter: (RCTPromiseRejectBlock)reject)
{
    NSError *error;
    BOOL isSuccess = [self.cvr resetSettings:&error];
    if (!isSuccess && error) {
        NSString *code = [NSString stringWithFormat:@"%ld",(long)error.code];
        reject(code, error.localizedDescription, error);
    } else {
        resolve(@(true));
    }
}

RCT_EXPORT_METHOD(initSettings:(NSString *)content
                  resolve:(RCTPromiseResolveBlock)resolve
                  rejecter: (RCTPromiseRejectBlock)reject) {
    NSError *error;
    BOOL isSuccess = [self.cvr initSettings:content error:&error];
    if (!isSuccess && error) {
        NSString *code = [NSString stringWithFormat:@"%ld",(long)error.code];
        reject(code, error.localizedDescription, error);
    } else {
        resolve(@(true));
    }
}

RCT_EXPORT_METHOD(initSettingsFromFile:(NSString *)path
                  resolve:(RCTPromiseResolveBlock)resolve
                  rejecter: (RCTPromiseRejectBlock)reject) {
    NSError *error;
    BOOL isSuccess = [self.cvr initSettingsFromFile:path error:&error];
    if (!isSuccess && error) {
        NSString *code = [NSString stringWithFormat:@"%ld",(long)error.code];
        reject(code, error.localizedDescription, error);
    } else {
        resolve(@(true));
    }
}

RCT_EXPORT_METHOD(outputSettings:(NSString *)name
                  includeDefaultValues:(BOOL)includeDefaultValues
                  resolve:(RCTPromiseResolveBlock)resolve
                  rejecter: (RCTPromiseRejectBlock)reject)
{
    NSError *error;
    NSString *setting = [self.cvr outputSettings:name includeDefaultValues:includeDefaultValues error:&error];
    if (error) {
        NSString *code = [NSString stringWithFormat:@"%ld",(long)error.code];
        reject(code, error.localizedDescription, error);
    } else {
        resolve(setting);
    }
}

RCT_EXPORT_METHOD(outputSettingsToFile:(NSString *)name
                  file:(NSString *)path
                  includeDefaultValues:(BOOL)includeDefaultValues
                  resolve:(RCTPromiseResolveBlock)resolve
                  rejecter: (RCTPromiseRejectBlock)reject)
{
    NSError *error;
    BOOL isSuccess = [self.cvr outputSettingsToFile:name filePath:path includeDefaultValues:includeDefaultValues error:&error];
    if (!isSuccess && error) {
        NSString *code = [NSString stringWithFormat:@"%ld",(long)error.code];
        reject(code, error.localizedDescription, error);
    } else {
        resolve(@(true));
    }
}

#pragma mark - Listener
RCT_EXPORT_METHOD(continueCRR) {
    dispatch_semaphore_signal(semaphore);
}

RCT_EXPORT_METHOD(addResultReceiver:(NSString *)event) {
    [self.receiveDictionary setValue:@(true) forKey:event];
}

RCT_EXPORT_METHOD(removeResultReceiver:(NSString *)event) {
    [self.receiveDictionary removeObjectForKey:event];
}

RCT_EXPORT_METHOD(removeAllResultListeners) {
    [self.cvr removeResultReceiver:self];
}

RCT_EXPORT_METHOD(addFilter:(NSString *)key) {
    [self.cvr addResultFilter:[self.filterDictionary valueForKey:key]];
}

RCT_EXPORT_METHOD(removeFilter:(NSString *)key) {
    [self.cvr removeResultFilter:[self.filterDictionary valueForKey:key]];
}

- (NSArray<NSString *> *)supportedEvents
{
  return @[@"onCapturedResultReceived",
           @"onDecodedBarcodesReceived",
           @"onProcessedDocumentResultReceived",
           @"onRecognizedTextLinesReceived",
           @"onParsedResultsReceived"];
}

-(void)startObserving {
    hasListeners = YES;
    [self.cvr addResultReceiver:self];
}

-(void)stopObserving {
    hasListeners = NO;
    [self.cvr removeResultReceiver:self];
}

- (void)onCapturedResultReceived:(DSCapturedResult *)result {
    if (hasListeners && [self.receiveDictionary valueForKey:@"onCapturedResultReceived"].boolValue) {
        DSProcessedDocumentResult *imageResult = result.processedDocumentResult;
        [self.deskewedImageDataArray removeAllObjects];
        for (DSDeskewedImageResultItem *item in imageResult.deskewedImageResultItems) {
            DSImageData *data = item.imageData;
            if (data) {
                [self.deskewedImageDataArray addObject:data];
            }
        }
        [self.enhancedImageDataArray removeAllObjects];
        for (DSEnhancedImageResultItem *item in imageResult.enhancedImageResultItems) {
            DSImageData *data = item.imageData;
            if (data) {
                [self.enhancedImageDataArray addObject:data];
            }
        }
        [self sendEventWithName:@"onCapturedResultReceived" body:[result toJson]];
        dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
        __unused long result = dispatch_semaphore_wait(semaphore, timeout);
    }
}

- (void)onDecodedBarcodesReceived:(DSDecodedBarcodesResult *)result {
    if (hasListeners && [self.receiveDictionary valueForKey:@"onDecodedBarcodesReceived"].boolValue) {
        [self sendEventWithName:@"onDecodedBarcodesReceived" body:[result toJson]];
        dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
        __unused long result = dispatch_semaphore_wait(semaphore, timeout);
    }
}

- (void)onProcessedDocumentResultReceived:(DSProcessedDocumentResult *)result {
    if (hasListeners && [self.receiveDictionary valueForKey:@"onProcessedDocumentResultReceived"].boolValue) {
        [self.deskewedImageDataArray removeAllObjects];
        for (DSDeskewedImageResultItem *item in result.deskewedImageResultItems) {
            DSImageData *data = item.imageData;
            if (data) {
                [self.deskewedImageDataArray addObject:data];
            }
        }
        [self.enhancedImageDataArray removeAllObjects];
        for (DSEnhancedImageResultItem *item in result.enhancedImageResultItems) {
            DSImageData *data = item.imageData;
            if (data) {
                [self.enhancedImageDataArray addObject:data];
            }
        }
        [self sendEventWithName:@"onProcessedDocumentResultReceived" body:[result toJson]];
        dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
        __unused long result = dispatch_semaphore_wait(semaphore, timeout);
    }
}

- (void)onRecognizedTextLinesReceived:(DSRecognizedTextLinesResult *)result {
    if (hasListeners && [self.receiveDictionary valueForKey:@"onRecognizedTextLinesReceived"].boolValue) {
        [self sendEventWithName:@"onRecognizedTextLinesReceived" body:[result toJson]];
        dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
        __unused long result = dispatch_semaphore_wait(semaphore, timeout);
    }
}

- (void)onParsedResultsReceived:(DSParsedResult *)result {
    if (hasListeners && [self.receiveDictionary valueForKey:@"onParsedResultsReceived"].boolValue) {
        [self sendEventWithName:@"onParsedResultsReceived" body:[result toJson]];
        dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
        __unused long result = dispatch_semaphore_wait(semaphore, timeout);
    }
}

#pragma mark - Private
- (NSMutableArray<DSImageData *> *)deskewedImageDataArray {
    if (!_deskewedImageDataArray) {
        _deskewedImageDataArray = [NSMutableArray array];
    }
    return _deskewedImageDataArray;
}

- (NSMutableArray<DSImageData *> *)enhancedImageDataArray {
    if (!_enhancedImageDataArray) {
        _enhancedImageDataArray = [NSMutableArray array];
    }
    return _enhancedImageDataArray;
}

- (NSMutableDictionary<NSString *,DSMultiFrameResultCrossFilter *> *)filterDictionary {
    if (!_filterDictionary) {
        _filterDictionary = [NSMutableDictionary dictionary];
    }
    return _filterDictionary;
}

- (NSMutableDictionary<NSString *,NSNumber *> *)receiveDictionary {
    if (!_receiveDictionary) {
        _receiveDictionary = [NSMutableDictionary dictionary];
    }
    return _receiveDictionary;
}

- (DSImageData *)getOriginalImage:(NSString *)imageHashId {
    return [[self.cvr getIntermediateResultManager] getOriginalImage:imageHashId];
}

#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 Macros ---
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, RNDynamsoftCaptureVisionRouter *rnCapture) {

    // 1. captureImageData(imageDataHostObject, templateName)
    installHostFunction(jsiRuntime, "captureImageData", 2,
        [rnCapture](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value {
            // Argument check
            if (count != 2 || !arguments[1].isString() || !arguments[0].isObject()) {
                return Value(false); // Or throw a JSI exception
            }

            NSString *name = convertJSIStringToNSString(runtime, arguments[1].getString(runtime));
            Object obj = arguments[0].getObject(runtime);

            if (obj.isHostObject<ImageDataHostObject>(runtime)) {
                // Assuming ImageDataHostObject::getHOFromObject returns a non-owning copy or reference
                ImageDataHostObject hostObject = ImageDataHostObject::getHOFromObject(runtime, obj);
                
                // Get the Objective-C object from the HostObject
                DSImageData *data = [DSImageData imageDataFromHostObject:hostObject];
                
                DSCapturedResult *result = [rnCapture.cvr captureFromBuffer:data templateName:name];
                return [result convertToJSIObject:runtime];
            }
            return Value(false);
        }
    );

    // 2. captureFile(filePath, templateName)
    installHostFunction(jsiRuntime, "captureFile", 2,
        [rnCapture](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value {
            // Argument check
            if (count != 2 || !arguments[0].isString() || !arguments[1].isString()) {
                return Value(false);
            }
            
            NSString *file = convertJSIStringToNSString(runtime, arguments[0].getString(runtime));
            NSString *name = convertJSIStringToNSString(runtime, arguments[1].getString(runtime));
            
            DSCapturedResult *result = [rnCapture.cvr captureFromFile:file templateName:name];
            return [result convertToJSIObject:runtime];
        }
    );

    // 3. captureFileBytes(arrayBuffer, templateName)
    installHostFunction(jsiRuntime, "captureFileBytes", 2,
        [rnCapture](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value {
            // Argument check
            if (count != 2 || !arguments[0].isObject() || !arguments[1].isString()) {
                return Value(false);
            }
            
            Object obj = arguments[0].asObject(runtime);
            if (!obj.isArrayBuffer(runtime)) {
                return Value(false);
            }
            
            ArrayBuffer buffer = obj.getArrayBuffer(runtime);
            NSData *data = convertArrayBufferToNSData(runtime, buffer);
            NSString *name = convertJSIStringToNSString(runtime, arguments[1].getString(runtime));
            
            DSCapturedResult *result = [rnCapture.cvr captureFromFileBytes:data templateName:name];
            return [result convertToJSIObject:runtime];
        }
    );
    
    // --- Image Data Array Accessors ---

    // Generic lambda for getting a list of images (deskewed or enhanced)
    auto getImageArrayFunc = [rnCapture](Runtime &runtime, NSArray<DSImageData *> *imageDataArray) -> Value {
        Array array(runtime, imageDataArray.count);
        for (int i = 0; i < imageDataArray.count; i++) {
            // Create a new JSI Host Object wrapping the existing Objective-C object's data
            // This assumes ImageDataHostObject has a suitable constructor/initialization.
            ImageDataHostObject hostObject = [[imageDataArray objectAtIndex:i] hostObject];
            Object obj = Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(hostObject));
            array.setValueAtIndex(runtime, i, obj);
        }
        return array;
    };

    // 4. getCurrentDeskewedImages()
    installHostFunction(jsiRuntime, "getCurrentDeskewedImages", 0,
        [rnCapture, getImageArrayFunc](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value {
            if (count != 0) {
                return Value(Array(runtime, 0));
            }
            return getImageArrayFunc(runtime, rnCapture.deskewedImageDataArray);
        }
    );

    // 5. getCurrentEnhancedImages()
    installHostFunction(jsiRuntime, "getCurrentEnhancedImages", 0,
        [rnCapture, getImageArrayFunc](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value {
            if (count != 0) {
                return Value(Array(runtime, 0));
            }
            return getImageArrayFunc(runtime, rnCapture.enhancedImageDataArray);
        }
    );

    // 6. cvr_getOriginalImage(imageHashId)
    installHostFunction(jsiRuntime, "cvr_getOriginalImage", 1,
        [rnCapture](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value {
            // Argument check
            if (count != 1 || !arguments[0].isString()) {
                return Value::null(); // Or Value(false) depending on expected JS behavior
            }
            
            NSString *imageHashId = convertJSIStringToNSString(runtime, arguments[0].getString(runtime));
            
            DSImageData *imageData = [rnCapture getOriginalImage:imageHashId];
            if (imageData == nil) {
                return Value::null();
            }
            
            ImageDataHostObject hostObject = [imageData hostObject];
            // Create a new JSI Host Object wrapper
            Object obj = Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(hostObject));
            return obj;
        }
    );
}

@end

