# -------------------------------------------------------
# Repos — list, navigate and open repos quickly
# Personal config lives in ~/.reposrc (symlinked from dotfiles)
# Docs: https://tools.edwardhorsford.com/scripts/repos/
# Usage: repos --help
#
# Version: 0.1.3
# Updated: 2026-03-10
# -------------------------------------------------------

# Defaults — override in ~/.reposrc
REPOS_DIR="$HOME/Code"
REPOS_GROUPS=()
REPOS_HISTORY="$HOME/.repo_history"
REPOS_HISTORY_SIZE=8

typeset -A REPOS
REPOS=()  # clear on reload
typeset -a _REPOS_ORDER
_REPOS_ORDER=()  # tracks insertion order

# Use fav to define favourites — preserves order
fav() { REPOS[$1]="$2"; _REPOS_ORDER+=("$1") }

# Load personal config — create from template if missing
if [[ ! -f "$HOME/.reposrc" ]]; then
  cat > "$HOME/.reposrc" << 'EOF'
# ~/.reposrc — personal config for repos.zsh
# Docs: repos --help

# Root folder containing your repos
REPOS_DIR="$HOME/Code"

# Subfolders of REPOS_DIR that themselves contain repos (walked into for search/list)
REPOS_GROUPS=()

# Favourites — short names you can type instead of the full folder name
# fav <shortname> <path relative to REPOS_DIR>
# fav tools "myorg/tools-project"
EOF
  echo "Created ~/.reposrc — run 'repos edit' to configure it."
fi
source "$HOME/.reposrc"

# Index populated when a list is shown; enables `repos 1`, `code 3` etc
typeset -a _REPO_INDEX
_REPO_INDEX=()

# Colors
_R_RESET=$'\e[0m'
_R_BOLD=$'\e[1m'
_R_DIM=$'\e[37m'
_R_CYAN=$'\e[36m'
_R_YELLOW=$'\e[33m'

# Record a repo visit to history
_repo_record() {
  local name="$1"
  local history=()
  [[ -f "$REPOS_HISTORY" ]] && history=("${(@f)$(< $REPOS_HISTORY)}")
  history=("$name" "${(@)history:#$name}")
  printf '%s\n' "${(@)history[1,$REPOS_HISTORY_SIZE]}" >"$REPOS_HISTORY"
}

# Resolve a name/alias/index to a full path
_repo_path() {
  local input="$1"
  # Numeric index from last list
  if [[ "$input" =~ ^[0-9]+$ ]] && [[ -n "${_REPO_INDEX[$input]}" ]]; then
    _repo_path "${_REPO_INDEX[$input]}"
    return
  fi
  # Alias
  if [[ -n "${REPOS[$input]}" ]]; then
    local val="${REPOS[$input]}"
    val="${val/#\~/$HOME}"  # expand leading ~ if present
    if [[ "$val" == /* ]]; then
      echo "$val"
    else
      echo "$REPOS_DIR/$val"
    fi
    return
  fi
  # Direct folder in REPOS_DIR (skip path traversal inputs like . or ..)
  if [[ "$input" != "." && "$input" != ".." ]] && [[ -d "$REPOS_DIR/$input" ]]; then
    echo "$REPOS_DIR/$input"
    return
  fi
  # Folder inside a group
  for group in "${REPOS_GROUPS[@]}"; do
    if [[ -d "$REPOS_DIR/$group/$input" ]]; then
      echo "$REPOS_DIR/$group/$input"
      return
    fi
  done
}

# Print a numbered repo entry and record it in _REPO_INDEX
_repo_list_entry() {
  local name="$1" label="$2"
  _REPO_INDEX+=("$name")
  local i=${#_REPO_INDEX}
  if [[ -n "$label" ]]; then
    printf "  ${_R_DIM}[%d]${_R_RESET}  ${_R_BOLD}${_R_CYAN}%-20s${_R_RESET}  ${_R_DIM}%s${_R_RESET}\n" "$i" "$name" "$label"
  else
    printf "  ${_R_DIM}[%d]${_R_RESET}  ${_R_CYAN}%s${_R_RESET}\n" "$i" "$name"
  fi
}

# repos          — show numbered list (favourites + recently accessed)
# repos -a       — show numbered list of all repos
# repos <name>   — cd to repo by name or favourite
# repos <number> — cd to repo by index from last list
# repos add <name> [path]  — add a favourite (path defaults to current dir)
# repos edit     — open config file in $EDITOR
# repos --help   — show usage
repos() {
  if [[ "$1" == "--help" || "$1" == "-h" ]]; then
    echo "Usage:"
    echo "  repos              List favourites and recently accessed repos (numbered)"
    echo "  repos -a           List all repos"
    echo "  repos <name>       cd to repo by name or favourite"
    echo "  repos <number>     cd to repo by number from last list"
    echo "  repos add <name> [path]  Add current dir as a favourite (or specify path)"
    echo "  repos add <number> [alias]  Add a repo by index from last list"
    echo "  repos edit         Open config file (~/.reposrc) in \$EDITOR"
    echo "  repos --clear-history    Clear recently accessed history"
    echo "  code <name/#>      Open repo in VS Code"
    echo ""
    echo "Config (~/.reposrc):"
    echo "  REPOS_DIR        Root folder containing repos (currently: $REPOS_DIR)"
    echo "  REPOS_GROUPS     Subfolders to walk into (currently: ${REPOS_GROUPS[*]:-none})"
    echo "  fav <name> <rel-path>  Define a favourite"
    return 0
  fi
  if [[ "$1" == "edit" ]]; then
    ${=EDITOR:-code} "$HOME/.reposrc"
    return 0
  fi

  if [[ "$1" == "add" ]]; then
    local name="$2"
    if [[ -z "$name" ]]; then
      echo "Usage: repos add <name|number> [alias]"
      echo "  number: index from last 'repos' list"
      echo "  alias defaults to the folder name if adding by number"
      return 1
    fi
    # Resolve index to a name
    if [[ "$name" =~ ^[0-9]+$ ]] && [[ -n "${_REPO_INDEX[$name]}" ]]; then
      local folder_name="${_REPO_INDEX[$name]}"
      name="${3:-$folder_name}"  # use provided alias or folder name
      local target_abs="$(_repo_path "$folder_name")"
    else
      local target_abs="${3:-$PWD}"
    fi
    # Make path relative to REPOS_DIR if possible
    local rel="${target_abs#$REPOS_DIR/}"
    if [[ "$rel" == "$target_abs" ]]; then
      echo "Warning: $target_abs is not inside REPOS_DIR ($REPOS_DIR)"
      echo "Storing as absolute path."
      rel="$target_abs"
    fi
    echo "fav $name \"$rel\"" >> "$HOME/.reposrc"
    echo "Added favourite: $name -> $rel"
    echo "Run 'source ~/.zshrc' to apply."
    return 0
  fi

  if [[ "$1" == "--clear-history" ]]; then
    rm -f "$REPOS_HISTORY"
    echo "Repo history cleared."
    return 0
  fi

  # No args or -a/ls: show list
  if [[ -z "$1" || "$1" == "-a" || "$1" == "ls" ]]; then
    if [[ ! -d "$REPOS_DIR" ]]; then
      echo "REPOS_DIR not found: $REPOS_DIR"
      echo "Set REPOS_DIR in ~/.reposrc"
      return 1
    fi
    local show_all=0
    [[ "$1" == "-a" ]] && show_all=1
    _REPO_INDEX=()

    echo "${_R_BOLD}${_R_YELLOW}Favourites:${_R_RESET}"
    for alias in "${_REPOS_ORDER[@]}"; do
      _repo_list_entry "$alias" "${REPOS[$alias]}"
    done

    if (( show_all )); then
      local aliased_paths=("${(@v)REPOS}")
      echo "\n${_R_BOLD}${_R_YELLOW}Top-level repos:${_R_RESET}"
      for folder in "$REPOS_DIR"/*/; do
        local name="${${folder%/}##*/}"
        (( ${REPOS_GROUPS[(Ie)$name]} )) && continue
        (( ${aliased_paths[(Ie)$name]} )) && continue
        _repo_list_entry "$name"
      done
      for group in "${REPOS_GROUPS[@]}"; do
        [[ -d "$REPOS_DIR/$group" ]] || continue
        echo "\n${_R_BOLD}${_R_YELLOW}$group:${_R_RESET}"
        for sub in "$REPOS_DIR/$group"/*/; do
          local name="${${sub%/}##*/}"
          local rel="$group/$name"
          local alias_for=""
          for k in "${(@k)REPOS}"; do
            [[ "${REPOS[$k]}" == "$rel" ]] && alias_for="$k" && break
          done
          [[ -n "$alias_for" ]] && continue  # already listed under aliases
          _repo_list_entry "$name"
        done
      done
    else
      echo "\n${_R_BOLD}${_R_YELLOW}Recently accessed:${_R_RESET}"
      if [[ -f "$REPOS_HISTORY" ]]; then
        local alias_keys=("${(@k)REPOS}")
        local -a hist_entries
        hist_entries=("${(@f)$(< $REPOS_HISTORY)}")
        for name in "${hist_entries[@]}"; do
          [[ -z "$name" ]] && continue
          # skip if it's already shown as an alias
          (( ${alias_keys[(Ie)$name]} )) && continue
          _repo_list_entry "$name"
        done
      else
        echo "  ${_R_DIM}(none yet — use 'repos <name>' to build history)${_R_RESET}"
      fi
      echo "\n${_R_DIM}Run 'repos -a' to see all.\nRun 'repos --help' for usage.${_R_RESET}"
    fi
    return 0
  fi

  # Otherwise: navigate to named or numbered repo
  local target="$(_repo_path "$1")"
  if [[ -n "$target" ]]; then
    # Record the resolved name (alias or folder name, not the number)
    local resolved="$1"
    if [[ "$1" =~ ^[0-9]+$ ]] && [[ -n "${_REPO_INDEX[$1]}" ]]; then
      resolved="${_REPO_INDEX[$1]}"
    fi
    _repo_record "$resolved"
    cd "$target"
  else
    echo "Unknown repo: $1"
    return 1
  fi
}

# code <name|number|path> — open repo or file in VS Code
code() {
  if [[ $# -eq 1 ]]; then
    local arg="$1"
    # Skip repo lookup for path-like arguments (., .., ./foo, ../foo, /abs, ~/path)
    case "$arg" in
      .|..|./*|../*|/*|~|~/*) ;;
      *)
        local target="$(_repo_path "$arg")"
        if [[ -n "$target" ]]; then
          local resolved="$arg"
          if [[ "$arg" =~ ^[0-9]+$ ]] && [[ -n "${_REPO_INDEX[$arg]}" ]]; then
            resolved="${_REPO_INDEX[$arg]}"
          fi
          _repo_record "$resolved"
          command code "$target" 2>/dev/null
          return
        fi
        ;;
    esac
  fi
  command code "$@" 2>/dev/null
}

# Tab completion — aliases + top-level folders + group subfolders
_repos_complete() {
  local -a names
  names=("${(@k)REPOS}")
  names+=("$REPOS_DIR"/*(N/:t))
  for group in "${REPOS_GROUPS[@]}"; do
    [[ -d "$REPOS_DIR/$group" ]] && names+=("$REPOS_DIR/$group"/*(N/:t))
  done
  _describe 'repos' names
}

_code_complete() {
  local -a names
  names=("${(@k)REPOS}")
  names+=("$REPOS_DIR"/*(N/:t))
  for group in "${REPOS_GROUPS[@]}"; do
    [[ -d "$REPOS_DIR/$group" ]] && names+=("$REPOS_DIR/$group"/*(N/:t))
  done
  _alternative \
    'repos:repos:compadd -a names' \
    'files:files:_files'
}

compdef _repos_complete repos
compdef _code_complete code