#!/usr/bin/env bash

# Propagates the colour scheme to applications that do not read the desktop
# portal.
#
# Most modern applications DO follow org.freedesktop.portal.Settings and need
# nothing from us: GTK4/libadwaita, Qt6, Chromium and Electron all read
# org.freedesktop.appearance color-scheme, which xdg-desktop-portal-gtk serves
# from gsettings. Panama sets that, so those follow automatically.
#
# Terminals are the notable exception -- they predate the standard and carry
# their own palettes. kitty is handled here.
#
# GTK3 is the other one. Under GNOME, gnome-settings-daemon publishes the theme
# over XSETTINGS; under Hyprland nothing does, so ~/.config/gtk-3.0/settings.ini
# is authoritative for GTK3 applications. Pinned to dark, it contradicted the
# scheme in light mode, so it is generated from a template here instead.
#
#   panama-theme-apps dark|light [accent]
#
# kitty gets it twice: the generated include file so terminals opened later
# start correct, and a live `set-colors` over its control socket so terminals
# already open change now. Without the second, a scheme change appears to do
# nothing until you open a new window.
#
# kitty's active_border_color and hyprlock's focus ring are also the accent
# role, not just the scheme -- see accent_hex() below, which mirrors the same
# lookup hypr/looks.lua and scripts/panama-lock carry for the same reason.

set -euo pipefail

scheme="${1:-dark}"
case "$scheme" in
    dark|light) ;;
    *) printf 'usage: panama-theme-apps [dark|light] [accent]\n' >&2; exit 2 ;;
esac

# Falls back to blue for an unknown or omitted name, matching Theme.qml's own
# accentPair fallback -- so a caller that only ever knew about scheme (a stale
# ColorScheme.qml, a manual invocation) still gets a real accent instead of an
# empty string reaching sed.
accent="${2:-blue}"
case "$accent" in
    blue|orchid|teal|green|amber|orange|rose|slate) ;;
    *) accent=blue ;;
esac

# Same 8 named accents as config/Theme.qml's `accents` map (kept in sync by
# hand -- there is no shared source between QML and a shell script), primary
# hue per scheme only: both consumers below draw a flat colour, not the
# two-stop gradient the window border does.
accent_hex() {
    local name="$1" scheme="$2"
    case "$name" in
        orchid) [[ "$scheme" == light ]] && printf '7847bd' || printf 'c099ff' ;;
        teal)   [[ "$scheme" == light ]] && printf '007197' || printf '86e1fc' ;;
        green)  [[ "$scheme" == light ]] && printf '587539' || printf 'c3e88d' ;;
        amber)  [[ "$scheme" == light ]] && printf '8c6c3e' || printf 'ffc777' ;;
        orange) [[ "$scheme" == light ]] && printf 'b15c00' || printf 'ff966c' ;;
        rose)   [[ "$scheme" == light ]] && printf 'f52a65' || printf 'ff757f' ;;
        slate)  [[ "$scheme" == light ]] && printf '6172b0' || printf '828bb8' ;;
        *)      [[ "$scheme" == light ]] && printf '2e7de9' || printf '82aaff' ;;
    esac
}

# hyprlock wants "R, G, B" decimal, not hex.
hex_to_rgb() {
    local hex="$1"
    printf '%d, %d, %d' "0x${hex:0:2}" "0x${hex:2:2}" "0x${hex:4:2}"
}

accent_border_hex="$(accent_hex "$accent" "$scheme")"

# ── hyprlock ─────────────────────────────────────────────────────────────────
# The lock screen. hyprlock is launched fresh on every lock (`pidof hyprlock ||
# hyprlock`), so it reads this file each time and needs no restart.
#
# It takes rgba(r, g, b, a) in DECIMAL, not hex, so the palette is expressed as
# "R, G, B" triples here rather than the hex used everywhere else. The two
# _HEX values are the exception: they sit inside Pango markup, where hyprlock
# wants ##rrggbb.
lock_dir="${XDG_CONFIG_HOME:-$HOME/.config}/hypr"
lock_template="$lock_dir/hyprlock.conf.template"

if [[ "$scheme" == "light" ]]; then
    lock_fg="55, 96, 191"        # #3760bf
    lock_muted="97, 114, 176"    # #6172b0
    lock_error="245, 42, 101"    # #f52a65
    lock_bg="225, 226, 231"      # #e1e2e7
    lock_field="208, 213, 227"   # #d0d5e3
    lock_muted_hex="6172b0"
    lock_error_hex="f52a65"
else
    lock_fg="200, 211, 245"      # #c8d3f5
    lock_muted="130, 139, 184"   # #828bb8
    lock_error="255, 117, 127"   # #ff757f
    lock_bg="34, 36, 54"         # #222436
    lock_field="46, 47, 61"      # #2e2f3d
    lock_muted_hex="828bb8"
    lock_error_hex="ff757f"
fi

# The focus ring is the accent role, not a scheme-relative one -- follows
# accentName the same way scripts/panama-lock's own generator does.
lock_accent="$(hex_to_rgb "$accent_border_hex")"

status_hyprlock="skipped"
if [[ -r "$lock_template" ]]; then
    # Written atomically: a lock triggered mid-write would otherwise read a
    # truncated config and fall back to hyprlock's own defaults, which is a
    # bright grey screen with none of this desktop's identity.
    if sed -e "s/@FG@/$lock_fg/g" \
           -e "s/@MUTED@/$lock_muted/g" \
           -e "s/@ACCENT@/$lock_accent/g" \
           -e "s/@ERROR@/$lock_error/g" \
           -e "s/@BG@/$lock_bg/g" \
           -e "s/@FIELD@/$lock_field/g" \
           -e "s/@MUTED_HEX@/$lock_muted_hex/g" \
           -e "s/@ERROR_HEX@/$lock_error_hex/g" \
           "$lock_template" >"$lock_dir/hyprlock.conf.tmp" 2>/dev/null \
        && mv "$lock_dir/hyprlock.conf.tmp" "$lock_dir/hyprlock.conf" 2>/dev/null; then
        status_hyprlock="written"
    else
        rm -f "$lock_dir/hyprlock.conf.tmp"
        status_hyprlock="failed"
    fi
fi

# ── tmux ─────────────────────────────────────────────────────────────────────
# Generated like kitty's: tmux.conf sources current-theme.conf, and that file is
# machine state rather than configuration. Running servers are re-sourced so an
# open session changes now instead of at next launch -- tmux applies a
# source-file to every attached client immediately.
tmux_dir="${XDG_CONFIG_HOME:-$HOME/.config}/tmux"
tmux_theme="$tmux_dir/themes/tokyonight-moon.conf"
[[ "$scheme" == "light" ]] && tmux_theme="$tmux_dir/themes/tokyonight-day.conf"

status_tmux="skipped"
if [[ -r "$tmux_theme" ]]; then
    if cp "$tmux_theme" "$tmux_dir/current-theme.conf.tmp" 2>/dev/null \
        && mv "$tmux_dir/current-theme.conf.tmp" "$tmux_dir/current-theme.conf" 2>/dev/null; then
        status_tmux="written"
        # Only if a server is actually running; `tmux source-file` would
        # otherwise start one just to theme it.
        if command -v tmux >/dev/null 2>&1 && tmux has-session 2>/dev/null; then
            tmux source-file "$tmux_dir/current-theme.conf" 2>/dev/null \
                && status_tmux="applied to running sessions"
        fi
    else
        rm -f "$tmux_dir/current-theme.conf.tmp"
        status_tmux="failed"
    fi
fi

# ── btop ─────────────────────────────────────────────────────────────────────
# Only the color_theme line is rewritten, in place. btop OWNS btop.conf -- it
# rewrites the whole file on exit -- so the config is not symlinked into Panama
# and not replaced wholesale here; just this one value is edited, and btop keeps
# it on the next write.
#
# btop reads its theme once at startup, so a running instance keeps the old
# colours until it is restarted. That is acceptable for a monitor you open when
# you want it, and forcing a restart would kill a process the user is watching.
btop_conf="${XDG_CONFIG_HOME:-$HOME/.config}/btop/btop.conf"
btop_theme="tokyonight-moon"
[[ "$scheme" == "light" ]] && btop_theme="tokyonight-day"

status_btop="skipped"
if [[ -w "$btop_conf" ]]; then
    if sed -i "s|^color_theme *=.*|color_theme = \"$btop_theme\"|" "$btop_conf" 2>/dev/null; then
        status_btop="written"
    else
        status_btop="failed"
    fi
fi

# ── GTK ──────────────────────────────────────────────────────────────────────
# adw-gtk3, not Adwaita: no Adwaita GTK theme is installed on Fedora 44, and
# naming a theme that does not exist makes GTK fall back to its light default --
# which made dark mode silently produce light windows.
if [[ "$scheme" == "light" ]]; then
    gtk_theme="adw-gtk3"
    prefer_dark=0
else
    gtk_theme="adw-gtk3-dark"
    prefer_dark=1
fi

status_gtk="skipped"
for gtk_version in 3.0 4.0; do
    gtk_dir="${XDG_CONFIG_HOME:-$HOME/.config}/gtk-$gtk_version"
    template="$gtk_dir/settings.ini.template"
    [[ -r "$template" ]] || continue

    # Written atomically: a GTK application starting mid-write would otherwise
    # read a truncated file and fall back to defaults.
    if sed -e "s/@GTK_THEME@/$gtk_theme/" -e "s/@PREFER_DARK@/$prefer_dark/" "$template" \
        >"$gtk_dir/settings.ini.tmp" 2>/dev/null \
        && mv "$gtk_dir/settings.ini.tmp" "$gtk_dir/settings.ini" 2>/dev/null; then
        status_gtk="written"
    else
        rm -f "$gtk_dir/settings.ini.tmp"
        status_gtk="failed"
    fi
done

kitty_dir="${XDG_CONFIG_HOME:-$HOME/.config}/kitty"
theme_file="$kitty_dir/themes/tokyonight-moon.conf"
[[ "$scheme" == "light" ]] && theme_file="$kitty_dir/themes/tokyonight-day.conf"

status_kitty="skipped"
if [[ -r "$theme_file" ]]; then
    # Written atomically: kitty may read this while a new window is starting.
    # The accent override is appended after the copied theme rather than
    # edited into the tracked theme file: kitty applies settings top to
    # bottom, so a later active_border_color line wins, and this file is
    # itself generated -- the tracked per-scheme theme stays scheme-only.
    if cp "$theme_file" "$kitty_dir/current-theme.conf.tmp" 2>/dev/null \
        && printf 'active_border_color #%s\n' "$accent_border_hex" >>"$kitty_dir/current-theme.conf.tmp" \
        && mv "$kitty_dir/current-theme.conf.tmp" "$kitty_dir/current-theme.conf" 2>/dev/null; then
        status_kitty="written"
    else
        rm -f "$kitty_dir/current-theme.conf.tmp"
    fi

    # Live-apply to running terminals. kitty appends its PID to the socket name
    # from listen_on, so there is one socket per instance -- "unix:@mykitty"
    # alone reaches nothing, which is exactly how this looked like remote
    # control being disabled when it was not.
    if command -v kitty >/dev/null 2>&1 && command -v ss >/dev/null 2>&1; then
        applied=0
        while read -r socket; do
            [[ -n "$socket" ]] || continue
            if kitty @ --to "unix:@${socket#@}" set-colors --all --configured "$theme_file" >/dev/null 2>&1 \
                && kitty @ --to "unix:@${socket#@}" set-colors --override "active_border_color=#$accent_border_hex" >/dev/null 2>&1; then
                applied=$((applied + 1))
            fi
        done < <(ss -xl 2>/dev/null | grep -oE '@mykitty[^[:space:]]*' | sort -u)
        (( applied > 0 )) && status_kitty="applied to $applied"
    fi
fi

# Every target reports what actually happened. A helper that says only
# "kitty: applied" while silently skipping three other applications is how a
# half-applied theme goes unnoticed.
jq -cn \
    --arg scheme "$scheme" \
    --arg kitty "$status_kitty" \
    --arg gtk "$status_gtk" \
    --arg btop "$status_btop" \
    --arg tmux "$status_tmux" \
    --arg hyprlock "$status_hyprlock" \
    '{scheme: $scheme, kitty: $kitty, gtk: $gtk, btop: $btop, tmux: $tmux, hyprlock: $hyprlock}'
