import Adw from 'gi://Adw';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gio from 'gi://Gio';
import Gtk from 'gi://Gtk';
import Pango from 'gi://Pango';

import { gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';

import { registerClass } from '../../common/gjs.js';
import { Icon } from '../../common/icons.js';

var __decorate =
	(this && this.__decorate) ||
	function (decorators, target, key, desc) {
		var c = arguments.length,
			r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc,
			d;
		if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function')
			r = Reflect.decorate(decorators, target, key, desc);
		else
			for (var i = decorators.length - 1; i >= 0; i--)
				if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
		return (c > 3 && r && Object.defineProperty(target, key, r), r);
	};

Gio._promisify(Adw.AlertDialog.prototype, 'choose');
let AppSelectionPopover = class AppSelectionPopover extends Gtk.Popover {
	_searchEntry;
	_scrollWindow;

	constructor() {
		super({
			css_classes: ['app-selection-popover'],
		});
		const box = new Gtk.Box({
			orientation: Gtk.Orientation.VERTICAL,
		});
		this.child = box;
		this._searchEntry = new Gtk.SearchEntry({
			placeholder_text: _('Search apps'),
			margin_top: 6,
			margin_bottom: 6,
			margin_start: 6,
			margin_end: 6,
		});
		box.append(this._searchEntry);
		const separator = new Gtk.Separator({ orientation: Gtk.Orientation.HORIZONTAL });
		box.append(separator);
		const stack = new Gtk.Stack();
		box.append(stack);
		this._scrollWindow = new Gtk.ScrolledWindow({
			height_request: 300,
			width_request: 300,
			hscrollbar_policy: Gtk.PolicyType.NEVER,
			vscrollbar_policy: Gtk.PolicyType.AUTOMATIC,
			focusable: false,
		});
		stack.add_named(this._scrollWindow, 'list');
		const list = new Gtk.ListBox({
			css_classes: ['listview'],
			selection_mode: Gtk.SelectionMode.NONE,
			valign: Gtk.Align.START,
			margin_top: 6,
			margin_bottom: 6,
			margin_start: 6,
			margin_end: 6,
		});
		this._scrollWindow.child = list;
		const placeholder = new Adw.StatusPage({
			title: _('No Apps Found'),
			icon_name: Icon.Search,
			css_classes: ['compact', 'dim-label'],
			height_request: 300,
		});
		stack.add_named(placeholder, 'placeholder');

		// Bind model
		const model = new Gio.ListStore({ item_type: Gio.AppInfo.$gtype });
		const apps = Gio.AppInfo.get_all()
			.filter((info) => info.should_show() && 'get_startup_wm_class' in info)
			.sort((info1, info2) => info1.get_display_name().localeCompare(info2.get_display_name()));
		model.splice(0, 0, apps);
		const filter = new Gtk.StringFilter({
			expression: Gtk.ClosureExpression.new(GObject.TYPE_STRING, (info) => info.get_display_name(), null),
			ignore_case: true,
			match_mode: Gtk.StringFilterMatchMode.SUBSTRING,
		});
		this._searchEntry.connect('search-changed', () => filter.set_search(this._searchEntry.text));
		const filterModel = new Gtk.FilterListModel({
			model,
			filter,
			incremental: true,
		});
		filterModel.connect('notify::n-items', () => {
			const n = filterModel.n_items;
			stack.set_visible_child_name(n !== 0 ? 'list' : 'placeholder');
		});
		list.connect('row-activated', (_list, row) => {
			const item = filterModel.get_item(row.get_index());
			this.emit('activate', item);
			this.hide();
		});
		list.bind_model(filterModel, (info) => {
			const row = new Gtk.ListBoxRow({
				activatable: true,
			});
			const rowBox = new Gtk.Box({
				spacing: 6,
				margin_top: 3,
				margin_bottom: 3,
				margin_start: 3,
				margin_end: 3,
			});
			row.child = rowBox;
			rowBox.append(
				new Gtk.Image({
					gicon: info.get_icon() ?? Gio.Icon.new_for_string('application-x-executable'),
					icon_size: Gtk.IconSize.LARGE,
				}),
			);
			rowBox.append(
				new Gtk.Label({
					label: info.get_display_name(),
					halign: Gtk.Align.START,
					ellipsize: Pango.EllipsizeMode.END,
				}),
			);
			return row;
		});
	}

	vfunc_map() {
		super.vfunc_map();
		this._searchEntry.text = '';
		this._scrollWindow.vadjustment.value = 0;
	}
};
AppSelectionPopover = __decorate(
	[
		registerClass({
			Signals: {
				activate: {
					param_types: [Gio.AppInfo.$gtype],
				},
			},
		}),
	],
	AppSelectionPopover,
);
let WMClassExclusionDialog = class WMClassExclusionDialog extends Adw.AlertDialog {
	_entry;

	constructor() {
		super({
			heading: _('Add App Exclusion'),
			body: _('Enter the WM Class of the application you want to exclude:'),
		});
		this.add_response('cancel', _('Cancel'));
		this.set_close_response('cancel');
		this.add_response('add', _('Add'));
		this.set_response_appearance('add', Adw.ResponseAppearance.SUGGESTED);
		this.set_response_enabled('add', false);

		// Entry
		const list = new Gtk.ListBox({ css_classes: ['boxed-list'], selection_mode: Gtk.SelectionMode.NONE });
		this.extra_child = list;
		this._entry = new Adw.EntryRow({
			title: _('WM Class'),
		});
		this.focus_widget = this._entry;
		list.append(this._entry);
		const popover = new AppSelectionPopover();
		popover.connect('activate', (_popover, info) => {
			const wmClass = info.get_startup_wm_class() ?? info.get_id()?.replace(/.desktop$/, '');
			if (wmClass) this._entry.text = wmClass;
		});
		const appsButton = new Gtk.MenuButton({
			icon_name: Icon.ViewList,
			valign: Gtk.Align.CENTER,
			css_classes: ['flat'],
			popover,
		});
		this._entry.add_suffix(appsButton);
		this._entry.connect('changed', () => {
			this.set_response_enabled('add', this.exclusion.length > 0);
		});
	}

	get exclusion() {
		return this._entry.text;
	}
};
WMClassExclusionDialog = __decorate(
	[
		registerClass({
			Properties: {
				exclusion: GObject.ParamSpec.string('exclusion', null, null, GObject.ParamFlags.READABLE, ''),
			},
		}),
	],
	WMClassExclusionDialog,
);
let AppExclusionsGroup = class AppExclusionsGroup extends Adw.PreferencesGroup {
	_listModel = new Gtk.StringList();

	constructor(window, title) {
		super({ title });
		const addExclusion = new Gtk.Button({
			icon_name: Icon.Add,
			valign: Gtk.Align.CENTER,
			css_classes: ['flat'],
		});
		this.header_suffix = addExclusion;
		const list = new Gtk.ListBox({ css_classes: ['boxed-list'], selection_mode: Gtk.SelectionMode.NONE });
		this.add(list);
		list.bind_model(this._listModel, (item) => {
			const row = new Adw.ActionRow({ title: item.string });
			const deleteButton = new Gtk.Button({
				icon_name: Icon.Delete,
				valign: Gtk.Align.CENTER,
				css_classes: ['flat', 'destructive-action'],
			});
			row.add_suffix(deleteButton);
			deleteButton.connect('clicked', () => {
				this._listModel.remove(row.get_index());
				this.notify('exclusions');
			});
			return row;
		});
		list.set_placeholder(
			new Adw.ActionRow({
				title: _('No Exclusions'),
				sensitive: false,
			}),
		);

		// Dialog
		addExclusion.connect('clicked', async () => {
			const addExclusionDialog = new WMClassExclusionDialog();
			const response = await addExclusionDialog.choose(window, null);
			if (response === 'add') {
				this._listModel.append(addExclusionDialog.exclusion);
				this.notify('exclusions');
			}
		});
	}

	get exclusions() {
		const strings = [];
		for (let i = 0; i < this._listModel.n_items; i++) {
			const string = this._listModel.get_string(i);
			if (string) strings.push(string);
		}
		return strings;
	}

	set exclusions(exclusions) {
		this._listModel.splice(0, this._listModel.n_items, exclusions);
		this.notify('exclusions');
	}
};
AppExclusionsGroup = __decorate(
	[
		registerClass({
			Properties: {
				exclusions: GObject.ParamSpec.boxed(
					'exclusions',
					null,
					null,
					GObject.ParamFlags.READWRITE,
					GLib.strv_get_type(),
				),
			},
		}),
	],
	AppExclusionsGroup,
);
let AppExclusionsPage = class AppExclusionsPage extends Adw.NavigationPage {
	constructor(prefs, window) {
		super({
			title: _('App Exclusions'),
		});
		const toolbarView = new Adw.ToolbarView();
		toolbarView.add_top_bar(new Adw.HeaderBar());
		this.child = toolbarView;
		const page = new Adw.PreferencesPage();
		toolbarView.content = page;

		// App Exclusions
		const wmClassExclusionsGroup = new AppExclusionsGroup(window, _('App Exclusions'));
		page.add(wmClassExclusionsGroup);

		// Bind properties
		const settings = prefs.getSettings();
		settings.bind('wmclass-exclusions', wmClassExclusionsGroup, 'exclusions', Gio.SettingsBindFlags.DEFAULT);
	}
};
AppExclusionsPage = __decorate([registerClass()], AppExclusionsPage);

export { AppExclusionsPage };

let AppExclusionSettings = class AppExclusionSettings extends Adw.PreferencesGroup {
	constructor(prefs, window) {
		super({
			title: _('Exclusions'),
		});
		const appExclusionsPage = new AppExclusionsPage(prefs, window);
		const manageExclusions = new Adw.ActionRow({
			title: _('Manage App Exclusions'),
			subtitle: _('Manage apps that will be excluded from clipboard history'),
			activatable: true,
		});
		manageExclusions.add_suffix(new Gtk.Image({ icon_name: Icon.Next }));
		this.add(manageExclusions);
		manageExclusions.connect('activated', () => window.push_subpage(appExclusionsPage));
	}
};
AppExclusionSettings = __decorate([registerClass()], AppExclusionSettings);

export { AppExclusionSettings };
