// SPDX-FileCopyrightText: 2026 Marco Allegretti
// SPDX-License-Identifier: EUPL-1.2

import QtQuick
import org.kde.kwin as KWinComponents
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings

KWinComponents.SceneEffect {
    id: effect

    visible: false

    readonly property int outerGap: 8
    readonly property int floatEscapeMargin: 32
    readonly property int previewAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.SpatialDefault)
    readonly property int previewFadeDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast)

    property var dragConnectedWindows: ({})
    property var draggingWindow: null
    property rect dragSourceGeometry: Qt.rect(0, 0, 0, 0)

    property bool animatePreview: false
    property bool previewVisible: false
    property string previewMode: ""
    property string previewScreenName: ""
    property rect previewGeometry: Qt.rect(0, 0, 0, 0)

    function isActive() {
        return ShellSettings.Settings.convergenceModeEnabled
            && !ShellSettings.Settings.gamingModeEnabled
            && ShellSettings.Settings.dynamicTilingEnabled;
    }

    function windowKey(window) {
        return window && window.internalId !== undefined ? String(window.internalId) : "";
    }

    function shouldIgnore(window) {
        if (!window || window.deleted) {
            return true;
        }
        if (!window.normalWindow || !window.maximizable || window.fullScreen) {
            return true;
        }
        return window.resourceClass === "xwaylandvideobridge";
    }

    function validRect(geometry) {
        return geometry && geometry.width > 0 && geometry.height > 0;
    }

    function rectContainsPoint(geometry, point) {
        return validRect(geometry)
            && point.x >= geometry.x
            && point.x <= geometry.x + geometry.width
            && point.y >= geometry.y
            && point.y <= geometry.y + geometry.height;
    }

    function workRect(window) {
        if (!window || !window.output || window.desktops.length === 0) {
            return null;
        }
        return KWinComponents.Workspace.clientArea(
            KWinComponents.Workspace.MaximizeArea,
            window.output,
            window.desktops[0]
        );
    }

    function outsideWorkArea(window, cursor) {
        const area = workRect(window);
        if (!area) {
            return false;
        }
        const expandedArea = Qt.rect(
            area.x - floatEscapeMargin,
            area.y - floatEscapeMargin,
            area.width + floatEscapeMargin * 2,
            area.height + floatEscapeMargin * 2
        );
        return !rectContainsPoint(expandedArea, cursor);
    }

    function findTileAtCursor(cursor, ignoredWindow) {
        const ignoredKey = windowKey(ignoredWindow);
        const windows = KWinComponents.Workspace.windows;
        let bestWindow = null;
        let bestGeometry = Qt.rect(0, 0, 0, 0);
        let bestArea = 0;

        for (let index = 0; index < windows.length; index++) {
            const candidate = windows[index];
            if (shouldIgnore(candidate) || windowKey(candidate) === ignoredKey) {
                continue;
            }
            if (!ignoredWindow || !candidate.output || !ignoredWindow.output || candidate.output.name !== ignoredWindow.output.name) {
                continue;
            }

            const geometry = candidate.frameGeometry;
            if (!rectContainsPoint(geometry, cursor)) {
                continue;
            }

            const area = geometry.width * geometry.height;
            if (!bestWindow || area < bestArea) {
                bestWindow = candidate;
                bestGeometry = geometry;
                bestArea = area;
            }
        }

        return bestWindow ? bestGeometry : null;
    }

    function insetPreviewGeometry(geometry) {
        const inset = Math.max(2, Math.round(outerGap / 2));
        return Qt.rect(
            geometry.x + inset,
            geometry.y + inset,
            Math.max(1, geometry.width - inset * 2),
            Math.max(1, geometry.height - inset * 2)
        );
    }

    function showPreview(mode, geometry, screenName) {
        hidePreview();
    }

    function hidePreview() {
        previewVisible = false;
        draggingWindow = null;
        dragSourceGeometry = Qt.rect(0, 0, 0, 0);
        disableEffectTimer.restart();
    }

    function updatePreview(window, dragGeometry) {
        if (!isActive() || draggingWindow !== window || !window.output) {
            hidePreview();
            return;
        }

        const cursor = KWinComponents.Workspace.cursorPos;
        const targetGeometry = findTileAtCursor(cursor, window);
        if (targetGeometry) {
            showPreview("swap", targetGeometry, window.output.name);
            return;
        }

        if (outsideWorkArea(window, cursor)) {
            showPreview("float", validRect(dragGeometry) ? dragGeometry : window.frameGeometry, window.output.name);
            return;
        }

        showPreview("restore", dragSourceGeometry, window.output.name);
    }

    function connectDragHandlers(window) {
        const key = windowKey(window);
        if (!key || dragConnectedWindows[key]) {
            return;
        }

        dragConnectedWindows[key] = true;
        window.interactiveMoveResizeStarted.connect(function() {
            if (!isActive() || shouldIgnore(window)) {
                return;
            }
            draggingWindow = window;
            dragSourceGeometry = window.frameGeometry;
            showPreview("restore", dragSourceGeometry, window.output ? window.output.name : "");
        });
        window.interactiveMoveResizeStepped.connect(function(geometry) {
            updatePreview(window, geometry);
        });
        window.interactiveMoveResizeFinished.connect(function() {
            if (draggingWindow === window) {
                hidePreview();
            }
        });
    }

    function connectExistingWindows() {
        const windows = KWinComponents.Workspace.windows;
        for (let index = 0; index < windows.length; index++) {
            connectDragHandlers(windows[index]);
        }
    }

    function previewFillColor(mode) {
        if (mode === "float") {
            return Qt.rgba(1.0, 0.62, 0.24, 0.18);
        }
        if (mode === "restore") {
            return Qt.rgba(1.0, 1.0, 1.0, 0.10);
        }
        return Qt.rgba(0.18, 0.72, 0.66, 0.22);
    }

    function previewBorderColor(mode) {
        if (mode === "float") {
            return Qt.rgba(1.0, 0.72, 0.36, 0.72);
        }
        if (mode === "restore") {
            return Qt.rgba(1.0, 1.0, 1.0, 0.36);
        }
        return Qt.rgba(0.64, 0.90, 0.86, 0.82);
    }

    Timer {
        id: enableAnimationTimer
        interval: 1
        repeat: false
        onTriggered: effect.animatePreview = true
    }

    Timer {
        id: disableEffectTimer
        interval: effect.previewFadeDuration
        repeat: false
        onTriggered: {
            if (!effect.previewVisible) {
                effect.visible = false;
            }
        }
    }

    Connections {
        target: KWinComponents.Workspace

        function onWindowAdded(window) {
            effect.connectDragHandlers(window);
        }

        function onWindowRemoved(window) {
            const key = effect.windowKey(window);
            if (key) {
                delete effect.dragConnectedWindows[key];
            }
            if (effect.draggingWindow === window) {
                effect.hidePreview();
            }
        }
    }

    Connections {
        target: ShellSettings.Settings

        function onConvergenceModeEnabledChanged() {
            if (!effect.isActive()) {
                effect.hidePreview();
            }
        }

        function onGamingModeEnabledChanged() {
            if (!effect.isActive()) {
                effect.hidePreview();
            }
        }

        function onDynamicTilingEnabledChanged() {
            if (!effect.isActive()) {
                effect.hidePreview();
            }
        }
    }

    delegate: Rectangle {
        id: screenDelegate

        readonly property var targetScreen: KWinComponents.SceneView.screen
        readonly property bool previewOnScreen: effect.previewScreenName === targetScreen.name

        color: "transparent"

        Rectangle {
            id: previewSurface

            visible: opacity > 0
            x: effect.previewGeometry.x - screenDelegate.targetScreen.geometry.x
            y: effect.previewGeometry.y - screenDelegate.targetScreen.geometry.y
            width: effect.previewGeometry.width
            height: effect.previewGeometry.height
            radius: 14
            opacity: effect.previewVisible && screenDelegate.previewOnScreen ? 1 : 0
            color: effect.previewFillColor(effect.previewMode)
            border.width: 2
            border.color: effect.previewBorderColor(effect.previewMode)

            Behavior on x {
                enabled: effect.animatePreview
                MobileShell.MotionNumberAnimation { type: MobileShell.Motion.SpatialDefault; duration: effect.previewAnimationDuration }
            }
            Behavior on y {
                enabled: effect.animatePreview
                MobileShell.MotionNumberAnimation { type: MobileShell.Motion.SpatialDefault; duration: effect.previewAnimationDuration }
            }
            Behavior on width {
                enabled: effect.animatePreview
                MobileShell.MotionNumberAnimation { type: MobileShell.Motion.SpatialDefault; duration: effect.previewAnimationDuration }
            }
            Behavior on height {
                enabled: effect.animatePreview
                MobileShell.MotionNumberAnimation { type: MobileShell.Motion.SpatialDefault; duration: effect.previewAnimationDuration }
            }
            Behavior on opacity {
                MobileShell.MotionNumberAnimation { type: MobileShell.Motion.EffectsFast; duration: effect.previewFadeDuration }
            }
            Behavior on color {
                MobileShell.MotionColorAnimation { type: MobileShell.Motion.EffectsFast; duration: effect.previewFadeDuration }
            }
            Behavior on border.color {
                MobileShell.MotionColorAnimation { type: MobileShell.Motion.EffectsFast; duration: effect.previewFadeDuration }
            }

            Rectangle {
                anchors.fill: parent
                anchors.margins: 5
                radius: Math.max(0, parent.radius - anchors.margins)
                color: "transparent"
                border.width: 1
                border.color: Qt.rgba(1, 1, 1, 0.14)
                opacity: effect.previewMode === "insert" ? 1 : 0.45
            }
        }
    }

    Component.onCompleted: connectExistingWindows()
}