// Your account, and anyone else who signs in to this machine.
//
// The layout puts your own account first because that is what someone opens
// this page for, and the avatar leads because it is the thing that shows up
// elsewhere in the desktop -- the Control Center draws it, and the lock screen
// and login screen read the same file.
//
// Everything privileged here prompts through polkit. A prompt that is dismissed
// is a normal outcome and says so plainly rather than reporting a failure.

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

SettingsPage {
    id: root

    objectName: "users"
    title: "Users"
    lede: "Your account, and anyone else who signs in to this machine."

    // One panel open at a time: changing a password and adding an account are
    // both multi-field, and two open at once reads as a form with no shape.
    property string openPanel: ""
    property string newPassword: ""
    property string confirmPassword: ""
    property string newUserName: ""
    property string newRealName: ""
    property bool newUserIsAdministrator: false
    property string confirmingRemoval: ""

    readonly property var me: UserAccounts.me

    readonly property string passwordProblem: {
        if (root.newPassword === "")
            return "";
        if (root.newPassword.length < 6)
            return "Use at least six characters.";
        if (root.confirmPassword !== "" && root.newPassword !== root.confirmPassword)
            return "The two entries do not match.";
        return "";
    }

    readonly property bool passwordReady: root.newPassword.length >= 6
        && root.newPassword === root.confirmPassword

    function closePanels(): void {
        root.openPanel = "";
        root.newPassword = "";
        root.confirmPassword = "";
        root.newUserName = "";
        root.newRealName = "";
        root.newUserIsAdministrator = false;
    }

    Component.onCompleted: {
        UserAccounts.refresh();
        // Probing fprintd bus-activates it, so it waits for the page rather
        // than costing every shell launch.
        Fingerprint.refresh();
    }

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

    // ── You ──────────────────────────────────────────────────────────────────

    // Chosen but not yet framed. While this is set the cropper replaces the
    // account card, because framing is a decision to finish, not a setting to
    // leave half-made.
    property string pendingPicture: ""

    SettingsCard {
        visible: root.pendingPicture !== ""
        title: "Frame the picture"

        AvatarCropper {
            width: parent.width
            source: root.pendingPicture

            onCropped: (x, y, size) => {
                UserAccounts.setIconCropped(String(root.me?.userName ?? ""),
                                            root.pendingPicture, x, y, size);
                root.pendingPicture = "";
            }
            onCancelled: root.pendingPicture = ""
        }
    }

    SettingsCard {
        visible: root.me !== null && root.pendingPicture === ""

        Row {
            width: parent.width
            spacing: 20

            Item {
                width: 96
                height: 96

                ClippingRectangle {
                    anchors.fill: parent
                    radius: width / 2
                    color: Theme.alpha(Theme.fg, 0.08)

                    Image {
                        anchors.fill: parent
                        source: UserAccounts.avatarUrl
                        visible: UserAccounts.avatarUrl !== ""
                        fillMode: Image.PreserveAspectCrop
                        // accountsservice replaces the file in place, so the
                        // path never changes. cache:false is not enough on its
                        // own -- an unchanged source is never re-read at all --
                        // which is why avatarUrl carries a revision fragment.
                        cache: false
                        asynchronous: true
                        sourceSize.width: 192
                        sourceSize.height: 192
                    }

                    Text {
                        anchors.centerIn: parent
                        visible: UserAccounts.avatarUrl === ""
                        text: UserAccounts.displayName(root.me).slice(0, 1).toUpperCase()
                        color: Theme.fgDim
                        font.family: Theme.fontFamily
                        font.pixelSize: 38
                        font.weight: Font.DemiBold
                    }
                }
            }

            Column {
                width: parent.width - 116
                spacing: 4
                anchors.verticalCenter: parent.verticalCenter

                Text {
                    text: UserAccounts.displayName(root.me)
                    color: Theme.fg
                    font.family: Theme.fontFamily
                    font.pixelSize: Theme.fontSizeTitle
                    font.weight: Font.DemiBold
                }

                Text {
                    text: String(root.me?.userName ?? "") + " · "
                        + (root.me?.administrator ? "Administrator" : "Standard account")
                    color: Theme.fgDim
                    font.family: Theme.fontFamily
                    font.pixelSize: Theme.fontSize
                }

                Item { width: 1; height: 6 }

                SettingsButton {
                    text: "Change picture…"
                    enabled: !UserAccounts.busy
                    onClicked: avatarPicker.open()
                }
            }
        }
    }

    SettingsCard {
        title: "Account"
        visible: root.me !== null

        TextFieldRow {
            label: "Full name"
            detail: "Shown on the lock screen and in the Control Center"
            text: String(root.me?.realName ?? "")
            placeholder: "Your name"
            enabled: !UserAccounts.busy
            onAccepted: value => UserAccounts.setRealName(String(root.me?.userName ?? ""), value)
        }

        TextRow {
            label: "Username"
            detail: "Fixed when the account was created, because files and permissions are keyed to it"
            value: String(root.me?.userName ?? "")
        }

        SegmentRow {
            label: "Account type"
            detail: root.me?.administrator && UserAccounts.administratorCount <= 1
                ? "This is the only administrator, so it cannot be changed"
                : "Administrators can install software and manage other accounts"
            options: [
                { value: "standard", label: "Standard" },
                { value: "administrator", label: "Administrator" }
            ]
            value: root.me?.administrator ? "administrator" : "standard"
            enabled: !UserAccounts.busy
                && !(root.me?.administrator && UserAccounts.administratorCount <= 1)
            onSelected: value => UserAccounts.setAccountType(String(root.me?.userName ?? ""), value)
        }

        ActionRow {
            label: "Password"
            detail: root.openPanel === "password"
                ? "Changing it asks for authorization first"
                : "Change the password used to sign in and to unlock the screen"
            action: root.openPanel === "password" ? "Cancel" : "Change…"
            enabled: !UserAccounts.busy
            divider: root.openPanel === "password"
            onTriggered: {
                if (root.openPanel === "password")
                    root.closePanels();
                else {
                    root.closePanels();
                    root.openPanel = "password";
                }
            }
        }

        Column {
            width: parent.width
            visible: root.openPanel === "password"

            PasswordRow {
                width: parent.width
                label: "New password"
                detail: "At least six characters"
                onChanged: value => root.newPassword = value
            }

            PasswordRow {
                width: parent.width
                label: "Confirm"
                detail: root.passwordProblem !== ""
                    ? root.passwordProblem
                    : "Type it a second time"
                onChanged: value => root.confirmPassword = value
            }

            ActionRow {
                width: parent.width
                label: "Set this password"
                detail: "You will be asked to authorize the change"
                action: "Set password"
                enabled: root.passwordReady && !UserAccounts.busy
                divider: false
                onTriggered: {
                    UserAccounts.setPassword(String(root.me?.userName ?? ""), root.newPassword);
                    root.closePanels();
                }
            }
        }

        SwitchRow {
            label: "Automatic login"
            detail: "Sign in without typing a password. The login keyring stays locked when this is on, so stored passwords are unavailable until something asks for them."
            checked: root.me?.automaticLogin === true
            enabled: !UserAccounts.busy
            divider: false
            onToggled: value => UserAccounts.setAutomaticLogin(String(root.me?.userName ?? ""), value)
        }
    }

    // ── Fingerprint ──────────────────────────────────────────────────────────
    //
    // Hidden in full on a machine with no reader. Two systems make this work
    // and the card keeps them honest with each other: fprintd holds the
    // enrolled prints (GNOME's Users panel owns that dialog, so enrollment
    // hands off the same way password-adjacent panels do), and authselect
    // decides whether PAM asks the reader at all -- a print enrolled while
    // that is off does nothing, which reads as "fingerprint is broken".

    SettingsCard {
        visible: Fingerprint.readerPresent
        title: "Fingerprint"
        subtitle: Fingerprint.readerName !== ""
            ? Fingerprint.readerName
            : "A fingerprint reader is present."

        SwitchRow {
            label: "Unlock with a fingerprint"
            detail: {
                if (Fingerprint.enrolled.length === 0)
                    return "Enroll a finger below first; until then the password is the only way in";
                return Fingerprint.pamEnabled
                    ? "The lock screen and sudo accept an enrolled finger, with the password as fallback"
                    : "Enrolled fingers are ignored until this is on";
            }
            checked: Fingerprint.pamEnabled
            enabled: !Fingerprint.busy
            onToggled: value => Fingerprint.setUnlockEnabled(value)
        }

        ActionRow {
            label: "Enrolled fingers"
            detail: Fingerprint.enrolled.length === 0
                ? "None yet"
                : Fingerprint.enrolled.map(finger => Fingerprint.fingerLabel(finger)).join(", ")
            action: "Manage…"
            divider: Fingerprint.lastError !== ""
            // GNOME's Users panel owns the enrollment dialog; growing our own
            // means reimplementing a guided capture flow fprintd already has
            // a good one of.
            onTriggered: SystemSettings.openGnomePanel("system", "users")
        }

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

    // ── Everyone else ────────────────────────────────────────────────────────

    SettingsCard {
        title: "Other accounts"
        subtitle: UserAccounts.others.length === 0
            ? "Only your account exists on this machine."
            : UserAccounts.others.length + " other account"
              + (UserAccounts.others.length === 1 ? "" : "s")

        Repeater {
            model: UserAccounts.others

            delegate: Column {
                id: otherBlock

                required property var modelData

                width: parent.width

                readonly property string userName: String(otherBlock.modelData.userName ?? "")
                readonly property bool confirming: root.confirmingRemoval === otherBlock.userName

                SettingRow {
                    width: otherBlock.width
                    label: UserAccounts.displayName(otherBlock.modelData)
                    detail: otherBlock.userName + " · "
                        + (otherBlock.modelData.administrator ? "Administrator" : "Standard account")
                    controlWidth: 210
                    divider: false

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

                        SettingsButton {
                            text: otherBlock.confirming ? "Keep" : "Remove…"
                            enabled: !UserAccounts.busy
                            onClicked: root.confirmingRemoval = otherBlock.confirming
                                ? "" : otherBlock.userName
                        }

                        SettingsButton {
                            visible: otherBlock.confirming
                            text: "Delete account and files"
                            tone: "danger"
                            enabled: !UserAccounts.busy
                            onClicked: {
                                root.confirmingRemoval = "";
                                UserAccounts.deleteUser(otherBlock.userName, true);
                            }
                        }
                    }
                }

                TextRow {
                    width: otherBlock.width
                    visible: otherBlock.confirming
                    label: "This cannot be undone"
                    detail: "Their home directory and everything in it is deleted."
                    value: ""
                    divider: false
                }

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

        ActionRow {
            label: "Add an account"
            detail: "Creating an account asks for authorization first"
            action: root.openPanel === "newUser" ? "Cancel" : "Add…"
            enabled: !UserAccounts.busy
            divider: root.openPanel === "newUser"
            onTriggered: {
                if (root.openPanel === "newUser")
                    root.closePanels();
                else {
                    root.closePanels();
                    root.openPanel = "newUser";
                }
            }
        }

        Column {
            width: parent.width
            visible: root.openPanel === "newUser"

            TextFieldRow {
                width: parent.width
                label: "Full name"
                placeholder: "Their name"
                detail: "Shown on the login screen"
                text: root.newRealName
                onAccepted: value => root.newRealName = value
            }

            TextFieldRow {
                width: parent.width
                label: "Username"
                placeholder: "lowercase, no spaces"
                detail: "Their home directory is named after this and cannot be changed later"
                text: root.newUserName
                onAccepted: value => root.newUserName = value
            }

            SegmentRow {
                width: parent.width
                label: "Account type"
                detail: "Standard accounts cannot install software or manage other accounts"
                options: [
                    { value: "standard", label: "Standard" },
                    { value: "administrator", label: "Administrator" }
                ]
                value: root.newUserIsAdministrator ? "administrator" : "standard"
                onSelected: value => root.newUserIsAdministrator = value === "administrator"
            }

            ActionRow {
                width: parent.width
                label: "Create the account"
                detail: "They set their own password the first time they sign in"
                action: "Create"
                enabled: !UserAccounts.busy && /^[a-z_][a-z0-9_-]*$/.test(root.newUserName)
                divider: false
                onTriggered: {
                    UserAccounts.createUser(root.newUserName, root.newRealName,
                                        root.newUserIsAdministrator ? "administrator" : "standard");
                    root.closePanels();
                }
            }
        }
    }

    // Picking a picture goes through the desktop portal, which is the same
    // chooser every other application gets and needs no privilege of its own.
    AvatarPicker {
        id: avatarPicker
        onPicked: path => root.pendingPicture = path
    }
}
