// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
//
// SPDX-License-Identifier: GPL-2.0-or-later

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

import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';

import {gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
import {getIcon} from './utils.js';

import * as GMenu from './gmenu.js';
import Tooltip from './tooltip.js';


/**
 * A battery widget with an icon, text percentage and time estimate tooltip
 */
export const Battery = GObject.registerClass({
    GTypeName: 'GSConnectShellDeviceBattery',
}, class Battery extends St.BoxLayout {

    _init(params) {
        super._init({
            reactive: true,
            style_class: 'gsconnect-device-battery',
            track_hover: true,
        });
        Object.assign(this, params);

        // Percent Label
        this.label = new St.Label({
            y_align: Clutter.ActorAlign.CENTER,
        });
        this.label.clutter_text.ellipsize = 0;
        this.add_child(this.label);

        // Battery Icon
        this.icon = new St.Icon({
            fallback_icon_name: 'battery-missing-symbolic',
            icon_size: 16,
        });
        this.add_child(this.icon);

        // Battery Estimate
        this.tooltip = new Tooltip({
            parent: this,
            text: null,
        });

        // Battery GAction
        this._actionAddedId = this.device.action_group.connect(
            'action-added',
            this._onActionChanged.bind(this)
        );
        this._actionRemovedId = this.device.action_group.connect(
            'action-removed',
            this._onActionChanged.bind(this)
        );
        this._actionStateChangedId = this.device.action_group.connect(
            'action-state-changed',
            this._onStateChanged.bind(this)
        );

        this._onActionChanged(this.device.action_group, 'battery');

        // Cleanup on destroy
        this.connect('destroy', this._onDestroy);
    }

    _onActionChanged(action_group, action_name) {
        if (action_name !== 'battery')
            return;

        if (action_group.has_action('battery')) {
            const value = action_group.get_action_state('battery');
            const [charging, icon_name, level, time] = value.deepUnpack();

            this._state = {
                charging: charging,
                icon_name: icon_name,
                level: level,
                time: time,
            };
        } else {
            this._state = null;
        }

        this._sync();
    }

    _onStateChanged(action_group, action_name, value) {
        if (action_name !== 'battery')
            return;

        const [charging, icon_name, level, time] = value.deepUnpack();

        this._state = {
            charging: charging,
            icon_name: icon_name,
            level: level,
            time: time,
        };

        this._sync();
    }

    _getBatteryLabel() {
        if (!this._state)
            return null;

        const {charging, level, time} = this._state;

        if (level === 100)
            // TRANSLATORS: When the battery level is 100%
            return _('Fully Charged');

        if (time === 0)
            // TRANSLATORS: When no time estimate for the battery is available
            // EXAMPLE: 42% (Estimating…)
            return _('%d%% (Estimating…)').format(level);

        const total = time / 60;
        const minutes = Math.floor(total % 60);
        const hours = Math.floor(total / 60);

        if (charging) {
            // TRANSLATORS: Estimated time until battery is charged
            // EXAMPLE: 42% (1:15 Until Full)
            return _('%d%% (%d\u2236%02d Until Full)').format(
                level,
                hours,
                minutes
            );
        } else {
            // TRANSLATORS: Estimated time until battery is empty
            // EXAMPLE: 42% (12:15 Remaining)
            return _('%d%% (%d\u2236%02d Remaining)').format(
                level,
                hours,
                minutes
            );
        }
    }

    _onDestroy(actor) {
        actor.device.action_group.disconnect(actor._actionAddedId);
        actor.device.action_group.disconnect(actor._actionRemovedId);
        actor.device.action_group.disconnect(actor._actionStateChangedId);
    }

    _sync() {
        this.visible = !!this._state;

        if (!this.visible)
            return;

        this.icon.icon_name = this._state.icon_name;
        this.label.text = (this._state.level > -1) ? `${this._state.level}%` : '';
        this.tooltip.text = this._getBatteryLabel();
    }
});


/**
 * A cell signal strength widget with two icons
 */
export const SignalStrength = GObject.registerClass({
    GTypeName: 'GSConnectShellDeviceSignalStrength',
}, class SignalStrength extends St.BoxLayout {

    _init(params) {
        super._init({
            reactive: true,
            style_class: 'gsconnect-device-signal-strength',
            track_hover: true,
        });
        Object.assign(this, params);

        // Network Type Icon
        this.networkTypeIcon = new St.Icon({
            fallback_icon_name: 'network-cellular-symbolic',
            icon_size: 16,
        });
        this.add_child(this.networkTypeIcon);

        // Signal Strength Icon
        this.signalStrengthIcon = new St.Icon({
            fallback_icon_name: 'network-cellular-offline-symbolic',
            icon_size: 16,
        });
        this.add_child(this.signalStrengthIcon);

        // Network Type Text
        this.tooltip = new Tooltip({
            parent: this,
            text: null,
        });

        // ConnectivityReport GAction
        this._actionAddedId = this.device.action_group.connect(
            'action-added',
            this._onActionChanged.bind(this)
        );
        this._actionRemovedId = this.device.action_group.connect(
            'action-removed',
            this._onActionChanged.bind(this)
        );
        this._actionStateChangedId = this.device.action_group.connect(
            'action-state-changed',
            this._onStateChanged.bind(this)
        );

        this._onActionChanged(this.device.action_group, 'connectivityReport');

        // Cleanup on destroy
        this.connect('destroy', this._onDestroy);
    }

    _onActionChanged(action_group, action_name) {
        if (action_name !== 'connectivityReport')
            return;

        if (action_group.has_action('connectivityReport')) {
            const value = action_group.get_action_state('connectivityReport');
            const [
                cellular_network_type,
                cellular_network_type_icon,
                cellular_network_strength,
                cellular_network_strength_icon,
                hotspot_name,
                hotspot_bssid,
            ] = value.deepUnpack();

            this._state = {
                cellular_network_type: cellular_network_type,
                cellular_network_type_icon: cellular_network_type_icon,
                cellular_network_strength: cellular_network_strength,
                cellular_network_strength_icon: cellular_network_strength_icon,
                hotspot_name: hotspot_name,
                hotspot_bssid: hotspot_bssid,
            };
        } else {
            this._state = null;
        }

        this._sync();
    }

    _onStateChanged(action_group, action_name, value) {
        if (action_name !== 'connectivityReport')
            return;

        const [
            cellular_network_type,
            cellular_network_type_icon,
            cellular_network_strength,
            cellular_network_strength_icon,
            hotspot_name,
            hotspot_bssid,
        ] = value.deepUnpack();

        this._state = {
            cellular_network_type: cellular_network_type,
            cellular_network_type_icon: cellular_network_type_icon,
            cellular_network_strength: cellular_network_strength,
            cellular_network_strength_icon: cellular_network_strength_icon,
            hotspot_name: hotspot_name,
            hotspot_bssid: hotspot_bssid,
        };

        this._sync();
    }

    _onDestroy(actor) {
        actor.device.action_group.disconnect(actor._actionAddedId);
        actor.device.action_group.disconnect(actor._actionRemovedId);
        actor.device.action_group.disconnect(actor._actionStateChangedId);
    }

    _sync() {
        this.visible = !!this._state;

        if (!this.visible)
            return;

        this.networkTypeIcon.icon_name = this._state.cellular_network_type_icon;
        this.signalStrengthIcon.icon_name = this._state.cellular_network_strength_icon;
        this.tooltip.text = this._state.cellular_network_type;
    }
});


/**
 * A PopupMenu used as an information and control center for a device
 */
export class Menu extends PopupMenu.PopupMenuSection {

    constructor(params) {
        super();
        Object.assign(this, params);

        this.actor.add_style_class_name('gsconnect-device-menu');

        // Title
        this._title = new PopupMenu.PopupSeparatorMenuItem(this.device.name);
        this.addMenuItem(this._title);

        // Title -> Name
        this._title.label.style_class = 'gsconnect-device-name';
        this._title.label.clutter_text.ellipsize = 0;
        this.device.bind_property(
            'name',
            this._title.label,
            'text',
            GObject.BindingFlags.SYNC_CREATE
        );

        // Title -> Cellular Signal Strength
        this._signalStrength = new SignalStrength({device: this.device});
        this._title.actor.add_child(this._signalStrength);

        // Title -> Battery
        this._battery = new Battery({device: this.device});
        this._title.actor.add_child(this._battery);

        // Actions
        let actions;

        if (this.menu_type === 'icon') {
            actions = new GMenu.IconBox({
                action_group: this.device.action_group,
                model: this.device.menu,
            });
        } else if (this.menu_type === 'list') {
            actions = new GMenu.ListBox({
                action_group: this.device.action_group,
                model: this.device.menu,
            });
        }

        this.addMenuItem(actions);
    }

    isEmpty() {
        return false;
    }
}


/**
 * An indicator representing a Device in the Status Area
 */
export const Indicator = GObject.registerClass({
    GTypeName: 'GSConnectDeviceIndicator',
}, class Indicator extends PanelMenu.Button {

    _init(params) {
        super._init(0.0, `${params.device.name} Indicator`, false);
        Object.assign(this, params);

        // Device Icon
        this._icon = new St.Icon({
            gicon: getIcon(this.device.icon_name),
            style_class: 'system-status-icon gsconnect-device-indicator',
        });
        this.add_child(this._icon);

        // Menu
        const menu = new Menu({
            device: this.device,
            menu_type: 'icon',
        });
        this.menu.addMenuItem(menu);
    }
});

