// Snapshots: what is protected, and how to get something back.
//
// Per volume, because on this machine the news was that one volume was covered
// and the important one was not -- six hundred snapshots of the system, none of
// anyone's documents. A timeline that opened on all those snapshots would have
// buried that.
//
// Inside a volume, the timeline is the Time Machine view: points in time,
// newest first, each one openable as a folder tree you can take a file out of.
//
// Rollback is deliberately absent. snapper's rollback changes the btrfs default
// subvolume, and this system's fstab pins subvol= explicitly, which overrides
// it -- so it would report success and change nothing after a reboot.

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

SettingsPage {
    id: root

    objectName: "snapshots"
    title: "Snapshots"
    lede: "Points in time you can go back to, taken automatically for each volume."

    // Points in time shown without asking. The page used to show none: the
    // timeline and its Delete buttons sat behind a row labelled "Browse…", a
    // word that promises a file browser, so a page about points in time
    // appeared to list only how many there were.
    readonly property int previewCount: 3

    property string openConfig: ""
    property string confirmingDelete: ""
    property string confirmingRestore: ""

    readonly property bool browsingOpen: Snapshots.browsingConfig !== ""

    Component.onCompleted: Snapshots.refresh()

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

    // What just happened to the file that was already there.
    TextRow {
        visible: Snapshots.lastRestore !== null
        label: "Restored"
        detail: Snapshots.lastRestore
            ? String(Snapshots.lastRestore.restored ?? "")
              + (String(Snapshots.lastRestore.keptAs ?? "") !== ""
                  ? " — the version that was there was kept as "
                    + String(Snapshots.lastRestore.keptAs).split("/").pop()
                  : "")
            : ""
        value: ""
        divider: false
    }

    // ── Anything unprotected is the headline ─────────────────────────────────

    SettingsCard {
        visible: Snapshots.unprotected.length > 0
        title: "Not protected"
        subtitle: "These volumes have no snapshot configuration, so nothing on them can be recovered."

        Repeater {
            model: Snapshots.unprotected

            delegate: TextRow {
                required property var modelData
                width: parent.width
                label: String(modelData.path ?? "")
                detail: "btrfs subvolume " + String(modelData.subvolume ?? "")
                    + " · needs a configuration, which takes a password once"
                value: "Unprotected"
                divider: false
            }
        }
    }

    // ── One card per volume ──────────────────────────────────────────────────

    Repeater {
        model: Snapshots.configs

        delegate: SettingsCard {
            id: volumeCard

            required property var modelData

            readonly property string configName: String(volumeCard.modelData.name ?? "")
            readonly property var snapshots: volumeCard.modelData.snapshots ?? []
            readonly property bool open: root.openConfig === volumeCard.configName
            readonly property int shownCount: volumeCard.open
                ? volumeCard.snapshots.length
                : Math.min(root.previewCount, volumeCard.snapshots.length)

            title: Snapshots.labelFor(volumeCard.modelData)
            subtitle: String(volumeCard.modelData.subvolume ?? "")

            SwitchRow {
                label: "Take snapshots automatically"
                detail: volumeCard.modelData.timelineEnabled
                    ? Snapshots.describe(volumeCard.modelData)
                    : "Nothing is being taken for this volume"
                checked: volumeCard.modelData.timelineEnabled === true
                enabled: !Snapshots.busy && volumeCard.modelData.readable === true
                onToggled: value => Snapshots.setTimeline(volumeCard.configName, value)
            }

            TextRow {
                label: "Keep"
                detail: "Older points are removed automatically once these counts are exceeded"
                value: Snapshots.retentionSummary(volumeCard.modelData)
            }

            ActionRow {
                label: "Take one now"
                detail: "Kept until you remove it, unlike the automatic ones"
                action: "Take snapshot"
                enabled: !Snapshots.busy && volumeCard.modelData.readable === true
                onTriggered: Snapshots.take(volumeCard.configName, "Taken from Settings")
            }

            TextRow {
                visible: volumeCard.snapshots.length === 0
                label: "No points in time yet"
                detail: "One is taken automatically on the schedule above."
                value: ""
                divider: false
            }

            // ── The timeline ─────────────────────────────────────────────────

            Column {
                width: parent.width
                visible: !root.browsingOpen

                Repeater {
                    model: volumeCard.open
                        ? volumeCard.snapshots
                        : volumeCard.snapshots.slice(0, root.previewCount)

                    delegate: SettingRow {
                        id: pointRow

                        required property var modelData
                        required property int index

                        readonly property string token: volumeCard.configName + ":" + pointRow.modelData.number
                        readonly property bool confirming: root.confirmingDelete === pointRow.token

                        width: parent.width
                        // A kept snapshot is one the timeline will not remove,
                        // which is the distinction that matters when choosing
                        // what to rely on later.
                        icon: pointRow.modelData.kept ? "\u{F0A22}" : "\u{F0954}"
                        label: String(pointRow.modelData.date ?? "")
                        detail: String(pointRow.modelData.description ?? "")
                            + " · #" + pointRow.modelData.number
                            + (pointRow.modelData.kept ? " · kept" : "")
                        controlWidth: 250
                        divider: pointRow.index < volumeCard.shownCount - 1

                        Row {
                            anchors.right: parent.right
                            anchors.verticalCenter: parent.verticalCenter
                            spacing: 8

                            SettingsButton {
                                text: "Open"
                                enabled: !Snapshots.browsing
                                onClicked: Snapshots.browse(volumeCard.configName,
                                                            Number(pointRow.modelData.number), "")
                            }

                            SettingsButton {
                                text: pointRow.confirming ? "Keep" : "Delete"
                                enabled: !Snapshots.busy
                                onClicked: root.confirmingDelete =
                                    pointRow.confirming ? "" : pointRow.token
                            }

                            SettingsButton {
                                visible: pointRow.confirming
                                text: "Delete it"
                                tone: "danger"
                                enabled: !Snapshots.busy
                                onClicked: {
                                    root.confirmingDelete = "";
                                    Snapshots.remove(volumeCard.configName,
                                                     Number(pointRow.modelData.number));
                                }
                            }
                        }
                    }
                }

                SettingRow {
                    width: parent.width
                    visible: volumeCard.snapshots.length > root.previewCount
                    activatable: true
                    divider: false
                    label: volumeCard.open
                        ? "Showing all " + volumeCard.snapshots.length + " points in time"
                        : (volumeCard.snapshots.length - root.previewCount)
                          + " older point"
                          + (volumeCard.snapshots.length - root.previewCount === 1 ? "" : "s")
                          + " in time"
                    detail: volumeCard.open
                        ? ""
                        : "Oldest is " + String(volumeCard.snapshots[volumeCard.snapshots.length - 1]?.date ?? "")
                    controlWidth: 110
                    onActivated: {
                        root.confirmingDelete = "";
                        root.openConfig = volumeCard.open ? "" : volumeCard.configName;
                    }

                    Text {
                        anchors.right: parent.right
                        anchors.verticalCenter: parent.verticalCenter
                        text: volumeCard.open ? "Show fewer  \u25B4" : "Show all  \u25BE"
                        color: Theme.fgDim
                        font.family: Theme.fontFamily
                        font.pixelSize: Theme.fontSizeSmall
                    }
                }
            }

            // ── Inside one point in time ─────────────────────────────────────

            Column {
                width: parent.width
                visible: volumeCard.open && root.browsingOpen
                    && Snapshots.browsingConfig === volumeCard.configName

                ActionRow {
                    width: parent.width
                    label: Snapshots.browsingPath === ""
                        ? "Snapshot #" + Snapshots.browsingSnapshot
                        : "…/" + Snapshots.browsingPath
                    detail: "Choosing Restore puts a copy back where it came from, keeping whatever is there now"
                    action: "Back"
                    enabled: !Snapshots.browsing
                    onTriggered: Snapshots.browseUp()
                }

                TextRow {
                    width: parent.width
                    visible: Snapshots.browsing
                    label: "Reading the snapshot…"
                    detail: "Listing a folder from a point in time"
                    value: ""
                }

                Repeater {
                    model: Snapshots.browsing ? [] : Snapshots.browseEntries

                    delegate: SettingRow {
                        id: entryRow

                        required property var modelData
                        required property int index

                        readonly property string entryPath: Snapshots.browsingPath === ""
                            ? String(entryRow.modelData.name)
                            : Snapshots.browsingPath + "/" + String(entryRow.modelData.name)
                        readonly property bool confirming: root.confirmingRestore === entryRow.entryPath

                        width: parent.width
                        icon: entryRow.modelData.directory ? "\u{F024B}" : "\u{F0214}"
                        label: String(entryRow.modelData.name ?? "")
                        detail: entryRow.modelData.directory
                            ? "Folder"
                            : Snapshots.formatBytes(entryRow.modelData.bytes ?? 0)
                        controlWidth: 230
                        divider: entryRow.index < Snapshots.browseEntries.length - 1

                        Row {
                            anchors.right: parent.right
                            anchors.verticalCenter: parent.verticalCenter
                            spacing: 8

                            SettingsButton {
                                visible: entryRow.modelData.directory === true
                                text: "Open"
                                enabled: !Snapshots.browsing
                                onClicked: Snapshots.browse(Snapshots.browsingConfig,
                                                            Snapshots.browsingSnapshot,
                                                            entryRow.entryPath)
                            }

                            SettingsButton {
                                text: entryRow.confirming ? "Cancel" : "Restore"
                                enabled: !Snapshots.busy
                                onClicked: root.confirmingRestore =
                                    entryRow.confirming ? "" : entryRow.entryPath
                            }

                            SettingsButton {
                                visible: entryRow.confirming
                                text: "Put it back"
                                tone: "danger"
                                enabled: !Snapshots.busy
                                onClicked: {
                                    root.confirmingRestore = "";
                                    Snapshots.restore(Snapshots.browsingConfig,
                                                      Snapshots.browsingSnapshot,
                                                      entryRow.entryPath);
                                }
                            }
                        }
                    }
                }

                TextRow {
                    width: parent.width
                    visible: !Snapshots.browsing && Snapshots.browseTruncated
                    label: "Only the first entries are shown"
                    detail: "This folder holds more than this list can usefully show"
                    value: ""
                    divider: false
                }

                TextRow {
                    width: parent.width
                    visible: !Snapshots.browsing && Snapshots.browseEntries.length === 0
                    label: "Nothing here"
                    detail: "This folder was empty at that point in time"
                    value: ""
                    divider: false
                }
            }
        }
    }

    // ── What it costs ────────────────────────────────────────────────────────

    SettingsCard {
        title: "Space"
        subtitle: "A snapshot shares its data with the live filesystem and grows only as files change afterwards."

        TextRow {
            label: "Free space"
            detail: "Snapshots are removed oldest-first when this runs low"
            value: Snapshots.formatBytes(Snapshots.space?.freeBytes ?? 0)
        }

        TextRow {
            label: "Automatic snapshots"
            detail: Snapshots.timelineRunning
                ? "The hourly timer is running"
                : "The hourly timer is not running, so nothing new is being taken"
            value: Snapshots.timelineRunning ? "Running" : "Stopped"
        }

        // Honest about what cannot be measured: per-snapshot size needs btrfs
        // quota groups, which cost performance on every write. Reporting a
        // made-up number would be worse than saying so.
        TextRow {
            label: "Space used by snapshots"
            detail: "Measuring this per snapshot needs btrfs quotas, which slow down every write. Free space above is the number that matters."
            value: "Not measured"
            divider: false
        }
    }
}
