/****************************************************************************
 Copyright (c) 2017-2023 Xiamen Yaji Software Co., Ltd.

 http://www.cocos.com

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights to
 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 of the Software, and to permit persons to whom the Software is furnished to do so,
 subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
****************************************************************************/

#include "jsb_cocos_manual.h"

#include "bindings/manual/jsb_global.h"
#include "cocos/bindings/auto/jsb_cocos_auto.h"
#include "cocos/bindings/jswrapper/SeApi.h"
#include "cocos/bindings/manual/jsb_conversions.h"
#include "cocos/bindings/manual/jsb_global_init.h"

#include "application/ApplicationManager.h"
#include "platform/interfaces/modules/ISystemWindowManager.h"
#include "storage/local-storage/LocalStorage.h"

extern se::Object *__jsb_cc_FileUtils_proto; // NOLINT(readability-redundant-declaration, readability-identifier-naming)

static bool jsb_ccx_empty_func(se::State & /*s*/) { // NOLINT(readability-identifier-naming)
    return true;
}
SE_BIND_FUNC(jsb_ccx_empty_func) // NOLINT(readability-identifier-naming)

class JSPlistDelegator : public cc::SAXDelegator {
public:
    static JSPlistDelegator *getInstance() {
        static JSPlistDelegator *pInstance = nullptr;
        if (pInstance == nullptr) {
            pInstance = ccnew JSPlistDelegator();
        }
        return pInstance;
    };

    ~JSPlistDelegator() override;

    cc::SAXParser *getParser();

    ccstd::string parse(const ccstd::string &path);
    ccstd::string parseText(const ccstd::string &text);

    // implement pure virtual methods of SAXDelegator
    void startElement(void *ctx, const char *name, const char **atts) override;
    void endElement(void *ctx, const char *name) override;
    void textHandler(void *ctx, const char *ch, int len) override;

private:
    cc::SAXParser _parser;
    ccstd::string _result;
    bool _isStoringCharacters;
    ccstd::string _currentValue;
};

// cc.PlistParser.getInstance()
static bool js_PlistParser_getInstance(se::State &s) { // NOLINT(readability-identifier-naming)
    JSPlistDelegator *delegator = JSPlistDelegator::getInstance();
    cc::SAXParser *parser = delegator->getParser();

    if (parser) {
        native_ptr_to_seval<cc::SAXParser>(parser, __jsb_cc_SAXParser_class, &s.rval());
        s.rval().toObject()->root();
        return true;
    }
    return false;
}
SE_BIND_FUNC(js_PlistParser_getInstance) // NOLINT(readability-identifier-naming)

// cc.PlistParser.getInstance().parse(text)
static bool js_PlistParser_parse(se::State &s) { // NOLINT(readability-identifier-naming)
    const auto &args = s.args();
    size_t argc = args.size();
    JSPlistDelegator *delegator = JSPlistDelegator::getInstance();

    bool ok = true;
    if (argc == 1) {
        ccstd::string arg0;
        ok &= sevalue_to_native(args[0], &arg0);
        SE_PRECONDITION2(ok, false, "Error processing arguments");

        ccstd::string parsedStr = delegator->parseText(arg0);
        std::replace(parsedStr.begin(), parsedStr.end(), '\n', ' ');

        se::Value strVal;
        nativevalue_to_se(parsedStr, strVal);

        se::HandleObject robj(se::Object::createJSONObject(strVal.toString()));
        s.rval().setObject(robj);
        return true;
    }
    SE_REPORT_ERROR("js_PlistParser_parse : wrong number of arguments: %d, was expecting %d", (int)argc, 1);
    return false;
}
SE_BIND_FUNC(js_PlistParser_parse)

cc::SAXParser *JSPlistDelegator::getParser() {
    return &_parser;
}

ccstd::string JSPlistDelegator::parse(const ccstd::string &path) {
    _result.clear();

    cc::SAXParser parser;
    if (parser.init("UTF-8")) {
        parser.setDelegator(this);
        parser.parse(cc::FileUtils::getInstance()->fullPathForFilename(path));
    }

    return _result;
}

JSPlistDelegator::~JSPlistDelegator() {
    CC_LOG_INFO("deallocing __JSSAXDelegator: %p", this);
}

ccstd::string JSPlistDelegator::parseText(const ccstd::string &text) {
    _result.clear();

    cc::SAXParser parser;
    if (parser.init("UTF-8")) {
        parser.setDelegator(this);
        parser.parse(text.c_str(), text.size());
    }

    return _result;
}

void JSPlistDelegator::startElement(void * /*ctx*/, const char *name, const char ** /*atts*/) {
    _isStoringCharacters = true;
    _currentValue.clear();

    ccstd::string elementName{name};

    auto end = static_cast<int>(_result.size()) - 1;
    if (end >= 0 && _result[end] != '{' && _result[end] != '[' && _result[end] != ':') {
        _result += ",";
    }

    if (elementName == "dict") {
        _result += "{";
    } else if (elementName == "array") {
        _result += "[";
    }
}

void JSPlistDelegator::endElement(void * /*ctx*/, const char *name) {
    _isStoringCharacters = false;
    ccstd::string elementName{name};

    if (elementName == "dict") {
        _result += "}";
    } else if (elementName == "array") {
        _result += "]";
    } else if (elementName == "key") {
        _result += "\"" + _currentValue + "\":";
    } else if (elementName == "string") {
        _result += "\"" + _currentValue + "\"";
    } else if (elementName == "false" || elementName == "true") {
        _result += elementName;
    } else if (elementName == "real" || elementName == "integer") {
        _result += _currentValue;
    }
}

void JSPlistDelegator::textHandler(void * /*unused*/, const char *ch, int len) {
    ccstd::string text(ch, 0, len);

    if (_isStoringCharacters) {
        _currentValue += text;
    }
}

static bool register_plist_parser(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming)
    se::Value v;
    __jsbObj->getProperty("PlistParser", &v);
    CC_ASSERT(v.isObject());
    v.toObject()->defineFunction("getInstance", _SE(js_PlistParser_getInstance));

    __jsb_cc_SAXParser_proto->defineFunction("parse", _SE(js_PlistParser_parse));

    se::ScriptEngine::getInstance()->clearException();

    return true;
}

// cc.sys.localStorage

static bool JSB_localStorageGetItem(se::State &s) { // NOLINT(readability-identifier-naming)
    const auto &args = s.args();
    size_t argc = args.size();
    if (argc == 1) {
        bool ok = true;
        ccstd::string key;
        ok = sevalue_to_native(args[0], &key);
        SE_PRECONDITION2(ok, false, "Error processing arguments");
        ccstd::string value;
        ok = localStorageGetItem(key, &value);
        if (ok) {
            s.rval().setString(value);
        } else {
            s.rval().setNull(); // Should return null to make JSB behavior same as Browser since returning undefined will make JSON.parse(undefined) trigger exception.
        }

        return true;
    }

    SE_REPORT_ERROR("Invalid number of arguments");
    return false;
}
SE_BIND_FUNC(JSB_localStorageGetItem) // NOLINT(readability-identifier-naming)

static bool JSB_localStorageRemoveItem(se::State &s) { // NOLINT(readability-identifier-naming)
    const auto &args = s.args();
    size_t argc = args.size();
    if (argc == 1) {
        bool ok = true;
        ccstd::string key;
        ok = sevalue_to_native(args[0], &key);
        SE_PRECONDITION2(ok, false, "Error processing arguments");
        localStorageRemoveItem(key);
        return true;
    }

    SE_REPORT_ERROR("Invalid number of arguments");
    return false;
}
SE_BIND_FUNC(JSB_localStorageRemoveItem) // NOLINT(readability-identifier-naming)

static bool JSB_localStorageSetItem(se::State &s) { // NOLINT(readability-identifier-naming)
    const auto &args = s.args();
    size_t argc = args.size();
    if (argc == 2) {
        bool ok = true;
        ccstd::string key;
        ok = sevalue_to_native(args[0], &key);
        SE_PRECONDITION2(ok, false, "Error processing arguments");

        ccstd::string value = args[1].toStringForce();
        localStorageSetItem(key, value);
        return true;
    }

    SE_REPORT_ERROR("Invalid number of arguments");
    return false;
}
SE_BIND_FUNC(JSB_localStorageSetItem) // NOLINT(readability-identifier-naming)

static bool JSB_localStorageClear(se::State &s) { // NOLINT(readability-identifier-naming)
    const auto &args = s.args();
    size_t argc = args.size();
    if (argc == 0) {
        localStorageClear();
        return true;
    }

    SE_REPORT_ERROR("Invalid number of arguments");
    return false;
}
SE_BIND_FUNC(JSB_localStorageClear) // NOLINT(readability-identifier-naming)

static bool JSB_localStorageKey(se::State &s) { // NOLINT(readability-identifier-naming)
    const auto &args = s.args();
    size_t argc = args.size();
    if (argc == 1) {
        bool ok = true;
        int nIndex = 0;
        ok = sevalue_to_native(args[0], &nIndex);
        SE_PRECONDITION2(ok, false, "Error processing arguments");
        ccstd::string value;
        localStorageGetKey(nIndex, &value);
        s.rval().setString(value);
        return true;
    }

    SE_REPORT_ERROR("Invalid number of arguments");
    return false;
}
SE_BIND_FUNC(JSB_localStorageKey) // NOLINT(readability-identifier-naming)

static bool JSB_localStorage_getLength(se::State &s) { // NOLINT(readability-identifier-naming)
    const auto &args = s.args();
    size_t argc = args.size();
    if (argc == 0) {
        int nLength = 0;

        localStorageGetLength(nLength);
        s.rval().setInt32(nLength);
        return true;
    }

    SE_REPORT_ERROR("Invalid number of arguments");
    return false;
}
SE_BIND_PROP_GET(JSB_localStorage_getLength); // NOLINT(readability-identifier-naming)

static bool register_sys_localStorage(se::Object *obj) { // NOLINT(readability-identifier-naming)
    se::Value sys;
    if (!obj->getProperty("sys", &sys)) {
        se::HandleObject sysObj(se::Object::createPlainObject());
        obj->setProperty("sys", se::Value(sysObj));
        sys.setObject(sysObj);
    }

    se::HandleObject localStorageObj(se::Object::createPlainObject());
    sys.toObject()->setProperty("localStorage", se::Value(localStorageObj));

    localStorageObj->defineFunction("getItem", _SE(JSB_localStorageGetItem));
    localStorageObj->defineFunction("removeItem", _SE(JSB_localStorageRemoveItem));
    localStorageObj->defineFunction("setItem", _SE(JSB_localStorageSetItem));
    localStorageObj->defineFunction("clear", _SE(JSB_localStorageClear));
    localStorageObj->defineFunction("key", _SE(JSB_localStorageKey));
    localStorageObj->defineProperty("length", _SE(JSB_localStorage_getLength), nullptr);

    ccstd::string strFilePath = cc::FileUtils::getInstance()->getWritablePath();
#if defined(__QNX__)
    // In the QNX environment, the execution of this statement will not take effect.
    // Not sure why
    // strFilePath += "/jsb.sqlite";

    // Use another way
    char path[256] = {0};
    sprintf(path, "%s/jsb.sqlite", strFilePath.c_str());
    localStorageInit(path);
#else
    strFilePath += "/jsb.sqlite";
    localStorageInit(strFilePath);
#endif

    se::ScriptEngine::getInstance()->addBeforeCleanupHook([]() {
        localStorageFree();
    });

    se::ScriptEngine::getInstance()->clearException();

    return true;
}

//IDEA:  move to auto bindings.
static bool js_CanvasRenderingContext2D_setCanvasBufferUpdatedCallback(se::State &s) { // NOLINT(readability-identifier-naming)
    auto *cobj = static_cast<cc::ICanvasRenderingContext2D *>(s.nativeThisObject());
    SE_PRECONDITION2(cobj, false, "Invalid Native Object");
    const auto &args = s.args();
    size_t argc = args.size();
    CC_UNUSED bool ok = true;
    if (argc == 1) {
        std::function<void(const cc::Data &)> arg0;
        do {
            if (args[0].isObject() && args[0].toObject()->isFunction()) {
                se::Value jsThis(s.thisObject());
                se::Value jsFunc(args[0]);
                jsThis.toObject()->attachObject(jsFunc.toObject());
                se::Object *thisObj = s.thisObject();
                auto lambda = [=](const cc::Data &larg0) -> void {
                    se::ScriptEngine::getInstance()->clearException();
                    se::AutoHandleScope hs;

                    CC_UNUSED bool ok = true;
                    se::ValueArray args;
                    args.resize(1);
                    ok &= Data_to_TypedArray(larg0, &args[0]);
                    se::Value rval;
                    se::Object *funcObj = jsFunc.toObject();
                    bool succeed = funcObj->call(args, thisObj, &rval);
                    if (!succeed) {
                        se::ScriptEngine::getInstance()->clearException();
                    }
                };
                // Add an unroot to avoid the root of the copy constructor caused by the internal reference of Lambda.
                if (thisObj) {
                    thisObj->unroot();
                }
                jsFunc.toObject()->unroot();
                arg0 = lambda;
            } else {
                arg0 = nullptr;
            }
        } while (false);
        SE_PRECONDITION2(ok, false, "Error processing arguments");
        cobj->setCanvasBufferUpdatedCallback(arg0);
        return true;
    }
    SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1);
    return false;
}
SE_BIND_FUNC(js_CanvasRenderingContext2D_setCanvasBufferUpdatedCallback) // NOLINT(readability-identifier-naming)

static void setCanvasRenderingContext2DProps(cc::ICanvasRenderingContext2D *context, const se::Value &val) {
    se::Object *props = val.toObject();
    se::Value propVal;

    props->getProperty("lineWidth", &propVal);
    if (!propVal.isUndefined()) context->setLineWidth(propVal.toFloat());

    props->getProperty("lineJoin", &propVal);
    if (!propVal.isUndefined()) context->setLineJoin(propVal.toString());

    props->getProperty("fillStyle", &propVal);
    if (!propVal.isUndefined()) context->setFillStyle(propVal.toString());

    props->getProperty("font", &propVal);
    if (!propVal.isUndefined()) context->setFont(propVal.toString());

    props->getProperty("lineCap", &propVal);
    if (!propVal.isUndefined()) context->setLineCap(propVal.toString());

    props->getProperty("textAlign", &propVal);
    if (!propVal.isUndefined()) context->setTextAlign(propVal.toString());

    props->getProperty("textBaseline", &propVal);
    if (!propVal.isUndefined()) context->setTextBaseline(propVal.toString());

    props->getProperty("strokeStyle", &propVal);
    if (!propVal.isUndefined()) context->setStrokeStyle(propVal.toString());

    props->getProperty("globalCompositeOperation", &propVal);
    if (!propVal.isUndefined()) context->setGlobalCompositeOperation(propVal.toString());

    props->getProperty("shadowBlur", &propVal);
    if (!propVal.isUndefined()) context->setShadowBlur(propVal.toFloat());

    props->getProperty("shadowColor", &propVal);
    if (!propVal.isUndefined()) context->setShadowColor(propVal.toString());

    props->getProperty("shadowOffsetX", &propVal);
    if (!propVal.isUndefined()) context->setShadowOffsetX(propVal.toFloat());

    props->getProperty("shadowOffsetY", &propVal);
    if (!propVal.isUndefined()) context->setShadowOffsetY(propVal.toFloat());
}

static bool js_engine_CanvasRenderingContext2D_measureText(se::State &s) { // NOLINT(readability-identifier-naming)
    auto *cobj = static_cast<cc::ICanvasRenderingContext2D *>(s.nativeThisObject());
    SE_PRECONDITION2(cobj, false, "Invalid Native Object");
    const auto &args = s.args();
    size_t argc = args.size();
    CC_UNUSED bool ok = true;
    if (argc == 2) {
        ccstd::string arg0;
        ok &= sevalue_to_native(args[0], &arg0);
        SE_PRECONDITION2(ok, false, "Error processing arguments");
        SE_PRECONDITION2(args[1].isObject(), false, "no attributes set.");
        setCanvasRenderingContext2DProps(cobj, args[1]);
        cc::Size result = cobj->measureText(arg0);
        ok &= nativevalue_to_se(result, s.rval());
        SE_PRECONDITION2(ok, false, "Error processing arguments");
        return true;
    }
    SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 1);
    return false;
}
SE_BIND_FUNC(js_engine_CanvasRenderingContext2D_measureText) // NOLINT(readability-identifier-naming)

static bool js_engine_CanvasRenderingContext2D_fillRect(se::State &s) { // NOLINT(readability-identifier-naming)
    auto *cobj = static_cast<cc::ICanvasRenderingContext2D *>(s.nativeThisObject());
    SE_PRECONDITION2(cobj, false, "Invalid Native Object");
    const auto &args = s.args();
    size_t argc = args.size();
    CC_UNUSED bool ok = true;
    if (argc == 5) {
        float arg0 = 0;
        float arg1 = 0;
        float arg2 = 0;
        float arg3 = 0;
        ok &= sevalue_to_native(args[0], &arg0);
        ok &= sevalue_to_native(args[1], &arg1);
        ok &= sevalue_to_native(args[2], &arg2);
        ok &= sevalue_to_native(args[3], &arg3);
        SE_PRECONDITION2(args[4].isObject(), false, "no attributes set.");
        setCanvasRenderingContext2DProps(cobj, args[4]);
        SE_PRECONDITION2(ok, false, "Error processing arguments");
        cobj->fillRect(arg0, arg1, arg2, arg3);
        return true;
    }
    SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 4);
    return false;
}
SE_BIND_FUNC(js_engine_CanvasRenderingContext2D_fillRect) // NOLINT(readability-identifier-naming)

static bool js_engine_CanvasRenderingContext2D_fillText(se::State &s) { // NOLINT(readability-identifier-naming)
    auto *cobj = static_cast<cc::ICanvasRenderingContext2D *>(s.nativeThisObject());
    SE_PRECONDITION2(cobj, false, "Invalid Native Object");
    const auto &args = s.args();
    size_t argc = args.size();
    CC_UNUSED bool ok = true;
    if (argc == 5) {
        ccstd::string arg0;
        float arg1 = 0;
        float arg2 = 0;
        float arg3 = 0;
        ok &= sevalue_to_native(args[0], &arg0);
        ok &= sevalue_to_native(args[1], &arg1);
        ok &= sevalue_to_native(args[2], &arg2);
        SE_PRECONDITION2(args[4].isObject(), false, "no attributes set.");
        setCanvasRenderingContext2DProps(cobj, args[4]);
        if (args[3].isUndefined()) {
            SE_PRECONDITION2(ok, false, "Error processing arguments");
            cobj->fillText(arg0, arg1, arg2, -1.0F);
        } else {
            ok &= sevalue_to_native(args[3], &arg3);
            SE_PRECONDITION2(ok, false, "Error processing arguments");
            cobj->fillText(arg0, arg1, arg2, arg3);
        }
        return true;
    }
    SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 4);
    return false;
}
SE_BIND_FUNC(js_engine_CanvasRenderingContext2D_fillText) // NOLINT(readability-identifier-naming)

static bool js_engine_CanvasRenderingContext2D_strokeText(se::State &s) { // NOLINT(readability-identifier-naming)
    auto *cobj = static_cast<cc::ICanvasRenderingContext2D *>(s.nativeThisObject());
    SE_PRECONDITION2(cobj, false, "Invalid Native Object");
    const auto &args = s.args();
    size_t argc = args.size();
    bool ok = true;
    if (argc == 5) {
        ccstd::string arg0;
        float arg1 = 0;
        float arg2 = 0;
        float arg3 = 0;
        ok &= sevalue_to_native(args[0], &arg0);
        ok &= sevalue_to_native(args[1], &arg1);
        ok &= sevalue_to_native(args[2], &arg2);
        SE_PRECONDITION2(args[4].isObject(), false, "no attributes set.");
        setCanvasRenderingContext2DProps(cobj, args[4]);
        if (!args[3].isUndefined()) {
            ok &= sevalue_to_native(args[3], &arg3);
            SE_PRECONDITION2(ok, false, "Error processing arguments");
            cobj->strokeText(arg0, arg1, arg2, arg3);
        } else {
            SE_PRECONDITION2(ok, false, "Error processing arguments");
            cobj->strokeText(arg0, arg1, arg2, -1.0F);
        }

        return true;
    }
    SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 4);
    return false;
}
SE_BIND_FUNC(js_engine_CanvasRenderingContext2D_strokeText) // NOLINT(readability-identifier-naming)

static se::Object *deviceMotionObject = nullptr;
static bool JSB_getDeviceMotionValue(se::State &s) { // NOLINT(readability-identifier-naming)
    if (deviceMotionObject == nullptr) {
        deviceMotionObject = se::Object::createArrayObject(9);
        deviceMotionObject->root();
    }

    const auto &v = cc::Device::getDeviceMotionValue();

    deviceMotionObject->setArrayElement(0, se::Value(v.accelerationX));
    deviceMotionObject->setArrayElement(1, se::Value(v.accelerationY));
    deviceMotionObject->setArrayElement(2, se::Value(v.accelerationZ));
    deviceMotionObject->setArrayElement(3, se::Value(v.accelerationIncludingGravityX));
    deviceMotionObject->setArrayElement(4, se::Value(v.accelerationIncludingGravityY));
    deviceMotionObject->setArrayElement(5, se::Value(v.accelerationIncludingGravityZ));
    deviceMotionObject->setArrayElement(6, se::Value(v.rotationRateAlpha));
    deviceMotionObject->setArrayElement(7, se::Value(v.rotationRateBeta));
    deviceMotionObject->setArrayElement(8, se::Value(v.rotationRateGamma));

    s.rval().setObject(deviceMotionObject);
    return true;
}
SE_BIND_FUNC(JSB_getDeviceMotionValue) // NOLINT(readability-identifier-naming)

static bool register_device(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming)
    se::Value device;
    __jsbObj->getProperty("Device", &device);

    device.toObject()->defineFunction("getDeviceMotionValue", _SE(JSB_getDeviceMotionValue));

    se::ScriptEngine::getInstance()->addBeforeCleanupHook([]() {
        if (deviceMotionObject != nullptr) {
            deviceMotionObject->unroot();
            deviceMotionObject->decRef();
            deviceMotionObject = nullptr;
        }
    });

    se::ScriptEngine::getInstance()->clearException();
    return true;
}

static bool register_canvas_context2d(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming)
    __jsb_cc_ICanvasRenderingContext2D_proto->defineFunction("_setCanvasBufferUpdatedCallback", _SE(js_CanvasRenderingContext2D_setCanvasBufferUpdatedCallback));
    __jsb_cc_ICanvasRenderingContext2D_proto->defineFunction("fillText", _SE(js_engine_CanvasRenderingContext2D_fillText));
    __jsb_cc_ICanvasRenderingContext2D_proto->defineFunction("strokeText", _SE(js_engine_CanvasRenderingContext2D_strokeText));
    __jsb_cc_ICanvasRenderingContext2D_proto->defineFunction("fillRect", _SE(js_engine_CanvasRenderingContext2D_fillRect));
    __jsb_cc_ICanvasRenderingContext2D_proto->defineFunction("measureText", _SE(js_engine_CanvasRenderingContext2D_measureText));

    se::ScriptEngine::getInstance()->clearException();

    return true;
}

static bool js_engine_FileUtils_listFilesRecursively(se::State &s) { // NOLINT(readability-identifier-naming)
    auto *cobj = static_cast<cc::FileUtils *>(s.nativeThisObject());
    SE_PRECONDITION2(cobj, false, "Invalid Native Object");
    const auto &args = s.args();
    size_t argc = args.size();
    CC_UNUSED bool ok = true;
    if (argc == 2) {
        ccstd::string arg0;
        ccstd::vector<ccstd::string> arg1;
        ok &= sevalue_to_native(args[0], &arg0);
        SE_PRECONDITION2(ok, false, "Error processing arguments");
        cobj->listFilesRecursively(arg0, &arg1);
        se::Object *list = args[1].toObject();
        SE_PRECONDITION2(args[1].isObject() && list->isArray(), false, "2nd argument should be an Array");
        for (uint32_t i = 0; i < static_cast<uint32_t>(arg1.size()); i++) {
            list->setArrayElement(i, se::Value(arg1[i]));
        }
        list->setProperty("length", se::Value(static_cast<uint32_t>(arg1.size())));
        return true;
    }
    SE_REPORT_ERROR("wrong number of arguments: %d, was expecting %d", (int)argc, 2);
    return false;
}
SE_BIND_FUNC(js_engine_FileUtils_listFilesRecursively) // NOLINT(readability-identifier-naming)

static bool js_se_setExceptionCallback(se::State &s) { // NOLINT(readability-identifier-naming)
    const auto &args = s.args();
    if (args.size() != 1 || !args[0].isObject() || !args[0].toObject()->isFunction()) {
        SE_REPORT_ERROR("expect 1 arguments of Function type, %d provided", (int)args.size());
        return false;
    }

    se::Object *objFunc = args[0].toObject();
    // se::Value::reset will invoke decRef() while destroying s.args()
    // increase ref here
    objFunc->incRef();
    if (s.thisObject()) {
        s.thisObject()->attachObject(objFunc); // prevent GC
    } else {
        //prevent GC in C++ & JS
        objFunc->root();
    }

    se::ScriptEngine::getInstance()->setJSExceptionCallback([objFunc](const char *location, const char *message, const char *stack) {
        se::AutoHandleScope scope;
        se::ValueArray jsArgs;
        jsArgs.resize(3);
        jsArgs[0] = se::Value(location);
        jsArgs[1] = se::Value(message);
        jsArgs[2] = se::Value(stack);
        objFunc->call(jsArgs, nullptr);
    });

    se::ScriptEngine::getInstance()->addBeforeCleanupHook([objFunc] {
        objFunc->decRef();
    });

    return true;
}
SE_BIND_FUNC(js_se_setExceptionCallback) // NOLINT(readability-identifier-naming)

static bool register_filetuils_ext(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming)
    __jsb_cc_FileUtils_proto->defineFunction("listFilesRecursively", _SE(js_engine_FileUtils_listFilesRecursively));
    return true;
}

static bool register_se_setExceptionCallback(se::Object *obj) { // NOLINT(readability-identifier-naming)
    se::Value jsb;
    if (!obj->getProperty("jsb", &jsb)) {
        jsb.setObject(se::Object::createPlainObject());
        obj->setProperty("jsb", jsb);
    }
    auto *jsbObj = jsb.toObject();
    jsbObj->defineFunction("onError", _SE(js_se_setExceptionCallback));

    return true;
}

static bool js_engine_Color_get_val(se::State &s) // NOLINT(readability-identifier-naming)
{
    auto *cobj = SE_THIS_OBJECT<cc::Color>(s);
    SE_PRECONDITION2(cobj, false, "Invalid Native Object");

    CC_UNUSED bool ok = true;
    se::Value jsret;
    auto r = static_cast<uint32_t>(cobj->r);
    auto g = static_cast<uint32_t>(cobj->g);
    auto b = static_cast<uint32_t>(cobj->b);
    auto a = static_cast<uint32_t>(cobj->a);
    uint32_t val = (a << 24) + (b << 16) + (g << 8) + r;
    ok &= nativevalue_to_se(val, jsret, s.thisObject() /*ctx*/);
    s.rval() = jsret;
    return true;
}
SE_BIND_PROP_GET(js_engine_Color_get_val)

static bool js_engine_Color_set_val(se::State &s) // NOLINT(readability-identifier-naming)
{
    const auto &args = s.args();
    auto *cobj = SE_THIS_OBJECT<cc::Color>(s);
    SE_PRECONDITION2(cobj, false, "Invalid Native Object");

    CC_UNUSED bool ok = true;
    uint32_t val{0};
    ok &= sevalue_to_native(args[0], &val, s.thisObject());
    cobj->r = val & 0x000000FF;
    cobj->g = (val & 0x0000FF00) >> 8;
    cobj->b = (val & 0x00FF0000) >> 16;
    cobj->a = (val & 0xFF000000) >> 24;
    SE_PRECONDITION2(ok, false, "Error processing new value");
    return true;
}
SE_BIND_PROP_SET(js_engine_Color_set_val)

static bool register_engine_Color_manual(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming)
    __jsb_cc_Color_proto->defineProperty("_val", _SE(js_engine_Color_get_val), _SE(js_engine_Color_set_val));

    se::ScriptEngine::getInstance()->clearException();

    return true;
}
static bool js_cc_ISystemWindowManager_getInstance_static(se::State &s) { // NOLINT(readability-identifier-naming)
    const auto &args = s.args();
    CC_UNUSED bool ok = true;
    auto *instance = CC_GET_PLATFORM_INTERFACE(cc::ISystemWindowManager);
    ok &= nativevalue_to_se(instance, s.rval(), s.thisObject());
    SE_PRECONDITION2(ok, false, "js_cc_ISystemWindowManager_getInstance Failed");

    return true;
}
SE_BIND_FUNC(js_cc_ISystemWindowManager_getInstance_static)

static bool register_platform(se::Object * /*obj*/) { // NOLINT(readability-identifier-naming)
    se::Value constructor;
    bool result = __jsb_cc_ISystemWindowManager_proto->getProperty("constructor", &constructor);
    result &= constructor.toObject()->defineFunction("getInstance", _SE(js_cc_ISystemWindowManager_getInstance_static));
    return result;
}

template <typename T>
static bool bindAsExternalBuffer(se::State &s) { // NOLINT
    auto *self = SE_THIS_OBJECT<T>(s);
    if (!self) {
        return false;
    }
    // NOLINTNEXTLINE
    se::HandleObject buffer(se::Object::createExternalArrayBufferObject(self, sizeof(*self), [](void *, size_t, void *) {}));
    s.rval().setObject(buffer);
    return true;
}

static bool js_cc_Vec2_underlyingData(se::State &s) { // NOLINT
    return bindAsExternalBuffer<cc::Vec2>(s);
}
SE_BIND_FUNC(js_cc_Vec2_underlyingData)

static bool js_cc_Vec3_underlyingData(se::State &s) { // NOLINT
    return bindAsExternalBuffer<cc::Vec3>(s);
}
SE_BIND_FUNC(js_cc_Vec3_underlyingData)

static bool js_cc_Vec4_underlyingData(se::State &s) { // NOLINT
    return bindAsExternalBuffer<cc::Vec4>(s);
}
SE_BIND_FUNC(js_cc_Vec4_underlyingData)

static bool js_cc_Mat3_underlyingData(se::State &s) { // NOLINT
    return bindAsExternalBuffer<cc::Mat3>(s);
}
SE_BIND_FUNC(js_cc_Mat3_underlyingData)

static bool js_cc_Mat4_underlyingData(se::State &s) { // NOLINT
    return bindAsExternalBuffer<cc::Mat4>(s);
}
SE_BIND_FUNC(js_cc_Mat4_underlyingData)

static bool js_cc_Quaternion_underlyingData(se::State &s) { // NOLINT
    return bindAsExternalBuffer<cc::Quaternion>(s);
}
SE_BIND_FUNC(js_cc_Quaternion_underlyingData)

bool register_all_cocos_manual(se::Object *obj) { // NOLINT(readability-identifier-naming)

    __jsb_cc_Vec2_proto->defineFunction("underlyingData", _SE(js_cc_Vec2_underlyingData));
    __jsb_cc_Vec3_proto->defineFunction("underlyingData", _SE(js_cc_Vec3_underlyingData));
    __jsb_cc_Vec4_proto->defineFunction("underlyingData", _SE(js_cc_Vec4_underlyingData));
    __jsb_cc_Mat3_proto->defineFunction("underlyingData", _SE(js_cc_Mat3_underlyingData));
    __jsb_cc_Mat4_proto->defineFunction("underlyingData", _SE(js_cc_Mat4_underlyingData));
    __jsb_cc_Quaternion_proto->defineFunction("underlyingData", _SE(js_cc_Quaternion_underlyingData));

    register_plist_parser(obj);
    register_sys_localStorage(obj);
    register_device(obj);
    register_canvas_context2d(obj);
    register_filetuils_ext(obj);
    register_engine_Color_manual(obj);
    register_se_setExceptionCallback(obj);
    register_platform(obj);
    return true;
}
