// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#ifndef QCORE_OHOS_P_H
#define QCORE_OHOS_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <QtCore/private/qnapi_p.h>
#include <QtCore/private/qohoscommon_p.h>
#include <QtCore/private/qohosqpafunctions_p.h>
#include <QtCore/qglobal.h>
#include <QtCore/qobject.h>
#include <QtCore/qpointer.h>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <pthread.h>
#include <string>
#include <tuple>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <vector>

QT_BEGIN_NAMESPACE

namespace QtOhos {

template<typename Enum>
struct OhosEnumMeta
{
};

class Q_CORE_EXPORT QObjectThreadSafeRef
{
public:
    QObjectThreadSafeRef();
    QObjectThreadSafeRef(QPointer<QObject> obj);

    QObjectThreadSafeRef(const QObjectThreadSafeRef &other);
    QObjectThreadSafeRef &operator=(const QObjectThreadSafeRef &other);

    bool operator==(const QObjectThreadSafeRef &other) const;
    bool operator!=(const QObjectThreadSafeRef &other) const;

    std::string refName() const;

    // it may be called only inside Qt thread that created the reference
    QPointer<QObject> data() const;

    void visitInQtThreadIfAlive(std::function<void(QObject &)> visitFunc) const;

private:
    struct QObjectRef
    {
        QPointer<QObject> obj;
        std::string refName;
    };

    static std::mutex refsMapMutex;
    static std::uint64_t refsMapInsertCounter;
    static std::map<QObject *, std::shared_ptr<QObjectRef>> refsMap;

    pthread_t m_creatorThread;
    std::string m_refName;
    std::weak_ptr<QObjectRef> m_weakObjRef;
};

template<typename T>
class QThreadSafeRef : private QObjectThreadSafeRef
{
public:
    static_assert(std::is_base_of<QObject, T>::value, "The class supports QObject subtypes only");

    QThreadSafeRef();
    QThreadSafeRef(QPointer<T> obj);

    QThreadSafeRef(const QThreadSafeRef<T> &other);
    QThreadSafeRef &operator=(const QThreadSafeRef<T> &other);

    QObjectThreadSafeRef toQObjectThreadSafeRef() const;

    std::string refName() const;

    // it may be called only inside Qt thread that created the reference
    QPointer<T> data() const;

    void visitInQtThreadIfAlive(std::function<void(T &)> visitFunc) const;

private:
    QObjectThreadSafeRef m_ref;
};

class Q_CORE_EXPORT JsWindowsTracker
{
public:
    static void tagWindowAsClosing(QNapi::Object jsWindow, const char *logContext);
    static bool isWindowClosing(QNapi::Object jsWindow);

    JsWindowsTracker() = delete;
};

class Q_CORE_EXPORT QAbilityPeer
{
public:
    virtual ~QAbilityPeer();

    virtual std::string instanceId() = 0;
    virtual QNapi::Object uiContext() = 0;
    virtual QNapi::Object qAbility() = 0;
    virtual QNapi::Object launchWant() = 0;
    virtual QObjectThreadSafeRef qWindowRef() = 0;
    virtual QOhosOptional<QNapi::Promise> qWindowDestroyPromise() = 0;
    virtual std::shared_ptr<std::atomic_bool> destroyAllowedFlag() = 0;
    virtual bool isTerminating() = 0;

    virtual void setQWindow(Napi::Env env, QObjectThreadSafeRef qwindow) = 0;

    virtual void *tryCastWithTypeIdObject(const void *matchTypeIdObject) = 0;

protected:
    QAbilityPeer();
};

struct Q_CORE_EXPORT QAbilityInfo
{
    std::string name;
    std::string bundleName;
    std::string moduleName;
};

class Q_CORE_EXPORT QAbilityEngine
{
public:
    virtual ~QAbilityEngine();

    virtual QAbilityInfo readAbilityInfo(const QNapi::Object &ability) const = 0;

protected:
    QAbilityEngine();
};

class JsState;

class Q_CORE_EXPORT AppFunctions
{
public:
    virtual ~AppFunctions();

    virtual void startQAbilityInstance(
        QNapi::Object baseQAbility, QObjectThreadSafeRef qwindow,
        QNapi::Object optStartOptions,
        std::function<void(JsState &, std::shared_ptr<QAbilityPeer>)> startupNotifyFunc) = 0;

    virtual void startAppProcess(
        QNapi::Object baseQAbility, const std::string &processId, QNapi::Object want,
        QNapi::Object optStartOptions) = 0;

    virtual void startAppProcess(
        QNapi::Object baseQAbility, const std::string &processId, QNapi::Object want,
        QNapi::Object optStartOptions, std::function<void(JsState &)> continueFunc) = 0;

    virtual void startNoUiChildProcess(JsState &jsState, const std::string &libraryName, const std::vector<std::string> &args) = 0;

    virtual void tagWidgetOrWindowAsFloatWindow(QObject *widgetOrWindow, bool floatWindowEnabled) = 0;
};

enum class QtRunMode
{
    Normal,
    NoUiChildProcess,
    UiExtension,
    BundledUiExtension,
};

class JsState
{
public:
    JsState(const JsState &) = delete;
    JsState &operator=(const JsState &) = delete;

    virtual ~JsState();

    virtual napi_env env() = 0;
    QT_DEPRECATED virtual QNapi::Object getModule(const std::string &moduleName) = 0;

    virtual QNapi::Object appLaunchWant() = 0;
    virtual QOhosOptional<QNapi::Object> optAppLaunchParam() = 0;

    virtual std::shared_ptr<QAbilityPeer> defaultQAbilityPeer() = 0;
    virtual std::shared_ptr<QAbilityPeer> tryGetQAbilityPeerByInstanceId(const std::string &instanceId) = 0;
    virtual std::shared_ptr<QAbilityPeer> tryGetQAbilityPeerByInstance(QNapi::Object qAbility) = 0;
    virtual std::shared_ptr<QAbilityPeer> tryGetQAbilityPeerByQWindow(QObjectThreadSafeRef qwindow) = 0;

    virtual void visitEachQAbilityPeer(const std::function<void(std::shared_ptr<QAbilityPeer>)> &visitor) = 0;

    virtual void startNewQAbilityInstance(
        std::shared_ptr<QAbilityPeer> baseQAbilityPeer, QObjectThreadSafeRef qwindow,
        QNapi::Object optStartOptions,
        std::function<void(JsState &, std::shared_ptr<QAbilityPeer>)> startupNotifyFunc) = 0;

    virtual void startAppProcess(
        const std::string &processId, QNapi::Object requestWant,
        QNapi::Object optStartOptions = {}) = 0;

    virtual void startAppProcess(
        const std::string &processId, QNapi::Object requestWant,
        QNapi::Object optStartOptions, std::function<void(JsState &)> continueFunc) = 0;

    virtual void addNewWantConsumer(QOhosConsumer<JsState &, QNapi::Object, QNapi::Object> wantConsumer) = 0;

    virtual void startNoUiChildProcess(const std::string &libraryName, const std::vector<std::string> &args) = 0;

    virtual QtRunMode qtRunMode() = 0;

    template<typename T>
    std::enable_if_t<std::is_default_constructible<T>::value, T> &getAttachedObjectWithLazyCreate();

    template<typename T = QNapi::Value>
    T eval(const std::string &expr, const std::vector<QNapi::ValueWrapper> &exprArgs = {});

    template<typename Enum>
    QNapi::Number mapOhosEnumToJs(Enum enumValue);

    template<typename Enum>
    QOhosOptional<Enum> tryMapOhosEnumFromJs(QNapi::Number enumJsValue);

    template<typename Enum>
    Enum mapOhosEnumFromJs(QNapi::Number enumJsValue);

    template<typename T>
    QNapi::Symbol getJsSymbolForType();

protected:
    struct OhosEnumInfo
    {
        std::string fullTypeName;
        std::vector<std::pair<int, const char *>> enumeratorsNames;
    };

    JsState();

private:
    template<typename Enum, typename = void>
    struct OhosEnumFullTypeNameFetcher
    {
        static std::string fullTypeName()
        {
            return OhosEnumMeta<Enum>::moduleName + std::string(".") + OhosEnumMeta<Enum>::typeName;
        }
    };

    template<typename Enum>
    struct OhosEnumFullTypeNameFetcher<Enum, decltype(static_cast<void>(&OhosEnumMeta<Enum>::fullTypeName))>
    {
        static std::string fullTypeName()
        {
            return OhosEnumMeta<Enum>::fullTypeName;
        }
    };

    template<typename Enum>
    static OhosEnumInfo makeOhosEnumInfo();

    virtual void *getAttachedObjectWithLazyCreate(
        const std::type_info &objectTypeInfo, QOhosSupplier<std::shared_ptr<void>> objectFactory) = 0;

    virtual std::tuple<QNapi::Object, std::string> extractModuleFromEvalExpr(const std::string &expr) = 0;
    virtual QNapi::Number mapOhosEnumToJs(int enumValue, const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)()) = 0;
    virtual QOhosOptional<int> tryMapOhosEnumFromJs(QNapi::Number enumJsValue, const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)()) = 0;
    virtual int mapOhosEnumFromJs(QNapi::Number enumJsValue, const std::type_info &enumTypeInfo, OhosEnumInfo (*ohosEnumInfoFactory)()) = 0;
    virtual QNapi::Symbol getJsSymbolForType(const std::type_info &typeInfo) = 0;
};

enum class QOhosAbilityOnContinueResult
{
    AGREE,
    REJECT,
    MISMATCH,
};

class Q_CORE_EXPORT QUiAbilityPeer : public virtual QAbilityPeer, public std::enable_shared_from_this<QUiAbilityPeer>
{
public:
    static std::shared_ptr<QUiAbilityPeer> tryCastFromQAbilityPeerOrNull(std::shared_ptr<QAbilityPeer> qAbilityPeer);

    ~QUiAbilityPeer() override;

    virtual QNapi::Object launchParam() = 0;

    virtual QNapi::Object windowStage() = 0;
    virtual QNapi::Object window() = 0;

    virtual void setOnContinueRequestsHandler(
        std::function<void(JsState &, QNapi::Object, QOhosConsumer<JsState &, QOhosAbilityOnContinueResult>)> requestsHandler) = 0;

protected:
    QUiAbilityPeer();

private:
    static const nullptr_t typeIdObject;

    void *tryCastWithTypeIdObject(const void *matchTypeIdObject) final;
};

class Q_CORE_EXPORT QUiExtensionAbilityPeer : public virtual QAbilityPeer, public std::enable_shared_from_this<QUiExtensionAbilityPeer>
{
public:
    static std::shared_ptr<QUiExtensionAbilityPeer> tryCastFromQAbilityPeerOrNull(std::shared_ptr<QAbilityPeer> qAbilityPeer);

    virtual QNapi::Object uiExtensionContentSession() = 0;
    virtual QOhosOptional<std::string> tryGetQAbilityQWindowBindingKey() = 0;

    ~QUiExtensionAbilityPeer() override;

protected:
    QUiExtensionAbilityPeer();

private:
    static const nullptr_t typeIdObject;

    void *tryCastWithTypeIdObject(const void *matchTypeIdObject) final;
};

class Q_CORE_EXPORT CallbackInfo : public QNapi::CallbackInfo
{
public:
    using QNapi::CallbackInfo::CallbackInfo;

    JsState &jsState() const;
};

class QtState
{
public:
    QtState(const QtState &) = delete;
    QtState &operator=(const QtState &) = delete;

    virtual ~QtState();

    virtual void invokeTask(std::function<void()> &&task) = 0;

    virtual QOhosQpaFunctions &qpaFunctions() = 0;

protected:
    QtState();
};

template<typename T>
QThreadSafeRef<T> makeQThreadSafeRef(T *obj);

// this function should be called once from JS thread at some point during startup
Q_CORE_EXPORT void initJsThreadState(
    napi_env env, std::map<std::string, QNapi::Reference<QNapi::Object>> &&jsModules,
    std::shared_ptr<AppFunctions> appFunctions, QtRunMode qtRunMode);

// this function should be called from JS thread for each UIAbility when it's ready
Q_CORE_EXPORT void addJsQAbilityPeer(std::shared_ptr<QAbilityPeer> qAbilityPeer);

// this function should be called from JS thread when WindowStage of UIAbility is destroyed
Q_CORE_EXPORT void removeMatchingJsQAbilityPeer(QNapi::Object qAbility);

// this function should be called from JS thread when new Want object is received
Q_CORE_EXPORT void dispatchNewWant(QNapi::Object want, QNapi::Object launchParam);

// invokes the task inside the JS thread, can be called from Qt thread at any time
Q_CORE_EXPORT void invokeInJsThread(std::function<void(JsState &)> task);

// Invokes the task inside the JS thread and blocks the caller's thread until
// the "continue" function (std::function<void()>, passed as second argument to
// the task) is called on the JS side.
// It can be called from the Qt thread at any time, calling it from the JS
// thread is illegal.
Q_CORE_EXPORT void invokeInJsThreadAndWaitForContinue(
    std::function<void(JsState &, std::function<void()>)> &&task);

// Runs the task inside the JS thread and waits until its execution ends.
// When called from the JS thread, it calls the task directly. For other threads
// it behaves like a wrapper around the invokeInJsThreadAndWaitForContinue().
Q_CORE_EXPORT void runInJsThreadAndWait(const std::function<void(JsState &)> &task);

// this function should be called once from Qt thread at some point during startup
Q_CORE_EXPORT void initQtThreadState(std::shared_ptr<QOhosQpaFunctions> qpaFunctions);

template<typename Func>
auto evalInJsThread(Func &&func) -> decltype(func(std::declval<JsState &>()));

template<typename T>
T evalInJsThreadWithConsumer(std::function<void(QtOhos::JsState &, std::function<void(T)>)> evalFunc);

// Creates a proxy for the base shared_ptr which uses custom deleter to ensure
// that the underlying shared_ptr is destroyed (synchronously) in the JS thread.
// This ensures that the managed object will be also destroyed in the JS thread,
// as long as the caller passes the only existing shared_ptr instance managing
// that object to the function. This is the caller's responsibility to do this
// (preferably by using a call to some std::shared_ptr<T> factory function as
// the function's argument).
template<typename T>
std::shared_ptr<T> makeProxyWithJsThreadDeleter(std::shared_ptr<T> &&baseSharedPtr);

// invokes the task inside the Qt thread, can be called from any thread at any time
Q_CORE_EXPORT void invokeInQtThread(std::function<void()> task);

// Invokes the task inside the Qt thread and blocks the caller's thread until either:
//  - the "continue" function (std::function<void()>, passed as second argument to
//    the task) is called on the Qt side and returns,
//  - timeout occurs (we don't receive response from the finished task within the
//    specified time limit),
//  - deadlock is detected (both Qt and JS thread use synchronous calls at the same
//    time).
// It can be called from the JS thread. Calling it from the Qt thread is illegal.
//
// Returns true iff the caller receives confirmation about finishing the task within
// the time limit.
//
// Note:
// If the function returns false then:
//   - either the task wasn't started at all (timeout or deadlock)
//   - or the task was still running (and may be still running!) in the Qt thread
// after reaching the timeout.
Q_CORE_EXPORT Q_REQUIRED_RESULT bool tryInvokeInQtThreadAndTryWaitForContinue(
    std::function<void(std::function<void()>)> &&task,
    std::chrono::milliseconds timeout);

Q_CORE_EXPORT QtState &getQtState();

// logs error message with supplied prefix and error details extracted from
// JS error object passed as an argument via CallbackInfo
Q_CORE_EXPORT void logJsCallbackError(const CallbackInfo &cbInfo, const char *errorMessagePrefix);

// Creates JS callback that expects JS error object as a JS argument
// and logs details of the error. The logged message includes provided
// call context string (usually the name of called method).
Q_CORE_EXPORT std::function<void(const CallbackInfo &)> makeErrorLoggingJsCallback(std::string callContext);

template<typename T>
QThreadSafeRef<T> makeQThreadSafeRef(T *obj)
{
    return QThreadSafeRef<T>(obj);
}

template<typename T>
QThreadSafeRef<T>::QThreadSafeRef() = default;

template<typename T>
QThreadSafeRef<T>::QThreadSafeRef(QPointer<T> obj)
    : QObjectThreadSafeRef(obj.data())
{
}

template<typename T>
QThreadSafeRef<T>::QThreadSafeRef(const QThreadSafeRef<T> &other) = default;

template<typename T>
QThreadSafeRef<T> &QThreadSafeRef<T>::operator=(const QThreadSafeRef<T> &other) = default;

template<typename T>
std::string QThreadSafeRef<T>::refName() const
{
    return QObjectThreadSafeRef::refName();
}

template<typename T>
QObjectThreadSafeRef QThreadSafeRef<T>::toQObjectThreadSafeRef() const
{
    return static_cast<const QObjectThreadSafeRef &>(*this);
}

template<typename T>
QPointer<T> QThreadSafeRef<T>::data() const
{
    return static_cast<T *>(
        static_cast<QObject *>(
            QObjectThreadSafeRef::data()));
}

template<typename T>
void QThreadSafeRef<T>::visitInQtThreadIfAlive(std::function<void(T &)> visitFunc) const
{
    QObjectThreadSafeRef::visitInQtThreadIfAlive(
        [visitFunc = std::move(visitFunc)](QObject &obj) {
            visitFunc(static_cast<T &>(obj));
        });
}

template<typename Func>
auto evalInJsThread(Func &&func) -> decltype(func(std::declval<JsState &>()))
{
    using Result = decltype(func(std::declval<JsState &>()));
    static_assert(
        !(std::is_class<Result>::value
            && (
                std::is_convertible<Result, napi_value>::value
                || std::is_convertible<Result, napi_ref>::value)),
        "NAPI values/references must not be accessed outside the JS thread");

    std::unique_ptr<Result> result;
    runInJsThreadAndWait(
        [&](JsState &jsState) {
            result = std::make_unique<Result>(func(jsState));
        });
    return std::move(*result);
}

template<typename T>
T evalInJsThreadWithConsumer(std::function<void(QtOhos::JsState &, std::function<void(T)>)> evalFunc)
{
    static_assert(
        !(std::is_class<T>::value
            && (
                std::is_convertible<T, napi_value>::value
                || std::is_convertible<T, napi_ref>::value)),
        "NAPI values/references must not be accessed outside the JS thread");

    std::unique_ptr<T> result;
    invokeInJsThreadAndWaitForContinue(
        [&](QtOhos::JsState &jsState, std::function<void()> continueFunc) {
            evalFunc(
                jsState,
                [&result, continueFunc = std::move(continueFunc)](T value) {
                    result = std::make_unique<T>(std::move(value));
                    continueFunc();
                });
        });

    return std::move(*result);
}

template<typename T>
std::shared_ptr<T> makeProxyWithJsThreadDeleter(std::shared_ptr<T> &&baseSharedPtr)
{
    auto *baseRawPtr = baseSharedPtr.get();
    return std::shared_ptr<T>(
        baseRawPtr,
        [baseSharedPtr = std::move(baseSharedPtr)](T *) mutable {
            runInJsThreadAndWait(
                [&](JsState &) {
                    baseSharedPtr.reset();
                });
        });
}

template<typename T>
std::enable_if_t<std::is_default_constructible<T>::value, T> &JsState::getAttachedObjectWithLazyCreate()
{
    auto *objectPtr = reinterpret_cast<T *>(
        getAttachedObjectWithLazyCreate(typeid(T), &std::make_shared<T>));
    return *objectPtr;
}

template<typename T>
T JsState::eval(const std::string &expr, const std::vector<QNapi::ValueWrapper> &exprArgs)
{
    using namespace std::string_literals;

    QNapi::Object module;
    std::string subExpr;
    std::tie(module, subExpr) = extractModuleFromEvalExpr(expr);

    return !subExpr.empty()
        ? module.eval<T>(subExpr, exprArgs)
        : QNapi::checkedCast<T>(
            module,
            [&]() {
                return "module '"s + expr + "'"s;
            });
}

template<typename Enum>
QNapi::Number JsState::mapOhosEnumToJs(Enum enumValue)
{
    return mapOhosEnumToJs(static_cast<int>(enumValue), typeid(Enum), &makeOhosEnumInfo<Enum>);
}

template<typename Enum>
QOhosOptional<Enum> JsState::tryMapOhosEnumFromJs(QNapi::Number enumJsValue)
{
    auto intValue = tryMapOhosEnumFromJs(enumJsValue, typeid(Enum), &makeOhosEnumInfo<Enum>);
    return intValue.hasValue()
        ? makeQOhosOptional(static_cast<Enum>(intValue.value()))
        : makeEmptyQOhosOptional();
}

template<typename Enum>
Enum JsState::mapOhosEnumFromJs(QNapi::Number enumJsValue)
{
    return static_cast<Enum>(mapOhosEnumFromJs(enumJsValue, typeid(Enum), &makeOhosEnumInfo<Enum>));
}

template<typename T>
QNapi::Symbol JsState::getJsSymbolForType()
{
    return getJsSymbolForType(typeid(T));
}

template<typename Enum>
JsState::OhosEnumInfo JsState::makeOhosEnumInfo()
{
    static const auto enumEnumeratorsNames = OhosEnumMeta<Enum>::enumeratorsNames;

    std::vector<std::pair<int, const char *>> intEnumeratorsNames;
    std::transform(
        enumEnumeratorsNames.begin(), enumEnumeratorsNames.end(),
        std::back_inserter(intEnumeratorsNames),
        [](const auto &valueNamePair) {
            return std::pair<int, const char *>(static_cast<int>(valueNamePair.first), valueNamePair.second);
        });

    return OhosEnumInfo {
        .fullTypeName = OhosEnumFullTypeNameFetcher<Enum>::fullTypeName(),
        .enumeratorsNames = std::move(intEnumeratorsNames),
    };
}

template<>
struct OhosEnumMeta<QOhosAbilityOnContinueResult>
{
    static constexpr const char *fullTypeName = "@ohos.app.ability.AbilityConstant.OnContinueResult";
    static constexpr std::array<std::pair<QOhosAbilityOnContinueResult, const char *>, 3> enumeratorsNames = {{
        {QOhosAbilityOnContinueResult::AGREE, "AGREE"},
        {QOhosAbilityOnContinueResult::REJECT, "REJECT"},
        {QOhosAbilityOnContinueResult::MISMATCH, "MISMATCH"},
    }};
};

}

QT_END_NAMESPACE

#endif
