import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import St from 'gi://St';
import Clutter from 'gi://Clutter';

import * as LoginManager from 'resource:///org/gnome/shell/misc/loginManager.js';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as StatusSystem from 'resource:///org/gnome/shell/ui/status/system.js';
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
import * as ExtensionSystem from 'resource:///org/gnome/shell/ui/extensionSystem.js';
import * as ModalDialog from 'resource:///org/gnome/shell/ui/modalDialog.js';
import * as Dialog from 'resource:///org/gnome/shell/ui/dialog.js';
import * as CheckBoxImport from 'resource:///org/gnome/shell/ui/checkBox.js';
import {loadInterfaceXML} from 'resource:///org/gnome/shell/misc/fileUtils.js';

const CheckBox = CheckBoxImport.CheckBox;
// Use __ () and N__() for the extension gettext domain, and reuse
// the shell domain with the default _() and N_()
import {Extension, gettext as __} from 'resource:///org/gnome/shell/extensions/extension.js';
export {__};
const N__ = function (e) {
    return e;
};

const HIBERNATE_CHECK_TIMEOUT = 20000;

export default class HibernateButtonExtension extends Extension {
    _loginManagerCanHibernate(asyncCallback) {
        if (this._loginManager._proxy) {
            // systemd path
            this._loginManager._proxy.call(
                'CanHibernate',
                null,
                Gio.DBusCallFlags.NONE,
                -1,
                null,
                function (proxy, asyncResult) {
                    let result, error;

                    try {
                        result = proxy.call_finish(asyncResult).deep_unpack();
                    } catch (e) {
                        error = e;
                    }

                    if (error) asyncCallback(false);
                    else asyncCallback(!['no', 'na'].includes(result[0]));
                }
            );
        } else {
            this.can_hibernate_sourceID = GLib.idle_add(() => {
                asyncCallback(false);
                return false;
            });
        }
    }

    _loginManagerHibernate() {
        if (this._setting.get_boolean('hibernate-works-check')) {
            this._hibernateStarted = new Date();
            this.hibernate_sourceID = GLib.timeout_add(
                GLib.PRIORITY_DEFAULT,
                HIBERNATE_CHECK_TIMEOUT,
                () => this._checkDidHibernate()
            );
        }
        if (this._loginManager._proxy) {
            // systemd path
            this._loginManager._proxy.call(
                'Hibernate',
                GLib.Variant.new('(b)', [true]),
                Gio.DBusCallFlags.NONE,
                -1,
                null,
                null
            );
        } else {
            // Can't do in ConsoleKit
            this._loginManager.emit('prepare-for-sleep', true);
            this._loginManager.emit('prepare-for-sleep', false);
        }
    }

    _loginManagerCanHybridSleep(asyncCallback) {
        if (this._loginManager._proxy) {
            // systemd path
            this._loginManager._proxy.call(
                'CanHybridSleep',
                null,
                Gio.DBusCallFlags.NONE,
                -1,
                null,
                function (proxy, asyncResult) {
                    let result, error;

                    try {
                        result = proxy.call_finish(asyncResult).deep_unpack();
                    } catch (e) {
                        error = e;
                    }

                    if (error) asyncCallback(false);
                    else asyncCallback(!['no', 'na'].includes(result[0]));
                }
            );
        } else {
            this.can_hybrid_sleep_sourceID = GLib.idle_add(() => {
                asyncCallback(false);
                return false;
            });
        }
    }

    _loginManagerHybridSleep() {
        if (this._loginManager._proxy) {
            // systemd path
            this._loginManager._proxy.call(
                'HybridSleep',
                GLib.Variant.new('(b)', [true]),
                Gio.DBusCallFlags.NONE,
                -1,
                null,
                null
            );
        } else {
            // Can't do in ConsoleKit
            this._loginManager.emit('prepare-for-sleep', true);
            this._loginManager.emit('prepare-for-sleep', false);
        }
    }

    _loginManagerCanSuspendThenHibernate(asyncCallback) {
        if (this._loginManager._proxy) {
            // systemd path
            this._loginManager._proxy.call(
                'CanSuspendThenHibernate',
                null,
                Gio.DBusCallFlags.NONE,
                -1,
                null,
                function (proxy, asyncResult) {
                    let result, error;

                    try {
                        result = proxy.call_finish(asyncResult).deep_unpack();
                    } catch (e) {
                        error = e;
                    }

                    if (error) asyncCallback(false);
                    else asyncCallback(!['no', 'na'].includes(result[0]));
                }
            );
        } else {
            this.can_suspend_then_hibernate_sourceID = GLib.idle_add(() => {
                asyncCallback(false);
                return false;
            });
        }
    }

    _loginManagerSuspendThenHibernate() {
        if (this._loginManager._proxy) {
            // systemd path
            this._loginManager._proxy.call(
                'SuspendThenHibernate',
                GLib.Variant.new('(b)', [true]),
                Gio.DBusCallFlags.NONE,
                -1,
                null,
                null
            );
        } else {
            // Can't do in ConsoleKit
            this._loginManager.emit('prepare-for-sleep', true);
            this._loginManager.emit('prepare-for-sleep', false);
        }
    }

    _updateHaveHibernate() {
        this._loginManagerCanHibernate(result => {
            log(`Able to hibernate: ${result}`);
            this._haveHibernate = result;
            this._updateHibernate();
        });
    }

    _updateHibernate() {
        this._hibernateMenuItem.visible =
            this._haveHibernate && !Main.sessionMode.isLocked && this._setting.get_boolean('show-hibernate');
    }

    _updateHaveHybridSleep() {
        this._loginManagerCanHybridSleep(result => {
            log(`Able to hybrid-sleep: ${result}`);
            this._haveHybridSleep = result;
            this._updateHybridSleep();
        });
    }

    _updateHybridSleep() {
        this._hybridSleepMenuItem.visible =
            this._haveHybridSleep && !Main.sessionMode.isLocked  && this._setting.get_boolean('show-hybrid-sleep');
    }

    _updateHaveSuspendThenHibernate() {
        this._loginManagerCanSuspendThenHibernate(result => {
            log(`Able to suspend then hibernate: ${result}`);
            this._haveSuspendThenHibernate = result;
            this._updateSuspendThenHibernate();
        });
    }

    _updateSuspendThenHibernate() {
        this._suspendThenHibernateMenuItem.visible =
            this._haveSuspendThenHibernate && !Main.sessionMode.isLocked  && this._setting.get_boolean('show-suspend-then-hibernate');
    }

    _updateDefaults() {
        console.log("Update defaults");
        let menuItems = this.systemMenu._systemItem.menu._getMenuItems()
        for (let menuItem of menuItems) {
            console.log(menuItem.label.get_text())
            if ( menuItem.label.get_text() === _('Suspend') ) {
                console.log(`Show suspend button: ${this._setting.get_boolean('show-suspend')}`)
                menuItem.visible = this._setting.get_boolean('show-suspend');
            }
            if ( menuItem.label.get_text() === _('Restart…') ) {
                console.log(`Show restart button: ${this._setting.get_boolean('show-restart')}`)
                menuItem.visible = this._setting.get_boolean('show-restart');
            }
            if ( menuItem.label.get_text() === _('Power Off…') ) {
                console.log(`Show shutdown button: ${this._setting.get_boolean('show-shutdown')}`)
                menuItem.visible = this._setting.get_boolean('show-shutdown');
            }
        }
    }

    _onHibernateClicked() {
        this.systemMenu._systemItem.menu.itemActivated();

        if (this._setting.get_boolean('show-hibernate-dialog')) {
            let HibernateDialogContent = {
                subject: C_('title', __('Hibernate')),
                description: __('Do you really want to hibernate the system?'),
                confirmButtons: [
                    {
                        signal: 'Cancel',
                        label: C_('button', __('Cancel')),
                        key: Clutter.Escape,
                    },
                    {
                        signal: 'ConfirmedHibernate',
                        label: C_('button', __('Hibernate')),
                        default: true,
                    },
                ],
            };

            this._dialog = new ConfirmDialog(
                HibernateDialogContent
            );
            this._dialog.connect('ConfirmedHibernate', () =>
                this._loginManagerHibernate()
            );
            this._dialog.open();
        } else {
            this._loginManagerHibernate()
        }
    }

    _onHybridSleepClicked() {
        this.systemMenu._systemItem.menu.itemActivated();
        this._loginManagerHybridSleep();
    }

    _onSuspendThenHibernateClicked() {
        this.systemMenu._systemItem.menu.itemActivated();
        this._loginManagerSuspendThenHibernate();
    }

    _disableExtension() {
        Main.extensionManager.disableExtension('hibernate-status@dromi')
        console.log('Disabled')
    }

    _cancelDisableExtension(notAgain) {
        if (notAgain) this.setHibernateWorksCheckEnabled(false);
    }

    _checkRequirements() {
        if (GLib.access('/run/systemd/seats', 0) < 0) {
            let SystemdMissingDialogContent = {
                subject: C_('title', __('Hibernate button: Systemd Missing')),
                description: __('Systemd seems to be missing and is required.'),
                confirmButtons: [
                    {
                        signal: 'Cancel',
                        label: C_('button', __('Cancel')),
                        key: Clutter.Escape,
                    },
                    {
                        signal: 'DisableExtension',
                        label: C_('button', __('Disable Extension')),
                        default: true,
                    },
                ],
                iconName: 'document-save-symbolic',
                iconStyleClass: 'end-session-dialog-shutdown-icon',
            };

            this._dialog = new ConfirmDialog(
                SystemdMissingDialogContent
            );
            this._dialog.connect('DisableExtension', this._disableExtension);
            this._dialog.open();
        }
    }

    _checkDidHibernate() {
        /* This function is called HIBERNATE_CHECK_TIMEOUT ms after
         * hibernate started. If it is successful, at that point the GS
         * process is already frozen; so when this function is actually
         * called, way more than HIBERNATE_CHECK_TIMEOUT ms are passed*/
        if (
            new Date() - this._hibernateStarted >
            HIBERNATE_CHECK_TIMEOUT + 5000
        ) {
            // hibernate succeeded
            return;
        }
        // hibernate failed

        let HibernateFailedDialogContent = {
            subject: C_('title', __('Hibernate button: Hibernate failed')),
            description: __(
                'Looks like hibernation failed. On some linux distributions hibernation is disabled ' +
                    'because not all hardware supports it well; ' +
                    'please check your distribution documentation ' +
                    'on how to enable it.'
            ),
            checkBox: __("You are wrong, don't check this anymore!"),
            confirmButtons: [
                {
                    signal: 'Cancel',
                    label: C_('button', __('Cancel')),
                    key: Clutter.Escape,
                },
                {
                    signal: 'DisableExtension',
                    label: C_('button', __('Disable Extension')),
                    default: true,
                },
            ],
            iconName: 'document-save-symbolic',
            iconStyleClass: 'end-session-dialog-shutdown-icon',
        }
        this._dialog = new ConfirmDialog(
            HibernateFailedDialogContent
        );
        this._dialog.connect('DisableExtension', this._disableExtension);
        this._dialog.connect('Cancel', this._cancelDisableExtension);
        this._dialog.open();
    }

    setHibernateWorksCheckEnabled(enabled) {
        let key = 'hibernate-works-check';
        if (this._setting.is_writable(key)) {
            if (this._setting.set_boolean(key, enabled)) {
                Gio.Settings.sync();
            } else {
                throw this._errorSet(key);
            }
        } else {
            throw this._errorWritable(key);
        }
    }

    _modifySystemItem() {
        this._setting = this.getSettings()
        this._checkRequirements();
        this._loginManager = LoginManager.getLoginManager();
        this.systemMenu = Main.panel.statusArea.quickSettings._system;
        this._hibernateMenuItem = new PopupMenu.PopupMenuItem(__('Hibernate'));
        this._hibernateMenuItemId = this._hibernateMenuItem.connect(
            'activate',
            () => this._onHibernateClicked()
        );

        this._hybridSleepMenuItem = new PopupMenu.PopupMenuItem(
            __('Hybrid Sleep')
        );
        this._hybridSleepMenuItemId = this._hybridSleepMenuItem.connect(
            'activate',
            () => this._onHybridSleepClicked()
        );

        this._suspendThenHibernateMenuItem = new PopupMenu.PopupMenuItem(
            __('Suspend then Hibernate')
        );
        this._suspendThenHibernateMenuItemId = this._suspendThenHibernateMenuItem.connect(
            'activate',
            () => this._onSuspendThenHibernateClicked()
        );

        let afterSuspendPosition =
            this.systemMenu._systemItem.menu.numMenuItems - 5;

        this.systemMenu._systemItem.menu.addMenuItem(
            this._hybridSleepMenuItem,
            afterSuspendPosition
        );
        this.systemMenu._systemItem.menu.addMenuItem(
            this._hibernateMenuItem,
            afterSuspendPosition
        );
        this.systemMenu._systemItem.menu.addMenuItem(
            this._suspendThenHibernateMenuItem,
            afterSuspendPosition
        );

        this._menuOpenStateChangedId = this.systemMenu._systemItem.menu.connect(
            'open-state-changed',
            (menu, open) => {
                if (!open) return;
                this._updateDefaults();
                this._updateHaveHibernate();
                this._updateHaveHybridSleep();
                this._updateHaveSuspendThenHibernate();
            }
        );
    }

    _queueModifySystemItem() {
        this.sourceId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
            if (!Main.panel.statusArea.quickSettings._system)
                return GLib.SOURCE_CONTINUE;
    
            this._modifySystemItem();
            return GLib.SOURCE_REMOVE;
        });
    }

    enable() {
        if (!Main.panel.statusArea.quickSettings._system) {
            this._queueModifySystemItem();
        } else {
            this._modifySystemItem();
        }
    }

    disable() {
        this._setting = null;
        if (this._menuOpenStateChangedId) {
            this.systemMenu._systemItem.menu.disconnect(
                this._menuOpenStateChangedId
            );
            this._menuOpenStateChangedId = 0;
        }

        if (this._suspendThenHibernateMenuItemId) {
            this._suspendThenHibernateMenuItem.disconnect(this._suspendThenHibernateMenuItemId);
            this._suspendThenHibernateMenuItemId = 0;
        }

        if (this._hybridSleepMenuItemId) {
            this._hybridSleepMenuItem.disconnect(this._hybridSleepMenuItemId);
            this._hybridSleepMenuItemId = 0;
        }

        if (this._hibernateMenuItemId) {
            this._hibernateMenuItem.disconnect(this._hibernateMenuItemId);
            this._hibernateMenuItemId = 0;
        }

        if (this._suspendThenHibernateMenuItem) {
            this._suspendThenHibernateMenuItem.destroy();
            this._suspendThenHibernateMenuItem = 0;
        }

        if (this._hybridSleepMenuItem) {
            this._hybridSleepMenuItem.destroy();
            this._hybridSleepMenuItem = 0;
        }

        if (this._hibernateMenuItem) {
            this._hibernateMenuItem.destroy();
            this._hibernateMenuItem = 0;
        }

        if (this.sourceId) {
            GLib.Source.remove(this.sourceId);
            this.sourceId = null;
        }

        if (this.can_suspend_then_hibernate_sourceID) {
            GLib.Source.remove(this.can_suspend_then_hibernate_sourceID);
            this.can_suspend_then_hibernate_sourceID = null;
        }

        if (this.can_hybrid_sleep_sourceID) {
            GLib.Source.remove(this.can_hybrid_sleep_sourceID);
            this.can_hybrid_sleep_sourceID = null;
        }

        if (this.can_hibernate_sourceID) {
            GLib.Source.remove(this.can_hibernate_sourceID);
            this.can_hibernate_sourceID = null;
        }

        if (this.hibernate_sourceID) {
            GLib.Source.remove(this.hibernate_sourceID);
            this.hibernate_sourceID = null;
        }
    };
}

var ConfirmDialog = GObject.registerClass(
    {
        Signals: {
            ConfirmedHibernate: {param_types: [GObject.TYPE_BOOLEAN]},
            DisableExtension: {param_types: [GObject.TYPE_BOOLEAN]},
            Cancel: {param_types: [GObject.TYPE_BOOLEAN]},
        },
    },
    class ConfirmDialog extends ModalDialog.ModalDialog {
        _init(dialog) {
            super._init({
                styleClass: 'end-session-dialog',
                destroyOnClose: true,
            });


            this._messageDialogContent = new Dialog.MessageDialogContent();


            this._messageDialogContent.description = dialog.description;
            this._messageDialogContent.title = dialog.subject;

            if (dialog.iconName) {
                this._icon = new St.Icon({
                    icon_name: dialog.iconName,
                    icon_size: _DIALOG_ICON_SIZE,
                    style_class: dialog.iconStyleClass,
                });
            }

            if (dialog.checkBox) {
                this._checkBox = new CheckBox(dialog.checkBox);
                this._messageDialogContent.add(this._checkBox.actor);
            }

            this.contentLayout.add_child(this._messageDialogContent);

            let buttons = [];
            for (let i = 0; i < dialog.confirmButtons.length; i++) {
                let signal = dialog.confirmButtons[i].signal;
                let label = dialog.confirmButtons[i].label;
                let keys = dialog.confirmButtons[i].key;
                buttons.push({
                    action: () => {
                        let signalId = this.connect('closed', () => {
                            this.disconnect(signalId);
                            this._confirm(signal);
                        });
                        this.close();
                    },
                    label: label,
                    key: keys,
                });
            }

            this.setButtons(buttons);
        }

        _confirm(signal) {
            var checked;
            if (this._checkBox) checked = this._checkBox.actor.get_checked();
            this.emit(signal, checked);
        }

        cancel() {
            this.close();
        }
    }
);

const _DIALOG_ICON_SIZE = 32;