import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Controls.Material
import QtQuick.Controls.Material.impl
import QtQuick.Window

import QtQml

import org.electrum 1.0

import "controls"

ApplicationWindow
{
    id: app

    visible: false // initial value

    readonly property int statusBarHeight: AppController ? AppController.getStatusBarHeight() : 0
    readonly property int navigationBarHeight: AppController ? AppController.getNavigationBarHeight() : 0

    // dimensions ignored on android
    width: 480
    height: 800

    Material.theme: Material.Dark
    Material.primary: Material.Indigo
    Material.accent: Material.LightBlue
    font.pixelSize: constants.fontSizeMedium

    property QtObject constants: appconstants
    Constants { id: appconstants }

    property alias stack: mainStackView
    property alias keyboardFreeZone: _keyboardFreeZone
    property alias infobanner: _infobanner
    property color _navigationBarBackgroundColor: 'transparent'

    property string pendingIntent: ""

    property variant activeDialogs: []

    property var _exceptionDialog

    property var pluginobjects: ({})

    property QtObject appMenu: Menu {
        id: menu

        parent: Overlay.overlay
        dim: true
        modal: true
        Overlay.modal: Rectangle {
            color: "#44000000"
        }

        property int implicitChildrenWidth: 64
        width: implicitChildrenWidth + 60 + constants.paddingLarge

        MenuItem {
            icon.color: action.enabled ? 'transparent' : Material.iconDisabledColor
            icon.source: '../../icons/network.png'
            action: Action {
                text: qsTr('Network')
                onTriggered: menu.openPage(Qt.resolvedUrl('NetworkOverview.qml'))
                enabled: stack.currentItem.objectName != 'NetworkOverview'
            }
        }

        MenuItem {
            icon.color: action.enabled ? 'transparent' : Material.iconDisabledColor
            icon.source: '../../icons/preferences.png'
            action: Action {
                text: qsTr('Preferences')
                onTriggered: menu.openPage(Qt.resolvedUrl('Preferences.qml'))
                enabled: stack.currentItem.objectName != 'Properties'
            }
        }

        MenuItem {
            icon.color: action.enabled ? 'transparent' : Material.iconDisabledColor
            icon.source: '../../icons/electrum.png'
            action: Action {
                text: qsTr('About');
                onTriggered: menu.openPage(Qt.resolvedUrl('About.qml'))
                enabled: stack.currentItem.objectName != 'About'
            }
        }

        function openPage(url) {
            stack.pushOnRoot(url)
            currentIndex = -1
        }

        // determine widest element and store in implicitChildrenWidth
        function updateImplicitWidth() {
            for (let i = 0; i < menu.count; i++) {
                var item = menu.itemAt(i)
                var txt = item.text
                var txtwidth = fontMetrics.advanceWidth(txt)
                if (txtwidth > menu.implicitChildrenWidth) {
                    menu.implicitChildrenWidth = txtwidth
                }
            }
        }

        FontMetrics {
            id: fontMetrics
            font: menu.font
        }

        Component.onCompleted: updateImplicitWidth()
    }

    function openAppMenu() {
        appMenu.open()
        appMenu.x = app.width - appMenu.width
        appMenu.y = toolbar.height
    }

    header: ToolBar {
        id: toolbar

        // Add top margin for status bar on Android when using edge-to-edge
        topPadding: app.statusBarHeight

        background: Rectangle {
            implicitHeight: 48
            color: constants.dialogColor

            layer.enabled: true
            layer.effect: ElevationEffect {
                elevation: 4
                fullWidth: true
            }
        }

        ColumnLayout {
            spacing: 0
            anchors.left: parent.left
            anchors.right: parent.right
            height: toolbar.availableHeight

            RowLayout {
                id: toolbarTopLayout

                Layout.fillWidth: true
                Layout.rightMargin: constants.paddingMedium
                Layout.alignment: Qt.AlignVCenter

                Item {
                    Layout.fillWidth: true
                    Layout.preferredHeight: Math.max(implicitHeight, toolbarTopLayout.height)

                    MouseArea {
                        anchors.fill: parent
                        enabled: Daemon.currentWallet &&
                            (!stack.currentItem || !stack.currentItem.title || stack.currentItem.title == Daemon.currentWallet.name)
                        onClicked: {
                            stack.getRoot().menu.open()  // open wallet-menu
                            stack.getRoot().menu.y = toolbar.height
                        }
                    }

                    RowLayout {
                        width: parent.width

                        Item {
                            Layout.preferredWidth: constants.paddingXLarge
                            Layout.preferredHeight: 1
                        }

                        Image {
                            Layout.preferredWidth: constants.iconSizeSmall
                            Layout.preferredHeight: constants.iconSizeSmall
                            visible: Daemon.currentWallet &&
                                (!stack.currentItem || !stack.currentItem.title || stack.currentItem.title == Daemon.currentWallet.name)
                            source: '../../icons/wallet.png'
                        }

                        Label {
                            Layout.fillWidth: true
                            Layout.preferredHeight: Math.max(implicitHeight, toolbarTopLayout.height)
                            text: stack.currentItem && stack.currentItem.title
                                ? stack.currentItem.title
                                : Daemon.currentWallet.name
                            elide: Label.ElideRight
                            verticalAlignment: Qt.AlignVCenter
                            font.pixelSize: constants.fontSizeMedium
                            font.bold: true
                        }
                    }
                }

                Item {
                    implicitHeight: 48
                    implicitWidth: statusIconsLayout.width

                    MouseArea {
                        anchors.fill: parent
                        onClicked: openAppMenu()  // open global-app-menu
                    }

                    RowLayout {
                        id: statusIconsLayout
                        anchors.verticalCenter: parent.verticalCenter
                        Item {
                            Layout.preferredWidth: constants.paddingLarge
                            Layout.preferredHeight: 1
                        }

                        Item {
                            visible: Network.isTestNet
                            width: column.width
                            height: column.height

                            ColumnLayout {
                                id: column
                                spacing: 0
                                Image {
                                    Layout.alignment: Qt.AlignHCenter
                                    Layout.preferredWidth: constants.iconSizeSmall
                                    Layout.preferredHeight: constants.iconSizeSmall
                                    source: "../../icons/info.png"
                                }

                                Label {
                                    id: networkNameLabel
                                    text: Network.networkName
                                    color: Material.accentColor
                                    font.pixelSize: constants.fontSizeXSmall
                                }
                            }
                        }

                        LightningNetworkStatusIndicator {
                            id: lnnsi
                        }
                        OnchainNetworkStatusIndicator { }
                    }
                }
            }

            // hack to force relayout of toolbar
            // since qt6 LightningNetworkStatusIndicator.visible doesn't trigger relayout(?)
            Item {
                Layout.preferredHeight: 1
                Layout.topMargin: -1
                Layout.preferredWidth: lnnsi.visible
                    ? 1
                    : 2
            }
        }
    }

    ColumnLayout {
        width: parent.width
        height: _keyboardFreeZone.height - header.height
        spacing: 0

        InfoBanner {
            id: _infobanner
            Layout.fillWidth: true
        }

        StackView {
            id: mainStackView
            Layout.fillHeight: true
            Layout.fillWidth: true

            initialItem: Component {
                Wallets {}
            }

            function getRoot() {
                return mainStackView.get(0)
            }
            function pushOnRoot(item) {
                if (mainStackView.depth > 1) {
                    mainStackView.replace(mainStackView.get(1), item)
                } else {
                    mainStackView.push(item)
                }
            }
            function replaceRoot(item_url) {
                mainStackView.clear()
                mainStackView.push(Qt.resolvedUrl(item_url))
            }
            function updateStylingFromItem(item) {
                _navigationBarBackgroundColor = item && 'navigationBarBackgroundColor' in item
                    ? item.navigationBarBackgroundColor
                    : 'transparent'
            }
            onCurrentItemChanged: updateStylingFromItem(currentItem)
        }

        // Add bottom padding for navigation bar on Android when UI is edge-to-edge
        Item {
            visible: app.navigationBarHeight > 0 && _keyboardFreeZone.state != 'visible'
            Layout.fillWidth: true
            Layout.preferredHeight: app.navigationBarHeight

            Rectangle { anchors.fill: parent; color: _navigationBarBackgroundColor }
        }
    }

    Timer {
        id: coverTimer
        interval: 10
        onTriggered: {
            app.visible = true
            cover.opacity = 0
        }
    }

    Rectangle {
        id: cover
        parent: Overlay.overlay
        anchors.fill: parent

        z: 1000
        color: 'black'

        Behavior on opacity {
            enabled: AppController ? AppController.isAndroid() : false
            NumberAnimation {
                duration: 1000
                easing.type: Easing.OutQuad;
            }
        }
    }

    Item {
        id: _keyboardFreeZone
        // Item as first child in Overlay that adjusts its size to the available
        // screen space minus the virtual keyboard (e.g. to center dialogs in)
        // see also ElDialog.resizeWithKeyboard property
        parent: Overlay.overlay
        width: parent.width
        height: parent.height

        states: [
            State {
                name: 'visible'
                when: Qt.inputMethod.keyboardRectangle.y
                PropertyChanges {
                    target: _keyboardFreeZone
                    height: _keyboardFreeZone.parent.height - (Screen.desktopAvailableHeight - (Qt.inputMethod.keyboardRectangle.y/Screen.devicePixelRatio))
                }
            }
        ]

        transitions: [
            Transition {
                from: ''
                to: 'visible'
                NumberAnimation {
                    properties: 'height'
                    duration: 100
                    easing.type: Easing.OutQuad
                }
            },
            Transition {
                from: 'visible'
                to: ''
                SequentialAnimation {
                    PauseAnimation {
                        duration: 200
                    }
                    NumberAnimation {
                        properties: 'height'
                        duration: 50
                        easing.type: Easing.OutQuad
                    }
                }
            }
        ]
    }

    property alias newWalletWizard: _newWalletWizard
    Component {
        id: _newWalletWizard
        NewWalletWizard {
            onClosed: destroy()
        }
    }

    property alias termsOfUseWizard: _termsOfUseWizard
    Component {
        id: _termsOfUseWizard
        TermsOfUseWizard {
            onClosed: destroy()
        }
    }

    property alias serverConnectWizard: _serverConnectWizard
    Component {
        id: _serverConnectWizard
        ServerConnectWizard {
            onClosed: destroy()
        }
    }

    property alias messageDialog: _messageDialog
    Component {
        id: _messageDialog
        MessageDialog {
            onClosed: destroy()
        }
    }

    property alias helpDialog: _helpDialog
    Component {
        id: _helpDialog
        HelpDialog {
            onClosed: destroy()
        }
    }

    property alias passwordDialog: _passwordDialog
    Component {
        id: _passwordDialog
        PasswordDialog {
            onClosed: destroy()
        }
    }

    property alias genericShareDialog: _genericShareDialog
    Component {
        id: _genericShareDialog
        GenericShareDialog {
            onClosed: destroy()
        }
    }

    property alias openWalletDialog: _openWalletDialog
    Component {
        id: _openWalletDialog
        OpenWalletDialog {
            onClosed: destroy()
        }
    }

    property alias loadingWalletDialog: _loadingWalletDialog
    Component {
        id: _loadingWalletDialog
        LoadingWalletDialog {
            onClosed: destroy()
        }
    }

    property Component scanDialog  // set in Component.onCompleted
    Component {
        id: _scanDialog
        QRScanner {
            onFinished: destroy()
        }
    }

    Component {
        id: crashDialog
        ExceptionDialog {
            onClosed: destroy()
        }
    }

    property alias channelOpenProgressDialog: _channelOpenProgressDialog
    ChannelOpenProgressDialog {
        id: _channelOpenProgressDialog
    }

    property alias signVerifyMessageDialog: _signVerifyMessageDialog
    Component {
        id: _signVerifyMessageDialog
        SignVerifyMessageDialog {
            onClosed: destroy()
        }
    }

    property alias nostrSwapServersDialog: _nostrSwapServersDialog
    Component {
        id: _nostrSwapServersDialog
        NostrSwapServersDialog {
            onClosed: destroy()
        }
    }

    Component {
        id: swapDialog
        SwapDialog {
            id: _swapdialog
            onClosed: destroy()
            swaphelper: SwapHelper {
                id: _swaphelper
                wallet: Daemon.currentWallet
                onAuthRequired: (method, authMessage) => {
                    app.handleAuthRequired(_swaphelper, method, authMessage)
                }
                onError: (message) => {
                    var dialog = app.messageDialog.createObject(app, {
                        title: qsTr('Error'),
                        iconSource: Qt.resolvedUrl('../../icons/warning.png'),
                        text: message
                    })
                    dialog.open()
                }
                onUndefinedNPub: {
                    var dialog = app.nostrSwapServersDialog.createObject(app, {
                        swaphelper: _swaphelper,
                        selectedPubkey: Config.swapServerNPub
                    })
                    dialog.accepted.connect(function() {
                        Config.swapServerNPub = dialog.selectedPubkey
                        _swaphelper.setReadyState()
                    })
                    dialog.rejected.connect(function() {
                        _swaphelper.npubSelectionCancelled()
                    })
                    dialog.open()
                }
            }
        }
    }

    NotificationPopup {
        id: notificationPopup
        width: parent.width
    }

    Component.onCompleted: {
        coverTimer.start()

        if (AppController.isAndroid()) {
            app.scanDialog = _scanDialog
        } else {
            // for running on Desktop. uses QtMultimedia.
            app.scanDialog = Qt.createComponent('ScanDialog.qml')
        }

        function continueWithServerConnection() {
            if (!Network.autoConnectDefined) {
                var dialog = serverConnectWizard.createObject(app)
                // without completed serverConnectWizard we can't start
                dialog.rejected.connect(function() {
                    app.visible = false
                    AppController.wantClose = true
                    Qt.callLater(Qt.quit)
                })
                dialog.accepted.connect(function() {
                    Daemon.startNetwork()
                    var newww = app.newWalletWizard.createObject(app)
                    newww.walletCreated.connect(function() {
                        Daemon.availableWallets.reload()
                        // and load the new wallet
                        Daemon.loadWallet(newww.path, newww.wizard_data['password'])
                    })
                    newww.open()
                })
                dialog.open()
            } else {
                Daemon.startNetwork()
                if (Daemon.availableWallets.rowCount() > 0) {
                    Daemon.loadWallet()
                } else {
                    var newww = app.newWalletWizard.createObject(app)
                    newww.walletCreated.connect(function() {
                        Daemon.availableWallets.reload()
                        // and load the new wallet
                        Daemon.loadWallet(newww.path, newww.wizard_data['password'])
                    })
                    newww.open()
                }
            }
        }

        if (!Config.termsOfUseAccepted) {
            var dialog = termsOfUseWizard.createObject(app)

            dialog.rejected.connect(function() {
                app.visible = false
                AppController.wantClose = true
                Qt.callLater(Qt.quit)
            })
            dialog.accepted.connect(function() {
                Config.termsOfUseAccepted = true
                continueWithServerConnection()
            })
            dialog.open()
        } else {
            continueWithServerConnection()
        }
    }

    onClosing: (close) => {
        if (AppController.wantClose) {
            // destroy most GUI components so that we don't dump so many null reference warnings on exit
            app.header.visible = false
            mainStackView.clear()
            return
        }
        if (activeDialogs.length > 0) {
            var activeDialog = activeDialogs[activeDialogs.length - 1]
            if (activeDialog.allowClose) {
                console.log('main: dialog.doClose')
                activeDialog.doClose()
            } else {
                console.log('dialog disallowed close')
            }
            close.accepted = false
            return
        }
        if (stack.depth > 1) {
            close.accepted = false
            stack.pop()
        } else {
            var dialog = app.messageDialog.createObject(app, {
                title: qsTr('Close Electrum?'),
                yesno: true
            })
            dialog.accepted.connect(function() {
                AppController.wantClose = true
                app.close()
            })
            dialog.open()
            close.accepted = false
        }
    }

    property var _pendingBiometricAuth: null
    property var _loadingWalletContext: null

    Connections {
        target: Biometrics
        function onUnlockSuccess(password) {
            if (app._pendingBiometricAuth) {
                if (app._pendingBiometricAuth.action === 'load_wallet') {
                    app._loadingWalletContext = _pendingBiometricAuth
                    Daemon.loadWallet(app._pendingBiometricAuth.path, password)
                    app._pendingBiometricAuth = null
                    return
                }

                let qtobject = app._pendingBiometricAuth.qtobject
                let method = app._pendingBiometricAuth.method

                if (Daemon.currentWallet.verifyPassword(password)) {
                    qtobject.authProceed()
                } else {
                    console.warn("Biometric password invalid falling back to manual input")
                    // this shouldn't really happen so we better disable biometric auth
                    Biometrics.disable()
                    handleManualAuth(qtobject, method, app._pendingBiometricAuth.authMessage)
                }
                app._pendingBiometricAuth = null
            }
        }

        function onUnlockError(error) {
            console.log("Biometric auth failed: " + error)
            // we end up here if QEBiometrics fails to give us the decrypted password. The user might
            // have cancelled the biometric auth popup or the key got invalidated because a new fingerprint got registered.
            if (app._pendingBiometricAuth) {
                if (app._pendingBiometricAuth.action === 'load_wallet') {
                    // set loadingWalletContext to disable biometric auth until the OpenWalletDialog is closed
                    app._loadingWalletContext = app._pendingBiometricAuth
                    showOpenWalletDialog(app._pendingBiometricAuth.name, app._pendingBiometricAuth.path)
                } else {
                    console.log('biometric auth failed, not falling back to passwordDialog')
                    app._pendingBiometricAuth.qtobject.authCancel()  // no fallback to password dialog
                }
                app._pendingBiometricAuth = null
            }
        }

        function onAuthRequired(method, authMessage) {
            handleAuthRequired(Biometrics, method, authMessage)
        }
    }

    property var _opendialog: null
    property var _opendialog_startup: true

    function showOpenWalletDialog(name, path) {
        if (!_opendialog) {
            _opendialog = openWalletDialog.createObject(app, {
                name: name,
                path: path,
                isStartup: _opendialog_startup,
            })
            _opendialog.closed.connect(function() {
                _opendialog = null
                app._loadingWalletContext = null  // dialog closed, we can allow trying biometric auth again
                _opendialog_startup = false
            })
            _opendialog.open()
        }
    }

    Connections {
        target: Daemon
        function onWalletRequiresPassword(name, path) {
            console.log('wallet requires password')
            if (Biometrics.isAvailable && Biometrics.isEnabled && !app._loadingWalletContext) {
                app._pendingBiometricAuth = {
                    action: 'load_wallet',
                    name: name,
                    path: path
                }
                Biometrics.unlock()
            } else {
                showOpenWalletDialog(name, path)
            }
        }
        function onWalletOpenError(error) {
            console.log('wallet open error')
            var dialog = app.messageDialog.createObject(app, {
                title: qsTr('Error'),
                iconSource: Qt.resolvedUrl('../../icons/warning.png'),
                text: error
            })
            dialog.open()
        }
        function onAuthRequired(method, authMessage) {
            handleAuthRequired(Daemon, method, authMessage)
        }
        function onLoadingChanged() {
            if (!Daemon.loading)
                return
            console.log('wallet loading')
            var dialog = loadingWalletDialog.createObject(app, { allowClose: false } )
            dialog.open()
        }
        function onWalletLoaded() {
            app._loadingWalletContext = null  // either biometric auth or manual auth was successful
        }
    }

    Connections {
        target: AppController
        function onUserNotify(wallet_name, message) {
            notificationPopup.show(wallet_name, message)
        }
        function onShowException(crash_data) {
            if (app._exceptionDialog)
                return
            app._exceptionDialog = crashDialog.createObject(app, {
                crashData: crash_data
            })
            app._exceptionDialog.onClosed.connect(function() {
                app._exceptionDialog = null
            })
            app._exceptionDialog.open()
        }
        function onPluginLoaded(name) {
            console.log('plugin ' + name + ' loaded')
            var loader = AppController.plugin(name).loader
            if (loader == undefined)
                return
            var url = Qt.resolvedUrl('../../../plugins/' + name + '/qml/' + loader)
            var comp = Qt.createComponent(url)
            if (comp.status == Component.Error) {
                console.log('Could not find/parse PluginLoader for plugin ' + name)
                console.log(comp.errorString())
                return
            }
            var obj = comp.createObject(app)
            if (obj != null)
                app.pluginobjects[name] = obj
        }
        function onUriReceived(uri) {
            console.log('uri received (main): ' + uri)
            app.pendingIntent = uri
        }
    }

    function pluginsComponentsByName(comp_name) {
        // return named QML components from plugins
        var plugins = AppController.plugins
        var result = []
        for (var i=0; i < plugins.length; i++) {
            if (!plugins[i].enabled)
                continue
            var pluginobject = app.pluginobjects[plugins[i].name]
            if (!pluginobject)
                continue
            if (!(comp_name in pluginobject))
                continue
            var comp = pluginobject[comp_name]
            if (!comp)
                continue

            result.push(comp)
        }
        return result
    }

    Connections {
        target: Daemon.currentWallet
        function onAuthRequired(method, authMessage) {
            handleAuthRequired(Daemon.currentWallet, method, authMessage)
        }
        // TODO: add to notification queue instead of barging through
        function onPaymentSucceeded(key) {
            notificationPopup.show(Daemon.currentWallet.name, qsTr('Payment succeeded'))
        }
        function onPaymentFailed(key, reason) {
            notificationPopup.show(Daemon.currentWallet.name, qsTr('Payment failed') + ': ' + reason)
        }
    }

    Connections {
        target: Config
        function onAuthRequired(method, authMessage) {
            handleAuthRequired(Config, method, authMessage)
        }
    }

    function handleAuthRequired(qtobject, method, authMessage) {
        console.log('auth using method ' + method)

        if (method === 'payment_auth') {
            if (Config.paymentAuthentication) {
                // treat like a wallet auth request
                method = 'wallet'
            } else {
                handleAuthConfirmationOnly(qtobject, authMessage)
                return
            }
        }

        if (Daemon.currentWallet.verifyPassword('')) {
            // wallet has no password
            qtobject.authProceed()
            return
        }

        if (method !== 'wallet_password_only') {
            if (Biometrics.isAvailable && Biometrics.isEnabled) {
                app._pendingBiometricAuth = {
                    qtobject: qtobject,
                    method: method,
                    authMessage: authMessage
                }
                Biometrics.unlock(authMessage)
                return
            }
        }

        handleManualAuth(qtobject, method, authMessage)
    }

    function handleManualAuth(qtobject, method, authMessage) {
        // 'payment_auth' should have been converted to 'wallet' at this point
        if (method === 'wallet' || method === 'wallet_password_only') {
            var dialog = app.passwordDialog.createObject(app, authMessage ? {'title': authMessage} : {})
            dialog.passwordEntered.connect(function(password) {
                if (Daemon.currentWallet.verifyPassword(password)) {
                    dialog.close()
                    qtobject.authProceed()
                } else {
                    dialog.clearPassword()
                    dialog.errorMessage = qsTr("Invalid Password")
                }
            })
            dialog.rejected.connect(function() {
                qtobject.authCancel()
            })
            dialog.open()
        } else {
            console.log('unknown auth method ' + method)
            qtobject.authCancel()
        }
    }

    function handleAuthConfirmationOnly(qtobject, authMessage) {
        if (!authMessage) {
            qtobject.authProceed()
            return
        }
        var dialog = app.messageDialog.createObject(app, {
            title: authMessage,
            yesno: true
        })
        dialog.accepted.connect(function() {
            qtobject.authProceed()
        })
        dialog.rejected.connect(function() {
            qtobject.authCancel()
        })
        dialog.open()
    }

    function startSwap() {
        var swapdialog = swapDialog.createObject(app)
        swapdialog.open()
    }

    property var _lastActive: 0 // record time of last activity
    property bool _lockDialogShown: false

}
