pragma Singleton
pragma ComponentBehavior: Bound

import QtQuick
import Quickshell
import Quickshell.Io
import qs.Common

Singleton {
    id: root

    property int refCount: 0
    property var availableUpdates: []
    property bool isChecking: false
    property bool hasError: false
    property string errorMessage: ""
    property string updChecker: ""
    property string pkgManager: ""
    property string distribution: ""
    property bool distributionSupported: false
    property string shellVersion: ""
    property string shellCodename: ""
    property string semverVersion: ""

    function getParsedShellVersion() {
        return parseVersion(semverVersion);
    }

    readonly property var archBasedUCSettings: {
        "listUpdatesSettings": {
            "params": [],
            "correctExitCodes": [0, 2]   // Exit code 0 = updates available, 2 = no updates
        },
        "parserSettings": {
            "lineRegex": /^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/,
            "entryProducer": function (match) {
                return {
                    "name": match[1],
                    "currentVersion": match[2],
                    "newVersion": match[3],
                    "description": `${match[1]} ${match[2]} → ${match[3]}`
                };
            }
        }
    }

    readonly property var archBasedPMSettings: function(requiresSudo) {
        return {
            "listUpdatesSettings": {
                "params": ["-Qu"],
                "correctExitCodes": [0, 1]   // Exit code 0 = updates available, 1 = no updates
            },
            "upgradeSettings": {
                "params": ["-Syu"],
                "requiresSudo": requiresSudo
            },
            "parserSettings": {
                "lineRegex": /^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/,
                "entryProducer": function (match) {
                    return {
                        "name": match[1],
                        "currentVersion": match[2],
                        "newVersion": match[3],
                        "description": `${match[1]} ${match[2]} → ${match[3]}`
                    };
                }
            }
        }
    }

    readonly property var fedoraBasedPMSettings: {
        "listUpdatesSettings": {
            "params": ["list", "--upgrades", "--quiet", "--color=never"],
            "correctExitCodes": [0, 1]   // Exit code 0 = updates available, 1 = no updates
        },
        "upgradeSettings": {
            "params": ["upgrade"],
            "requiresSudo": true
        },
        "parserSettings": {
            "lineRegex": /^([^\s]+)\s+([^\s]+)\s+.*$/,
            "entryProducer": function (match) {
                return {
                    "name": match[1],
                    "currentVersion": "",
                    "newVersion": match[2],
                    "description": `${match[1]} → ${match[2]}`
                };
            }
        }
    }

    readonly property var updateCheckerParams: {
        "checkupdates": archBasedUCSettings
    }
    readonly property var packageManagerParams: {
        "yay": archBasedPMSettings(false),
        "paru": archBasedPMSettings(false),
        "pacman": archBasedPMSettings(true),
        "dnf": fedoraBasedPMSettings
    }
    readonly property list<string> supportedDistributions: ["arch", "artix", "cachyos", "manjaro", "endeavouros", "fedora"]
    readonly property int updateCount: availableUpdates.length
    readonly property bool helperAvailable: pkgManager !== "" && distributionSupported

    Process {
        id: distributionDetection
        command: ["sh", "-c", "cat /etc/os-release | grep '^ID=' | cut -d'=' -f2 | tr -d '\"'"]
        running: true

        onExited: exitCode => {
            if (exitCode === 0) {
                distribution = stdout.text.trim().toLowerCase();
                distributionSupported = supportedDistributions.includes(distribution);

                if (distributionSupported) {
                    updateFinderDetection.running = true;
                    pkgManagerDetection.running = true;
                    checkForUpdates();
                } else {
                    console.warn("SystemUpdate: Unsupported distribution:", distribution);
                }
            } else {
                console.warn("SystemUpdate: Failed to detect distribution");
            }
        }

        stdout: StdioCollector {}

        Component.onCompleted: {
            versionDetection.running = true;
        }
    }

    Process {
        id: versionDetection
        command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -d .git ]; then echo "(git) $(git rev-parse --short HEAD)"; elif [ -f VERSION ]; then cat VERSION; fi`]

        stdout: StdioCollector {
            onStreamFinished: {
                shellVersion = text.trim();
            }
        }
    }

    Process {
        id: semverDetection
        command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -f VERSION ]; then cat VERSION; fi`]
        running: true

        stdout: StdioCollector {
            onStreamFinished: {
                semverVersion = text.trim();
            }
        }
    }

    Process {
        id: codenameDetection
        command: ["sh", "-c", `cd "${Quickshell.shellDir}" && if [ -f CODENAME ]; then cat CODENAME; fi`]
        running: true

        stdout: StdioCollector {
            onStreamFinished: {
                shellCodename = text.trim();
            }
        }
    }

    Process {
        id: updateFinderDetection
        command: ["sh", "-c", "which checkupdates"]

        onExited: exitCode => {
            if (exitCode === 0) {
                const exeFound = stdout.text.trim();
                updChecker = exeFound.split('/').pop();
            } else {
                console.warn("SystemUpdate: No update checker found. Will use package manager.");
            }
        }

        stdout: StdioCollector {}
    }

    Process {
        id: pkgManagerDetection
        command: ["sh", "-c", "which paru || which yay || which pacman || which dnf"]

        onExited: exitCode => {
            if (exitCode === 0) {
                const exeFound = stdout.text.trim();
                pkgManager = exeFound.split('/').pop();
            } else {
                console.warn("SystemUpdate: No package manager found");
            }
        }

        stdout: StdioCollector {}
    }

    Process {
        id: updateChecker

        onExited: exitCode => {
            isChecking = false;
            const correctExitCodes = updChecker.length > 0 ? [updChecker].concat(updateCheckerParams[updChecker].listUpdatesSettings.correctExitCodes) : [pkgManager].concat(packageManagerParams[pkgManager].listUpdatesSettings.correctExitCodes);
            if (correctExitCodes.includes(exitCode)) {
                parseUpdates(stdout.text);
                hasError = false;
                errorMessage = "";
            } else {
                hasError = true;
                errorMessage = "Failed to check for updates";
                console.warn("SystemUpdate: Update check failed with code:", exitCode);
            }
        }

        stdout: StdioCollector {}
    }

    Process {
        id: updater
        onExited: exitCode => {
            checkForUpdates();
        }
    }

    function checkForUpdates() {
        if (!distributionSupported || (!pkgManager && !updChecker) || isChecking)
            return;
        isChecking = true;
        hasError = false;
        if (pkgManager === "paru" || pkgManager === "yay") {
            const repoCmd = updChecker.length > 0 ? updChecker : `${pkgManager} -Qu`;
            updateChecker.command = ["sh", "-c", `(${repoCmd} 2>/dev/null; ${pkgManager} -Qua 2>/dev/null) || true`];
        } else if (updChecker.length > 0) {
            updateChecker.command = [updChecker].concat(updateCheckerParams[updChecker].listUpdatesSettings.params);
        } else {
            updateChecker.command = [pkgManager].concat(packageManagerParams[pkgManager].listUpdatesSettings.params);
        }
        updateChecker.running = true;
    }

    function parseUpdates(output) {
        const lines = output.trim().split('\n').filter(line => line.trim());
        const updates = [];

        const regex = packageManagerParams[pkgManager].parserSettings.lineRegex;
        const entryProducer = packageManagerParams[pkgManager].parserSettings.entryProducer;

        for (const line of lines) {
            const match = line.match(regex);
            if (match) {
                updates.push(entryProducer(match));
            }
        }

        availableUpdates = updates;
    }

    function runUpdates() {
        if (!distributionSupported || !pkgManager || updateCount === 0)
            return;
        const terminal = Quickshell.env("TERMINAL") || "xterm";

        if (SettingsData.updaterUseCustomCommand && SettingsData.updaterCustomCommand.length > 0) {
            const updateCommand = `${SettingsData.updaterCustomCommand} && echo -n "Updates complete! " ; echo "Press Enter to close..." && read`;
            const termClass = SettingsData.updaterTerminalAdditionalParams;

            var finalCommand = [terminal];
            if (termClass.length > 0) {
                finalCommand = finalCommand.concat(termClass.split(" "));
            }
            finalCommand.push("-e");
            finalCommand.push("sh");
            finalCommand.push("-c");
            finalCommand.push(updateCommand);
            updater.command = finalCommand;
        } else {
            const params = packageManagerParams[pkgManager].upgradeSettings.params.join(" ");
            const sudo = packageManagerParams[pkgManager].upgradeSettings.requiresSudo ? "sudo" : "";
            const updateCommand = `${sudo} ${pkgManager} ${params} && echo -n "Updates complete! " ; echo "Press Enter to close..." && read`;

            updater.command = [terminal, "-e", "sh", "-c", updateCommand];
        }
        updater.running = true;
    }

    Timer {
        interval: 30 * 60 * 1000
        repeat: true
        running: refCount > 0 && distributionSupported && (pkgManager || updChecker)
        onTriggered: checkForUpdates()
    }

    IpcHandler {
        target: "systemupdater"

        function updatestatus(): string {
            if (root.isChecking) {
                return "ERROR: already checking";
            }
            if (!distributionSupported) {
                return "ERROR: distribution not supported";
            }
            if (!pkgManager && !updChecker) {
                return "ERROR: update checker not available";
            }
            root.checkForUpdates();
            return "SUCCESS: Now checking...";
        }
    }

    function parseVersion(versionStr) {
        if (!versionStr || typeof versionStr !== "string")
            return {
                major: 0,
                minor: 0,
                patch: 0
            };

        let v = versionStr.trim();
        if (v.startsWith("v"))
            v = v.substring(1);

        const dashIdx = v.indexOf("-");
        if (dashIdx !== -1)
            v = v.substring(0, dashIdx);

        const plusIdx = v.indexOf("+");
        if (plusIdx !== -1)
            v = v.substring(0, plusIdx);

        const parts = v.split(".");
        return {
            major: parseInt(parts[0], 10) || 0,
            minor: parseInt(parts[1], 10) || 0,
            patch: parseInt(parts[2], 10) || 0
        };
    }

    function compareVersions(v1, v2) {
        if (v1.major !== v2.major)
            return v1.major - v2.major;
        if (v1.minor !== v2.minor)
            return v1.minor - v2.minor;
        return v1.patch - v2.patch;
    }

    function checkVersionRequirement(requirementStr, currentVersion) {
        if (!requirementStr || typeof requirementStr !== "string")
            return true;

        const req = requirementStr.trim();
        let operator = "";
        let versionPart = req;

        if (req.startsWith(">=")) {
            operator = ">=";
            versionPart = req.substring(2);
        } else if (req.startsWith("<=")) {
            operator = "<=";
            versionPart = req.substring(2);
        } else if (req.startsWith(">")) {
            operator = ">";
            versionPart = req.substring(1);
        } else if (req.startsWith("<")) {
            operator = "<";
            versionPart = req.substring(1);
        } else if (req.startsWith("=")) {
            operator = "=";
            versionPart = req.substring(1);
        } else {
            operator = ">=";
        }

        const reqVersion = parseVersion(versionPart);
        const cmp = compareVersions(currentVersion, reqVersion);

        switch (operator) {
        case ">=":
            return cmp >= 0;
        case ">":
            return cmp > 0;
        case "<=":
            return cmp <= 0;
        case "<":
            return cmp < 0;
        case "=":
            return cmp === 0;
        default:
            return cmp >= 0;
        }
    }
}
