// A free-text setting, bound to a schema key by name.
//
//   TextEntryRow { setting: "keyboardOptions"; placeholder: "compose:ralt" }
//
// For the settings whose value is a short string with too many legal values to
// enumerate -- XKB layouts and options, chiefly. Label, explanation, and
// validation all come from PreferenceSchema, so a row cannot drift from the
// setting it edits.
//
// Rejected input is shown as rejected rather than silently dropped or quietly
// sanitised. Several of these strings are serialized into an hl.config payload,
// where stripping an unexpected character would turn a typo into a different
// working setting -- so the schema's pattern decides, the field turns red when
// it fails, and nothing is committed until it passes.
//
// Committed on Enter or when focus leaves, not per keystroke: half a layout
// name is a valid string that means something else, and each commit is a round
// trip to the compositor.

import QtQuick
import qs.config
import qs.services

SettingRow {
    id: root

    required property string setting
    property string placeholder: ""

    readonly property var spec: PreferenceSchema.spec(root.setting)
    readonly property string stored: String(DesktopPreferences.get(root.setting) ?? "")

    // Empty is always allowed to be typed through, even where the pattern
    // forbids it, so a field can be cleared on the way to a new value.
    readonly property bool valid: input.text === ""
        || !root.spec?.pattern
        || new RegExp(root.spec.pattern).test(input.text)

    label: root.spec ? root.spec.label : root.setting
    detail: root.spec ? root.spec.detail : ""
    controlWidth: 210

    function commit(): void {
        if (!root.valid || input.text === root.stored)
            return;
        SystemSettings.commitPreference(root.setting, input.text);
    }

    function revert(): void {
        input.text = root.stored;
    }

    // Follows the store when the value changes elsewhere -- a reset, a restored
    // backup -- but never while the field has focus, which would overwrite what
    // is being typed.
    onStoredChanged: if (!input.activeFocus) input.text = root.stored

    Rectangle {
        anchors.right: parent.right
        anchors.verticalCenter: parent.verticalCenter
        width: root.controlWidth
        height: 30
        radius: 8
        color: Theme.alpha(Theme.fg, 0.05)
        border.width: 1
        border.color: !root.valid
            ? Theme.danger
            : (input.activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.12))

        TextInput {
            id: input

            anchors.fill: parent
            anchors.leftMargin: 10
            anchors.rightMargin: 10
            verticalAlignment: TextInput.AlignVCenter
            clip: true

            text: root.stored
            color: root.valid ? Theme.fg : Theme.danger
            font.family: Theme.fontMono
            font.pixelSize: Theme.fontSizeSmall
            selectByMouse: true
            selectionColor: Theme.alpha(Theme.accent, 0.35)

            onAccepted: root.commit()

            // Commit, then show what is actually stored. If the compositor
            // refused the value, the field snaps back to the value in effect
            // rather than displaying a setting that was never applied; if it
            // accepted, onStoredChanged brings the field back up to date the
            // moment the write lands.
            onActiveFocusChanged: if (!activeFocus) { root.commit(); root.revert(); }

            Keys.onEscapePressed: {
                root.revert();
                input.focus = false;
            }

            Text {
                anchors.verticalCenter: parent.verticalCenter
                visible: input.text === "" && !input.activeFocus
                text: root.placeholder
                color: Theme.fgMuted
                font: input.font
            }
        }
    }
}
