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

#import <React/RCTBridge+Private.h>
#import <React/RCTUtils.h>
#import <React/RCTEventEmitter.h>
#import <React/RCTViewManager.h>
#import <DynamsoftCaptureVisionSpec/DynamsoftCaptureVisionSpec.h>
#import <jsi/jsi.h>
#import <sys/utsname.h>
#import "YeetJSIUtils.h"
#import <DynamsoftCaptureVisionBundle/DynamsoftCaptureVisionBundle.h>
#import "DSImageData+HostObject.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, RNDynamsoftImageSourceAdapter *rnAdapter);

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

@implementation RNDynamsoftImageSourceAdapter (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::NativeDynamsoftImageSourceAdapterModuleSpecJSI>(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 (ISAModule.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 Lambdas ---
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, RNDynamsoftImageSourceAdapter *rnAdapter) {
    using namespace jsi;

    // Helper Lambda: Extract ImageDataHostObject from JSI Value
    auto getHostImageData = [&](Runtime &runtime, const Value &value) -> std::optional<ImageDataHostObject> {
        if (!value.isObject()) return std::nullopt;
        auto obj = value.getObject(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 {
            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 {
            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 {
            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;
            UIImage *image = [imageData toUIImage:&error];
            if (!error && image) {
                NSData *data = UIImageJPEGRepresentation(image, 1.0);
                NSString *base64String = [data base64EncodedStringWithOptions:0];

                return convertNSStringToJSIString(runtime, base64String);
            }

            return Value(false);
        }
    );
}
