import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Widgets
import Quickshell.Hyprland
import Quickshell.I3
import qs.Common
import qs.Services
import qs.Widgets

Item {
    id: root

    property bool isVertical: axis?.isVertical ?? false
    property var axis: null
    property string screenName: ""
    property real widgetHeight: 30
    property real barThickness: 48
    property var barConfig: null
    property var blurBarWindow: null
    property var hyprlandOverviewLoader: null
    property var parentScreen: null

    readonly property real _leftMargin: {
        if (isVertical)
            return 0;
        root.x;
        if (!root.parent)
            return 0;
        const gap = root.mapToItem(null, 0, 0).x;
        return (gap > 0 && gap < 30) ? gap + 5 : 0;
    }
    readonly property real _rightMargin: {
        if (isVertical)
            return 0;
        root.x;
        root.width;
        if (!root.parent || !blurBarWindow)
            return 0;
        const gap = blurBarWindow.width - root.mapToItem(null, root.width, 0).x;
        return (gap > 0 && gap < 30) ? gap + 5 : 0;
    }
    readonly property real _topMargin: {
        if (!isVertical)
            return 0;
        root.y;
        if (!root.parent)
            return 0;
        const gap = root.mapToItem(null, 0, 0).y;
        return (gap > 0 && gap < 30) ? gap + 5 : 0;
    }
    readonly property real _bottomMargin: {
        if (!isVertical)
            return 0;
        root.y;
        root.height;
        if (!root.parent || !blurBarWindow)
            return 0;
        const gap = blurBarWindow.height - root.mapToItem(null, 0, root.height).y;
        return (gap > 0 && gap < 30) ? gap + 5 : 0;
    }

    property int _desktopEntriesUpdateTrigger: 0
    readonly property var sortedToplevels: {
        return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
    }

    readonly property string effectiveScreenName: {
        if (!SettingsData.workspaceFollowFocus)
            return root.screenName;

        switch (CompositorService.compositor) {
        case "niri":
            return NiriService.currentOutput || root.screenName;
        case "hyprland":
            return Hyprland.focusedWorkspace?.monitor?.name || root.screenName;
        case "dwl":
            return DwlService.activeOutput || root.screenName;
        case "sway":
        case "scroll":
        case "miracle":
            const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
            return focusedWs?.monitor?.name || root.screenName;
        default:
            return root.screenName;
        }
    }

    readonly property bool useExtWorkspace: DMSService.forceExtWorkspace || (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isDwl && !CompositorService.isSway && !CompositorService.isScroll && !CompositorService.isMiracle && ExtWorkspaceService.extWorkspaceAvailable)

    Connections {
        target: DesktopEntries
        function onApplicationsChanged() {
            _desktopEntriesUpdateTrigger++;
        }
    }

    property var currentWorkspace: {
        if (useExtWorkspace)
            return getExtWorkspaceActiveWorkspace();

        switch (CompositorService.compositor) {
        case "niri":
            return getNiriActiveWorkspace();
        case "hyprland":
            return getHyprlandActiveWorkspace();
        case "dwl":
            const activeTags = getDwlActiveTags();
            return activeTags.length > 0 ? activeTags[0] : -1;
        case "sway":
        case "scroll":
        case "miracle":
            return getSwayActiveWorkspace();
        default:
            return 1;
        }
    }
    property var dwlActiveTags: {
        if (CompositorService.isDwl) {
            return getDwlActiveTags();
        }
        return [];
    }
    property var workspaceList: {
        if (useExtWorkspace) {
            const baseList = getExtWorkspaceWorkspaces();
            return SettingsData.showWorkspacePadding ? padWorkspaces(baseList) : baseList;
        }

        let baseList;
        switch (CompositorService.compositor) {
        case "niri":
            baseList = getNiriWorkspaces();
            break;
        case "hyprland":
            baseList = getHyprlandWorkspaces();
            break;
        case "dwl":
            baseList = getDwlTags();
            break;
        case "sway":
        case "scroll":
        case "miracle":
            baseList = getSwayWorkspaces();
            break;
        default:
            return [1];
        }
        return SettingsData.showWorkspacePadding ? padWorkspaces(baseList) : baseList;
    }

    function getSwayWorkspaces() {
        const workspaces = I3.workspaces?.values || [];
        if (workspaces.length === 0)
            return [
                {
                    "num": 1
                }
            ];

        function mapWorkspace(ws) {
            return {
                "num": ws.number,
                "name": ws.name,
                "focused": ws.focused,
                "active": ws.active,
                "urgent": ws.urgent,
                "monitor": ws.monitor
            };
        }

        if (!root.screenName || SettingsData.workspaceFollowFocus) {
            return workspaces.slice().sort((a, b) => a.num - b.num).map(mapWorkspace);
        }

        const monitorWorkspaces = workspaces.filter(ws => ws.monitor?.name === root.screenName);
        return monitorWorkspaces.length > 0 ? monitorWorkspaces.sort((a, b) => a.num - b.num).map(mapWorkspace) : [
            {
                "num": 1
            }
        ];
    }

    function getSwayActiveWorkspace() {
        if (!root.screenName || SettingsData.workspaceFollowFocus) {
            const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
            return focusedWs ? focusedWs.num : 1;
        }

        const focusedWs = I3.workspaces?.values?.find(ws => ws.monitor?.name === root.screenName && ws.focused === true);
        return focusedWs ? focusedWs.num : 1;
    }

    function getHyprlandWorkspaces() {
        const workspaces = Hyprland.workspaces?.values || [];
        if (workspaces.length === 0) {
            return [
                {
                    id: 1,
                    name: "1"
                }
            ];
        }

        let filtered = workspaces.filter(ws => ws.id > -1);
        if (filtered.length === 0) {
            return [
                {
                    id: 1,
                    name: "1"
                }
            ];
        }

        if (!root.screenName || SettingsData.workspaceFollowFocus) {
            filtered = filtered.slice().sort((a, b) => a.id - b.id);
        } else {
            const monitorWorkspaces = filtered.filter(ws => ws.monitor?.name === root.screenName);
            filtered = monitorWorkspaces.length > 0 ? monitorWorkspaces.sort((a, b) => a.id - b.id) : [
                {
                    id: 1,
                    name: "1"
                }
            ];
        }

        if (!SettingsData.showOccupiedWorkspacesOnly) {
            return filtered;
        }

        const hyprlandToplevels = Array.from(Hyprland.toplevels?.values || []);
        const activeWsId = root.currentWorkspace;
        return filtered.filter(ws => {
            if (ws.id === activeWsId)
                return true;
            return hyprlandToplevels.some(tl => tl.workspace?.id === ws.id);
        });
    }

    function getHyprlandActiveWorkspace() {
        if (!root.screenName || SettingsData.workspaceFollowFocus) {
            return Hyprland.focusedWorkspace?.id || 1;
        }

        const monitor = Hyprland.monitors?.values?.find(m => m.name === root.screenName);
        return monitor?.activeWorkspace?.id || 1;
    }

    function getWorkspaceIcons(ws) {
        _desktopEntriesUpdateTrigger;
        if (!SettingsData.showWorkspaceApps || !ws) {
            return [];
        }

        let targetWorkspaceId;
        if (CompositorService.isNiri) {
            if (!ws || typeof ws !== "object") {
                const wsNumber = typeof ws === "number" ? ws : -1;
                if (wsNumber <= 0) {
                    return [];
                }
                const workspace = NiriService.allWorkspaces.find(w => w.idx + 1 === wsNumber && w.output === root.effectiveScreenName);
                if (!workspace) {
                    return [];
                }
                targetWorkspaceId = workspace.id;
            } else {
                if (ws.id === undefined || ws.id === -1 || ws.idx === -1) {
                    return [];
                }
                targetWorkspaceId = ws.id;
            }
        } else if (CompositorService.isHyprland) {
            targetWorkspaceId = ws.id !== undefined ? ws.id : ws;
        } else if (CompositorService.isDwl) {
            if (typeof ws !== "object" || ws.tag === undefined) {
                return [];
            }
            targetWorkspaceId = ws.tag;
        } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
            targetWorkspaceId = ws.num !== undefined ? ws.num : ws;
        } else {
            return [];
        }

        const wins = CompositorService.isNiri ? (NiriService.windows || []) : CompositorService.sortedToplevels;

        const byApp = {};
        let isActiveWs = false;
        if (CompositorService.isNiri) {
            isActiveWs = NiriService.allWorkspaces.some(ws => ws.id === targetWorkspaceId && ws.is_active);
        } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
            const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
            isActiveWs = focusedWs ? (focusedWs.num === targetWorkspaceId) : false;
        } else if (CompositorService.isDwl) {
            const output = DwlService.getOutputState(root.effectiveScreenName);
            if (output && output.tags) {
                const tag = output.tags.find(t => t.tag === targetWorkspaceId);
                isActiveWs = tag ? (tag.state === 1) : false;
            }
        } else {
            isActiveWs = targetWorkspaceId === root.currentWorkspace;
        }

        wins.forEach((w, i) => {
            if (!w) {
                return;
            }

            let winWs = null;
            if (CompositorService.isNiri) {
                winWs = w.workspace_id;
            } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
                winWs = w.workspace?.num;
            } else {
                const hyprlandToplevels = Array.from(Hyprland.toplevels?.values || []);
                const hyprToplevel = hyprlandToplevels.find(ht => ht.wayland === w);
                winWs = hyprToplevel?.workspace?.id;
            }

            if (winWs === undefined || winWs === null || winWs !== targetWorkspaceId) {
                return;
            }

            const keyBase = (w.app_id || w.appId || w.class || w.windowClass || "unknown");
            const moddedId = Paths.moddedAppId(keyBase);
            const key = isActiveWs || !SettingsData.groupWorkspaceApps ? `${moddedId}_${i}` : moddedId;

            if (!byApp[key]) {
                const isQuickshell = keyBase === "org.quickshell" || keyBase === "com.danklinux.dms";
                const isSteamApp = Paths.isSteamApp(moddedId);
                const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
                const icon = Paths.getAppIcon(moddedId, desktopEntry);
                const appName = Paths.getAppName(moddedId, desktopEntry);
                byApp[key] = {
                    "type": "icon",
                    "icon": icon,
                    "isQuickshell": isQuickshell,
                    "isSteamApp": isSteamApp,
                    "active": !!((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)),
                    "count": 1,
                    "windowId": w.address || w.id,
                    "fallbackText": appName || ""
                };
            } else {
                byApp[key].count++;
                if ((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)) {
                    byApp[key].active = true;
                }
            }
        });

        return Object.values(byApp);
    }

    function padWorkspaces(list) {
        const padded = list.slice();
        let placeholder;
        if (useExtWorkspace) {
            placeholder = {
                "id": "",
                "name": "",
                "active": false,
                "hidden": true
            };
        } else if (CompositorService.isNiri) {
            placeholder = {
                "id": -1,
                "idx": -1,
                "name": ""
            };
        } else if (CompositorService.isHyprland) {
            placeholder = {
                "id": -1,
                "name": ""
            };
        } else if (CompositorService.isDwl) {
            placeholder = {
                "tag": -1
            };
        } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
            placeholder = {
                "num": -1
            };
        } else {
            placeholder = -1;
        }
        while (padded.length < 3) {
            padded.push(placeholder);
        }
        return padded;
    }

    function getNiriWorkspaces() {
        if (NiriService.allWorkspaces.length === 0) {
            return [
                {
                    "id": 1,
                    "idx": 0,
                    "name": ""
                },
                {
                    "id": 2,
                    "idx": 1,
                    "name": ""
                }
            ];
        }

        const fallbackWorkspaces = [
            {
                "id": 1,
                "idx": 0,
                "name": ""
            },
            {
                "id": 2,
                "idx": 1,
                "name": ""
            }
        ];

        let workspaces;
        if (!root.screenName || SettingsData.workspaceFollowFocus) {
            const currentWorkspaces = NiriService.getCurrentOutputWorkspaces();
            workspaces = currentWorkspaces.length > 0 ? currentWorkspaces : fallbackWorkspaces;
        } else {
            const displayWorkspaces = NiriService.allWorkspaces.filter(ws => ws.output === root.screenName);
            workspaces = displayWorkspaces.length > 0 ? displayWorkspaces : fallbackWorkspaces;
        }

        workspaces = workspaces.slice().sort((a, b) => a.idx - b.idx);

        if (!SettingsData.showOccupiedWorkspacesOnly) {
            return workspaces;
        }

        return workspaces.filter(ws => {
            if (ws.is_active)
                return true;
            return NiriService.windows?.some(win => win.workspace_id === ws.id) ?? false;
        });
    }

    function getNiriActiveWorkspace() {
        if (NiriService.allWorkspaces.length === 0) {
            return 1;
        }

        if (!root.screenName || SettingsData.workspaceFollowFocus) {
            return NiriService.getCurrentWorkspaceNumber();
        }

        const activeWs = NiriService.allWorkspaces.find(ws => ws.output === root.screenName && ws.is_active);
        return activeWs ? activeWs.idx : 1;
    }

    function getDwlTags() {
        if (!DwlService.dwlAvailable)
            return [];

        const targetScreen = root.effectiveScreenName;
        const output = DwlService.getOutputState(targetScreen);
        if (!output || !output.tags || output.tags.length === 0)
            return [];

        if (SettingsData.dwlShowAllTags) {
            return output.tags.map(tag => ({
                        "tag": tag.tag,
                        "state": tag.state,
                        "clients": tag.clients,
                        "focused": tag.focused
                    }));
        }

        const visibleTagIndices = DwlService.getVisibleTags(targetScreen);
        return visibleTagIndices.map(tagIndex => {
            const tagData = output.tags.find(t => t.tag === tagIndex);
            return {
                "tag": tagIndex,
                "state": tagData?.state ?? 0,
                "clients": tagData?.clients ?? 0,
                "focused": tagData?.focused ?? false
            };
        });
    }

    function getDwlActiveTags() {
        if (!DwlService.dwlAvailable)
            return [];

        return DwlService.getActiveTags(root.effectiveScreenName);
    }

    function getExtWorkspaceWorkspaces() {
        const groups = ExtWorkspaceService.groups;
        if (!ExtWorkspaceService.extWorkspaceAvailable || groups.length === 0) {
            return [
                {
                    "id": "1",
                    "name": "1",
                    "active": false
                }
            ];
        }

        const group = groups.find(g => g.outputs && g.outputs.includes(root.screenName));
        if (!group || !group.workspaces) {
            return [
                {
                    "id": "1",
                    "name": "1",
                    "active": false
                }
            ];
        }

        let visible = group.workspaces.filter(ws => !ws.hidden);

        const hasValidCoordinates = visible.some(ws => ws.coordinates && ws.coordinates.length > 0);
        if (hasValidCoordinates) {
            visible = visible.sort((a, b) => {
                const coordsA = a.coordinates || [0, 0];
                const coordsB = b.coordinates || [0, 0];
                if (coordsA[0] !== coordsB[0])
                    return coordsA[0] - coordsB[0];
                return coordsA[1] - coordsB[1];
            });
        }

        visible = visible.map(ws => ({
                    id: ws.id,
                    name: ws.name,
                    coordinates: ws.coordinates,
                    state: ws.state,
                    active: ws.active,
                    urgent: ws.urgent,
                    hidden: ws.hidden,
                    groupID: group.id
                }));

        return visible.length > 0 ? visible : [
            {
                "id": "1",
                "name": "1",
                "active": false
            }
        ];
    }

    function getExtWorkspaceActiveWorkspace() {
        if (!ExtWorkspaceService.extWorkspaceAvailable) {
            return 1;
        }

        const activeWs = ExtWorkspaceService.getActiveWorkspaceForOutput(root.screenName);
        return activeWs ? (activeWs.id || activeWs.name || "1") : "1";
    }

    readonly property real dpr: parentScreen ? CompositorService.getScreenScale(parentScreen) : 1
    readonly property real padding: (root.barConfig?.removeWidgetPadding ?? false) ? 0 : Theme.snap((root.barConfig?.widgetPadding ?? 12) * (widgetHeight / 30), dpr)
    readonly property real visualWidth: isVertical ? widgetHeight : (workspaceRow.implicitWidth + padding * 2)
    readonly property real visualHeight: isVertical ? (workspaceRow.implicitHeight + padding * 2) : widgetHeight
    readonly property real appIconSize: Theme.barIconSize(barThickness, -6 + SettingsData.workspaceAppIconSizeOffset, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale)

    function getRealWorkspaces() {
        return root.workspaceList.filter(ws => {
            if (useExtWorkspace)
                return ws && (ws.id !== "" || ws.name !== "") && !ws.hidden;
            if (CompositorService.isNiri)
                return ws && ws.idx !== -1;
            if (CompositorService.isHyprland)
                return ws && ws.id !== -1;
            if (CompositorService.isDwl)
                return ws && ws.tag !== -1;
            if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
                return ws && ws.num !== -1;
            return ws !== -1;
        });
    }

    function switchToWorkspaceByModelData(data) {
        if (!data)
            return;

        if (root.useExtWorkspace && (data.id || data.name)) {
            ExtWorkspaceService.activateWorkspace(data.id || data.name, data.groupID || "");
            return;
        }

        switch (CompositorService.compositor) {
        case "niri":
            if (data.idx !== undefined)
                NiriService.switchToWorkspace(data.idx);
            break;
        case "hyprland":
            if (data.id)
                Hyprland.dispatch(`workspace ${data.id}`);
            break;
        case "dwl":
            if (data.tag !== undefined)
                DwlService.switchToTag(root.screenName, data.tag);
            break;
        case "sway":
        case "scroll":
        case "miracle":
            if (data.num)
                try {
                    I3.dispatch(`workspace number ${data.num}`);
                } catch (_) {}
            break;
        }
    }

    function findClosestWorkspaceIndex(localX, localY) {
        if (workspaceRepeater.count === 0)
            return -1;

        let closestIdx = -1;
        let closestDist = Infinity;

        for (let i = 0; i < workspaceRepeater.count; i++) {
            const item = workspaceRepeater.itemAt(i);
            if (!item)
                continue;
            const center = item.mapToItem(root, item.width / 2, item.height / 2);
            const dist = isVertical ? Math.abs(localY - center.y) : Math.abs(localX - center.x);
            if (dist < closestDist) {
                closestDist = dist;
                closestIdx = i;
            }
        }
        return closestIdx;
    }

    function switchWorkspace(direction) {
        if (useExtWorkspace) {
            const realWorkspaces = getRealWorkspaces();
            if (realWorkspaces.length < 2) {
                return;
            }

            const currentIndex = realWorkspaces.findIndex(ws => (ws.id || ws.name) === root.currentWorkspace);
            const validIndex = currentIndex === -1 ? 0 : currentIndex;
            const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);

            if (nextIndex === validIndex) {
                return;
            }

            const nextWorkspace = realWorkspaces[nextIndex];
            ExtWorkspaceService.activateWorkspace(nextWorkspace.id || nextWorkspace.name, nextWorkspace.groupID || "");
        } else if (CompositorService.isNiri) {
            const realWorkspaces = getRealWorkspaces();
            if (realWorkspaces.length < 2) {
                return;
            }

            const currentIndex = realWorkspaces.findIndex(ws => ws && ws.idx === root.currentWorkspace);
            const validIndex = currentIndex === -1 ? 0 : currentIndex;
            const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);

            if (nextIndex === validIndex) {
                return;
            }

            const nextWorkspace = realWorkspaces[nextIndex];
            if (!nextWorkspace || nextWorkspace.idx === undefined) {
                return;
            }
            NiriService.switchToWorkspace(nextWorkspace.idx);
        } else if (CompositorService.isHyprland) {
            const realWorkspaces = getRealWorkspaces();
            if (realWorkspaces.length < 2) {
                return;
            }

            const currentIndex = realWorkspaces.findIndex(ws => ws.id === root.currentWorkspace);
            const validIndex = currentIndex === -1 ? 0 : currentIndex;
            const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);

            if (nextIndex === validIndex) {
                return;
            }

            Hyprland.dispatch(`workspace ${realWorkspaces[nextIndex].id}`);
        } else if (CompositorService.isDwl) {
            const realWorkspaces = getRealWorkspaces();
            if (realWorkspaces.length < 2) {
                return;
            }

            const currentIndex = realWorkspaces.findIndex(ws => ws.tag === root.currentWorkspace);
            const validIndex = currentIndex === -1 ? 0 : currentIndex;
            const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);

            if (nextIndex === validIndex) {
                return;
            }

            DwlService.switchToTag(root.screenName, realWorkspaces[nextIndex].tag);
        } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
            const realWorkspaces = getRealWorkspaces();
            if (realWorkspaces.length < 2) {
                return;
            }

            const currentIndex = realWorkspaces.findIndex(ws => ws.num === root.currentWorkspace);
            const validIndex = currentIndex === -1 ? 0 : currentIndex;
            const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0);

            if (nextIndex === validIndex) {
                return;
            }

            try {
                I3.dispatch(`workspace number ${realWorkspaces[nextIndex].num}`);
            } catch (_) {}
        }
    }

    function getWorkspaceIndexFallback(modelData, index) {
        if (root.useExtWorkspace)
            return index + 1;
        if (CompositorService.isNiri)
            return (modelData?.idx !== undefined && modelData?.idx !== -1) ? modelData.idx : "";
        if (CompositorService.isHyprland)
            return modelData?.id || "";
        if (CompositorService.isDwl)
            return (modelData?.tag !== undefined) ? (modelData.tag + 1) : "";
        if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
            return modelData?.num || "";
        return modelData - 1;
    }

    function getWorkspaceIndex(modelData, index) {
        let isPlaceholder;
        if (root.useExtWorkspace) {
            isPlaceholder = modelData?.hidden === true;
        } else if (CompositorService.isNiri) {
            isPlaceholder = modelData?.idx === -1;
        } else if (CompositorService.isHyprland) {
            isPlaceholder = modelData?.id === -1;
        } else if (CompositorService.isDwl) {
            isPlaceholder = modelData?.tag === -1;
        } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
            isPlaceholder = modelData?.num === -1;
        } else {
            isPlaceholder = modelData === -1;
        }

        if (isPlaceholder)
            return index + 1;

        let workspaceName = "";
        if (SettingsData.showWorkspaceName) {
            workspaceName = modelData?.name ?? "";

            if (workspaceName && workspaceName !== "") {
                if (root.isVertical) {
                    workspaceName = workspaceName.charAt(0);
                }
            } else {
                workspaceName = "";
            }
        }

        if (workspaceName) {
            if (SettingsData.showWorkspaceIndex) {
                const indexLabel = getWorkspaceIndexFallback(modelData, index);
                return indexLabel ? `${indexLabel}: ${workspaceName}` : workspaceName;
            }
            return workspaceName;
        }

        return getWorkspaceIndexFallback(modelData, index);
    }

    readonly property bool hasNativeWorkspaceSupport: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle
    readonly property bool hasWorkspaces: getRealWorkspaces().length > 0
    readonly property bool shouldShow: hasNativeWorkspaceSupport || (useExtWorkspace && hasWorkspaces)

    width: shouldShow ? (isVertical ? barThickness : visualWidth) : 0
    height: shouldShow ? (isVertical ? visualHeight : barThickness) : 0
    visible: shouldShow

    Item {
        id: visualBackground
        width: root.visualWidth
        height: root.visualHeight
        anchors.centerIn: parent

        Rectangle {
            id: outline
            anchors.centerIn: parent
            width: {
                const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
                return parent.width + borderWidth * 2;
            }
            height: {
                const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
                return parent.height + borderWidth * 2;
            }
            radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
            color: "transparent"
            border.width: {
                if (barConfig?.widgetOutlineEnabled ?? false) {
                    return barConfig?.widgetOutlineThickness ?? 1;
                }
                return 0;
            }
            border.color: {
                if (!(barConfig?.widgetOutlineEnabled ?? false)) {
                    return "transparent";
                }
                const colorOption = barConfig?.widgetOutlineColor || "primary";
                const opacity = barConfig?.widgetOutlineOpacity ?? 1.0;
                switch (colorOption) {
                case "surfaceText":
                    return Theme.withAlpha(Theme.surfaceText, opacity);
                case "secondary":
                    return Theme.withAlpha(Theme.secondary, opacity);
                case "primary":
                    return Theme.withAlpha(Theme.primary, opacity);
                default:
                    return Theme.withAlpha(Theme.primary, opacity);
                }
            }
        }

        Rectangle {
            id: background
            anchors.fill: parent
            radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius
            color: {
                if ((barConfig?.noBackground ?? false))
                    return "transparent";
                const baseColor = Theme.widgetBaseBackgroundColor;
                const transparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0;
                if (Theme.widgetBackgroundHasAlpha) {
                    return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * transparency);
                }
                return Theme.withAlpha(baseColor, transparency);
            }
        }
    }

    MouseArea {
        id: edgeMouseArea
        z: -1
        x: -root._leftMargin
        y: -root._topMargin
        width: root.width + root._leftMargin + root._rightMargin
        height: root.height + root._topMargin + root._bottomMargin
        acceptedButtons: Qt.LeftButton | Qt.RightButton
        hoverEnabled: true
        cursorShape: Qt.PointingHandCursor

        property real touchpadAccumulator: 0
        property real mouseAccumulator: 0
        property bool scrollInProgress: false

        Timer {
            id: scrollCooldown
            interval: 100
            onTriggered: parent.scrollInProgress = false
        }

        onClicked: mouse => {
            const rootPos = edgeMouseArea.mapToItem(root, mouse.x, mouse.y);
            switch (mouse.button) {
            case Qt.RightButton:
                if (CompositorService.isNiri) {
                    NiriService.toggleOverview();
                } else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) {
                    root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen;
                }
                break;
            case Qt.LeftButton:
                const idx = root.findClosestWorkspaceIndex(rootPos.x, rootPos.y);
                if (idx >= 0)
                    root.switchToWorkspaceByModelData(root.workspaceList[idx]);
                break;
            }
        }

        onWheel: wheel => {
            if (Math.abs(wheel.angleDelta.x) > Math.abs(wheel.angleDelta.y)) {
                wheel.accepted = false;
                return;
            }

            if (scrollInProgress)
                return;

            const delta = wheel.angleDelta.y;
            const isTouchpad = wheel.pixelDelta && wheel.pixelDelta.y !== 0;
            const reverse = SettingsData.reverseScrolling ? -1 : 1;

            if (isTouchpad) {
                touchpadAccumulator += delta;
                if (Math.abs(touchpadAccumulator) < 500)
                    return;
                const direction = touchpadAccumulator * reverse < 0 ? 1 : -1;
                root.switchWorkspace(direction);
                scrollInProgress = true;
                scrollCooldown.restart();
                touchpadAccumulator = 0;
                return;
            }

            mouseAccumulator += delta;
            if (Math.abs(mouseAccumulator) < 120)
                return;
            const direction = mouseAccumulator * reverse < 0 ? 1 : -1;
            root.switchWorkspace(direction);
            scrollInProgress = true;
            scrollCooldown.restart();
            mouseAccumulator = 0;
        }
    }

    property int dragSourceIndex: -1
    property int dragTargetIndex: -1
    property bool suppressShiftAnimation: false

    onWorkspaceListChanged: {
        if (dragSourceIndex >= 0) {
            dragSourceIndex = -1;
            dragTargetIndex = -1;
            suppressShiftAnimation = false;
        }
    }

    Flow {
        id: workspaceRow

        x: isVertical ? visualBackground.x : (parent.width - implicitWidth) / 2
        y: isVertical ? (parent.height - implicitHeight) / 2 : visualBackground.y
        spacing: Theme.spacingS
        flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight

        Repeater {
            id: workspaceRepeater
            model: ScriptModel {
                values: root.workspaceList
            }

            Item {
                id: delegateRoot

                property bool isDropTarget: root.dragTargetIndex === index

                z: dragHandler.dragging ? 1000 : 1

                property real shiftOffset: {
                    if (root.dragSourceIndex < 0 || index === root.dragSourceIndex)
                        return 0;
                    const dragIdx = root.dragSourceIndex;
                    const dropIdx = root.dragTargetIndex;
                    if (dropIdx < 0)
                        return 0;
                    const shiftAmount = delegateRoot.width + Theme.spacingS;
                    if (dragIdx < dropIdx && index > dragIdx && index <= dropIdx)
                        return -shiftAmount;
                    if (dragIdx > dropIdx && index >= dropIdx && index < dragIdx)
                        return shiftAmount;
                    return 0;
                }

                transform: Translate {
                    x: root.isVertical ? 0 : delegateRoot.shiftOffset
                    y: root.isVertical ? delegateRoot.shiftOffset : 0
                    Behavior on x {
                        enabled: !root.suppressShiftAnimation
                        NumberAnimation {
                            duration: 150
                            easing.type: Easing.OutCubic
                        }
                    }
                    Behavior on y {
                        enabled: !root.suppressShiftAnimation
                        NumberAnimation {
                            duration: 150
                            easing.type: Easing.OutCubic
                        }
                    }
                }

                property bool isActive: {
                    if (root.useExtWorkspace)
                        return (modelData?.id || modelData?.name) === root.currentWorkspace;
                    if (CompositorService.isNiri)
                        return !!(modelData && modelData.idx === root.currentWorkspace);
                    if (CompositorService.isHyprland)
                        return !!(modelData && modelData.id === root.currentWorkspace);
                    if (CompositorService.isDwl)
                        return !!(modelData && root.dwlActiveTags.includes(modelData.tag));
                    if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
                        return !!(modelData && modelData.num === root.currentWorkspace);
                    return modelData === root.currentWorkspace;
                }
                property bool isOccupied: {
                    if (CompositorService.isHyprland)
                        return Array.from(Hyprland.toplevels?.values || []).some(tl => tl.workspace?.id === modelData?.id);
                    if (CompositorService.isDwl)
                        return modelData.clients > 0;
                    if (CompositorService.isNiri) {
                        const workspace = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.effectiveScreenName);
                        return workspace ? (NiriService.windows?.some(win => win.workspace_id === workspace.id) ?? false) : false;
                    }
                    return false;
                }
                property bool isPlaceholder: {
                    if (root.useExtWorkspace)
                        return !!(modelData && modelData.hidden);
                    if (CompositorService.isNiri)
                        return !!(modelData && modelData.idx === -1);
                    if (CompositorService.isHyprland)
                        return !!(modelData && modelData.id === -1);
                    if (CompositorService.isDwl)
                        return !!(modelData && modelData.tag === -1);
                    if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
                        return !!(modelData && modelData.num === -1);
                    return modelData === -1;
                }
                property bool isHovered: mouseArea.containsMouse

                property var loadedWorkspaceData: null
                property bool loadedIsUrgent: false
                property bool isUrgent: {
                    if (root.useExtWorkspace)
                        return modelData?.urgent ?? false;
                    if (CompositorService.isHyprland)
                        return modelData?.urgent ?? false;
                    if (CompositorService.isNiri)
                        return loadedIsUrgent;
                    if (CompositorService.isDwl)
                        return modelData?.state === 2;
                    if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
                        return loadedIsUrgent;
                    return false;
                }
                readonly property var loadedIconData: {
                    if (isPlaceholder)
                        return null;
                    const name = modelData?.name;
                    if (!name)
                        return null;
                    return SettingsData.getWorkspaceNameIcon(name);
                }
                readonly property bool loadedHasIcon: loadedIconData !== null
                property var loadedIcons: []

                readonly property int stableIconCount: {
                    if (!SettingsData.showWorkspaceApps || isPlaceholder)
                        return 0;

                    let targetWorkspaceId;
                    if (root.useExtWorkspace) {
                        targetWorkspaceId = modelData?.id || modelData?.name;
                    } else if (CompositorService.isNiri) {
                        targetWorkspaceId = modelData?.id;
                    } else if (CompositorService.isHyprland) {
                        targetWorkspaceId = modelData?.id;
                    } else if (CompositorService.isDwl) {
                        targetWorkspaceId = modelData?.tag;
                    } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
                        targetWorkspaceId = modelData?.num;
                    }
                    if (targetWorkspaceId === undefined || targetWorkspaceId === null)
                        return 0;

                    const wins = CompositorService.isNiri ? (NiriService.windows || []) : CompositorService.sortedToplevels;
                    const seen = {};
                    let groupedCount = 0;
                    let totalCount = 0;

                    for (let i = 0; i < wins.length; i++) {
                        const w = wins[i];
                        if (!w)
                            continue;

                        let winWs = null;
                        if (CompositorService.isNiri) {
                            winWs = w.workspace_id;
                        } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
                            winWs = w.workspace?.num;
                        } else if (CompositorService.isHyprland) {
                            const hyprlandToplevels = Array.from(Hyprland.toplevels?.values || []);
                            const hyprToplevel = hyprlandToplevels.find(ht => ht.wayland === w);
                            winWs = hyprToplevel?.workspace?.id;
                        }

                        if (winWs !== targetWorkspaceId)
                            continue;
                        totalCount++;

                        const appKey = w.app_id || w.appId || w.class || w.windowClass || "unknown";
                        if (!seen[appKey]) {
                            seen[appKey] = true;
                            groupedCount++;
                        }
                    }

                    return (SettingsData.groupWorkspaceApps && !isActive) ? groupedCount : totalCount;
                }

                readonly property real baseWidth: root.isVertical ? (SettingsData.showWorkspaceApps ? Math.max(widgetHeight * 0.7, root.appIconSize + Theme.spacingXS * 2) : widgetHeight * 0.5) : (isActive ? Math.max(root.widgetHeight * 1.05, root.appIconSize * 1.6) : Math.max(root.widgetHeight * 0.7, root.appIconSize * 1.2))
                readonly property real baseHeight: root.isVertical ? (isActive ? Math.max(root.widgetHeight * 1.05, root.appIconSize * 1.6) : Math.max(root.widgetHeight * 0.7, root.appIconSize * 1.2)) : (SettingsData.showWorkspaceApps ? Math.max(widgetHeight * 0.7, root.appIconSize + Theme.spacingXS * 2) : widgetHeight * 0.5)
                readonly property bool hasWorkspaceName: SettingsData.showWorkspaceName && modelData?.name && modelData.name !== ""
                readonly property bool workspaceNamesEnabled: SettingsData.showWorkspaceName && (CompositorService.isNiri || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
                readonly property real contentImplicitWidth: appIconsLoader.item?.contentWidth ?? 0
                readonly property real contentImplicitHeight: appIconsLoader.item?.contentHeight ?? 0

                readonly property real iconsExtraWidth: {
                    if (!root.isVertical && SettingsData.showWorkspaceApps && stableIconCount > 0) {
                        const numIcons = Math.min(stableIconCount, SettingsData.maxWorkspaceIcons);
                        return numIcons * root.appIconSize + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
                    }
                    return 0;
                }
                readonly property real iconsExtraHeight: {
                    if (root.isVertical && SettingsData.showWorkspaceApps && stableIconCount > 0) {
                        const numIcons = Math.min(stableIconCount, SettingsData.maxWorkspaceIcons);
                        return numIcons * root.appIconSize + (numIcons > 0 ? (numIcons - 1) * Theme.spacingXS : 0) + (isActive ? Theme.spacingXS : 0);
                    }
                    return 0;
                }

                readonly property real visualWidth: {
                    if (contentImplicitWidth <= 0)
                        return baseWidth + iconsExtraWidth;
                    const padding = root.isVertical ? Theme.spacingXS : Theme.spacingS;
                    return Math.max(baseWidth + iconsExtraWidth, contentImplicitWidth + padding);
                }
                readonly property real visualHeight: {
                    if (contentImplicitHeight <= 0)
                        return baseHeight + iconsExtraHeight;
                    const padding = root.isVertical ? Theme.spacingS : Theme.spacingXS;
                    return Math.max(baseHeight + iconsExtraHeight, contentImplicitHeight + padding);
                }

                readonly property color unfocusedColor: {
                    switch (SettingsData.workspaceUnfocusedColorMode) {
                    case "s":
                        return Theme.surface;
                    case "sc":
                        return Theme.surfaceContainer;
                    case "sch":
                        return Theme.surfaceContainerHigh;
                    default:
                        return Theme.surfaceTextAlpha;
                    }
                }

                readonly property color activeColor: {
                    switch (SettingsData.workspaceColorMode) {
                    case "s":
                        return Theme.surface;
                    case "sc":
                        return Theme.surfaceContainer;
                    case "sch":
                        return Theme.surfaceContainerHigh;
                    case "none":
                        return unfocusedColor;
                    default:
                        return Theme.primary;
                    }
                }

                readonly property color occupiedColor: {
                    switch (SettingsData.workspaceOccupiedColorMode) {
                    case "sec":
                        return Theme.secondary;
                    case "s":
                        return Theme.surface;
                    case "sc":
                        return Theme.surfaceContainer;
                    case "sch":
                        return Theme.surfaceContainerHigh;
                    case "schh":
                        return Theme.surfaceContainerHighest;
                    default:
                        return unfocusedColor;
                    }
                }

                readonly property color urgentColor: {
                    switch (SettingsData.workspaceUrgentColorMode) {
                    case "primary":
                        return Theme.primary;
                    case "secondary":
                        return Theme.secondary;
                    case "s":
                        return Theme.surface;
                    case "sc":
                        return Theme.surfaceContainer;
                    default:
                        return Theme.error;
                    }
                }

                readonly property color focusedBorderColor: {
                    switch (SettingsData.workspaceFocusedBorderColor) {
                    case "surfaceText":
                        return Theme.surfaceText;
                    case "secondary":
                        return Theme.secondary;
                    default:
                        return Theme.primary;
                    }
                }

                function getContrastingIconColor(bgColor) {
                    const luminance = 0.299 * bgColor.r + 0.587 * bgColor.g + 0.114 * bgColor.b;
                    return luminance > 0.4 ? Qt.rgba(0.15, 0.15, 0.15, 1) : Qt.rgba(0.8, 0.8, 0.8, 1);
                }

                readonly property color quickshellIconActiveColor: getContrastingIconColor(activeColor)
                readonly property color quickshellIconInactiveColor: getContrastingIconColor(unfocusedColor)

                Item {
                    id: dragHandler
                    anchors.fill: parent
                    property bool dragging: false
                    property point dragStartPos: Qt.point(0, 0)
                    property real dragAxisOffset: 0

                    Connections {
                        target: root
                        function onWorkspaceListChanged() {
                            if (dragHandler.dragging) {
                                dragHandler.dragging = false;
                                dragHandler.dragAxisOffset = 0;
                                mouseArea.mousePressed = false;
                            }
                        }
                    }
                }

                MouseArea {
                    id: mouseArea
                    anchors.fill: parent
                    hoverEnabled: !isPlaceholder
                    cursorShape: isPlaceholder ? Qt.ArrowCursor : (dragHandler.dragging ? Qt.ClosedHandCursor : Qt.PointingHandCursor)
                    enabled: !isPlaceholder
                    acceptedButtons: Qt.LeftButton | Qt.RightButton

                    property bool mousePressed: false

                    onPressed: mouse => {
                        if (mouse.button === Qt.LeftButton && CompositorService.isNiri && SettingsData.workspaceDragReorder && !isPlaceholder) {
                            mousePressed = true;
                            dragHandler.dragStartPos = Qt.point(mouse.x, mouse.y);
                        }
                    }

                    onPositionChanged: mouse => {
                        if (!mousePressed || !CompositorService.isNiri || !SettingsData.workspaceDragReorder || isPlaceholder)
                            return;

                        if (!dragHandler.dragging) {
                            const distance = root.isVertical ? Math.abs(mouse.y - dragHandler.dragStartPos.y) : Math.abs(mouse.x - dragHandler.dragStartPos.x);
                            if (distance > 5) {
                                dragHandler.dragging = true;
                                root.dragSourceIndex = index;
                                root.dragTargetIndex = index;
                            }
                        }

                        if (!dragHandler.dragging)
                            return;

                        const rawAxisOffset = root.isVertical ? (mouse.y - dragHandler.dragStartPos.y) : (mouse.x - dragHandler.dragStartPos.x);

                        const itemSize = (root.isVertical ? delegateRoot.height : delegateRoot.width) + Theme.spacingS;
                        const maxOffsetPositive = (root.workspaceList.length - 1 - index) * itemSize;
                        const maxOffsetNegative = -index * itemSize;
                        const axisOffset = Math.max(maxOffsetNegative, Math.min(maxOffsetPositive, rawAxisOffset));
                        dragHandler.dragAxisOffset = axisOffset;

                        const slotOffset = Math.round(axisOffset / itemSize);
                        const newTargetIndex = Math.max(0, Math.min(root.workspaceList.length - 1, index + slotOffset));

                        if (newTargetIndex !== root.dragTargetIndex) {
                            root.dragTargetIndex = newTargetIndex;
                        }
                    }

                    onReleased: mouse => {
                        const wasDragging = dragHandler.dragging;
                        const didReorder = wasDragging && root.dragTargetIndex >= 0 && root.dragTargetIndex !== root.dragSourceIndex;

                        if (didReorder) {
                            const sourceWs = root.workspaceList[root.dragSourceIndex];
                            const targetWs = root.workspaceList[root.dragTargetIndex];

                            if (sourceWs && targetWs && sourceWs.idx !== undefined && targetWs.idx !== undefined) {
                                root.suppressShiftAnimation = true;
                                NiriService.moveWorkspaceToIndex(sourceWs.idx, targetWs.idx);
                                Qt.callLater(() => root.suppressShiftAnimation = false);
                            }
                        }

                        mousePressed = false;
                        dragHandler.dragging = false;
                        dragHandler.dragAxisOffset = 0;
                        root.dragSourceIndex = -1;
                        root.dragTargetIndex = -1;

                        if (wasDragging || isPlaceholder)
                            return;

                        if (mouse.button === Qt.LeftButton) {
                            if (root.useExtWorkspace && (modelData?.id || modelData?.name)) {
                                ExtWorkspaceService.activateWorkspace(modelData.id || modelData.name, modelData.groupID || "");
                            } else if (CompositorService.isNiri) {
                                if (modelData && modelData.idx !== undefined) {
                                    NiriService.switchToWorkspace(modelData.idx);
                                }
                            } else if (CompositorService.isHyprland && modelData?.id) {
                                Hyprland.dispatch(`workspace ${modelData.id}`);
                            } else if (CompositorService.isDwl && modelData?.tag !== undefined) {
                                DwlService.switchToTag(root.screenName, modelData.tag);
                            } else if ((CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) && modelData?.num) {
                                try {
                                    I3.dispatch(`workspace number ${modelData.num}`);
                                } catch (_) {}
                            }
                        } else if (mouse.button === Qt.RightButton) {
                            if (CompositorService.isNiri) {
                                NiriService.toggleOverview();
                            } else if (CompositorService.isHyprland && root.hyprlandOverviewLoader?.item) {
                                root.hyprlandOverviewLoader.item.overviewOpen = !root.hyprlandOverviewLoader.item.overviewOpen;
                            } else if (CompositorService.isDwl && modelData?.tag !== undefined) {
                                DwlService.toggleTag(root.screenName, modelData.tag);
                            }
                        }
                    }
                }

                Timer {
                    id: dataUpdateTimer
                    interval: 50
                    onTriggered: {
                        if (isPlaceholder) {
                            delegateRoot.loadedWorkspaceData = null;
                            delegateRoot.loadedIcons = [];
                            delegateRoot.loadedIsUrgent = false;
                            return;
                        }

                        var wsData = null;
                        if (root.useExtWorkspace) {
                            wsData = modelData;
                        } else if (CompositorService.isNiri) {
                            wsData = modelData || null;
                        } else if (CompositorService.isHyprland) {
                            wsData = modelData;
                        } else if (CompositorService.isDwl) {
                            wsData = modelData;
                        } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
                            wsData = modelData;
                        }
                        delegateRoot.loadedWorkspaceData = wsData;
                        if (CompositorService.isNiri) {
                            const workspaceId = wsData?.id;
                            delegateRoot.loadedIsUrgent = workspaceId ? NiriService.windows.some(w => w.workspace_id === workspaceId && w.is_urgent) : false;
                        } else {
                            delegateRoot.loadedIsUrgent = wsData?.urgent ?? false;
                        }

                        if (SettingsData.showWorkspaceApps) {
                            if (CompositorService.isDwl || CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) {
                                delegateRoot.loadedIcons = root.getWorkspaceIcons(modelData);
                            } else if (CompositorService.isNiri) {
                                delegateRoot.loadedIcons = root.getWorkspaceIcons(isPlaceholder ? null : modelData);
                            } else {
                                delegateRoot.loadedIcons = root.getWorkspaceIcons(CompositorService.isHyprland ? modelData : (modelData === -1 ? null : modelData));
                            }
                        } else {
                            delegateRoot.loadedIcons = [];
                        }
                    }
                }

                function updateAllData() {
                    dataUpdateTimer.restart();
                }

                width: root.isVertical ? root.widgetHeight : visualWidth
                height: root.isVertical ? visualHeight : root.widgetHeight

                Behavior on width {
                    NumberAnimation {
                        duration: Theme.mediumDuration
                        easing.type: Theme.emphasizedEasing
                    }
                }

                Behavior on height {
                    NumberAnimation {
                        duration: Theme.mediumDuration
                        easing.type: Theme.emphasizedEasing
                    }
                }

                Rectangle {
                    id: focusedBorderRing
                    x: root.isVertical ? (root.widgetHeight - width) / 2 : (parent.width - width) / 2
                    y: root.isVertical ? (parent.height - height) / 2 : (root.widgetHeight - height) / 2
                    width: {
                        const borderWidth = (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0;
                        return delegateRoot.visualWidth + borderWidth * 2;
                    }
                    height: {
                        const borderWidth = (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0;
                        return delegateRoot.visualHeight + borderWidth * 2;
                    }
                    radius: Theme.cornerRadius
                    color: "transparent"
                    border.width: (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0
                    border.color: (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? focusedBorderColor : "transparent"

                    Behavior on width {
                        NumberAnimation {
                            duration: Theme.mediumDuration
                            easing.type: Theme.emphasizedEasing
                        }
                    }

                    Behavior on height {
                        NumberAnimation {
                            duration: Theme.mediumDuration
                            easing.type: Theme.emphasizedEasing
                        }
                    }

                    Behavior on border.width {
                        NumberAnimation {
                            duration: Theme.mediumDuration
                            easing.type: Theme.emphasizedEasing
                        }
                    }

                    Behavior on border.color {
                        ColorAnimation {
                            duration: Theme.mediumDuration
                            easing.type: Theme.emphasizedEasing
                        }
                    }
                }

                Rectangle {
                    id: visualContent
                    width: delegateRoot.visualWidth
                    height: delegateRoot.visualHeight
                    x: root.isVertical ? (root.widgetHeight - width) / 2 : (parent.width - width) / 2
                    y: root.isVertical ? (parent.height - height) / 2 : (root.widgetHeight - height) / 2
                    radius: Theme.cornerRadius
                    color: isActive ? activeColor : isUrgent ? urgentColor : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.withAlpha(unfocusedColor, 0.7) : isOccupied ? occupiedColor : unfocusedColor
                    opacity: dragHandler.dragging ? 0.8 : 1.0

                    border.width: dragHandler.dragging ? 2 : (isUrgent ? 2 : (isDropTarget ? 2 : 0))
                    border.color: dragHandler.dragging ? Theme.primary : (isUrgent ? urgentColor : (isDropTarget ? Theme.primary : "transparent"))

                    transform: Translate {
                        x: root.isVertical ? 0 : (dragHandler.dragging ? dragHandler.dragAxisOffset : 0)
                        y: root.isVertical ? (dragHandler.dragging ? dragHandler.dragAxisOffset : 0) : 0
                    }

                    Behavior on opacity {
                        NumberAnimation {
                            duration: Theme.shortDuration
                            easing.type: Theme.emphasizedEasing
                        }
                    }

                    Behavior on width {
                        NumberAnimation {
                            duration: Theme.mediumDuration
                            easing.type: Theme.emphasizedEasing
                        }
                    }

                    Behavior on height {
                        NumberAnimation {
                            duration: Theme.mediumDuration
                            easing.type: Theme.emphasizedEasing
                        }
                    }

                    Behavior on color {
                        ColorAnimation {
                            duration: Theme.mediumDuration
                            easing.type: Theme.emphasizedEasing
                        }
                    }

                    Behavior on border.width {
                        NumberAnimation {
                            duration: Theme.mediumDuration
                            easing.type: Theme.emphasizedEasing
                        }
                    }

                    Behavior on border.color {
                        ColorAnimation {
                            duration: Theme.mediumDuration
                            easing.type: Theme.emphasizedEasing
                        }
                    }

                    Loader {
                        id: appIconsLoader
                        anchors.fill: parent
                        active: SettingsData.showWorkspaceApps || SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName || loadedHasIcon
                        sourceComponent: Item {
                            id: contentRoot
                            readonly property real contentWidth: contentRow.item?.implicitWidth ?? 0
                            readonly property real contentHeight: contentRow.item?.implicitHeight ?? 0

                            Loader {
                                id: contentRow
                                anchors.centerIn: parent
                                sourceComponent: root.isVertical ? columnLayout : rowLayout
                            }

                            Component {
                                id: rowLayout
                                Row {
                                    spacing: 4
                                    visible: loadedIcons.length > 0 || SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName || loadedHasIcon

                                    Item {
                                        visible: loadedHasIcon && loadedIconData?.type === "icon"
                                        width: wsIcon.width
                                        height: root.appIconSize

                                        DankIcon {
                                            id: wsIcon
                                            anchors.verticalCenter: parent.verticalCenter
                                            name: loadedIconData?.value ?? ""
                                            size: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
                                            color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
                                            weight: (isActive && !isPlaceholder) ? 500 : 400
                                        }
                                    }

                                    Item {
                                        visible: loadedHasIcon && loadedIconData?.type === "text"
                                        width: wsText.implicitWidth
                                        height: root.appIconSize

                                        StyledText {
                                            id: wsText
                                            anchors.verticalCenter: parent.verticalCenter
                                            text: loadedIconData?.value ?? ""
                                            color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
                                            font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
                                            font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
                                        }
                                    }

                                    Item {
                                        visible: ((SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon) || (loadedHasIcon && SettingsData.showWorkspaceName && hasWorkspaceName)
                                        width: wsIndexText.implicitWidth
                                        height: root.appIconSize

                                        StyledText {
                                            id: wsIndexText
                                            anchors.verticalCenter: parent.verticalCenter
                                            text: loadedHasIcon ? (modelData?.name ?? "") : root.getWorkspaceIndex(modelData, index)
                                            color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
                                            font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
                                            font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
                                        }
                                    }

                                    Repeater {
                                        model: ScriptModel {
                                            values: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
                                        }
                                        delegate: Item {
                                            width: root.appIconSize
                                            height: root.appIconSize
                                            readonly property bool appHighlightActive: SettingsData.workspaceActiveAppHighlightEnabled && modelData.active
                                            readonly property color appBorderColor: appHighlightActive ? focusedBorderColor : Theme.primarySelected
                                            readonly property color appGlyphColor: appHighlightActive ? focusedBorderColor : Theme.primary
                                            readonly property real appOpacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6

                                            IconImage {
                                                id: rowAppIcon
                                                anchors.fill: parent
                                                source: modelData.icon || ""
                                                opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
                                                visible: !modelData.isQuickshell && !modelData.isSteamApp && status === Image.Ready
                                            }

                                            Rectangle {
                                                anchors.fill: parent
                                                visible: !modelData.isQuickshell && !modelData.isSteamApp && rowAppIcon.status !== Image.Ready
                                                color: Theme.surfaceContainer
                                                radius: Theme.cornerRadius * (root.appIconSize / 40)
                                                border.width: 1
                                                border.color: appBorderColor
                                                opacity: appOpacity

                                                StyledText {
                                                    anchors.centerIn: parent
                                                    text: (modelData.fallbackText || "?").charAt(0).toUpperCase()
                                                    font.pixelSize: parent.width * 0.45
                                                    color: appGlyphColor
                                                    font.weight: Font.Bold
                                                }
                                            }

                                            Rectangle {
                                                anchors.fill: parent
                                                visible: !modelData.isQuickshell && modelData.isSteamApp && rowSteamIcon.status !== Image.Ready
                                                color: Theme.surfaceContainer
                                                radius: Theme.cornerRadius * (root.appIconSize / 40)
                                                border.width: 1
                                                border.color: appBorderColor
                                                opacity: appOpacity

                                                DankIcon {
                                                    anchors.centerIn: parent
                                                    size: parent.width * 0.7
                                                    name: "sports_esports"
                                                    color: appGlyphColor
                                                }
                                            }

                                            IconImage {
                                                anchors.fill: parent
                                                source: modelData.icon
                                                opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
                                                visible: modelData.isQuickshell
                                                layer.enabled: true
                                                layer.effect: MultiEffect {
                                                    saturation: 0
                                                    colorization: 1
                                                    colorizationColor: appHighlightActive ? focusedBorderColor : (isActive ? quickshellIconActiveColor : quickshellIconInactiveColor)
                                                }
                                            }

                                            IconImage {
                                                id: rowSteamIcon
                                                anchors.fill: parent
                                                source: modelData.icon
                                                opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
                                                visible: modelData.isSteamApp && modelData.icon
                                            }

                                            DankIcon {
                                                anchors.centerIn: parent
                                                size: root.appIconSize
                                                name: "sports_esports"
                                                color: appHighlightActive ? focusedBorderColor : Theme.widgetTextColor
                                                opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
                                                visible: modelData.isSteamApp && !modelData.icon
                                            }

                                            Rectangle {
                                                anchors.fill: parent
                                                visible: (rowAppIcon.visible || rowSteamIcon.visible || modelData.isQuickshell) && appHighlightActive
                                                color: "transparent"
                                                radius: Theme.cornerRadius * (root.appIconSize / 40)
                                                border.width: 1
                                                border.color: focusedBorderColor
                                                z: 1
                                            }

                                            MouseArea {
                                                id: rowAppMouseArea
                                                anchors.fill: parent
                                                enabled: isActive
                                                cursorShape: Qt.PointingHandCursor
                                                onClicked: {
                                                    const winId = modelData.windowId;
                                                    if (!winId)
                                                        return;
                                                    if (CompositorService.isHyprland) {
                                                        Hyprland.dispatch(`focuswindow address:${winId}`);
                                                    } else if (CompositorService.isNiri) {
                                                        NiriService.focusWindow(winId);
                                                    }
                                                }
                                            }

                                            Rectangle {
                                                visible: modelData.count > 1 && !isActive
                                                width: root.appIconSize * 0.67
                                                height: root.appIconSize * 0.67
                                                radius: root.appIconSize * 0.33
                                                color: "black"
                                                border.color: "white"
                                                border.width: 1
                                                anchors.right: parent.right
                                                anchors.bottom: parent.bottom
                                                z: 2

                                                Text {
                                                    anchors.centerIn: parent
                                                    text: modelData.count
                                                    font.pixelSize: root.appIconSize * 0.44
                                                    color: "white"
                                                }
                                            }
                                        }
                                    }
                                }
                            }

                            Component {
                                id: columnLayout
                                Column {
                                    spacing: 4
                                    visible: loadedIcons.length > 0 || SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName || loadedHasIcon

                                    DankIcon {
                                        visible: loadedHasIcon && loadedIconData?.type === "icon"
                                        anchors.horizontalCenter: parent.horizontalCenter
                                        name: loadedIconData?.value ?? ""
                                        size: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
                                        color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
                                        weight: (isActive && !isPlaceholder) ? 500 : 400
                                    }

                                    StyledText {
                                        visible: loadedHasIcon && loadedIconData?.type === "text"
                                        anchors.horizontalCenter: parent.horizontalCenter
                                        text: loadedIconData?.value ?? ""
                                        color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
                                        font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
                                        font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
                                    }

                                    StyledText {
                                        visible: ((SettingsData.showWorkspaceIndex || SettingsData.showWorkspaceName) && !loadedHasIcon) || (loadedHasIcon && SettingsData.showWorkspaceName && hasWorkspaceName)
                                        anchors.horizontalCenter: parent.horizontalCenter
                                        text: loadedHasIcon ? (root.isVertical ? (modelData?.name ?? "").charAt(0) : (modelData?.name ?? "")) : root.getWorkspaceIndex(modelData, index)
                                        color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
                                        font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale, barConfig?.maximizeWidgetText)
                                        font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
                                    }

                                    Repeater {
                                        model: ScriptModel {
                                            values: loadedIcons.slice(0, SettingsData.maxWorkspaceIcons)
                                        }
                                        delegate: Item {
                                            width: root.appIconSize
                                            height: root.appIconSize
                                            readonly property bool appHighlightActive: SettingsData.workspaceActiveAppHighlightEnabled && modelData.active
                                            readonly property color appBorderColor: appHighlightActive ? focusedBorderColor : Theme.primarySelected
                                            readonly property color appGlyphColor: appHighlightActive ? focusedBorderColor : Theme.primary
                                            readonly property real appOpacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6

                                            IconImage {
                                                id: colAppIcon
                                                anchors.fill: parent
                                                source: modelData.icon || ""
                                                opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
                                                visible: !modelData.isQuickshell && !modelData.isSteamApp && status === Image.Ready
                                            }

                                            Rectangle {
                                                anchors.fill: parent
                                                visible: !modelData.isQuickshell && !modelData.isSteamApp && colAppIcon.status !== Image.Ready
                                                color: Theme.surfaceContainer
                                                radius: Theme.cornerRadius * (root.appIconSize / 40)
                                                border.width: 1
                                                border.color: appBorderColor
                                                opacity: appOpacity

                                                StyledText {
                                                    anchors.centerIn: parent
                                                    text: (modelData.fallbackText || "?").charAt(0).toUpperCase()
                                                    font.pixelSize: parent.width * 0.45
                                                    color: appGlyphColor
                                                    font.weight: Font.Bold
                                                }
                                            }

                                            Rectangle {
                                                anchors.fill: parent
                                                visible: !modelData.isQuickshell && modelData.isSteamApp && colSteamIcon.status !== Image.Ready
                                                color: Theme.surfaceContainer
                                                radius: Theme.cornerRadius * (root.appIconSize / 40)
                                                border.width: 1
                                                border.color: appBorderColor
                                                opacity: appOpacity

                                                DankIcon {
                                                    anchors.centerIn: parent
                                                    size: parent.width * 0.7
                                                    name: "sports_esports"
                                                    color: appGlyphColor
                                                }
                                            }

                                            IconImage {
                                                anchors.fill: parent
                                                source: modelData.icon
                                                opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
                                                visible: modelData.isQuickshell
                                                layer.enabled: true
                                                layer.effect: MultiEffect {
                                                    saturation: 0
                                                    colorization: 1
                                                    colorizationColor: appHighlightActive ? focusedBorderColor : (isActive ? quickshellIconActiveColor : quickshellIconInactiveColor)
                                                }
                                            }

                                            IconImage {
                                                id: colSteamIcon
                                                anchors.fill: parent
                                                source: modelData.icon
                                                opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
                                                visible: modelData.isSteamApp && modelData.icon
                                            }

                                            DankIcon {
                                                anchors.centerIn: parent
                                                size: root.appIconSize
                                                name: "sports_esports"
                                                color: appHighlightActive ? focusedBorderColor : Theme.widgetTextColor
                                                opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
                                                visible: modelData.isSteamApp && !modelData.icon
                                            }

                                            Rectangle {
                                                anchors.fill: parent
                                                visible: (colAppIcon.visible || colSteamIcon.visible || modelData.isQuickshell) && appHighlightActive
                                                color: "transparent"
                                                radius: Theme.cornerRadius * (root.appIconSize / 40)
                                                border.width: 1
                                                border.color: focusedBorderColor
                                                z: 1
                                            }

                                            MouseArea {
                                                id: colAppMouseArea
                                                anchors.fill: parent
                                                enabled: isActive
                                                cursorShape: Qt.PointingHandCursor
                                                onClicked: {
                                                    const winId = modelData.windowId;
                                                    if (!winId)
                                                        return;
                                                    if (CompositorService.isHyprland) {
                                                        Hyprland.dispatch(`focuswindow address:${winId}`);
                                                    } else if (CompositorService.isNiri) {
                                                        NiriService.focusWindow(winId);
                                                    }
                                                }
                                            }

                                            Rectangle {
                                                visible: modelData.count > 1 && !isActive
                                                width: root.appIconSize * 0.67
                                                height: root.appIconSize * 0.67
                                                radius: root.appIconSize * 0.33
                                                color: "black"
                                                border.color: "white"
                                                border.width: 1
                                                anchors.right: parent.right
                                                anchors.bottom: parent.bottom
                                                z: 2

                                                Text {
                                                    anchors.centerIn: parent
                                                    text: modelData.count
                                                    font.pixelSize: root.appIconSize * 0.44
                                                    color: "white"
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                Component.onCompleted: updateAllData()

                Connections {
                    target: CompositorService
                    function onSortedToplevelsChanged() {
                        delegateRoot.updateAllData();
                    }
                }
                Connections {
                    target: NiriService
                    enabled: CompositorService.isNiri
                    function onAllWorkspacesChanged() {
                        delegateRoot.updateAllData();
                    }
                    function onWindowUrgentChanged() {
                        delegateRoot.updateAllData();
                    }
                    function onWindowsChanged() {
                        delegateRoot.updateAllData();
                    }
                }
                Connections {
                    target: SettingsData
                    function onShowWorkspaceAppsChanged() {
                        delegateRoot.updateAllData();
                    }
                    function onWorkspaceNameIconsChanged() {
                        delegateRoot.updateAllData();
                    }
                    function onAppIdSubstitutionsChanged() {
                        delegateRoot.updateAllData();
                    }
                }
                Connections {
                    target: DwlService
                    enabled: CompositorService.isDwl
                    function onStateChanged() {
                        delegateRoot.updateAllData();
                    }
                }
                Connections {
                    target: Hyprland.workspaces
                    enabled: CompositorService.isHyprland
                    function onValuesChanged() {
                        delegateRoot.updateAllData();
                    }
                }
                Connections {
                    target: I3.workspaces
                    enabled: (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle)
                    function onValuesChanged() {
                        delegateRoot.updateAllData();
                    }
                }
                Connections {
                    target: ExtWorkspaceService
                    enabled: root.useExtWorkspace
                    function onStateChanged() {
                        delegateRoot.updateAllData();
                    }
                }
            }
        }
    }

    Component.onCompleted: {
        if (useExtWorkspace && !DMSService.activeSubscriptions.includes("extworkspace")) {
            DMSService.addSubscription("extworkspace");
        }
        _updateBlurRegistration();
    }

    property bool _blurRegistered: false
    readonly property bool _shouldBlur: BlurService.enabled && blurBarWindow && blurBarWindow.registerBlurWidget && !(barConfig?.noBackground ?? false) && root.visible && root.width > 0

    on_ShouldBlurChanged: _updateBlurRegistration()

    function _updateBlurRegistration() {
        if (_shouldBlur && !_blurRegistered) {
            blurBarWindow.registerBlurWidget(visualBackground);
            _blurRegistered = true;
        } else if (!_shouldBlur && _blurRegistered) {
            if (blurBarWindow && blurBarWindow.unregisterBlurWidget)
                blurBarWindow.unregisterBlurWidget(visualBackground);
            _blurRegistered = false;
        }
    }

    Component.onDestruction: {
        if (_blurRegistered && blurBarWindow && blurBarWindow.unregisterBlurWidget)
            blurBarWindow.unregisterBlurWidget(visualBackground);
    }
}
