/* ide-extension-set-adapter.c
 *
 * Copyright 2015-2019 Christian Hergert <christian@hergert.me>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

#define G_LOG_DOMAIN "ide-extension-set-adapter"

#include "config.h"

#include <dazzle.h>
#include <glib/gi18n.h>
#include <stdlib.h>

#include "ide-extension-set-adapter.h"
#include "ide-extension-util-private.h"

struct _IdeExtensionSetAdapter
{
  IdeObject   parent_instance;

  PeasEngine *engine;
  gchar      *key;
  gchar      *value;
  GHashTable *extensions;
  GPtrArray  *settings;

  GType       interface_type;

  guint       reload_handler;
};

G_DEFINE_FINAL_TYPE (IdeExtensionSetAdapter, ide_extension_set_adapter, IDE_TYPE_OBJECT)

enum {
  EXTENSIONS_LOADED,
  EXTENSION_ADDED,
  EXTENSION_REMOVED,
  LAST_SIGNAL
};

enum {
  PROP_0,
  PROP_ENGINE,
  PROP_INTERFACE_TYPE,
  PROP_KEY,
  PROP_VALUE,
  LAST_PROP
};

static GParamSpec *properties [LAST_PROP];
static guint signals [LAST_SIGNAL];

static void ide_extension_set_adapter_queue_reload (IdeExtensionSetAdapter *);

static gchar *
ide_extension_set_adapter_repr (IdeObject *object)
{
  IdeExtensionSetAdapter *self = (IdeExtensionSetAdapter *)object;

  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));

  return g_strdup_printf ("%s interface=\"%s\" key=\"%s\" value=\"%s\"",
                          G_OBJECT_TYPE_NAME (self),
                          g_type_name (self->interface_type),
                          self->key ?: "",
                          self->value ?: "");
}

static void
add_extension (IdeExtensionSetAdapter *self,
               PeasPluginInfo         *plugin_info,
               PeasExtension          *exten)
{
  g_assert (IDE_IS_MAIN_THREAD ());
  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
  g_assert (plugin_info != NULL);
  g_assert (exten != NULL);
  g_assert (g_type_is_a (G_OBJECT_TYPE (exten), self->interface_type));

  g_hash_table_insert (self->extensions, plugin_info, exten);

  /* Ensure that we take the reference in case it's a floating ref */
  if (G_IS_INITIALLY_UNOWNED (exten) && g_object_is_floating (exten))
    g_object_ref_sink (exten);

  /*
   * If the plugin object turned out to have IdeObject as a
   * base, make it a child of ourselves, because we're an
   * IdeObject too and that gives it access to the context.
   */
  if (IDE_IS_OBJECT (exten))
    ide_object_append (IDE_OBJECT (self), IDE_OBJECT (exten));

  g_signal_emit (self, signals [EXTENSION_ADDED], 0, plugin_info, exten);
}

static void
remove_extension (IdeExtensionSetAdapter *self,
                  PeasPluginInfo         *plugin_info,
                  PeasExtension          *exten)
{
  g_autoptr(GObject) hold = NULL;

  g_assert (IDE_IS_MAIN_THREAD ());
  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
  g_assert (plugin_info != NULL);
  g_assert (exten != NULL);
  g_assert (self->interface_type == G_TYPE_INVALID ||
            g_type_is_a (G_OBJECT_TYPE (exten), self->interface_type));

  hold = g_object_ref (exten);

  g_hash_table_remove (self->extensions, plugin_info);
  g_signal_emit (self, signals [EXTENSION_REMOVED], 0, plugin_info, hold);

  if (IDE_IS_OBJECT (hold))
    ide_object_destroy (IDE_OBJECT (hold));
}

static void
ide_extension_set_adapter_enabled_changed (IdeExtensionSetAdapter *self,
                                           const gchar            *key,
                                           GSettings              *settings)
{
  g_assert (IDE_IS_MAIN_THREAD ());
  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
  g_assert (key != NULL);
  g_assert (G_IS_SETTINGS (settings));

  ide_extension_set_adapter_queue_reload (self);
}

static void
watch_extension (IdeExtensionSetAdapter *self,
                 PeasPluginInfo         *plugin_info,
                 GType                   interface_type)
{
  g_autoptr(GSettings) settings = NULL;
  g_autofree char *path = NULL;

  g_assert (IDE_IS_MAIN_THREAD ());
  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
  g_assert (plugin_info != NULL);
  g_assert (G_TYPE_IS_INTERFACE (interface_type) || G_TYPE_IS_OBJECT (interface_type));

  path = g_strdup_printf ("/org/gnome/builder/extension-types/%s/%s/",
                          peas_plugin_info_get_module_name (plugin_info),
                          g_type_name (interface_type));
  settings = g_settings_new_with_path ("org.gnome.builder.extension-type", path);

  g_ptr_array_add (self->settings, g_object_ref (settings));

  /* We have to fetch the key once to get changed events */
  g_settings_get_boolean (settings, "enabled");

  g_signal_connect_object (settings,
                           "changed::enabled",
                           G_CALLBACK (ide_extension_set_adapter_enabled_changed),
                           self,
                           G_CONNECT_SWAPPED);
}

static void
ide_extension_set_adapter_reload (IdeExtensionSetAdapter *self)
{
  const GList *plugins;

  g_assert (IDE_IS_MAIN_THREAD ());
  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
  g_assert (self->interface_type != G_TYPE_INVALID);

  while (self->settings->len > 0)
    {
      GSettings *settings;

      settings = g_ptr_array_index (self->settings, self->settings->len - 1);
      g_signal_handlers_disconnect_by_func (settings,
                                            ide_extension_set_adapter_enabled_changed,
                                            self);
      g_ptr_array_remove_index (self->settings, self->settings->len - 1);
    }

  plugins = peas_engine_get_plugin_list (self->engine);

  for (; plugins; plugins = plugins->next)
    {
      PeasPluginInfo *plugin_info = plugins->data;
      gint priority;

      if (!peas_plugin_info_is_loaded (plugin_info))
        continue;

      if (!peas_engine_provides_extension (self->engine, plugin_info, self->interface_type))
        continue;

      watch_extension (self, plugin_info, self->interface_type);

      if (ide_extension_util_can_use_plugin (self->engine,
                                             plugin_info,
                                             self->interface_type,
                                             self->key,
                                             self->value,
                                             &priority))
        {
          if (!g_hash_table_contains (self->extensions, plugin_info))
            {
              PeasExtension *exten;

              exten = ide_extension_new (self->engine,
                                         plugin_info,
                                         self->interface_type,
                                         NULL);

              add_extension (self, plugin_info, exten);
            }
        }
      else
        {
          PeasExtension *exten;

          if ((exten = g_hash_table_lookup (self->extensions, plugin_info)))
            remove_extension (self, plugin_info, exten);
        }
    }

  g_signal_emit (self, signals [EXTENSIONS_LOADED], 0);
}

static gboolean
ide_extension_set_adapter_do_reload (gpointer data)
{
  IdeExtensionSetAdapter *self = data;

  g_assert (IDE_IS_MAIN_THREAD ());
  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));

  self->reload_handler = 0;

  if (self->interface_type != G_TYPE_INVALID)
    ide_extension_set_adapter_reload (self);

  return G_SOURCE_REMOVE;
}

static void
ide_extension_set_adapter_queue_reload (IdeExtensionSetAdapter *self)
{
  g_assert (IDE_IS_MAIN_THREAD ());
  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));

  g_clear_handle_id (&self->reload_handler, g_source_remove);

  self->reload_handler = g_idle_add_full (G_PRIORITY_HIGH,
                                          ide_extension_set_adapter_do_reload,
                                          self,
                                          NULL);
}

static void
ide_extension_set_adapter_load_plugin (IdeExtensionSetAdapter *self,
                                       PeasPluginInfo         *plugin_info,
                                       PeasEngine             *engine)
{
  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
  g_assert (plugin_info != NULL);
  g_assert (PEAS_IS_ENGINE (engine));

  ide_extension_set_adapter_queue_reload (self);
}

static void
ide_extension_set_adapter_unload_plugin (IdeExtensionSetAdapter *self,
                                         PeasPluginInfo         *plugin_info,
                                         PeasEngine             *engine)
{
  PeasExtension *exten;

  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
  g_assert (plugin_info != NULL);
  g_assert (PEAS_IS_ENGINE (engine));

  if ((exten = g_hash_table_lookup (self->extensions, plugin_info)))
    {
      remove_extension (self, plugin_info, exten);
      g_hash_table_remove (self->extensions, plugin_info);
    }
}

static void
ide_extension_set_adapter_set_engine (IdeExtensionSetAdapter *self,
                                      PeasEngine             *engine)
{
  g_assert (IDE_IS_MAIN_THREAD ());
  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
  g_assert (!engine || PEAS_IS_ENGINE (engine));

  if (engine == NULL)
    engine = peas_engine_get_default ();

  if (g_set_object (&self->engine, engine))
    {
      g_signal_connect_object (self->engine, "load-plugin",
                               G_CALLBACK (ide_extension_set_adapter_load_plugin),
                               self,
                               G_CONNECT_AFTER | G_CONNECT_SWAPPED);
      g_signal_connect_object (self->engine, "unload-plugin",
                               G_CALLBACK (ide_extension_set_adapter_unload_plugin),
                               self,
                               G_CONNECT_SWAPPED);
      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ENGINE]);
      ide_extension_set_adapter_queue_reload (self);
    }
}

static void
ide_extension_set_adapter_set_interface_type (IdeExtensionSetAdapter *self,
                                              GType                   interface_type)
{
  g_assert (IDE_IS_MAIN_THREAD ());
  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));
  g_assert (G_TYPE_IS_INTERFACE (interface_type) || G_TYPE_IS_OBJECT (interface_type));

  if (interface_type != self->interface_type)
    {
      self->interface_type = interface_type;
      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_INTERFACE_TYPE]);
      ide_extension_set_adapter_queue_reload (self);
    }
}

static void
ide_extension_set_adapter_destroy (IdeObject *object)
{
  IdeExtensionSetAdapter *self = (IdeExtensionSetAdapter *)object;
  g_autoptr(GHashTable) extensions = NULL;
  GHashTableIter iter;
  gpointer key;
  gpointer value;

  g_assert (IDE_IS_MAIN_THREAD ());
  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (self));

  self->interface_type = G_TYPE_INVALID;
  g_clear_handle_id (&self->reload_handler, g_source_remove);

  /*
   * Steal the extensions so we can be re-entrant safe and not break
   * any assumptions about extensions being a real pointer.
   */
  extensions = g_steal_pointer (&self->extensions);
  self->extensions = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);

  g_hash_table_iter_init (&iter, extensions);

  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      PeasPluginInfo *plugin_info = key;
      PeasExtension *exten = value;

      remove_extension (self, plugin_info, exten);
      g_hash_table_iter_remove (&iter);
    }

  IDE_OBJECT_CLASS (ide_extension_set_adapter_parent_class)->destroy (object);
}

static void
ide_extension_set_adapter_finalize (GObject *object)
{
  IdeExtensionSetAdapter *self = (IdeExtensionSetAdapter *)object;

  while (self->settings->len > 0)
    {
      guint i = self->settings->len - 1;
      GSettings *settings = g_ptr_array_index (self->settings, i);

      g_signal_handlers_disconnect_by_func (settings,
                                            ide_extension_set_adapter_enabled_changed,
                                            self);
      g_ptr_array_remove_index (self->settings, i);
    }

  g_clear_object (&self->engine);
  g_clear_pointer (&self->key, g_free);
  g_clear_pointer (&self->value, g_free);
  g_clear_pointer (&self->extensions, g_hash_table_unref);
  g_clear_pointer (&self->settings, g_ptr_array_unref);

  G_OBJECT_CLASS (ide_extension_set_adapter_parent_class)->finalize (object);
}

static void
ide_extension_set_adapter_get_property (GObject    *object,
                                        guint       prop_id,
                                        GValue     *value,
                                        GParamSpec *pspec)
{
  IdeExtensionSetAdapter *self = IDE_EXTENSION_SET_ADAPTER (object);

  switch (prop_id)
    {
    case PROP_ENGINE:
      g_value_set_object (value, ide_extension_set_adapter_get_engine (self));
      break;

    case PROP_INTERFACE_TYPE:
      g_value_set_gtype (value, ide_extension_set_adapter_get_interface_type (self));
      break;

    case PROP_KEY:
      g_value_set_string (value, ide_extension_set_adapter_get_key (self));
      break;

    case PROP_VALUE:
      g_value_set_string (value, ide_extension_set_adapter_get_value (self));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
ide_extension_set_adapter_set_property (GObject      *object,
                                        guint         prop_id,
                                        const GValue *value,
                                        GParamSpec   *pspec)
{
  IdeExtensionSetAdapter *self = IDE_EXTENSION_SET_ADAPTER (object);

  switch (prop_id)
    {
    case PROP_ENGINE:
      ide_extension_set_adapter_set_engine (self, g_value_get_object (value));
      break;

    case PROP_INTERFACE_TYPE:
      ide_extension_set_adapter_set_interface_type (self, g_value_get_gtype (value));
      break;

    case PROP_KEY:
      ide_extension_set_adapter_set_key (self, g_value_get_string (value));
      break;

    case PROP_VALUE:
      ide_extension_set_adapter_set_value (self, g_value_get_string (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
ide_extension_set_adapter_class_init (IdeExtensionSetAdapterClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);

  object_class->finalize = ide_extension_set_adapter_finalize;
  object_class->get_property = ide_extension_set_adapter_get_property;
  object_class->set_property = ide_extension_set_adapter_set_property;

  i_object_class->destroy = ide_extension_set_adapter_destroy;
  i_object_class->repr = ide_extension_set_adapter_repr;

  properties [PROP_ENGINE] =
    g_param_spec_object ("engine",
                         "Engine",
                         "Engine",
                         PEAS_TYPE_ENGINE,
                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  properties [PROP_INTERFACE_TYPE] =
    g_param_spec_gtype ("interface-type",
                        "Interface Type",
                        "Interface Type",
                        G_TYPE_OBJECT,
                        (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  properties [PROP_KEY] =
    g_param_spec_string ("key",
                         "Key",
                         "Key",
                         NULL,
                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  properties [PROP_VALUE] =
    g_param_spec_string ("value",
                         "Value",
                         "Value",
                         NULL,
                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_properties (object_class, LAST_PROP, properties);

  signals [EXTENSION_ADDED] =
    g_signal_new ("extension-added",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE,
                  2,
                  PEAS_TYPE_PLUGIN_INFO,
                  PEAS_TYPE_EXTENSION);

  signals [EXTENSION_REMOVED] =
    g_signal_new ("extension-removed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE,
                  2,
                  PEAS_TYPE_PLUGIN_INFO,
                  PEAS_TYPE_EXTENSION);

  signals [EXTENSIONS_LOADED] =
    g_signal_new ("extensions-loaded",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE, 0);
}

static void
ide_extension_set_adapter_init (IdeExtensionSetAdapter *self)
{
  self->settings = g_ptr_array_new_with_free_func (g_object_unref);
  self->extensions = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
}

/**
 * ide_extension_set_adapter_get_engine:
 *
 * Gets the #IdeExtensionSetAdapter:engine property.
 *
 * Returns: (transfer none): a #PeasEngine.
 *
 * Since: 3.32
 */
PeasEngine *
ide_extension_set_adapter_get_engine (IdeExtensionSetAdapter *self)
{
  g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), NULL);

  return self->engine;
}

GType
ide_extension_set_adapter_get_interface_type (IdeExtensionSetAdapter *self)
{
  g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), G_TYPE_INVALID);

  return self->interface_type;
}

const gchar *
ide_extension_set_adapter_get_key (IdeExtensionSetAdapter *self)
{
  g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), NULL);

  return self->key;
}

void
ide_extension_set_adapter_set_key (IdeExtensionSetAdapter *self,
                                   const gchar            *key)
{
  g_return_if_fail (IDE_IS_MAIN_THREAD ());
  g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self));

  if (!ide_str_equal0 (self->key, key))
    {
      g_free (self->key);
      self->key = g_strdup (key);
      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_KEY]);
      ide_extension_set_adapter_queue_reload (self);
    }
}

const gchar *
ide_extension_set_adapter_get_value (IdeExtensionSetAdapter *self)
{
  g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
  g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), NULL);

  return self->value;
}

void
ide_extension_set_adapter_set_value (IdeExtensionSetAdapter *self,
                                     const gchar            *value)
{
  g_return_if_fail (IDE_IS_MAIN_THREAD ());
  g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self));

  IDE_TRACE_MSG ("Setting extension adapter %s value to \"%s\"",
                 g_type_name (self->interface_type),
                 value ?: "");

  if (!ide_str_equal0 (self->value, value))
    {
      g_free (self->value);
      self->value = g_strdup (value);
      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VALUE]);
      ide_extension_set_adapter_queue_reload (self);
    }
}

/**
 * ide_extension_set_adapter_foreach:
 * @self: an #IdeExtensionSetAdapter
 * @foreach_func: (scope call): A callback
 * @user_data: user data for @foreach_func
 *
 * Calls @foreach_func for every extension loaded by the extension set.
 *
 * Since: 3.32
 */
void
ide_extension_set_adapter_foreach (IdeExtensionSetAdapter            *self,
                                   IdeExtensionSetAdapterForeachFunc  foreach_func,
                                   gpointer                           user_data)
{
  const GList *list;

  g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self));
  g_return_if_fail (foreach_func != NULL);

  /*
   * Use the ordered list of plugins as it is sorted including any
   * dependencies of plugins.
   */

  list = peas_engine_get_plugin_list (self->engine);

  for (const GList *iter = list; iter; iter = iter->next)
    {
      PeasPluginInfo *plugin_info = iter->data;
      PeasExtension *exten = g_hash_table_lookup (self->extensions, plugin_info);

      if (exten != NULL)
        foreach_func (self, plugin_info, exten, user_data);
    }
}

typedef struct
{
  PeasPluginInfo *plugin_info;
  PeasExtension  *exten;
  gint            priority;
} SortedInfo;

static gint
sort_by_priority (gconstpointer a,
                  gconstpointer b)
{
  const SortedInfo *sa = a;
  const SortedInfo *sb = b;

  /* Greater values are higher priority */

  if (sa->priority < sb->priority)
    return -1;
  else if (sa->priority > sb->priority)
    return 1;
  else
    return 0;
}

/**
 * ide_extension_set_adapter_foreach_by_priority:
 * @self: an #IdeExtensionSetAdapter
 * @foreach_func: (scope call): A callback
 * @user_data: user data for @foreach_func
 *
 * Calls @foreach_func for every extension loaded by the extension set.
 *
 * Since: 3.32
 */
void
ide_extension_set_adapter_foreach_by_priority (IdeExtensionSetAdapter            *self,
                                               IdeExtensionSetAdapterForeachFunc  foreach_func,
                                               gpointer                           user_data)
{
  g_autoptr(GArray) sorted = NULL;
  g_autofree gchar *prio_key = NULL;
  GHashTableIter iter;
  gpointer key;
  gpointer value;

  g_return_if_fail (IDE_IS_MAIN_THREAD ());
  g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self));
  g_return_if_fail (foreach_func != NULL);

  if (self->key == NULL)
    {
      ide_extension_set_adapter_foreach (self, foreach_func, user_data);
      return;
    }

  prio_key = g_strdup_printf ("%s-Priority", self->key);
  sorted = g_array_new (FALSE, FALSE, sizeof (SortedInfo));

  g_hash_table_iter_init (&iter, self->extensions);

  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      PeasPluginInfo *plugin_info = key;
      PeasExtension *exten = value;
      const gchar *priostr = peas_plugin_info_get_external_data (plugin_info, prio_key);
      gint prio = priostr ? atoi (priostr) : 0;
      SortedInfo info = { plugin_info, exten, prio };

      g_array_append_val (sorted, info);
    }

  g_array_sort (sorted, sort_by_priority);

  for (guint i = 0; i < sorted->len; i++)
    {
      const SortedInfo *info = &g_array_index (sorted, SortedInfo, i);

      foreach_func (self, info->plugin_info, info->exten, user_data);
    }
}

guint
ide_extension_set_adapter_get_n_extensions (IdeExtensionSetAdapter *self)
{
  g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), 0);

  if (self->extensions != NULL)
    return g_hash_table_size (self->extensions);

  return 0;
}

IdeExtensionSetAdapter *
ide_extension_set_adapter_new (IdeObject   *parent,
                               PeasEngine  *engine,
                               GType        interface_type,
                               const gchar *key,
                               const gchar *value)
{
  IdeExtensionSetAdapter *ret;

  g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
  g_return_val_if_fail (!parent || IDE_IS_OBJECT (parent), NULL);
  g_return_val_if_fail (!engine || PEAS_IS_ENGINE (engine), NULL);
  g_return_val_if_fail (G_TYPE_IS_INTERFACE (interface_type) ||
                        G_TYPE_IS_OBJECT (interface_type), NULL);

  ret = g_object_new (IDE_TYPE_EXTENSION_SET_ADAPTER,
                      "engine", engine,
                      "interface-type", interface_type,
                      "key", key,
                      "value", value,
                      NULL);

  if (parent != NULL)
    ide_object_append (parent, IDE_OBJECT (ret));

  /* If we have a reload queued, just process it immediately so that
   * there is some determinism in plugin loading.
   */
  if (ret->reload_handler != 0)
    {
      g_clear_handle_id (&ret->reload_handler, g_source_remove);
      ide_extension_set_adapter_do_reload (ret);
    }

  return ret;
}

/**
 * ide_extension_set_adapter_get_extension:
 * @self: a #IdeExtensionSetAdapter
 * @plugin_info: a #PeasPluginInfo
 *
 * Locates the extension owned by @plugin_info if such extension exists.
 *
 * Returns: (transfer none) (nullable): a #PeasExtension or %NULL
 *
 * Since: 3.32
 */
PeasExtension *
ide_extension_set_adapter_get_extension (IdeExtensionSetAdapter *self,
                                         PeasPluginInfo         *plugin_info)
{
  g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
  g_return_val_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (self), NULL);
  g_return_val_if_fail (plugin_info != NULL, NULL);

  return g_hash_table_lookup (self->extensions, plugin_info);
}
