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 Soup from 'gi://Soup?version=3.0';

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

import {
	HljsSha512,
	HljsUrls,
	UserAgent,
	getDataPath,
	getHljsLanguageUrls,
	getHljsPath,
} from '../../common/constants.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);
	};

export async function checkGda(prefs) {
	try {
		const Gda = (await import('gi://Gda')).default;

		// Check if SQLite provider is installed
		Gda.Config.get_provider('SQLite');
		return true;
	} catch (err) {
		prefs.getLogger().error(err.message);
		return false;
	}
}

async function checkGSound() {
	try {
		await import('gi://GSound');
		return true;
	} catch {
		return false;
	}
}

async function checkHighlightJS(prefs) {
	try {
		await import(getHljsPath(prefs).get_uri());
		return true;
	} catch (error) {
		return false;
	}
}

let GuideDialog = class GuideDialog extends Adw.Dialog {
	constructor(title, subtitle, guideTitle, fedora, arch, ubuntu, opensuse) {
		super();
		const toolbarView = new Adw.ToolbarView({
			extend_content_to_top_edge: true,
		});
		toolbarView.add_top_bar(new Adw.HeaderBar());
		this.set_child(toolbarView);
		const content = new Gtk.Box({
			orientation: Gtk.Orientation.VERTICAL,
			margin_top: 36,
			margin_bottom: 24,
			margin_start: 24,
			margin_end: 24,
			spacing: 24,
		});
		toolbarView.content = new Gtk.ScrolledWindow({
			propagate_natural_height: true,
			propagate_natural_width: true,
			hscrollbar_policy: Gtk.PolicyType.NEVER,
			child: content,
		});

		// Title
		content.append(
			new Gtk.Label({
				label: title,
				css_classes: ['title-2'],
			}),
		);

		// Subtitle
		content.append(
			new Gtk.Label({
				label: subtitle,
				xalign: 0,
				max_width_chars: 0,
				wrap: true,
				css_classes: ['dim-label'],
			}),
		);

		// Install guide
		const installGuide = new Adw.PreferencesGroup({
			title: guideTitle,
		});
		content.append(installGuide);
		installGuide.add(
			new Adw.ActionRow({
				title: 'Fedora',
				subtitle: `sudo dnf install ${fedora}`,
				subtitle_selectable: true,
				css_classes: ['property'],
			}),
		);
		installGuide.add(
			new Adw.ActionRow({
				title: 'Arch Linux',
				subtitle: `sudo pacman -S ${arch}`,
				subtitle_selectable: true,
				css_classes: ['property'],
			}),
		);
		installGuide.add(
			new Adw.ActionRow({
				title: 'Ubuntu/Debian',
				subtitle: `sudo apt install ${ubuntu}`,
				subtitle_selectable: true,
				css_classes: ['property'],
			}),
		);
		installGuide.add(
			new Adw.ActionRow({
				title: 'OpenSUSE',
				subtitle: `sudo zypper install ${opensuse}`,
				subtitle_selectable: true,
				css_classes: ['property'],
			}),
		);
	}
};
GuideDialog = __decorate([registerClass()], GuideDialog);
let GdaDialog = class GdaDialog extends GuideDialog {
	constructor() {
		super(
			_('Libgda Not Installed'),
			_(
				'Libgda is required to store clipboard history between sessions. ' +
					'Install either libgda 5.0 or 6.0 with SQLite support to use this feature. ' +
					'After installing libgda you will need to log out or restart your system.',
			),
			_('Install Libgda'),
			'libgda libgda-sqlite',
			'libgda6',
			'gir1.2-gda-5.0',
			'libgda-6_0-sqlite typelib-1_0-Gda-6_0',
		);
	}
};
GdaDialog = __decorate([registerClass()], GdaDialog);

export { GdaDialog };

let HljsDialog = class HljsDialog extends Adw.AlertDialog {
	constructor(prefs) {
		super({
			heading: _('Highlight.js Not Installed'),
			body: _('Highlight.js is required to display syntax highlighted code.'),
		});
		const expander = new Gtk.Expander({
			label: _('Manual Installation'),
		});
		this.extra_child = expander;
		const box = new Gtk.Box({
			orientation: Gtk.Orientation.VERTICAL,
			margin_top: 8,
			spacing: 12,
		});
		expander.child = box;
		box.append(
			new Gtk.Label({
				label: _(
					'You can manually install highlight.js by downloading highlight.min.js from "Url" to "Install Location".',
				),
				wrap: true,
				halign: Gtk.Align.FILL,
				xalign: 0,
			}),
		);
		const list = new Gtk.ListBox({
			css_classes: ['boxed-list'],
			selection_mode: Gtk.SelectionMode.NONE,
		});
		box.append(list);
		list.append(
			new Adw.ActionRow({
				css_classes: ['property'],
				title: _('Url'),
				subtitle: HljsUrls[0],
				subtitle_selectable: true,
				activatable: false,
			}),
		);
		list.append(
			new Adw.ActionRow({
				css_classes: ['property'],
				title: _('Install Location'),
				subtitle: getDataPath(prefs).get_path() ?? '',
				subtitle_selectable: true,
				activatable: false,
			}),
		);
		this.add_response('cancel', _('Cancel'));
		this.set_close_response('cancel');
		this.add_response('install', _('Install'));
		this.set_response_appearance('install', Adw.ResponseAppearance.SUGGESTED);
	}
};
HljsDialog = __decorate([registerClass()], HljsDialog);
Gio._promisify(Gio.File.prototype, 'replace_contents_async');
Gio._promisify(Soup.Session.prototype, 'send_and_read_async');

async function downloadHljsModule(prefs, url, hash, path, cancellable) {
	try {
		if (path.query_exists(null)) {
			prefs.getLogger().error(`Highlight.js module ${path.get_path()} already installed`);
			return false;
		}

		// Check if URL is valid
		const uri = GLib.uri_parse(url, GLib.UriFlags.NONE);

		// Download page
		const session = new Soup.Session({ user_agent: UserAgent, idle_timeout: 5 });
		const message = Soup.Message.new_from_uri('GET', uri);

		// Send request
		const response = await session.send_and_read_async(message, GLib.PRIORITY_DEFAULT, cancellable);
		if (response == null) return false;
		const data = response.get_data();
		if (data == null) return false;

		// Check integrity
		const sha512 = GLib.compute_checksum_for_bytes(GLib.ChecksumType.SHA512, data);
		if (sha512 !== hash) {
			prefs
				.getLogger()
				.error(
					`Highlight.js module ${path.get_path()} integrity check failed\nExpected: ${hash}\nActual: ${sha512}`,
				);
			return false;
		}

		// Write to file
		const parent = path.get_parent();
		if (!parent.query_exists(cancellable)) parent.make_directory_with_parents(cancellable);
		await path.replace_contents_async(data, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, cancellable);
		return true;
	} catch (error) {
		prefs.getLogger().error(error);
		return false;
	}
}

async function downloadHljs(prefs, cancellable) {
	const path = getHljsPath(prefs);
	let prefUrl = null;
	for (const url of HljsUrls) {
		if (prefUrl) prefs.getLogger().warn(`Failed to download highlight.js from '${prefUrl}'. Trying next cdn`);
		prefUrl = url;

		if (await downloadHljsModule(prefs, url, HljsSha512, path, cancellable)) {
			return true;
		}
	}
	prefs.getLogger().error(`Failed to download highlight.js from '${prefUrl}'`);
	return false;
}

export async function downloadHljsLanguage(prefs, language, hash, path, cancellable) {
	let prefUrl = null;
	for (const url of getHljsLanguageUrls(language)) {
		if (prefUrl)
			prefs
				.getLogger()
				.warn(`Failed to download highlight.js language '${language}' from '${prefUrl}'. Trying next cdn`);
		prefUrl = url;

		if (await downloadHljsModule(prefs, url, hash, path, cancellable)) {
			return true;
		}
	}
	prefs.getLogger().error(`Failed to download highlight.js language '${language}' from '${prefUrl}'`);
	return false;
}

let DependenciesWarningButton = class DependenciesWarningButton extends Gtk.MenuButton {
	_libgda = false;
	_gsound = false;
	_hljs = false;
	_menu;
	_items = ['libgda', 'gsound', 'hljs'];
	_cancellable = new Gio.Cancellable();

	constructor(prefs, window) {
		super({
			icon_name: Icon.Warning,
			css_classes: ['flat'],
		});
		const gdaDialog = new GdaDialog();
		const gsoundDialog = new GuideDialog(
			_('GSound Not Installed'),
			_(
				'GSound is required to play sound effects. Install GSound to use this feature. ' +
					'After installing GSound you will need to log out or restart your system.',
			),
			_('Install GSound'),
			'gsound',
			'gsound',
			'gir1.2-gsound-1.0',
			'typelib-1_0-GSound-1_0',
		);
		const hljsDialog = new HljsDialog(prefs);
		hljsDialog.connect('response', async (_dialog, response) => {
			if (response === 'install') {
				hljsAction.enabled = false;

				// Show start download toast
				const toast = new Adw.Toast({ title: _('Starting Highlight.js Download') });
				window.add_toast(toast);

				// Show spinner
				const spinner = new Gtk.Image();
				spinner.paintable = new Adw.SpinnerPaintable({ widget: spinner });
				this.child = spinner;
				this.remove_css_class('warning');

				// Start download
				const success = await downloadHljs(prefs, this._cancellable);

				// Hide spinner
				this.set_child(null);
				this.add_css_class('warning');

				// Show end download toast
				toast.dismiss();
				window.add_toast(
					new Adw.Toast({
						title: success ? _('Successfully Installed Highlight.js') : _('Error Installing Highlight.js'),
					}),
				);
				if (success) {
					this._hljs = await checkHighlightJS(prefs);
					if (this._hljs) {
						this.deleteItem('hljs');

						// Re-enable hljs dialog for if hljs was uninstalled for some reason
						const settings = prefs.getSettings();
						settings.set_boolean('disable-hljs-dialog', false);
					}
					this.notify('hljs');
					this.emit('hljs-installed');
				} else {
					// Re-enable action in menu if download failed
					hljsAction.enabled = true;
				}
			} else if (response === 'cancel') {
				// Disable hljs dialog from showing up since hljs is not installed
				const settings = prefs.getSettings();
				settings.set_boolean('disable-hljs-dialog', true);
			}
		});

		// Cancel operations on destroy
		this.connect('destroy', () => this._cancellable.cancel());

		// Menu
		const actionGroup = new Gio.SimpleActionGroup();
		const libgdaAction = Gio.SimpleAction.new('libgda', null);
		libgdaAction.connect('activate', () => gdaDialog.present(window));
		actionGroup.add_action(libgdaAction);
		const gsoundAction = Gio.SimpleAction.new('gsound', null);
		gsoundAction.connect('activate', () => gsoundDialog.present(window));
		actionGroup.add_action(gsoundAction);
		const hljsAction = Gio.SimpleAction.new('hljs', null);
		hljsAction.connect('activate', () => hljsDialog.present(window));
		actionGroup.add_action(hljsAction);
		this.insert_action_group('dependencies', actionGroup);
		this._menu = new Gio.Menu();
		this._menu.append(_('Libgda Not Installed'), 'dependencies.libgda');
		this._menu.append(_('GSound Not Installed'), 'dependencies.gsound');
		this._menu.append(_('Highlight.js Not Installed'), 'dependencies.hljs');
		this.menu_model = this._menu;

		// Checks
		checkGda(prefs)
			.then((libgda) => {
				this._libgda = libgda;
				if (libgda) this.deleteItem('libgda');
				this.notify('libgda');
			})
			.catch(() => prefs.getLogger().warn('Libgda check failed'));
		checkGSound()
			.then((gsound) => {
				this._gsound = gsound;
				if (gsound) this.deleteItem('gsound');
				this.notify('gsound');
			})
			.catch(() => prefs.getLogger().warn('GSound check failed'));
		checkHighlightJS(prefs)
			.then((hljs) => {
				this._hljs = hljs;
				if (hljs) {
					this.deleteItem('hljs');
				} else {
					// Open dialog for the first time if hljs is not installed
					const settings = prefs.getSettings();
					if (!settings.get_boolean('disable-hljs-dialog')) {
						hljsDialog.present(window);
					}
				}
				this.notify('hljs');
			})
			.catch(() => prefs.getLogger().warn('Highlight.js check failed'));
	}

	get libgda() {
		return this._libgda;
	}

	get gsound() {
		return this._gsound;
	}

	get hljs() {
		return this._hljs;
	}

	deleteItem(item) {
		const index = this._items.indexOf(item);
		if (index < 0) return;
		this._items.splice(index, 1);
		this._menu.remove(index);
		if (this._items.length === 0) {
			this.visible = false;
		}
	}
};
DependenciesWarningButton = __decorate(
	[
		registerClass({
			Properties: {
				libgda: GObject.ParamSpec.boolean('libgda', null, null, GObject.ParamFlags.READABLE, false),
				gsound: GObject.ParamSpec.boolean('gsound', null, null, GObject.ParamFlags.READABLE, false),
				hljs: GObject.ParamSpec.boolean('hljs', null, null, GObject.ParamFlags.READABLE, false),
			},
			Signals: {
				'hljs-installed': {},
			},
		}),
	],
	DependenciesWarningButton,
);

export { DependenciesWarningButton };
