/*
 * 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 "DSImageData+HostObject.h"
#import "RNDynamsoft+Json.h"
#import "RNDynamsoft+JSI.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, RNDynamsoftImageManager *rnManager);

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

@implementation RNDynamsoftImageManager (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::NativeDynamsoftImageManagerModuleSpecJSI>(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 (DynamsoftImageManagerModule.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, RNDynamsoftImageManager *rnManager) {
    using namespace jsi;

    // 1. 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);
    };

    // 2. 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));
    };

    // imageIO_readFromFile(path)
    installHostFunction(jsiRuntime, "imageIO_readFromFile", 1,
        [rnManager, createJsiHostObject](Runtime &runtime, const Value &, const Value *args, size_t count) -> Value {
            if (count < 1 || !args[0].isString()) return Value::null();

            NSString *path = convertJSIStringToNSString(runtime, args[0].getString(runtime));
            NSError *error = nil;
            DSImageData *imageData = [rnManager.manager readFromFile:path error:&error];

            return createJsiHostObject(runtime, imageData);
        }
    );

    // imageIO_saveToFile(imageData, path, overwrite)
    installHostFunction(jsiRuntime, "imageIO_saveToFile", 3,
        [rnManager, getHostImageData](Runtime &runtime, const Value &, const Value *args, size_t count) -> Value {
            if (count < 3 || !args[1].isString() || !args[2].isBool()) return Value(false);

            auto hostOpt = getHostImageData(runtime, args[0]);
            if (!hostOpt) return Value(false);

            DSImageData *data = [DSImageData imageDataFromHostObject:*hostOpt];
            NSString *path = convertJSIStringToNSString(runtime, args[1].getString(runtime));
            bool overwrite = args[2].getBool();

            NSError *error = nil;
            BOOL success = [rnManager.manager saveToFile:data path:path overWrite:overwrite error:&error];
            return Value(success && !error);
        }
    );

    // imageIO_readFromMemory(arrayBuffer)
    installHostFunction(jsiRuntime, "imageIO_readFromMemory", 1,
        [rnManager, createJsiHostObject](Runtime &runtime, const Value &, const Value *args, size_t count) -> Value {
            if (count < 1 || !args[0].isObject()) return Value::null();

            jsi::Object jsObj = args[0].getObject(runtime);
            if (!jsObj.isArrayBuffer(runtime)) return Value::null();

            jsi::ArrayBuffer buffer = jsObj.getArrayBuffer(runtime);
            NSData *data = convertArrayBufferToNSData(runtime, buffer);
            if (!data || data.length == 0) return Value::null();

            NSError *error = nil;
            DSImageData *imageData = [rnManager.manager readFromMemory:data error:&error];

            return createJsiHostObject(runtime, imageData);
        }
    );

    // imageIO_saveToMemory(imageData, format)
    installHostFunction(jsiRuntime, "imageIO_saveToMemory", 2,
        [rnManager, getHostImageData](Runtime &runtime, const Value &, const Value *args, size_t count) -> Value {
            if (count < 2 || !args[1].isNumber()) return Value::null();

            auto hostOpt = getHostImageData(runtime, args[0]);
            if (!hostOpt) return Value::null();

            DSImageData *imageData = [DSImageData imageDataFromHostObject:*hostOpt];
            NSInteger format = args[1].asNumber(); // Enum is passed as number
            NSError *error = nil;

            NSData *data = [rnManager.manager saveToMemory:imageData imageFormat:(DSImageFileFormat)format error:&error];
            if (!data || error) return Value::null();

            return Value(converNSDataToArrayBuffer(runtime, data));
        }
    );

    // Helper lambda for simple image processing functions (2 arguments: data, value)
    auto processImageTwoArg =
        [rnManager, getHostImageData, createJsiHostObject](
            Runtime &runtime,
            const Value *args,
            size_t count,
            std::function<DSImageData*(DSImageData*, NSInteger)> processorBlock
        ) -> Value {
            if (count < 2 || !args[1].isNumber()) return Value::null();
            auto hostOpt = getHostImageData(runtime, args[0]);
            if (!hostOpt) return Value::null();

            NSInteger arg2 = args[1].asNumber();
            DSImageData *data = [DSImageData imageDataFromHostObject:*hostOpt];
            DSImageData *outData = processorBlock(data, arg2);

            return createJsiHostObject(runtime, outData);
        };

    // imageProcessor_adjustBrightness(imageData, brightness)
    installHostFunction(jsiRuntime, "imageProcessor_adjustBrightness", 2,
        [rnManager, processImageTwoArg](Runtime &runtime, const Value &, const Value *args, size_t count) -> Value {
            return processImageTwoArg(runtime, args, count,
                [rnManager](DSImageData* data, NSInteger brightness) -> DSImageData * {
                    return [rnManager.processor adjust:data brightness:brightness];
                }
            );
        }
    );

    // imageProcessor_adjustContrast(imageData, contrast)
    installHostFunction(jsiRuntime, "imageProcessor_adjustContrast", 2,
        [rnManager, processImageTwoArg](Runtime &runtime, const Value &, const Value *args, size_t count) -> Value {
            return processImageTwoArg(runtime, args, count,
                [rnManager](DSImageData* data, NSInteger contrast) -> DSImageData * {
                    return [rnManager.processor adjust:data contrast:contrast];
                }
            );
        }
    );

    // imageProcessor_filterImage(imageData, filterType)
    installHostFunction(jsiRuntime, "imageProcessor_filterImage", 2,
        [rnManager, processImageTwoArg](Runtime &runtime, const Value &, const Value *args, size_t count) -> Value {
            return processImageTwoArg(runtime, args, count,
                [rnManager](DSImageData* data, NSInteger filterType) -> DSImageData * {
                    return [rnManager.processor filterImage:data filterType:(DSFilterType)filterType];
                }
            );
        }
    );

    // imageProcessor_cropImage(imageData, rect)
    installHostFunction(jsiRuntime, "imageProcessor_cropImage", 2,
        [rnManager, getHostImageData, createJsiHostObject](Runtime &runtime, const Value &, const Value *args, size_t count) -> Value {
            if (count < 2 || !args[1].isObject()) return Value::null();
            auto hostOpt = getHostImageData(runtime, args[0]);
            if (!hostOpt) return Value::null();

            DSImageData *data = [DSImageData imageDataFromHostObject:*hostOpt];
            DSRect *rect = convertJSIValueToDSRect(runtime, args[1].getObject(runtime));

            NSError *error = nil;
            DSImageData *outData = [rnManager.processor cropImage:data rect:rect error:&error];

            return createJsiHostObject(runtime, outData);
        }
    );

    // imageProcessor_cropAndDeskewImage(data, quad, dstWidth, dstHeight, padding)
    installHostFunction(jsiRuntime, "imageProcessor_cropAndDeskewImage", 5,
        [rnManager, getHostImageData, createJsiHostObject](Runtime &runtime, const Value &, const Value *args, size_t count) -> Value {
            if (count < 5 || !args[1].isObject() || !args[2].isNumber() || !args[3].isNumber() || !args[4].isNumber()) return Value::null();
            auto hostOpt = getHostImageData(runtime, args[0]);
            if (!hostOpt) return Value::null();

            DSImageData *data = [DSImageData imageDataFromHostObject:*hostOpt];
            DSQuadrilateral *quad = convertJSIValueToDSQuadrilateral(runtime, args[1].getObject(runtime));
            NSInteger dstWidth = args[2].asNumber();
            NSInteger dstHeight = args[3].asNumber();
            NSInteger padding = args[4].asNumber();

            NSError *error = nil;
            DSImageData *outData = [rnManager.processor cropAndDeskewImage:data quad:quad dstWidth:dstWidth dstHeight:dstHeight padding:padding error:&error];

            return createJsiHostObject(runtime, outData);
        }
    );

    // imageProcessor_convertToGray(imageData, r, g, b)
    installHostFunction(jsiRuntime, "imageProcessor_convertToGray", 4,
        [rnManager, getHostImageData, createJsiHostObject](Runtime &runtime, const Value &, const Value *args, size_t count) -> Value {
            if (count < 4 || !args[1].isNumber() || !args[2].isNumber() || !args[3].isNumber()) return Value::null();
            auto hostOpt = getHostImageData(runtime, args[0]);
            if (!hostOpt) return Value::null();

            CGFloat r = args[1].asNumber();
            CGFloat g = args[2].asNumber();
            CGFloat b = args[3].asNumber();

            DSImageData *data = [DSImageData imageDataFromHostObject:*hostOpt];
            DSImageData *outData = [rnManager.processor convertToGray:data r:r g:g b:b];

            return createJsiHostObject(runtime, outData);
        }
    );

    // imageProcessor_convertToBinaryGlobal(imageData, threshold, invert)
    installHostFunction(jsiRuntime, "imageProcessor_convertToBinaryGlobal", 3,
        [rnManager, getHostImageData, createJsiHostObject](Runtime &runtime, const Value &, const Value *args, size_t count) -> Value {
            if (count < 3 || !args[1].isNumber() || !args[2].isBool()) return Value::null();
            auto hostOpt = getHostImageData(runtime, args[0]);
            if (!hostOpt) return Value::null();

            NSInteger threshold = args[1].asNumber();
            BOOL invert = args[2].getBool();

            DSImageData *data = [DSImageData imageDataFromHostObject:*hostOpt];
            DSImageData *outData = [rnManager.processor convertToBinaryGlobal:data threshold:threshold invert:invert];

            return createJsiHostObject(runtime, outData);
        }
    );

    // imageProcessor_convertToBinaryLocal(imageData, blockSize, compensation, invert)
    installHostFunction(jsiRuntime, "imageProcessor_convertToBinaryLocal", 4,
        [rnManager, getHostImageData, createJsiHostObject](Runtime &runtime, const Value &, const Value *args, size_t count) -> Value {
            if (count < 4 || !args[1].isNumber() || !args[2].isNumber() || !args[3].isBool()) return Value::null();
            auto hostOpt = getHostImageData(runtime, args[0]);
            if (!hostOpt) return Value::null();

            NSInteger blockSize = args[1].asNumber();
            NSInteger compensation = args[2].asNumber();
            BOOL invert = args[3].getBool();

            DSImageData *data = [DSImageData imageDataFromHostObject:*hostOpt];
            DSImageData *outData = [rnManager.processor convertToBinaryLocal:data blockSize:blockSize compensation:compensation invert:invert];

            return createJsiHostObject(runtime, outData);
        }
    );

    // imageDrawer_drawOnImage(data, quads, hexColor, thickness)
    installHostFunction(jsiRuntime, "imageDrawer_drawOnImage", 4,
        [rnManager, getHostImageData, createJsiHostObject](Runtime &runtime, const Value &, const Value *args, size_t count) -> Value {
            if (count < 4 || !args[1].isObject() || !args[2].isNumber() || !args[3].isNumber()) return Value::null();
            auto hostOpt = getHostImageData(runtime, args[0]);
            if (!hostOpt) return Value::null();

            DSImageData *data = [DSImageData imageDataFromHostObject:*hostOpt];

            Object jsArrayObj = args[1].getObject(runtime);
            if (!jsArrayObj.isArray(runtime)) return Value::null();
            Array jsArray = jsArrayObj.getArray(runtime);
            size_t size = jsArray.size(runtime);

            NSMutableArray<DSQuadrilateral *> *quads = [NSMutableArray arrayWithCapacity:size];
            for (size_t i = 0; i < size; i++) {
                auto val = jsArray.getValueAtIndex(runtime, i);
                if (val.isObject()) {
                    DSQuadrilateral *quad = convertJSIValueToDSQuadrilateral(runtime, val.getObject(runtime));
                    [quads addObject:quad];
                }
            }

            NSInteger hex = args[2].asNumber();
            int thickness = args[3].asNumber();

            UIColor *color = [UIColor colorWithRed:((hex >> 16) & 0xFF) / 255.0
                                            green:((hex >> 8) & 0xFF) / 255.0
                                             blue:(hex & 0xFF) / 255.0
                                            alpha:((hex >> 24) & 0xFF) / 255.0];

            DSImageData *outData = [rnManager.drawer drawOnImage:data quads:[quads copy] colour:color thickness:thickness];

            return createJsiHostObject(runtime, outData);
        }
    );
}
