import QtQuick
import QtQuick.Layouts 2.15
import QtQuick.Controls 2.15
import Minecraft_launcher

Window {
    id: window
    width: 1280
    height: 720
    visible: true
    flags: Qt.Window
    title: qsTr("Minecraft Launcher")

    // ── Backend ────────────────────────────────────────────────────────────
    LauncherBackend {
        id: backend

        onLaunched: (profileName, buildName, serverUrl) => {
            window.showToast(qsTr("Запущено: %1 — %2").arg(profileName).arg(buildName), "#4b7a1f")
        }
        onLaunchProgress: (message) => window.showToast(message, "#3a5a8c", 0)
        onLaunchError: (message) => window.showToast(message, "#cc3333", 8000)
        onTwoFactorRequired: (profileName) => {
            twoFactorDialog.profileName = profileName
            twoFactorCode.text = ""
            twoFactorDialog.open()
        }
        onGameFinished: (exitCode, crashed) => {
            window.showToast(crashed || exitCode !== 0
                             ? qsTr("Игра завершилась с ошибкой (код %1)").arg(exitCode)
                             : qsTr("Игра закрыта"),
                             crashed || exitCode !== 0 ? "#cc3333" : "#555555")
        }
        onMicrosoftLoginUrlReady: (url) => window.openMicrosoftLogin(url)
        // Выбор в списке здесь не трогаем: новый профиль уже выбран тем, кто
        // его создал, а повторный вход мог быть и не в последний профиль.
        onMicrosoftLoginSucceeded: (playerName) =>
            window.showToast(qsTr("Вход выполнен: %1").arg(playerName), "#4b7a1f")
        onMicrosoftLoginFailed: (message) => window.showToast(message, "#cc3333", 8000)
        onMicrosoftReloginRequired: (profileIndex) => backend.startMicrosoftLogin(profileIndex)
        onGameOutput: (line) => console.log(line)
        onSeasonalInstallFinished: (seasonalId, buildName) => {
            window.showToast(qsTr("Сборка «%1» установлена — можно запускать").arg(buildName),
                             "#4b7a1f", 8000)
        }
    }

    // Единая всплывающая плашка: сообщения о ходе запуска, ошибки, статус игры.
    // timeout === 0 — держим до следующего сообщения.
    function showToast(text, color, timeout) {
        toastLabel.text = text
        toastBackground.color = color
        toastTimer.stop()
        if (timeout === undefined)
            timeout = 4000
        if (timeout > 0) {
            toastTimer.interval = timeout
            toastTimer.restart()
        }
    }

    // Окно входа Microsoft создаётся по требованию, а не вместе с главным
    // окном: MicrosoftLoginDialog.qml попадает в модуль только в сборках с Qt
    // WebEngine, и обычная декларация сломала бы всё окно в остальных.
    property var microsoftLoginDialog: null

    function openMicrosoftLogin(url) {
        if (microsoftLoginDialog === null) {
            const component = Qt.createComponent("MicrosoftLoginDialog.qml")
            if (component.status !== Component.Ready) {
                backend.cancelMicrosoftLogin()
                window.showToast(qsTr("Окно входа Microsoft недоступно: сборка без Qt WebEngine"),
                                 "#cc3333", 8000)
                return
            }
            microsoftLoginDialog = component.createObject(window, { backend: backend })
            microsoftLoginDialog.failed.connect(
                (message) => window.showToast(message, "#cc3333", 6000))
        }
        microsoftLoginDialog.openAt(url)
    }

    function formatMb(bytes) {
        return (bytes / 1048576).toFixed(1)
    }

    // ── Background ─────────────────────────────────────────────────────────
    Image {
        id: image
        opacity: 0.296
        anchors.fill: parent
        source: "images/GovuztTW8AAHqBf.jpeg"
        fillMode: Image.Stretch
    }

    // ── Status toast ───────────────────────────────────────────────────────
    Rectangle {
        id: toastBackground
        visible: toastLabel.text !== ""
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.bottom: parent.bottom
        anchors.bottomMargin: (downloadPanel.visible || switchPanel.visible) ? 110 : 40
        width: Math.min(window.width - 80, toastLabel.implicitWidth + 32)
        height: toastLabel.implicitHeight + 20
        radius: 8
        color: "#cc3333"

        Text {
            id: toastLabel
            anchors.centerIn: parent
            width: parent.width - 32
            color: "#ffffff"
            font.pixelSize: 14
            horizontalAlignment: Text.AlignHCenter
            wrapMode: Text.Wrap
        }

        Timer {
            id: toastTimer
            interval: 4000
            onTriggered: toastLabel.text = ""
        }
    }

    // ── Панели хода работ ──────────────────────────────────────────────────
    // Загрузка и смена сборки взаимоисключающи по построению: busy() не даёт
    // начать переключение во время установки и наоборот. Поэтому у панелей
    // одни и те же якоря.
    ProgressPanel {
        id: downloadPanel
        visible: backend.downloading
        anchors.left: parent.left
        anchors.bottom: parent.bottom
        anchors.leftMargin: 24
        anchors.bottomMargin: 24

        title: qsTr("Загрузка %1").arg(backend.downloadVersion)
        fraction: backend.downloadProgress
        status: backend.downloadStatus
        detail: backend.downloadBytesTotal > 0
                ? qsTr("%1 — %2 / %3 МБ").arg(backend.downloadStatus)
                      .arg(window.formatMb(backend.downloadBytesDone))
                      .arg(window.formatMb(backend.downloadBytesTotal))
                : ""
        onCancelRequested: backend.cancelDownload()
    }

    ProgressPanel {
        id: switchPanel
        visible: backend.switching
        anchors.left: parent.left
        anchors.bottom: parent.bottom
        anchors.leftMargin: 24
        anchors.bottomMargin: 24

        title: backend.switchStage
        fraction: backend.switchProgress
        status: backend.switchStatus
        // Отменять можно только пока идёт архивация: после очистки .minecraft
        // отступать некуда, операцию нужно довести до конца.
        cancellable: false
    }

    // ── Play button ────────────────────────────────────────────────────────
    Button {
        id: button
        width: 335
        height: 170
        anchors.centerIn: parent
        hoverEnabled: false
        enabled: !backend.busy && !backend.gameRunning

        background: Image {
            id: buttonImage
            source: "images/Play_Button/Play_Active.svg"
            opacity: button.enabled ? 1.0 : 0.45
        }

        onPressed:  buttonImage.source = "images/Play_Button/Play_pressed.svg"
        onReleased: buttonImage.source = "images/Play_Button/Play_Active.svg"
        onClicked:  backend.launchGame(profileBox.currentIndex, backend.activeBuildIndex)
    }

    // ── Profile ComboBox ───────────────────────────────────────────────────
    ComboBox {
        id: profileBox
        y: 451
        width: 146
        height: 42
        anchors.left: button.right
        anchors.bottom: button.top
        anchors.leftMargin: -335
        anchors.bottomMargin: -218
        rotation: 0
        editable: false
        hoverEnabled: true

        model: backend.profileNames

        // Пока список закрыт: открытый popup накрывает кнопку, и подсказка
        // висела бы поверх собственного меню.
        ToolTip.visible: profileBox.hovered && !profileBox.down
        ToolTip.text: backend.profileNames.length === 0
                      ? qsTr("Профили игрока — добавьте первый профиль")
                      : qsTr("Профиль игрока: %1").arg(profileBox.displayText)

        contentItem: Text {
            id: profileText
            text: profileBox.displayText
            font: profileBox.font
            color: "#ffffff"
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignLeft
            elide: Text.ElideRight
            anchors.fill: parent
            anchors.leftMargin: 30
            anchors.rightMargin: 5
            anchors.topMargin: 5
            anchors.bottomMargin: 5
        }

        indicator: Image {
            id: profileArrow
            width: 10; height: 10
            visible: true
            anchors.left: parent.left
            anchors.leftMargin: 10
            anchors.verticalCenter: parent.verticalCenter
            source: profileBox.down ? "images/Profile_Box/Asset_23.svg"
                                    : "images/Profile_Box/Asset_24.svg"
            rotation: 180
            sourceSize.width: 10; sourceSize.height: 10
            fillMode: Image.PreserveAspectFit
            autoTransform: true
        }

        background: Rectangle {
            implicitWidth: 120; implicitHeight: 40
            color: profileBox.down ? "#91B315" : "#232323"
            radius: 6
            border.color: profileBox.pressed ? "#ffffff" : "#232323"
            border.width: profileBox.visualFocus ? 2 : 1
        }

        popup: Popup {
            y: profileBox.height - 1
            width: profileBox.width
            padding: 0
            // Высота = кнопка "+" (32) + элементы списка, не более 200
            height: 32 + Math.min(profileItemList.contentHeight, 200)

            contentItem: Item {
                anchors.fill: parent

                // ── "+" button — всегда сверху ──────────────────────────
                Rectangle {
                    id: profileAddBtnBg
                    anchors.top: parent.top
                    anchors.left: parent.left
                    anchors.right: parent.right
                    height: 32
                    color: profileAddArea.containsPress ? "#2d2d2d" : "transparent"

                    Text {
                        anchors.centerIn: parent
                        text: "+ Добавить профиль"
                        color: "#91B315"
                        font.pixelSize: 12
                    }

                    MouseArea {
                        id: profileAddArea
                        anchors.fill: parent
                        onClicked: {
                            profileBox.popup.close()
                            addProfileDialog.open()
                        }
                    }
                }

                // ── Добавленные профили — всегда ниже кнопки ───────────
                ListView {
                    id: profileItemList
                    anchors.top: profileAddBtnBg.bottom
                    anchors.left: parent.left
                    anchors.right: parent.right
                    height: Math.min(contentHeight, 200)
                    clip: true
                    model: backend.profileNames

                    delegate: ItemDelegate {
                        id: profileItem
                        required property string modelData
                        required property int index
                        width: profileItemList.width
                        height: 40
                        contentItem: Text {
                            text: profileItem.modelData
                            color: "#ffffff"
                            font: profileBox.font
                            elide: Text.ElideRight
                            horizontalAlignment: Text.AlignLeft
                            verticalAlignment: Text.AlignVCenter
                            leftPadding: 8
                            rightPadding: 62
                        }
                        background: Rectangle {
                            color: (profileBox.currentIndex === profileItem.index || profileItem.hovered)
                                   ? "#91B315" : "#232323"
                            radius: 5
                        }
                        onClicked: {
                            profileBox.currentIndex = profileItem.index
                            profileBox.popup.close()
                        }

                        // ── Карандаш — видна при наведении на строку ────
                        Image {
                            id: profilePencil
                            width: 16; height: 16
                            anchors.right: profileTrash.left
                            anchors.rightMargin: 12
                            anchors.verticalCenter: parent.verticalCenter
                            visible: profileItem.hovered
                            opacity: profilePencilArea.containsMouse ? 1.0 : 0.7
                            source: "images/Pencil.svg"
                            sourceSize.width: 16; sourceSize.height: 16
                            fillMode: Image.PreserveAspectFit

                            MouseArea {
                                id: profilePencilArea
                                anchors.fill: parent
                                anchors.margins: -6
                                hoverEnabled: true
                                onClicked: editProfileDialog.openFor(profileItem.index)
                            }
                        }

                        // ── Корзина — видна при наведении на строку ─────
                        Image {
                            id: profileTrash
                            width: 16; height: 16
                            anchors.right: parent.right
                            anchors.rightMargin: 10
                            anchors.verticalCenter: parent.verticalCenter
                            visible: profileItem.hovered
                            opacity: profileTrashArea.containsMouse ? 1.0 : 0.7
                            source: "images/Trash.svg"
                            sourceSize.width: 16; sourceSize.height: 16
                            fillMode: Image.PreserveAspectFit

                            MouseArea {
                                id: profileTrashArea
                                anchors.fill: parent
                                anchors.margins: -6
                                hoverEnabled: true
                                onClicked: backend.removeProfile(profileItem.index)
                            }
                        }
                    }

                    ScrollIndicator.vertical: ScrollIndicator {
                        parent: profileItemList.parent
                        anchors.top: profileItemList.top
                        anchors.left: profileItemList.right
                        anchors.bottom: profileItemList.bottom
                    }
                }
            }

            background: Rectangle {
                color: "#232323"
                radius: 6
                border.color: "#232323"
            }
        }
    }

    // ── Активная сборка ────────────────────────────────────────────────────
    // Кнопка занимает место бывшего выпадающего списка версий: и сама сборка,
    // и её содержимое правятся теперь в модальном окне.
    Button {
        id: buildButton
        x: 624
        y: 451
        width: 183
        height: 42
        anchors.right: button.right
        anchors.bottom: button.top
        anchors.rightMargin: 0
        anchors.bottomMargin: -218
        hoverEnabled: true
        enabled: !backend.gameRunning && !backend.switching

        contentItem: Text {
            text: backend.activeBuildName !== "" ? backend.activeBuildName
                                                 : qsTr("Сборка не выбрана")
            color: "#ffffff"
            font: buildButton.font
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignLeft
            elide: Text.ElideRight
            leftPadding: 30
            rightPadding: 5
        }

        background: Rectangle {
            color: buildButton.pressed || buildsDialog.opened ? "#91B315" : "#232323"
            radius: 6
            border.color: buildButton.hovered ? "#91B315" : "#232323"
            border.width: 1

            Image {
                width: 10; height: 10
                anchors.left: parent.left
                anchors.leftMargin: 10
                anchors.verticalCenter: parent.verticalCenter
                source: buildsDialog.opened ? "images/Profile_Box/Asset_23.svg"
                                            : "images/Profile_Box/Asset_24.svg"
                rotation: 180
                sourceSize.width: 10; sourceSize.height: 10
                fillMode: Image.PreserveAspectFit
            }
        }

        ToolTip.visible: hovered && !buildsDialog.opened
        ToolTip.text: qsTr("Пользовательские сборки")

        onClicked: buildsDialog.open()
    }

    // ── Open mods folder button ────────────────────────────────────────────
    Button {
        id: folderButton
        width: 70
        height: 40
        anchors.left: buildButton.left
        anchors.bottom: profileBox.bottom
        anchors.leftMargin: -59
        anchors.bottomMargin: -45
        hoverEnabled: true

        background: Image {
            id: folderButtonImage
            anchors.fill: parent
            source: folderButton.pressed ? "images/Folder/Folder_Pressed.svg"
                  : folderButton.hovered ? "images/Folder/Folder_Active.svg"
                                         : "images/Folder/Folder_Idle.svg"
            fillMode: Image.PreserveAspectFit
        }

        ToolTip.visible: hovered
        ToolTip.text: qsTr("Открыть папку с модами Minecraft")

        onClicked: backend.openMinecraftFolder()
    }

    // ── Settings button ────────────────────────────────────────────────────
    Button {
        id: settingsButton
        width: 70
        height: 40
        anchors.left: folderButton.right
        anchors.verticalCenter: folderButton.verticalCenter
        anchors.leftMargin: 8
        hoverEnabled: true

        background: Image {
            anchors.fill: parent
            // Ассеты Options названы наоборот относительно Folder: зелёный
            // #637a10 лежит в Options_Pressed.svg, а Options_active.svg —
            // тёмный, как Idle. Берём их по цвету, а не по имени, чтобы
            // наведение выглядело так же, как у кнопки папки модов.
            source: settingsButton.pressed ? "images/Options/Options_active.svg"
                  : settingsButton.hovered ? "images/Options/Options_Pressed.svg"
                                           : "images/Options/Options_Idle.svg"
            fillMode: Image.PreserveAspectFit
        }

        ToolTip.visible: hovered
        ToolTip.text: qsTr("Настройки запуска: Java, память, папка игры")

        onClicked: settingsDialog.load()
    }

    // ── Seasonal builds button ─────────────────────────────────────────────
    // Правый нижний угол — единственная свободная часть окна: панели хода работ
    // висят слева, а всё остальное собрано вокруг кнопки запуска.
    Button {
        id: seasonsButton
        width: 183
        height: 42
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        anchors.rightMargin: 24
        anchors.bottomMargin: 24
        hoverEnabled: true

        contentItem: Text {
            text: qsTr("Сезонные сборки")
            color: "#ffffff"
            font.pixelSize: 14
            elide: Text.ElideRight
            horizontalAlignment: Text.AlignHCenter
            verticalAlignment: Text.AlignVCenter
        }

        background: Rectangle {
            color: seasonsButton.pressed ? "#2d2d2d" : "#232323"
            radius: 6
            border.color: "#91B315"
            border.width: seasonsButton.hovered ? 2 : 1
        }

        ToolTip.visible: hovered
        ToolTip.text: qsTr("Готовые сборки с сервера: моды, конфиги и Java одной кнопкой")

        onClicked: seasonalDialog.openCatalog()
    }

    SeasonalBuildsDialog {
        id: seasonalDialog
        backend: backend
        x: (window.width  - width)  / 2
        y: (window.height - height) / 2
    }

    // ── Add Profile Dialog ─────────────────────────────────────────────────
    // Bug fix: header/footer must be Item (not Rectangle) with explicit
    // implicitHeight so Dialog correctly computes its own total height.
    // Rectangle.implicitHeight defaults to 0 regardless of height:.
    Dialog {
        id: addProfileDialog
        modal: true
        width: 320
        x: (window.width  - width)  / 2
        y: (window.height - height) / 2
        padding: 0

        background: Rectangle {
            color: "#1e1e1e"
            radius: 10
            border.color: "#91B315"
            border.width: 1
        }

        header: Item {
            implicitHeight: 52      // tells Dialog how tall the header is
            Text {
                anchors.centerIn: parent
                text: "Новый профиль"
                color: "#ffffff"
                font.pixelSize: 17
                font.bold: true
            }
            Rectangle {
                anchors.bottom: parent.bottom
                width: parent.width
                height: 1
                color: "#333333"
            }
        }

        contentItem: Column {
            spacing: 12
            topPadding: 20
            bottomPadding: 20

            TextField {
                id: pfName
                x: 20
                width: parent.width - 40
                placeholderText: "Имя профиля"
                color: "#ffffff"
                placeholderTextColor: "#666666"
                background: Rectangle {
                    color: "#2a2a2a"
                    radius: 6
                    border.color: pfName.activeFocus ? "#91B315" : "#444444"
                    border.width: 1
                }
            }

            TextField {
                id: pfLogin
                x: 20
                width: parent.width - 40
                // Ник лицензионного профиля приходит из аккаунта: поля здесь нет.
                visible: pfAuth.currentIndex !== 2
                placeholderText: "Логин"
                color: "#ffffff"
                placeholderTextColor: "#666666"
                background: Rectangle {
                    color: "#2a2a2a"
                    radius: 6
                    border.color: pfLogin.activeFocus ? "#91B315" : "#444444"
                    border.width: 1
                }
            }

            TextField {
                id: pfPassword
                x: 20
                width: parent.width - 40
                visible: pfAuth.currentIndex === 1
                placeholderText: "Пароль Ely.by"
                echoMode: TextInput.Password
                color: "#ffffff"
                placeholderTextColor: "#666666"
                background: Rectangle {
                    color: "#2a2a2a"
                    radius: 6
                    border.color: pfPassword.activeFocus ? "#91B315" : "#444444"
                    border.width: 1
                }
            }

            DarkCombo {
                id: pfAuth
                x: 20
                width: parent.width - 40
                model: backend.microsoftAvailable
                       ? ["Офлайн (без пароля)", "Ely.by", "Microsoft (лицензия)"]
                       : ["Офлайн (без пароля)", "Ely.by"]
            }
        }

        footer: Item {
            implicitHeight: 60      // tells Dialog how tall the footer is
            Rectangle {
                anchors.top: parent.top
                width: parent.width
                height: 1
                color: "#333333"
            }
            Row {
                anchors.centerIn: parent
                spacing: 12

                Button {
                    text: "Отмена"
                    width: 110; height: 36
                    contentItem: Text {
                        text: parent.text
                        color: "#ffffff"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }
                    background: Rectangle {
                        color: parent.pressed ? "#444444" : "#333333"
                        radius: 6
                    }
                    onClicked: addProfileDialog.reject()
                }

                Button {
                    text: "Добавить"
                    width: 110; height: 36
                    contentItem: Text {
                        text: parent.text
                        color: "#ffffff"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }
                    background: Rectangle {
                        color: parent.pressed ? "#6a8510" : "#91B315"
                        radius: 6
                    }
                    onClicked: addProfileDialog.accept()
                }
            }
        }

        onAccepted: {
            const name = pfName.text.trim()
            const type = pfAuth.currentIndex === 2 ? "microsoft"
                       : pfAuth.currentIndex === 1 ? "elyby" : "offline"
            if (name !== "") {
                // Скрытые поля не сохраняем: в них мог остаться текст, набранный
                // до переключения типа профиля.
                backend.addProfile(name,
                                   type === "microsoft" ? "" : pfLogin.text.trim(),
                                   type === "elyby" ? pfPassword.text : "",
                                   type)
                profileBox.currentIndex = backend.profileNames.length - 1
                // Профиль Microsoft без входа бесполезен: сразу показываем окно.
                if (type === "microsoft")
                    backend.startMicrosoftLogin(backend.profileNames.length - 1)
            }
            pfName.text = ""; pfLogin.text = ""; pfPassword.text = ""; pfAuth.currentIndex = 0
        }
        onRejected: {
            pfName.text = ""; pfLogin.text = ""; pfPassword.text = ""; pfAuth.currentIndex = 0
        }
    }

    // ── Edit Profile Dialog ────────────────────────────────────────────────
    // Идентичен диалогу создания, но с предзаполненными данными выбранного
    // профиля. По нажатию ОК данные перезаписываются под тем же индексом.
    Dialog {
        id: editProfileDialog
        modal: true
        width: 320
        x: (window.width  - width)  / 2
        y: (window.height - height) / 2
        padding: 0

        property int editIndex: -1
        // Состояние входа в Microsoft у открытого профиля — для строки статуса.
        property bool msProfile: false
        property bool msLinked: false
        property string msName: ""

        function openFor(index) {
            const data = backend.profileAt(index)
            editIndex = index
            epName.text       = data.name     || ""
            epLogin.text      = data.login    || ""
            epPassword.text   = data.password || ""
            msProfile = data.authType === "microsoft"
            epAuth.currentIndex = msProfile ? 2 : data.authType === "elyby" ? 1 : 0
            msLinked = data.hasMicrosoftSession === true
            msName   = data.resolvedName || ""
            profileBox.popup.close()
            open()
        }

        background: Rectangle {
            color: "#1e1e1e"
            radius: 10
            border.color: "#91B315"
            border.width: 1
        }

        header: Item {
            implicitHeight: 52
            Text {
                anchors.centerIn: parent
                text: "Редактирование профиля"
                color: "#ffffff"
                font.pixelSize: 17
                font.bold: true
            }
            Rectangle {
                anchors.bottom: parent.bottom
                width: parent.width
                height: 1
                color: "#333333"
            }
        }

        contentItem: Column {
            spacing: 12
            topPadding: 20
            bottomPadding: 20

            TextField {
                id: epName
                x: 20
                width: parent.width - 40
                placeholderText: "Имя профиля"
                color: "#ffffff"
                placeholderTextColor: "#666666"
                background: Rectangle {
                    color: "#2a2a2a"
                    radius: 6
                    border.color: epName.activeFocus ? "#91B315" : "#444444"
                    border.width: 1
                }
            }

            TextField {
                id: epLogin
                x: 20
                width: parent.width - 40
                // Для лицензии логин не вводят: ниже показан ник из аккаунта.
                visible: epAuth.currentIndex !== 2
                placeholderText: "Логин"
                color: "#ffffff"
                placeholderTextColor: "#666666"
                background: Rectangle {
                    color: "#2a2a2a"
                    radius: 6
                    border.color: epLogin.activeFocus ? "#91B315" : "#444444"
                    border.width: 1
                }
            }

            // Логин лицензионного профиля — ник, полученный при официальной
            // авторизации. Отдельное поле, а не подмена epLogin.text: биндинг
            // сломался бы первым же вводом в поле логина обычного профиля.
            TextField {
                id: epMsLogin
                x: 20
                width: parent.width - 40
                visible: epAuth.currentIndex === 2 && editProfileDialog.msName !== ""
                readOnly: true
                text: editProfileDialog.msName
                color: "#ffffff"
                background: Rectangle {
                    color: "#2a2a2a"
                    radius: 6
                    border.color: "#444444"
                    border.width: 1
                }
            }

            TextField {
                id: epPassword
                x: 20
                width: parent.width - 40
                visible: epAuth.currentIndex === 1
                placeholderText: "Пароль Ely.by"
                echoMode: TextInput.Password
                color: "#ffffff"
                placeholderTextColor: "#666666"
                background: Rectangle {
                    color: "#2a2a2a"
                    radius: 6
                    border.color: epPassword.activeFocus ? "#91B315" : "#444444"
                    border.width: 1
                }
            }

            DarkCombo {
                id: epAuth
                x: 20
                width: parent.width - 40
                // Тип уже сохранённого профиля показываем всегда: иначе в
                // сборке без WebEngine он молча стал бы офлайновым.
                model: (backend.microsoftAvailable || editProfileDialog.msProfile)
                       ? ["Офлайн (без пароля)", "Ely.by", "Microsoft (лицензия)"]
                       : ["Офлайн (без пароля)", "Ely.by"]
            }

            // Состояние аккаунта Microsoft и кнопка повторного входа: токен
            // может протухнуть, а сменить аккаунт иначе нечем.
            Column {
                x: 20
                width: parent.width - 40
                spacing: 8
                visible: epAuth.currentIndex === 2

                Text {
                    text: editProfileDialog.msLinked ? qsTr("Вход выполнен")
                                                     : qsTr("Вход не выполнен")
                    color: editProfileDialog.msLinked ? "#91B315" : "#aaaaaa"
                    font.pixelSize: 13
                }

                Button {
                    width: parent.width; height: 34
                    enabled: backend.microsoftAvailable
                    text: editProfileDialog.msLinked ? "Войти заново" : "Войти в Microsoft"
                    contentItem: Text {
                        text: parent.text
                        color: "#ffffff"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }
                    background: Rectangle {
                        color: parent.pressed ? "#6a8510" : "#91B315"
                        radius: 6
                    }
                    onClicked: {
                        // Тип профиля мог быть только что переключён на
                        // Microsoft — сохраняем его до входа, иначе backend
                        // припишет токены профилю другого типа.
                        backend.updateProfile(editProfileDialog.editIndex,
                                              epName.text.trim(), "", "", "microsoft")
                        editProfileDialog.close()
                        backend.startMicrosoftLogin(editProfileDialog.editIndex)
                    }
                }
            }
        }

        footer: Item {
            implicitHeight: 60
            Rectangle {
                anchors.top: parent.top
                width: parent.width
                height: 1
                color: "#333333"
            }
            Row {
                anchors.centerIn: parent
                spacing: 12

                Button {
                    text: "Отмена"
                    width: 110; height: 36
                    contentItem: Text {
                        text: parent.text
                        color: "#ffffff"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }
                    background: Rectangle {
                        color: parent.pressed ? "#444444" : "#333333"
                        radius: 6
                    }
                    onClicked: editProfileDialog.reject()
                }

                Button {
                    text: "ОК"
                    width: 110; height: 36
                    contentItem: Text {
                        text: parent.text
                        color: "#ffffff"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }
                    background: Rectangle {
                        color: parent.pressed ? "#6a8510" : "#91B315"
                        radius: 6
                    }
                    onClicked: editProfileDialog.accept()
                }
            }
        }

        onAccepted: {
            const name = epName.text.trim()
            const type = epAuth.currentIndex === 2 ? "microsoft"
                       : epAuth.currentIndex === 1 ? "elyby" : "offline"
            if (editIndex >= 0 && name !== "")
                backend.updateProfile(editIndex, name,
                                      type === "microsoft" ? "" : epLogin.text.trim(),
                                      type === "elyby" ? epPassword.text : "",
                                      type)
            editIndex = -1
            epName.text = ""; epLogin.text = ""; epPassword.text = ""
        }
        onRejected: {
            editIndex = -1
            epName.text = ""; epLogin.text = ""; epPassword.text = ""
        }
    }

    // ── Builds Dialog ──────────────────────────────────────────────────────
    BuildsDialog {
        id: buildsDialog
        backend: backend
        x: (window.width  - width)  / 2
        y: (window.height - height) / 2
    }

    // ── Two-factor Dialog ──────────────────────────────────────────────────
    // Ely.by отклоняет пароль с пометкой two factor — код добираем здесь и
    // продолжаем прерванный запуск.
    Dialog {
        id: twoFactorDialog
        modal: true
        width: 320
        x: (window.width  - width)  / 2
        y: (window.height - height) / 2
        padding: 0
        closePolicy: Popup.NoAutoClose

        property string profileName: ""

        background: Rectangle {
            color: "#1e1e1e"
            radius: 10
            border.color: "#91B315"
            border.width: 1
        }

        header: Item {
            implicitHeight: 52
            Text {
                anchors.centerIn: parent
                text: "Двухфакторная аутентификация"
                color: "#ffffff"
                font.pixelSize: 15
                font.bold: true
            }
            Rectangle {
                anchors.bottom: parent.bottom
                width: parent.width
                height: 1
                color: "#333333"
            }
        }

        contentItem: Column {
            spacing: 12
            topPadding: 20
            bottomPadding: 20

            Text {
                x: 20
                width: parent.width - 40
                text: qsTr("Введите код из приложения-аутентификатора для «%1»")
                        .arg(twoFactorDialog.profileName)
                color: "#aaaaaa"
                font.pixelSize: 12
                wrapMode: Text.Wrap
            }

            TextField {
                id: twoFactorCode
                x: 20
                width: parent.width - 40
                placeholderText: "000000"
                inputMethodHints: Qt.ImhDigitsOnly
                color: "#ffffff"
                placeholderTextColor: "#666666"
                background: Rectangle {
                    color: "#2a2a2a"
                    radius: 6
                    border.color: twoFactorCode.activeFocus ? "#91B315" : "#444444"
                    border.width: 1
                }
                onAccepted: twoFactorDialog.accept()
            }
        }

        footer: Item {
            implicitHeight: 60
            Rectangle {
                anchors.top: parent.top
                width: parent.width
                height: 1
                color: "#333333"
            }
            Row {
                anchors.centerIn: parent
                spacing: 12

                Button {
                    text: "Отмена"
                    width: 110; height: 36
                    contentItem: Text {
                        text: parent.text
                        color: "#ffffff"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }
                    background: Rectangle {
                        color: parent.pressed ? "#444444" : "#333333"
                        radius: 6
                    }
                    onClicked: twoFactorDialog.reject()
                }

                Button {
                    text: "Войти"
                    width: 110; height: 36
                    contentItem: Text {
                        text: parent.text
                        color: "#ffffff"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }
                    background: Rectangle {
                        color: parent.pressed ? "#6a8510" : "#91B315"
                        radius: 6
                    }
                    onClicked: twoFactorDialog.accept()
                }
            }
        }

        onAccepted: backend.submitTwoFactorCode(twoFactorCode.text)
        onRejected: backend.cancelPendingLaunch()
    }

    // ── Settings Dialog ────────────────────────────────────────────────────
    Dialog {
        id: settingsDialog
        modal: true
        width: 460
        x: (window.width  - width)  / 2
        y: (window.height - height) / 2
        padding: 0

        // Выбранная сборка Java живёт в свойстве, а не в поле ввода: её выбирают
        // в отдельном окне, а записывается она только по «Сохранить».
        property string javaRuntimeId: ""
        // Не привязка: javaRuntimeInfo() — обычный вызов, и сам он не
        // пересчитается, когда сборка докачается. Обновляем по событиям.
        property var javaRuntimeInfo: null
        onJavaRuntimeIdChanged: settingsDialog.refreshJavaInfo()

        function refreshJavaInfo() {
            javaRuntimeInfo = javaRuntimeId === "" ? null
                                                   : backend.javaRuntimeInfo(javaRuntimeId)
        }

        Connections {
            target: backend
            function onJavaRuntimeInstalled(runtimeId) { settingsDialog.refreshJavaInfo() }
        }

        function load() {
            const s = backend.settings()
            stGameDir.text     = s.gameDir     || ""
            stJavaPath.text    = s.javaPath    || ""
            settingsDialog.javaRuntimeId = s.javaRuntime || ""
            settingsDialog.refreshJavaInfo()
            stMinMemory.text   = String(s.minMemoryMb)
            stMaxMemory.text   = String(s.maxMemoryMb)
            stJvmArgs.text     = s.jvmArgs     || ""
            stWidth.text       = String(s.windowWidth)
            stHeight.text      = String(s.windowHeight)
            stFullscreen.checked = s.fullscreen === true
            stResolved.text    = qsTr("Папка игры: %1").arg(s.resolvedGameDir)
            stJavaList.text    = backend.detectedJava().join("\n") || qsTr("Java не найдена")
            open()
        }

        background: Rectangle {
            color: "#1e1e1e"
            radius: 10
            border.color: "#91B315"
            border.width: 1
        }

        header: Item {
            implicitHeight: 52
            Text {
                anchors.centerIn: parent
                text: "Настройки запуска"
                color: "#ffffff"
                font.pixelSize: 17
                font.bold: true
            }
            Rectangle {
                anchors.bottom: parent.bottom
                width: parent.width
                height: 1
                color: "#333333"
            }
        }

        contentItem: Column {
            spacing: 10
            topPadding: 18
            bottomPadding: 18

            LabelledField {
                id: stGameDir
                x: 20
                width: parent.width - 40
                label: "Папка .minecraft"
                placeholder: "по умолчанию"
            }
            LabelledField {
                id: stJavaPath
                x: 20
                width: parent.width - 40
                label: "Путь к Java"
                placeholder: "автоопределение"
            }

            // Сборка Java из папки лаунчера. Выбрана — запуск идёт ей, а не тем,
            // что нашлось в системе; путь выше остаётся запасным вариантом.
            Column {
                x: 20
                width: parent.width - 40
                spacing: 3

                Text {
                    text: "Версия Java из папки лаунчера"
                    color: "#aaaaaa"
                    font.pixelSize: 11
                }

                Rectangle {
                    width: parent.width
                    height: 32
                    radius: 6
                    color: "#2a2a2a"
                    border.color: javaFieldArea.containsMouse ? "#91B315" : "#444444"
                    border.width: 1

                    Text {
                        anchors.fill: parent
                        leftPadding: 10
                        rightPadding: 150
                        verticalAlignment: Text.AlignVCenter
                        elide: Text.ElideRight
                        color: settingsDialog.javaRuntimeId === "" ? "#666666" : "#ffffff"
                        text: {
                            if (settingsDialog.javaRuntimeId === "")
                                return qsTr("не выбрана — искать в системе")
                            const info = settingsDialog.javaRuntimeInfo
                            if (!info)
                                return settingsDialog.javaRuntimeId
                            return info.installed ? info.label
                                                  : qsTr("%1 — скачивается").arg(info.label)
                        }
                    }

                    Text {
                        anchors.right: clearJava.left
                        anchors.rightMargin: 12
                        anchors.verticalCenter: parent.verticalCenter
                        text: qsTr("Выбрать…")
                        color: "#91B315"
                        font.pixelSize: 12
                    }

                    Text {
                        id: clearJava
                        anchors.right: parent.right
                        anchors.rightMargin: 12
                        anchors.verticalCenter: parent.verticalCenter
                        visible: settingsDialog.javaRuntimeId !== ""
                        text: "✕"
                        color: clearJavaArea.containsMouse ? "#cc6666" : "#666666"
                        font.pixelSize: 12

                        MouseArea {
                            id: clearJavaArea
                            anchors.fill: parent
                            anchors.margins: -6
                            hoverEnabled: true
                            onClicked: settingsDialog.javaRuntimeId = ""
                        }
                    }

                    MouseArea {
                        id: javaFieldArea
                        anchors.fill: parent
                        anchors.rightMargin: 26
                        hoverEnabled: true
                        onClicked: javaPicker.openFor(settingsDialog.javaRuntimeId,
                                                      backend.requiredJavaMajor(backend.activeBuildIndex))
                    }
                }
            }

            Row {
                x: 20
                spacing: 10
                LabelledField {
                    id: stMinMemory
                    width: 100
                    label: "-Xms, МБ"
                    validator: IntValidator { bottom: 0; top: 1048576 }
                }
                LabelledField {
                    id: stMaxMemory
                    width: 100
                    label: "-Xmx, МБ"
                    validator: IntValidator { bottom: 0; top: 1048576 }
                }
                LabelledField {
                    id: stWidth
                    width: 100
                    label: "Ширина окна"
                    validator: IntValidator { bottom: 0; top: 16384 }
                }
                LabelledField {
                    id: stHeight
                    width: 100
                    label: "Высота окна"
                    validator: IntValidator { bottom: 0; top: 16384 }
                }
            }

            LabelledField {
                id: stJvmArgs
                x: 20
                width: parent.width - 40
                label: "Дополнительные аргументы JVM"
                placeholder: "-XX:+UseG1GC …"
            }

            CheckBox {
                id: stFullscreen
                x: 20
                text: "Запускать в полноэкранном режиме"

                // Индикатор Basic-стиля — крупный светлый квадрат, на тёмном
                // фоне диалога он не читается. Рисуем свой в палитре окна.
                indicator: Rectangle {
                    implicitWidth: 18
                    implicitHeight: 18
                    x: stFullscreen.leftPadding
                    y: (stFullscreen.height - height) / 2
                    radius: 4
                    color: "#2a2a2a"
                    border.color: stFullscreen.checked ? "#91B315" : "#444444"
                    border.width: 1

                    Rectangle {
                        anchors.centerIn: parent
                        width: 10; height: 10
                        radius: 2
                        color: "#91B315"
                        visible: stFullscreen.checked
                    }
                }

                contentItem: Text {
                    text: stFullscreen.text
                    color: "#ffffff"
                    leftPadding: (stFullscreen.indicator ? stFullscreen.indicator.width : 0) + 6
                    verticalAlignment: Text.AlignVCenter
                }
            }

            Text {
                id: stResolved
                x: 20
                width: parent.width - 40
                color: "#888888"
                font.pixelSize: 11
                wrapMode: Text.Wrap
            }
            Text {
                id: stJavaList
                x: 20
                width: parent.width - 40
                color: "#888888"
                font.pixelSize: 11
                wrapMode: Text.Wrap
            }
        }

        footer: Item {
            implicitHeight: 60
            Rectangle {
                anchors.top: parent.top
                width: parent.width
                height: 1
                color: "#333333"
            }
            Row {
                anchors.centerIn: parent
                spacing: 12

                Button {
                    text: "Отмена"
                    width: 110; height: 36
                    contentItem: Text {
                        text: parent.text
                        color: "#ffffff"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }
                    background: Rectangle {
                        color: parent.pressed ? "#444444" : "#333333"
                        radius: 6
                    }
                    onClicked: settingsDialog.reject()
                }

                Button {
                    text: "Сохранить"
                    width: 110; height: 36
                    contentItem: Text {
                        text: parent.text
                        color: "#ffffff"
                        horizontalAlignment: Text.AlignHCenter
                        verticalAlignment: Text.AlignVCenter
                    }
                    background: Rectangle {
                        color: parent.pressed ? "#6a8510" : "#91B315"
                        radius: 6
                    }
                    onClicked: settingsDialog.accept()
                }
            }
        }

        onAccepted: backend.updateSettings({
            "gameDir":      stGameDir.text.trim(),
            "javaPath":     stJavaPath.text.trim(),
            "javaRuntime":  settingsDialog.javaRuntimeId,
            "minMemoryMb":  parseInt(stMinMemory.text) || 0,
            "maxMemoryMb":  parseInt(stMaxMemory.text) || 0,
            "jvmArgs":      stJvmArgs.text.trim(),
            "windowWidth":  parseInt(stWidth.text)  || 0,
            "windowHeight": parseInt(stHeight.text) || 0,
            "fullscreen":   stFullscreen.checked
        })
    }

    // Окно выбора Java. Живёт рядом с настройками, а не внутри: оно шире их и
    // центрируется по самому окну лаунчера.
    JavaPickerDialog {
        id: javaPicker
        backend: backend
        x: (window.width  - width)  / 2
        y: (window.height - height) / 2
        onRuntimeChosen: (runtimeId) => settingsDialog.javaRuntimeId = runtimeId
    }
}
