pragma Singleton
pragma ComponentBehavior: Bound

import qs
import QtQuick
import Quickshell
import Quickshell.Io

Singleton {
    id: root

    property string interfaceName: ""
    property real rxBytesPerSecond: 0
    property real txBytesPerSecond: 0
    property string downloadSpeedText: formatSpeed(rxBytesPerSecond)
    property string uploadSpeedText: formatSpeed(txBytesPerSecond)
    property string downloadSpeedCompactText: formatCompactSpeed(rxBytesPerSecond)
    property string uploadSpeedCompactText: formatCompactSpeed(txBytesPerSecond)
    readonly property bool available: interfaceName.length > 0

    property real previousRxBytes: -1
    property real previousTxBytes: -1
    property double previousTimestampMs: 0

    function formatSpeed(bytesPerSecond) {
        const units = ["B/s", "K/s", "M/s", "G/s"];
        let value = Math.max(0, bytesPerSecond);
        let unitIndex = 0;

        while (value >= 1024 && unitIndex < units.length - 1) {
            value /= 1024;
            unitIndex++;
        }

        const precision = value >= 100 ? 0 : value >= 10 ? 1 : 2;
        return `${value.toFixed(precision)}${units[unitIndex]}`;
    }

    function formatCompactSpeed(bytesPerSecond) {
        const units = ["B", "K", "M", "G"];
        let value = Math.max(0, bytesPerSecond);
        let unitIndex = 0;

        while (value >= 1024 && unitIndex < units.length - 1) {
            value /= 1024;
            unitIndex++;
        }

        let textValue = "0";
        if (value >= 100)
            textValue = `${Math.round(value)}`;
        else if (value >= 10)
            textValue = `${Math.round(value)}`;
        else
            textValue = `${value.toFixed(1)}`.replace(/\.0$/, "");

        return `${textValue}${units[unitIndex]}/s`;
    }

    function pickInterfaceName(routeText, devText) {
        const routeLines = routeText.trim().split("\n").slice(1);
        for (const line of routeLines) {
            const columns = line.trim().split(/\s+/);
            if (columns.length < 2)
                continue;

            const iface = columns[0];
            const destination = columns[1];
            if (destination === "00000000" && iface !== "lo")
                return iface;
        }

        const devLines = devText.trim().split("\n").slice(2);
        for (const line of devLines) {
            const iface = line.split(":")[0]?.trim() ?? "";
            if (iface.length > 0 && iface !== "lo")
                return iface;
        }

        return "";
    }

    function parseCounters(devText, ifaceName) {
        if (!ifaceName)
            return null;

        const escapedName = ifaceName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
        const lineMatch = devText.match(new RegExp(`^\\s*${escapedName}\\s*:\\s*(.+)$`, "m"));
        if (!lineMatch)
            return null;

        const fields = lineMatch[1].trim().split(/\s+/);
        if (fields.length < 9)
            return null;

        return {
            rx: Number(fields[0]),
            tx: Number(fields[8])
        };
    }

    function update() {
        routeFile.reload();
        devFile.reload();

        const routeText = routeFile.text();
        const devText = devFile.text();
        const detectedInterface = pickInterfaceName(routeText, devText);

        if (detectedInterface !== interfaceName) {
            interfaceName = detectedInterface;
            previousRxBytes = -1;
            previousTxBytes = -1;
            previousTimestampMs = 0;
        }

        if (!interfaceName) {
            rxBytesPerSecond = 0;
            txBytesPerSecond = 0;
            return;
        }

        const counters = parseCounters(devText, interfaceName);
        if (!counters) {
            rxBytesPerSecond = 0;
            txBytesPerSecond = 0;
            return;
        }

        const now = Date.now();

        if (previousRxBytes < 0 || previousTxBytes < 0 || previousTimestampMs <= 0) {
            previousRxBytes = counters.rx;
            previousTxBytes = counters.tx;
            previousTimestampMs = now;
            rxBytesPerSecond = 0;
            txBytesPerSecond = 0;
            return;
        }

        const elapsedMs = Math.max(1, now - previousTimestampMs);
        rxBytesPerSecond = Math.max(0, (counters.rx - previousRxBytes) * 1000 / elapsedMs);
        txBytesPerSecond = Math.max(0, (counters.tx - previousTxBytes) * 1000 / elapsedMs);

        previousRxBytes = counters.rx;
        previousTxBytes = counters.tx;
        previousTimestampMs = now;
    }

    Timer {
        interval: 1000
        // Souveraine: pause while the display is inactive (idle-power task 4).
        // The rate math is elapsed-time based, so the first tick after resume
        // yields one correctly averaged sample rather than a spike.
        running: GlobalStates.displayActive
        repeat: true
        triggeredOnStart: true
        onTriggered: root.update()
    }

    FileView { id: routeFile; path: "/proc/net/route" }
    FileView { id: devFile; path: "/proc/net/dev" }
}
