/*
 * Pidgin - Internet Messenger
 * Copyright (C) Pidgin Developers <devel@pidgin.im>
 *
 * Pidgin is the legal property of its developers, whose names are too numerous
 * to list here. Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * 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 2 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 <https://www.gnu.org/licenses/>.
 */

#include <glib/gi18n-lib.h>

#include <adwaita.h>

#include "pidginrequestpage.h"

#include "pidginaccountdisplay.h"

struct _PidginRequestPage {
	GtkWindow parent;

	PurpleRequestPage *request_page;

	GtkWidget *action_bar;
	GtkWidget *preferences_page;
};

enum {
	PROP_0,
	PROP_REQUEST_PAGE,
	N_PROPERTIES,
};
static GParamSpec *properties[N_PROPERTIES] = {NULL, };

enum {
	SIG_ACCEPTED,
	SIG_CANCELLED,
	N_SIGNALS,
};
static guint signals[N_SIGNALS] = {0, };

/******************************************************************************
 * Helpers
 *****************************************************************************/
static void
pidgin_request_page_emit_accepted(PidginRequestPage *request_page) {
	g_signal_emit(request_page, signals[SIG_ACCEPTED], 0);
}

static void
pidgin_request_page_emit_cancelled(PidginRequestPage *request_page) {
	g_signal_emit(request_page, signals[SIG_CANCELLED], 0);
}

/******************************************************************************
 * Callbacks
 *****************************************************************************/
static void
pidgin_request_page_close_cb(G_GNUC_UNUSED PurpleRequestPage *request_page,
                             gpointer data)
{
	gtk_window_close(data);
}

static void
pidgin_request_page_valid_cb(GObject *self,
                             G_GNUC_UNUSED GParamSpec *pspec,
                             gpointer data)
{
	GtkWidget *widget = data;

	gtk_widget_action_set_enabled(widget, "window.accept",
	                              purple_request_page_is_valid(PURPLE_REQUEST_PAGE(self)));
}

static void
pidgin_request_page_account_field_setup_cb(G_GNUC_UNUSED GtkSignalListItemFactory *self,
                                           GObject *object,
                                           G_GNUC_UNUSED gpointer data)
{
	gtk_list_item_set_child(GTK_LIST_ITEM(object),
	                        pidgin_account_display_new(NULL));
}

static void
pidgin_request_page_account_field_bind_cb(G_GNUC_UNUSED GtkSignalListItemFactory *self,
                                          GObject *object,
                                          G_GNUC_UNUSED gpointer data)
{
	PurpleAccount *account = NULL;
	GtkListItem *item = GTK_LIST_ITEM(object);
	GtkWidget *display = NULL;

	account = gtk_list_item_get_item(item);
	display = gtk_list_item_get_child(item);

	pidgin_account_display_set_account(PIDGIN_ACCOUNT_DISPLAY(display),
	                                   account);
}

static void
pidgin_request_page_close_request_cb(GtkWindow *self,
                                     G_GNUC_UNUSED gpointer data)
{
	pidgin_request_page_emit_cancelled(PIDGIN_REQUEST_PAGE(self));
}

/******************************************************************************
 * Builder
 *****************************************************************************/
static GtkWidget *
pidgin_request_page_generate_label(PurpleRequestField *request_field,
                                   gboolean is_error)
{
	GtkWidget *widget_field = NULL;

	widget_field = adw_action_row_new();
	g_object_bind_property(request_field, "label", widget_field, "title",
	                       G_BINDING_SYNC_CREATE);

	if(is_error) {
		gtk_widget_add_css_class(widget_field, "error");
	}

	return widget_field;
}

static GtkWidget *
pidgin_request_page_generate_bool(PurpleRequestFieldBool *request_field) {
	GtkWidget *widget_field = NULL;

	widget_field = adw_switch_row_new();
	g_object_bind_property(request_field, "label",
	                       widget_field, "title",
	                       G_BINDING_SYNC_CREATE);
	g_object_bind_property(request_field, "value",
	                       widget_field, "active",
	                       G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
	g_object_bind_property(request_field, "sensitive",
	                       widget_field, "sensitive",
	                       G_BINDING_SYNC_CREATE);

	return widget_field;
}

static GtkWidget *
pidgin_request_page_generate_string(PurpleRequestFieldString *request_field) {
	GtkWidget *widget_field = NULL;
	PurpleRequestFieldString *string_field = NULL;

	string_field = PURPLE_REQUEST_FIELD_STRING(request_field);
	if(purple_request_field_string_is_multiline(string_field)) {
		GtkTextBuffer *buffer = NULL;
		GtkWidget *vbox = NULL;
		GtkWidget *scrolled_window = NULL;
		GtkWidget *text_view = NULL;
		GtkWidget *title = NULL;

		widget_field = adw_preferences_row_new();

		vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
		gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(widget_field),
		                           vbox);

		title = gtk_label_new(NULL);
		gtk_widget_set_halign(title, GTK_ALIGN_START);
		gtk_widget_set_margin_start(title, 12);
		gtk_widget_set_margin_end(title, 12);
		gtk_widget_add_css_class(title, "subtitle");
		gtk_box_append(GTK_BOX(vbox), title);
		g_object_bind_property(request_field, "label", title, "label",
		                       G_BINDING_SYNC_CREATE);

		scrolled_window = gtk_scrolled_window_new();
		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
		                               GTK_POLICY_NEVER,
		                               GTK_POLICY_AUTOMATIC);
		gtk_box_append(GTK_BOX(vbox), scrolled_window);

		text_view = gtk_text_view_new();
		gtk_widget_set_margin_start(text_view, 12);
		gtk_widget_set_margin_end(text_view, 12);
		gtk_widget_remove_css_class(text_view, "view");
		gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_view),
									GTK_WRAP_WORD_CHAR);
		g_object_bind_property(request_field, "sensitive",
		                       text_view, "sensitive",
		                       G_BINDING_SYNC_CREATE);

		buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view));
		g_object_bind_property(request_field, "value", buffer, "text",
		                       G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
		gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled_window),
		                              text_view);
	} else {
		if(purple_request_field_string_is_masked(string_field)) {
			widget_field = adw_password_entry_row_new();
		} else {
			widget_field = adw_entry_row_new();
		}

		g_object_bind_property(request_field, "label", widget_field, "title",
		                       G_BINDING_SYNC_CREATE);
		g_object_bind_property(request_field, "sensitive",
		                       widget_field, "sensitive",
		                       G_BINDING_SYNC_CREATE);
		g_object_bind_property(request_field, "value",
		                       widget_field, "text",
		                       G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
	}

	return widget_field;
}

static GtkWidget *
pidgin_request_page_generate_int(PurpleRequestFieldInt *request_field) {
	GtkAdjustment *adjustment = NULL;
	GtkWidget *widget_field = NULL;

	/* We use g_object_new as we're going to bind all the properties
	 * that we care about.
	 */
	adjustment = g_object_new(GTK_TYPE_ADJUSTMENT,
	                          "step-increment", 1.0,
	                          NULL);
	g_object_bind_property(request_field, "lower-bound",
	                       adjustment, "lower",
	                       G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
	g_object_bind_property(request_field, "upper-bound",
	                       adjustment, "upper",
	                       G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
	g_object_bind_property(request_field, "value",
	                       adjustment, "value",
	                       G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);

	widget_field = adw_spin_row_new(adjustment, 1.0, 0);
	adw_spin_row_set_numeric(ADW_SPIN_ROW(widget_field), TRUE);
	g_object_bind_property(request_field, "label",
	                       widget_field, "title",
	                       G_BINDING_SYNC_CREATE);
	g_object_bind_property(request_field, "sensitive",
	                       widget_field, "sensitive",
	                       G_BINDING_SYNC_CREATE);

	return widget_field;
}

static GtkWidget *
pidgin_request_page_generate_account(PurpleRequestFieldAccount *request_field) {
	GtkWidget *widget_field = NULL;
	GtkListItemFactory *factory = NULL;

	widget_field = adw_combo_row_new();

	factory = gtk_signal_list_item_factory_new();
	g_signal_connect(factory,
	                 "setup",
	                 G_CALLBACK(pidgin_request_page_account_field_setup_cb),
	                 NULL);
	g_signal_connect(factory,
	                 "bind",
	                 G_CALLBACK(pidgin_request_page_account_field_bind_cb),
	                 NULL);
	adw_combo_row_set_factory(ADW_COMBO_ROW(widget_field), factory);
	g_clear_object(&factory);

	g_object_bind_property(request_field, "label",
	                       widget_field, "title",
	                       G_BINDING_SYNC_CREATE);
	g_object_bind_property(request_field, "sensitive",
	                       widget_field, "sensitive",
	                       G_BINDING_SYNC_CREATE);
	g_object_bind_property(request_field, "model",
	                       widget_field, "model",
	                       G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);

	return widget_field;
}

static GtkWidget *
pidgin_request_page_generate_image(PurpleRequestFieldImage *request_field) {
	PurpleImage *image = NULL;
	GdkTexture *texture = NULL;
	GtkWidget *picture = NULL;
	GtkWidget *title = NULL;
	GtkWidget *vbox = NULL;
	GtkWidget *widget_field = NULL;
	GError *error = NULL;

	widget_field = adw_preferences_row_new();

	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
	gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(widget_field),
	                           vbox);

	title = gtk_label_new(NULL);
	gtk_widget_set_halign(title, GTK_ALIGN_START);
	gtk_widget_set_margin_start(title, 12);
	gtk_widget_set_margin_end(title, 12);
	gtk_widget_add_css_class(title, "subtitle");
	gtk_box_append(GTK_BOX(vbox), title);
	g_object_bind_property(request_field, "label", title, "label",
	                       G_BINDING_SYNC_CREATE);

	image = purple_request_field_image_get_image(request_field);
	texture = gdk_texture_new_from_bytes(purple_image_get_contents(image),
	                                     &error);

	if(error == NULL) {
		picture = gtk_picture_new_for_paintable(GDK_PAINTABLE(texture));
		/* If the picture can shrink it is often not displayed. */
		gtk_picture_set_can_shrink(GTK_PICTURE(picture), FALSE);
		g_object_bind_property(image, "alt-text", picture, "alternative-text",
		                       G_BINDING_SYNC_CREATE);
		gtk_box_append(GTK_BOX(vbox), picture);
	} else {
		GtkWidget *label = NULL;
		char *message = NULL;

		message = g_strdup_printf(_("failed to load image: %s"),
		                          error->message);
		g_clear_error(&error);

		label = gtk_label_new(message);
		g_free(message);

		gtk_widget_add_css_class(label, "error");

		gtk_box_append(GTK_BOX(vbox), label);
	}

	g_clear_object(&texture);

	return widget_field;
}

static GtkWidget *
pidgin_request_page_generate_choice(PurpleRequestFieldChoice *request_field) {
	GtkExpression *expression = NULL;
	GtkWidget *widget_field = NULL;

	widget_field = adw_combo_row_new();

	expression = gtk_property_expression_new(BIRB_TYPE_LOCALIZED_STRING,
	                                         NULL, "label");
	adw_combo_row_set_expression(ADW_COMBO_ROW(widget_field), expression);
	g_clear_pointer(&expression, gtk_expression_unref);

	/* Set the model before the bindings so the selection will work. */
	adw_combo_row_set_model(ADW_COMBO_ROW(widget_field),
	                        G_LIST_MODEL(request_field));

	g_object_bind_property(request_field, "label", widget_field, "title",
	                       G_BINDING_SYNC_CREATE);
	g_object_bind_property(request_field, "sensitive",
	                       widget_field, "sensitive",
	                       G_BINDING_SYNC_CREATE);
	g_object_bind_property(request_field, "selected", widget_field, "selected",
	                       G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);

	return widget_field;
}

static void
pidgin_request_page_list_selection_update(PurpleRequestFieldList *field_list,
                                          GtkSelectionModel *model)
{
	GtkBitset *bitset = NULL;
	guint n_selected = 0;

	purple_request_field_list_clear_selected(field_list);

	bitset = gtk_selection_model_get_selection(model);
	n_selected = gtk_bitset_get_size(bitset);

	for(guint index = 0; index < n_selected; index++) {
		BirbLocalizedString *str = NULL;

		str = g_list_model_get_item(G_LIST_MODEL(model),
		                            gtk_bitset_get_nth(bitset, index));
		if(BIRB_IS_LOCALIZED_STRING(str)) {
			const char *id = NULL;

			id = birb_localized_string_get_id(str);
			purple_request_field_list_select_item(field_list, id);
		}
		g_clear_object(&str);
	}

	gtk_bitset_unref(bitset);
}

static void
pidgin_request_page_list_selection_changed_cb(GtkSelectionModel *self,
                                              G_GNUC_UNUSED guint position,
                                              G_GNUC_UNUSED guint n_items,
                                              gpointer data)
{
	pidgin_request_page_list_selection_update(data, self);
}

static void
pidgin_request_page_list_field_setup_listitem_cb(G_GNUC_UNUSED GtkSignalListItemFactory *self,
                                                 GtkListItem *item,
                                                 G_GNUC_UNUSED gpointer data)
{
	GtkWidget *widget = NULL;

	widget = gtk_label_new(NULL);
	gtk_label_set_use_underline(GTK_LABEL(widget), TRUE);
	gtk_widget_set_halign(widget, GTK_ALIGN_START);
	gtk_list_item_set_child(item, widget);
}

static void
pidgin_request_page_list_field_bind_listitem_cb(G_GNUC_UNUSED GtkSignalListItemFactory *self,
                                                GtkListItem *item,
                                                G_GNUC_UNUSED gpointer data)
{
	GtkWidget *label = NULL;
	GObject *obj = NULL;

	obj = gtk_list_item_get_item(item);

	label = gtk_list_item_get_child(item);
	g_object_bind_property(obj, "label", label, "label",
	                       G_BINDING_SYNC_CREATE);
}

static GtkWidget *
pidgin_request_page_generate_list(PurpleRequestFieldList *request_field) {
	GtkWidget *list_view = NULL;
	GtkWidget *title = NULL;
	GtkWidget *vbox = NULL;
	GtkWidget *widget_field = NULL;
	GtkSelectionModel *model = NULL;
	GtkListItemFactory *factory = NULL;

	widget_field = adw_preferences_row_new();

	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
	gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(widget_field),
	                           vbox);

	/* Create the title widget. */
	title = gtk_label_new(NULL);
	gtk_widget_set_halign(title, GTK_ALIGN_START);
	gtk_widget_set_margin_start(title, 12);
	gtk_widget_set_margin_end(title, 12);
	gtk_widget_add_css_class(title, "subtitle");
	gtk_box_append(GTK_BOX(vbox), title);
	g_object_bind_property(request_field, "label", title, "label",
	                       G_BINDING_SYNC_CREATE);

	/* Build the list view. */

	/* The selection _new methods take ownership of the model, so we need to
	 * give them a ref to take.
	 */
	g_object_ref(request_field);
	if(purple_request_field_list_get_multi_select(request_field)) {
		GtkMultiSelection *multi = NULL;

		multi = gtk_multi_selection_new(G_LIST_MODEL(request_field));
		model = GTK_SELECTION_MODEL(multi);
	} else {
		GtkSingleSelection *single = NULL;

		single = gtk_single_selection_new(G_LIST_MODEL(request_field));
		gtk_single_selection_set_can_unselect(GTK_SINGLE_SELECTION(single),
		                                      TRUE);

		model = GTK_SELECTION_MODEL(single);

		/* Force an update of the fields selection as GTK always auto selects
		 * an item, even when you tell it not to.
		 */
		pidgin_request_page_list_selection_update(PURPLE_REQUEST_FIELD_LIST(request_field),
		                                          model);
	}
	g_signal_connect_object(model,
	                        "selection-changed",
	                        G_CALLBACK(pidgin_request_page_list_selection_changed_cb),
	                        request_field,
	                        G_CONNECT_DEFAULT);

	factory = gtk_signal_list_item_factory_new();
	g_signal_connect(factory,
	                 "setup",
	                 G_CALLBACK(pidgin_request_page_list_field_setup_listitem_cb),
	                 request_field);
	g_signal_connect(factory,
	                 "bind",
	                 G_CALLBACK(pidgin_request_page_list_field_bind_listitem_cb),
	                 request_field);

	list_view = gtk_list_view_new(model, factory);
	gtk_box_append(GTK_BOX(vbox), list_view);
	gtk_widget_add_css_class(list_view, "rich-list");
	g_object_bind_property(request_field, "sensitive", list_view, "sensitive",
	                       G_BINDING_SYNC_CREATE);

	return widget_field;
}

static void
pidgin_request_page_generate_group(PurpleRequestGroup *request_group,
                                   AdwPreferencesGroup *widget_group)
{
	GListModel *model = NULL;
	guint n_items = 0;

	model = G_LIST_MODEL(request_group);
	n_items = g_list_model_get_n_items(model);
	for(guint i = 0; i < n_items; i++) {
		PurpleRequestField *request_field = NULL;
		GtkWidget *widget_field = NULL;

		request_field = g_list_model_get_item(model, i);

		if(!purple_request_field_is_visible(request_field)) {
			g_clear_object(&request_field);

			continue;
		}

		if(PURPLE_IS_REQUEST_FIELD_LABEL(request_field)) {
			widget_field = pidgin_request_page_generate_label(request_field,
			                                                  FALSE);
		} else if(PURPLE_IS_REQUEST_FIELD_BOOL(request_field)) {
			widget_field = pidgin_request_page_generate_bool(PURPLE_REQUEST_FIELD_BOOL(request_field));
		} else if(PURPLE_IS_REQUEST_FIELD_STRING(request_field)) {
			widget_field = pidgin_request_page_generate_string(PURPLE_REQUEST_FIELD_STRING(request_field));
		} else if(PURPLE_IS_REQUEST_FIELD_INT(request_field)) {
			widget_field = pidgin_request_page_generate_int(PURPLE_REQUEST_FIELD_INT(request_field));
		} else if(PURPLE_IS_REQUEST_FIELD_ACCOUNT(request_field)) {
			widget_field = pidgin_request_page_generate_account(PURPLE_REQUEST_FIELD_ACCOUNT(request_field));
		} else if(PURPLE_IS_REQUEST_FIELD_IMAGE(request_field)) {
			widget_field = pidgin_request_page_generate_image(PURPLE_REQUEST_FIELD_IMAGE(request_field));
		} else if(PURPLE_IS_REQUEST_FIELD_CHOICE(request_field)) {
			widget_field = pidgin_request_page_generate_choice(PURPLE_REQUEST_FIELD_CHOICE(request_field));
		} else if(PURPLE_IS_REQUEST_FIELD_LIST(request_field)) {
			widget_field = pidgin_request_page_generate_list(PURPLE_REQUEST_FIELD_LIST(request_field));
		} else {
			widget_field = pidgin_request_page_generate_label(request_field,
			                                                  TRUE);
		}

		adw_preferences_group_add(widget_group, widget_field);

		g_clear_object(&request_field);
	}
}

static void
pidgin_request_page_generate_page(PidginRequestPage *request_page) {
	GListModel *model = NULL;
	guint n_items = 0;

	/* Add the close signal handler. */
	g_signal_connect_object(request_page->request_page,
	                        "close",
	                        G_CALLBACK(pidgin_request_page_close_cb),
	                        request_page,
	                        G_CONNECT_DEFAULT);
	g_signal_connect_object(request_page->request_page,
	                        "notify::valid",
	                        G_CALLBACK(pidgin_request_page_valid_cb),
	                        request_page,
	                        G_CONNECT_DEFAULT);

	/* Add the groups. */
	model = G_LIST_MODEL(request_page->request_page);
	n_items = g_list_model_get_n_items(model);
	for(guint i = 0; i < n_items; i++) {
		PurpleRequestGroup *request_group = NULL;
		GtkWidget *widget_group = NULL;
		const char *title = NULL;

		request_group = g_list_model_get_item(model, i);
		title = purple_request_group_get_title(request_group);

		widget_group = adw_preferences_group_new();
		adw_preferences_group_set_title(ADW_PREFERENCES_GROUP(widget_group),
		                                title);
		adw_preferences_page_add(ADW_PREFERENCES_PAGE(request_page->preferences_page),
		                         ADW_PREFERENCES_GROUP(widget_group));

		pidgin_request_page_generate_group(request_group,
		                                   ADW_PREFERENCES_GROUP(widget_group));

		g_clear_object(&request_group);
	}
}

static void
pidgin_request_page_set_request_page(PidginRequestPage *pidgin_request_page,
                                     PurpleRequestPage *purple_request_page)
{
	g_return_if_fail(PIDGIN_IS_REQUEST_PAGE(pidgin_request_page));

	if(g_set_object(&pidgin_request_page->request_page, purple_request_page)) {
		pidgin_request_page_generate_page(pidgin_request_page);

		g_object_notify_by_pspec(G_OBJECT(pidgin_request_page),
		                         properties[PROP_REQUEST_PAGE]);
	}
}

/******************************************************************************
 * Actions
 *****************************************************************************/
static void
pidgin_request_page_accept(GtkWidget *self,
                           G_GNUC_UNUSED const char *action_name,
                           G_GNUC_UNUSED GVariant *parameter)
{
	pidgin_request_page_emit_accepted(PIDGIN_REQUEST_PAGE(self));
}

static void
pidgin_request_page_cancel(GtkWidget *self,
                           G_GNUC_UNUSED const char *action_name,
                           G_GNUC_UNUSED GVariant *parameter)
{
	pidgin_request_page_emit_cancelled(PIDGIN_REQUEST_PAGE(self));
}

/******************************************************************************
 * GObject Implementation
 *****************************************************************************/
G_DEFINE_FINAL_TYPE(PidginRequestPage, pidgin_request_page, GTK_TYPE_WINDOW)

static void
pidgin_request_page_finalize(GObject *obj) {
	PidginRequestPage *request_page = PIDGIN_REQUEST_PAGE(obj);

	g_clear_object(&request_page->request_page);

	G_OBJECT_CLASS(pidgin_request_page_parent_class)->finalize(obj);
}

static void
pidgin_request_page_get_property(GObject *obj, guint param_id, GValue *value,
                                 GParamSpec *pspec)
{
	PidginRequestPage *request_page = PIDGIN_REQUEST_PAGE(obj);

	switch(param_id) {
	case PROP_REQUEST_PAGE:
		g_value_set_object(value,
		                   pidgin_request_page_get_request_page(request_page));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
		break;
	}
}

static void
pidgin_request_page_set_property(GObject *obj, guint param_id,
                                 const GValue *value, GParamSpec *pspec)
{
	PidginRequestPage *request_page = PIDGIN_REQUEST_PAGE(obj);

	switch(param_id) {
	case PROP_REQUEST_PAGE:
		pidgin_request_page_set_request_page(request_page,
		                                     g_value_get_object(value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
		break;
	}
}

static void
pidgin_request_page_init(PidginRequestPage *request_page) {
	gtk_widget_init_template(GTK_WIDGET(request_page));
}

static void
pidgin_request_page_class_init(PidginRequestPageClass *klass) {
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);

	obj_class->finalize = pidgin_request_page_finalize;
	obj_class->get_property = pidgin_request_page_get_property;
	obj_class->set_property = pidgin_request_page_set_property;

	/**
	 * PidginRequestPage:request-page:
	 *
	 * The [class@Purple.RequestPage] that is being displayed.
	 *
	 * Since: 3.0
	 */
	properties[PROP_REQUEST_PAGE] = g_param_spec_object(
		"request-page", NULL, NULL,
		PURPLE_TYPE_REQUEST_PAGE,
		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY);

	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);

	/* Signals. */

	/**
	 * PidginRequestPage::accepted:
	 * @request_page: the request page
	 *
	 * Emitted when the user has accepted the page.
	 *
	 * This is typically done by hitting the `OK` button.
	 *
	 * Since: 3.0
	 */
	signals[SIG_ACCEPTED] = g_signal_new_class_handler(
		"accepted",
		G_OBJECT_CLASS_TYPE(klass),
		G_SIGNAL_RUN_LAST,
		NULL,
		NULL,
		NULL,
		NULL,
		G_TYPE_NONE,
		0);

	/**
	 * PidginRequestPage::cancelled:
	 * @request_page: the request page
	 *
	 * Emitted when the user has cancelled the page.
	 *
	 * This is typically done by hitting the `Cancel` button.
	 *
	 * Since: 3.0
	 */
	signals[SIG_CANCELLED] = g_signal_new_class_handler(
		"cancelled",
		G_OBJECT_CLASS_TYPE(klass),
		G_SIGNAL_RUN_LAST,
		NULL,
		NULL,
		NULL,
		NULL,
		G_TYPE_NONE,
		0);

	/* Actions. */

	/**
	 * PidginRequestPage|window.accept:
	 *
	 * Accepts the inputs of the dialog.
	 *
	 * This is typically activated by the `OK` button.
	 *
	 * Since: 3.0
	 */
	gtk_widget_class_install_action(widget_class, "window.accept", NULL,
	                                pidgin_request_page_accept);

	/**
	 * PidginRequestPage|window.cancel:
	 *
	 * Cancels the dialog.
	 *
	 * This is typically activated by the `Cancel` button.
	 *
	 * Since: 3.0
	 */
	gtk_widget_class_install_action(widget_class, "window.cancel", NULL,
	                                pidgin_request_page_cancel);

	/* Keybindings. */
	gtk_widget_class_add_binding_action(widget_class,
	                                    GDK_KEY_Escape,
	                                    GDK_NO_MODIFIER_MASK,
	                                    "window.cancel",
	                                    NULL);
	/* Templates. */
	gtk_widget_class_set_template_from_resource(widget_class,
	                                            "/im/pidgin/Pidgin3/requestpage.ui");

	gtk_widget_class_bind_template_child(widget_class, PidginRequestPage,
	                                     action_bar);
	gtk_widget_class_bind_template_child(widget_class, PidginRequestPage,
	                                     preferences_page);

	gtk_widget_class_bind_template_callback(widget_class,
	                                        pidgin_request_page_close_request_cb);
}

/******************************************************************************
 * Public API
 *****************************************************************************/
PurpleRequestPage *
pidgin_request_page_get_request_page(PidginRequestPage *request_page) {
	g_return_val_if_fail(PIDGIN_IS_REQUEST_PAGE(request_page), NULL);

	return request_page->request_page;
}

GtkWidget *
pidgin_request_page_new(GtkApplication *application,
                        PurpleRequestPage *request_page)
{
	g_return_val_if_fail(GTK_IS_APPLICATION(application), NULL);
	g_return_val_if_fail(PURPLE_IS_REQUEST_PAGE(request_page), NULL);

	return g_object_new(PIDGIN_TYPE_REQUEST_PAGE,
	                    "application", application,
	                    "request-page", request_page,
	                    NULL);
}
