import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js';
import * as ExtensionUtils from 'resource:///org/gnome/shell/misc/extensionUtils.js';

const MONITOR_CONSTS = {
    logPrefix: 'System Extension Monitor',
    notificationSourceTitle: 'stillOS',
    notificationIconName: 'dialog-warning-symbolic',
};

function getExtensionDisplayName(extension) {
    return extension?.metadata?.name || extension?.uuid || 'A system extension';
}

export default class SystemExtensionMonitor {
    enable(ownUuid) {
        this._ownUuid = ownUuid;
        this._notifiedUuids = new Set();
        this._source = null;
        this._sourceIsCustom = false;
        this._extensionStateChangedId = null;

        this._extensionStateChangedId = Main.extensionManager.connect(
            'extension-state-changed',
            (_manager, extension) => {
                this._handleExtensionStateChange(extension);
            }
        );

        this._checkAllSystemExtensions();
    }

    _isSystemExtension(extension) {
        return extension?.type === ExtensionUtils.ExtensionType.SYSTEM;
    }

    _handleExtensionStateChange(extension) {
        if (!extension || !this._isSystemExtension(extension))
            return;

        if (extension.uuid === this._ownUuid)
            return;

        if (extension.state === ExtensionUtils.ExtensionState.ERROR)
            this._notifyExtensionCrash(extension);
        else if (this._notifiedUuids.has(extension.uuid))
            this._notifiedUuids.delete(extension.uuid);
    }

    _checkAllSystemExtensions() {
        for (const uuid of Main.extensionManager.getUuids()) {
            const extension = Main.extensionManager.lookup(uuid);
            this._handleExtensionStateChange(extension);
        }
    }

    _notifyExtensionCrash(extension) {
        if (!extension?.uuid || this._notifiedUuids.has(extension.uuid))
            return;

        const displayName = getExtensionDisplayName(extension);
        const body = `${displayName} has crashed. We recommend logging out and back in to restore your session.`;

        if (extension.error)
            log(`${MONITOR_CONSTS.logPrefix}: ${displayName} (${extension.uuid}): ${extension.error}`);

        try {
            const source = this._getNotificationSource();
            const notification = new MessageTray.Notification({
                source,
                title: `${displayName} crashed`,
                body,
            });
            notification.urgency = MessageTray.Urgency.CRITICAL;
            notification.iconName = MONITOR_CONSTS.notificationIconName;
            notification.resident = true;

            source.addNotification(notification);
            this._notifiedUuids.add(extension.uuid);
        } catch (e) {
            log(`${MONITOR_CONSTS.logPrefix}: Failed to show crash notification for ${extension.uuid}: ${e}`);
        }
    }

    _getNotificationSource() {
        if (this._source)
            return this._source;

        if (MessageTray.getSystemSource) {
            this._source = MessageTray.getSystemSource();
        } else {
            this._source = new MessageTray.Source({
                title: MONITOR_CONSTS.notificationSourceTitle,
                iconName: MONITOR_CONSTS.notificationIconName,
            });
            this._sourceIsCustom = true;
            Main.messageTray.add(this._source);
        }

        this._source.title = MONITOR_CONSTS.notificationSourceTitle;
        this._source.iconName = MONITOR_CONSTS.notificationIconName;

        return this._source;
    }

    disable() {
        if (this._extensionStateChangedId) {
            Main.extensionManager.disconnect(this._extensionStateChangedId);
            this._extensionStateChangedId = null;
        }

        if (this._sourceIsCustom && this._source?.destroy) {
            this._source.destroy();
            this._source = null;
            this._sourceIsCustom = false;
        } else {
            this._source = null;
        }

        this._notifiedUuids.clear();
        this._ownUuid = null;
    }
}
