#!/bin/bash
# wallp — unified wallpaper manager. See docs/superpowers/specs/.

set -u

# Expand a leading ~ or ~/ to $HOME. Leaves other paths unchanged.
expand_tilde() {
  case "$1" in
    "~") printf '%s\n' "$HOME" ;;
    "~/"*) printf '%s\n' "$HOME/${1#\~/}" ;;
    *) printf '%s\n' "$1" ;;
  esac
}

# Parse key=value conf into CONF_* globals. Ignores blanks and #-comments.
# Expands ~ in DEFAULT_* path values. Does not validate (see require_conf_keys).
CONF_THEME="" CONF_OUTPUT_H="" CONF_OUTPUT_V="" CONF_DEFAULT_H="" CONF_DEFAULT_V=""
parse_conf() {
  local file="$1" line key val
  while IFS= read -r line || [ -n "$line" ]; do
    case "$line" in ''|'#'*) continue ;; esac
    key="${line%%=*}"
    val="${line#*=}"
    case "$key" in
      THEME)     CONF_THEME="$val" ;;
      OUTPUT_H)  CONF_OUTPUT_H="$val" ;;
      OUTPUT_V)  CONF_OUTPUT_V="$val" ;;
      DEFAULT_H) CONF_DEFAULT_H="$(expand_tilde "$val")" ;;
      DEFAULT_V) CONF_DEFAULT_V="$(expand_tilde "$val")" ;;
    esac
  done < "$file"
}

conf_path() { printf '%s\n' "$HOME/.config/wallp/wallp.conf"; }

write_conf_template() {
  local path; path="$(conf_path)"
  mkdir -p "$(dirname "$path")"
  cat > "$path" <<'EOF'
# wallp config. Fill in real values, then re-run wallp.
# THEME is optional (defaults to sexy-splurge).
THEME=sexy-splurge
# Physical output names (see: swaymsg -t get_outputs / wlr-randr)
OUTPUT_H=DP-1
OUTPUT_V=DP-3
# Default wallpapers used by --restore when no saved state exists.
DEFAULT_H=~/Pictures/wallpapers/SFW/horizontal.png
DEFAULT_V=~/Pictures/wallpapers/SFW/vertical.png
EOF
}

# Hard-error if any required path/output key is empty. Returns 1 on first miss.
require_conf_keys() {
  local k var
  for k in OUTPUT_H OUTPUT_V DEFAULT_H DEFAULT_V; do
    var="CONF_$k"
    if [ -z "${!var}" ]; then
      echo "wallp: required config key '$k' is missing or empty in $(conf_path)" >&2
      return 1
    fi
  done
  return 0
}

# Returns: 0 ok, 1 invalid conf, 10 bootstrapped (caller should exit 0).
load_conf() {
  local path; path="$(conf_path)"
  if [ ! -f "$path" ]; then
    write_conf_template
    echo "wallp: generated config at $path — fill it in and re-run." >&2
    command -v notify-send >/dev/null 2>&1 && \
      notify-send -u normal "wallp" "Config generated at $path. Fill it in and re-run."
    return 10
  fi
  parse_conf "$path"
  [ -z "$CONF_THEME" ] && CONF_THEME="sexy-splurge"
  require_conf_keys || return 1
  return 0
}

theme_file() { printf '%s\n' "$HOME/.config/wallp/theme"; }

persist_theme() {
  local f; f="$(theme_file)"
  mkdir -p "$(dirname "$f")"
  printf '%s\n' "$1" > "$f"
}

# Precedence: flag arg > persisted file > CONF_THEME > sexy-splurge.
resolve_theme() {
  local flag="$1" f; f="$(theme_file)"
  if [ -n "$flag" ]; then printf '%s\n' "$flag"; return; fi
  if [ -s "$f" ]; then head -n1 "$f"; return; fi
  if [ -n "${CONF_THEME:-}" ]; then printf '%s\n' "$CONF_THEME"; return; fi
  printf '%s\n' "sexy-splurge"
}

# Maps logical output (H|V) to physical connector name from config.
output_for() {
  case "$1" in
    H) printf '%s\n' "$CONF_OUTPUT_H" ;;
    V) printf '%s\n' "$CONF_OUTPUT_V" ;;
    *) echo "wallp: unknown logical output '$1'" >&2; return 1 ;;
  esac
}

# Returns path to saved wall state for logical output (H|V).
wall_file_for() {
  case "$1" in
    H) printf '%s\n' "$HOME/.config/wallp/wall_h" ;;
    V) printf '%s\n' "$HOME/.config/wallp/wall_v" ;;
  esac
}

# Returns path to PID file for logical output (H|V).
pid_file_for() {
  case "$1" in
    H) printf '%s\n' "$HOME/.cache/wallp/H.pid" ;;
    V) printf '%s\n' "$HOME/.cache/wallp/V.pid" ;;
  esac
}

# Parse key=value conf into SET_* globals. Expands ~ in path values.
# Rejects unknown tokens (not H= or V=).
SET_H="" SET_V=""
parse_set_args() {
  local tok
  for tok in "$@"; do
    case "$tok" in
      H=*) SET_H="$(expand_tilde "${tok#H=}")" ;;
      V=*) SET_V="$(expand_tilde "${tok#V=}")" ;;
      *) echo "wallp: unknown --set argument '$tok' (expected H=<file> or V=<file>)" >&2; return 1 ;;
    esac
  done
  return 0
}

# Kill the swaybg recorded for a logical output, if its PID is still alive.
kill_output() {
  local logical="$1" pf pid; pf="$(pid_file_for "$logical")"
  [ -f "$pf" ] || return 0
  pid="$(cat "$pf")"
  if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
    kill "$pid" 2>/dev/null
  fi
  rm -f "$pf"
}

# apply_output <H|V> <file>: set wallpaper for one output. rc1 = skipped.
apply_output() {
  local logical="$1" file="$2" output pf wf
  if [ ! -f "$file" ]; then
    echo "wallp: file not found, skipping $logical: $file" >&2
    return 1
  fi
  output="$(output_for "$logical")" || return 1
  kill_output "$logical"
  swaybg -o "$output" -i "$file" -m fill &
  pf="$(pid_file_for "$logical")"; mkdir -p "$(dirname "$pf")"
  printf '%s\n' "$!" > "$pf"
  wf="$(wall_file_for "$logical")"; mkdir -p "$(dirname "$wf")"
  printf '%s\n' "$file" > "$wf"
  return 0
}

update_wpaper() {
  local wf img link; wf="$(wall_file_for H)"
  [ -s "$wf" ] || return 0
  img="$(cat "$wf")"
  link="$HOME/.cache/wal/wpaper"
  mkdir -p "$(dirname "$link")"
  ln -sf "$img" "$link"
}

apply_theme() {
  local theme="$1"
  persist_theme "$theme"
  wal --backend colorz -nq --theme "$theme" -o "$HOME/bin/wal.sh"
  command -v notify-send >/dev/null 2>&1 && \
    notify-send -u normal -t 5000 "color theme update" "setting color theme to: $theme"
}

# Pick saved path for a logical output, else its conf default.
restore_path_for() {
  local logical="$1" wf; wf="$(wall_file_for "$logical")"
  if [ -s "$wf" ]; then cat "$wf"; return; fi
  case "$logical" in
    H) printf '%s\n' "$CONF_DEFAULT_H" ;;
    V) printf '%s\n' "$CONF_DEFAULT_V" ;;
  esac
}

do_restore() {
  local flag_theme="$1" logical file
  for logical in H V; do
    file="$(restore_path_for "$logical")"
    apply_output "$logical" "$file" || true
  done
  finalize "$(resolve_theme "$flag_theme")"
}

finalize() {
  update_wpaper
  apply_theme "$1"
}

# Interactive selection via qarma. Populates SET_H/SET_V. Isolated for testing.
qarma_select() {
  local choice file
  # --radiolist + --print-column=2 is required: a plain --list returns nothing
  # on selection. Column 1 is the radio bool, column 2 the screen value.
  choice="$(qarma --list --radiolist --print-column=2 \
    --title="wallp" --text="Which screen?" \
    --column="Pick" --column="Screen" \
    FALSE H FALSE V FALSE Both 2>/dev/null)" || return 1
  case "$choice" in
    H|Both)
      file="$(qarma --file-selection --preview-images 500 --width 1300 --height 600 \
        --title='Choose Horizontal Wallpaper')" && SET_H="$file" ;;
  esac
  case "$choice" in
    V|Both)
      file="$(qarma --file-selection --preview-images 500 --width 1300 --height 600 \
        --title='Choose Vertical Wallpaper')" && SET_V="$file" ;;
  esac
  return 0
}

do_set() {
  local flag_theme="$1"; shift
  SET_H="" SET_V=""
  if [ "$#" -gt 0 ]; then
    parse_set_args "$@" || return 1
  fi
  if [ -z "$SET_H" ] && [ -z "$SET_V" ]; then
    if [ -n "${WAYLAND_DISPLAY:-}" ]; then
      qarma_select || return 1
    else
      echo "wallp: no display: pass H=<file> and/or V=<file>" >&2
      return 1
    fi
  fi
  [ -n "$SET_H" ] && { apply_output H "$SET_H" || true; }
  [ -n "$SET_V" ] && { apply_output V "$SET_V" || true; }
  finalize "$(resolve_theme "$flag_theme")"
}

show_help() {
  local text="wallp — unified wallpaper manager

Usage:
  wallp                      Show this help
  wallp --help | -h          Show this help
  wallp --set                Interactive (qarma) wallpaper selection
  wallp --set H=<file>       Set horizontal screen only
  wallp --set V=<file>       Set vertical screen only
  wallp --set H=<f> V=<f>    Set both screens
  wallp --restore            Restore last session (defaults if none)
  wallp --theme <name>       Override theme (with --set or --restore)

Config: ~/.config/wallp/wallp.conf"
  if [ -n "${WAYLAND_DISPLAY:-}" ] && command -v qarma >/dev/null 2>&1; then
    # qarma's --info collapses whitespace and newlines: use <br> for line breaks
    # and &#160; (non-breaking space) for the command/description column padding.
    local n="&#160;"
    local markup="<b>wallp</b> — unified wallpaper manager<br><br>\
<b>Usage</b><br>\
<tt>wallp${n}${n}${n}${n}${n}${n}${n}${n}${n}${n}${n}${n}${n}${n}${n}${n}${n}${n}</tt> Show this help<br>\
<tt>wallp --help | -h${n}${n}${n}${n}${n}</tt> Show this help<br>\
<tt>wallp --set${n}${n}${n}${n}${n}${n}${n}${n}${n}${n}${n}</tt> Interactive (qarma) selection<br>\
<tt>wallp --set H=&lt;file&gt;${n}${n}${n}${n}</tt> Set horizontal screen only<br>\
<tt>wallp --set V=&lt;file&gt;${n}${n}${n}${n}</tt> Set vertical screen only<br>\
<tt>wallp --set H=&lt;f&gt; V=&lt;f&gt;${n}</tt> Set both screens<br>\
<tt>wallp --restore${n}${n}${n}${n}${n}${n}${n}</tt> Restore last session (defaults if none)<br>\
<tt>wallp --theme &lt;name&gt;${n}${n}${n}${n}${n}</tt> Override theme (with --set or --restore)<br><br>\
<b>Config:</b> <tt>~/.config/wallp/wallp.conf</tt>"
    # --width prevents qarma from wrapping the description column onto new lines.
    # (GTK enforces a minimum dialog width, so the window may look wider.)
    qarma --info --width=800 --height=400 --title="wallp help" --text="$markup" 2>/dev/null
  else
    printf '%s\n' "$text"
  fi
}

# Verify required external binaries are present. swaybg + wal always required;
# qarma required only when GUI selection is needed (caller passes "gui").
# notify-send is optional and not checked here.
require_bins() {
  local need_gui="${1:-}" bin
  for bin in swaybg wal; do
    if ! command -v "$bin" >/dev/null 2>&1; then
      echo "wallp: required binary '$bin' not found in PATH" >&2
      return 1
    fi
  done
  if [ "$need_gui" = "gui" ] && ! command -v qarma >/dev/null 2>&1; then
    echo "wallp: required binary 'qarma' not found in PATH" >&2
    return 1
  fi
  return 0
}

main() {
  local action="" flag_theme="" set_args=()
  while [ "$#" -gt 0 ]; do
    case "$1" in
      --help|-h) show_help; return 0 ;;
      --set)     action="set" ;;
      --restore) action="restore" ;;
      --theme)   shift; flag_theme="${1:-}";
                 [ -z "$flag_theme" ] && { echo "wallp: --theme needs a name" >&2; return 1; } ;;
      H=*|V=*)   set_args+=("$1") ;;
      *)         echo "wallp: unknown argument '$1'" >&2; show_help; return 1 ;;
    esac
    shift
  done

  if [ -z "$action" ]; then show_help; return 0; fi

  # GUI (qarma) is needed only for an interactive --set with no H=/V= args.
  local need_gui=""
  if [ "$action" = "set" ] && [ "${#set_args[@]}" -eq 0 ] && [ -n "${WAYLAND_DISPLAY:-}" ]; then
    need_gui="gui"
  fi
  require_bins "$need_gui" || return 1

  load_conf; local rc=$?
  if [ "$rc" -eq 10 ]; then return 0; fi   # bootstrapped, no change
  if [ "$rc" -ne 0 ]; then return 1; fi

  case "$action" in
    set)     do_set "$flag_theme" "${set_args[@]}" ;;
    restore) do_restore "$flag_theme" ;;
  esac
}

if [ "${BASH_SOURCE[0]}" = "$0" ]; then main "$@"; fi
