// A single live window preview inside a workspace card, with the app icon and
// title underneath — GNOME's overview window chrome.
//
// Capture is deliberately one-shot (`live: false` + an explicit captureFrame()
// on open). Streaming every window continuously would repaint the whole
// overview forever, which is exactly the idle-repaint cost this shell avoids.

import Quickshell
import Quickshell.Wayland
import Quickshell.Widgets
import QtQuick
import qs.config

Item {
    id: root

    required property var toplevel // HyprlandToplevel

    // Bumped by Overview each time it opens; each bump re-captures.
    property int refreshToken: 0
    property bool selected: false

    signal activated
    signal closeRequested
    signal dragStarted
    signal dragFinished

    property real dragStartX: 0
    property real dragStartY: 0

    z: dragHandler.active ? 100 : 0
    opacity: dragHandler.active ? 0.88 : 1
    Drag.active: dragHandler.active
    Drag.hotSpot.x: width / 2
    Drag.hotSpot.y: height / 2
    Drag.supportedActions: Qt.MoveAction

    // Null until Hyprland reports the address — and always null on compositors
    // without wlr-screencopy, which is why every use of it is guarded.
    readonly property var source: toplevel ? toplevel.wayland : null
    readonly property string title: toplevel && toplevel.title ? toplevel.title : ""
    readonly property string appId: source && source.appId ? source.appId : ""
    // The `applications` read is deliberate: the entry scan finishes
    // asynchronously, and heuristicLookup() alone creates no binding dependency.
    readonly property var entry: appId && DesktopEntries.applications.values.length > 0 ? DesktopEntries.heuristicLookup(appId) : null

    // "" when nothing resolves — a missing icon is left blank rather than shown
    // as the theme's broken-icon placeholder.
    readonly property string iconSource: {
        const name = entry && entry.icon ? entry.icon : appId;
        if (!name)
            return "";
        if (name.startsWith("/"))
            return "file://" + name;
        return Quickshell.iconPath(name, true);
    }

    onRefreshTokenChanged: root.recapture()

    function recapture(): void {
        if (shotLoader.item)
            shotLoader.item.tryCapture();
    }

    Rectangle {
        id: preview
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.top: parent.top
        anchors.bottom: caption.top
        anchors.bottomMargin: 4

        radius: Theme.cardRadius
        border.width: 0 // QTBUG-137166
        color: Theme.alpha(Theme.bg, 0.6)
        clip: true

        scale: dragHandler.active ? 1.04 : (hover.hovered ? 1.025 : 1)

        Behavior on scale {
            NumberAnimation {
                duration: Theme.durFast
                easing.type: Easing.OutCubic
            }
        }

        // Shown until (or unless) a frame arrives. On a compositor without
        // screencopy this is all the user ever sees, rather than an error.
        IconImage {
            anchors.centerIn: parent
            visible: root.iconSource !== ""
            source: root.iconSource
            implicitSize: Math.max(24, Math.min(64, Math.min(preview.width, preview.height) * 0.4))
            asynchronous: true
            mipmap: true
            opacity: shotLoader.hasFrame ? 0 : 1

            Behavior on opacity {
                NumberAnimation {
                    duration: Theme.durFast
                }
            }
        }

        Loader {
            id: shotLoader
            anchors.fill: parent

            readonly property bool hasFrame: item ? item.hasContent : false

            // Only instantiate a capture when there is something to capture AND
            // the overview has been opened at least once.
            //
            // refreshToken is incremented by Overview.qml at the moment it maps,
            // so gating on it guarantees the ScreencopyView is created while the
            // window is actually mapped. Creating it earlier makes the initial
            // captureFrame() fail with "no recording context is ready", because
            // an unmapped layer surface has no capture context yet.
            active: !!root.source && root.refreshToken > 0
            sourceComponent: shotComponent
        }

        Rectangle {
            anchors.fill: parent
            visible: root.selected
            radius: parent.radius
            color: "transparent"
            border.width: 2
            border.color: Theme.accent
            z: 3
        }

        Rectangle {
            id: closeButton
            anchors.top: parent.top
            anchors.right: parent.right
            anchors.margins: 7
            visible: hover.hovered && !dragHandler.active
            width: 26
            height: 26
            radius: 13
            z: 4
            border.width: 1
            border.color: Theme.alpha(Theme.fg, 0.1)
            color: closeHover.hovered ? Theme.alpha(Theme.danger, 0.28) : Theme.alpha(Theme.bgDark, 0.82)

            Text {
                anchors.centerIn: parent
                text: "\u{F0156}"
                color: closeHover.hovered ? Theme.danger : Theme.fg
                font.family: Theme.fontMono
                font.pixelSize: 13
            }

            HoverHandler { id: closeHover }
            TapHandler {
                onTapped: root.closeRequested()
            }
        }
    }

    Component {
        id: shotComponent

        ScreencopyView {
            id: shot
            captureSource: root.source
            live: false
            paintCursor: false
            constraintSize: Qt.size(preview.width, preview.height)

            // Fit inside the preview without distorting the window's shape.
            readonly property real aspect: sourceSize.height > 0 ? sourceSize.width / sourceSize.height : 16 / 9
            anchors.centerIn: parent
            width: Math.min(parent.width, parent.height * aspect)
            height: aspect > 0 ? width / aspect : parent.height
            opacity: hasContent ? 1 : 0

            // The capture context isn't ready the instant the layer surface is
            // told to become visible — it needs the surface to actually map,
            // which takes a frame or two. Calling captureFrame() before then
            // logs "no recording context is ready" and yields nothing, so
            // retry a few times and then give up quietly (the caption and app
            // icon are still shown, so a missing thumbnail is cosmetic).
            property int captureAttempts: 0

            function tryCapture(): void {
                captureAttempts = 0;
                captureRetry.restart();
            }

            // `shot`, not `parent`: Timer is a QtObject, so `parent` does not
            // resolve to the enclosing ScreencopyView.
            Timer {
                id: captureRetry
                interval: 80
                repeat: true
                running: false
                onTriggered: {
                    if (shot.hasContent || shot.captureAttempts >= 6) {
                        stop();
                        return;
                    }
                    shot.captureAttempts++;
                    shot.captureFrame();
                }
            }

            Component.onCompleted: tryCapture()
        }
    }

    Row {
        id: caption
        anchors.bottom: parent.bottom
        anchors.horizontalCenter: parent.horizontalCenter
        spacing: 6
        height: 18

        IconImage {
            anchors.verticalCenter: parent.verticalCenter
            visible: root.iconSource !== ""
            width: visible ? 16 : 0
            height: 16
            source: root.iconSource
            asynchronous: true
            mipmap: true
        }

        Text {
            anchors.verticalCenter: parent.verticalCenter
            // Clamped against the thumbnail rather than the Row: sizing against
            // the Row would make its width depend on its own implicitWidth.
            width: Math.min(implicitWidth, Math.max(0, root.width - 22))
            text: root.title
            elide: Text.ElideRight
            color: Theme.fgDim
            font.family: Theme.fontFamily
            font.pixelSize: Theme.fontSizeSmall
        }
    }

    HoverHandler {
        id: hover
    }

    TapHandler {
        onTapped: root.activated()
    }

    DragHandler {
        id: dragHandler
        target: root
        cursorShape: Qt.DragMoveCursor

        onActiveChanged: {
            if (active) {
                root.dragStartX = root.x;
                root.dragStartY = root.y;
                root.dragStarted();
            } else {
                root.Drag.drop();
                root.x = root.dragStartX;
                root.y = root.dragStartY;
                root.dragFinished();
            }
        }
    }
}
