// Storage: what is using the drive, then what the drive is.
//
// The page is one scroll with a rule behind it: everything above the divider
// answers "how much space do I have and what took it", everything below answers
// "what is this device and is it healthy". A card that answers neither does not
// belong here.
//
// Measuring what is using the space is expensive -- a Steam library alone can
// be a terabyte -- so it happens on request rather than on open. The page says
// so plainly instead of showing an empty list that reads as "nothing here".
//
// Partitioning and formatting are deliberately absent; GNOME Disks is one row
// away for that.

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

SettingsPage {
    id: root

    objectName: "storage"
    title: "Storage"
    lede: "How much space is left, what is using it, and whether the drive is healthy."

    readonly property var rootFs: Disks.rootFilesystem
    readonly property var drive: Disks.primaryDrive

    // The measured folders, as a share of the largest one, so the bars compare
    // against each other rather than against a total they do not sum to.
    readonly property real largestFolder: {
        let largest = 0;
        for (const folder of Disks.folders)
            largest = Math.max(largest, Number(folder.bytes ?? 0));
        return largest;
    }

    readonly property var removableDrives: Disks.drives.filter(entry => entry.removable)

    Component.onCompleted: Disks.refresh()

    TextRow {
        visible: Disks.lastError !== ""
        label: "Storage needs attention"
        detail: Disks.lastError
        value: ""
        divider: false
    }

    // ── Space ────────────────────────────────────────────────────────────────

    SettingsCard {
        title: "Free space"
        subtitle: root.rootFs && (root.rootFs.mountpoints ?? []).length > 1
            ? "One filesystem is mounted at " + Disks.mountLabel(root.rootFs)
              + ", so they share the same space."
            : "The filesystem this session runs from."

        Column {
            width: parent.width
            spacing: 10

            Row {
                width: parent.width
                spacing: 12

                Text {
                    text: root.rootFs ? Disks.formatBytes(root.rootFs.availBytes) + " free" : "Reading…"
                    color: Theme.fg
                    font.family: Theme.fontFamily
                    font.pixelSize: Theme.fontSizeTitle
                    font.weight: Font.DemiBold
                }

                Text {
                    anchors.verticalCenter: parent.verticalCenter
                    visible: root.rootFs !== null
                    text: root.rootFs
                        ? "of " + Disks.formatBytes(root.rootFs.sizeBytes)
                          + " · " + String(root.rootFs.fstype ?? "")
                        : ""
                    color: Theme.fgDim
                    font.family: Theme.fontFamily
                    font.pixelSize: Theme.fontSize
                }
            }

            Rectangle {
                width: parent.width
                height: 10
                radius: 5
                color: Theme.alpha(Theme.fg, 0.09)

                Rectangle {
                    width: Math.max(parent.width * Disks.usedFraction(root.rootFs), parent.height)
                    height: parent.height
                    radius: parent.radius
                    // Color is the only warning this bar gives, so it changes
                    // at the point where free space starts to be a problem
                    // rather than at a round number.
                    color: Disks.usedFraction(root.rootFs) > 0.92
                        ? Theme.danger
                        : (Disks.usedFraction(root.rootFs) > 0.8 ? Theme.warn : Theme.accent)
                }
            }

            Text {
                text: root.rootFs
                    ? Disks.formatBytes(root.rootFs.usedBytes) + " used · "
                      + Math.round(Disks.usedFraction(root.rootFs) * 100) + "%"
                    : ""
                color: Theme.fgMuted
                font.family: Theme.fontFamily
                font.pixelSize: Theme.fontSizeSmall
            }
        }
    }

    SettingsCard {
        title: "What is using it"
        subtitle: Disks.foldersMeasured
            ? "Measured by walking each folder."
            : "Measuring means walking every file, so it is not done automatically."

        ActionRow {
            visible: !Disks.foldersMeasured || Disks.scanning
            label: Disks.scanning ? "Measuring…" : "Measure folders"
            detail: Disks.scanning
                ? "Walking the largest folders. This can take a minute."
                : "Walk the usual suspects and report what is biggest."
            action: "Measure"
            enabled: !Disks.scanning
            divider: Disks.foldersMeasured
            onTriggered: Disks.scan()
        }

        Repeater {
            model: Disks.foldersMeasured ? Disks.folders : []

            delegate: Column {
                id: folderBlock

                required property var modelData
                required property int index

                width: parent.width

                TextRow {
                    width: folderBlock.width
                    label: String(folderBlock.modelData.label ?? "")
                    detail: String(folderBlock.modelData.path ?? "")
                    value: Disks.formatBytes(folderBlock.modelData.bytes ?? 0)
                    divider: false
                }

                Rectangle {
                    width: folderBlock.width
                    height: 4
                    radius: 2
                    color: Theme.alpha(Theme.fg, 0.08)

                    // Relative to the LARGEST folder, not to the drive. At this
                    // scale one folder holds almost everything, so bars drawn
                    // against the total would leave every other row invisible.
                    Rectangle {
                        width: root.largestFolder > 0
                            ? Math.max(parent.width * (Number(folderBlock.modelData.bytes ?? 0) / root.largestFolder), 2)
                            : 0
                        height: parent.height
                        radius: parent.radius
                        color: Theme.accent
                    }
                }

                Item { width: 1; height: 10 }
            }
        }

        TextRow {
            visible: Disks.foldersMeasured && Disks.folderScanTruncated
            label: "Some folders were not measured"
            detail: "The walk ran out of time before reaching them, so this list is incomplete."
            value: ""
            divider: Disks.containers !== null
        }

        ActionRow {
            visible: Disks.containers !== null
                && Number(Disks.containers?.reclaimableBytes ?? 0) > 0
            label: "Unused container images"
            detail: Disks.containers
                ? Disks.formatBytes(Disks.containers.reclaimableBytes)
                  + " of " + Disks.formatBytes(Disks.containers.totalBytes)
                  + " is not used by any container"
                : ""
            action: "Show"
            divider: false
            // Reclaiming is not offered here on purpose: pruning images can
            // destroy work that lives outside this desktop, and a settings pane
            // should not put that one click deep. This opens a terminal showing
            // what would be reclaimed, and leaves the decision there.
            onTriggered: Quickshell.execDetached(
                ["kitty", "--hold", "-e", "podman", "system", "df"])
        }
    }

    // ── The device ───────────────────────────────────────────────────────────

    SettingsCard {
        title: "Drive"
        subtitle: root.drive
            ? String(root.drive.model) + (root.drive.encrypted ? " · encrypted" : "")
            : "Reading…"

        TextRow {
            visible: root.drive !== null
            label: "Health"
            detail: root.drive && root.drive.selfTest !== ""
                ? "Last self-test: " + String(root.drive.selfTest)
                : "Reported by the drive itself"
            value: Disks.healthSummary(root.drive)
        }

        TextRow {
            visible: root.drive !== null && root.drive.temperatureC !== null
            label: "Temperature"
            detail: "Measured by the drive controller"
            value: root.drive && root.drive.temperatureC !== null
                ? String(root.drive.temperatureC) + " °C" : ""
        }

        TextRow {
            visible: root.drive !== null && root.drive.powerOnHours !== null
            label: "Powered on"
            detail: "Total hours this drive has been running"
            value: root.drive && root.drive.powerOnHours !== null
                ? String(root.drive.powerOnHours) + " hours" : ""
        }

        TextRow {
            visible: root.drive !== null && root.drive.encrypted
            label: "Encryption"
            detail: "The filesystem is encrypted and unlocked at boot"
            value: "LUKS"
        }

        ActionRow {
            label: "Partitioning and formatting"
            detail: "Deliberately not here. Disks is the tool that owns erasing a drive."
            action: "Open Disks"
            divider: false
            onTriggered: Quickshell.execDetached(["gapplication", "launch", "org.gnome.DiskUtility"])
        }
    }

    SettingsCard {
        title: "Filesystems"
        subtitle: "Every mounted filesystem, grouped by the device behind it."

        Repeater {
            model: Disks.filesystems

            delegate: TextRow {
                id: filesystemRow

                required property var modelData
                required property int index

                width: parent.width
                label: Disks.mountLabel(filesystemRow.modelData)
                detail: String(filesystemRow.modelData.fstype ?? "")
                    + " on " + String(filesystemRow.modelData.device ?? "")
                value: Disks.formatBytes(filesystemRow.modelData.availBytes) + " free of "
                    + Disks.formatBytes(filesystemRow.modelData.sizeBytes)
                divider: filesystemRow.index < Disks.filesystems.length - 1
            }
        }
    }

    // Named here because this is the page somebody opens when space is short,
    // and container images are usually the largest thing nobody remembers
    // having. Only shown when there is actually something to reclaim.
    SettingsCard {
        visible: Containers.available && Containers.reclaimable > 0
        title: "Containers are holding " + Containers.formatBytes(Containers.reclaimable)

        ActionRow {
            label: "Images and volumes nothing references"
            detail: Containers.unusedImages.length + " image"
                + (Containers.unusedImages.length === 1 ? "" : "s") + " and "
                + Containers.unusedVolumes.length + " volume"
                + (Containers.unusedVolumes.length === 1 ? "" : "s")
            action: "Review"
            divider: false
            onTriggered: ShellState.settingsPage = "containers"
        }
    }

    SettingsCard {
        title: "Removable drives"
        subtitle: "USB drives and memory cards."

        Repeater {
            model: root.removableDrives

            delegate: ActionRow {
                id: removableRow

                required property var modelData

                width: parent.width
                label: String(removableRow.modelData.model ?? removableRow.modelData.name)
                detail: Disks.formatBytes(removableRow.modelData.sizeBytes)
                    + " · " + String(removableRow.modelData.path)
                action: removableRow.modelData.ejectable ? "Eject" : "Unmount"
                divider: false
                onTriggered: removableRow.modelData.ejectable
                    ? Disks.eject(String(removableRow.modelData.path))
                    : Disks.unmount(String(removableRow.modelData.path))
            }
        }

        TextRow {
            visible: root.removableDrives.length === 0
            label: "None connected"
            detail: "Drives you plug in appear here, with a way to unmount them safely."
            value: ""
            divider: false
        }
    }

    SettingsCard {
        visible: Disks.swap.length > 0
        title: "Swap"
        subtitle: "Compressed swap lives in memory, not on the drive."

        Repeater {
            model: Disks.swap

            delegate: TextRow {
                id: swapRow

                required property var modelData
                required property int index

                width: parent.width
                label: String(swapRow.modelData.name ?? "")
                detail: String(swapRow.modelData.kind) === "zram"
                    ? "Compressed swap in RAM" : "Swap"
                value: Disks.formatBytes(swapRow.modelData.sizeBytes)
                divider: swapRow.index < Disks.swap.length - 1
            }
        }
    }
}
