#!/usr/bin/env bash

# Generates hypridle's configuration from Panama's shared settings.
#
# Why this exists rather than editing hypridle.conf directly: ~/.config/hypr is
# a symlink into the Panama repository, so writing hypridle.conf at runtime
# would dirty a tracked file with machine state. The generated config therefore
# lives under XDG_STATE_HOME, and a systemd drop-in points hypridle at it with
# `-c`. The repository's hypridle.conf remains the shipped default and is what
# runs if this has never been set up.
#
#   panama-idle apply     regenerate and restart hypridle
#   panama-idle status    report as JSON what is in effect
#   panama-idle install   write the systemd drop-in (idempotent)
#   panama-idle remove    remove the drop-in and fall back to the shipped config
#
# All values are read from the settings store and clamped here as well as in the
# schema, because this script is also reachable from a shell.

set -euo pipefail

settings="${XDG_CONFIG_HOME:-$HOME/.config}/panama/settings.json"
state_dir="${XDG_STATE_HOME:-$HOME/.local/state}/panama"
generated="$state_dir/hypridle.conf"
dropin_dir="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/hypridle.service.d"
dropin="$dropin_dir/panama.conf"

# Set by generate() to the mktemp path it is currently writing, so concurrent
# invocations (e.g. rapid settings changes each spawning `apply`) never share
# a tmp file and interleave writes into a corrupt hypridle.conf. Cleared once
# the atomic mv below lands, so this is a no-op on a normal exit.
generated_tmp=""
cleanup() { rm -f "$generated_tmp" 2>/dev/null || true; }
trap cleanup EXIT

read_setting() {
    local key="$1" fallback="$2"
    [[ -r "$settings" ]] || { printf '%s' "$fallback"; return; }
    jq -r --arg k "$key" --arg d "$fallback" \
        'if has($k) and (.[$k] != null) then (.[$k] | tostring) else $d end' \
        "$settings" 2>/dev/null || printf '%s' "$fallback"
}

clamp_int() {
    local value="$1" low="$2" high="$3" fallback="$4"
    [[ "$value" =~ ^-?[0-9]+$ ]] || { printf '%s' "$fallback"; return; }
    (( value < low )) && value="$low"
    (( value > high )) && value="$high"
    printf '%s' "$value"
}

# hypridle has no concept of a power source: one config, one set of timeouts.
# So rather than maintaining two configs and swapping them, the single config
# is regenerated whenever the machine moves between wall power and battery, and
# this decides which set of keys it is built from. IdleLock watches
# Battery.acChanged and calls `apply` for exactly this reason.
#
# A machine with no battery never consults the battery keys at all, which is
# what keeps a desktop's generated config byte-for-byte what it was before any
# of this existed.
on_battery() {
    local hw="${PANAMA_PATH:-$HOME/.local/share/Panama}/bin/panama-hw"
    [[ -x "$hw" ]] || return 1
    "$hw" battery || return 1
    ! "$hw" ac
}

load() {
    local suffix=""
    if on_battery; then
        suffix="Battery"
    fi

    # An unwritten key falls back to ITS OWN schema default, never to the
    # other power source's value. The battery keys once fell back to their AC
    # counterparts, which sounded protective and produced a lie instead: the
    # Power page shows the schema default (suspend at 20) for an unwritten
    # battery key, while this generator quietly used the AC value (never), so
    # a fresh laptop displayed one behavior and shipped another -- and
    # discharged to zero in a bag. Whatever the sliders show is what must be
    # generated; these fallbacks are pinned to the schema by
    # tests/hypr/idle-defaults-contract.
    if [[ "$suffix" == "Battery" ]]; then
        blank_min="$(clamp_int "$(read_setting screenBlankMinutesBattery 2)" 0 120 2)"
        lock_min="$(clamp_int "$(read_setting lockMinutesBattery 5)" 0 240 5)"
        suspend_min="$(clamp_int "$(read_setting suspendMinutesBattery 20)" 0 480 20)"
    else
        blank_min="$(clamp_int "$(read_setting screenBlankMinutes 5)" 0 120 5)"
        lock_min="$(clamp_int "$(read_setting lockMinutes 10)" 0 240 10)"
        suspend_min="$(clamp_int "$(read_setting suspendMinutes 0)" 0 480 0)"
    fi
    lock_on_sleep="$(read_setting lockOnSleep true)"
    [[ "$lock_on_sleep" == "true" || "$lock_on_sleep" == "false" ]] || lock_on_sleep=true
}

generate() {
    load
    mkdir -p "$state_dir"

    # Unique per invocation, in the same directory as the destination so the
    # final mv is an atomic same-filesystem rename rather than a copy.
    generated_tmp="$(mktemp "$generated.XXXXXX")"

    {
        printf '# Generated by panama-idle from %s\n' "$settings"
        printf '# Power source at generation: %s\n' \
            "$(on_battery && echo battery || echo 'wall power')"
        printf '# Do not edit: it is rewritten whenever the idle settings change.\n'
        printf '# The shipped defaults live in the Panama repo at config/dot/hypr/hypridle.conf.\n\n'

        printf 'general {\n'
        printf '    lock_cmd = pidof hyprlock || ~/.config/quickshell/scripts/panama-lock run\n'
        if [[ "$lock_on_sleep" == "true" ]]; then
            printf '    before_sleep_cmd = loginctl lock-session\n'
        fi
        printf "    after_sleep_cmd = hyprctl dispatch 'hl.dsp.dpms({ action = \"on\" })'\n"
        printf '    inhibit_sleep = 2\n'
        printf '}\n'

        if (( blank_min > 0 )); then
            printf '\n# %s minutes -> screen off.\n' "$blank_min"
            printf 'listener {\n'
            printf '    timeout = %s\n' "$(( blank_min * 60 ))"
            printf "    on-timeout = hyprctl dispatch 'hl.dsp.dpms({ action = \"off\" })'\n"
            printf "    on-resume = hyprctl dispatch 'hl.dsp.dpms({ action = \"on\" })'\n"
            printf '}\n'
        fi

        if (( lock_min > 0 )); then
            printf '\n# %s minutes -> lock.\n' "$lock_min"
            printf 'listener {\n'
            printf '    timeout = %s\n' "$(( lock_min * 60 ))"
            printf '    on-timeout = loginctl lock-session\n'
            printf '}\n'
        fi

        if (( suspend_min > 0 )); then
            printf '\n# %s minutes -> suspend.\n' "$suspend_min"
            printf 'listener {\n'
            printf '    timeout = %s\n' "$(( suspend_min * 60 ))"
            printf '    on-timeout = systemctl suspend\n'
            printf '}\n'
        fi
    } >"$generated_tmp"

    mv "$generated_tmp" "$generated"
    generated_tmp=""
}

install_dropin() {
    mkdir -p "$dropin_dir"
    cat >"$dropin" <<EOF
# Installed by panama-idle. Points hypridle at the configuration Panama
# generates from its settings store, so idle timings are adjustable from
# Panama Settings rather than by editing a file in the Panama repository.
[Service]
ExecStart=
ExecStart=$(command -v hypridle || echo /usr/bin/hypridle) -c $generated
EOF
    systemctl --user daemon-reload
}

case "${1:-apply}" in
    apply)
        generate
        if [[ -f "$dropin" ]]; then
            systemctl --user restart hypridle.service
        fi
        ;;
    install)
        generate
        install_dropin
        # Restart only a daemon that is already running. `restart` on an
        # inactive unit STARTS it, and this verb now also runs from
        # change-settings during ./install -- possibly inside a GNOME
        # session, where starting hypridle would fight GNOME's own idle
        # handling. A session that has not started hypridle yet picks the
        # drop-in up at its next launch.
        if systemctl --user is-active -q hypridle.service; then
            systemctl --user restart hypridle.service
        fi
        ;;
    remove)
        rm -f "$dropin"
        systemctl --user daemon-reload
        systemctl --user restart hypridle.service
        ;;
    status)
        load
        managed=false
        [[ -f "$dropin" ]] && managed=true
        printf '{"managed":%s,"active":"%s","powerSource":"%s","blankMinutes":%s,"lockMinutes":%s,"suspendMinutes":%s,"lockOnSleep":%s,"generated":"%s"}\n' \
            "$managed" \
            "$(systemctl --user is-active hypridle.service 2>/dev/null || printf unknown)" \
            "$(on_battery && printf battery || printf ac)" \
            "$blank_min" "$lock_min" "$suspend_min" "$lock_on_sleep" "$generated"
        ;;
    *)
        printf 'usage: panama-idle [apply|install|remove|status]\n' >&2
        exit 2
        ;;
esac
