/* pidgin
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 */

#include <glib/gi18n-lib.h>

#include <birb.h>

#include <purple.h>

#include "gtkrequest.h"
#include "pidginaccountdisplay.h"
#include "pidginaccountfilterconnected.h"
#include "pidginaccountrow.h"
#include "pidgincore.h"

#include <gdk/gdkkeysyms.h>

typedef struct {
	PurpleRequestType type;
	PurpleRequestPage *page;

	gpointer user_data;
	GtkWidget *dialog;
	GCancellable *cancellable;

	PurpleRequestHelpCb help_cb;
	gpointer help_data;

	GPtrArray *action_widgets;
	GPtrArray *action_cbs;
} PidginRequestData;

static void
pidgin_widget_decorate_account(GtkWidget *cont, PurpleAccount *account)
{
	GtkWidget *display = NULL;

	if(!PURPLE_IS_ACCOUNT(account)) {
		return;
	}

	if(!GTK_IS_BOX(cont)) {
		return;
	}

	display = pidgin_account_display_new(account);
	gtk_widget_set_halign(display, GTK_ALIGN_CENTER);
	gtk_box_append(GTK_BOX(cont), display);
}

static void
field_choice_option_cb(GObject *obj, G_GNUC_UNUSED GParamSpec *pspec,
                       gpointer data)
{
	PurpleRequestField *field = data;
	GtkDropDown *dropdown = GTK_DROP_DOWN(obj);
	GObject *item = gtk_drop_down_get_selected_item(dropdown);

	if(G_IS_OBJECT(item)) {
		gpointer value = g_object_get_data(item, "choice_value");
		purple_request_field_choice_set_value(PURPLE_REQUEST_FIELD_CHOICE(field),
		                                      value);
	}
}

static void
field_account_cb(GObject *obj, G_GNUC_UNUSED GParamSpec *pspec, gpointer data)
{
	PurpleRequestField *field = data;
	PidginAccountRow *row = PIDGIN_ACCOUNT_ROW(obj);

	purple_request_field_account_set_value(
	        PURPLE_REQUEST_FIELD_ACCOUNT(field),
	        pidgin_account_row_get_account(row));
}

static void
multifield_response_cb(GtkButton *button, gpointer data) {
	PidginRequestData *req_data = data;
	PurpleRequestFieldsCb cb = NULL;
	guint index = 0;

	gtk_widget_set_visible(GTK_WIDGET(req_data->dialog), FALSE);

	if (g_ptr_array_find(req_data->action_widgets, button, &index)) {
		cb = g_ptr_array_index(req_data->action_cbs, index);
	}

	if(cb != NULL) {
		cb(req_data->user_data, req_data->page);
	}

	purple_request_close(PURPLE_REQUEST_FIELDS, data);
}

static GtkWidget *
pidgin_request_dialog_icon(PurpleRequestType dialog_type,
	PurpleRequestCommonParameters *cpar)
{
	GtkWidget *img = NULL;
	PurpleRequestIconType icon_type;
	gconstpointer icon_data;
	gsize icon_size;
	const char *icon_name = NULL;

	/* Dialog icon. */
	icon_data = purple_request_cpar_get_custom_icon(cpar, &icon_size);
	if (icon_data) {
		GBytes *contents = NULL;
		GError *error = NULL;
		GdkTexture *texture = NULL;

		contents = g_bytes_new(icon_data, icon_size);
		texture = gdk_texture_new_from_bytes(contents, &error);
		g_clear_pointer(&contents, g_bytes_unref);

		if(error != NULL) {
			purple_debug_info("pidgin", "failed to parse dialog icon: %s",
			                  error->message);
			g_clear_error(&error);
		} else {
			img = gtk_image_new_from_paintable(GDK_PAINTABLE(texture));
			g_object_unref(texture);

			gtk_image_set_icon_size(GTK_IMAGE(img), GTK_ICON_SIZE_LARGE);
		}
	}

	if (img)
		return img;

	icon_type = purple_request_cpar_get_icon(cpar);
	switch (icon_type)
	{
		case PURPLE_REQUEST_ICON_DEFAULT:
			icon_name = NULL;
			break;
		case PURPLE_REQUEST_ICON_REQUEST:
			icon_name = "dialog-question";
			break;
		case PURPLE_REQUEST_ICON_DIALOG:
		case PURPLE_REQUEST_ICON_INFO:
		case PURPLE_REQUEST_ICON_WAIT: /* TODO: we need another icon */
			icon_name = "dialog-information";
			break;
		case PURPLE_REQUEST_ICON_WARNING:
			icon_name = "dialog-warning";
			break;
		case PURPLE_REQUEST_ICON_ERROR:
			icon_name = "dialog-error";
			break;
		/* intentionally no default value */
	}

	if (icon_name == NULL) {
		switch (dialog_type) {
			case PURPLE_REQUEST_INPUT:
			case PURPLE_REQUEST_CHOICE:
			case PURPLE_REQUEST_ACTION:
			case PURPLE_REQUEST_FIELDS:
			case PURPLE_REQUEST_FILE:
			case PURPLE_REQUEST_FOLDER:
				icon_name = "dialog-question";
				break;
			case PURPLE_REQUEST_WAIT:
				icon_name = "dialog-information";
				break;
			/* intentionally no default value */
		}
	}

	if(icon_name == NULL) {
		icon_name = "dialog-question";
	}

	img = gtk_image_new_from_icon_name(icon_name);
	gtk_image_set_icon_size(GTK_IMAGE(img), GTK_ICON_SIZE_LARGE);

	return img;
}

static void
pidgin_request_help_clicked(G_GNUC_UNUSED GtkButton *button, gpointer data)
{
	PidginRequestData *req_data = data;

	g_return_if_fail(req_data->help_cb != NULL);
	req_data->help_cb(req_data->help_data);
}

static void
pidgin_request_add_help(PidginRequestData *req_data, GtkWidget *cbox,
                        PurpleRequestCommonParameters *cpar)
{
	GtkWidget *button;
	PurpleRequestHelpCb help_cb;
	gpointer help_data;

	help_cb = purple_request_cpar_get_help_cb(cpar, &help_data);
	if (help_cb == NULL) {
		return;
	}

	button = gtk_button_new_with_mnemonic(_("_Help"));
	gtk_center_box_set_start_widget(GTK_CENTER_BOX(cbox), button);

	req_data->help_cb = help_cb;
	req_data->help_data = help_data;

	g_signal_connect(G_OBJECT(button), "clicked",
	                 G_CALLBACK(pidgin_request_help_clicked), req_data);
}

static GtkWidget *
create_label_field(void) {
	GtkWidget *row = NULL;
	GtkWidget *label = NULL;

	row = adw_preferences_row_new();
	gtk_widget_set_focusable(row, FALSE);
	gtk_list_box_row_set_activatable(GTK_LIST_BOX_ROW(row), FALSE);

	label = gtk_label_new(NULL);
	gtk_label_set_xalign(GTK_LABEL(label), 0.0);
	gtk_widget_set_margin_start(label, 12);
	gtk_widget_set_margin_end(label, 12);
	gtk_widget_set_margin_bottom(label, 12);
	gtk_widget_set_margin_top(label, 12);
	g_object_bind_property(row, "title", label, "label", G_BINDING_DEFAULT);
	g_object_bind_property(row, "use-markup", label, "use-markup",
	                       G_BINDING_DEFAULT);
	g_object_bind_property(row, "use-underline", label, "use-underline",
	                       G_BINDING_DEFAULT);
	gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), label);

	return row;
}

static void
multiline_state_flags_changed_cb(GtkWidget *self, GtkStateFlags flags,
                                 gpointer data)
{
	GtkWidget *row = data;
	gboolean before = FALSE, after = FALSE;

	before = !!(flags & GTK_STATE_FLAG_FOCUS_WITHIN);
	after = !!(gtk_widget_get_state_flags(self) & GTK_STATE_FLAG_FOCUS_WITHIN);
	if(before != after) {
		if(after) {
			gtk_widget_add_css_class(row, "focused");
		} else {
			gtk_widget_remove_css_class(row, "focused");
		}
	}
}

static GtkWidget *
create_string_field(PurpleRequestField *field,
                    G_GNUC_UNUSED PurpleKeyValuePair **hinted_widget)
{
	PurpleRequestFieldString *strfield = PURPLE_REQUEST_FIELD_STRING(field);
	const char *value;
	GtkWidget *row;

	value = purple_request_field_string_get_default_value(strfield);

	if(purple_request_field_string_is_multiline(strfield)) {
		GtkWidget *vbox = NULL;
		GtkWidget *title = NULL;
		GtkWidget *textview = NULL;
		GtkWidget *sw = NULL;
		GtkTextBuffer *buffer = NULL;

		vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
		gtk_widget_set_margin_top(vbox, 6);

		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);

		textview = gtk_text_view_new();
		gtk_widget_set_margin_start(textview, 12);
		gtk_widget_set_margin_end(textview, 12);
		gtk_widget_remove_css_class(textview, "view");
		gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview),
									GTK_WRAP_WORD_CHAR);

		buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
		if(value != NULL) {
			gtk_text_buffer_set_text(buffer, value, -1);
		}
		g_object_bind_property(field, "value", buffer, "text",
		                       G_BINDING_BIDIRECTIONAL);

		sw = gtk_scrolled_window_new();
		gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(sw), textview);
		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
		                               GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
		gtk_widget_set_size_request(sw, -1, 75);
		gtk_widget_set_hexpand(sw, TRUE);
		gtk_widget_set_vexpand(sw, TRUE);
		gtk_box_append(GTK_BOX(vbox), sw);

		row = adw_preferences_row_new();
		g_object_bind_property(row, "title", title, "label",
		                       G_BINDING_DEFAULT);
		g_object_bind_property(row, "use-markup", title, "use-markup",
		                       G_BINDING_DEFAULT);
		g_object_bind_property(row, "use-underline", title, "use-underline",
		                       G_BINDING_DEFAULT);
		gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), vbox);
		gtk_widget_set_focusable(row, FALSE);
		gtk_widget_add_css_class(row, "entry");
		g_signal_connect(textview, "state-flags-changed",
		                 G_CALLBACK(multiline_state_flags_changed_cb), row);

	} else {
		if(purple_request_field_string_is_masked(strfield)) {
			row = adw_password_entry_row_new();
		} else {
			row = adw_entry_row_new();
		}

		if(value != NULL) {
			gtk_editable_set_text(GTK_EDITABLE(row), value);
		}

		g_object_bind_property(field, "value", row, "text",
		                       G_BINDING_BIDIRECTIONAL);
	}

	return row;
}

static GtkWidget *
create_int_field(PurpleRequestField *field,
                 G_GNUC_UNUSED PurpleKeyValuePair **hinted_widget)
{
	PurpleRequestFieldInt *intfield = PURPLE_REQUEST_FIELD_INT(field);
	GtkWidget *widget = NULL;
	GtkWidget *spin = NULL;
	int value;

	spin = gtk_spin_button_new_with_range(
		purple_request_field_int_get_lower_bound(intfield),
		purple_request_field_int_get_upper_bound(intfield), 1);

	value = purple_request_field_int_get_default_value(intfield);
	gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value);
	g_object_bind_property(field, "value", spin, "value",
	                       G_BINDING_BIDIRECTIONAL);

	gtk_widget_set_valign(spin, GTK_ALIGN_CENTER);

	widget = adw_action_row_new();
	gtk_widget_set_focusable(widget, FALSE);
	adw_action_row_add_suffix(ADW_ACTION_ROW(widget), spin);
	adw_action_row_set_activatable_widget(ADW_ACTION_ROW(widget), spin);

	return widget;
}

static GtkWidget *
create_bool_field(PurpleRequestField *field) {
	PurpleRequestFieldBool *boolfield = PURPLE_REQUEST_FIELD_BOOL(field);
	GtkWidget *row = NULL;
	GtkWidget *sw = NULL;

	sw = gtk_switch_new();
	gtk_switch_set_active(GTK_SWITCH(sw),
	                      purple_request_field_bool_get_default_value(boolfield));
	gtk_widget_set_focusable(sw, TRUE);
	gtk_widget_set_valign(sw, GTK_ALIGN_CENTER);
	g_object_bind_property(field, "value", sw, "active",
	                       G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);

	row = adw_action_row_new();
	gtk_widget_set_focusable(row, FALSE);
	adw_action_row_add_suffix(ADW_ACTION_ROW(row), sw);
	adw_action_row_set_activatable_widget(ADW_ACTION_ROW(row), sw);

	return row;
}

static GtkWidget *
create_choice_field(PurpleRequestField *field) {
	PurpleRequestFieldChoice *choicefield = PURPLE_REQUEST_FIELD_CHOICE(field);
	GtkWidget *widget;
	GListModel *model = NULL;
	GList *elements = NULL;
	guint default_index = GTK_INVALID_LIST_POSITION;
	gpointer default_value;
	guint index;

	default_value = purple_request_field_choice_get_value(choicefield);
	widget = gtk_drop_down_new_from_strings(NULL);
	model = gtk_drop_down_get_model(GTK_DROP_DOWN(widget));

	index = 0;
	elements = purple_request_field_choice_get_elements(choicefield);
	for(GList *l = elements; l != NULL; l = g_list_next(l)) {
		PurpleKeyValuePair *choice = l->data;
		GObject *item = NULL;

		gtk_string_list_append(GTK_STRING_LIST(model), choice->key);
		item = g_list_model_get_item(model, index);
		g_object_set_data(item, "choice_value", choice->value);
		if(choice->value == default_value) {
			default_index = index;
		}

		g_clear_object(&item);
		index++;
	}

	gtk_drop_down_set_selected(GTK_DROP_DOWN(widget), default_index);

	g_signal_connect(G_OBJECT(widget), "notify::selected",
	                 G_CALLBACK(field_choice_option_cb), field);

	if(default_index == GTK_INVALID_LIST_POSITION && index > 0) {
		GObject *item = g_list_model_get_item(model, 0);
		gpointer value = g_object_get_data(item, "choice_value");
		purple_request_field_choice_set_value(choicefield, value);
		g_clear_object(&item);
	}

	return widget;
}

static GtkWidget *
create_image_field(PurpleRequestField *field)
{
	PurpleRequestFieldImage *ifield = PURPLE_REQUEST_FIELD_IMAGE(field);
	GBytes *contents = NULL;
	GError *error = NULL;
	GdkTexture *texture = NULL;
	GtkWidget *widget;
	gconstpointer data = NULL;
	gsize size = 0;

	data = purple_request_field_image_get_buffer(ifield);
	size = purple_request_field_image_get_size(ifield);

	contents = g_bytes_new(data, size);
	texture = gdk_texture_new_from_bytes(contents, &error);
	g_clear_pointer(&contents, g_bytes_unref);

	if(error != NULL) {
		purple_debug_warning("requests",
		                     "failed to create text from image field: %s",
		                     error->message);
		g_clear_error(&error);

		return NULL;
	}

	widget = gtk_image_new_from_paintable(GDK_PAINTABLE(texture));
	g_object_unref(texture);

	return widget;
}

static gboolean
field_custom_account_filter_cb(gpointer item, gpointer data) {
	PurpleRequestFieldAccount *field = data;
	gboolean ret = FALSE;

	if(PURPLE_IS_ACCOUNT(item)) {
		ret = purple_request_field_account_match(field, PURPLE_ACCOUNT(item));
	}

	return ret;
}

static GtkWidget *
create_account_field(PurpleRequestField *field, GtkWidget **account_hint)
{
	PurpleRequestFieldAccount *afield = NULL;
	GtkWidget *widget = NULL;
	PurpleAccount *account = NULL;
	GtkCustomFilter *custom_filter = NULL;
	GtkFilter *filter = NULL;
	const char *type_hint = NULL;

	widget = pidgin_account_row_new();
	afield = PURPLE_REQUEST_FIELD_ACCOUNT(field);
	account = purple_request_field_account_get_default_value(afield);

	custom_filter = gtk_custom_filter_new(field_custom_account_filter_cb,
	                                      afield, NULL);
	filter = GTK_FILTER(custom_filter);

	if(!purple_request_field_account_get_show_all(afield)) {
		GtkEveryFilter *every = NULL;

		every = gtk_every_filter_new();

		if(GTK_IS_FILTER(filter)) {
			gtk_multi_filter_append(GTK_MULTI_FILTER(every), filter);
		}

		filter = pidgin_account_filter_connected_new();
		gtk_multi_filter_append(GTK_MULTI_FILTER(every), filter);

		filter = GTK_FILTER(every);
	}

	pidgin_account_row_set_account(PIDGIN_ACCOUNT_ROW(widget), account);
	g_signal_connect(widget, "notify::account", G_CALLBACK(field_account_cb),
	                 field);

	if(GTK_IS_FILTER(filter)) {
		pidgin_account_row_set_filter(PIDGIN_ACCOUNT_ROW(widget), filter);
		g_object_unref(filter);
	}

	type_hint = purple_request_field_get_type_hint(field);
	if(birb_str_equal(type_hint, "account")) {
		*account_hint = widget;
	}

	return widget;
}

static void
setup_list_field_listitem_cb(G_GNUC_UNUSED GtkSignalListItemFactory *self,
                             GtkListItem *item, gpointer data)
{
	PurpleRequestFieldList *field = data;
	GtkWidget *box = NULL;
	GtkWidget *widget = NULL;

	box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
	gtk_list_item_set_child(item, box);

	widget = gtk_label_new(NULL);
	gtk_box_append(GTK_BOX(box), widget);

	if(purple_request_field_list_has_icons(field)) {
		widget = gtk_image_new();
		gtk_box_append(GTK_BOX(box), widget);
	}
}

static void
bind_list_field_listitem_cb(G_GNUC_UNUSED GtkSignalListItemFactory *self,
                            GtkListItem *item, gpointer data)
{
	PurpleRequestFieldList *field = data;
	GtkWidget *box = NULL;
	GtkWidget *label = NULL;
	GObject *wrapper = NULL;

	box = gtk_list_item_get_child(item);
	wrapper = gtk_list_item_get_item(item);

	label = gtk_widget_get_first_child(box);
	gtk_label_set_text(GTK_LABEL(label), g_object_get_data(wrapper, "text"));

	if(purple_request_field_list_has_icons(field)) {
		GtkWidget *image = NULL;
		GdkTexture *texture = NULL;

		image = gtk_widget_get_last_child(box);
		texture = g_object_get_data(wrapper, "texture");
		gtk_image_set_from_paintable(GTK_IMAGE(image), GDK_PAINTABLE(texture));
	}
}

static void
list_field_select_changed_cb(GtkSelectionModel *self,
                             G_GNUC_UNUSED guint position,
                             G_GNUC_UNUSED guint n_items, gpointer data)
{
	PurpleRequestFieldList *field = data;
	GtkBitset *bitset = NULL;

	purple_request_field_list_clear_selected(field);

	bitset = gtk_selection_model_get_selection(self);
	n_items = gtk_bitset_get_size(bitset);

	for(guint index = 0; index < n_items; index++) {
		GObject *wrapper = NULL;
		const char *text = NULL;

		wrapper = g_list_model_get_item(G_LIST_MODEL(self),
		                                gtk_bitset_get_nth(bitset, index));

		text = g_object_get_data(wrapper, "text");
		purple_request_field_list_add_selected(field, text);

		g_object_unref(wrapper);
	}

	gtk_bitset_unref(bitset);
}

static GtkWidget *
create_list_field(PurpleRequestField *field) {
	PurpleRequestFieldList *listfield = PURPLE_REQUEST_FIELD_LIST(field);
	GtkWidget *sw;
	GtkWidget *listview = NULL;
	GtkSelectionModel *sel = NULL;
	GtkListItemFactory *factory = NULL;
	GListStore *store = NULL;
	guint index = 0;
	GList *l;
	gboolean has_icons;

	has_icons = purple_request_field_list_has_icons(listfield);

	/* Create the list store */
	store = g_list_store_new(G_TYPE_OBJECT);
	if(purple_request_field_list_get_multi_select(listfield)) {
		sel = GTK_SELECTION_MODEL(gtk_multi_selection_new(G_LIST_MODEL(store)));
	} else {
		sel = GTK_SELECTION_MODEL(gtk_single_selection_new(G_LIST_MODEL(store)));
	}

	/* Create the row factory. */
	factory = gtk_signal_list_item_factory_new();
	g_signal_connect(factory, "setup", G_CALLBACK(setup_list_field_listitem_cb),
	                 field);
	g_signal_connect(factory, "bind", G_CALLBACK(bind_list_field_listitem_cb),
	                 field);

	/* Create the list view */
	listview = gtk_list_view_new(sel, factory);

	if (has_icons) {
		gtk_widget_set_size_request(listview, 200, 400);
	}

	for(index = 0, l = purple_request_field_list_get_items(listfield);
	    l != NULL;
	    index++, l = l->next)
	{
		PurpleKeyValuePair *item = l->data;
		const char *text = (const char *)item->key;
		GObject *wrapper = NULL;

		wrapper = g_object_new(G_TYPE_OBJECT, NULL);
		g_list_store_append(store, wrapper);

		g_object_set_data(wrapper, "data",
		                  purple_request_field_list_get_data(listfield, text));
		g_object_set_data_full(wrapper, "text", g_strdup(text), g_free);

		if(has_icons) {
			const char *icon_path = (const char *)item->value;
			GdkTexture *texture = NULL;

			if(icon_path) {
				GError *error = NULL;

				texture = gdk_texture_new_from_filename(icon_path, &error);
				if(error != NULL) {
					purple_debug_warning("requests",
					                     "failed to load icon from %s: %s",
					                     icon_path, error->message);
					g_clear_error(&error);
				}
			}

			g_object_set_data_full(wrapper, "texture", texture, g_object_unref);
		}

		if(purple_request_field_list_is_selected(listfield, text)) {
			gtk_selection_model_select_item(sel, index, FALSE);
		}

		g_object_unref(wrapper);
	}

	/*
	 * We only want to catch changes made by the user, so it's important
	 * that we wait until after the list is created to connect this
	 * handler.  If we connect the handler before the loop above and
	 * there are multiple items selected, then selecting the first item
	 * in the view causes list_field_select_changed_cb to be triggered
	 * which clears out the rest of the list of selected items.
	 */
	g_signal_connect(G_OBJECT(sel), "selection-changed",
	                 G_CALLBACK(list_field_select_changed_cb), field);

	sw = gtk_scrolled_window_new();
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
	                               GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(sw), listview);
	return sw;
}

static void *
pidgin_request_fields(const char *title, const char *primary,
	const char *secondary, PurpleRequestPage *page, const char *ok_text,
	GCallback ok_cb, const char *cancel_text, GCallback cancel_cb,
	PurpleRequestCommonParameters *cpar, void *user_data)
{
	PidginRequestData *data;
	GtkWidget *win;
	GtkWidget *content = NULL;
	GtkWidget *cbox = NULL;
	GtkWidget *action_area = NULL;
	GtkWidget *button = NULL;
	GtkWidget *ok_button = NULL;
	GtkWidget *page_widget = NULL;
	AdwPreferencesGroup *group_widget = NULL;
	GtkWidget *vbox = NULL;
	GtkWidget *img;
	GSList *extra_actions;
	guint n_groups;

	data            = g_new0(PidginRequestData, 1);
	data->type      = PURPLE_REQUEST_FIELDS;
	data->user_data = user_data;
	data->page = page;

	data->dialog = win = gtk_window_new();
	if(title != NULL) {
		gtk_window_set_title(GTK_WINDOW(win), title);
	} else {
		gtk_window_set_title(GTK_WINDOW(win), PIDGIN_ALERT_TITLE);
	}
	gtk_window_set_resizable(GTK_WINDOW(win), TRUE);
	gtk_window_set_default_size(GTK_WINDOW(win), 480, -1);

	content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
	gtk_window_set_child(GTK_WINDOW(win), content);

	page_widget = adw_preferences_page_new();
	gtk_widget_set_vexpand(page_widget, TRUE);
	gtk_box_append(GTK_BOX(content), page_widget);

	/* Setup the general info box. */
	group_widget = ADW_PREFERENCES_GROUP(adw_preferences_group_new());
	adw_preferences_page_add(ADW_PREFERENCES_PAGE(page_widget), group_widget);
	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
	gtk_widget_set_margin_bottom(vbox, 12);
	adw_preferences_group_add(group_widget, vbox);

	/* Dialog icon. */
	img = pidgin_request_dialog_icon(PURPLE_REQUEST_FIELDS, cpar);
	if(gtk_image_get_storage_type(GTK_IMAGE(img)) == GTK_IMAGE_ICON_NAME) {
		gtk_image_set_pixel_size(GTK_IMAGE(img), 64);
	}
	gtk_box_append(GTK_BOX(vbox), img);

	pidgin_widget_decorate_account(vbox,
	                               purple_request_cpar_get_account(cpar));

	cbox = gtk_center_box_new();
	gtk_orientable_set_orientation(GTK_ORIENTABLE(cbox),
	                               GTK_ORIENTATION_HORIZONTAL);
	gtk_widget_set_margin_start(cbox, 6);
	gtk_widget_set_margin_end(cbox, 6);
	gtk_widget_set_margin_top(cbox, 6);
	gtk_widget_set_margin_bottom(cbox, 6);
	gtk_box_append(GTK_BOX(content), cbox);
	action_area = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
	gtk_widget_set_halign(action_area, GTK_ALIGN_END);
	gtk_center_box_set_end_widget(GTK_CENTER_BOX(cbox), action_area);

	pidgin_request_add_help(data, cbox, cpar);

	/* Add responses and callbacks. */
	extra_actions = purple_request_cpar_get_extra_actions(cpar);
	data->action_widgets = g_ptr_array_sized_new(2 + g_slist_length(extra_actions));
	data->action_cbs = g_ptr_array_sized_new(2 + g_slist_length(extra_actions));

	for(; extra_actions != NULL; extra_actions = extra_actions->next) {
		PurpleKeyValuePair *extra_action = extra_actions->data;

		button = gtk_button_new_with_label(extra_action->key);
		g_signal_connect(button, "clicked", G_CALLBACK(multifield_response_cb),
		                 data);
		gtk_box_append(GTK_BOX(action_area), button);
		g_ptr_array_add(data->action_widgets, button);
		g_ptr_array_add(data->action_cbs, extra_action->value);
	}

	/* Cancel button */
	button = gtk_button_new_with_label(cancel_text);
	g_signal_connect(button, "clicked", G_CALLBACK(multifield_response_cb),
	                 data);
	gtk_box_append(GTK_BOX(action_area), button);
	gtk_window_set_default_widget(GTK_WINDOW(win), button);
	g_ptr_array_add(data->action_widgets, button);
	g_ptr_array_add(data->action_cbs, cancel_cb);

	/* OK button */
	if(ok_text != NULL) {
		ok_button = gtk_button_new_with_label(ok_text);
		g_signal_connect(ok_button, "clicked",
		                 G_CALLBACK(multifield_response_cb), data);
		gtk_box_append(GTK_BOX(action_area), ok_button);
		gtk_window_set_default_widget(GTK_WINDOW(win), ok_button);
		g_ptr_array_add(data->action_widgets, ok_button);
		g_ptr_array_add(data->action_cbs, ok_cb);
	}

	if(primary != NULL) {
		GtkWidget *label = gtk_label_new(primary);
		gtk_label_set_wrap(GTK_LABEL(label), TRUE);
		gtk_widget_add_css_class(label, "title-1");
		adw_preferences_group_add(group_widget, label);
	}

	if(secondary != NULL) {
		GtkWidget *label = gtk_label_new(secondary);
		gtk_label_set_wrap(GTK_LABEL(label), TRUE);
		adw_preferences_group_add(group_widget, label);
	}

	n_groups = g_list_model_get_n_items(G_LIST_MODEL(page));
	for(guint group_index = 0; group_index < n_groups; group_index++) {
		PurpleRequestGroup *group = NULL;
		guint n_fields = 0;
		GSList *username_widgets = NULL;
		GtkWidget *account_hint = NULL;

		group = g_list_model_get_item(G_LIST_MODEL(page), group_index);
		group_widget = ADW_PREFERENCES_GROUP(adw_preferences_group_new());
		adw_preferences_group_set_title(group_widget,
		                                purple_request_group_get_title(group));
		adw_preferences_page_add(ADW_PREFERENCES_PAGE(page_widget),
		                         group_widget);

		n_fields = g_list_model_get_n_items(G_LIST_MODEL(group));
		for(guint field_index = 0; field_index < n_fields; field_index++) {
			PurpleRequestField *field = NULL;
			GtkWidget *widget = NULL;
			gboolean was_handled_by_create = FALSE;

			field = g_list_model_get_item(G_LIST_MODEL(group), field_index);
			if(!purple_request_field_is_visible(field)) {
				g_object_unref(field);
				continue;
			}

			if(PURPLE_IS_REQUEST_FIELD_LABEL(field)) {
				widget = create_label_field();
				was_handled_by_create = TRUE;
			} else if(PURPLE_IS_REQUEST_FIELD_STRING(field)) {
				PurpleKeyValuePair *username_hint = NULL;

				widget = create_string_field(field, &username_hint);
				was_handled_by_create = TRUE;

				if(username_hint != NULL) {
					username_widgets = g_slist_prepend(username_widgets,
					                                   username_hint);
				}
			} else if(PURPLE_IS_REQUEST_FIELD_INT(field)) {
				PurpleKeyValuePair *username_hint = NULL;

				widget = create_int_field(field, &username_hint);
				was_handled_by_create = TRUE;

				if(username_hint != NULL) {
					username_widgets = g_slist_prepend(username_widgets,
					                                   username_hint);
				}
			} else if(PURPLE_IS_REQUEST_FIELD_BOOL(field)) {
				widget = create_bool_field(field);
				was_handled_by_create = TRUE;
			} else if(PURPLE_IS_REQUEST_FIELD_CHOICE(field)) {
				widget = create_choice_field(field);
			} else if(PURPLE_IS_REQUEST_FIELD_LIST(field)) {
				widget = create_list_field(field);
			} else if(PURPLE_IS_REQUEST_FIELD_IMAGE(field)) {
				widget = create_image_field(field);
			} else if(PURPLE_IS_REQUEST_FIELD_ACCOUNT(field)) {
				widget = create_account_field(field, &account_hint);
				was_handled_by_create = TRUE;
			} else {
				g_warning("Unhandled field type: %s",
				          G_OBJECT_TYPE_NAME(field));
				g_object_unref(field);
				continue;
			}

			g_object_bind_property(field, "tooltip", widget, "tooltip-text",
			                       G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
			g_object_bind_property(field, "sensitive", widget, "sensitive",
			                       G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);

			if(!was_handled_by_create) {
				GtkWidget *row = NULL;

				gtk_widget_set_valign(widget, GTK_ALIGN_CENTER);

				row = adw_action_row_new();
				adw_action_row_add_suffix(ADW_ACTION_ROW(row), widget);
				widget = row;
			}

			g_object_bind_property(field, "label", widget, "title",
			                       G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
			adw_preferences_row_set_use_underline(ADW_PREFERENCES_ROW(widget),
			                                      TRUE);

			adw_preferences_group_add(group_widget, widget);

			g_object_unref(field);
		}

		g_slist_free_full(username_widgets,
		                  (GDestroyNotify)purple_key_value_pair_free);

		g_object_unref(group);
	}

	if(ok_button != NULL) {
		g_object_bind_property(page, "valid", ok_button, "sensitive", 0);
	}

	gtk_widget_set_visible(win, TRUE);

	return data;
}

static void
pidgin_request_close_request(PurpleRequestType type, gpointer ui_handle) {
	PidginRequestData *data = ui_handle;

	if(type != PURPLE_REQUEST_FIELDS) {
		/* This shouldn't happen as we don't support creating any others. */
		return;
	}

	g_ptr_array_free(data->action_widgets, TRUE);
	g_ptr_array_free(data->action_cbs, TRUE);
	gtk_window_destroy(GTK_WINDOW(data->dialog));
	g_free(data);
}

GtkWindow *
pidgin_request_get_dialog_window(void *ui_handle)
{
	PidginRequestData *data = ui_handle;

	g_return_val_if_fail(
		purple_request_is_valid_ui_handle(data, NULL), NULL);

	if (data->type == PURPLE_REQUEST_FILE ||
	    data->type == PURPLE_REQUEST_FOLDER) {
		/* Not a GtkWidget, but a GtkFileDialog. Eventually this function
		 * should not be needed, once we don't need to auto-parent. */
		return NULL;
	}

	return GTK_WINDOW(data->dialog);
}

static PurpleRequestUiOps ops = {
	.request_fields = pidgin_request_fields,
	.close_request = pidgin_request_close_request,
};

PurpleRequestUiOps *
pidgin_request_get_ui_ops(void)
{
	return &ops;
}

void *
pidgin_request_get_handle(void)
{
	static int handle;

	return &handle;
}

void
pidgin_request_init(void)
{
}

void
pidgin_request_uninit(void)
{
}
