#!/usr/bin/env bash

# The preference store is derived from config/PreferenceSchema.qml rather than
# restating each key. This contract pins the properties that derivation is
# supposed to buy, so that a future change cannot quietly reintroduce the
# hand-maintained variant:
#
#   * every schema key round-trips through disk
#   * out-of-range numbers are clamped, not stored raw and not rejected
#   * unknown keys in the file are preserved across a write
#   * set() refuses a key that is not in the schema
#   * reset() restores *every* schema default with no hand-written list
#   * a corrupt file yields shipped defaults rather than a broken shell

set -euo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
harness="$repo_dir/config/dot/quickshell/preference-schema-harness.qml"
state_home="$(mktemp -d /tmp/panama-schema-state.XXXXXX)"
config_home="$(mktemp -d /tmp/panama-schema-config.XXXXXX)"
store="$config_home/panama/settings.json"

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

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

cleanup() {
    qs_for_harness kill >/dev/null 2>&1 || true
}
trap cleanup EXIT

start_harness() {
    XDG_STATE_HOME="$state_home" XDG_CONFIG_HOME="$config_home" qs -p "$harness" --daemonize >/dev/null
    for _ in $(seq 1 40); do
        if qs_for_harness ipc show 2>/dev/null | rg -q '^target preference-schema-test$'; then
            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
        # A bare `return` would propagate the failed `ipc show` status, which
        # under `set -e` ends the whole contract instead of the function.
        qs_for_harness ipc show >/dev/null 2>&1 || return 0
        sleep 0.1
    done
    fail 'test shell did not stop cleanly'
}

wait_for_store() {
    for _ in $(seq 1 40); do
        [[ -f "$store" ]] && jq -e . "$store" >/dev/null 2>&1 && return
        sleep 0.1
    done
    fail 'preferences file was not written'
}

# ── A fresh store reports schema defaults ────────────────────────────────────
start_harness
key_count="$(qs_for_harness ipc call preference-schema-test keyCount)"
[[ "$key_count" -gt 0 ]] || fail 'schema is empty'

initial="$(qs_for_harness ipc call preference-schema-test dump | jq -cS .)"
defaults="$(qs_for_harness ipc call preference-schema-test defaults | jq -cS .)"
[[ "$initial" == "$defaults" ]] || fail "a fresh store did not report schema defaults: $initial"

# ── Out-of-range numbers are clamped, not stored raw ─────────────────────────
qs_for_harness ipc call preference-schema-test applyJson \
    '{"dockHideDelayMs": 99999, "nightLightTemperature": 100, "focusDurationMinutes": 45}' >/dev/null
clamped="$(qs_for_harness ipc call preference-schema-test dump)"
[[ "$(jq -r .dockHideDelayMs <<<"$clamped")" == "2000" ]] \
    || fail "an above-range value was not clamped to the schema maximum: $(jq -r .dockHideDelayMs <<<"$clamped")"
[[ "$(jq -r .nightLightTemperature <<<"$clamped")" == "2000" ]] \
    || fail "a below-range value was not clamped to the schema minimum: $(jq -r .nightLightTemperature <<<"$clamped")"

# ── An unknown key is refused rather than silently accepted ──────────────────
verdict="$(qs_for_harness ipc call preference-schema-test applyJson '{"__not_a_setting__": 1}')"
[[ "$(jq -r .__not_a_setting__ <<<"$verdict")" == "false" ]] || fail 'set() accepted a key outside the schema'
qs_for_harness ipc call preference-schema-test dump | jq -e 'has("__not_a_setting__") | not' >/dev/null \
    || fail 'a key outside the schema entered the store'

# ── An out-of-range enum value is refused ────────────────────────────────────
verdict="$(qs_for_harness ipc call preference-schema-test applyJson '{"vrrPolicy": 7}')"
[[ "$(jq -r .vrrPolicy <<<"$verdict")" == "false" ]] || fail 'set() accepted an enum value outside its options'

# ── Every schema key round-trips across a restart ────────────────────────────
qs_for_harness ipc call preference-schema-test applyJson \
    '{"use24Hour": true, "showSeconds": false, "showWeekday": false, "showCpu": false,
      "showMemory": false, "showGpu": false, "dockAutohide": false, "dockRevealDelayMs": 75,
      "dockHideDelayMs": 400, "focusDurationMinutes": 25, "autoHdr": false, "vrrPolicy": 0,
      "directScanoutPolicy": 0, "nightLightEnabled": true, "nightLightAutomatic": true,
      "nightLightTemperature": 4100, "lastPage": "desktop"}' >/dev/null
wait_for_store
before="$(qs_for_harness ipc call preference-schema-test dump | jq -cS .)"
[[ "$before" != "$defaults" ]] || fail 'the fixture did not change anything'

stop_harness
start_harness
after=""
for _ in $(seq 1 40); do
    after="$(qs_for_harness ipc call preference-schema-test dump | jq -cS .)"
    [[ "$after" == "$before" ]] && break
    sleep 0.1
done
[[ "$after" == "$before" ]] || fail "values did not survive a restart: $after"

# ── A key this build does not know is carried through, not dropped ───────────
stop_harness
jq '. + {"__future_setting__": "keep me"}' "$store" >"$store.tmp" && mv "$store.tmp" "$store"
start_harness
qs_for_harness ipc call preference-schema-test applyJson '{"dockHideDelayMs": 500}' >/dev/null
for _ in $(seq 1 40); do
    jq -e '.__future_setting__ == "keep me" and .dockHideDelayMs == 500' "$store" >/dev/null 2>&1 && break
    sleep 0.1
done
jq -e '.__future_setting__ == "keep me"' "$store" >/dev/null \
    || fail 'a setting from a newer build was dropped on write'

# ── reset() restores every schema key, with no hand-maintained list ──────────
qs_for_harness ipc call preference-schema-test reset >/dev/null
restored=""
for _ in $(seq 1 40); do
    restored="$(qs_for_harness ipc call preference-schema-test dump | jq -cS .)"
    [[ "$restored" == "$defaults" ]] && break
    sleep 0.1
done
[[ "$restored" == "$defaults" ]] || fail "reset did not restore every schema default: $restored"
jq -e '.__future_setting__ == "keep me"' "$store" >/dev/null \
    || fail 'reset discarded a setting it does not own'

# ── A corrupt file degrades to defaults instead of breaking the shell ────────
stop_harness
printf '{ this is not json' >"$store"
start_harness
corrupt="$(qs_for_harness ipc call preference-schema-test dump | jq -cS .)"
[[ "$corrupt" == "$defaults" ]] || fail "a corrupt store did not fall back to defaults: $corrupt"

trap - EXIT
cleanup
printf 'preference schema contract: PASS\n'
