#!/usr/bin/env bash

# Every role the settings page offers must actually be settable.
#
# The service kept its own list of which roles it would accept. When the helper
# and the page grew documents, text and archives, that list stayed at seven --
# so choosing a PDF viewer set an error string and changed nothing. There is no
# crash and no log line; the row simply does not update, which reads as "the
# settings app does not work".
#
# This drives the REAL service through every role the helper reports, setting
# each to the handler it already has. Nothing on the machine changes, and the
# whole path is still exercised: the QML guard, the desktop-id lookup against
# DesktopEntries, the helper, and the refresh afterwards.

set -uo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
harness="$repo_dir/tests/quickshell/DefaultAppsHarness.qml"
helper="$repo_dir/config/dot/quickshell/scripts/panama-default-apps"
service="$repo_dir/config/dot/quickshell/services/DefaultApps.qml"
page="$repo_dir/config/dot/quickshell/modules/settings/ApplicationsPage.qml"

fail() {
    printf 'default apps roles contract: %s\n' "$1" >&2
    exit 1
}

for path in "$harness" "$helper" "$service" "$page"; do
    [[ -r "$path" ]] || fail "missing $path"
done

# ── Static: the service must not keep its own copy of the role list ─────────
grep -qE 'supportedRoles:\s*\[' "$service" \
    && fail 'the service hardcodes its own role list again; derive it from the snapshot instead'

# ── Category matching must survive a QML list ───────────────────────────────
# DesktopEntries returns a QML list for `categories`, and Array.isArray is false
# for it. Treating that as a string yields "Network,WebBrowser", which split on
# ";" alone becomes one token matching nothing -- so roles that rely on
# categories silently offered no applications to choose from, and the row could
# only ever show what it already had.
grep -q 'String(entry.categories ?? "")' "$page" \
    && fail 'category matching stringifies a QML list again; join the list instead'
grep -qE 'split\(/\[;,\]/\)' "$page" \
    || fail 'categories are not split on both ";" and "," so a stringified list still matches nothing'

# ── The page and the helper must agree ──────────────────────────────────────
helper_roles="$(python3 - "$helper" <<'PYTHON'
import ast, re, sys
source = open(sys.argv[1]).read()
table = ast.literal_eval(re.search(r"ROLE_TARGETS = (\{.*?\n\})", source, re.S).group(1))
print("\n".join(sorted(table)))
PYTHON
)" || fail 'could not read the helper roles'
page_roles="$(grep -oE 'key: "[a-z]+"' "$page" | sed 's/key: "//; s/"//' | sort -u)"
missing="$(comm -23 <(printf '%s\n' "$helper_roles") <(printf '%s\n' "$page_roles") | tr '\n' ' ')"
[[ -z "${missing// }" ]] || fail "the helper supports roles the page never offers: $missing"

# ── Live: a change made through the service must stick and be seen ──────────
command -v qs >/dev/null 2>&1 || { printf 'default apps roles contract: SKIP (no quickshell)\n'; exit 0; }
[[ -n "${WAYLAND_DISPLAY:-}" ]] || { printf 'default apps roles contract: SKIP (no Wayland session)\n'; exit 0; }
command -v jq >/dev/null 2>&1 || { printf 'default apps roles contract: SKIP (no jq)\n'; exit 0; }

# The value this test moves, captured before anything runs and restored on the
# way out no matter how the harness ends. A test that changes someone's browser
# and leaves it changed is worse than no test.
original_browser="$("$helper" snapshot | jq -r '.handlers.browser // ""')"

work="$(mktemp -d /tmp/panama-default-roles.XXXXXX)"
staged="$repo_dir/config/dot/quickshell/default-apps-roles-harness.qml"
cleanup() {
    if [[ -n "$original_browser" ]]; then
        current="$("$helper" snapshot 2>/dev/null | jq -r '.handlers.browser // ""')"
        [[ "$current" == "$original_browser" ]] \
            || "$helper" set-default browser "$original_browser" >/dev/null 2>&1
    fi
    rm -rf "$work"
    rm -f "$staged"
}
trap cleanup EXIT

# The harness has to run from inside the shell tree, or `import qs.services`
# does not resolve.
cp "$harness" "$staged"
out="$work/result.json"

PANAMA_HARNESS_OUT="$out" timeout 75 qs -p "$staged" >/dev/null 2>&1

[[ -s "$out" ]] || fail 'the harness produced no result; the service never reported a handler list'

outcome="$(jq -r '.outcome' "$out")"
case "$outcome" in
    ok) ;;
    skipped-no-second-browser)
        printf 'default apps roles contract: PASS (static only; this machine has one browser)\n'
        exit 0
        ;;
    change-not-observed)
        jq -r '.log[] | "    \(.)"' "$out" >&2
        fail 'a change made through the service never reached the settings page: the write lands and the refresh afterwards does nothing, so the row keeps showing the old application'
        ;;
    restore-not-observed)
        jq -r '.log[] | "    \(.)"' "$out" >&2
        fail 'the service did not observe the value being changed back'
        ;;
    *)
        jq -r '.log[] | "    \(.)"' "$out" >&2
        fail "the harness ended as \"$outcome\""
        ;;
esac

# And it really is back where it started.
final="$("$helper" snapshot | jq -r '.handlers.browser // ""')"
[[ "$final" == "$original_browser" ]] \
    || fail "the default browser was left as $final instead of $original_browser"

printf 'default apps roles contract: PASS (%d roles offered; a change through the service sticks and is observed)\n' \
    "$(grep -c . <<<"$helper_roles")"
