
#include <string.h>

#include <vector>

#include "boxed.h"
#include "callback.h"
#include "closure.h"
#include "error.h"
#include "function.h"
#include "gi.h"
#include "gobject.h"
#include "macros.h"
#include "toggle_queue.h"
#include "util.h"
#include "value.h"

using v8::Array;
using v8::BigInt;
using v8::External;
using v8::Function;
using v8::FunctionTemplate;
using v8::Local;
using v8::MaybeLocal;
using v8::Object;
using v8::String;
using Nan::New;
using Nan::Persistent;
using Nan::FunctionCallbackInfo;
using Nan::WeakCallbackType;

#define OFFSET_NOT_FOUND 0xffff

namespace GNodeJS {

// Our base template for all GObjects
static Nan::Persistent<FunctionTemplate> baseTemplate;

// JS callback (registerClass) invoked to lazily register an unregistered JS
// subclass the first time it is constructed. Installed via SetLazyClassRegister,
// this makes registerClass() optional. Empty until JS installs it.
static Nan::Persistent<Function> lazyClassRegister;

// JS callback invoked the first time a private/non-introspectable concrete type
// (eg GLocalFile) is wrapped, to mix its implemented interfaces' methods into
// the class prototype. Installed via SetInterfaceMethodsApplier. See issue #441.
static Nan::Persistent<Function> interfaceMethodsApplier;


static MaybeLocal<FunctionTemplate> GetClassTemplate(GType gtype);
static MaybeLocal<Function>         GetClass(GType gtype);
static void StoreVFunc(GType gtype, Callback *callback);
static void DestroyVFuncs(GType gtype);


static GObject* CreateGObjectFromObject(GType gtype, Local<Value> object) {
    if (!object->IsObject ())
        return (GObject*) g_object_new(gtype, NULL);

    Local<Object> property_hash = TO_OBJECT (object);
    Local<Array> properties = Nan::GetOwnPropertyNames (property_hash).ToLocalChecked();
    int n_properties = properties->Length ();
    const char **names = g_new0 (const char*, n_properties + 1);
    GValue *values = g_new0 (GValue, n_properties);

    void *klass = g_type_class_ref (gtype);
    GObject *gobject = NULL;

    int n_valid_properties = 0;
    int index = 0;

    for (int i = 0; i < n_properties; i++) {
        Local<String> name = TO_STRING (Nan::Get(properties, i).ToLocalChecked());
        // Accept camelCase property names (e.g. iconName) in addition to
        // dashed/underscored ones; GObject canonicalizes '_' to '-' but not
        // camelCase, so convert here (#320). The original spelling is kept so
        // an unknown name is reported as the user wrote it.
        Nan::Utf8String name_original (name);
        char *name_string = Util::ToDashed (*name_original);
        Local<Value> value = Nan::Get(property_hash, name).ToLocalChecked();

        auto value_spec = g_object_class_find_property (G_OBJECT_CLASS (klass), name_string);
        if (value_spec == NULL) {
            Throw::InvalidPropertyName(*name_original);
            g_free(name_string);
            goto out;
        }

        index = n_valid_properties++;

        g_value_init(&values[index], value_spec->value_type);

        if (!V8ToGValue(&values[index], value, kCopy)) {
            // V8ToGValue throws the error
            goto out;
        }

        names[index] = name_string;
    }

    gobject = (GObject*) g_object_new_with_properties(gtype, n_valid_properties, names, values);

out:
    g_strfreev ((gchar**) names);

    for (int i = 0; i < n_properties; i++)
        g_value_unset(&values[i]);
    g_free (values);

    g_type_class_unref (klass);

    return gobject;
}

struct GObjectWrapper;
static void GObjectDestroyedFirstPass(const v8::WeakCallbackInfo<GObjectWrapper> &data);
static void GObjectDestroyedSecondPass(const v8::WeakCallbackInfo<GObjectWrapper> &data);
static void GObjectFinalized(gpointer data, GObject *where_the_object_was);

struct GObjectWrapper {
    Nan::Persistent<Object> persistent;
    GObject *gobject;
    /* Set to true the moment SetWeak is called. Between that point and the
     * destroy callback actually running, the V8 handle is weak (and, once GC
     * reclaims it, dead). If ToggleNotify fires in that window (because native
     * code adjusts the refcount), touching the persistent crashes. Guard every
     * persistent access with this flag. */
    bool dying = false;
    /* Set in the first-pass weak callback, i.e. the instant GC reclaims the
     * wrapper, before any JS/GTK code resumes. While this is true the
     * persistent is dead but the qdata still points here until the second-pass
     * callback runs; WrapperFromGObject must build a fresh wrapper rather than
     * resurrect this one. */
    bool collected = false;
};

/* Reconcile the wrapper's persistent with the state the object's current
 * refcount calls for. Main thread only — called inline by ToggleNotify on the
 * JS thread, and by toggleQueue's drain for deferred off-thread notifications.
 *
 * Weak when the toggle ref is the only reference left: we are the last
 * holder, so the wrapper may be collected. The two-pass weak callback's first
 * pass runs *during* GC (before any JS/GTK code resumes) and only flips a
 * flag, so WrapperFromGObject can tell a reclaimed wrapper from a live one
 * and never marshals a dead handle to JS. All GObject teardown happens in the
 * second pass — a first-pass callback may not call into GObject.
 *
 * Strong otherwise: something other than us holds the object, so the wrapper
 * must stay alive until that ref is dropped again. Reviving is essential —
 * without it a wrapper that went weak once (e.g. a freshly constructed object
 * at refcount 1) would never become strong again when GTK takes ownership,
 * and GC could then collect a wrapper whose GObject is still in use (notably
 * a subclassed widget owned by GTK, losing its overridden vfuncs and instance
 * state). */
void SynchronizeToggleState(GObject *gobject) {
    void *data = g_object_get_qdata (gobject, GNodeJS::object_quark());
    if (data == NULL)
        return;

    auto *wrapper = (GObjectWrapper *) data;

    /* The V8 handle has already been reclaimed by GC (collected) — it is dead
     * and can be made neither weak nor strong. If the object is marshalled
     * again, WrapperFromGObject builds a fresh wrapper. */
    if (wrapper->collected)
        return;

    bool only_toggle_ref =
        g_atomic_int_get ((const gint *) &gobject->ref_count) <= 1;

    if (only_toggle_ref && !wrapper->dying) {
        wrapper->dying = true;
        wrapper->persistent.v8::PersistentBase<Object>::SetWeak (
            wrapper, GObjectDestroyedFirstPass, v8::WeakCallbackType::kParameter);
    } else if (!only_toggle_ref && wrapper->dying) {
        wrapper->dying = false;
        wrapper->persistent.ClearWeak ();
    }
}

/* Fires synchronously on whatever thread crosses the 1<->2 refcount boundary
 * — e.g. GLib's worker thread dropping the ref it held on a GSubprocess while
 * waiting for the child to exit. V8 global handles may only be touched from
 * the JS thread: a SetWeak/ClearWeak from a worker raced the scavenger's
 * weak-handle processing and corrupted the global-handle list (fatal "Check
 * failed: Heap::InFromPage(heap_object)" in a later scavenge, or random
 * SIGSEGV). Off-thread notifications are deferred to the main context; the
 * notified direction is not forwarded because the deferred reconciliation
 * re-derives it from the refcount (see toggle_queue.h). */
static void ToggleNotify(gpointer user_data, GObject *gobject, gboolean toggle_down) {
    if (G_UNLIKELY (g_thread_self () != GNodeJS::js_thread)) {
        toggleQueue.Synchronize (gobject);
        return;
    }

    SynchronizeToggleState (gobject);
}

static void AssociateGObject(Local<Object> object, GObject *gobject, GType gtype) {
    Nan::SetInternalFieldPointer(object, 0, gobject);

    SET_OBJECT_GTYPE(object, gtype);

    auto *wrapper = new GObjectWrapper();
    wrapper->gobject = gobject;
    wrapper->persistent.Reset(object);
    g_object_set_qdata (gobject, GNodeJS::object_quark(), wrapper);

    // Because we can't sink floating ref and add toggle ref at the same time,
    // first sink the floating ref, add the toggle ref, and then release the
    // ref we've just sunken. At the end, we must carry only the toggle ref.
    g_object_ref_sink (gobject);
    g_object_add_toggle_ref (gobject, ToggleNotify, NULL);
    g_object_unref (gobject);

    // The toggle ref above is supposed to keep the GObject alive for as long as
    // the wrapper exists. A weak ref guards against the case where it doesn't —
    // e.g. a JS-subclassed instance whose refcount is driven to 0 from the GTK
    // side while we still hold the toggle ref: GObjectFinalized then clears the
    // dangling pointer so the destroy callbacks never touch freed memory.
    g_object_weak_ref (gobject, GObjectFinalized, wrapper);
}

static void GObjectFinalized(gpointer data, GObject *where_the_object_was) {
    auto *wrapper = (GObjectWrapper *) data;
    wrapper->gobject = NULL;
    /* A deferred off-thread toggle may still be queued for this object; the
     * drain must not touch freed memory. */
    toggleQueue.Cancel (where_the_object_was);
}

static void GObjectConstructor(const FunctionCallbackInfo<Value> &info) {
    /* The flow of this function is a bit twisty.

     * There's two cases for when this code is called:
     * user code doing `new Gtk.Widget({ ... })`, and
     * internal code as part of WrapperFromGObject, where
     * the constructor is called with one external. */

    if (!info.IsConstructCall ()) {
        Nan::ThrowTypeError("Not a construct call.");
        return;
    }

    GObject *gobject;
    GType gtype;
    Local<Object> self = info.This ();

    if (info[0]->IsExternal ()) {
        /* The External case. This is how WrapperFromGObject is called. */
        gobject = G_OBJECT (External::Cast (*info[0])->Value ());
        gtype   = G_OBJECT_TYPE (gobject);
        AssociateGObject(self, gobject, gtype);
        return;
    }

    /* User code calling `new Gtk.Widget({ ... })` */

    // Nan provides Nan::SetPrototype but no GetPrototype wrapper, and V8 14
    // renamed Object::GetPrototype() to GetPrototypeV2().
#if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >= 14
    Local<Object> proto = Nan::To<Object>(self->GetPrototypeV2()).ToLocalChecked();
#else
    Local<Object> proto = Nan::To<Object>(self->GetPrototype()).ToLocalChecked();
#endif

    /* A JS subclass (`class Foo extends Gtk.Widget {}`) that was never passed to
     * registerClass() owns no GType: `__gtype__` is only *inherited* from its
     * nearest registered ancestor, so constructing it as-is would silently
     * instantiate that ancestor — losing the subtype and any vfunc overrides.
     * Detect the missing *own* property and register the subclass on demand,
     * which is what makes registerClass() optional. The JS callback installs an
     * own `__gtype__` on `proto`, so the lookup below resolves to the
     * freshly-registered subtype. */
    if (!lazyClassRegister.IsEmpty()
            && !Nan::HasOwnProperty(proto, UTF8("__gtype__")).FromMaybe(true)) {
        Local<Function> registerFn = Nan::New<Function>(lazyClassRegister);
        Local<Value> klass = Nan::Get(proto, UTF8("constructor")).ToLocalChecked();
        Local<Value> argv[] = { klass };
        Nan::TryCatch tryCatch;
        Nan::Call(registerFn, Nan::GetCurrentContext()->Global(), 1, argv);
        if (tryCatch.HasCaught()) {
            tryCatch.ReThrow();
            return;
        }
    }

    // FIXME: getting the gtype from the External is faster but doesn't
    // work for dynamically-registered types. Check if we can find something
    // better.
    //gtype = (GType) External::Cast(*info.Data())->Value();
    gtype = GET_OBJECT_GTYPE (proto);

    gobject = CreateGObjectFromObject (gtype, info[0]);

    if (gobject == NULL) {
        // Error will already be thrown from CreateGObjectFromObject
        return;
    }

    AssociateGObject(self, gobject, gtype);
    if (G_IS_INITIALLY_UNOWNED(gobject)) {
        // AssociateGObject() has sunken the floating ref.
    } else {
        // AssociateGObject() has added its own ref.
        g_object_unref(gobject);
    }
}

static void GObjectDestroyedFirstPass(const v8::WeakCallbackInfo<GObjectWrapper> &data) {
    GObjectWrapper *wrapper = data.GetParameter ();

    /* This runs *during* GC, where it is not legal to call into V8 (beyond
     * resetting the handle, which the two-pass contract requires) or into
     * GObject — the GObject is not safe to touch here, and doing so crashes in
     * g_type_check_instance_is_fundamentally_a. So only flip a flag and reset
     * the handle; the real teardown is deferred to the second pass.
     *
     * The flag lets WrapperFromGObject distinguish a reclaimed wrapper (whose
     * persistent is now dead) from a live one during the window before the
     * second pass nulls the qdata, so it builds a fresh wrapper instead of
     * handing the dead handle to JS — which crashed on first property access. */
    wrapper->collected = true;
    wrapper->persistent.Reset ();

    data.SetSecondPassCallback (GObjectDestroyedSecondPass);
}

static gboolean GObjectTeardownIdle(gpointer data) {
    GObjectWrapper *wrapper = (GObjectWrapper *) data;
    GObject *gobject = wrapper->gobject;

    /* If the GObject was already finalized out from under us, GObjectFinalized
     * cleared the pointer; there is nothing left to detach or unref. */
    if (gobject != NULL) {
        /* The weak ref that would cancel a queued off-thread toggle on
         * finalize is removed below, so cancel any pending entry now — the
         * toggle ref drop at the end may be the object's last reference. */
        toggleQueue.Cancel (gobject);

        /* Drop the weak ref first so removing the toggle ref (which may finalize
         * the object) doesn't re-enter GObjectFinalized. */
        g_object_weak_unref (gobject, GObjectFinalized, wrapper);

        /* Only detach the qdata if it still points at us — WrapperFromGObject
         * may have resurrected this GObject with a fresh wrapper while we were
         * pending, and we must not clobber it. */
        if (g_object_get_qdata (gobject, GNodeJS::object_quark()) == wrapper)
            g_object_set_qdata (gobject, GNodeJS::object_quark(), NULL);

        /* Dropping the last toggle ref disposes the object, and GTK's dispose
         * synchronously emits signals (e.g. ::destroy) into still-connected
         * node-gtk closures — i.e. it re-enters arbitrary JS. That is only legal
         * here because we run from a GLib idle on the main loop, not from the GC
         * second-pass callback that scheduled us (see GObjectDestroyedSecondPass). */
        g_object_remove_toggle_ref (gobject, &ToggleNotify, NULL);
    }

    delete wrapper;
    return G_SOURCE_REMOVE;
}

static void GObjectDestroyedSecondPass(const v8::WeakCallbackInfo<GObjectWrapper> &data) {
    GObjectWrapper *wrapper = data.GetParameter ();

    /* Defer the actual teardown to a main-loop idle instead of running it here.
     * This callback fires from V8's InvokeSecondPassPhantomCallbacks *during* a
     * garbage collection. Dropping the toggle ref can take the GObject's refcount
     * to zero, and GTK's dispose then emits signals into node-gtk closures,
     * re-entering JS (Nan::Call) — which crashes when invoked mid-GC. Running the
     * teardown from a GLib idle moves the ref drop (and any disposal/signal
     * emission it triggers) to a point where calling into JS is safe again.
     *
     * The GObject stays alive across the window because we still hold the toggle
     * ref; the wrapper (with its now-reset persistent and collected=true) is kept
     * until the idle deletes it. WrapperFromGObject already handles a resurrected
     * GObject during this window by building a fresh wrapper, and the idle's
     * qdata check above won't clobber it. */
    g_idle_add (GObjectTeardownIdle, wrapper);
}

static void GObjectClassDestroyed(const Nan::WeakCallbackInfo<GType> &info) {
    GType* gtypePtr = info.GetParameter();
    GType gtype = *gtypePtr;

    DestroyVFuncs(gtype);

    auto persistentTpl = (Nan::Persistent<FunctionTemplate> *)
        g_type_get_qdata (gtype, GNodeJS::template_quark());
    auto persistentFn  = (Nan::Persistent<Function> *)
        g_type_get_qdata (gtype, GNodeJS::function_quark());
    delete persistentTpl;
    delete persistentFn;

    g_type_set_qdata (gtype, GNodeJS::template_quark(), NULL);
    g_type_set_qdata (gtype, GNodeJS::function_quark(), NULL);
    g_free(gtypePtr);
}

#if defined(V8_MAJOR_VERSION) && (V8_MAJOR_VERSION > 12 || \
    (V8_MAJOR_VERSION == 12 && defined(V8_MINOR_VERSION) && V8_MINOR_VERSION > 4))
#define PROPERTY_CALLBACK_RETURN_TYPE v8::Intercepted
#define PROPERTY_CALLBACK_INFO_TYPE v8::PropertyCallbackInfo<void>
#define PROPERTY_CALLBACK_INFO_VALUE_TYPE void
#define PROPERTY_CALLBACK_IS_INTERCEPTED 1
#else
#define PROPERTY_CALLBACK_RETURN_TYPE void
#define PROPERTY_CALLBACK_INFO_TYPE v8::PropertyCallbackInfo<Value>
#define PROPERTY_CALLBACK_INFO_VALUE_TYPE Value
#define PROPERTY_CALLBACK_IS_INTERCEPTED 0
#endif

static PROPERTY_CALLBACK_RETURN_TYPE
GObjectFallbackPropertyGetter(Local<v8::Name> property,
                              const v8::PropertyCallbackInfo<Value>& info) {
    // V8 14 removed PropertyCallbackInfo::Holder(); the Nan wrapper's Holder()
    // aliases HolderV2() on new V8 and Holder() on older V8. The handler is
    // installed on InstanceTemplate, so the holder is the instance.
    Nan::PropertyCallbackInfo<Value> nanInfo(info, info.Data());
    auto self = nanInfo.Holder();
    GObject *gobject = GObjectFromWrapper (self);

    g_assert(gobject != NULL);

    Nan::Utf8String prop_name_v (TO_STRING (property));
    const char *prop_name_camel = *prop_name_v;

    if (strstr(prop_name_camel, "-")) {
        // Has dash, not a camel-case property name.
        RETURN(Nan::Undefined());
        return Nan::Intercepted::Yes();
    }

    char *prop_name = Util::ToDashed(prop_name_camel);

    auto value = GetGObjectProperty(gobject, prop_name);
    if (!value.IsEmpty()) {
        RETURN(value.ToLocalChecked());
        g_free(prop_name);
        return Nan::Intercepted::Yes();
    }

    g_free(prop_name);
    return Nan::Intercepted::No();
}

static PROPERTY_CALLBACK_RETURN_TYPE
GObjectFallbackPropertySetter(Local<v8::Name> property, Local<Value> value,
                              const PROPERTY_CALLBACK_INFO_TYPE& info) {
    Nan::PropertyCallbackInfo<PROPERTY_CALLBACK_INFO_VALUE_TYPE> nanInfo(info, info.Data());
    auto self = nanInfo.Holder();
    GObject *gobject = GNodeJS::GObjectFromWrapper (self);

    Nan::Utf8String prop_name_v (TO_STRING (property));
    const char *prop_name_camel = *prop_name_v;

    if (strstr(prop_name_camel, "-")) {
        // Has dash, not a camel-case property name.
        return Nan::Intercepted::No();
    }

    char *prop_name = Util::ToDashed(prop_name_camel);

    if (gobject == NULL) {
        WARN("Can't set \"%s\" on null GObject", prop_name);
        g_free(prop_name);
        return Nan::Intercepted::No();
    }

    auto setResult = SetGObjectProperty(gobject, prop_name, value);
    if (setResult.IsEmpty()) {
        // Non-existent property. Let node consider the set not intercepted
        // by not setting return value;
        g_free(prop_name);
        return Nan::Intercepted::No();
    } else {
        // Property exists. Whether we can convert the value and set the
        // property or not, consider the set handled.
#if !PROPERTY_CALLBACK_IS_INTERCEPTED
        // Non-intercepted API (V8 <= 12.4): signal "handled" by setting the
        // return value. Without it V8 falls through and defines a shadowing
        // own-property on the wrapper, masking the interceptor getter (e.g. a
        // 64-bit property would then read back as a Number, not a BigInt).
        RETURN(value);
#endif
        // Intercepted API (V8 > 12.4): the info is <void>, so signal via the
        // Intercepted return value rather than by setting a return value.
        g_free(prop_name);
        return Nan::Intercepted::Yes();
    }
}

static GISignalInfo* FindSignalInfo(GIObjectInfo *info, const char *signal_detail) {
    char* signalName = Util::GetSignalName(signal_detail);

    GISignalInfo *signalInfo = NULL;

    GIBaseInfo *current = g_base_info_ref(info);

    while (current) {
        // Find on GObject
        signalInfo = g_object_info_find_signal (current, signalName);
        if (signalInfo)
            break;

        // Find on Interfaces
        int n_interfaces = g_object_info_get_n_interfaces (current);
        for (int i = 0; i < n_interfaces; i++) {
            GIBaseInfo* interface_info = g_object_info_get_interface (current, i);
            signalInfo = g_interface_info_find_signal (interface_info, signalName);
            g_base_info_unref (interface_info);

            if (signalInfo)
                goto out;
        }

        GIBaseInfo* parent = g_object_info_get_parent(current);
        g_base_info_unref(current);
        current = parent;
    }

out:

    if (current)
        g_base_info_unref(current);

    g_free(signalName);

    return signalInfo;
}

static void StoreVFunc(GType gtype, Callback *callback) {
    auto vfuncList = (GSList*) g_type_get_qdata(gtype, GNodeJS::vfuncs_quark());
    vfuncList = g_slist_prepend(vfuncList, (gpointer) callback);
    g_type_set_qdata(gtype, GNodeJS::vfuncs_quark(), vfuncList);
}

static void DestroyVFuncs(GType gtype) {
    /* Destroy vfunc list, if any */
    GSList *list = (GSList *) g_type_get_qdata (gtype, GNodeJS::vfuncs_quark());
    for (GSList *item = list; item != NULL; item = item->next) {
        auto callback = (Callback *) item->data;
        delete callback;
    }
    g_slist_free (list);
    g_type_set_qdata (gtype, GNodeJS::vfuncs_quark(), NULL);
}

/*
 * Signal handlers are stored in a JS array held on the wrapper object itself
 * (via a private symbol), so they are reachable only through the wrapper and
 * the wrapper <-> handler reference loop can be garbage-collected (#375; see
 * doc/signal-handler-gc.md). A Closure keeps only its index into that array.
 */
static Local<v8::Private> SignalHandlersKey(v8::Isolate *isolate) {
    return v8::Private::ForApi(isolate, Nan::New("__gnodejs_signal_handlers__").ToLocalChecked());
}

// Append a handler to the wrapper's handler array, returning its index.
static guint AddSignalHandler(Local<Object> wrapper, Local<Function> handler) {
    // Object::GetIsolate() was removed in V8 14; use the current isolate.
    v8::Isolate *isolate = v8::Isolate::GetCurrent();
    Local<v8::Context> context = isolate->GetCurrentContext();
    Local<v8::Private> key = SignalHandlersKey(isolate);

    Local<Value> existing = wrapper->GetPrivate(context, key).ToLocalChecked();
    Local<Array> handlers;
    if (existing->IsArray()) {
        handlers = existing.As<Array>();
    } else {
        handlers = Nan::New<Array>();
        wrapper->SetPrivate(context, key, handlers).Check();
    }

    guint index = handlers->Length();
    Nan::Set(handlers, index, handler);
    return index;
}

// Look up a handler by the instance it is connected to and its index. Returns
// an empty handle if the wrapper has been collected or the slot is empty.
Local<Value> GetSignalHandler(GObject *gobject, guint index) {
    void *data = g_object_get_qdata (gobject, GNodeJS::object_quark());
    if (data == NULL)
        return Local<Value>();

    auto *wrapper = (GObjectWrapper *) data;
    if (wrapper->collected)
        return Local<Value>();

    Local<Object> object = Nan::New(wrapper->persistent);
    if (object.IsEmpty())
        return Local<Value>();

    v8::Isolate *isolate = v8::Isolate::GetCurrent();
    Local<v8::Context> context = isolate->GetCurrentContext();
    Local<Value> handlers =
        object->GetPrivate(context, SignalHandlersKey(isolate)).ToLocalChecked();
    if (!handlers->IsArray())
        return Local<Value>();

    return Nan::Get(handlers.As<Array>(), index).ToLocalChecked();
}

NAN_METHOD(SignalConnect) {
    bool after = false;

    GObject *gobject = GObjectFromWrapper (info.This ());

    if (!gobject) {
        Nan::ThrowTypeError("Object is not a GObject");
        return;
    }

    if (!info[0]->IsString()) {
        Nan::ThrowTypeError("Signal ID invalid");
        return;
    }

    if (!info[1]->IsFunction()) {
        Nan::ThrowTypeError("Signal callback is not a function");
        return;
    }

    if (info[2]->IsBoolean()) {
        after = Nan::To<bool>(info[2]).ToChecked();
    }

    Local<Function> callback = info[1].As<Function>();
    GType gtype = GET_OBJECT_GTYPE (info.This());

    GIBaseInfo *object_info = g_irepository_find_by_gtype (NULL, gtype);

    guint signalId;
    GQuark detail;
    GClosure *gclosure;
    guint handlerIndex;
    gulong handler_id;

    // Hold the Utf8String for the whole function: `*Nan::Utf8String(...)` alone
    // dangles after the statement, and AddSignalHandler() below allocates in V8,
    // which would clobber the freed buffer before g_signal_connect_closure.
    Nan::Utf8String signalNameValue (TO_STRING (info[0]));
    const char *signalName = *signalNameValue;
    if (!g_signal_parse_name(signalName, gtype, &signalId, &detail, FALSE)) {
        Nan::ThrowTypeError("Signal name is invalid");
        return;
    }
    GISignalInfo* signal_info = NULL;
    if (object_info) {
        signal_info = FindSignalInfo (object_info, signalName);
        if (signal_info == NULL) {
            Throw::SignalNotFound(object_info, signalName);
            goto out;
        }
    }

    handlerIndex = AddSignalHandler (TO_OBJECT (info.This ()), callback);
    gclosure = Closure::New (handlerIndex, signal_info, signalId);
    handler_id = g_signal_connect_closure (gobject, signalName, gclosure, after);

    info.GetReturnValue().Set((double)handler_id);

out:
    if (signal_info) g_base_info_unref(signal_info);
    if (object_info) g_base_info_unref(object_info);
}

NAN_METHOD(SignalDisconnect) {
    GObject *gobject = GObjectFromWrapper (info.This ());

    if (!gobject) {
        Nan::ThrowTypeError("Object is not a GObject");
        return;
    }

    if (!info[0]->IsNumber()) {
        Nan::ThrowTypeError("Signal ID should be a number");
        return;
    }

    gpointer instance = static_cast<gpointer>(gobject);
    gulong handler_id = TO_LONG (info[0]);
    g_signal_handler_disconnect (instance, handler_id);

    info.GetReturnValue().Set((double)handler_id);
}

NAN_METHOD(SignalEmit) {

    if (!info[0]->IsString()) {
        Nan::ThrowTypeError("Signal name should be a string");
        return;
    }

    Local<Object> self = info.This();
    GObject *gobject = GObjectFromWrapper (self);
    GType gtype = G_OBJECT_TYPE (gobject);

    size_t argc;
    bool failed;

    guint signal_id;
    GQuark detail_id;
    GSignalQuery signal_query;
    GValue rvalue = G_VALUE_INIT;
    GValue* args;

    const char *detailedSignal = *Nan::Utf8String(TO_STRING(info[0]));

    if (!g_signal_parse_name(detailedSignal, gtype, &signal_id, &detail_id, FALSE)) {
        Throw::InvalidSignal(g_type_name(gtype), detailedSignal);
        return;
    }

    g_signal_query(signal_id, &signal_query);

    /*
     * For signals, the instance is an implicit parameter,
     * therefore we add space for 1 more argument.
     */
    argc = signal_query.n_params + 1;

    if ((info.Length() - 1) < (int) signal_query.n_params) {
        Throw::NotEnoughArguments(signal_query.n_params + 1, info.Length());
        return;
    }

    if (signal_query.return_type != G_TYPE_NONE) {
        g_value_init(&rvalue, signal_query.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE);
    }

    args = g_newa(GValue, argc);
    memset(args, 0, sizeof(GValue) * argc);

    g_value_init(&args[0], G_OBJECT_TYPE (gobject));
    g_value_set_object(&args[0], gobject);

    failed = false;
    for (guint i = 0; i < signal_query.n_params; i++) {
        GValue *gvalue = &args[i + 1];

        g_value_init(gvalue, signal_query.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE);

        if ((signal_query.param_types[i] & G_SIGNAL_TYPE_STATIC_SCOPE) != 0)
            failed = !V8ToGValue(gvalue, info[i + 1], kNone); // no-copy
        else
            failed = !V8ToGValue(gvalue, info[i + 1], kCopy); // copy

        if (failed)
            break;
    }

    if (!failed) {
        g_signal_emitv(args, signal_id, detail_id, &rvalue);

        if (signal_query.return_type != G_TYPE_NONE) {
            RETURN (GValueToV8(&rvalue));
            g_value_unset(&rvalue);
        }
    }

    for (guint i = 0; i < argc; i++) {
        g_value_unset(&args[i]);
    }
}

NAN_METHOD(GObjectToString) {
    Local<Object> self = info.This();

    if (!ValueHasInternalField(self)) {
        Nan::ThrowTypeError("Object is not a GObject");
        return;
    }

    GObject* g_object = GObjectFromWrapper(self);
    GType type = G_OBJECT_TYPE (g_object);

    const char* typeName = g_type_name(type);
    char *className = *Nan::Utf8String(self->GetConstructorName());
    void *address = Nan::GetInternalFieldPointer(self, 0);

    char *str = g_strdup_printf("[%s:%s %#zx]", typeName, className, (size_t)address);

    info.GetReturnValue().Set(UTF8(str));
    g_free(str);
}


Local<FunctionTemplate> GetBaseClassTemplate() {
    static bool isBaseClassCreated = false;

    if (!isBaseClassCreated) {
        isBaseClassCreated = true;

        Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>();
        tpl->SetClassName (UTF8("BaseClass"));
        Nan::SetPrototypeMethod(tpl, "connect", SignalConnect);
        Nan::SetPrototypeMethod(tpl, "disconnect", SignalDisconnect);
        Nan::SetPrototypeMethod(tpl, "emit", SignalEmit);
        Nan::SetPrototypeMethod(tpl, "toString", GObjectToString);
        baseTemplate.Reset(tpl);
    }

    // get FunctionTemplate from persistent object
    Local<FunctionTemplate> tpl = Nan::New(baseTemplate);
    return tpl;
}

static MaybeLocal<FunctionTemplate> NewClassTemplate (GType gtype) {
    g_assert(gtype != G_TYPE_NONE && gtype != G_TYPE_INVALID);

    const char *class_name = g_type_name (gtype);

    auto tpl = New<FunctionTemplate> (GObjectConstructor, New<External>((void *) gtype));
    tpl->SetClassName (UTF8(class_name));
    tpl->InstanceTemplate()->SetInternalFieldCount(1);
    Nan::SetPrototypeTemplate(
        tpl, "__gtype__", v8::BigInt::NewFromUnsigned(v8::Isolate::GetCurrent(), gtype));

    GType parent_type = g_type_parent(gtype);
    if (parent_type == G_TYPE_INVALID) {
        tpl->Inherit(GetBaseClassTemplate());
    } else {
        auto parent_tpl = GetClassTemplate(parent_type);
        if (parent_tpl.IsEmpty())
            return MaybeLocal<FunctionTemplate> ();
        tpl->Inherit(parent_tpl.ToLocalChecked());
    }

    // Set the fallback accessor to allow non-introspected property.
    // Nan::SetNamedPropertyHandler() does not support flags. Thus, using
    // V8 interface directly.
    v8::NamedPropertyHandlerConfiguration config(GObjectFallbackPropertyGetter,
        GObjectFallbackPropertySetter);
    config.flags = static_cast<v8::PropertyHandlerFlags>(
        static_cast<int>(v8::PropertyHandlerFlags::kNonMasking) |
        static_cast<int>(v8::PropertyHandlerFlags::kOnlyInterceptStrings));
    tpl->InstanceTemplate()->SetHandler(config);

    return MaybeLocal<FunctionTemplate> (tpl);
}

/*
 * Mix the methods of a type's implemented interfaces into its class prototype.
 *
 * Introspectable object types get this for free: makeObject() in JS iterates
 * their interfaces and installs the methods. Private/non-introspectable concrete
 * types (eg GLocalFile, which implements the public GFile interface) are never
 * seen by makeObject(), so their instances would only expose the base GObject
 * methods. Here we enumerate the type's introspectable interfaces and hand them
 * to the JS `interfaceMethodsApplier`, which reuses makeInterface()/define() to
 * install the methods (and property accessors) on the class prototype.
 *
 * See issue #441.
 */
static void ApplyInterfaceMethods(Local<Function> constructor, GType gtype) {
    if (interfaceMethodsApplier.IsEmpty())
        return;

    guint n_interfaces = 0;
    GType *interfaces = g_type_interfaces(gtype, &n_interfaces);

    Local<Array> refs = New<Array>();
    uint32_t count = 0;
    for (guint i = 0; i < n_interfaces; i++) {
        GIBaseInfo *iface_info = g_irepository_find_by_gtype(NULL, interfaces[i]);
        if (iface_info == NULL)
            continue;
        if (g_base_info_get_type(iface_info) == GI_INFO_TYPE_INTERFACE) {
            Local<Object> ref = New<Object>();
            Nan::Set(ref, UTF8("namespace"), UTF8(g_base_info_get_namespace(iface_info)));
            Nan::Set(ref, UTF8("name"),      UTF8(g_base_info_get_name(iface_info)));
            Nan::Set(refs, count++, ref);
        }
        g_base_info_unref(iface_info);
    }
    g_free(interfaces);

    if (count == 0)
        return;

    Local<Function> applier = New<Function>(interfaceMethodsApplier);
    Local<Value> argv[] = { constructor, refs };
    Nan::TryCatch tryCatch;
    Nan::Call(applier, Nan::GetCurrentContext()->Global(), 2, argv);
    if (tryCatch.HasCaught()) {
        Nan::Utf8String message(tryCatch.Exception());
        g_warning("node-gtk: could not apply interface methods for %s: %s",
                  g_type_name(gtype), *message);
    }
}

NAN_METHOD(SetInterfaceMethodsApplier) {
    interfaceMethodsApplier.Reset(info[0].As<Function>());
}

static MaybeLocal<FunctionTemplate> GetClassTemplate(GType gtype) {
    void *data = g_type_get_qdata (gtype, GNodeJS::template_quark());

    if (data) {
        auto *persistent = (Nan::Persistent<FunctionTemplate> *) data;
        auto tpl = New<FunctionTemplate> (*persistent);
        return tpl;
    }

    auto maybeTpl = NewClassTemplate(gtype);
    if (maybeTpl.IsEmpty())
        return MaybeLocal<FunctionTemplate> ();

    /* NewClassTemplate() runs JS while building the parent chain (each
     * ancestor's template fires the type materializer below), so JS may have
     * re-entered here and created this very template already. Keep that one:
     * its function is the one JS has started decorating. */
    data = g_type_get_qdata (gtype, GNodeJS::template_quark());
    if (data) {
        auto *persistent = (Nan::Persistent<FunctionTemplate> *) data;
        auto existingTpl = New<FunctionTemplate> (*persistent);
        return existingTpl;
    }

    auto tpl = maybeTpl.ToLocalChecked();
    auto fn = Nan::GetFunction (tpl).ToLocalChecked();
    auto persistentTpl = new Nan::Persistent<FunctionTemplate>(tpl);
    auto persistentFn  = new Nan::Persistent<Function>(fn);

    GType *gtypePtr = g_new(GType, 1);
    *gtypePtr = gtype;

    persistentTpl->SetWeak(
        gtypePtr, GObjectClassDestroyed, WeakCallbackType::kParameter);

    g_type_set_qdata(gtype, GNodeJS::template_quark(), persistentTpl);
    g_type_set_qdata(gtype, GNodeJS::function_quark(), persistentFn);

    // Introspectable object types have their methods/properties installed by
    // makeObject() in JS. Modules hold lazy accessors, so a type reached from
    // C first (method return value, signal argument) must be materialized here
    // or its wrappers would expose a bare prototype. Private concrete types
    // are invisible to makeObject(), so mix in their interface methods
    // instead (issue #441).
    GIBaseInfo *own_info = g_irepository_find_by_gtype(NULL, gtype);
    if (own_info == NULL) {
        ApplyInterfaceMethods(fn, gtype);
    } else {
        GNodeJS::MaterializeType(own_info);
        g_base_info_unref(own_info);
    }

    return MaybeLocal<FunctionTemplate> (tpl);
}

static MaybeLocal<Function> GetClass(GType gtype) {
    void *data = g_type_get_qdata (gtype, GNodeJS::function_quark());

    if (data) {
        auto persistent = (Nan::Persistent<Function> *) data;
        auto fn = New<Function> (*persistent);
        return MaybeLocal<Function> (fn);
    }

    /* GetClassTemplate() will initalize function_quark */
    auto maybeTpl = GetClassTemplate(gtype);
    if (maybeTpl.IsEmpty()) {
        ERROR("Failed initialization of function %s", g_type_name(gtype));
        return MaybeLocal<Function> ();
    }

    data = g_type_get_qdata (gtype, GNodeJS::function_quark());

    if (data) {
        auto persistent = (Nan::Persistent<Function> *) data;
        auto fn = New<Function> (*persistent);
        return MaybeLocal<Function> (fn);
    }

    ERROR("Could not retrieve function %s", g_type_name(gtype));
    return MaybeLocal<Function> ();
}

MaybeLocal<Function> MakeClass(GIBaseInfo *info) {
    GType gtype = g_registered_type_info_get_g_type ((GIRegisteredTypeInfo *) info);

    if (gtype == G_TYPE_NONE || gtype == G_TYPE_INVALID) {
        const char *error = g_module_error();
        Throw::GTypeNotFound(info, error);
        return MaybeLocal<Function>();
    }

    return GetClass(gtype);
}

Local<Value> WrapperFromGObject(GObject *gobject) {
    if (gobject == NULL)
        return Nan::Null();

    void *data = g_object_get_qdata (gobject, GNodeJS::object_quark());

    if (data) {
        auto *wrapper = (GObjectWrapper *) data;
        /* Reuse the existing wrapper unless GC has already reclaimed it (its
         * persistent is dead and only awaiting the second-pass teardown). In
         * that case fall through and build a fresh wrapper; the stale one's
         * second pass is guarded so it won't clobber the new qdata. */
        if (!wrapper->collected) {
            auto obj = New<Object> (wrapper->persistent);
            return obj;
        }
    }

    GType gtype = G_OBJECT_TYPE(gobject);
    auto maybeFn = GetClass(gtype);
    if (maybeFn.IsEmpty())
        return Nan::Null();

    Local<Function> constructor = maybeFn.ToLocalChecked();
    Local<Value> gobject_external = New<External> (gobject);
    Local<Value> args[] = { gobject_external };
    Local<Object> obj = Nan::NewInstance(constructor, 1, args).ToLocalChecked();

    return obj;
}

GObject * GObjectFromWrapper(Local<Value> value) {
    if (!ValueHasInternalField(value))
        return nullptr;

    Local<Object> object = TO_OBJECT (value);

    void    *ptr     = Nan::GetInternalFieldPointer(object, 0);
    GObject *gobject = G_OBJECT (ptr);
    return gobject;
}

MaybeLocal<Value> GetGObjectProperty(GObject * gobject, const char *prop_name) {
    GParamSpec *pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (gobject), prop_name);

    if (pspec == NULL) {
        return MaybeLocal<Value>();
    }

    GValue value = G_VALUE_INIT;
    g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
    g_object_get_property (gobject, prop_name, &value);

    auto ret = GNodeJS::GValueToV8(&value, kCopy);

    g_value_unset(&value);

    return MaybeLocal<Value>(ret);
}

MaybeLocal<v8::Boolean> SetGObjectProperty(GObject * gobject, const char *prop_name, Local<Value> value) {
    GParamSpec *pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (gobject), prop_name);

    if (pspec == NULL) {
        return MaybeLocal<v8::Boolean>();
    }

    Local<v8::Boolean> ret;

    GValue gvalue = G_VALUE_INIT;
    g_value_init(&gvalue, G_PARAM_SPEC_VALUE_TYPE (pspec));

    if (GNodeJS::V8ToGValue (&gvalue, value, kCopy)) {
        g_object_set_property (gobject, prop_name, &gvalue);
        ret = Nan::True();
    } else {
        ret = Nan::False();
    }

    g_value_unset(&gvalue);

    return MaybeLocal<v8::Boolean>(ret);
}

namespace ObjectClass {

static GObject* ClassConstructor(
    GType type, unsigned n_construct_properties,
    GObjectConstructParam* construct_properties) {

    /* FIXME: handle case where object is not constructed from
     * JS (eg Gtk.Builder) */

    /* The object is being constructed from JS:
     * Simply chain up to the first non-gjs constructor */
    GType parent_type = g_type_parent(type);

    while (G_OBJECT_CLASS(g_type_class_peek(parent_type))->constructor == ClassConstructor)
        parent_type = g_type_parent(parent_type);

    return G_OBJECT_CLASS(g_type_class_peek(parent_type))
        ->constructor(type, n_construct_properties, construct_properties);
}

static void ClassSetProperty(GObject* object, unsigned id, const GValue* value, GParamSpec* pspec) {}
static void ClassGetProperty(GObject* object, unsigned id, GValue* value, GParamSpec* pspec) {}

static void ClassInit(void* klass_pointer, void* data) {
    GObjectClass* klass = G_OBJECT_CLASS(klass_pointer);
    GType gtype = G_OBJECT_CLASS_TYPE(klass);

    klass->constructor = ClassConstructor;
    // klass->set_property = ClassSetProperty;
    // klass->get_property = ClassGetProperty;
}

constexpr GTypeFlags gobject_class_flags = (GTypeFlags)0;
constexpr GTypeInfo gobject_class_info = {
    /* interface types, classed types, instantiated types */
    0, // guint16                class_size;

    nullptr, // GBaseInitFunc          base_init;
    nullptr, // GBaseFinalizeFunc      base_finalize;

    /* interface types, classed types, instantiated types */
    ClassInit, // GClassInitFunc         class_init;
    GClassFinalizeFunc(nullptr), // GClassFinalizeFunc     class_finalize;
    nullptr,  // gconstpointer          class_data;

    /* instantiated types */
    0,       // guint16                instance_size;
    0,       // guint16                n_preallocs;
    nullptr, // GInstanceInitFunc      instance_init;

    /* value handling */
    nullptr, // const GTypeValueTable *value_table;
};

static void TypeQuerySafe(GType type, GTypeQuery* query) {
    while (g_type_get_qdata(type, GNodeJS::dynamic_type_quark()))
        type = g_type_parent(type);
    g_type_query(type, query);
}

static bool FindVFuncInfo(GType implementor_gtype,
                            GIBaseInfo* info, const char* name,
                            void** vtable,
                            GIFieldInfo** fieldInfoOut) {
    int i, length;
    bool result = false;

    *vtable = NULL;
    *fieldInfoOut = NULL;

    auto ancestorInfo = BaseInfo(g_base_info_get_container(info));
    auto ancestorGType = g_registered_type_info_get_g_type(*ancestorInfo);

    auto implementor_class = (GTypeInstance*) g_type_class_ref(implementor_gtype);
    BaseInfo structInfo;

    if (ancestorInfo.is(GI_INFO_TYPE_INTERFACE)) {
        auto implementor_iface_class =
            (GTypeInstance*) g_type_interface_peek(implementor_class,
                                                        ancestorGType);
        if (implementor_iface_class == NULL) {
            Nan::ThrowError("Couldn't find GType of implementor of interface.");
            result = false;
            goto out;
        }

        *vtable = implementor_iface_class;
        structInfo = g_interface_info_get_iface_struct(*ancestorInfo);
    } else {
        *vtable = implementor_class;
        structInfo = g_object_info_get_class_struct(*ancestorInfo);
    }

    length = g_struct_info_get_n_fields(*structInfo);
    for (i = 0; i < length; i++) {
        BaseInfo fieldInfo = g_struct_info_get_field(*structInfo, i);
        if (strcmp(fieldInfo.name(), name) != 0)
            continue;

        BaseInfo typeInfo = g_field_info_get_type(*fieldInfo);
        if (typeInfo.tag() != GI_TYPE_TAG_INTERFACE) {
            /* We have a field with the same name, but it's not a callback.
             * There's no hope of being another field with a correct name,
             * so just abort early. */
            result = true;
            goto out;
        } else {
            *fieldInfoOut = fieldInfo.ref();
            result = true;
            goto out;
        }
    }

out:
    g_type_class_unref(implementor_class);
    return result;
}


NAN_METHOD(SetLazyClassRegister) {
    lazyClassRegister.Reset(info[0].As<Function>());
}

NAN_METHOD(RegisterClass) {
    auto jsKlassName  = Nan::To<String>(info[0]).ToLocalChecked();
    auto jsKlass      = info[1].As<Object>();
    auto jsParentName = Nan::To<String>(info[2]).ToLocalChecked();
    auto jsParent     = info[3].As<Object>();

    Nan::Utf8String utf8KlassName(jsKlassName);
    Nan::Utf8String utf8ParentName(jsParentName);
    auto parentType = g_type_from_name(*utf8ParentName);

    GTypeQuery query;
    TypeQuerySafe(parentType, &query);
    if (query.type == 0) {
        Nan::ThrowError("Failed to initialize type query");
        return;
    }

    GTypeFlags typeFlags = gobject_class_flags;
    GTypeInfo typeInfo = gobject_class_info;
    typeInfo.class_size = query.class_size;
    typeInfo.instance_size = query.instance_size;

    GType instanceType = g_type_register_static(
        parentType, *utf8KlassName, &typeInfo, typeFlags);

    g_type_set_qdata(instanceType, GNodeJS::dynamic_type_quark(), GINT_TO_POINTER(1));

    // FIXME: need to link klass destruction to GObjectClassDestroyed

    RETURN(v8::BigInt::NewFromUnsigned(Isolate::GetCurrent(), instanceType));
}

NAN_METHOD(RegisterVFunc) {
    auto jsVFuncInfo  = info[0].As<Object>();
    auto jsKlassGType = info[1].As<BigInt>();
    auto jsName       = info[2].As<String>();
    auto jsFunction   = info[3].As<Function>();

    Nan::Utf8String utf8Name(jsName);

    GType klassGType = jsKlassGType->Uint64Value();

    BaseInfo vfuncInfo(jsVFuncInfo);

    void *implementor_vtable;
    BaseInfo fieldInfo;
    if (!FindVFuncInfo(klassGType, *vfuncInfo, *utf8Name,
            &implementor_vtable, &fieldInfo))
        return;

    if (fieldInfo.isEmpty())
        return;

    auto offset = g_field_info_get_offset(*fieldInfo);

    /* Abort if vfunc offset not found */
    if (offset == OFFSET_NOT_FOUND)
        ERROR("Virtual function offset not found (%s.%s)",
                g_type_name(klassGType), vfuncInfo.name());

    auto functionPtr = G_STRUCT_MEMBER_P(implementor_vtable, offset);
    auto callback = new Callback(jsFunction, *vfuncInfo, GI_SCOPE_TYPE_NOTIFIED);
    StoreVFunc(klassGType, callback);

    *reinterpret_cast<ffi_closure**>(functionPtr) = callback->closure;

    RETURN(true);
    return;
}

/*
 * Invoke a parent class's implementation of a vfunc — the native half of
 * `super.<vfunc>(...)`. JS args: (vfuncInfo, implementorGType, instance, argsArray).
 *
 * `g_vfunc_info_invoke` resolves the vfunc through `implementorGType`'s class
 * vtable, so passing the *parent* GType runs the parent's implementation rather
 * than the overriding subclass's (which is what `super` means). The instance is
 * passed as in-arg 0, the JS args follow.
 *
 * Scope: in-only arguments + (void or simple) return value. Out/inout arguments
 * are rejected rather than silently mishandled.
 */
NAN_METHOD(CallVFunc) {
    auto jsVFuncInfo  = info[0].As<Object>();
    auto jsImplGType  = info[1].As<BigInt>();
    auto jsInstance   = info[2];
    auto jsArgs       = info[3].As<Array>();

    BaseInfo vfuncInfo(jsVFuncInfo);
    GType implementor = jsImplGType->Uint64Value();

    int n_callable = g_callable_info_get_n_args(*vfuncInfo);

    GObject *instance = GObjectFromWrapper(jsInstance);
    if (instance == NULL) {
        // The wrapper has no associated GObject yet — e.g. chaining up to a
        // construction-time vfunc (`constructed`), which fires inside g_object_new
        // before node-gtk associates the JS wrapper with the GObject. There is no
        // valid instance to invoke the parent on; fail loudly instead of crashing.
        Throw::Error("Cannot chain up to parent vfunc '%s': instance has no GObject yet "
                "(chaining up during construction is unsupported)",
                g_base_info_get_name(*vfuncInfo));
        return;
    }

    std::vector<GIArgument> in_args(n_callable + 1);
    in_args[0].v_pointer = instance;

    for (int i = 0; i < n_callable; i++) {
        GIArgInfo arg_info;
        GITypeInfo arg_type;
        g_callable_info_load_arg(*vfuncInfo, i, &arg_info);
        g_arg_info_load_type(&arg_info, &arg_type);

        if (g_arg_info_get_direction(&arg_info) != GI_DIRECTION_IN) {
            Throw::Error("Cannot chain up to parent vfunc '%s': out/inout argument %d is unsupported",
                    g_base_info_get_name(*vfuncInfo), i);
            return;
        }

        Local<Value> value = Nan::Get(jsArgs, i).ToLocalChecked();
        bool may_be_null = g_arg_info_may_be_null(&arg_info);
        V8ToGIArgument(&arg_type, &in_args[i + 1], value, may_be_null);
    }

    GIArgument return_value = {};
    GError *error = NULL;
    gboolean ok = g_vfunc_info_invoke(*vfuncInfo, implementor,
            in_args.data(), n_callable + 1, NULL, 0, &return_value, &error);

    if (!ok) {
        Throw::GError("Failed to chain up to parent vfunc", error);
        return;
    }

    GITypeInfo return_type;
    g_callable_info_load_return_type(*vfuncInfo, &return_type);
    if (g_type_info_get_tag(&return_type) != GI_TYPE_TAG_VOID) {
        info.GetReturnValue().Set(GIArgumentToV8(&return_type, &return_value));
    }
}

};

};
