pragma ComponentBehavior: Bound

import QtCore
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
import qs.Widgets

Item {
    id: root

    LayoutMirroring.enabled: I18n.isRtl
    LayoutMirroring.childrenInherit: true

    property var parentModal: null
    property var windowRulesIncludeStatus: ({
            "exists": false,
            "included": false
        })
    property bool checkingInclude: false
    property bool fixingInclude: false
    property var windowRules: []
    property var activeWindows: getActiveWindows()

    signal rulesChanged

    function getActiveWindows() {
        const toplevels = ToplevelManager.toplevels?.values || [];
        return toplevels.map(t => ({
                    appId: t.appId || "",
                    title: t.title || ""
                }));
    }

    Connections {
        target: ToplevelManager.toplevels
        function onValuesChanged() {
            root.activeWindows = root.getActiveWindows();
        }
    }

    function getWindowRulesConfigPaths() {
        const configDir = Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation));
        switch (CompositorService.compositor) {
        case "niri":
            return {
                "configFile": configDir + "/niri/config.kdl",
                "rulesFile": configDir + "/niri/dms/windowrules.kdl",
                "grepPattern": 'include.*"dms/windowrules.kdl"',
                "includeLine": 'include "dms/windowrules.kdl"'
            };
        case "hyprland":
            return {
                "configFile": configDir + "/hypr/hyprland.conf",
                "rulesFile": configDir + "/hypr/dms/windowrules.conf",
                "grepPattern": 'source.*dms/windowrules.conf',
                "includeLine": "source = ./dms/windowrules.conf"
            };
        default:
            return null;
        }
    }

    function loadWindowRules() {
        const compositor = CompositorService.compositor;
        if (compositor !== "niri" && compositor !== "hyprland") {
            windowRules = [];
            return;
        }

        Proc.runCommand("load-windowrules", ["dms", "config", "windowrules", "list", compositor], (output, exitCode) => {
            if (exitCode !== 0) {
                windowRules = [];
                return;
            }
            try {
                const result = JSON.parse(output.trim());
                const allRules = result.rules || [];
                windowRules = allRules.filter(r => (r.source || "").includes("dms/windowrules"));
                if (result.dmsStatus) {
                    windowRulesIncludeStatus = {
                        "exists": result.dmsStatus.exists,
                        "included": result.dmsStatus.included
                    };
                }
            } catch (e) {
                windowRules = [];
            }
        });
    }

    function removeRule(ruleId) {
        const compositor = CompositorService.compositor;
        if (compositor !== "niri" && compositor !== "hyprland")
            return;

        Proc.runCommand("remove-windowrule", ["dms", "config", "windowrules", "remove", compositor, ruleId], (output, exitCode) => {
            if (exitCode === 0) {
                loadWindowRules();
                rulesChanged();
            }
        });
    }

    function reorderRules(fromIndex, toIndex) {
        if (fromIndex === toIndex)
            return;

        const compositor = CompositorService.compositor;
        if (compositor !== "niri" && compositor !== "hyprland")
            return;

        let ids = windowRules.map(r => r.id);
        const [moved] = ids.splice(fromIndex, 1);
        ids.splice(toIndex, 0, moved);

        Proc.runCommand("reorder-windowrules", ["dms", "config", "windowrules", "reorder", compositor, JSON.stringify(ids)], (output, exitCode) => {
            if (exitCode === 0) {
                loadWindowRules();
                rulesChanged();
            }
        });
    }

    function checkWindowRulesIncludeStatus() {
        const compositor = CompositorService.compositor;
        if (compositor !== "niri" && compositor !== "hyprland") {
            windowRulesIncludeStatus = {
                "exists": false,
                "included": false
            };
            return;
        }

        const filename = (compositor === "niri") ? "windowrules.kdl" : "windowrules.conf";
        checkingInclude = true;
        Proc.runCommand("check-windowrules-include", ["dms", "config", "resolve-include", compositor, filename], (output, exitCode) => {
            checkingInclude = false;
            if (exitCode !== 0) {
                windowRulesIncludeStatus = {
                    "exists": false,
                    "included": false
                };
                return;
            }
            try {
                windowRulesIncludeStatus = JSON.parse(output.trim());
            } catch (e) {
                windowRulesIncludeStatus = {
                    "exists": false,
                    "included": false
                };
            }
        });
    }

    function fixWindowRulesInclude() {
        const paths = getWindowRulesConfigPaths();
        if (!paths)
            return;
        fixingInclude = true;
        const rulesDir = paths.rulesFile.substring(0, paths.rulesFile.lastIndexOf("/"));
        const unixTime = Math.floor(Date.now() / 1000);
        const backupFile = paths.configFile + ".backup" + unixTime;
        Proc.runCommand("fix-windowrules-include", ["sh", "-c", `cp "${paths.configFile}" "${backupFile}" 2>/dev/null; ` + `mkdir -p "${rulesDir}" && ` + `touch "${paths.rulesFile}" && ` + `if ! grep -v '^[[:space:]]*\\(//\\|#\\)' "${paths.configFile}" 2>/dev/null | grep -q '${paths.grepPattern}'; then ` + `echo '' >> "${paths.configFile}" && ` + `echo '${paths.includeLine}' >> "${paths.configFile}"; fi`], (output, exitCode) => {
            fixingInclude = false;
            if (exitCode !== 0)
                return;
            checkWindowRulesIncludeStatus();
            loadWindowRules();
        });
    }

    function openRuleModal(window) {
        if (!PopoutService.windowRuleModalLoader)
            return;
        PopoutService.windowRuleModalLoader.active = true;
        if (PopoutService.windowRuleModalLoader.item) {
            PopoutService.windowRuleModalLoader.item.onRuleSubmitted.connect(loadWindowRules);
            PopoutService.windowRuleModalLoader.item.show(window || null);
        }
    }

    function editRule(rule) {
        if (!PopoutService.windowRuleModalLoader)
            return;
        PopoutService.windowRuleModalLoader.active = true;
        if (PopoutService.windowRuleModalLoader.item) {
            PopoutService.windowRuleModalLoader.item.onRuleSubmitted.connect(loadWindowRules);
            PopoutService.windowRuleModalLoader.item.showEdit(rule);
        }
    }

    Component.onCompleted: {
        if (CompositorService.isNiri || CompositorService.isHyprland) {
            checkWindowRulesIncludeStatus();
            loadWindowRules();
        }
    }

    DankFlickable {
        id: flickable
        anchors.fill: parent
        clip: true
        contentWidth: width
        contentHeight: contentColumn.implicitHeight

        Column {
            id: contentColumn
            width: flickable.width
            spacing: Theme.spacingL
            topPadding: Theme.spacingXL
            bottomPadding: Theme.spacingXL

            StyledRect {
                width: Math.min(650, parent.width - Theme.spacingL * 2)
                height: headerSection.implicitHeight + Theme.spacingL * 2
                anchors.horizontalCenter: parent.horizontalCenter
                radius: Theme.cornerRadius
                color: Theme.surfaceContainerHigh

                Column {
                    id: headerSection
                    anchors.fill: parent
                    anchors.margins: Theme.spacingL
                    spacing: Theme.spacingM

                    RowLayout {
                        width: parent.width
                        spacing: Theme.spacingM

                        DankIcon {
                            name: "select_window"
                            size: Theme.iconSize
                            color: Theme.primary
                            Layout.alignment: Qt.AlignVCenter
                        }

                        ColumnLayout {
                            Layout.fillWidth: true
                            spacing: Theme.spacingXS

                            StyledText {
                                text: I18n.tr("Window Rules")
                                font.pixelSize: Theme.fontSizeLarge
                                font.weight: Font.Medium
                                color: Theme.surfaceText
                                Layout.fillWidth: true
                            }

                            StyledText {
                                text: I18n.tr("Define rules for window behavior. Saves to %1").arg(CompositorService.isNiri ? "dms/windowrules.kdl" : "dms/windowrules.conf")
                                font.pixelSize: Theme.fontSizeSmall
                                color: Theme.surfaceVariantText
                                wrapMode: Text.WordWrap
                                Layout.fillWidth: true
                            }
                        }

                        DankActionButton {
                            Layout.preferredWidth: 40
                            Layout.preferredHeight: 40
                            circular: false
                            iconName: "add"
                            iconSize: Theme.iconSize
                            iconColor: Theme.primary
                            onClicked: root.openRuleModal()
                        }
                    }

                    RowLayout {
                        width: parent.width
                        spacing: Theme.spacingM
                        visible: root.activeWindows.length > 0

                        StyledText {
                            text: I18n.tr("Create rule for:")
                            font.pixelSize: Theme.fontSizeSmall
                            color: Theme.surfaceVariantText
                            Layout.alignment: Qt.AlignVCenter
                        }

                        DankDropdown {
                            id: windowSelector
                            Layout.fillWidth: true
                            dropdownWidth: 400
                            compactMode: true
                            emptyText: I18n.tr("Select a window...")
                            options: root.activeWindows.map(w => {
                                const label = w.appId + (w.title ? " - " + w.title : "");
                                return label.length > 60 ? label.substring(0, 57) + "..." : label;
                            })
                            onValueChanged: value => {
                                if (!value)
                                    return;
                                const index = options.indexOf(value);
                                if (index < 0 || index >= root.activeWindows.length)
                                    return;
                                const window = root.activeWindows[index];
                                root.openRuleModal(window);
                                currentValue = "";
                            }
                        }
                    }
                }
            }

            StyledRect {
                id: warningBox
                width: Math.min(650, parent.width - Theme.spacingL * 2)
                height: warningSection.implicitHeight + Theme.spacingL * 2
                anchors.horizontalCenter: parent.horizontalCenter
                radius: Theme.cornerRadius

                readonly property bool showError: root.windowRulesIncludeStatus.exists && !root.windowRulesIncludeStatus.included
                readonly property bool showSetup: !root.windowRulesIncludeStatus.exists && !root.windowRulesIncludeStatus.included

                color: (showError || showSetup) ? Theme.withAlpha(Theme.warning, 0.15) : "transparent"
                border.color: (showError || showSetup) ? Theme.withAlpha(Theme.warning, 0.3) : "transparent"
                border.width: 1
                visible: (showError || showSetup) && !root.checkingInclude && (CompositorService.isNiri || CompositorService.isHyprland)

                Row {
                    id: warningSection
                    anchors.fill: parent
                    anchors.margins: Theme.spacingL
                    spacing: Theme.spacingM

                    DankIcon {
                        name: "warning"
                        size: Theme.iconSize
                        color: Theme.warning
                        anchors.verticalCenter: parent.verticalCenter
                    }

                    Column {
                        width: parent.width - Theme.iconSize - (fixButton.visible ? fixButton.width + Theme.spacingM : 0) - Theme.spacingM
                        spacing: Theme.spacingXS
                        anchors.verticalCenter: parent.verticalCenter

                        StyledText {
                            text: warningBox.showSetup ? I18n.tr("Window Rules Not Configured") : I18n.tr("Window Rules Include Missing")
                            font.pixelSize: Theme.fontSizeMedium
                            font.weight: Font.Medium
                            color: Theme.warning
                            width: parent.width
                            horizontalAlignment: Text.AlignLeft
                        }

                        StyledText {
                            readonly property string rulesFile: CompositorService.isNiri ? "dms/windowrules.kdl" : "dms/windowrules.conf"
                            text: warningBox.showSetup ? I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg(rulesFile) : I18n.tr("%1 exists but is not included. Window rules won't apply.").arg(rulesFile)
                            font.pixelSize: Theme.fontSizeSmall
                            color: Theme.surfaceVariantText
                            wrapMode: Text.WordWrap
                            width: parent.width
                            horizontalAlignment: Text.AlignLeft
                        }
                    }

                    DankButton {
                        id: fixButton
                        visible: warningBox.showError || warningBox.showSetup
                        text: root.fixingInclude ? I18n.tr("Fixing...") : (warningBox.showSetup ? I18n.tr("Setup") : I18n.tr("Fix Now"))
                        backgroundColor: Theme.warning
                        textColor: Theme.background
                        enabled: !root.fixingInclude
                        anchors.verticalCenter: parent.verticalCenter
                        onClicked: root.fixWindowRulesInclude()
                    }
                }
            }

            StyledRect {
                width: Math.min(650, parent.width - Theme.spacingL * 2)
                height: rulesSection.implicitHeight + Theme.spacingL * 2
                anchors.horizontalCenter: parent.horizontalCenter
                radius: Theme.cornerRadius
                color: Theme.surfaceContainerHigh

                Column {
                    id: rulesSection
                    anchors.fill: parent
                    anchors.margins: Theme.spacingL
                    spacing: Theme.spacingM

                    RowLayout {
                        width: parent.width
                        spacing: Theme.spacingM

                        DankIcon {
                            name: "list"
                            size: Theme.iconSize
                            color: Theme.primary
                            Layout.alignment: Qt.AlignVCenter
                        }

                        StyledText {
                            text: I18n.tr("Rules (%1)").arg(root.windowRules?.length ?? 0)
                            font.pixelSize: Theme.fontSizeMedium
                            font.weight: Font.Medium
                            color: Theme.surfaceText
                            Layout.fillWidth: true
                        }
                    }

                    Column {
                        width: parent.width
                        spacing: Theme.spacingXS
                        visible: !root.windowRules || root.windowRules.length === 0

                        Item {
                            width: 1
                            height: Theme.spacingM
                        }

                        DankIcon {
                            name: "select_window"
                            size: 40
                            color: Theme.surfaceVariantText
                            anchors.horizontalCenter: parent.horizontalCenter
                            opacity: 0.5
                        }

                        StyledText {
                            text: I18n.tr("No window rules configured")
                            font.pixelSize: Theme.fontSizeSmall
                            color: Theme.surfaceVariantText
                            anchors.horizontalCenter: parent.horizontalCenter
                        }

                        Item {
                            width: 1
                            height: Theme.spacingM
                        }
                    }

                    Column {
                        id: rulesListColumn
                        width: parent.width
                        spacing: Theme.spacingXS
                        visible: root.windowRules && root.windowRules.length > 0

                        Repeater {
                            model: ScriptModel {
                                objectProp: "id"
                                values: root.windowRules || []
                            }

                            delegate: Item {
                                id: ruleDelegateItem
                                required property var modelData
                                required property int index

                                property bool held: ruleDragArea.pressed
                                property real originalY: y

                                readonly property string ruleIdRef: modelData.id
                                readonly property var liveRuleData: {
                                    const rules = root.windowRules || [];
                                    return rules.find(r => r.id === ruleIdRef) ?? modelData;
                                }
                                readonly property string displayName: {
                                    const name = liveRuleData.name || "";
                                    if (name)
                                        return name;
                                    const m = liveRuleData.matchCriteria || {};
                                    return m.appId || m.title || I18n.tr("Unnamed Rule");
                                }

                                width: rulesListColumn.width
                                height: ruleCard.height
                                z: held ? 2 : 1

                                Rectangle {
                                    id: ruleCard
                                    width: parent.width
                                    height: ruleContent.implicitHeight + Theme.spacingM * 2
                                    radius: Theme.cornerRadius
                                    color: ruleDelegateItem.liveRuleData.enabled !== false ? Theme.surfaceContainer : Theme.withAlpha(Theme.surfaceContainer, 0.4)

                                    RowLayout {
                                        id: ruleContent
                                        anchors.fill: parent
                                        anchors.margins: Theme.spacingM
                                        anchors.leftMargin: 28
                                        spacing: Theme.spacingM

                                        ColumnLayout {
                                            Layout.fillWidth: true
                                            spacing: 2

                                            StyledText {
                                                text: ruleDelegateItem.displayName
                                                font.pixelSize: Theme.fontSizeMedium
                                                font.weight: Font.Medium
                                                color: ruleDelegateItem.liveRuleData.enabled !== false ? Theme.surfaceText : Theme.surfaceVariantText
                                                elide: Text.ElideRight
                                                Layout.fillWidth: true
                                            }

                                            StyledText {
                                                text: {
                                                    const m = ruleDelegateItem.liveRuleData.matchCriteria || {};
                                                    let parts = [];
                                                    if (m.appId)
                                                        parts.push(m.appId);
                                                    if (m.title)
                                                        parts.push("title: " + m.title);
                                                    return parts.length > 0 ? parts.join(" · ") : I18n.tr("No match criteria");
                                                }
                                                font.pixelSize: Theme.fontSizeSmall
                                                color: Theme.surfaceVariantText
                                                elide: Text.ElideRight
                                                Layout.fillWidth: true
                                            }

                                            Flow {
                                                Layout.fillWidth: true
                                                Layout.topMargin: 4
                                                spacing: Theme.spacingXS
                                                visible: {
                                                    const a = ruleDelegateItem.liveRuleData.actions || {};
                                                    return Object.keys(a).some(k => a[k] !== undefined && a[k] !== null && a[k] !== "");
                                                }

                                                Repeater {
                                                    model: {
                                                        const a = ruleDelegateItem.liveRuleData.actions || {};
                                                        const labels = {
                                                            "opacity": I18n.tr("Opacity"),
                                                            "openFloating": I18n.tr("Float"),
                                                            "openMaximized": I18n.tr("Maximize"),
                                                            "openMaximizedToEdges": I18n.tr("Max Edges"),
                                                            "openFullscreen": I18n.tr("Fullscreen"),
                                                            "openFocused": I18n.tr("Focus"),
                                                            "openOnOutput": I18n.tr("Output"),
                                                            "openOnWorkspace": I18n.tr("Workspace"),
                                                            "defaultColumnWidth": I18n.tr("Width"),
                                                            "defaultWindowHeight": I18n.tr("Height"),
                                                            "variableRefreshRate": I18n.tr("VRR"),
                                                            "blockOutFrom": I18n.tr("Block Out"),
                                                            "defaultColumnDisplay": I18n.tr("Display"),
                                                            "scrollFactor": I18n.tr("Scroll"),
                                                            "cornerRadius": I18n.tr("Radius"),
                                                            "clipToGeometry": I18n.tr("Clip"),
                                                            "tiledState": I18n.tr("Tiled"),
                                                            "minWidth": I18n.tr("Min W"),
                                                            "maxWidth": I18n.tr("Max W"),
                                                            "minHeight": I18n.tr("Min H"),
                                                            "maxHeight": I18n.tr("Max H"),
                                                            "tile": I18n.tr("Tile"),
                                                            "nofocus": I18n.tr("No Focus"),
                                                            "noborder": I18n.tr("No Border"),
                                                            "noshadow": I18n.tr("No Shadow"),
                                                            "nodim": I18n.tr("No Dim"),
                                                            "noblur": I18n.tr("No Blur"),
                                                            "noanim": I18n.tr("No Anim"),
                                                            "norounding": I18n.tr("No Round"),
                                                            "pin": I18n.tr("Pin"),
                                                            "opaque": I18n.tr("Opaque"),
                                                            "size": I18n.tr("Size"),
                                                            "move": I18n.tr("Move"),
                                                            "monitor": I18n.tr("Monitor"),
                                                            "workspace": I18n.tr("Workspace")
                                                        };
                                                        return Object.keys(a).filter(k => a[k] !== undefined && a[k] !== null && a[k] !== "").map(k => {
                                                            const val = a[k];
                                                            if (typeof val === "boolean")
                                                                return labels[k] || k;
                                                            return (labels[k] || k) + ": " + val;
                                                        });
                                                    }

                                                    delegate: Rectangle {
                                                        required property string modelData
                                                        width: chipText.implicitWidth + Theme.spacingS * 2
                                                        height: 20
                                                        radius: 10
                                                        color: Theme.withAlpha(Theme.primary, 0.15)

                                                        StyledText {
                                                            id: chipText
                                                            anchors.centerIn: parent
                                                            text: modelData
                                                            font.pixelSize: Theme.fontSizeSmall - 2
                                                            color: Theme.primary
                                                        }
                                                    }
                                                }
                                            }
                                        }

                                        RowLayout {
                                            Layout.alignment: Qt.AlignVCenter
                                            spacing: 2

                                            DankActionButton {
                                                buttonSize: 28
                                                iconName: "edit"
                                                iconSize: 16
                                                backgroundColor: "transparent"
                                                iconColor: Theme.surfaceVariantText
                                                onClicked: root.editRule(ruleDelegateItem.liveRuleData)
                                            }

                                            DankActionButton {
                                                id: deleteBtn
                                                buttonSize: 28
                                                iconName: "delete"
                                                iconSize: 16
                                                backgroundColor: "transparent"
                                                iconColor: deleteArea.containsMouse ? Theme.error : Theme.surfaceVariantText

                                                MouseArea {
                                                    id: deleteArea
                                                    anchors.fill: parent
                                                    hoverEnabled: true
                                                    cursorShape: Qt.PointingHandCursor
                                                    onClicked: root.removeRule(ruleDelegateItem.ruleIdRef)
                                                }
                                            }
                                        }
                                    }
                                }

                                MouseArea {
                                    id: ruleDragArea
                                    anchors.left: parent.left
                                    anchors.top: parent.top
                                    width: 40
                                    height: ruleCard.height
                                    hoverEnabled: true
                                    cursorShape: Qt.SizeVerCursor
                                    drag.target: ruleDelegateItem.held ? ruleDelegateItem : undefined
                                    drag.axis: Drag.YAxis
                                    preventStealing: true

                                    onPressed: {
                                        ruleDelegateItem.z = 2;
                                        ruleDelegateItem.originalY = ruleDelegateItem.y;
                                    }
                                    onReleased: {
                                        ruleDelegateItem.z = 1;
                                        if (!drag.active) {
                                            ruleDelegateItem.y = ruleDelegateItem.originalY;
                                            return;
                                        }
                                        const spacing = Theme.spacingXS;
                                        const itemH = ruleDelegateItem.height + spacing;
                                        var newIndex = Math.round(ruleDelegateItem.y / itemH);
                                        newIndex = Math.max(0, Math.min(newIndex, (root.windowRules?.length ?? 1) - 1));
                                        if (newIndex !== ruleDelegateItem.index)
                                            root.reorderRules(ruleDelegateItem.index, newIndex);
                                        ruleDelegateItem.y = ruleDelegateItem.originalY;
                                    }
                                }

                                DankIcon {
                                    x: Theme.spacingM - 2
                                    y: (ruleCard.height / 2) - (size / 2)
                                    name: "drag_indicator"
                                    size: 18
                                    color: Theme.outline
                                    opacity: ruleDragArea.containsMouse || ruleDragArea.pressed ? 1 : 0.5
                                }

                                Behavior on y {
                                    enabled: !ruleDragArea.pressed && !ruleDragArea.drag.active
                                    NumberAnimation {
                                        duration: Theme.shortDuration
                                        easing.type: Theme.standardEasing
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
