pragma Singleton
pragma ComponentBehavior: Bound

import QtQuick
import Quickshell
import Quickshell.Services.UPower
import qs.Common

Singleton {
    id: root

    property bool suppressSound: true
    property bool previousPluggedState: false

    Timer {
        id: startupTimer
        interval: 500
        repeat: false
        running: true
        onTriggered: {
            root.suppressSound = false;
            root.applyPowerProfile();
        }
    }

    function applyPowerProfile() {
        if (!batteryAvailable)
            return;
        const profileValue = isPluggedIn ? SettingsData.acProfileName : SettingsData.batteryProfileName;
        if (profileValue === "")
            return;
        const targetProfile = parseInt(profileValue);
        if (isNaN(targetProfile) || PowerProfiles.profile === targetProfile)
            return;
        PowerProfiles.profile = targetProfile;
    }

    readonly property string preferredBatteryOverride: Quickshell.env("DMS_PREFERRED_BATTERY")

    // List of laptop batteries
    readonly property var batteries: UPower.devices.values.filter(dev => dev.isLaptopBattery)

    readonly property var readyBatteries: batteries.filter(b => b.ready)
    readonly property var stateKnownBatteries: batteries.filter(b => b.ready && b.state !== UPowerDeviceState.Unknown)

    property real _lastBatteryLevel: 0
    property bool _lastIsCharging: false
    property real _lastChangeRate: 0
    property real _lastBatteryEnergy: 0
    property real _lastBatteryCapacity: 0

    readonly property bool usePreferred: preferredBatteryOverride && preferredBatteryOverride.length > 0
    readonly property UPowerDevice preferredDevice: {
        if (!usePreferred)
            return null;
        const override = preferredBatteryOverride.toLowerCase();
        return batteries.find(dev => dev.nativePath.toLowerCase().includes(override)) || null;
    }
    readonly property bool preferredDeviceKnown: preferredDevice && preferredDevice.ready && preferredDevice.state !== UPowerDeviceState.Unknown
    readonly property bool _hasKnownChargingState: {
        if (!batteryAvailable)
            return false;
        if (usePreferred)
            return preferredDeviceKnown;
        return stateKnownBatteries.length > 0;
    }
    readonly property bool _currentIsCharging: {
        if (!batteryAvailable)
            return false;
        if (usePreferred && preferredDeviceKnown)
            return preferredDevice.state === UPowerDeviceState.Charging;
        if (usePreferred)
            return false;
        return stateKnownBatteries.some(b => b.state === UPowerDeviceState.Charging);
    }

    // Main battery (for backward compatibility)
    readonly property UPowerDevice device: {
        if (usePreferred) {
            if (preferredDeviceKnown)
                return preferredDevice;
            return stateKnownBatteries[0] || null;
        }
        return stateKnownBatteries[0] || readyBatteries[0] || batteries[0] || null;
    }
    // Whether at least one battery is available
    readonly property bool batteryAvailable: batteries.length > 0
    // Aggregated charge level (percentage)
    readonly property real batteryLevel: {
        if (!batteryAvailable)
            return 0;
        if (batteryCapacity === 0) {
            if (usePreferred && preferredDeviceKnown) {
                const val = Math.min(100, Math.round(preferredDevice.percentage * 100));
                _lastBatteryLevel = val;
                return val;
            }
            if (usePreferred && preferredDevice)
                return _lastBatteryLevel;
            const validBatteries = stateKnownBatteries.filter(b => b.ready && b.percentage >= 0);
            if (validBatteries.length === 0)
                return _lastBatteryLevel;
            const avgPercentage = validBatteries.reduce((sum, b) => sum + b.percentage, 0) / validBatteries.length;
            const val = Math.min(100, Math.round(avgPercentage * 100));
            _lastBatteryLevel = val;
            return val;
        }
        const energy = batteryEnergy;
        const cap = batteryCapacity;
        if (cap === 0)
            return _lastBatteryLevel;
        const val = Math.min(100, Math.round((energy * 100) / cap));
        _lastBatteryLevel = val;
        return val;
    }
    readonly property bool isCharging: _hasKnownChargingState ? _currentIsCharging : _lastIsCharging

    // Is the system plugged in (Is not running on battery)
    readonly property bool isPluggedIn: !UPower.onBattery
    readonly property bool isLowBattery: batteryAvailable && batteryLevel <= SettingsData.batteryLowThreshold
    readonly property bool isCriticalBattery: batteryAvailable && batteryLevel <= SettingsData.batteryCriticalThreshold

    property bool _hasNotifiedLowBattery: false
    property bool _hasNotifiedCriticalBattery: false
    property bool _hasNotifiedChargeLimit: false

    function _syncLastIsCharging() {
        if (_hasKnownChargingState)
            _lastIsCharging = _currentIsCharging;
    }

    on_HasKnownChargingStateChanged: _syncLastIsCharging()
    on_CurrentIsChargingChanged: _syncLastIsCharging()

    Component.onCompleted: _syncLastIsCharging()

    function sendAlert(title, message, isWarning, category, notificationType) {
        if (notificationType === 1) {
            Quickshell.execDetached(["notify-send", "-u", isWarning ? "critical" : "normal", "-a", "DMS", "-i", isWarning ? "material:battery_alert" : "material:battery_charging_full", title, message]);
        } else {
            if (isWarning) {
                ToastService.showWarning(title, message, "", category);
            } else {
                ToastService.showInfo(title, message, "", category);
            }
        }
    }

    onBatteryLevelChanged: {
        if (isCharging && batteryLevel >= SettingsData.batteryChargeLimit) {
            if (!_hasNotifiedChargeLimit && SettingsData.batteryNotifyChargeLimit) {
                _hasNotifiedChargeLimit = true;
                sendAlert(I18n.tr("Charge Limit Reached"), I18n.tr("Battery has charged to your set limit of %1%").arg(SettingsData.batteryChargeLimit), false, "material:battery_profile", SettingsData.batteryChargeLimitNotificationType);
            }
        } else if (!isCharging || batteryLevel < SettingsData.batteryChargeLimit - 2) {
            _hasNotifiedChargeLimit = false;
        }

        if (isCharging) {
            _hasNotifiedLowBattery = false;
            _hasNotifiedCriticalBattery = false;
            return;
        }

        // Critical battery check (higher priority)
        if (isCriticalBattery) {
            if (!_hasNotifiedCriticalBattery && SettingsData.batteryNotifyCritical) {
                _hasNotifiedCriticalBattery = true;
                sendAlert(I18n.tr("Critical Battery"), I18n.tr("Battery is at %1% - Connect charger immediately!").arg(batteryLevel), true, "material:battery_alert", SettingsData.batteryCriticalNotificationType);
            }
            return;
        }

        if (batteryLevel > SettingsData.batteryCriticalThreshold) {
            _hasNotifiedCriticalBattery = false;
        }

        // Low battery check
        if (isLowBattery) {
            if (!_hasNotifiedLowBattery && SettingsData.batteryNotifyLow) {
                _hasNotifiedLowBattery = true;
                sendAlert(I18n.tr("Low Battery"), I18n.tr("Battery is at %1% - Consider charging soon").arg(batteryLevel), true, "material:battery_0_bar", SettingsData.batteryLowNotificationType);
            }

            if (SettingsData.batteryAutoPowerSaver && PowerProfileWatcher.available) {
                if (PowerProfileWatcher.currentProfile !== PowerProfile.PowerSaver) {
                    PowerProfileWatcher.applyProfile(PowerProfile.PowerSaver);
                }
            }
        }

        if (batteryLevel > SettingsData.batteryLowThreshold) {
            _hasNotifiedLowBattery = false;
        }
    }

    onIsChargingChanged: {
        // Reset average when switching states
        _smoothedChangeRate = (_hasKnownChargingState && changeRate > 0) ? changeRate : 0;
        _lastRateSampleTime = _smoothedChangeRate > 0 ? Date.now() : 0;

        if (isCharging) {
            _hasNotifiedLowBattery = false;
            _hasNotifiedCriticalBattery = false;
        } else {
            _hasNotifiedChargeLimit = false;
        }
    }

    onIsPluggedInChanged: {
        if (suppressSound || !batteryAvailable) {
            previousPluggedState = isPluggedIn;
            return;
        }

        if (SettingsData.soundsEnabled && SettingsData.soundPluggedIn) {
            if (isPluggedIn && !previousPluggedState) {
                AudioService.playPowerPlugSound();
            } else if (!isPluggedIn && previousPluggedState) {
                AudioService.playPowerUnplugSound();
            }
        }

        applyPowerProfile();

        if (isPluggedIn) {
            const dismissLow = SettingsData.batteryLowNotificationType === 1 && SettingsData.notificationTimeoutNormal === 0;
            const dismissCritical = SettingsData.batteryCriticalNotificationType === 1 && SettingsData.notificationTimeoutCritical === 0;

            if (dismissLow || dismissCritical) {
                const lowSummary = I18n.tr("Low Battery");
                const criticalSummary = I18n.tr("Critical Battery");

                for (const w of NotificationService.visibleNotifications) {
                    if (!w || !w.notification)
                        continue;

                    const summary = w.notification.summary;

                    if ((dismissLow && summary === lowSummary) || (dismissCritical && summary === criticalSummary)) {
                        NotificationService.dismissNotification(w);
                    }
                }
            }
        }

        previousPluggedState = isPluggedIn;
    }

    // Aggregated charge/discharge rate
    readonly property real changeRate: {
        if (!batteryAvailable)
            return 0;
        if (usePreferred && preferredDeviceKnown) {
            _lastChangeRate = preferredDevice.changeRate;
            return _lastChangeRate;
        }
        if (usePreferred && preferredDevice)
            return _lastChangeRate;
        if (stateKnownBatteries.length === 0)
            return _lastChangeRate;
        const val = stateKnownBatteries.reduce((sum, b) => sum + b.changeRate, 0);
        _lastChangeRate = val;
        return val;
    }

    // A time-weighted exponential moving average based on the aggregated charge/discharge rate
    property real _smoothedChangeRate: 0
    property real _lastRateSampleTime: 0
    readonly property real _rateSmoothingHalfLife: 90 // in seconds

    function _updateSmoothedRate() {
        if (!_hasKnownChargingState || changeRate <= 0)
            return;

        const now = Date.now();
        if (_smoothedChangeRate <= 0 || _lastRateSampleTime <= 0) {
            _smoothedChangeRate = changeRate;
            _lastRateSampleTime = now;
            return;
        }

        const dt = (now - _lastRateSampleTime) / 1000;
        _lastRateSampleTime = now;
        if (dt <= 0)
            return;

        const tau = _rateSmoothingHalfLife / Math.LN2;
        const alpha = 1 - Math.exp(-dt / tau);
        _smoothedChangeRate += alpha * (changeRate - _smoothedChangeRate);
    }

    onChangeRateChanged: _updateSmoothedRate()
    onBatteryAvailableChanged: if (!batteryAvailable) {
        _smoothedChangeRate = 0;
        _lastRateSampleTime = 0;
    }

    // Aggregated battery health
    readonly property string batteryHealth: {
        if (!batteryAvailable)
            return "N/A";

        if (usePreferred && preferredDeviceKnown && preferredDevice.healthSupported)
            return `${Math.round(preferredDevice.healthPercentage)}%`;

        const validBatteries = stateKnownBatteries.filter(b => b.ready && b.healthSupported && b.healthPercentage > 0);
        if (validBatteries.length === 0)
            return "N/A";

        const avgHealth = validBatteries.reduce((sum, b) => sum + b.healthPercentage, 0) / validBatteries.length;
        return `${Math.round(avgHealth)}%`;
    }

    readonly property real batteryEnergy: {
        if (!batteryAvailable)
            return 0;
        if (usePreferred && preferredDeviceKnown) {
            _lastBatteryEnergy = preferredDevice.energy;
            return _lastBatteryEnergy;
        }
        if (usePreferred && preferredDevice)
            return _lastBatteryEnergy;
        if (stateKnownBatteries.length === 0)
            return _lastBatteryEnergy;
        const val = stateKnownBatteries.reduce((sum, b) => sum + b.energy, 0);
        _lastBatteryEnergy = val;
        return val;
    }

    // Total battery capacity (Wh)
    readonly property real batteryCapacity: {
        if (!batteryAvailable)
            return 0;
        if (usePreferred && preferredDeviceKnown) {
            _lastBatteryCapacity = preferredDevice.energyCapacity;
            return _lastBatteryCapacity;
        }
        if (usePreferred && preferredDevice)
            return _lastBatteryCapacity;
        if (stateKnownBatteries.length === 0)
            return _lastBatteryCapacity;
        const val = stateKnownBatteries.reduce((sum, b) => sum + b.energyCapacity, 0);
        _lastBatteryCapacity = val;
        return val;
    }

    function translateBatteryState(state) {
        switch (state) {
        case UPowerDeviceState.Charging:
            return I18n.tr("Charging", "battery status");
        case UPowerDeviceState.Discharging:
            return I18n.tr("Discharging", "battery status");
        case UPowerDeviceState.Empty:
            return I18n.tr("Empty", "battery status");
        case UPowerDeviceState.FullyCharged:
            return I18n.tr("Fully Charged", "battery status");
        case UPowerDeviceState.PendingCharge:
            return I18n.tr("Pending Charge", "battery status");
        case UPowerDeviceState.PendingDischarge:
            return I18n.tr("Pending Discharge", "battery status");
        default:
            return I18n.tr("Unknown", "battery status");
        }
    }

    // Aggregated battery status
    readonly property string batteryStatus: {
        if (!batteryAvailable) {
            return I18n.tr("No battery", "battery status");
        }

        const targetBatteries = stateKnownBatteries.length > 0 ? stateKnownBatteries : batteries;

        if (isCharging && !targetBatteries.some(b => b.changeRate > 0))
            return I18n.tr("Plugged In", "battery status");

        const states = targetBatteries.map(b => b.state);
        if (states.every(s => s === states[0]))
            return translateBatteryState(states[0]);

        return isCharging ? I18n.tr("Charging", "battery status") : (isPluggedIn ? I18n.tr("Plugged In", "battery status") : I18n.tr("Discharging", "battery status"));
    }

    readonly property bool suggestPowerSaver: false

    readonly property var bluetoothDevices: {
        const bluetoothTypes = [UPowerDeviceType.BluetoothGeneric, UPowerDeviceType.Headphones, UPowerDeviceType.Headset, UPowerDeviceType.Keyboard, UPowerDeviceType.Mouse, UPowerDeviceType.Speakers];

        const btDevices = UPower.devices.values.filter(dev => dev && dev.ready && bluetoothTypes.includes(dev.type)).map(dev => {
            return {
                "name": dev.model || UPowerDeviceType.toString(dev.type),
                "percentage": Math.round(dev.percentage * 100),
                "type": dev.type
            };
        });

        return btDevices;
    }

    // Format time remaining for charge/discharge
    function formatTimeRemaining() {
        if (!batteryAvailable) {
            return "Unknown";
        }

        const rate = _smoothedChangeRate > 0 ? _smoothedChangeRate : changeRate;
        const totalTime = (isCharging) ? ((batteryCapacity - batteryEnergy) / rate) : (batteryEnergy / rate);
        const avgTime = Math.abs(totalTime * 3600);
        if (!avgTime || avgTime <= 0 || avgTime > 86400)
            return "Unknown";

        const hours = Math.floor(avgTime / 3600);
        const minutes = Math.floor((avgTime % 3600) / 60);
        return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
    }

    function getBatteryIcon() {
        if (!batteryAvailable) {
            return "power";
        }

        if (isCharging) {
            if (batteryLevel >= 90) {
                return "battery_charging_full";
            }
            if (batteryLevel >= 80) {
                return "battery_charging_90";
            }
            if (batteryLevel >= 60) {
                return "battery_charging_80";
            }
            if (batteryLevel >= 50) {
                return "battery_charging_60";
            }
            if (batteryLevel >= 30) {
                return "battery_charging_50";
            }
            if (batteryLevel >= 20) {
                return "battery_charging_30";
            }
            return "battery_charging_20";
        }
        if (isPluggedIn) {
            if (batteryLevel >= 90) {
                return "battery_charging_full";
            }
            if (batteryLevel >= 80) {
                return "battery_charging_90";
            }
            if (batteryLevel >= 60) {
                return "battery_charging_80";
            }
            if (batteryLevel >= 50) {
                return "battery_charging_60";
            }
            if (batteryLevel >= 30) {
                return "battery_charging_50";
            }
            if (batteryLevel >= 20) {
                return "battery_charging_30";
            }
            return "battery_charging_20";
        }
        if (batteryLevel >= 95) {
            return "battery_full";
        }
        if (batteryLevel >= 85) {
            return "battery_6_bar";
        }
        if (batteryLevel >= 70) {
            return "battery_5_bar";
        }
        if (batteryLevel >= 55) {
            return "battery_4_bar";
        }
        if (batteryLevel >= 40) {
            return "battery_3_bar";
        }
        if (batteryLevel >= 25) {
            return "battery_2_bar";
        }
        return "battery_1_bar";
    }
}
