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

#import <React/RCTUIManager.h>
#import <React/RCTEventEmitter.h>
#import <React/RCTViewManager.h>
#import "RNDynamsoft+Json.h"
#import "RNDynamsoft+JSI.h"
#import <DynamsoftCaptureVisionSpec/DynamsoftCaptureVisionSpec.h>
#import <jsi/jsi.h>
#import <React/RCTBridge+Private.h>
#import "DSImageData+HostObject.h"
#import "YeetJSIUtils.h"
#if RCT_NEW_ARCH_ENABLED
#import <ReactCommon/RCTTurboModule.h>
#import <ReactCommon/RCTTurboModuleWithJSIBindings.h>
#endif

// Auto-generated Objective-C interface for this pod's Swift sources. The header name is
// derived from the pod's module name (hyphens become underscores) — verify against the
// build log if this ever fails to resolve.
#if __has_include("dynamsoft_capture_vision_react_native-Swift.h")
#import "dynamsoft_capture_vision_react_native-Swift.h"
#else
#import <dynamsoft_capture_vision_react_native/dynamsoft_capture_vision_react_native-Swift.h>
#endif

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

static void install(jsi::Runtime &jsiRuntime, RNDynamsoftCaptureVisionRouter *rnCapture);

@interface RNDynamsoftCaptureVisionRouter (JSIBindings)
#if RCT_NEW_ARCH_ENABLED
<RCTTurboModule, RCTTurboModuleWithJSIBindings>
#endif
@end

@implementation RNDynamsoftCaptureVisionRouter (JSIBindings)

#pragma mark - Global JSI
#if RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params
{
    return std::make_shared<facebook::react::NativeDynamsoftCaptureVisionRouterModuleSpecJSI>(params);
}

- (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime
                          callInvoker:(const std::shared_ptr<facebook::react::CallInvoker> &)callInvoker
{
    install(runtime, self);
}

// Kept as a no-op so existing JS call sites (CvrModule.install()) remain harmless.
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install)
{
    return @true;
}
#else
// Legacy Architecture only.
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install)
{
    RCTBridge *bridge = self.bridge;

    if ([bridge respondsToSelector:NSSelectorFromString(@"parentBridge")]) {
        bridge = [bridge valueForKey:@"parentBridge"];
    }

    RCTCxxBridge *cxxBridge = (RCTCxxBridge *)bridge;
    if (cxxBridge == nil || cxxBridge.runtime == nil) {
        return @false;
    }

    auto jsiRuntime = (jsi::Runtime*) cxxBridge.runtime;
    if (jsiRuntime == nil) {
        return @false;
    }

    install(*(facebook::jsi::Runtime *)cxxBridge.runtime, self);
    return @true;
}
#endif

@end

// --- Helper Functions and Macros ---
static void installHostFunction(
    jsi::Runtime &jsiRuntime,
    const char* name,
    unsigned int 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 {
            if (count != 2 || !arguments[1].isString() || !arguments[0].isObject()) {
                return Value(false);
            }

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

            if (obj.isHostObject<ImageDataHostObject>(runtime)) {
                ImageDataHostObject hostObject = ImageDataHostObject::getHOFromObject(runtime, obj);
                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 {
            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 {
            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 ---

    auto getImageArrayFunc = [rnCapture](Runtime &runtime, NSArray<DSImageData *> *imageDataArray) -> Value {
        Array array(runtime, imageDataArray.count);
        for (int i = 0; i < imageDataArray.count; i++) {
            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 {
            if (count != 1 || !arguments[0].isString()) {
                return Value::null();
            }

            NSString *imageHashId = convertJSIStringToNSString(runtime, arguments[0].getString(runtime));

            DSImageData *imageData = [rnCapture getOriginalImage:imageHashId];
            if (imageData == nil) {
                return Value::null();
            }

            ImageDataHostObject hostObject = [imageData hostObject];
            Object obj = Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(hostObject));
            return obj;
        }
    );
}
