# Main zsh config file

zmodload zsh/datetime
let __init_zsh_start="${EPOCHREALTIME}"

# Enable completions
FPATH="${HOME}/.local/share/zsh/site-functions:${FPATH}"
autoload -U compinit && compinit

# Some utility stuff
ZSH_PLUGIN_DIR="${ZSH_CONFIG_DIR}/plugins"
# load_plugin <name>
function load_plugin {
    source "${ZSH_PLUGIN_DIR}/${1}/${1}.plugin.zsh"
}
# cmd_exists <name>
function cmd_exists {
    hash "${1}" >/dev/null 2>&1
}
# source_user_file <name>
function source_user_file {
    local machine_specific="${ZSH_CONFIG_DIR}/${1}.${HOST}.zsh"
    local default="${ZSH_CONFIG_DIR}/${1}.zsh"
    if [ -e "${machine_specific}" ]; then
        source "${machine_specific}"
    elif [ -e "${default}" ]; then
        source "${default}"
    fi
}

# Load user early init file
source_user_file "early-init"

# Load distrobox early init file
local is_in_distrobox=false
if [[ -e /run/.containerenv ]] && cmd_exists distrobox-host-exec; then
    is_in_distrobox=true
    local ctenv=("${(@f)"$(</run/.containerenv)"}")
    for line in ${ctenv}; do
        if [[ "${line}" == 'name='* ]]; then
            export DISTROBOX_MY_NAME="${(Q)${line:5}}"
            if [[ -d "${HOME}/src/compat/${DISTROBOX_MY_NAME}" ]]; then
                export DISTROBOX_MY_COMPAT="${HOME}/src/compat/${DISTROBOX_MY_NAME}"
                local init_file="${DISTROBOX_MY_COMPAT}/early-init.zsh"
                [[ -e "${init_file}" ]] && source "${init_file}"
            fi
            break
        fi
    done
fi

# Some options
setopt autocd extendedglob rm_star_silent completealiases rc_quotes histignorespace
unsetopt beep notify

# Some general, random configuration
# History stuff
if [[ -v INSIDE_EMACS ]]; then
    () {
        emulate -L zsh
        set -o rematchpcre
        if [[ "${HISTFILE}" =~ '^~([^/]*)($|/.*)' ]]; then
            local user="${match[1]}"
            local rest="${match[2]}"
            if [[ -z "${user}" ]]; then
                HISTFILE="${HOME}${rest}"
            else
                HISTFILE="${userdirs[${user}]}"
            fi
        fi
    }
fi
[ ! -d "${HISTFILE:h}" ] && mkdir -p "${HISTFILE:h}"
[ -v HISTFILE ] || HISTFILE="${HOME}/.cache/zsh/history"
HISTSIZE=1000
SAVEHIST=10000

# Tools for graphical sessions
export BROWSER=mullvad-browser
export READER=zathura
if [[ -v WAYLAND_DISPLAY ]]; then
    function clip {
        (( ${#} >= 1 )) && (cat "${@}" | wl-copy) || wl-copy
    }
else
    alias clip="xclip -selection clipboard"
fi

# I mess this up a lot
alias cd..="cd .."

# Make xargs, sudo, etc. understand aliases
alias xargs='xargs '
if cmd_exists doas; then
    __zsh_sudo_cmd=doas
    alias sudo='doas '
    alias doas='doas '
    alias sudoedit="doas $EDITOR "
    alias se="doas $EDITOR "
else
    __zsh_sudo_cmd=sudo
    alias sudo='sudo '
    alias se='sudoedit '
    alias doas='sudo '
fi

# Emacs and Neovim stuff
if [[ -v NVIM ]]; then
    export EDITOR=nvr
    alias n=nvr
    alias nvim=nvr
    alias e=nvr
    alias emacs=nvr
elif [[ "${is_in_distrobox}" == true ]]; then
    if [[ -v INSIDE_EMACS ]]; then
        [[ -z "${XDG_RUNTIME_DIR}" ]] && \
            export XDG_RUNTIME_DIR="/run/user/${UID}"
        export EDITOR='distrobox-host-exec emacsclient -a nvim '
        alias emacs='distrobox-host-exec emacsclient -a nvim '
    else
        export EDITOR='distrobox-host-exec emacsclient -a nvim -c '
        alias emacs='distrobox-host-exec emacsclient -a nvim -c '
    fi
    alias n='emacs '
    alias e='emacs '
elif [[ -v INSIDE_EMACS ]]; then
    export EDITOR='emacsclient'
    alias e='emacsclient '
    alias emacs='emacsclient '
    alias n='emacsclient '
else
    export EDITOR='emacsclient -a nvim -nw'
    # Because I keep using n by mistake
    alias emacs="${EDITOR} "
    alias n=emacs
    alias e=emacs
fi
export VISUAL="${EDITOR}"

# Make SBCL *slightly* less frustrating to use on the command line
alias sbcl='rlwrap -pblue -q "\"" sbcl'

# Safer file functions
alias cp="cp -i"
alias mv="mv -i"

# Ledger stuff
alias ldg='ledger -f "${HOME}/finance/finances.ledger"'

# Trash put for safety
if cmd_exists trash-put; then
    alias rm='echo "rm: I''m unsafe! Don''t use me."; false'
    alias tp=trash-put
    alias trr=trash-restore
    alias trl=trash-list
    alias tre=trash-empty
    alias trm=trash-rm
else
    local rm_confirm_flag='-i'
    uname | grep -i linux >/dev/null && rm_confirm_flag='-I'
    alias rm="rm ${rm_confirm_flag}"
fi

# Enable mouse support in less
export LESS="--mouse"

# Bat configuration
local bat_exec
if cmd_exists bat; then
    bat_exec=bat
elif cmd_exists batcat; then
    bat_exec=batcat;
    alias bat=batcat
fi
if ! [[ -z "${bat_exec}" ]]; then
    # Pager
    export PAGER="${bat_exec} --paging=always"

    # Less syntax highlighting in interactive shells
    alias less="${bat_exec} --paging=always"

    # Use bat instead of cat
    alias cat="${bat_exec} --paging=never"
    alias pcat="${bat_exec} -pp"
    alias ncat="${bat_exec} -pp --color=never"

    # Bat as man pager
    if [[ "${bat_exec}" = (bat|*/bat) ]]; then
        export MANPAGER="zsh -c 'col -bx | ${bat_exec} -l man --paging=always --style=plain'"
        export MANROFFOPT="-c"
    fi
fi
unset bat_exec

# Eza configuration
# Don't define an alias if ls is already an alias
if cmd_exists eza && ! alias ls >/dev/null; then
    alias ls="eza --git -F=auto"
fi
alias la="ls -a"
alias l="ls -l"
alias ll="ls -al"

# Delta configuration
if cmd_exists delta; then
    export DELTA_FEATURES='unobtrusive-line-numbers decorations side-by-side'
    export DELTA_PAGER='bat -p'
    export GIT_PAGER='delta'
fi

# Git aliases
alias ga="git add"
alias gaa="git add -A"
alias gco="git commit"
gcm() {
    git commit -m "${${@}}"
}
alias gca="git commit -a"
gcam() {
    git commit -am "${${@}}"
}
alias gp="git push"
alias gu="git pull"
alias gf="git fetch"
alias gt="git status"
alias gd="git diff"

# Sudo last line with <Esc><Esc>
sudo-command-line() {
    [[ -z $BUFFER ]] && zle up-history
    if [[ $BUFFER == ${__zsh_sudo_cmd}\ * ]]; then
        LBUFFER="${LBUFFER#${__zsh_sudo_cmd} }"
    else
        LBUFFER="${__zsh_sudo_cmd} ${LBUFFER}"
    fi
}
zle -N sudo-command-line
bindkey -M vicmd "^f" sudo-command-line
bindkey -M viins "^f" sudo-command-line

# Use vi mode
bindkey -v
# Fast switch of modes
KEYTIMEOUT=1
# Implement a replace mode
bindkey -N virep viins
bindkey -M vicmd "R" overwrite-mode
function overwrite-mode {
  zle -K virep
  zle .overwrite-mode
}
zle -N overwrite-mode

# Fancy prompt (starship)
cmd_exists starship && eval "$(starship init zsh)"
# Change cursor shape for different vi modes.
function __zsh_vim_key_prompt_handler {
    SPACESHIP_CHAR_SYMBOL="❮"
    local _shape=0
    case "${KEYMAP}" in
        main)    _shape=6 ;; # vi insert: line
        viins)   _shape=6 ;; # vi insert: line
        isearch) _shape=6 ;; # inc search: line
        virep)   _shape=4 ;; # vi replace: underscore
        command) _shape=4 ;; # read a command: underscore
        vicmd)   _shape=2 ;; # vi cmd: block
        visual)  _shape=2 ;; # vi visual mode: block
        viopp)   _shape=1 ;; # vi operation pending: blinking block
        *)       _shape=0 ;;
    esac

    zle reset-prompt
    printf '\e[%d q' "${_shape}"
}
function zle-keymap-select {
    __zsh_vim_key_prompt_handler
}
function zle-line-init {
    printf '\e[6 q'
}
zle -N zle-keymap-select
zle -N zle-line-init

# Clear scrollback on ^l
__zsh_clear_screen_and_scrollback() {
    echoti civis >"$TTY"
    printf '%b' '\e[H\e[2J' >"$TTY"
    printf '%b' '\e[3J' >"$TTY"
    echoti cnorm >"$TTY"
    zle .reset-prompt
    zle -R
}
zle -N __zsh_clear_screen_and_scrollback
bindkey '^L' __zsh_clear_screen_and_scrollback

# Direnv
if cmd_exists direnv;  then
    eval "$(direnv hook zsh)"
fi

# Pyenv
if cmd_exists pyenv; then
    export PYENV_ROOT="${HOME}/.pyenv"
    [[ -d "${PYENV_ROOT/bin}" ]] && export PATH="${PYENV_ROOT}/bin:${PATH}"
    eval "$(pyenv init - zsh)"
fi

# Bookmarks
if cmd_exists emacs; then
    [[ -v BM_CWD_LS ]] || BM_CWD_LS=true
    [[ -v BM_MODE ]] || BM_MODE=daemon
    [[ -v BM_AUTO_RELOAD ]] || BM_AUTO_RELOAD=true
    source "${ZSH_CONFIG_DIR}/emacs-bookmark.zsh"
fi

# Platform specific stuff
[ -f /usr/bin/pacman ] && source "${ZSH_CONFIG_DIR}/arch.zsh"

# FZF Integration
load_plugin fzf-tab
# Disable sort when completing `git checkout`
zstyle ':completion:*:git-checkout:*' sort false
# Set descriptions format to enable group support
zstyle ':completion:*:descriptions' format '[%d]'
# Set list-colors to enable filename colorizing
zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
# Preview directory's content with eza when completing cd
zstyle ':fzf-tab:complete:cd:*' fzf-preview 'eza -1 --color=always $realpath'
# Remove the '.' prefix at the start of every completion
zstyle ':fzf-tab:*' prefix ''
# Switch groups
zstyle ':fzf-tab:*' switch-group 'ctrl-h' 'ctrl-l'
# Toggle selected for all visible entries
zstyle ':fzf-tab:*' fzf-bindings 'ctrl-a:toggle-all'

# Autosuggestions
load_plugin zsh-autosuggestions
ZSH_AUTOSUGGEST_STRATEGY=(history completion)
bindkey '^ ' autosuggest-accept

# Load user init file
source_user_file 'local'

# Load distrobox normal init file
if [[ -v DISTROBOX_MY_COMPAT ]] && \
       [[ -f "${DISTROBOX_MY_COMPAT}/init.zsh" ]]; then
    source "${DISTROBOX_MY_COMPAT}/init.zsh"
fi

# THE FOLLOWING PLUGINS MUST COME LAST

# More completions
load_plugin zsh-completions

# Syntax highlighting
load_plugin fast-syntax-highlighting

# History substring search
load_plugin zsh-history-substring-search
bindkey '^[[A' history-substring-search-up
bindkey '^[[B' history-substring-search-down
#bindkey '^k' history-substring-search-up
#bindkey '^j' history-substring-search-down
#bindkey -M vicmd '^k' history-substring-search-up
#bindkey -M vicmd '^j' history-substring-search-down
bindkey -M vicmd 'k' history-substring-search-up
bindkey -M vicmd 'j' history-substring-search-down
bindkey -M emacs '^P' history-substring-search-up
bindkey -M emacs '^N' history-substring-search-down

# Only match at the beginning of the line
HISTORY_SUBSTRING_SEARCH_PREFIXED="true"
HISTORY_SUBSTRING_SEARCH_HIGHLIGHT_FOUND=""
setopt histignoredups

# Run fortune and cowsay if we are not in nvim or ssh
if cmd_exists fortune && cmd_exists cowsay; then
    [[ -v NVIM ]] || [[ -v SSH_CLIENT ]] || [[ -v INSIDE_EMACS ]] || \
        fortune | cowsay -felephant-in-snake -n
fi

# [[ -v EAT_SHELL_INTEGRATION_DIR ]] && source "${EAT_SHELL_INTEGRATION_DIR}/zsh"

# Clean up internal functions
unfunction load_plugin
unfunction cmd_exists
unfunction source_user_file
unset is_in_distrobox

let ZSH_INIT_TIME="$(( EPOCHREALTIME - __init_zsh_start ))"
function zsh-init-time {
    printf 'Zsh initialization took: %05fms\n' "$(( ZSH_INIT_TIME * 1000 ))"
}