import Quickshell
import Quickshell.Io
import QtQuick

import qs.services

// Does a change made through the service actually stick, and does the service
// SEE that it stuck?
//
// The first version of this harness set every role to the handler it already
// had and asserted no error appeared. It passed while the feature was broken:
// the write landed, the refresh afterwards silently no-oped, and the settings
// page kept showing the old value with no error anywhere. "No error" is not
// evidence of anything -- the value has to move.
//
// So this changes one role to a genuinely different application, waits for the
// service to report the new value, and changes it back. The contract restores
// the original from the outside regardless of how this ends.
ShellRoot {
    id: root

    readonly property string role: "browser"

    property string original: ""
    property string target: ""
    property int phase: 0
    property var log: []
    property bool done: false

    function desktopId(entry) {
        const id = String(entry?.id ?? "");
        return id.endsWith(".desktop") ? id : id + ".desktop";
    }

    function isBrowser(entry) {
        // DesktopEntries returns a QML list, for which Array.isArray is false;
        // joining first and splitting on both separators handles either form.
        const raw = entry.categories;
        const joined = Array.isArray(raw) ? raw.join(";") : String(raw ?? "");
        for (const value of joined.split(/[;,]/)) {
            if (value.trim().toLowerCase() === "webbrowser")
                return true;
        }
        return false;
    }

    function finish(outcome) {
        if (root.done)
            return;
        root.done = true;
        Quickshell.execDetached(["sh", "-c",
            "printf '%s' " + JSON.stringify(JSON.stringify({
                outcome: outcome,
                original: root.original,
                target: root.target,
                log: root.log,
                error: String(DefaultApps.lastError)
            })) + " > " + Quickshell.env("PANAMA_HARNESS_OUT")]);
        Qt.callLater(() => Qt.quit());
    }

    Component.onCompleted: DefaultApps.refresh()

    Timer {
        interval: 300
        repeat: true
        running: !root.done
        onTriggered: {
            const apps = DesktopEntries.applications.values;
            const handlers = DefaultApps.handlers ?? ({});
            // DesktopEntries populates asynchronously; the service checks a
            // desktop id against it, so starting early fails for the wrong
            // reason.
            if (apps.length === 0 || Object.keys(handlers).length === 0)
                return;
            if (DefaultApps.busy)
                return;

            const current = String(handlers[root.role] ?? "");

            if (root.phase === 0) {
                root.original = current;
                const other = apps.filter(entry => root.isBrowser(entry)
                                                && root.desktopId(entry) !== current);
                if (other.length === 0) {
                    root.finish("skipped-no-second-browser");
                    return;
                }
                root.target = root.desktopId(other[0]);
                root.log = root.log.concat(["start at " + current]);
                root.phase = 1;
                DefaultApps.setDefault(root.role, root.target);
                return;
            }

            if (root.phase === 1) {
                if (current !== root.target) {
                    root.log = root.log.concat(["after change the service still reports " + current]);
                    root.phase = 4;
                    // Put it back before reporting the failure.
                    DefaultApps.setDefault(root.role, root.original);
                    return;
                }
                root.log = root.log.concat(["service observed " + current]);
                root.phase = 2;
                DefaultApps.setDefault(root.role, root.original);
                return;
            }

            if (root.phase === 2) {
                if (current !== root.original) {
                    root.log = root.log.concat(["restore not observed, service reports " + current]);
                    root.finish("restore-not-observed");
                    return;
                }
                root.log = root.log.concat(["restored to " + current]);
                root.finish("ok");
                return;
            }

            if (root.phase === 4) {
                root.finish("change-not-observed");
                return;
            }
        }
    }

    // A stuck harness fails rather than hangs.
    Timer {
        interval: 40000
        running: true
        onTriggered: root.finish("timeout")
    }
}
