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 { gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';

import { getHljsLanguages, getHljsPath } from '../../common/constants.js';
import { registerClass } from '../../common/gjs.js';
import { Icon } from '../../common/icons.js';
import { downloadHljsLanguage } from './dependencies.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);
	};

let Language = class Language extends GObject.Object {
	languageName;
	language;
	hash;
	file;
	isDefault;
	_installed;

	constructor(languageName, language, hash, file, isDefault) {
		super();
		this.languageName = languageName;
		this.language = language;
		this.hash = hash;
		this.file = file;
		this.isDefault = isDefault;
		this._installed = file.query_exists(null);
	}

	get installed() {
		return this._installed;
	}

	checkInstalled() {
		const installed = this.file.query_exists(null);
		if (installed === this._installed) return installed;
		this._installed = installed;
		this.notify('installed');
		return installed;
	}
};
Language = __decorate(
	[
		registerClass({
			Properties: {
				'language-name': GObject.ParamSpec.string('language-name', null, null, GObject.ParamFlags.READABLE, ''),
				'language': GObject.ParamSpec.string('language', null, null, GObject.ParamFlags.READABLE, ''),
				'hash': GObject.ParamSpec.string('hash', null, null, GObject.ParamFlags.READABLE, ''),
				'file': GObject.ParamSpec.object('file', null, null, GObject.ParamFlags.READABLE, Gio.File),
				'is-default': GObject.ParamSpec.boolean('is-default', null, null, GObject.ParamFlags.READABLE, false),
				'installed': GObject.ParamSpec.boolean('installed', null, null, GObject.ParamFlags.READABLE, false),
			},
		}),
	],
	Language,
);
let LanguageFilter = class LanguageFilter extends Gtk.Filter {
	_search = '';
	_installed = false;

	constructor() {
		super();
	}

	get search() {
		return this._search;
	}

	set search(search) {
		search = search.toLocaleLowerCase();
		if (search === this.search) return;
		let change;
		if (search === '') {
			change = Gtk.FilterChange.LESS_STRICT;
		} else if (this.search === '') {
			change = Gtk.FilterChange.MORE_STRICT;
		} else if (search.includes(this.search)) {
			change = Gtk.FilterChange.MORE_STRICT;
		} else if (this.search.includes(search)) {
			change = Gtk.FilterChange.LESS_STRICT;
		} else {
			change = Gtk.FilterChange.DIFFERENT;
		}
		this._search = search;
		this.changed(change);
		this.notify('search');
	}

	get installed() {
		return this._installed;
	}

	set installed(installed) {
		if (installed === this.installed) return;
		this._installed = installed;
		const change = installed ? Gtk.FilterChange.MORE_STRICT : Gtk.FilterChange.LESS_STRICT;
		this.changed(change);
		this.notify('installed');
	}

	vfunc_get_strictness() {
		return this.search !== '' || this.installed ? Gtk.FilterMatch.SOME : Gtk.FilterMatch.ALL;
	}

	vfunc_match(item) {
		if (!(item instanceof Language)) return false;
		if (this.installed && !(item.installed || item.isDefault)) return false;
		const name = item.languageName.toLocaleLowerCase();
		if (name.includes(this.search)) return true;
		const language = item.language.toLocaleLowerCase();
		return language.includes(this.search);
	}
};
LanguageFilter = __decorate(
	[
		registerClass({
			Properties: {
				search: GObject.ParamSpec.string('search', null, null, GObject.ParamFlags.READWRITE, ''),
				installed: GObject.ParamSpec.boolean('installed', null, null, GObject.ParamFlags.READWRITE, false),
			},
		}),
	],
	LanguageFilter,
);
Gio._promisify(Gio.File.prototype, 'delete_async');
let LanguageWidget = class LanguageWidget extends Gtk.ListBox {
	prefs;
	language;
	window;
	_busy = false;
	_check;
	_delete;
	_spinner;
	_cancellable = new Map();

	constructor(prefs, language, window) {
		super({
			selection_mode: Gtk.SelectionMode.NONE,
			css_classes: ['boxed-list'],
		});
		this.prefs = prefs;
		this.language = language;
		this.window = window;
		this.language.connect('notify', this.updateVisibility.bind(this));
		const row = new Adw.ActionRow({
			title: language.language,
			activatable: !language.isDefault,
		});
		row.connect('activated', this.onActivate.bind(this));
		this.append(row);
		this._check = new Gtk.Image({
			css_classes: [language.isDefault ? 'blue' : 'success'],
			icon_name: language.isDefault ? Icon.CheckOutline : Icon.Check,
			visible: false,
		});
		row.add_suffix(this._check);
		this._delete = new Gtk.Image({
			css_classes: ['error'],
			icon_name: Icon.Delete,
			visible: false,
		});
		row.add_suffix(this._delete);
		this._spinner = new Adw.Spinner({ visible: false });
		row.add_suffix(this._spinner);
		this.updateVisibility();

		// Cancel connections
		this.connect('destroy', () => this._cancellable.forEach((c) => c.cancel()));
	}

	vfunc_state_flags_changed(previous_state_flags) {
		super.vfunc_state_flags_changed(previous_state_flags);
		this.updateVisibility();
	}

	updateVisibility() {
		if (this._busy) {
			this._check.visible = false;
			this._delete.visible = false;
			this._spinner.visible = true;
			return;
		}
		this._check.visible = this.language.installed || this.language.isDefault;
		this._spinner.visible = false;
		if (!this.language.installed) return;
		const stateFlags = this.get_state_flags();
		const active = (stateFlags & (Gtk.StateFlags.ACTIVE | Gtk.StateFlags.PRELIGHT | Gtk.StateFlags.FOCUSED)) > 0;
		this._check.visible = !active;
		this._delete.visible = active;
	}

	async onActivate() {
		if (this.language.isDefault || this._busy) return;
		this._busy = true;
		this.updateVisibility();
		if (!this.language.installed) {
			// Install
			const cancellable = new Gio.Cancellable();
			this._cancellable.set(this.language.languageName, cancellable);
			const success = await downloadHljsLanguage(
				this.prefs,
				this.language.languageName,
				this.language.hash,
				this.language.file,
				cancellable,
			);
			this._cancellable.delete(this.language.languageName);
			const installed = success ? this.language.checkInstalled() : false;
			if (installed) this.emit('changed');
			this.window.add_toast(
				new Adw.Toast({
					title: installed
						? _('Successfully Installed %s').format(this.language.language)
						: _('Error Installing %s').format(this.language.language),
					priority: Adw.ToastPriority.HIGH,
				}),
			);
		} else {
			// Uninstall
			let success = false;
			try {
				await this.language.file.delete_async(GLib.PRIORITY_DEFAULT, null);
				success = true;
			} catch (err) {
				console.error(err);
			}
			const uninstalled = success ? !this.language.checkInstalled() : false;
			if (uninstalled) this.emit('changed');
			this.window.add_toast(
				new Adw.Toast({
					title: uninstalled
						? _('Successfully Uninstalled %s').format(this.language.language)
						: _('Error Uninstalling %s').format(this.language.language),
					priority: Adw.ToastPriority.HIGH,
				}),
			);
		}
		this._busy = false;
		this.updateVisibility();
	}
};
LanguageWidget = __decorate(
	[
		registerClass({
			Properties: {
				installed: GObject.ParamSpec.boolean('installed', null, null, GObject.ParamFlags.READABLE, false),
			},
			Signals: {
				changed: {},
			},
		}),
	],
	LanguageWidget,
);
let HighlightJsPage = class HighlightJsPage extends Adw.NavigationPage {
	prefs;
	window;
	_page;
	_list;
	_filter;

	constructor(prefs, window) {
		super({
			title: _('Manage Highlight.js'),
		});
		this.prefs = prefs;
		this.window = window;
		const toolbarView = new Adw.ToolbarView();
		toolbarView.add_top_bar(new Adw.HeaderBar());
		this.child = toolbarView;

		// Content
		this._page = new Adw.PreferencesPage();
		toolbarView.content = this._page;
		const languagesGroup = new Adw.PreferencesGroup({
			title: _('Languages'),
		});
		this._page.add(languagesGroup);
		this._list = new Gtk.FlowBox({
			css_classes: ['hljs-languages'],
			selection_mode: Gtk.SelectionMode.NONE,
			homogeneous: true,
			max_children_per_line: 2,
			column_spacing: 6,
			row_spacing: 6,
		});
		languagesGroup.add(this._list);
		this._filter = new LanguageFilter();

		// Search
		const suffix = new Gtk.Box({ spacing: 6 });
		languagesGroup.set_header_suffix(suffix);
		const searchEntry = new Gtk.SearchEntry({
			placeholder_text: _('Search'),
		});
		searchEntry.bind_property('text', this._filter, 'search', GObject.BindingFlags.DEFAULT);
		suffix.append(searchEntry);
		const installed = new Gtk.ToggleButton({ icon_name: Icon.CheckOutline });
		installed.bind_property('active', this._filter, 'installed', GObject.BindingFlags.DEFAULT);
		suffix.append(installed);
	}

	set hljs(hljs) {
		if (hljs) this.initLanguages().catch(() => {});
	}

	async initLanguages() {
		let defaultLanguages = [];
		try {
			const hljs = await import(getHljsPath(this.prefs).get_uri());
			defaultLanguages = hljs.default.listLanguages();
		} catch {
			// Ignore
		}
		const listModel = Gio.ListStore.new(Language.$gtype);
		for (const [name, language, hash, file, system] of getHljsLanguages(this.prefs)) {
			const isDefault = defaultLanguages.includes(name) || system;
			listModel.append(new Language(name, language, hash, file, isDefault));
		}
		listModel.sort((a, b) => a.language.localeCompare(b.language));
		const filterListModel = new Gtk.FilterListModel({ model: listModel, filter: this._filter });
		this._list.bind_model(filterListModel, (language) => {
			const widget = new LanguageWidget(this.prefs, language, this.window);
			return new Gtk.FlowBoxChild({ child: widget, focusable: false });
		});
	}
};
HighlightJsPage = __decorate(
	[
		registerClass({
			Properties: {
				hljs: GObject.ParamSpec.boolean('hljs', null, null, GObject.ParamFlags.WRITABLE, false),
			},
		}),
	],
	HighlightJsPage,
);
let DependenciesSettings = class DependenciesSettings extends Adw.PreferencesGroup {
	window;
	_highlightJsPage;

	constructor(prefs, window) {
		super({
			title: _('Dependencies'),
		});
		this.window = window;
		this._highlightJsPage = new HighlightJsPage(prefs, window);
		this.bind_property('hljs', this._highlightJsPage, 'hljs', GObject.BindingFlags.DEFAULT);
		const manageHighlightJs = new Adw.ActionRow({
			title: _('Manage Highlight.js'),
			subtitle: _('Manage the Highlight.js installation'),
			activatable: true,
		});
		this.bind_property('hljs', manageHighlightJs, 'sensitive', GObject.BindingFlags.DEFAULT);
		manageHighlightJs.add_suffix(new Gtk.Image({ icon_name: Icon.Next }));
		this.add(manageHighlightJs);
		manageHighlightJs.connect('activated', () => window.push_subpage(this._highlightJsPage));
	}

	openHighlightJsPage() {
		this.window.push_subpage(this._highlightJsPage);
	}
};
DependenciesSettings = __decorate(
	[
		registerClass({
			Properties: {
				hljs: GObject.ParamSpec.boolean('hljs', null, null, GObject.ParamFlags.READWRITE, false),
			},
		}),
	],
	DependenciesSettings,
);

export { DependenciesSettings };
