# Prompt styling and context

autoload -Uz add-zsh-hook vcs_info

zstyle ':vcs_info:*' enable git
zstyle ':vcs_info:git:*' formats ' %b'

typeset -g PROMPT_SHOW_SPACER=0
typeset -gA PROMPT_PROJECT_MATCH_CACHE
typeset -gA PROMPT_VERSION_CACHE
typeset -gA PROMPT_SEPARATOR_CACHE
typeset -g PROMPT_FIRST_RENDER=1
typeset -g PROMPT_PROJECT_ROOT_CACHE_PWD=''
typeset -g PROMPT_PROJECT_ROOT_CACHE=''

prompt_preexec() {
    case "$1" in
        clear|clear\ *|reset|reset\ *)
            PROMPT_SHOW_SPACER=0
            ;;
        *)
            PROMPT_SHOW_SPACER=1
            ;;
    esac
}

get_command_version() {
    local command_name="$1"
    local binary_path=""
    local raw_version=""

    if [[ -n ${PROMPT_VERSION_CACHE[$command_name]+x} ]]; then
        print -r -- "${PROMPT_VERSION_CACHE[$command_name]}"
        return 0
    fi

    binary_path="$(whence -p "$command_name" 2>/dev/null || true)"
    if ! [ -n "$binary_path" ] || ! [ -x "$binary_path" ]; then
        PROMPT_VERSION_CACHE[$command_name]=""
        return 0
    fi

    case "$command_name" in
        python)
            raw_version="$($binary_path --version 2>&1 || true)"
            raw_version="${raw_version#Python }"
            ;;
        node)
            raw_version="$($binary_path -v 2>/dev/null || true)"
            raw_version="${raw_version#v}"
            ;;
        go)
            raw_version="$($binary_path version 2>/dev/null || true)"
            raw_version="${raw_version#go version go}"
            raw_version="${raw_version%% *}"
            ;;
        rustc)
            raw_version="$($binary_path -V 2>/dev/null || true)"
            raw_version="${raw_version#rustc }"
            raw_version="${raw_version%% *}"
            ;;
        *)
            PROMPT_VERSION_CACHE[$command_name]=""
            return 0
            ;;
    esac

    [ -n "$raw_version" ] || {
        PROMPT_VERSION_CACHE[$command_name]=""
        return 0
    }

    PROMPT_VERSION_CACHE[$command_name]="$raw_version"
    print -r -- "$raw_version"
}

get_project_root() {
    if [ "$PROMPT_PROJECT_ROOT_CACHE_PWD" = "$PWD" ] && [ -n "$PROMPT_PROJECT_ROOT_CACHE" ]; then
        print -r -- "$PROMPT_PROJECT_ROOT_CACHE"
        return 0
    fi

    PROMPT_PROJECT_ROOT_CACHE_PWD="$PWD"
    PROMPT_PROJECT_ROOT_CACHE="$(command git rev-parse --show-toplevel 2>/dev/null || print -r -- "$PWD")"
    print -r -- "$PROMPT_PROJECT_ROOT_CACHE"
}

project_has_pattern() {
    local project_root=""
    local cache_key=""
    local pattern
    local matches=()

    project_root="$(get_project_root)"
    cache_key="${project_root}::${(j:|:)@}"

    if [[ -n ${PROMPT_PROJECT_MATCH_CACHE[$cache_key]+x} ]]; then
        [[ ${PROMPT_PROJECT_MATCH_CACHE[$cache_key]} == 1 ]]
        return
    fi

    for pattern in "$@"; do
        matches=("${project_root}"/${~pattern}(N))
        if [ "${#matches[@]}" -gt 0 ]; then
            PROMPT_PROJECT_MATCH_CACHE[$cache_key]=1
            return 0
        fi
    done

    PROMPT_PROJECT_MATCH_CACHE[$cache_key]=0

    return 1
}

build_git_status_segment() {
    local git_status_output=""
    local line=""
    local staged=0
    local unstaged=0
    local untracked=0
    local status_parts=()

    command git rev-parse --is-inside-work-tree >/dev/null 2>&1 || return 0
    git_status_output="$(command git status --porcelain 2>/dev/null || true)"

    if [ -z "$git_status_output" ]; then
        print -r -- "%F{green}✓ clean%f"
        return 0
    fi

    while IFS= read -r line; do
        [ -n "$line" ] || continue

        if [ "${line:0:2}" = "??" ]; then
            ((untracked++))
            continue
        fi

        [ "${line:0:1}" = " " ] || ((staged++))
        [ "${line:1:1}" = " " ] || ((unstaged++))
    done <<< "$git_status_output"

    [ "$staged" -eq 0 ] || status_parts+=("+$staged")
    [ "$unstaged" -eq 0 ] || status_parts+=("~$unstaged")
    [ "$untracked" -eq 0 ] || status_parts+=("?$untracked")

    if [ "${#status_parts[@]}" -eq 0 ]; then
        print -r -- "%F{green}✓ clean%f"
    else
        print -r -- "%F{yellow}${(j: :)status_parts}%f"
    fi
}

build_prompt_separator_line() {
    local last_status="$1"
    local line_width="${COLUMNS:-80}"
    local cache_key="${line_width}:${last_status}"
    local separator_line=""
    local color_prefix=""
    local color_suffix=""
    local i=0

    if [[ -n ${PROMPT_SEPARATOR_CACHE[$cache_key]+x} ]]; then
        print -r -- "${PROMPT_SEPARATOR_CACHE[$cache_key]}"
        return 0
    fi

    if [ "$line_width" -lt 1 ]; then
        line_width=1
    fi

    if [ "$last_status" -ne 0 ]; then
        color_prefix="%F{red}"
        color_suffix="%f"
    fi

    while [ "$i" -lt "$line_width" ]; do
        separator_line+="─"
        ((i++))
    done

    PROMPT_SEPARATOR_CACHE[$cache_key]="${color_prefix}${separator_line}${color_suffix}"
    print -r -- "${PROMPT_SEPARATOR_CACHE[$cache_key]}"
}

build_prompt() {
    local last_status=$?
    local git_segment=""
    local git_status_segment=""
    local env_segment=""
    local project_symbols=()
    local tool_versions=()
    local python_version=""
    local node_version=""
    local go_version=""
    local rust_version=""
    local context_line=""
    local newline=$'\n'
    local prompt_spacer=""
    local separator="::"

    if [ "$last_status" -ne 0 ]; then
        separator="%F{red}::%f"
    fi

    if [ "$PROMPT_FIRST_RENDER" -eq 1 ]; then
        PROMPT_FIRST_RENDER=0
        PROMPT="%B%~%b ${separator} "
        RPROMPT="%n@%m"
        return 0
    fi

    vcs_info
    git_segment="${vcs_info_msg_0_}"
    git_status_segment="$(build_git_status_segment)"

    if [ -n "${CONDA_DEFAULT_ENV:-}" ]; then
        env_segment=" ${CONDA_DEFAULT_ENV}"
    elif [ -n "${DIRENV_DIR:-}" ] || [ -n "${DIRENV_FILE:-}" ]; then
        env_segment=" direnv"
    fi

    if project_has_pattern '**/docker-compose*.yml' '**/docker-compose*.yaml' '**/compose*.yml' '**/compose*.yaml'; then
        project_symbols+=("%F{blue}%f")
    fi

    if project_has_pattern '**/Makefile' '**/makefile' '**/GNUmakefile' '**/Justfile' '**/justfile' '**/.justfile'; then
        project_symbols+=("%F{yellow}%f")
    fi

    if project_has_pattern '**/pyproject.toml' '**/requirements.txt' '**/.python-version' '**/setup.py'; then
        python_version="$(get_command_version python)"
        [ -z "$python_version" ] || tool_versions+=("%F{magenta} ${python_version}%f")
    fi

    if project_has_pattern '**/package.json' '**/.nvmrc' '**/pnpm-workspace.yaml' '**/yarn.lock' '**/package-lock.json'; then
        node_version="$(get_command_version node)"
        [ -z "$node_version" ] || tool_versions+=("%F{green} ${node_version}%f")
    fi

    if project_has_pattern '**/go.mod' '**/go.work'; then
        go_version="$(get_command_version go)"
        [ -z "$go_version" ] || tool_versions+=("%F{cyan} ${go_version}%f")
    fi

    if project_has_pattern '**/Cargo.toml' '**/Cargo.lock' '**/rust-toolchain.toml' '**/rust-toolchain'; then
        rust_version="$(get_command_version rustc)"
        [ -z "$rust_version" ] || tool_versions+=("%F{yellow} ${rust_version}%f")
    fi

    if [ -n "$git_segment" ]; then
        context_line="%F{cyan}${git_segment}%f"
        [ -z "$git_status_segment" ] || context_line+="  ${git_status_segment}"
    fi

    if [ -n "$env_segment" ]; then
        [ -z "$context_line" ] || context_line+="  %F{240}·%f  "
        context_line+="%F{green}${env_segment}%f"
    fi

    if [ "${#project_symbols[@]}" -gt 0 ]; then
        [ -z "$context_line" ] || context_line+="  %F{240}·%f  "
        context_line+="${(j: :)project_symbols}"
    fi

    if [ "${#tool_versions[@]}" -gt 0 ]; then
        [ -z "$context_line" ] || context_line+="  %F{240}·%f  "
        context_line+="${(j:  :)tool_versions}"
    fi

    if [ -n "$context_line" ]; then
        context_line+="${newline}"
    fi

    if [ "$PROMPT_SHOW_SPACER" -eq 1 ]; then
        prompt_spacer="$(build_prompt_separator_line "$last_status")${newline}"
    fi

    PROMPT="${prompt_spacer}${context_line}%B%~%b ${separator} "
    RPROMPT="%n@%m"
}

add-zsh-hook preexec prompt_preexec
add-zsh-hook precmd build_prompt
build_prompt
