import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import Quickshell.Hyprland

import "../config" as Cfg
import "../components"

PanelWindow {
    id: root

    property bool isTop:     true
    readonly property int barEdgePad: Cfg.Config.frameVariant === "bar" ? (isTop ? Cfg.Config.frameBarPadTop : Cfg.Config.frameBarPadBottom) : 0
    property int  barHeight: Cfg.Config.barHeight + barEdgePad
    property int  barMargin: Cfg.Config.margin

    readonly property var t: Cfg.Config.theme

    readonly property int launcherGap: 12

    GlobalShortcut {
        id: launcherShortcut
        name: "toggle"
        onPressed: root.toggle()
    }

    property bool   _isOpen:      false
    readonly property bool isOpen: _isOpen
    property bool   _keyboardNav: false
    property var    allItems:     []
    property bool   loading:      false
    property int    lastCacheUpdate: 0
    property var    favoriteIds:  []
    property var    accessCounts: ({})
    readonly property string statePath: Quickshell.env("HOME") + "/.config/quickshell/launcher/launcher_state.json"


    readonly property int defaultIndex: Cfg.Config.launcherSelectFirst ? 0 : -1

    readonly property var filteredItems: {
        const q = searchInput.text.toLowerCase()
        let filtered
        if (!q) {
            filtered = allItems.slice(0, 80)
        } else {
            filtered = allItems.filter(i => i.name.toLowerCase().startsWith(q))
        }
        const favs = filtered.filter(i => root.favoriteIds.includes(i.id))
        let rest = filtered.filter(i => !root.favoriteIds.includes(i.id))
        rest.sort((a, b) => (root.accessCounts[b.id] || 0) - (root.accessCounts[a.id] || 0))
        const result = []
        if (favs.length > 0) {
            result.push({ isHeader: true, section: "Favorites" })
            for (let i = 0; i < favs.length; i++)
                result.push(Object.assign({}, favs[i], { isHeader: false }))
        }
        if (rest.length > 0) {
            result.push({ isHeader: true, section: "All Applications" })
            for (let i = 0; i < rest.length; i++)
                result.push(Object.assign({}, rest[i], { isHeader: false }))
        }
        return result
    }

    function skipHeader(idx, dir) {
        while (idx >= 0 && idx < root.filteredItems.length) {
            const item = root.filteredItems[idx]
            if (item && !item.isHeader) return idx
            idx += dir
        }
        return -1
    }

    function toggle() {
        if (!_isOpen) {
            var fm = Hyprland.focusedMonitor
            if (!fm || fm.name !== screen.name) return
        }
        _isOpen ? close() : open()
    }

    function open() {
        var fm = Hyprland.focusedMonitor
        if (!fm || fm.name !== screen.name) return
        closeTimer.stop()
        visible = true
        _isOpen = true
        searchInput.text = ""
        resultsList.currentIndex = root.defaultIndex
        Qt.callLater(() => resultsList.positionViewAtBeginning())
        loadItems()
        loadState()
        Qt.callLater(() => searchInput.forceActiveFocus())
    }

    function close() {
        _isOpen = false
        closeTimer.restart()
    }

    Connections {
        target: root
        function onActiveChanged() {
            if (!root.active && root._isOpen) root.close()
        }
    }

    function loadItems() {
        loading = true
        var now = Date.now()
        var staleMs = 300000
        if (now - lastCacheUpdate < staleMs) {
            readCache()
            return
        }
        readCache()
        lastCacheUpdate = now
        updateProc.command = [Cfg.Config.launcherScript, "--update-only"]
        updateProc.running = true
    }

    function readCache() {
        const path = Cfg.Config.launcherCacheDir + "/drun.txt"
        dataProc.rawOutput = ""
        dataProc.command   = ["cat", path]
        dataProc.running   = true
    }

    function shellQuote(str) {
        return "'" + str.replace(/'/g, "'\\''") + "'"
    }

    function parseExec(str) {
        const args = []
        let current = ""
        let inSingle = false
        let inDouble = false

        for (let i = 0; i < str.length; i++) {
            const ch = str[i]

            if (inSingle) {
                if (ch === "'") inSingle = false
                else current += ch
            } else if (inDouble) {
                if (ch === '"') inDouble = false
                else current += ch
            } else if (ch === "'") {
                inSingle = true
            } else if (ch === '"') {
                inDouble = true
            } else if (/\s/.test(ch)) {
                if (current.length > 0) {
                    args.push(current)
                    current = ""
                }
            } else {
                current += ch
            }
        }
        if (current.length > 0) args.push(current)
        return args
    }

    function loadState() {
        stateReadProc.command = ["sh", "-c", "cat " + shellQuote(statePath) + " 2>/dev/null || true"]
        stateReadProc.running = true
    }

    function saveState() {
        const data = JSON.stringify({ favorites: favoriteIds, access: accessCounts })
        stateWriteProc.command = ["sh", "-c",
            "printf '%s' " + shellQuote(data) + " > " + shellQuote(statePath)]
        stateWriteProc.running = true
    }

    function toggleFavorite(itemId) {
        const arr = favoriteIds.slice()
        const idx = arr.indexOf(itemId)
        if (idx >= 0) {
            arr.splice(idx, 1)
        } else {
            arr.push(itemId)
        }
        favoriteIds = arr
        saveState()
    }

    function incrementAccess(itemId) {
        const counts = Object.assign({}, accessCounts)
        counts[itemId] = (counts[itemId] || 0) + 1
        accessCounts = counts
        saveState()
    }

    function launch(item) {
        if (!item || !item.exec) return

        let cleanExec = item.exec.replace(/%[fFuUik]/g, "").trim()
        const logPath = "/tmp/qml_launch.log"

        const args = parseExec(cleanExec)
        const cmd  = args.map(a => shellQuote(a)).join(" ") + " > " + shellQuote(logPath) + " 2>&1 < /dev/null &"

        launchProc.command = ["sh", "-c", cmd]
        launchProc.running = true

        console.log("Launching: " + cleanExec)
        incrementAccess(item.id)
        close()
    }

    visible: false
    color:   "transparent"
   
    anchors { top: true; bottom: true; left: true; right: true }
    WlrLayershell.layer:         WlrLayer.Overlay
    WlrLayershell.namespace:     "main-shell-launcher"
    WlrLayershell.exclusiveZone: -1
    WlrLayershell.keyboardFocus: _isOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
    mask: Region { item: root._isOpen ? fullMask : clipContainer }


    Item { id: fullMask; anchors.fill: parent }

    PopupHideTimer {
        id: closeTimer
        onTriggered: {
            root.visible  = false
            root.allItems = []
        }
    }

    Process {
        id: updateProc
        onRunningChanged: if (!running) root.readCache()
    }

    Process {
        id: dataProc
        property string rawOutput: ""
        stdout: SplitParser { onRead: data => dataProc.rawOutput += data + "\n" }
        onRunningChanged: {
            if (!running && rawOutput) {
                const lines = rawOutput.trim().split("\n")
                const items = []
                for (let i = 0; i < lines.length; i++) {
                    const p = lines[i].split("\t")
                    if (p.length >= 3) items.push({ name: p[0], exec: p[1], id: p[2], icon: p[3] || "" })
                }
                root.allItems = items
                root.loading  = false
            }
        }
    }

    Process { id: launchProc }

    Process {
        id: stateReadProc
        property string rawData: ""
        stdout: SplitParser { onRead: data => stateReadProc.rawData += data + "\n" }
        onRunningChanged: {
            if (!running) {
                if (rawData.trim()) {
                    try {
                        const state = JSON.parse(rawData.trim())
                        if (Array.isArray(state.favorites))
                            root.favoriteIds = state.favorites
                        if (state.access && typeof state.access === "object")
                            root.accessCounts = state.access
                    } catch (e) {
                        console.log("Failed to parse launcher state: " + e)
                    }
                }
                rawData = ""
            }
        }
    }

    Process { id: stateWriteProc }


    MouseArea {
        anchors.fill: parent
        enabled: root._isOpen
        onClicked: root.close()
    }

    Item {
        id: clipContainer
        anchors.horizontalCenter: parent.horizontalCenter
        width: 460

        anchors.top:          root.isTop ? parent.top    : undefined
        anchors.bottom:       root.isTop ? undefined     : parent.bottom
        anchors.topMargin:    root.isTop ? (root.barHeight + root.launcherGap) : 0
        anchors.bottomMargin: root.isTop ? 0 : (root.barHeight + root.launcherGap)
        height: parent.height - root.barHeight - root.launcherGap

        clip: true 

        PopupCard {
            id: bubble
            popupOpen: root._isOpen
            isTop: root.isTop
            animEnabled: root.visible
            property int targetHeight: Math.min(layout.implicitHeight + 28, 540)
            height: targetHeight
            Behavior on height {
                enabled: root._isOpen
                NumberAnimation { duration: 80; easing.type: Easing.OutCubic }
            }

            radius:       Cfg.Config.popupRadius
            color:        t.launcherBg
            border.color: t.launcherBorder
            border.width: Cfg.Config.popupBorderWidth

            focus: root._isOpen
            Keys.onEscapePressed: root.close()

            ColumnLayout {
                id: layout
                anchors.top: parent.top
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.margins: 14
                spacing: 10

                Rectangle {
                    Layout.fillWidth: true
                    height: 44
                    radius: 10
                    color: t.launcherInput
                    border.color: searchInput.activeFocus ? t.launcherInputBorderFocus : t.launcherBorder
                    border.width: Cfg.Config.popupBorderWidth

                    Behavior on color { NumberAnimation { duration: 180 } }

                    RowLayout {
                        anchors.fill: parent
                        anchors.leftMargin:  12
                        anchors.rightMargin: 8
                        spacing: 10

                        TextInput {
                            id: searchInput
                            Layout.fillWidth: true
                            color: t.launcherText
                            font.pixelSize: 15
                            verticalAlignment: TextInput.AlignVCenter
                            selectionColor: t.launcherAccent

                            onTextChanged: resultsList.currentIndex = root.defaultIndex

                            Keys.onReturnPressed: {
                                const idx = root.skipHeader(Math.max(0, resultsList.currentIndex), 1)
                                if (idx >= 0)
                                    root.launch(root.filteredItems[idx])
                            }

                            Keys.onDownPressed: {
                                root._keyboardNav = true
                                var next = resultsList.currentIndex < 0 ? 0 : resultsList.currentIndex + 1
                                if (next >= resultsList.count) next = resultsList.count - 1
                                next = root.skipHeader(next, 1)
                                if (next < 0) next = root.skipHeader(0, 1)
                                if (next >= 0) {
                                    resultsList.currentIndex = next
                                    resultsList.positionViewAtIndex(next, ListView.Contain)
                                }
                            }

                            Keys.onUpPressed: {
                                root._keyboardNav = true
                                var next = resultsList.currentIndex < 0 ? resultsList.count - 1 : resultsList.currentIndex - 1
                                if (next < 0) next = 0
                                next = root.skipHeader(next, -1)
                                if (next < 0) next = root.skipHeader(resultsList.count - 1, -1)
                                if (next >= 0) {
                                    resultsList.currentIndex = next
                                    resultsList.positionViewAtIndex(next, ListView.Contain)
                                }
                            }
                        }
                    }
                }

                ListView {
                    id: resultsList
                    Layout.fillWidth: true
                    Layout.preferredHeight: listHeight

                    // Avoid reacting to contentHeight changes from lazy delegate
                    // instantiation during scroll — only animate on model count changes.
                    property int listHeight: 0

                    NumberAnimation {
                        id: listHeightAnim
                        target: resultsList
                        property: "listHeight"
                        duration: 80
                        easing.type: Easing.OutCubic
                    }

                    function _syncListHeight() {
                        var h = Math.min(contentHeight, 400)
                        if (h === listHeight) return
                        listHeightAnim.stop()
                        listHeightAnim.from = listHeight
                        listHeightAnim.to   = h
                        listHeightAnim.start()
                    }

                    onCountChanged:        Qt.callLater(_syncListHeight)
                    Component.onCompleted: Qt.callLater(_syncListHeight)

                    clip: true
                    model: root.filteredItems
                    currentIndex: root.defaultIndex
                    spacing: 2
                    snapMode: ListView.NoSnap
                    highlightRangeMode: ListView.NoHighlightRange
                    cacheBuffer: 400
                    reuseItems: true
                    pixelAligned: false
                    boundsBehavior: Flickable.StopAtBounds
                    flickableDirection: Flickable.VerticalFlick

                    onModelChanged: {
                        currentIndex = root.defaultIndex
                        for (let i = 0; i < count; i++) {
                            const md = root.filteredItems[i]
                            if (md && !md.isHeader) {
                                currentIndex = i
                                break
                            }
                        }
                        Qt.callLater(() => positionViewAtBeginning())
                    }
                    ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }

                    delegate: Rectangle {
                        width: resultsList.width
                        height: 38
                        radius: modelData.isHeader ? 0 : 8
                        color: modelData.isHeader ? "transparent"
                            : (starHovered || hovered) ? t.launcherHover
                            : (resultsList.currentIndex === index && index !== -1
                                ? t.launcherSelected : "transparent")
                        property bool hovered: false
                        property bool starHovered: false

                        Text {
                            visible: modelData.isHeader
                            anchors { left: parent.left; leftMargin: 12; verticalCenter: parent.verticalCenter }
                            text: modelData.section || ""
                            color: t.launcherDim
                            font.pixelSize: 13
                            font.weight: Font.Bold
                        }

                        RowLayout {
                            visible: !modelData.isHeader
                            anchors.fill: parent
                            anchors.leftMargin:  12
                            anchors.rightMargin: 12
                            spacing: 12

                            Item {
                                width: 22; height: 22
                                Layout.alignment: Qt.AlignVCenter

                                Image {
                                    id: appIcon
                                    anchors.fill: parent
                                    source:       modelData.icon ? "image://icon/" + modelData.icon : ""
                                    sourceSize:   Qt.size(64, 64)
                                    fillMode:     Image.PreserveAspectFit
                                    smooth:       true
                                    mipmap:       Cfg.Config.launcherMipmapIcons
                                    visible:      status === Image.Ready
                                }

                                Text {
                                    anchors.centerIn: parent
                                    visible:        !appIcon.visible
                                    text:           "󰣆"
                                    color:          resultsList.currentIndex === index && index !== -1 ? t.launcherAccent : t.launcherDim
                                    font.pixelSize: 16
                                }
                            }

                            Text {
                                text: modelData.name
                                color: t.launcherText
                                font.pixelSize: 14
                                Layout.fillWidth: true
                                elide: Text.ElideRight
                            }

                            Item {
                                width: 26; height: 26
                                Layout.alignment: Qt.AlignVCenter

                                Text {
                                    id: starText
                                    anchors.centerIn: parent
                                    text: root.favoriteIds.includes(modelData.id) ? "★" : "☆"
                                    font.pixelSize: 18

                                    readonly property bool isFav: root.favoriteIds.includes(modelData.id)
                                    color: starHovered ? t.launcherStarHover
                                        : (isFav ? t.launcherAccent : t.launcherStar)

                                    Behavior on color {
                                        ColorAnimation { duration: 180; easing.type: Easing.OutCubic }
                                    }
                                }
                            }
                        }

                        MouseArea {
                            anchors.fill: parent
                            hoverEnabled: true
                            onEntered: parent.hovered = true
                            onExited:  { parent.hovered = false; parent.starHovered = false }
                            onPositionChanged: mouse => {
                                parent.starHovered = !modelData.isHeader && (mouse.x >= width - 46)
                            }
                            onClicked: mouse => {
                                if (modelData.isHeader) return
                                if (mouse.x >= width - 46) {
                                    root.toggleFavorite(modelData.id)
                                } else {
                                    root.launch(modelData)
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

