pragma Singleton
pragma ComponentBehavior: Bound

import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import Quickshell.Wayland

Singleton {
    id: root

    // true if Hyprland is running, false otherwise
    readonly property bool isHyprland: Compositor.require("hyprland")

    // reactive Hyprland data, only valid if Hyprland is running
    signal stateChanged()
    readonly property var toplevels: isHyprland ? Hyprland.toplevels : []
    readonly property var workspaces: isHyprland ? Hyprland.workspaces : []
    readonly property var monitors: isHyprland ? Hyprland.monitors : []
    readonly property Toplevel activeToplevel: isHyprland ? ToplevelManager.activeToplevel : null
    readonly property HyprlandWorkspace focusedWorkspace: isHyprland ? Hyprland.focusedWorkspace : null
    readonly property HyprlandMonitor focusedMonitor: isHyprland ? Hyprland.focusedMonitor : null
    readonly property int focusedWorkspaceId: focusedWorkspace?.id ?? 1
    property real screenW: focusedMonitor ? focusedMonitor.width : 0
    property real screenH: focusedMonitor ? focusedMonitor.height : 0
    property real screenScale: focusedMonitor ? focusedMonitor.scale : 1

    // parsed hyprctl data, defaults are empty
    property var windowList: []
    property var windowByAddress: ({})
    property var addresses: []
    property var layers: ({})
    property var monitorsInfo: []
    property var workspacesInfo: []
    property var workspaceById: ({})
    property var workspaceIds: []
    property var activeWorkspaceInfo: null
    property string keyboardLayout: "?"

    // dispatch a command to Hyprland, no-op if not running
    function dispatch(request: string): void {
        if (!isHyprland) return
        Hyprland.dispatch(request)
    }

    // switch workspace safely
    function changeWorkspace(targetWorkspaceId) {
        if (!isHyprland || !targetWorkspaceId) return
        root.dispatch("workspace " + targetWorkspaceId)
    }

    // find most recently focused window in a workspace
    function focusedWindowForWorkspace(workspaceId) {
        if (!isHyprland) return null
        const wsWindows = root.windowList.filter(w => w.workspace.id === workspaceId)
        if (wsWindows.length === 0) return null
        return wsWindows.reduce((best, win) => {
            const bestFocus = best?.focusHistoryID ?? Infinity
            const winFocus = win?.focusHistoryID ?? Infinity
            return winFocus < bestFocus ? win : best
        }, null)
    }

    // check if a workspace has any windows
    function isWorkspaceOccupied(id: int): bool {
        if (!isHyprland) return false
        return Hyprland.workspaces.values.find(w => w?.id === id)?.lastIpcObject.windows > 0 || false
    }

    // update all hyprctl processes
    function updateAll() {
        if (!isHyprland) return
        getClients.running = true
        getLayers.running = true
        getMonitors.running = true
        getWorkspaces.running = true
        getActiveWorkspace.running = true
    }

    // largest window in a workspace
    function biggestWindowForWorkspace(workspaceId) {
        if (!isHyprland) return null
        const windowsInThisWorkspace = root.windowList.filter(w => w.workspace.id === workspaceId)
        return windowsInThisWorkspace.reduce((maxWin, win) => {
            const maxArea = (maxWin?.size?.[0] ?? 0) * (maxWin?.size?.[1] ?? 0)
            const winArea = (win?.size?.[0] ?? 0) * (win?.size?.[1] ?? 0)
            return winArea > maxArea ? win : maxWin
        }, null)
    }

    // refresh keyboard layout
    function refreshKeyboardLayout() {
        if (!isHyprland) return
        hyprctlDevices.running = true
    }

    // only create hyprctl processes if Hyprland is running
    Component.onCompleted: {
        if (isHyprland) {
            updateAll()
            refreshKeyboardLayout()
        }
    }

    // process to get keyboard layout
    Process {
        id: hyprctlDevices
        running: false
        command: ["hyprctl", "devices", "-j"]
        stdout: StdioCollector {
            onStreamFinished: {
                try {
                    const devices = JSON.parse(this.text)
                    const keyboard = devices.keyboards.find(k => k.main) || devices.keyboards[0]
                    root.keyboardLayout = keyboard?.active_keymap?.toUpperCase()?.slice(0, 2) ?? "?"
                } catch (err) {
                    console.error("Failed to parse keyboard layout:", err)
                    root.keyboardLayout = "?"
                }
            }
        }
    }

    Process {
        id: getClients
        running: false
        command: ["hyprctl", "clients", "-j"]
        stdout: StdioCollector {
            onStreamFinished: {
                try {
                    root.windowList = JSON.parse(this.text)
                    let tempWinByAddress = {}
                    for (let win of root.windowList) tempWinByAddress[win.address] = win
                    root.windowByAddress = tempWinByAddress
                    root.addresses = root.windowList.map(w => w.address)
                } catch (e) {
                    console.error("Failed to parse clients:", e)
                }
            }
        }
    }

    Process {
        id: getMonitors
        running: false
        command: ["hyprctl", "monitors", "-j"]
        stdout: StdioCollector {
            onStreamFinished: {
                try { root.monitorsInfo = JSON.parse(this.text) }
                catch (e) { console.error("Failed to parse monitors:", e) }
            }
        }
    }

    Process {
        id: getLayers
        running: false
        command: ["hyprctl", "layers", "-j"]
        stdout: StdioCollector {
            onStreamFinished: {
                try { root.layers = JSON.parse(this.text) }
                catch (e) { console.error("Failed to parse layers:", e) }
            }
        }
    }

    Process {
        id: getWorkspaces
        running: false
        command: ["hyprctl", "workspaces", "-j"]
        stdout: StdioCollector {
            onStreamFinished: {
                try {
                    root.workspacesInfo = JSON.parse(this.text)
                    let map = {}
                    for (let ws of root.workspacesInfo) map[ws.id] = ws
                    root.workspaceById = map
                    root.workspaceIds = root.workspacesInfo.map(ws => ws.id)
                } catch (e) { console.error("Failed to parse workspaces:", e) }
            }
        }
    }

    Process {
        id: getActiveWorkspace
        running: false
        command: ["hyprctl", "activeworkspace", "-j"]
        stdout: StdioCollector {
            onStreamFinished: {
                try { root.activeWorkspaceInfo = JSON.parse(this.text) }
                catch (e) { console.error("Failed to parse active workspace:", e) }
            }
        }
    }

    // only connect to Hyprland events if running
    Connections {
        target: isHyprland ? Hyprland : null
        function onRawEvent(event) {
            if (!isHyprland || event.name.endsWith("v2")) return

            if (event.name.includes("activelayout"))
                refreshKeyboardLayout()
            else if (event.name.includes("mon"))
                Hyprland.refreshMonitors()
            else if (event.name.includes("workspace") || event.name.includes("window"))
                Hyprland.refreshWorkspaces()
            else
                Hyprland.refreshToplevels()

            updateAll()
            root.stateChanged()
        }
    }
}
