#!/usr/bin/env bash
set -uo pipefail

# git-identity — switch git author identity from a saved menu.
# Spec: specs/git-identity.spec.md

IDENTITIES_FILE="${GIT_IDENTITIES_FILE:-$HOME/.git-identities}"

GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
NC='\033[0m'

usage() {
    cat <<'EOF'
Usage: git-identity [OPTIONS] [ALIAS]

Switch git user.name/user.email from identities saved in ~/.git-identities.
Run with no arguments for an interactive menu (select, add, or remove).

Arguments:
  ALIAS           Apply this saved identity directly

Options:
  -g, --global    Apply to global git config (default: local when in a repo)
  -l, --list      List saved identities
  -c, --current   Show the identity in effect here
  -n, --dryrun    Preview actions without changing anything
  -h, --help      Show this help

File format (~/.git-identities): alias|Name|email, one per line, # comments.
EOF
}

dryrun=false
scope_global=false
action=""
alias_arg=""

while [[ $# -gt 0 ]]; do
    case "$1" in
        -h|--help)    usage; exit 0 ;;
        -n|--dryrun)  dryrun=true; shift ;;
        -g|--global)  scope_global=true; shift ;;
        -l|--list)    action="list"; shift ;;
        -c|--current) action="current"; shift ;;
        -*)           echo "Error: Unknown option: $1" >&2; exit 1 ;;
        *)
            if [[ -n "$alias_arg" ]]; then
                echo "Error: Too many arguments" >&2; exit 1
            fi
            alias_arg="$1"; shift ;;
    esac
done

in_repo() { git rev-parse --is-inside-work-tree >/dev/null 2>&1; }

scope_desc() {
    if $scope_global; then
        echo "--global"
    else
        echo "--local in $(git rev-parse --show-toplevel)"
    fi
}

# Load identities into parallel arrays.
aliases=() names=() emails=()
load_identities() {
    aliases=() names=() emails=()
    [[ -f "$IDENTITIES_FILE" ]] || return 0
    while IFS='|' read -r a n e; do
        [[ -z "$a" || "$a" == \#* ]] && continue
        [[ -z "$n" || -z "$e" ]] && continue
        aliases+=("$a"); names+=("$n"); emails+=("$e")
    done < "$IDENTITIES_FILE"
}

show_current() {
    local scope="global" name email
    if in_repo && ! $scope_global; then scope="local"; fi
    name=$(git config user.name 2>/dev/null || true)
    email=$(git config user.email 2>/dev/null || true)
    echo -e "Current identity (${scope}): ${CYAN}${name:-<unset>} <${email:-unset}>${NC}"
}

apply_identity() {
    local name="$1" email="$2"
    if ! $scope_global && ! in_repo; then
        echo -e "${YELLOW}Note: not inside a git repository — applying globally${NC}"
        scope_global=true
    fi
    local desc; desc=$(scope_desc)
    if $dryrun; then
        echo "[dryrun] Would set user.name '$name' and user.email '$email' ($desc)"
        return 0
    fi
    local flag="--local"
    $scope_global && flag="--global"
    git config "$flag" user.name "$name" && git config "$flag" user.email "$email" || {
        echo "Error: git config failed" >&2; exit 1
    }
    echo -e "${GREEN}Set user.name '$name' and user.email '$email' ($desc)${NC}"
}

apply_alias() {
    local wanted="$1" i
    for i in "${!aliases[@]}"; do
        if [[ "${aliases[$i]}" == "$wanted" ]]; then
            apply_identity "${names[$i]}" "${emails[$i]}"
            return 0
        fi
    done
    echo "Error: No identity '$wanted' in $IDENTITIES_FILE" >&2
    if [[ ${#aliases[@]} -gt 0 ]]; then
        echo "Available: ${aliases[*]}" >&2
    fi
    exit 1
}

list_identities() {
    local i
    for i in "${!aliases[@]}"; do
        printf '%-14s %s <%s>\n' "${aliases[$i]}" "${names[$i]}" "${emails[$i]}"
    done
}

ensure_file() {
    [[ -f "$IDENTITIES_FILE" ]] && return 0
    local gname gemail
    gname=$(git config --global user.name 2>/dev/null || true)
    gemail=$(git config --global user.email 2>/dev/null || true)
    echo "No identities file at $IDENTITIES_FILE."
    local seed=""
    if [[ -n "$gname" && -n "$gemail" ]]; then
        seed="default|$gname|$gemail"
        echo "Create it seeded with your global identity ($gname <$gemail>)? [Y/n]"
    else
        echo "Create an empty one? [Y/n]"
    fi
    read -r reply
    [[ "$reply" =~ ^[Nn] ]] && exit 0
    if $dryrun; then
        echo "[dryrun] Would create $IDENTITIES_FILE${seed:+ seeded with '$seed'}"
        return 0
    fi
    { echo "# alias|Name|email"; [[ -n "$seed" ]] && echo "$seed"; } > "$IDENTITIES_FILE"
    echo -e "${GREEN}Created $IDENTITIES_FILE${NC}"
}

add_identity() {
    local a n e
    while true; do
        read -rp "Alias: " a
        [[ -z "$a" ]] && { echo "Cancelled."; return 0; }
        if [[ "$a" == *"|"* ]]; then echo "Alias must not contain '|'"; continue; fi
        local i dup=false
        for i in "${!aliases[@]}"; do
            [[ "${aliases[$i]}" == "$a" ]] && dup=true
        done
        $dup && { echo "Alias '$a' already exists"; continue; }
        break
    done
    read -rp "Name: " n
    [[ -z "$n" ]] && { echo "Cancelled."; return 0; }
    while true; do
        read -rp "Email: " e
        [[ -z "$e" ]] && { echo "Cancelled."; return 0; }
        [[ "$e" == *@* ]] && break
        echo "Email must contain '@'"
    done
    if $dryrun; then
        echo "[dryrun] Would append '$a|$n|$e' to $IDENTITIES_FILE"
    else
        echo "$a|$n|$e" >> "$IDENTITIES_FILE"
        echo -e "${GREEN}Added '$a'${NC}"
    fi
    read -rp "Apply it now? [y/N] " reply
    if [[ "$reply" =~ ^[Yy] ]]; then
        apply_identity "$n" "$e"
    fi
}

remove_identity() {
    if [[ ${#aliases[@]} -eq 0 ]]; then
        echo "No identities to remove."
        return 0
    fi
    local i
    for i in "${!aliases[@]}"; do
        printf '  %d) %-14s %s <%s>\n' "$((i+1))" "${aliases[$i]}" "${names[$i]}" "${emails[$i]}"
    done
    read -rp "Remove which? [1-${#aliases[@]}/q]: " reply
    [[ -z "$reply" || "$reply" == "q" ]] && { echo "Cancelled."; return 0; }
    if ! [[ "$reply" =~ ^[0-9]+$ ]] || (( reply < 1 || reply > ${#aliases[@]} )); then
        echo "Error: Invalid selection" >&2; return 1
    fi
    local victim="${aliases[$((reply-1))]}"
    if $dryrun; then
        echo "[dryrun] Would remove identity '$victim' from $IDENTITIES_FILE"
        return 0
    fi
    local tmp; tmp=$(mktemp)
    awk -F'|' -v a="$victim" '$1 != a' "$IDENTITIES_FILE" > "$tmp" && mv "$tmp" "$IDENTITIES_FILE"
    echo -e "${GREEN}Removed '$victim'${NC}"
}

something_else() {
    echo "  a) Add identity"
    echo "  r) Remove identity"
    echo "  q) Cancel"
    read -rp "Select [a/r/q]: " reply
    case "$reply" in
        a) add_identity ;;
        r) remove_identity ;;
        *) echo "Cancelled." ;;
    esac
}

interactive_menu() {
    ensure_file
    load_identities
    show_current
    echo ""
    local i
    for i in "${!aliases[@]}"; do
        printf '  %d) %-14s %s <%s>\n' "$((i+1))" "${aliases[$i]}" "${names[$i]}" "${emails[$i]}"
    done
    echo "  s) Something else..."
    echo ""
    local prompt="Select [s/q]: "
    [[ ${#aliases[@]} -gt 0 ]] && prompt="Select [1-${#aliases[@]}/s/q]: "
    read -rp "$prompt" reply
    if [[ -z "$reply" || "$reply" == "q" ]]; then
        echo "Cancelled."
        exit 0
    elif [[ "$reply" == "s" ]]; then
        something_else
    elif [[ "$reply" =~ ^[0-9]+$ ]] && (( reply >= 1 && reply <= ${#aliases[@]} )); then
        apply_identity "${names[$((reply-1))]}" "${emails[$((reply-1))]}"
    else
        echo "Error: Invalid selection" >&2
        exit 1
    fi
}

case "$action" in
    current)
        show_current
        exit 0 ;;
    list)
        if [[ ! -f "$IDENTITIES_FILE" ]]; then
            echo "Error: No identities file at $IDENTITIES_FILE (run git-identity to create one)" >&2
            exit 1
        fi
        load_identities
        list_identities
        exit 0 ;;
esac

if [[ -n "$alias_arg" ]]; then
    if [[ ! -f "$IDENTITIES_FILE" ]]; then
        echo "Error: No identities file at $IDENTITIES_FILE (run git-identity to create one)" >&2
        exit 1
    fi
    load_identities
    apply_alias "$alias_arg"
else
    interactive_menu
fi
