// Privacy & Security.
//
// GNOME's Privacy panel covers screen lock, camera and microphone access, file
// history, trash, and device security. Panama covers the parts it genuinely
// owns and is explicit about the parts it does not.
//
// The file-history and trash settings are the notable omission, and the reason
// is worth stating: those are GNOME preferences enforced by gsd-housekeeping,
// which is not running in a Hyprland session. Offering switches for them would
// store a preference, change nothing, and give no sign of it -- the exact
// failure this codebase keeps designing against. So they are delegated by name
// rather than reimplemented as controls that lie.

import QtQuick
import qs.config
import qs.services

SettingsPage {
    id: root

    title: "Privacy & Security"

    // The stored-secret list is collapsed until asked for, and one item at a
    // time can be waiting on a confirmed Forget.
    property bool showingSecrets: false
    property string confirmingPath: ""

    // What a stored secret is FOR, from its attributes. Never its value.
    function describe(item: var): string {
        const attributes = item?.attributes ?? {};
        const parts = [];
        for (const key of ["user", "username", "account", "server", "host", "domain", "service", "application"]) {
            if (attributes[key])
                parts.push(String(attributes[key]));
        }
        if (parts.length > 0)
            return parts.join(" · ");
        const schema = String(item?.schema ?? "");
        return schema !== "" ? schema : "No further detail stored";
    }
    lede: DeviceSecurity.scanned && DeviceSecurity.attentionCount === 0
        ? "Screen lock, device access, and a machine whose security settings all check out."
        : "Screen lock, which applications can see you, and how this machine is protected."

    Component.onCompleted: {
        if (!DeviceSecurity.scanned)
            DeviceSecurity.refresh();
        if (!Keyring.scanned)
            Keyring.refresh();
        if (!Permissions.scanned)
            Permissions.refresh();
    }

    SettingsCard {
        title: "Screen lock"
        subtitle: "The same settings as Power & Lock, which is where the idle timings live."

        SliderRow { setting: "lockMinutes"; zeroLabel: "Never" }
        ToggleRow { setting: "lockOnSleep"; divider: false }
    }

    // The login keyring, which nothing else surfaces.
    //
    // It is unlocked at sign-in by PAM, so this card normally just confirms
    // that. It earns its place on the rare occasion it is not: a locked keyring
    // breaks saved passwords everywhere at once, and does it without ever
    // saying the word "keyring" -- you get a mail account that will not
    // authenticate and a git push that cannot find its key.
    SettingsCard {
        visible: Keyring.scanned
        title: "Saved passwords"
        subtitle: !Keyring.available
            ? "No secret service is answering, so saved passwords are unavailable."
            : Keyring.locked
                ? "The login keyring is locked. Saved passwords cannot be read until it is unlocked, and applications that need one will appear to fail for unrelated reasons."
                : "The login keyring is unlocked, as it is after every normal sign-in."

        // Two rows rather than one with a conditional button: a locked keyring
        // needs an action, an unlocked one is a statement of fact, and ActionRow
        // and TextRow already say exactly those two things.
        ActionRow {
            visible: Keyring.available && Keyring.locked
            label: "Login keyring"
            detail: "Unlock to restore access to stored passwords and keys"
            action: Keyring.unlocking ? "Waiting…" : "Unlock"
            enabled: !Keyring.unlocking
            divider: Keyring.replacementDaemon || Keyring.lastError !== ""
            onTriggered: Keyring.unlock()
        }

        TextRow {
            visible: !(Keyring.available && Keyring.locked)
            label: "Login keyring"
            detail: Keyring.available
                ? "Unlocked at sign-in by PAM, the same way GNOME does it"
                : "No secret service is answering on this session"
            value: Keyring.available ? "Unlocked" : "Unavailable"
            divider: Keyring.replacementDaemon || Keyring.lastError !== ""
        }

        // Only shown when it is true, because it is a diagnostic rather than a
        // setting: it means the daemon holding your secrets is not the one PAM
        // started, so whatever unlocked it will not survive a restart.
        SettingRow {
            visible: Keyring.replacementDaemon
            label: "Keyring service"
            detail: "The original keyring service was replaced during this session, usually after it crashed. Signing out and back in restores the one PAM unlocks."
            value: "Replaced"
            divider: Keyring.lastError !== ""
        }

        SettingRow {
            visible: Keyring.lastError !== ""
            label: "Keyring problem"
            detail: Keyring.lastError
            divider: false
        }
    }


    // ── What is actually stored ──────────────────────────────────────────────
    // Collapsed until asked. Opening the Privacy page should not enumerate
    // someone's saved passwords as a side effect, and the list is long enough
    // that it would bury every other setting on the page.
    SettingsCard {
        visible: Keyring.scanned && Keyring.available && !Keyring.locked
        title: "Stored secrets"
        subtitle: Keyring.listed
            ? "Passwords and tokens applications have saved. The values are never shown here."
            : "Passwords and tokens applications have saved, listed only when you ask."

        ActionRow {
            label: "Saved items"
            detail: Keyring.listed
                ? Keyring.storedCount + " stored across "
                  + Keyring.collections.length + " keyring"
                  + (Keyring.collections.length === 1 ? "" : "s")
                : "Read the keyring and list what is in it"
            action: root.showingSecrets
                ? "Hide"
                : (Keyring.listing ? "Reading…" : "Show")
            enabled: !Keyring.listing
            divider: root.showingSecrets
            onTriggered: {
                if (root.showingSecrets) {
                    root.showingSecrets = false;
                    root.confirmingPath = "";
                    return;
                }
                root.showingSecrets = true;
                if (!Keyring.listed)
                    Keyring.list();
            }
        }

        TextRow {
            visible: root.showingSecrets && Keyring.copiedPath !== ""
            label: "Copied to the clipboard"
            detail: "It clears itself in about a minute, unless you copy something else first."
            value: ""
            divider: true
        }

        Repeater {
            model: root.showingSecrets && Keyring.listed ? Keyring.collections : []

            delegate: Column {
                id: collectionBlock

                required property var modelData

                width: parent.width

                TextRow {
                    width: collectionBlock.width
                    label: String(collectionBlock.modelData.label ?? "")
                    detail: collectionBlock.modelData.locked
                        ? "Locked, so its contents cannot be listed"
                        : (collectionBlock.modelData.items ?? []).length + " stored"
                    value: ""
                    divider: false
                }

                Repeater {
                    model: collectionBlock.modelData.items ?? []

                    delegate: SettingRow {
                        id: secretRow

                        required property var modelData
                        required property int index

                        readonly property string itemPath: String(secretRow.modelData.path ?? "")
                        readonly property bool confirming: root.confirmingPath === secretRow.itemPath

                        width: collectionBlock.width
                        label: String(secretRow.modelData.label ?? "")
                        // Attributes, never the value: what the secret is FOR is
                        // the part that identifies it.
                        detail: root.describe(secretRow.modelData)
                        controlWidth: 200
                        divider: secretRow.index < (collectionBlock.modelData.items ?? []).length - 1

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

                            SettingsButton {
                                text: secretRow.confirming ? "Cancel" : "Copy"
                                enabled: !Keyring.working
                                onClicked: {
                                    if (secretRow.confirming) {
                                        root.confirmingPath = "";
                                        return;
                                    }
                                    Keyring.copy(secretRow.itemPath);
                                }
                            }

                            SettingsButton {
                                // Two presses, always. Forgetting a stored
                                // password cannot be undone, and the button
                                // sits next to Copy where a misclick is cheap.
                                text: secretRow.confirming ? "Forget it" : "Forget"
                                tone: secretRow.confirming ? "danger" : "normal"
                                enabled: !Keyring.working
                                onClicked: {
                                    if (!secretRow.confirming) {
                                        root.confirmingPath = secretRow.itemPath;
                                        return;
                                    }
                                    root.confirmingPath = "";
                                    Keyring.forget(secretRow.itemPath);
                                }
                            }
                        }
                    }
                }

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

        TextRow {
            visible: root.showingSecrets && Keyring.listed && Keyring.storedCount === 0
            label: "Nothing stored yet"
            detail: "Applications that save a password or token will appear here."
            value: ""
            divider: false
        }
    }

    SettingsCard {
        title: "Camera & microphone"
        subtitle: PrivacyState.anyActive
            ? "In use right now — the bar shows an indicator whenever this is true."
            : "Nothing is using your camera or microphone."

        TextRow {
            label: "Camera"
            detail: PrivacyState.cameraActive
                ? "In use by " + (PrivacyState.cameraApp || "an application")
                : "Not in use"
            value: PrivacyState.cameraActive ? "Active" : "Idle"
        }

        TextRow {
            label: "Microphone"
            detail: PrivacyState.microphoneActive
                ? "In use by " + (PrivacyState.microphoneApp || "an application")
                : "Not in use"
            value: PrivacyState.microphoneActive ? "Active" : "Idle"
        }

        TextRow {
            label: "Screen sharing"
            detail: PrivacyState.screenSharingActive
                ? "Being shared by " + (PrivacyState.screenSharingApp || "an application")
                : "Not being shared"
            value: PrivacyState.screenSharingActive ? "Active" : "Idle"
            divider: false
        }
    }

    SettingsCard {
        title: "Application permissions"
        // The limit is stated here rather than left to be discovered. Saying
        // "your camera is protected" when a native binary can open it
        // directly would be a claim this page cannot back up.
        subtitle: Permissions.available
            ? "Applications that asked through the desktop portal. Programs installed outside it can still reach these devices directly."
            : (Permissions.lastError || "The desktop portal's permission store is not running.")

        Repeater {
            model: Permissions.devices

            delegate: Column {
                id: deviceBlock

                required property var modelData
                readonly property var applications: deviceBlock.modelData.applications ?? []

                width: parent.width

                TextRow {
                    width: parent.width
                    visible: deviceBlock.applications.length === 0
                    label: String(deviceBlock.modelData.label ?? "")
                    detail: "No application has asked for this."
                    value: ""
                }

                Repeater {
                    model: deviceBlock.applications

                    delegate: SettingRow {
                        required property var modelData
                        width: parent.width
                        label: String(modelData.app ?? "")
                        detail: String(deviceBlock.modelData.label ?? "")
                        controlWidth: 150

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

                            SettingsButton {
                                anchors.verticalCenter: parent.verticalCenter
                                text: "Ask again"
                                enabled: !Permissions.busy
                                onClicked: Permissions.forget(
                                    String(deviceBlock.modelData.id), String(modelData.app))
                            }

                            SettingsToggle {
                                anchors.verticalCenter: parent.verticalCenter
                                checked: modelData.allowed === true
                                onToggled: value => Permissions.setAllowed(
                                    String(deviceBlock.modelData.id), String(modelData.app), value)
                            }
                        }
                    }
                }
            }
        }
    }

    SettingsCard {
        title: "Device security"
        subtitle: DeviceSecurity.attentionCount === 0
            ? "Everything below is in its recommended state."
            : DeviceSecurity.attentionCount + " item"
              + (DeviceSecurity.attentionCount === 1 ? "" : "s") + " below may deserve attention."

        Repeater {
            model: DeviceSecurity.facts

            TextRow {
                id: factRow
                required property var modelData
                required property int index

                label: factRow.modelData.label
                detail: factRow.modelData.detail
                value: factRow.modelData.value
                divider: factRow.index < DeviceSecurity.facts.length - 1
            }
        }
    }

    SettingsCard {
        title: "Owned by Fedora"
        subtitle: "File history and trash retention are GNOME preferences, applied by a housekeeping service that does not run in a Hyprland session. They are not offered as switches here, because storing that preference would change nothing."

        ActionRow {
            label: "File history & trash"
            detail: "Opens GNOME Settings, which owns these"
            action: "Open privacy"
            divider: false
            onTriggered: SystemSettings.openGnomePanel("privacy")
        }
    }
}
