// Software updates.
//
// Three sources that fail independently -- packages, applications, firmware --
// so each is counted and applied on its own. Blending them into one number
// would hide the case that matters: a flatpak mirror being down says nothing
// about whether a security fix is waiting.
//
// Applying packages takes a snapshot first, named after what is about to
// happen, so the Snapshots page shows "before 32 package updates" rather than a
// timestamp. That is the thing neither macOS nor Windows does cleanly, and it
// is nearly free here.

import Quickshell
import QtQuick
import qs.config
import qs.services

SettingsPage {
    id: root

    objectName: "updates"
    title: "Software Update"
    lede: "Packages, applications, and firmware, each from the place it actually comes from."

    property string expandedSource: ""

    Component.onCompleted: {
        Updates.refresh();
        // A first visit with nothing cached should not show a confident "up to
        // date" it has no basis for, so it goes and finds out.
        if (!Updates.everChecked && !Updates.checking)
            Updates.check();
    }

    TextRow {
        visible: Updates.lastError !== ""
        label: "Updates need attention"
        detail: Updates.lastError
        value: ""
        divider: false
    }

    TextRow {
        visible: Updates.lastApplied !== null
        label: "Updated"
        detail: Updates.lastApplied
            ? Updates.sourceLabel(String(Updates.lastApplied.source ?? ""))
              + (String(Updates.lastApplied.restorePoint ?? "") !== ""
                  ? " · a snapshot was taken first, number "
                    + String(Updates.lastApplied.restorePoint)
                  : "")
            : ""
        value: ""
        divider: false
    }

    // ── The headline ─────────────────────────────────────────────────────────

    SettingsCard {
        Column {
            width: parent.width
            spacing: 10

            Row {
                width: parent.width
                spacing: 12

                Text {
                    text: Updates.checking ? "Checking…" : Updates.summary()
                    color: Theme.fg
                    font.family: Theme.fontFamily
                    font.pixelSize: Theme.fontSizeTitle
                    font.weight: Font.DemiBold
                }

                Text {
                    anchors.verticalCenter: parent.verticalCenter
                    visible: Updates.securityCount > 0
                    text: Updates.securityCount + " carry a security advisory"
                    color: Theme.warn
                    font.family: Theme.fontFamily
                    font.pixelSize: Theme.fontSize
                }
            }

            Text {
                text: Updates.lastCheckedText()
                color: Theme.fgMuted
                font.family: Theme.fontFamily
                font.pixelSize: Theme.fontSizeSmall
            }
        }

        ActionRow {
            label: "Check for updates"
            detail: "Refreshes package metadata, application remotes, and firmware. Takes a few seconds."
            action: Updates.checking ? "Checking…" : "Check now"
            enabled: !Updates.busy
            divider: Updates.rebootNeeded
            onTriggered: Updates.check()
        }

        // The honest version of "restart required": the running kernel is not
        // the newest installed one, so a reboot would change which kernel runs.
        TextRow {
            visible: Updates.rebootNeeded
            label: "Restart to finish"
            detail: "A newer kernel is installed than the one running. "
                + String(Updates.kernel?.running ?? "") + " → "
                + String(Updates.kernel?.newestInstalled ?? "")
            value: "Restart needed"
            divider: false
        }
    }

    // ── One card per source ──────────────────────────────────────────────────

    SettingsCard {
        title: "System packages"
        subtitle: Updates.dnf?.available === false
            ? "dnf is not available on this machine."
            : (Number(Updates.dnf?.count ?? 0) === 0
                ? "Nothing waiting."
                : Updates.dnf.count + " package"
                  + (Updates.dnf.count === 1 ? "" : "s") + " ready to install"
                  + (Updates.securityCount > 0
                      ? ", " + Updates.securityCount + " carrying an advisory" : ""))

        ActionRow {
            visible: Number(Updates.dnf?.count ?? 0) > 0
            label: "Install package updates"
            detail: "Asks for your password, and takes a snapshot first so this can be undone"
            action: Updates.applying ? "Working…" : "Install"
            enabled: !Updates.busy
            onTriggered: Updates.apply("dnf")
        }

        ActionRow {
            visible: Number(Updates.dnf?.count ?? 0) > 0
            label: "What would change"
            detail: root.expandedSource === "dnf"
                ? "Every package that would be replaced"
                : Updates.dnf.count + " packages"
            action: root.expandedSource === "dnf" ? "Hide" : "Show"
            divider: root.expandedSource === "dnf"
            onTriggered: root.expandedSource = root.expandedSource === "dnf" ? "" : "dnf"
        }

        Repeater {
            model: root.expandedSource === "dnf" ? (Updates.dnf?.packages ?? []) : []

            delegate: TextRow {
                required property var modelData
                required property int index
                width: parent.width
                label: String(modelData.name ?? "")
                detail: String(modelData.repository ?? "")
                value: String(modelData.version ?? "")
                divider: index < (Updates.dnf?.packages ?? []).length - 1
            }
        }

        TextRow {
            visible: Number(Updates.dnf?.count ?? 0) === 0 && Updates.everChecked
            label: "Packages are current"
            detail: "Nothing from the system repositories is waiting"
            value: ""
            divider: false
        }
    }

    SettingsCard {
        title: "Applications"
        subtitle: Updates.flatpak?.available === false
            ? "Flatpak is not installed."
            : (Number(Updates.flatpak?.count ?? 0) === 0
                ? "Nothing waiting."
                : Updates.flatpak.count + " application"
                  + (Updates.flatpak.count === 1 ? "" : "s") + " ready to update")

        Repeater {
            model: Updates.flatpak?.applications ?? []

            delegate: TextRow {
                required property var modelData
                required property int index
                width: parent.width
                label: String(modelData.id ?? "")
                detail: "Flatpak"
                value: String(modelData.version ?? "")
                divider: true
            }
        }

        ActionRow {
            visible: Number(Updates.flatpak?.count ?? 0) > 0
            label: "Update applications"
            detail: "Needs no password: these are installed for your account"
            action: Updates.applying ? "Working…" : "Update"
            enabled: !Updates.busy
            onTriggered: Updates.apply("flatpak")
        }

        SwitchRow {
            label: "Update applications automatically"
            detail: "Once a day, in the background. Applications are not a security boundary the way packages are, so this is safe to leave on; packages still ask."
            checked: Updates.automatic?.flatpakEnabled === true
            enabled: !Updates.busy && Updates.automatic?.flatpakAvailable === true
            divider: false
            onToggled: value => Updates.setAutomaticFlatpak(value)
        }
    }

    SettingsCard {
        title: "Firmware"
        subtitle: Updates.firmware?.available === false
            ? "Firmware updating is not available on this machine."
            : (Number(Updates.firmware?.count ?? 0) === 0
                ? "No firmware updates are offered for this hardware."
                : Updates.firmware.count + " device"
                  + (Updates.firmware.count === 1 ? "" : "s") + " have firmware available")

        Repeater {
            model: Updates.firmware?.devices ?? []

            delegate: TextRow {
                required property var modelData
                required property int index
                width: parent.width
                label: String(modelData.name ?? "")
                detail: String(modelData.version ?? "") + " → " + String(modelData.target ?? "")
                    + (modelData.needsReboot ? " · installs on restart" : "")
                value: ""
                divider: true
            }
        }

        ActionRow {
            visible: Number(Updates.firmware?.count ?? 0) > 0
            label: "Install firmware"
            detail: "Some devices only finish updating after a restart"
            action: Updates.applying ? "Working…" : "Install"
            enabled: !Updates.busy
            divider: false
            onTriggered: Updates.apply("firmware")
        }

        // Offered only when the machinery exists. When it does not, this says
        // so rather than showing a switch that could not do anything --
        // installing software is not a settings action.
        SwitchRow {
            visible: Updates.automatic?.dnfAutomaticAvailable === true
            label: "Download package updates automatically"
            detail: "Fetches them in the background each morning so installing is quick. It does not install them: a machine that updates packages unattended can reboot into a kernel nobody chose."
            checked: Updates.automatic?.dnfAutomaticEnabled === true
            enabled: !Updates.busy
            divider: false
            onToggled: value => Updates.setAutomaticDnf(value)
        }

        TextRow {
            visible: Updates.automatic?.dnfAutomaticAvailable === false
            label: "Automatic package updates"
            detail: "Not set up. dnf-automatic is not installed, and Settings does not install software."
            value: "Off"
            divider: false
        }
    }

    // Worth showing because automatic updates leave no other trace. Something
    // that installed itself overnight is invisible until you look here.
    SettingsCard {
        title: "Recently installed"
        subtitle: Updates.historyLoaded
            ? "Packages and applications, newest first, from both sources at once."
            : "Asks dnf and flatpak for their transaction logs."

        ActionRow {
            visible: !Updates.historyLoaded
            label: "Show what has been installed"
            detail: "Not loaded with the page: it reads the whole history from both sources"
            action: Updates.loadingHistory ? "Reading…" : "Show"
            enabled: !Updates.loadingHistory
            divider: false
            onTriggered: Updates.loadHistory()
        }

        Repeater {
            model: Updates.history

            delegate: TextRow {
                required property var modelData
                required property int index
                width: parent.width
                label: String(modelData.summary ?? "")
                detail: Updates.agoText(Number(modelData.at ?? 0))
                value: Updates.describeHistory(modelData)
                divider: index < Updates.history.length - 1
            }
        }

        TextRow {
            visible: Updates.historyLoaded && Updates.history.length === 0
            label: "Nothing recorded"
            detail: "Neither dnf nor flatpak has a transaction history here."
            value: ""
            divider: false
        }
    }
}
