#!/usr/bin/env bash

set -euo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
harness="$repo_dir/config/dot/quickshell/home-preferences-harness.qml"
preferences="$repo_dir/config/dot/quickshell/config/HomePreferences.qml"
state_home="$(mktemp -d /tmp/panama-home-preferences-state.XXXXXX)"

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

qs_for_harness() {
    XDG_STATE_HOME="$state_home" qs -p "$harness" "$@"
}

cleanup() {
    qs_for_harness kill >/dev/null 2>&1 || true
    rm -rf "$state_home"
}
trap cleanup EXIT

start_harness() {
    qs_for_harness --daemonize >/dev/null
    for _ in $(seq 1 40); do
        if qs_for_harness ipc show 2>/dev/null | rg -q '^target home-pref-test$'; then
            # Construct the lazy singleton and let its explicit reload finish
            # before issuing mutations through the IPC boundary.
            qs_for_harness ipc call home-pref-test status >/dev/null
            sleep 0.2
            return
        fi
        sleep 0.1
    done
    fail 'test IPC target did not start'
}

stop_harness() {
    qs_for_harness kill >/dev/null 2>&1 || true
    for _ in $(seq 1 40); do
        if ! qs_for_harness ipc show >/dev/null 2>&1; then
            return
        fi
        sleep 0.1
    done
    fail 'test shell did not stop cleanly'
}

status_without_state_dir() {
    qs_for_harness ipc call home-pref-test status | jq -c 'del(.stateDir)'
}

wait_for_status() {
    local expected="$1"
    local actual=""

    for _ in $(seq 1 40); do
        actual="$(status_without_state_dir)"
        if jq -e --argjson expected "$expected" \
            '.initialized == $expected.initialized and .favorites == $expected.favorites and .saveError == $expected.saveError' \
            <<<"$actual" >/dev/null; then
            return
        fi
        sleep 0.1
    done
    fail "unexpected status: $actual"
}

wait_for_file_content() {
    local expected="$1"

    for _ in $(seq 1 40); do
        state_file="$(find "$state_home" -name panama-home.json -print -quit)"
        if [[ -n "$state_file" ]] \
            && jq -e --argjson expected "$expected" '. == $expected' "$state_file" >/dev/null; then
            return
        fi
        sleep 0.1
    done
    fail 'preferences file did not contain the complete atomic update'
}

assert_reset_persists_without_debounce() {
    local expected=$'    function resetHomeDefaults(): void {\n        persistTimer.stop();\n        values.favorites = [];\n        values.initialized = false;\n        root.saveError = "";\n        preferencesFile.writeAdapter();\n    }'
    local actual

    actual="$(sed -n '/^    function resetHomeDefaults(): void {$/,/^    }$/p' "$preferences")"
    [[ "$actual" == "$expected" ]] \
        || fail 'resetHomeDefaults must stop debounce before directly writing the default state'
}

# A leading JSON whitespace prevents qs from expanding the array into IPC
# positional arguments; JSON.parse() intentionally accepts that whitespace.
initial_ids=' ["light.kitchen","light.hall","light.desk"]'
expected='{"initialized":true,"favorites":[{"id":"light.desk","alias":""},{"id":"light.kitchen","alias":"Island"}],"saveError":""}'
expected_file='{"initialized":true,"favorites":[{"id":"light.desk","alias":""},{"id":"light.kitchen","alias":"Island"}]}'
reordered_expected='{"initialized":true,"favorites":[{"id":"light.desk","alias":""},{"id":"light.kitchen","alias":"Island"},{"id":"light.hall","alias":""}],"saveError":""}'
reordered_file='{"initialized":true,"favorites":[{"id":"light.desk","alias":""},{"id":"light.kitchen","alias":"Island"},{"id":"light.hall","alias":""}]}'
empty_expected='{"initialized":true,"favorites":[],"saveError":""}'
empty_file='{"initialized":true,"favorites":[]}'
reset_expected='{"initialized":false,"favorites":[],"saveError":""}'
reset_file='{"initialized":false,"favorites":[]}'

assert_reset_persists_without_debounce
start_harness
qs_for_harness ipc call home-pref-test initialize "$initial_ids" >/dev/null
qs_for_harness ipc call home-pref-test alias light.kitchen '  Island  ' >/dev/null
qs_for_harness ipc call home-pref-test move light.desk 0 >/dev/null
wait_for_status "$reordered_expected"
wait_for_file_content "$reordered_file"
stop_harness
start_harness
wait_for_status "$reordered_expected"

qs_for_harness ipc call home-pref-test remove light.hall >/dev/null
wait_for_status "$expected"
wait_for_file_content "$expected_file"

qs_for_harness ipc call home-pref-test reset >/dev/null
wait_for_status "$reset_expected"
wait_for_file_content "$reset_file"

stop_harness
start_harness
wait_for_status "$reset_expected"

qs_for_harness ipc call home-pref-test initialize "$initial_ids" >/dev/null
qs_for_harness ipc call home-pref-test alias light.kitchen '  Island  ' >/dev/null
qs_for_harness ipc call home-pref-test move light.desk 0 >/dev/null
qs_for_harness ipc call home-pref-test remove light.hall >/dev/null
wait_for_status "$expected"
wait_for_file_content "$expected_file"

qs_for_harness ipc call home-pref-test remove light.desk >/dev/null
qs_for_harness ipc call home-pref-test remove light.kitchen >/dev/null
wait_for_status "$empty_expected"
wait_for_file_content "$empty_file"
stop_harness
start_harness
wait_for_status "$empty_expected"
qs_for_harness ipc call home-pref-test initialize ' ["light.new","light.other"]' >/dev/null
wait_for_status "$empty_expected"

jq -e '(keys | sort) == ["favorites", "initialized"]' "$state_file" >/dev/null \
    || fail 'preferences file contains keys other than initialized and favorites'
if jq -r '.. | strings' "$state_file" | rg -qi 'token|url|api'; then
    fail 'preferences file contains credential-like data'
fi

trap - EXIT
cleanup
printf 'home preferences contract: PASS\n'
