#!/bin/bash
# shellcheck disable=SC2148,SC2034,SC1091

# load emoji logging for interactive shells if available
[[ $- != *i* ]] && return
[[ -r /etc/emoji-logging.sh ]] && source /etc/emoji-logging.sh

# shims if emoji logger not present
if ! declare -F log_info >/dev/null 2>&1; then
  log_info()    { printf 'INFO  %s\n' "$*" >&2; }
  log_warn()    { printf 'WARN  %s\n' "$*" >&2; }
  log_ok()      { printf 'OK    %s\n' "$*" >&2; }
  log_success() { printf 'OK    %s\n' "$*" >&2; }
  log_error()   { printf 'ERROR %s\n' "$*" >&2; }
fi
# legacy alias for older calls
log_err() { log_error "$@"; }

shopt -s checkwinsize
shopt -s no_empty_cmd_completion
shopt -s histappend
shopt -s globstar

PROMPT_COMMAND="history -a; history -n; history -r"

export HISTSIZE=100000
export HISTFILESIZE=1000000

export LC_CTYPE=${LC_CTYPE:-C.UTF-8}

export PATH="$HOME/.local/bin:$PATH"

# ── Terminal title setup ────────────────────────────────────────
case "$TERM" in
  *term*|rxvt*|gnome*|kitty*|lxterm*|tmux|qterminal*) PS1='\[\e]0;\u@\h:\w\007\]' ;;
  screen*)                                           PS1='\[\033k\u@\h:\w\033\\\]' ;;
  *)                                                 PS1="" ;;
esac

# ── Colour / prompt palette ───────────────────────────────
if command -v dircolors >/dev/null; then
  if   [[ -f ~/.ls_colors  ]]; then eval "$(dircolors -b ~/.ls_colors)"
  elif [[ -f /etc/LS_COLORS ]]; then eval "$(dircolors -b /etc/LS_COLORS)"
  else                               eval "$(dircolors -b)"
  fi
  use_color=true
elif [[ "$TERM" =~ (term|rxvt|gnome|kitty|lxterm|screen|tmux|cons25|color) ]]; then
  use_color=true
else
  use_color=false
fi

if [[ "$use_color" == true ]]; then
  if (( EUID == 0 )); then
    PS1+='\[\033[0;31m\]\u\[\033[1;37m\]@\[\033[0;91m\]\h \[\033[1;34m\]\w \[\033[0;37m\]# \[\033[0m\]'
  else
    PS1+='\[\033[0;32m\]\u\[\033[1;37m\]@\[\033[0;92m\]\h \[\033[1;34m\]\w \[\033[0;37m\]\$ \[\033[0m\]'
  fi
else
  PS1+='\u@\h \w \$ '
fi

# ── Sourcing extra bashrc fragments ─────────────────────────────
for d in /etc/bash/bashrc.d ~/.bashrc.d; do
  [[ -d $d ]] && for f in "$d"/*.sh; do
    [[ -r $f ]] && source "$f"
  done
done

# --- VTE / OSC 7 handling --------------------------------------
if [[ -f /etc/profile.d/vte.sh ]]; then
  source /etc/profile.d/vte.sh
else
  __vte_osc7() { printf "\033]7;file://%s%s\033\\" "${HOSTNAME}" "${PWD}"; }
  PROMPT_COMMAND="__vte_osc7; $PROMPT_COMMAND"
fi

# --- Compiler tuple export -------------------------------------
if command -v clang >/dev/null 2>&1; then
  export TUPLE="$(clang --print-target-triple)"
elif command -v gcc >/dev/null 2>&1; then
  export TUPLE="$(gcc -dumpmachine)"
else
  echo "No suitable compiler found" >&2
  unset TUPLE
fi

# ── Go cache setup ──────────────────────────────────────────────
SYS_GOPATH="/usr/share/go"

if mkdir -p "$SYS_GOPATH" 2>/dev/null && [[ -w "$SYS_GOPATH" ]]; then
  export GOPATH="$SYS_GOPATH"
else
  export GOPATH="$HOME/go"
fi

export GOMODCACHE="$GOPATH/pkg/mod"
export GOCACHE="${XDG_CACHE_HOME:-$HOME/.cache}/go-build"

case ":$PATH:" in
  *":$GOPATH/bin:"*) : ;;
  *) export PATH="$PATH:$GOPATH/bin" ;;
esac

export SSH_AUTH_SOCK="${XDG_RUNTIME_DIR}/ssh-agent.socket"
export GPG_TTY="$(tty)"

bind 'set bell-style none'
bind 'set colored-stats on'
bind 'set completion-ignore-case on'
bind 'set completion-prefix-display-length 3'
bind 'set convert-meta off'
bind 'set horizontal-scroll-mode off'
bind 'set input-meta on'
bind 'set mark-symlinked-directories on'
bind 'set meta-flag on'
bind 'set output-meta on'
bind 'set show-all-if-ambiguous on'
bind 'set show-all-if-unmodified on'
bind 'set visible-stats on'
bind '"\e[1~": beginning-of-line'
bind '"\e[2~": quoted-insert'
bind '"\e[3~": delete-char'
bind '"\e[4~": end-of-line'
bind '"\e[5~": history-search-backward'
bind '"\e[6~": history-search-forward'
bind '"\eOc": forward-word'
bind '"\eOd": backward-word'
bind '"\eOF": end-of-line'
bind '"\eOH": beginning-of-line'

unset use_color sh

# formatters map used by external helpers
declare -A formatters=(
  ["*.c"]="clang-format -i"
  ["*.h"]="clang-format -i"
  ["*.cpp"]="clang-format -i"
  ["*.hpp"]="clang-format -i"
  ["*.py"]="black"
  ["*.sh"]="beautysh -t"
  ["*.js"]="prettier --write"
  ["*.ts"]="prettier --write"
  ["*.java"]="clang-format -i"
  ["*.go"]="gofmt -w"
)

alias ......='cd ../../../../..'
alias .....='cd ../../../..'
alias ....='cd ../../..'
alias ...='cd ../..'
alias ..='cd ..'
alias expand='expand -t2'
alias unexpand='unexpand -t2'
alias imginfo="identify -format '-- %f -- \nType: %m\nSize: %b bytes\nResolution: %wpx x %hpx\nColors: %k'"
alias imgres="identify -format '%wx%h\n'"
alias k5='kill -9 %%'
alias ls='ls --color=auto -h'
alias lf='ls --color=auto -ap | grep -v /'
alias ll='ls --color=auto -lah'
alias grep='grep --colour=auto'
alias egrep='egrep --colour=auto'
alias fgrep='fgrep --colour=auto'
alias list-ssh='ps ax | grep "ssh" | grep -v grep'
alias t='tail'
alias h='history'
alias j='jobs -l'
alias path='echo -e ${PATH//:/\\n}'
alias ping='ping -c 5'
alias fastping='ping -c 1000 -f'
alias meminfo='free -m -l -t'
alias psmem='ps auxf | sort -nr -k 4'
alias psmem10='ps auxf | sort -nr -k 4 | head -10'
alias pscpu='ps auxf | sort -nr -k 3'
alias pscpu10='ps auxf | sort -nr -k 3 | head -10'
alias wget='wget -c'
alias df='df -HT'
alias du='du -ch --max-depth=1'
alias sha3-256='openssl dgst -sha3-256'
alias sha3-512='openssl dgst -sha3-512'
alias memfree='sync; echo 3 > /proc/sys/vm/drop_caches; free -m'
alias ssconns='ss -H -tunp | awk '\''$1 == "tcp" && $2 == "ESTAB" && $NF ~ /^users:/ { gsub(/^users:\(\(/, "", $NF); gsub(/"\)\)/, "", $NF); split($NF, a, ","); print $5 " -> " $6 " (" a[1] ")"; }'\'' | sort -u | head -n 5'

nspawn_machines() {
  for f in /etc/systemd/nspawn/*.nspawn; do
    [ -e "$f" ] || continue
    basename "${f%.nspawn}"
  done
}

startmachines() {
  nspawn_machines | while read -r m; do
    echo "starting $m"
    machinectl start "$m"
  done
}

stopmachines() {
  machinectl list --no-legend --no-pager | awk '{print $1}' | \
  while read -r m; do
    echo "powering off $m"
    machinectl poweroff "$m"
  done
}

restartmachines() {
  nspawn_machines | while read -r m; do
    echo "rebooting $m"
    machinectl reboot "$m"
  done
}


random_string() {
  [ -z "$1" ] && { log_error "Usage: random_string <length>"; return 1; }
  tr -dc A-Za-z0-9 </dev/urandom | head -c "$1" ; echo
}

sort_and_remove_duplicates() {
  if [ -f "$1" ]; then
    local tmp; tmp=$(mktemp)
    if sort "$1" | uniq >"$tmp" && [ -s "$tmp" ]; then
      mv "$tmp" "$1"
      log_ok "Sorted & deduped $1"
    else
      log_warn "No changes written to $1"
      rm -f "$tmp"
    fi
  else
    log_error "File not found: $1"
  fi
}

replace_in_files() {
  local search_string="$1"
  local replacement_string="$2"
  local start_directory="${3:-.}"

  if [[ -z $search_string || -z $replacement_string ]]; then
    log_error 'Usage: replace_in_files "search" "replacement" [start_dir]'
    return 1
  fi

  log_info "Scanning ${start_directory} (excluding .git) …"
  if find "$start_directory" -type f ! -path '*/.git/*' -print0 \
      | xargs -0 grep -l -- "$search_string" \
      | xargs -r -d $'\n' sed -i "s|$search_string|$replacement_string|g"; then
    log_ok "Replacement complete"
  else
    log_warn "No matches found or replacement failed"
  fi
}

encrypt_file() {
  [[ -z $1 ]] && { log_error "Usage: encrypt_file <file>"; return 1; }
  log_info "Encrypting $1 …"
  if gpg --encrypt --sign --armor -r "$FILE_KEYID" "$1"; then
    log_ok "Encrypted file: $1.asc"
  else
    log_error "Encryption failed"
  fi
}

decrypt_file() {
  [[ -z $1 ]] && { log_error "Usage: decrypt_file <file.asc>"; return 1; }
  log_info "Decrypting $1 …"
  if gpg --decrypt "$1" > "${1%.asc}.decrypted"; then
    log_ok "Decrypted → ${1%.asc}.decrypted"
  else
    log_error "Decryption failed"
  fi
}

backup_gpg() {
  [[ -z $1 ]] && { log_error "Usage: backup_gpg <dir>"; return 1; }
  local dst=$1; mkdir -p "$dst"
  log_info "Backing up GPG material to $dst …"
  gpg --export             --armor >"$dst/public-keys.asc"        &&
  gpg --export-secret-keys --armor >"$dst/private-keys.asc"       &&
  gpg --export-ownertrust          >"$dst/ownertrust-gpg.txt"     &&
  log_ok "GPG keys + trust DB saved in $dst"
}

restore_gpg() {
  [[ -z $1 ]] && { log_error "Usage: restore_gpg <dir>"; return 1; }
  local src=$1
  log_info "Restoring GPG material from $src …"
  gpg --import "$src/private-keys.asc" &&
  gpg --import "$src/public-keys.asc"  &&
  gpg --import-ownertrust "$src/ownertrust-gpg.txt" &&
  log_ok "GPG keys & trust DB restored"
}

backup_ssh() {
  local ts; ts=$(date +%Y%m%d%H%M%S)
  local tar="ssh_backup_${ts}.tar.bz2"
  local enc="${tar}.gpg"
  log_info "Creating encrypted SSH backup …"
  tar -cjf "$tar" -C "$HOME" .ssh          &&
  gpg --symmetric --cipher-algo AES256 --force-mdc "$tar" &&
  rm -f "$tar" &&
  log_ok "Backup ready: $(pwd)/$enc"
}

restore_ssh() {
  [[ -z $1 ]] && { log_error "Usage: restore_ssh <backup.gpg>"; return 1; }
  local enc=$1 tar=${enc%.gpg}
  [[ -f $enc ]] || { log_error "File not found: $enc"; return 1; }

  log_info "Restoring SSH keys from $enc …"
  gpg --decrypt "$enc" >"$tar" && tar -xjf "$tar" -C "$HOME" && {
    rm -f "$tar"
    log_ok "SSH keys restored"
  } || { log_error "Restore failed"; return 1; }
}

start_ssh_agent() {
  log_info "Adding private keys to ssh-agent …"
  local added=0
  for key in ~/.ssh/*; do
    [[ -f $key ]] || continue
    case $(basename "$key") in authorized_keys|config|known_hosts|bkup|*.*) continue;; esac
    ssh-add "$key" && (( added++ ))
  done
  log_ok "Added $added key(s) to agent"
}

get_kernel() {
  local tag=$1 url file
  [[ -z $tag ]] && { log_error "Usage: get_kernel <tag>"; return 1; }
  url="https://gitlab.com/linux-kernel/stable/-/archive/v${tag}/stable-v${tag}.tar.bz2"
  file="stable-v${tag}.tar.bz2"

  log_info "Downloading kernel v$tag …"
  if ! wget -q --show-progress "$url"; then
    log_error "Download failed"; return 1
  fi
  [[ $(stat -c%s "$file") -lt 100000000 ]] && {
    log_error "File too small/corrupt, aborting"; rm -f "$file"; return 1; }

  log_info "Extracting …"
  if tar -xjf "$file" && mv "stable-v${tag}" "linux-${tag}"; then
    log_ok "Kernel extracted to linux-${tag}/"
  else
    log_error "Extraction failed"
    return 1
  fi
}

clean_bash_history() {
  local file=$1 tmp; [[ -z $file ]] && { log_error "clean_bash_history <file>"; return 1; }
  tmp=$(mktemp)
  sed 's/^[[:space:]]*//;s/[[:space:]]*$//' "$file" \
    | sed '/^\s*#/d;/^\s*\/\//d' >"$tmp"
  awk '!seen[$0]++' "$tmp" >"${tmp}.cleaned"
  mv "${tmp}.cleaned" "$file"; rm -f "$tmp"
  log_ok "History cleaned: $file"
}

# ── jq json helpers ─────────────────────────────
json_keys() {
  [[ -z $1 ]] && { log_error "Usage: json_keys <file.json>"; return 1; }
  jq -r 'paths | map(tostring) | join(".")' "$1" | sort -u
}
json_shape() {
  [[ -z $1 ]] && { log_error "Usage: json_shape <file.json>"; return 1; }
  jq 'def shape:
        type as $t |
        if $t == "object" then
          to_entries | map({key, value: (shape(.value))})
        elif $t == "array" then
          if length == 0 then [] else [.[0] | shape] end
        else $t end;
      shape' "$1"
}
# convenient dashes as aliases
alias json-keys='json_keys'
alias json-shape='json_shape'

# --- ImageMagick helpers ---

# Make background transparent using top-left pixel as seed
# Usage: bgclear input.png [output.png] [fuzz%]
bgclear() {
    local in="$1"
    local out="${2:-${in%.*}-transparent.png}"
    local fuzz="${3:-10%}"

    if [ -z "$in" ]; then
        echo "usage: bgclear input.png [output.png] [fuzz%]"
        return 1
    fi

    magick "$in" \
        -alpha set \
        -channel A -fuzz "$fuzz" -fill none \
        -draw "color 0,0 floodfill" \
        +channel \
        "$out"
}

# Same as bgclear but uses fixed color instead of corner sampling
# Usage: bgclear-color '#f4e0b4' input.png [output.png] [fuzz%]
bgclear_color() {
    local color="$1"
    local in="$2"
    local out="${3:-${in%.*}-transparent.png}"
    local fuzz="${4:-10%}"

    [ -z "$color" ] || [ -z "$in" ] && \
        echo "usage: bgclear-color '#rrggbb' input.png [output.png] [fuzz%]" && return 1

    magick "$in" -fuzz "$fuzz" -transparent "$color" "$out"
}

# Auto trim solid-ish borders (great for cleaning up PNGs)
# Usage: img-trim input.png [output.png]
img_trim() {
    local in="$1"
    local out="${2:-${in%.*}-trim.png}"

    [ -z "$in" ] && echo "usage: img-trim input.png [output.png]" && return 1

    magick "$in" -trim +repage "$out"
}

# Resize so the longest edge is N pixels, keep aspect ratio
# Usage: img-resize-long 1024 input.png [output.png]
img_resize_long() {
    local size="$1"
    local in="$2"
    local out="${3:-${in%.*}-${size}.png}"

    [ -z "$size" ] || [ -z "$in" ] && \
        echo "usage: img-resize-long <pixels> input.[png|jpg] [output.png]" && return 1

    magick "$in" -resize "${size}x${size}\>" "$out"
}

# Make a square thumbnail with center crop
# Usage: img-thumb 256 input.png [output.png]
img_thumb() {
    local size="$1"
    local in="$2"
    local out="${3:-${in%.*}-thumb${size}.png}"

    [ -z "$size" ] || [ -z "$in" ] && \
        echo "usage: img-thumb <pixels> input.png [output.png]" && return 1

    magick "$in" -thumbnail "${size}x${size}^" -gravity center -extent "${size}x${size}" "$out"
}

# Quick WebP export for web use
# Usage: img-webp input.png [quality] [output.webp]
img_webp() {
    local in="$1"
    local quality="${2:-85}"
    local out="${3:-${in%.*}.webp}"

    [ -z "$in" ] && echo "usage: img-webp input.png [quality] [output.webp]" && return 1

    magick "$in" -quality "$quality" "$out"
}

# Extract a small palette preview image
# Usage: img-palette input.png [colors] [output.png]
img_palette() {
    local in="$1"
    local colors="${2:-8}"
    local out="${3:-${in%.*}-palette.png}"

    [ -z "$in" ] && echo "usage: img-palette input.png [colors] [output.png]" && return 1

    magick "$in" -format %c -depth 8 +dither -colors "$colors" histogram:"$out"
}

# Short aliases for the ones you'll use constantly
alias et-bg='bgclear'
alias et-trim='img_trim'
alias et-webp='img_webp'
alias et-thumb='img_thumb'
alias et-resize='img_resize_long'

