'use strict';

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

import { gettext as _, ngettext } from 'resource:///org/gnome/shell/extensions/extension.js';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js';
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
import * as QuickSettings from 'resource:///org/gnome/shell/ui/quickSettings.js';
import * as Config from 'resource://org/gnome/shell/misc/config.js';

import { ThinkPad } from './driver.js';

const ICONS_PATH = GLib.Uri.resolve_relative(import.meta.url, '../icons', GLib.UriFlags.NONE);
const SHELL_VERSION = Number(Config.PACKAGE_VERSION.split('.', 1));

/**
 * Get icon
 * 
 * @param {string} iconName Icon name
 * @param {boolean} colorMode Color mode or symbolic
 * @returns {Gio.Icon}
 */
function getIcon(iconName, colorMode = false) {
    return Gio.icon_new_for_string(`${ICONS_PATH}/${iconName}${colorMode ? '' : '-symbolic'}.svg`);
}

const BatteryItem = GObject.registerClass({
    GTypeName: 'BatteryItem',
}, class BatteryItem extends PopupMenu.PopupImageMenuItem {
    constructor(battery, settings) {
        super('', null);

        this._settings = settings;
        this._battery = battery;

        const box = new St.BoxLayout({
            'opacity': 128,
            'x_expand': true,
            'x_align': Clutter.ActorAlign.END,
            'style': 'spacing: 5px;',
        });

        this.add_child(box);

        // Reload icon
        this._reload = new St.Icon({
            'icon-size': 16,
            'reactive': true,
            'icon-name': 'view-refresh-symbolic',
        });
        this._reload.connectObject(
            'button-press-event', () => {
                this._battery.enable();
                return true;
            },
            this
        );
        box.add_child(this._reload);

        this._valuesLabel = new St.Label({
            'y_align': Clutter.ActorAlign.CENTER,
            'style': 'font-size: 0.75em;',
        });
        box.add_child(this._valuesLabel);

        // Battery signals
        this._battery.connectObject(
            'notify', () => {
                this._update();
            },
            this
        );

        // Settings changes
        this._settings.connectObject(
            'changed', () => {
                this._update();
            },
            this
        );

        // Menu item action
        this.connectObject(
            'activate', () => {
                this._battery.toggle();
            },
            'destroy', () => {
                this._settings.disconnectObject(this);
                this._battery.disconnectObject(this);
                this._reload.disconnectObject(this);
                this.disconnectObject(this);
                this._valuesLabel.destroy();
                this._valuesLabel = null;
                this._reload.destroy();
                this._reload = null;
                this._battery = null;
                this._settings = null;
            },
            this
        );

        this._update();
    }

    /**
     * Update UI
     */
    _update() {
        const colorMode = this._settings.get_boolean('color-mode');
        // Menu text and icon
        if (this._battery.isActive) {
            // TRANSLATORS: %s is the name of the battery.
            this.label.text = _('Disable thresholds (%s)').format(this._battery.name);
            if (this._battery.pendingChanges) {
                this.setIcon(getIcon('threshold-active-warning', colorMode));
            } else {
                this.setIcon(getIcon('threshold-active', colorMode));
            }
            // Status text
            const showCurrentValues = this._settings.get_boolean('show-current-values');
            if (showCurrentValues) {
                // TRANSLATORS: %d/%d are the [start/end] threshold values. The string %% is the percent symbol (may need to be escaped depending on the language)
                this._valuesLabel.text = _('%d/%d %%').format(this._battery.startValue || 0, this._battery.endValue || 100);
                this._valuesLabel.visible = true;
            } else {
                this._valuesLabel.visible = false;
            }
        } else {
            // TRANSLATORS: %s is the name of the battery.
            this.label.text = _('Enable thresholds (%s)').format(this._battery.name);
            this.setIcon(getIcon('threshold-inactive', colorMode));
            this._valuesLabel.visible = false;
        }
        // Reload 'button'
        this._reload.visible = this._battery.pendingChanges && this._battery.isActive;
        // Menu item visibility
        this.visible = this._battery.isAvailable;
    }
});

const ThresholdToggle = GObject.registerClass({
    GTypeName: 'ThresholdToggle',
}, class ThresholdToggle extends QuickSettings.QuickMenuToggle {
    constructor(driver, extensionObject) {
        super({
            'title': _('Thresholds'),
            'gicon': getIcon('threshold-app'),
            'toggle-mode': false,
            //'subtitle': 'subtitle'
        });

        // Header
        this.menu.setHeader(
            getIcon('threshold-app'), // Icon
            _('Battery Threshold'), // Title
            driver.environment.productVersion ? driver.environment.productVersion : _('Unknown model')// Subtitle
        );

        // Unavailable
        this.unavailableMenuItem = new PopupMenu.PopupImageMenuItem(_('Thresholds not available'), getIcon('threshold-unknown'));
        this.unavailableMenuItem.sensitive = false;
        this.unavailableMenuItem.visible = false;
        this.menu.addMenuItem(this.unavailableMenuItem);

        // Batteries
        driver.batteries.forEach(battery => {
            // Battery menu item
            const item = new BatteryItem(battery, extensionObject.getSettings());
            this.menu.addMenuItem(item);
        });

        // Unavailable status
        this.unavailableMenuItem.visible = !driver.isAvailable;

        // Checked status
        this.checked = driver.isActive;

        // Driver signals
        driver.connectObject(
            'notify::is-active', () => {
                this.checked = driver.isActive;
            },
            'notify::is-available', () => {
                this.unavailableMenuItem.visible = !driver.isAvailable;
            },
            this
        );

        // Signals
        this.connectObject(
            'clicked', () => {
                if (driver.isActive) {
                    driver.disableAll();
                } else {
                    driver.enableAll();
                }
            },
            'destroy', () => {
                this.disconnectObject(this);
                driver.disconnectObject(this);
                this.menu.removeAll();
            },
            this
        );

        // Add an entry-point for more getSettings()
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        const settingsItem = this.menu.addAction(_('Thresholds settings'),
            () => extensionObject.openPreferences());

        // Ensure the getSettings() are unavailable when the screen is locked
        settingsItem.visible = Main.sessionMode.allowSettings;
        this.menu._settingsActions[extensionObject.uuid] = settingsItem;
    }
});

export const ThresholdIndicator = GObject.registerClass({
    GTypeName: 'ThresholdIndicator',
}, class ThresholdIndicator extends QuickSettings.SystemIndicator {
    constructor(extensionObject) {
        super();

        this._settings = extensionObject.getSettings();
        this._driver = new ThinkPad({ 'settings': this._settings })
        this._name = extensionObject.metadata.name;

        this._indicator = this._addIndicator();
        this._indicator.gicon = getIcon('threshold-unknown');

        this.quickSettingsItems.push(new ThresholdToggle(this._driver, extensionObject));

        Main.panel.statusArea.quickSettings.addExternalIndicator(this);

        this._updateIndicator();

        // Driver signals
        this._driver.connectObject(
            'notify', () => this._updateIndicator(),
            'enable-battery-completed', (driver, battery, error) => {
                if (!error) {
                    this._notifyEnabled(
                        // TRANSLATORS: %s is the name of the battery. %d/%d are the [start/end] threshold values. The string %% is the percent symbol (may need to be escaped depending on the language)
                        _('Battery (%s) charge thresholds enabled at %d/%d %%').format(
                            battery.name, battery.startValue || 0, battery.endValue || 100
                        )
                    );
                } else {
                    this._notifyError(
                        // TRANSLATORS: The first %s is the name of the battery. The second %s is the error message. \n is new line.
                        _('Failed to enable thresholds on battery %s. \nError: %s').format(
                            battery.name, error.message
                        )
                    );
                }
            },
            'disable-battery-completed', (driver, battery, error) => {
                if (!error) {
                    this._notifyDisabled(
                        // TRANSLATORS: %s is the name of the battery.
                        _('Battery (%s) charge thresholds disabled').format(
                            battery.name
                        )
                    );
                } else {
                    this._notifyError(
                        // TRANSLATORS: The first %s is the name of the battery. The second %s is the error message. \n is new line.
                        _('Failed to disable thresholds on battery %s. \nError: %s').format(
                            battery.name, error.message
                        )
                    );
                }
            },
            'enable-all-completed', (driver, error) => {
                if (!error) {
                    this._notifyEnabled(_('Thresholds enabled for all batteries'))
                } else {
                    this._notifyError(
                        // TRANSLATORS: %s is the error message. \n is new line.
                        _('Failed to enable thresholds for all batteries. \nError: %s').format(
                            error.message
                        )
                    );
                }
            },
            'disable-all-completed', (driver, error) => {
                if (!error) {
                    this._notifyDisabled(_('Thresholds disabled for all batteries'));
                } else {
                    this._notifyError(
                        // TRANSLATORS: %s is the error message. \n is new line.
                        _('Failed to disable thresholds for all batteries. \nError: %s').format(
                            error.message
                        )
                    );
                }
            },
            this
        );

        // Settings signals
        this._settings.connectObject(
            'changed::color-mode', () => {
                this._updateIndicator();
            },
            'changed::indicator-mode', () => {
                this._updateIndicator();
            },
            this
        );

        this.connect('destroy', () => {
            this.quickSettingsItems.forEach(item => item.destroy());
            this._settings.disconnectObject(this);
            this._settings = null;
            this._driver.disconnectObject(this);
            this._driver.destroy();
            this._driver = null;
            this._extension = null;
        });

        // Pending changes alert
        this._driver.batteries.every(battery => {
            if (battery.pendingChanges && battery.isActive) {
                this._notify(_('Battery Threshold'), _('The currently set thresholds do not match the configured ones'), 'threshold-active-warning', false);
                return false;
            }
            return true;
        });
    }

    /**
     * Update indicator (tray-icon)
     */
    _updateIndicator() {
        const colorMode = this._settings.get_boolean('color-mode');
        if (this._driver.isAvailable) {
            if (this._driver.isActive) {
                if (this._driver.pendingChanges) {
                    this._indicator.gicon = getIcon('threshold-active-warning', colorMode);
                } else {
                    this._indicator.gicon = getIcon('threshold-active', colorMode);
                }
            } else {
                this._indicator.gicon = getIcon('threshold-inactive', colorMode);
            }
        } else {
            this._indicator.gicon = getIcon('threshold-unknown', colorMode);
        }

        const indicatorMode = this._settings.get_enum('indicator-mode');
        switch (indicatorMode) {
            case 0: // Active
                this._indicator.visible = this._driver.isActive;
                break;
            case 1: // Inactive (or pending changes)
                this._indicator.visible = !this._driver.isActive || this._driver.pendingChanges;
                break;
            case 2: // Always
                this._indicator.visible = true;
                break;
            case 3: // Never (or pending changes)
                this._indicator.visible = this._driver.pendingChanges;
                break;
            default:
                this._indicator.visible = true;
                break;
        }
    }

    /**
     * Show notification.
     * 
     * @param {string} msg Title
     * @param {string} details Message
     * @param {string} iconName Icon name
     * @param {boolean} [transient=true] Transient notification
     */
    _notify(msg, details, iconName, transient = true) {
        if (!this._settings.get_boolean('show-notifications')) return;
        if (SHELL_VERSION === 45) {
            const source = new MessageTray.Source(this._name);
            Main.messageTray.add(source);
            const notification = new MessageTray.Notification(
                source,
                msg,
                details,
                { gicon: getIcon(iconName, true) }
            );
            notification.setTransient(transient);
            source.showNotification(notification);
        } else {
            const source = new MessageTray.Source({ 'title': this._name });
            Main.messageTray.add(source);
            const notification = new MessageTray.Notification({
                source: source,
                title: msg,
                body: details,
                isTransient: transient,
                gicon: getIcon(iconName, true)
            });
            source.addNotification(notification);
        }
    }

    /**
     * Show error notification
     * 
     * @param {string} message Message
     */
    _notifyError(message) {
        this._notify(_('Battery Threshold'), message, 'threshold-error', false);
    }

    /**
    * Show enabled notification
    * 
    * @param {string} message Message
    */
    _notifyEnabled(message) {
        this._notify(_('Battery Threshold'), message, 'threshold-active');
    }

    /**
    * Show disabled notification
    * 
    * @param {string} message Message
    */
    _notifyDisabled(message) {
        this._notify(_('Battery Threshold'), message, 'threshold-inactive');
    }
});