import QtQuick
import QtQml
import Quickshell
import Quickshell.Io
import qs.Common
import qs.Modules.Plugins

DesktopPluginComponent {
    id: root

    property string command: pluginData.command ?? ""
    property real refreshInterval: normalizeRefreshInterval(pluginData.refreshInterval)
    property bool autoRefresh: pluginData.autoRefresh ?? false
    property real commandTimeout: normalizeCommandTimeout(pluginData.commandTimeout) // seconds
    property bool hasRunInitial: false
    property string output: ""
    property int rows: 0
    property int cols: 0
    property var windowRef: null
    property int fontSizePx: normalizeFontSize(pluginData.fontSize)
    property bool useDank16: (pluginData.useDank16 ?? true) && Theme.dank16 !== null
    property real backgroundOpacity: (pluginData.backgroundOpacity ?? 50) / 100
    property string pluginUrl: ""
    property string pluginDir: ""
    property string wrapCommandPath: ""
    property var dank16: Theme.isLightMode ? Theme.dank16.light : Theme.dank16.dark

    FontMetrics {
        id: fontMetrics
        font.pixelSize: root.fontSizePx
        font.family: Theme.monoFontFamily
    }

    Timer {
        id: timer
        interval: root.refreshInterval
        repeat: true
        running: false
        onTriggered: runCommand()
    }

    // workaround for widget being spawned with weird size initially
    Timer {
        id: initialRunTimer
        interval: 1000
        repeat: false
        running: false
        onTriggered: root.handleVisibilityChange("timer")
    }

    Component.onCompleted: {
        root.windowRef = Window.window ?? null
        root.handleVisibilityChange("completed")
        const url = Qt.resolvedUrl("Widget.qml") || (typeof __qmlfile__ !== "undefined" ? __qmlfile__ : "")
        const cleanedUrl = String(url ?? "")
        const cleanedPath = cleanedUrl.startsWith("file://") ? cleanedUrl.slice("file://".length) : cleanedUrl
        const lastSlash = cleanedPath.lastIndexOf("/")
        root.pluginUrl = cleanedUrl
        root.pluginDir = lastSlash !== -1 ? cleanedPath.slice(0, lastSlash) : ""
        const resolvedWrapUrl = Qt.resolvedUrl("wrapCommand")
        const resolvedWrap = String(resolvedWrapUrl ?? "")
        root.wrapCommandPath = resolvedWrap
            ? resolvedWrap.replace(/^file:\/\//, "")
            : (root.pluginDir ? `${root.pluginDir}/wrapCommand` : "wrapCommand")
    }

    onVisibleChanged: {
        root.handleVisibilityChange("root.visible")
    }

    onWidgetWidthChanged: root.handleVisibilityChange("sizeChanged")
    onWidgetHeightChanged: root.handleVisibilityChange("sizeChanged")

    Component.onDestruction: {
        root.stopAllActivity("destruction")
    }

    onCommandChanged: {
        handleVisibilityChange("commandChanged")
    }

    onAutoRefreshChanged: {
        timer.running = root.autoRefresh && root.isRunnable()
        if (root.autoRefresh && root.hasRunInitial && root.isRunnable()) {
            timer.restart()
        }
    }

    onRefreshIntervalChanged: {
        if (timer.running) {
            timer.restart()
        }
    }

    function normalizeRefreshInterval(value) {
        const parsed = Number(value)
        if (!isFinite(parsed) || parsed <= 0) {
            return 60000
        }
        return parsed * 1000
    }

    function normalizeCommandTimeout(value) {
        const parsed = Number(value)
        if (!isFinite(parsed) || parsed <= 0) {
            return 5
        }
        return parsed
    }

    function normalizeFontSize(value) {
        const parsed = parseInt(value, 10)
        if (!isFinite(parsed) || parsed <= 0) {
            return Theme.fontSizeSmall
        }
        return parsed
    }

    function isRunnable() {
        const win = root.windowRef
        const winVisible = win === null ? true : !!win.visible

        // in other weird cases, it will just start on timer with 2s delay

        return root.visible && winVisible && root.widgetWidth > 0 && root.widgetHeight > 0
    }

    function isStartingEdgeCase(){
        if (root.widgetWidth == 500 || root.widgetWidth == 200) {
            initialRunTimer.start()
            return true
        }

        if (root.widgetHeight == 500 || root.widgetHeight == 200) {
            initialRunTimer.start()
            return true
        }

        return false
    }

    function handleVisibilityChange(source) {
        if(source == "commandChanged" || source == "root.visible"){
            root.hasRunInitial = false
        }
        if (!root.isRunnable()) {
            root.stopAllActivity(source)
            return
        }
        if (!root.hasRunInitial) {
            if(root.isStartingEdgeCase() && source != "timer"){
                return
            }
            root.hasRunInitial = true
            initialRunTimer.stop()
            runCommand()
        }
        if (root.autoRefresh) {
            timer.start()
        }
    }

    function runCommand() {
        if (!root.isRunnable()) {
            console.warn(`[desktopCommand] runCommand skipped; not runnable (visible=${root.visible} winVisible=${root.windowRef ? root.windowRef.visible : "n/a"}`)
            return
        }
        if (process.running) {
            console.warn(`[desktopCommand] runCommand skipped; process already running; command="${root.command}"`)
            return
        }
        root.updateTerminalSize()

        let command = `"${root.wrapCommandPath}" --width=${root.cols} --height=${root.rows} --timeout=${root.commandTimeout} `
        if (root.useDank16) {
            command += `--colors='${JSON.stringify(root.dank16)}' `
        }
        command += `-- ${JSON.stringify(root.command)}`

        process.command = ["sh", "-c", command]
        process.running = true
    }

    function updateTerminalSize() {
        const horizontalMargin = 0
        const verticalMargin = 8
        const availableWidth = Math.max(200, (root.widgetWidth ?? root.width) - horizontalMargin)
        const availableHeight = Math.max(200, (root.widgetHeight ?? root.height) - verticalMargin)

        root.cols = Math.max(1, Math.floor(availableWidth / Math.max(1, fontMetrics.averageCharacterWidth)))
        root.rows = Math.max(1, Math.floor(availableHeight / Math.max(1, fontMetrics.lineSpacing)))
    }

    function stopAllActivity(reason) {
        timer.stop()
        process.running = false
        root.output = ""
    }

    Process {
        id: process

        stdout: StdioCollector {
            onStreamFinished: {
                root.output = this.text
            }
        }
    }

    Rectangle {
        anchors.fill: parent
        radius: Theme.cornerRadius
        color: Theme.withAlpha(Theme.surfaceContainer, root.backgroundOpacity)
        visible: root.visible

        Text {
            anchors.fill: parent
            anchors.margins: 8
            text: root.output
            textFormat: Text.RichText
            wrapMode: Text.NoWrap
            color: useDank16? Theme.surfaceText : "#c0c0c0"
            font.pixelSize: root.fontSizePx
            font.family: Theme.monoFontFamily
            horizontalAlignment: Text.AlignLeft
            verticalAlignment: Text.AlignTop
        }
    }
}
