import QtQuick
import Quickshell
import Quickshell.Services.Notifications
import Quickshell.Widgets

Item {
    id: root

    property NotificationTheme theme
    property int notificationId: -1
    property var notifObject: null
    property int notifVersion: 0
    property real createdAtMs: Date.now()

    property bool interactive: true
    property bool forceExpanded: false
    property bool peekMode: false

    signal closeFinished(int notificationId, string reason)

    property bool closing: false
    property bool contextOpen: false
    property real collapseFactor: 1
    property bool hovered: hoverHandler.hovered

    readonly property string notifTitle: notifObject ? String(notifObject.summary || "") : ""
    readonly property string notifBody: notifObject ? String(notifObject.body || "") : ""
    readonly property string notifAppName: notifObject ? String(notifObject.appName || "") : ""
    readonly property string notifAppIcon: notifObject ? String(notifObject.appIcon || "") : ""
    readonly property string notifDesktopEntry: {
        if (!notifObject || !notifObject.hints)
            return "";
        const value = notifObject.hints["desktop-entry"];
        return value === undefined || value === null ? "" : String(value);
    }
    readonly property string notifImage: notifObject ? String(notifObject.image || "") : ""
    readonly property bool isExpanded: forceExpanded || contextOpen

    function resolveIconSource(iconName) {
        const fallbackName = "deltarune-soul";
        const normalized = String(iconName || "").trim();
        if (normalized.length === 0)
            return String(Quickshell.iconPath(fallbackName, true) || "");
        if (normalized.indexOf("/") >= 0 || normalized.indexOf("file://") === 0)
            return normalized;

        const primaryPath = String(Quickshell.iconPath(normalized, true) || "");
        if (primaryPath.length > 0)
            return primaryPath;

        return String(Quickshell.iconPath(fallbackName, true) || "");
    }

    function isNotifyToolName(value) {
        const normalized = String(value || "").trim().toLowerCase();
        if (normalized.length === 0)
            return false;
        return normalized === "notify-send" || normalized === "dunstify" || normalized === "notify-send.desktop" || normalized === "dunstify.desktop";
    }

    readonly property bool preferSoulFallbackIcon: isNotifyToolName(notifAppName) || isNotifyToolName(notifDesktopEntry)

    readonly property string appIconSource: {
        if (preferSoulFallbackIcon)
            return "";

        const candidates = [];
        const desktopEntry = notifDesktopEntry.trim();
        const appName = notifAppName.trim();
        const appIcon = notifAppIcon.trim();

        if (desktopEntry.length > 0) {
            candidates.push(desktopEntry);
            if (desktopEntry.endsWith(".desktop"))
                candidates.push(desktopEntry.slice(0, desktopEntry.length - 8));
        }

        if (appName.length > 0) {
            candidates.push(appName);
            const normalizedAppName = appName.toLowerCase();
            candidates.push(normalizedAppName);
            candidates.push(normalizedAppName.replace(/\s+/g, "-"));
            candidates.push(normalizedAppName.replace(/\s+/g, ""));
        }

        if (appIcon.length > 0)
            candidates.push(appIcon);

        for (let i = 0; i < candidates.length; i++) {
            const resolved = resolveIconSource(candidates[i]);
            if (resolved.length > 0)
                return resolved;
        }

        return "";
    }

    readonly property string customImageSource: notifImage
    readonly property string primaryIconSource: customImageSource.length > 0 ? customImageSource : appIconSource
    property bool primaryIconReady: false
    property bool badgeIconReady: false
    readonly property bool showBadgeIcon: customImageSource.length > 0 && appIconSource.length > 0 && badgeIconReady
    readonly property int iconBoxSize: (theme ? theme.iconBox : 28) + 12

    readonly property var notifActions: {
        if (!notifObject || !notifObject.actions)
            return [];

        const normalized = [];
        for (let i = 0; i < notifObject.actions.length; i++) {
            const actionObject = notifObject.actions[i];
            if (!actionObject)
                continue;
            const actionText = String(actionObject.text || "").trim();
            if (actionText.length === 0)
                continue;
            normalized.push({
                actionObject: actionObject,
                actionText: actionText
            });
        }
        return normalized;
    }

    readonly property int actionCount: notifActions.length
    readonly property var contextItems: {
        const items = [];
        for (let i = 0; i < notifActions.length; i++)
            items.push(notifActions[i]);
        items.push({
            actionObject: null,
            actionText: "Dismiss"
        });
        return items;
    }

    function autoCloseTimeoutMs() {
        const fallbackTimeoutMs = 3000;
        if (!notifObject)
            return fallbackTimeoutMs;

        const specifiedTimeoutMs = Number(notifObject.expireTimeout);
        if (Number.isFinite(specifiedTimeoutMs) && specifiedTimeoutMs > 0)
            return Math.max(1, Math.round(specifiedTimeoutMs));

        return fallbackTimeoutMs;
    }

    function relativeTime() {
        const elapsedSeconds = Math.max(0, Math.floor((Date.now() - createdAtMs) / 1000));
        if (elapsedSeconds < 60)
            return "now";
        if (elapsedSeconds < 3600)
            return Math.floor(elapsedSeconds / 60) + "m";
        return Math.floor(elapsedSeconds / 3600) + "h";
    }

    function beginClose(reason) {
        if (closing)
            return;
        closing = true;
        timeoutTimer.stop();
        closeReason = reason;
        closeAnimation.start();
    }

    function openContextPanel() {
        if (!interactive || closing || peekMode)
            return;
        contextOpen = !contextOpen;
        restartTimeout();
    }

    function invokeAction(actionObject) {
        if (closing)
            return;
        if (actionObject)
            actionObject.invoke();
        beginClose(actionObject ? "click" : "dismiss");
    }

    function restartTimeout() {
        timeoutTimer.stop();
        if (closing || isExpanded || peekMode || hovered)
            return;

        const timeoutMs = autoCloseTimeoutMs();
        if (!(timeoutMs > 0))
            return;

        timeoutTimer.interval = timeoutMs;
        timeoutTimer.start();
    }

    property string closeReason: ""
    implicitWidth: theme ? theme.stackWidth : 420
    implicitHeight: peekMode ? (theme ? theme.stackPeekHeight : 36) : Math.max(1, Math.round((contentColumn.implicitHeight + ((theme ? theme.cardPadding : 14) * 2)) * collapseFactor))
    width: implicitWidth
    height: implicitHeight
    opacity: 1

    SequentialAnimation {
        id: enterAnimation
        running: !peekMode

        PropertyAction {
            target: root
            property: "opacity"
            value: 0
        }
        PropertyAction {
            target: root
            property: "x"
            value: 20
        }
        ParallelAnimation {
            NumberAnimation {
                target: root
                property: "opacity"
                to: 1
                duration: theme ? theme.enterMs : 170
                easing.type: Easing.OutCubic
            }
            NumberAnimation {
                target: root
                property: "x"
                to: 0
                duration: theme ? theme.enterMs : 170
                easing.type: Easing.OutCubic
            }
        }
    }

    ParallelAnimation {
        id: closeAnimation
        NumberAnimation {
            target: root
            property: "opacity"
            to: 0
            duration: theme ? theme.fadeMs : 120
            easing.type: Easing.InCubic
        }
        NumberAnimation {
            target: root
            property: "x"
            to: width
            duration: theme ? theme.exitMs : 140
            easing.type: Easing.InCubic
        }
        onFinished: root.closeFinished(root.notificationId, root.closeReason)
    }

    Timer {
        id: timeoutTimer
        interval: root.autoCloseTimeoutMs()
        repeat: false
        running: false
        onTriggered: root.beginClose("timeout")
    }

    Rectangle {
        anchors.fill: parent
        color: theme ? theme.panelBackground : "#000000"
        border.width: theme ? theme.borderWidth : 3
        border.color: theme ? theme.panelBorder : "#ffffff"
        radius: 0
        antialiasing: false
        visible: !peekMode
    }

    Rectangle {
        anchors.fill: parent
        color: theme ? theme.panelBackground : "#000000"
        border.width: theme ? theme.borderWidth : 3
        border.color: theme ? theme.panelBorder : "#ffffff"
        radius: 0
        antialiasing: false
        visible: peekMode
        opacity: 0.95
    }

    Column {
        id: contentColumn
        visible: !peekMode
        x: (theme ? theme.cardPadding : 14)
        y: (theme ? theme.cardPadding : 14)
        width: root.width - ((theme ? theme.cardPadding : 14) * 2)
        spacing: theme ? theme.lineGap : 2

        Row {
            id: mainRow
            width: parent.width
            spacing: theme ? theme.contentGap : 10
            anchors.left: parent.left
            anchors.right: parent.right

            Item {
                width: root.iconBoxSize
                height: root.iconBoxSize

                IconImage {
                    anchors.fill: parent
                    source: root.primaryIconSource
                    visible: root.primaryIconReady
                    asynchronous: true
                    onSourceChanged: root.primaryIconReady = false
                    onStatusChanged: root.primaryIconReady = status === Image.Ready
                }

                Rectangle {
                    anchors.fill: parent
                    visible: !root.primaryIconReady
                    color: "#000000"
                    border.width: 0
                    border.color: "transparent"
                    radius: 0
                    antialiasing: false
                }

                Image {
                    anchors.centerIn: parent
                    width: parent.width - 2
                    height: parent.height - 2
                    source: "../Topbar/topbar/soul_small.png"
                    visible: !root.primaryIconReady
                    smooth: false
                    antialiasing: false
                    fillMode: Image.PreserveAspectFit
                }

                IconImage {
                    visible: root.showBadgeIcon
                    width: Math.max(12, Math.round(parent.width * 0.42))
                    height: Math.max(12, Math.round(parent.height * 0.42))
                    source: root.appIconSource
                    x: parent.width - width
                    y: parent.height - height
                    asynchronous: true
                    onSourceChanged: root.badgeIconReady = false
                    onStatusChanged: root.badgeIconReady = status === Image.Ready
                }
            }

            Column {
                id: textColumn
                width: parent.width - root.iconBoxSize - (theme ? theme.contentGap : 10)
                spacing: theme ? theme.lineGap : 2

                Row {
                    width: parent.width

                    Text {
                        width: parent.width - timeLabel.implicitWidth - 6
                        text: root.notifTitle
                        color: theme ? theme.panelText : "#ffffff"
                        elide: Text.ElideRight
                        maximumLineCount: 1
                        font.family: theme ? theme.fontFamily : "8bitoperator JVE"
                        font.pixelSize: 24
                        font.letterSpacing: theme ? theme.fontLetterSpacing : 1
                        renderType: Text.NativeRendering
                        font.hintingPreference: Font.PreferNoHinting
                        smooth: false
                        antialiasing: false
                    }

                    Text {
                        id: timeLabel
                        text: root.relativeTime()
                        color: theme ? theme.panelText : "#ffffff"
                        font.family: theme ? theme.fontFamily : "8bitoperator JVE"
                        font.pixelSize: 16
                        font.letterSpacing: theme ? theme.fontLetterSpacing : 1
                        renderType: Text.NativeRendering
                        font.hintingPreference: Font.PreferNoHinting
                        smooth: false
                        antialiasing: false
                    }
                }

                Text {
                    width: parent.width
                    text: root.notifBody
                    color: theme ? theme.panelText : "#ffffff"
                    wrapMode: Text.Wrap
                    maximumLineCount: root.isExpanded ? (theme ? theme.expandedBodyLines : 8) : (theme ? theme.collapsedBodyLines : 2)
                    elide: Text.ElideRight
                    font.family: theme ? theme.fontFamily : "8bitoperator JVE"
                    font.pixelSize: 22
                    font.letterSpacing: theme ? theme.fontLetterSpacing : 1
                    renderType: Text.NativeRendering
                    font.hintingPreference: Font.PreferNoHinting
                    smooth: false
                    antialiasing: false
                }
            }
        }

        Column {
            visible: root.contextOpen
            width: parent.width
            spacing: theme ? theme.actionGap : 8

            Repeater {
                model: root.contextItems

                Rectangle {
                    width: parent.width
                    height: theme ? theme.actionHeight : 34
                    color: actionHover.hovered ? (theme ? theme.hoverInvertBackground : "#ffffff") : (theme ? theme.panelBackground : "#000000")
                    border.width: theme ? theme.actionBorderWidth : 2
                    border.color: theme ? theme.panelBorder : "#ffffff"
                    radius: 0
                    antialiasing: false

                    HoverHandler {
                        id: actionHover
                    }

                    Text {
                        anchors.centerIn: parent
                        text: modelData.actionText
                        color: actionHover.hovered ? (theme ? theme.hoverInvertText : "#000000") : (theme ? theme.panelText : "#ffffff")
                        font.family: theme ? theme.fontFamily : "8bitoperator JVE"
                        font.pixelSize: 18
                        font.letterSpacing: theme ? theme.fontLetterSpacing : 1
                        renderType: Text.NativeRendering
                        font.hintingPreference: Font.PreferNoHinting
                        smooth: false
                        antialiasing: false
                    }

                    TapHandler {
                        acceptedButtons: Qt.LeftButton
                        enabled: root.interactive && !root.closing
                        onTapped: root.invokeAction(modelData.actionObject)
                    }
                }
            }
        }
    }

    DragHandler {
        id: swipeHandler
        enabled: root.interactive && !root.peekMode && !root.closing
        xAxis.enabled: true
        yAxis.enabled: false

        onTranslationChanged: {
            root.x = Math.max(0, translation.x);
        }

        onActiveChanged: {
            if (active)
                return;

            const threshold = theme ? theme.dragDismissThreshold : 140;
            if (root.x >= threshold) {
                root.beginClose("drag");
                return;
            }
            snapBackAnimation.restart();
        }
    }

    NumberAnimation {
        id: snapBackAnimation
        target: root
        property: "x"
        to: 0
        duration: theme ? theme.expandMs : 120
        easing.type: Easing.OutCubic
    }

    TapHandler {
        acceptedButtons: Qt.LeftButton
        enabled: root.interactive && !root.peekMode && !root.closing
        onTapped: root.openContextPanel()
    }

    TapHandler {
        acceptedButtons: Qt.RightButton
        enabled: root.interactive && !root.peekMode && !root.closing
        onTapped: root.beginClose("dismiss")
    }

    HoverHandler {
        id: hoverHandler
    }

    onNotifObjectChanged: restartTimeout()
    onNotifVersionChanged: restartTimeout()
    onContextOpenChanged: {
        if (!contextOpen)
            restartTimeout();
        else
            timeoutTimer.stop();
    }
    onHoveredChanged: {
        if (hovered)
            timeoutTimer.stop();
        else
            restartTimeout();
    }
    onForceExpandedChanged: {
        if (forceExpanded)
            timeoutTimer.stop();
        else
            restartTimeout();
    }
    Component.onCompleted: restartTimeout()
}
