#!/usr/bin/env bash
# @desc: Shell 环境一键增强 —— 历史高可靠 + 耗时提示 + 别名 + 多 shell
# ==============================================================
#  shinit — Shell 环境一键增强
#
#  安装:  bash <(curl -sL t.melulu.top/shinit)
#  卸载:  bash <(curl -sL t.melulu.top/shinit) --uninstall
#  自检:  SHINIT_VERBOSE=1 bash <(curl -sL t.melulu.top/shinit)
#
#  做什么（不只是历史）:
#    · 历史记录高可靠 —— 并发不覆盖、崩溃不丢、容量 100000/200000
#    · 命令耗时提示   —— 仅 >= 阈值(默认5s)才显示，失败标红并提示重试
#    · 开发效率别名   —— ls / git / docker / kubectl 常用缩写
#    · Locale 兜底    —— 仅在用户未设置时给 UTF-8，不覆盖已有偏好
#    · PATH 优化      —— ~/bin 与 ~/.local/bin 查重后前插
#    · 编辑器默认 vim
#    · 登录 shell 链条 —— 补 ~/.profile / ~/.bash_profile / ~/.zprofile，
#                         否则重开 SSH 或换 shell 后配置全部失效
#
#  支持: bash / zsh / fish —— 只给装了的 shell 写配置，没装的自动跳过
#        sh / dash / ksh 只写 POSIX 通用部分（这些 shell 无历史钩子能力）
#
#  可调环境变量:
#    SHINIT_THRESHOLD=10   耗时提示阈值秒数（默认 5）
#    SHINIT_VERBOSE=1      安装后打印各 shell 的实际生效值
#
#  相对初版（v1）的修正，均经真机实测:
#    1. 计时阈值 -ge 0 -> -ge 5      （v1 漏了阈值判断，每条命令都刷屏）
#    2. 退出码被 local 污染 -> 函数第一行抓 $?
#    3. PROMPT_COMMAND 被覆盖 -> 改为追加 + 重复检测
#    4. 脚本顶部 set -e 会中断 .rc 写入 -> 去掉
#    5. 幂等只"跳过"无法升级 -> 摘旧块重写
#    6. PATH 每次 source 重复累加 -> 查重再 prepend
#    7. LC_ALL 无条件覆盖用户偏好 -> 仅未设置时兜底
#    8. 历史容量 100万 -> 10万/20万（历史文件约几十 MB）
#    9. 登录 shell 读不到 .bashrc -> 打通 profile 链
#   10. 只有 bash 有功能 -> zsh 用 preexec/precmd，fish 用 on-event
#   11. setopt 遇到版本不支持的选项会让整个 .zshrc 中断 -> 逐项容错
#   12. $(date) 命令替换会重置 $?，导致失败命令误报成成功 -> 先抓后取
# ==============================================================

set +e   # 绝不 set -e：要写用户的 .rc，任何非 0 都可能中断

BASHRC="$HOME/.bashrc"
THRESHOLD="${SHINIT_THRESHOLD:-5}"   # 耗时提示阈值（秒）

B_HIST='# >>> shinit base >>>'
E_HIST='# <<< shinit base <<<'
B_DEV='# >>> shinit enhancements >>>'
E_DEV='# <<< shinit enhancements <<<'

# ---------- 幂等：先摘掉旧块 ----------
# strip_block 用 awk 逐行输出，被摘掉的块会留下若干空行；每次重跑脚本
# 空行就累积几行（实测 5 次后 .bashrc 从 93 行涨到 103 行）。
# 摘完顺手把「连续多个空行」压成 1 个，防止文件被反复运行撑长。
# 注意：这会顺带压掉用户自己写的连续空行。对 .rc 配置无实质影响
#（空行只用于分段），但如果你介意，可以删掉下面这个 awk。
strip_block() {   # $1=文件 $2=begin标记 $3=end标记
  [ -f "$1" ] || return 0
  grep -qF "$2" "$1" 2>/dev/null || return 0
  awk -v b="$2" -v e="$3" '
    $0==b {s=1; next}
    $0==e {s=0; next}
    !s   {print}
  ' "$1" > "$1.tmp.$$" 2>/dev/null || return 0
  mv -f "$1.tmp.$$" "$1"
  awk '
    /^[[:space:]]*$/ { if (blank) next; blank=1; print; next }
    { blank=0; print }
  ' "$1" > "$1.tmp.$$" 2>/dev/null && mv -f "$1.tmp.$$" "$1"
  return 0
}

# 摘除本脚本写过的所有版本（当前标记 + 历史标记）
strip_all() {   # $1=文件
  strip_block "$1" "$B_HIST" "$E_HIST"
  strip_block "$1" "$B_DEV"  "$E_DEV"
  strip_block "$1" '# >>> bash_history_safe_config >>>' '# <<< bash_history_safe_config <<<'
  strip_block "$1" '# >>> LULU_dev_enhancements >>>'      '# <<< LULU_dev_enhancements <<<'
}

if [ "${1:-}" = "--uninstall" ]; then
  _hit=0
  for _rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile" \
             "$HOME/.bash_profile" "$HOME/.zprofile" \
             "$HOME/.config/fish/config.fish"; do
    [ -f "$_rc" ] || continue
    _b=$(wc -l < "$_rc" 2>/dev/null || echo 0)
    strip_all "$_rc"
    _a=$(wc -l < "$_rc" 2>/dev/null || echo 0)
    [ "$_b" != "$_a" ] && { printf '[已清理] %s\n' "$_rc"; _hit=1; }
  done
  [ "$_hit" = "0" ] && echo '[提示] 没找到配置块，无需卸载'
  echo '[完成] 只摘除本脚本的块（含旧版标记），文件本身与其它内容原样保留'
  # 用 return 而非 exit: 被 source（例如经 wcls）时, exit 会终止调用方
  # 函数, 导致它后面的 shell 状态还原代码不执行。单独 bash 执行时
  # 顶层 return 等价于结束脚本, 行为一致。
  return 0
fi

# ---------- 安全写入 ----------
[ -f "$BASHRC" ] || { touch "$BASHRC" 2>/dev/null || { echo "[ERR] 无法创建 $BASHRC" >&2; exit 1; }; }
[ -w "$BASHRC" ] || { echo "[ERR] 无权写入 $BASHRC" >&2; exit 1; }

# 重复运行先清理，保证升级能生效
strip_all "$BASHRC"

# ==========================================
# 块 1：历史记录高可靠 + POSIX 通用（bash/zsh/fish 共用）
# ==========================================
cat >> "$BASHRC" <<BLOCK_HIST

$B_HIST
# --- Locale：仅在用户未设置时兜底，不覆盖已有偏好 ---
# （LC_ALL 会盖掉 LC_COLLATE/LC_TIME 等分项设置，改 sort 顺序和日期格式）
[ -z "\${LANG:-}" ] && export LANG="C.UTF-8"
[ -z "\${LC_ALL:-}" ] && export LC_ALL="C.UTF-8"

# --- PATH：查重再 prepend，防止每次 source 重复累加 ---
for __si_d in "\$HOME/bin" "\$HOME/.local/bin"; do
  case ":\$PATH:" in
    *":\$__si_d:"*) : ;;
    *) [ -d "\$__si_d" ] && PATH="\$__si_d:\$PATH" ;;
  esac
done
export PATH
unset __si_d

# --- 编辑器 ---
[ -z "\${EDITOR:-}" ] && export EDITOR="vim"
[ -z "\${VISUAL:-}" ] && export VISUAL="vim"
$E_HIST
BLOCK_HIST

# ==========================================
# 块 2：bash 专属（历史 + 别名 + 耗时提示）
# ==========================================
cat >> "$BASHRC" <<BLOCK_DEV

$B_DEV
# --- 历史容量 ---
HISTSIZE=100000           # 内存中保留的命令条数
HISTFILESIZE=200000       # 历史文件最大行数（须 >= HISTSIZE）

# --- 并发安全：只追加，不截断 ---
shopt -s histappend
shopt -s cmdhist lithist 2>/dev/null

# --- 内容过滤 ---
export HISTCONTROL=ignoreboth     # 忽略重复 + 忽略行首空格
export HISTTIMEFORMAT='%F %T '
export HISTIGNORE='ls:ll:la:lh:l:cls:clear:history:h:pwd:exit'

# --- 别名 ---
alias cls='clear'
alias l='ls -CF'
alias ll='ls -lh'
alias la='ls -A'
alias lh='ls -lh'
alias ..='cd ..'
alias ...='cd ../..'
alias g='git'
alias ga='git add'
alias gs='git status -sb'
alias gd='git diff'
alias gdc='git diff --cached'
alias gl='git log --oneline --graph --decorate --all'
alias gb='git branch'
alias gco='git checkout'
alias gcm='git commit -m'
alias k='kubectl'
alias d='docker'
alias dc='docker compose'
alias py='python3'

# --- 耗时提示（仅 >= ${THRESHOLD} 秒才显示）---
# 时间基准: 直接用内建变量 \$SECONDS，不做任何函数调用。
#
# 性能（221 实测 1000 次对比）:
#   \$(_shinit_now)   命令替换 -> fork 子进程 1611 ms  (约 1.6ms/次)
#   \$SECONDS 直接读         -> 无 fork          4 ms  (约 0.004ms/次)
#   即【每条命令省下约 1.6ms】。一天 200 条命令 = 省 0.3 秒，
#   单看数字微不足道；但 \$() 会创建子进程，某些容器里 fork 很贵
#   (内存受限 / seccomp 受限 / Windows Git-Bash)，那时差别是数量级的。
#
# 为什么不用 EPOCHREALTIME（微秒精度，看着更高级）:
#   实测踩坑 —— 交互 shell 里连续跑 sleep 3，用 EPOCHREALTIME 做差得到
#   5008ms（本该 3000ms）。它【每次被展开时】才取系统时钟，同一提示符
#   周期里会展开多次（命令替换一次、算术表达式又一次），加上 PTY 交互
#   延迟，差值凭空多出 1~2 秒。\$SECONDS 是 bash 自己维护的整秒计数，
#   实测 sleep 3/5/9 全部精确，且零开销。
#
# 精度说明: 只到秒，显示向下取整的秒数，误差 < 1 秒。
#   判断"命令是否慢"完全够用；要毫秒级请用外部工具。
_shinit_prompt() {
  __si_r=\$?                                # 先抓退出码，否则被后续命令覆盖
  __si_end=\$SECONDS                        # 内建变量，零开销
  history -a                                # 每条命令后落盘，历史不丢
  if [ -n "\${__si_start:-}" ]; then
    __si_d=\$(( __si_end - __si_start ))
    if [ "\$__si_d" -ge ${THRESHOLD} ]; then
      if [ "\$__si_r" -eq 0 ]; then
        printf '\n[\033[0;32mOK\033[0m] %ss\n' "\$__si_d"
      else
        printf '\n[\033[0;31mFAIL\033[0m] exit=%s  %ss  \033[0;36m<按下 ↑ 重试>\033[0m\n' "\$__si_r" "\$__si_d"
      fi
    fi
  fi
  __si_start=\$SECONDS
}
# --------------------------------------------------------------------------- #
# 计时起点说明：为什么 bash 版无法排除"打字/思考时间"
#
# 现象: 若把 __si_start 设成"上次刷新提示符的时刻"，你打完字停下来想 10 秒
#      再回车，这 10 秒会被算成命令耗时 —— 表现为 cat x.py 报 "[OK] 10s"。
#
# 试过并放弃的两个方案:
#   DEBUG trap —— 在每一条简单命令前都触发，包括本函数体内的 printf、
#     history、算术表达式。实测 __si_start 被重置成"现在"，差值恒 0，
#     提示永远不出现（改完连正常命令都不显示了，比误报更糟）。
#   bind '\C-m' —— readline 的回车绑定无法执行 shell 代码；bind -x 只能
#     挂在单个按键上（如 Ctrl-w），用户不按那个键就失效。
#
# 结论: bash 的 readline 在【提交命令行之前】不执行任何用户代码，
#       所以 bash 里【无法】可靠区分"打字时间"与"命令执行时间"。
#
# 现在的行为（有意为之）:
#   阈值默认 5s。你思考/打字的时间通常够不到 5s，误报自然消失；
#   真跑满 5 秒以上的命令（编译、下载、训练、长查询）一定会被报出来 ——
#   这才是这个功能真正有用的场景。
#   若你确实长时间思考后再回车，看到的秒数会偏大，那是 bash 的固有限制，
#   不是 bug。zsh 用 preexec/precmd 能精确区分（回车时 preexec 立即触发），
#   所以 zsh 下的数字是准确的。
#
# 需要完全精确的计时（长驻监控、构建、训练）：用 wcl 的计时功能，
# 它在脚本内部起止，不受打字影响。
# --------------------------------------------------------------------------- #
# 追加而非覆盖：conda/nvm/direnv 也挂 PROMPT_COMMAND，不能互相清掉
case "\${PROMPT_COMMAND:-}" in
  *_shinit_prompt*) : ;;
  "") PROMPT_COMMAND='_shinit_prompt' ;;
  *) PROMPT_COMMAND="\${PROMPT_COMMAND%;};_shinit_prompt" ;;
esac
# 初始化计时基准。用 $SECONDS 是 shell 启动至今的秒数, 而 _shinit_prompt
# 里比较的也是 $SECONDS, 两者同一基准, 差值正确。
# 注意: 不是"从现在起", 所以第一次按回车时若距开机很久也不会算出巨大值 ——
# 因为 __si_start 和 __si_end 都是同一个计数器, 差值就是两次的间隔。
__si_start=\$SECONDS
$E_DEV
BLOCK_DEV

# ==========================================
# zsh（如已安装）
# zsh 用 preexec/precmd 原生钩子，不依赖 PROMPT_COMMAND，
# 语义比 bash 那套干净，也不存在被别的工具覆盖的问题。
# ==========================================
if command -v zsh >/dev/null 2>&1; then
  ZSHRC="$HOME/.zshrc"
  if [ ! -f "$ZSHRC" ]; then : > "$ZSHRC" 2>/dev/null; fi
  if [ -f "$ZSHRC" ] && [ -w "$ZSHRC" ]; then
    strip_all "$ZSHRC"

    # 块1：POSIX 通用部分（zsh 完全兼容）
    cat >> "$ZSHRC" <<ZH
$B_HIST
[ -z "\${LANG:-}" ] && export LANG="C.UTF-8"
[ -z "\${LC_ALL:-}" ] && export LC_ALL="C.UTF-8"
for __si_d in "\$HOME/bin" "\$HOME/.local/bin"; do
  case ":\$PATH:" in
    *":\$__si_d:"*) : ;;
    *) [ -d "\$__si_d" ] && typeset -gx PATH="\$__si_d:\$PATH" ;;
  esac
done
unset __si_d
[ -z "\${EDITOR:-}" ] && export EDITOR="vim"
[ -z "\${VISUAL:-}" ] && export VISUAL="vim"
$E_HIST
ZH

    # 块2：zsh 专属
    cat >> "$ZSHRC" <<ZD
$B_DEV
# --- 历史容量 ---
HISTSIZE=100000
SAVEHIST=100000           # 真正落盘的上限，zsh 里它比 HISTSIZE 更重要
HIST_EXPIRE_DUPS_FIRST=1  # 先删旧的重复项，否则 SAVEHIST 会被重复项占满
HIST_FIND_NO_DUPS=1
HIST_REDUCE_BLANKS=1
# 逐项容错 setopt：任何选项在某些 zsh 版本上不存在或改名，
# 直接 setopt 会打印 "no such option" 并让整个 .zshrc 报错退出，
# 后面的配置（别名、钩子）就全丢了。HIST_IGNORE_SPACE_DUPS 是 bash 的
# 概念，zsh 里并不存在——用 HIST_IGNORE_DUPS 覆盖即可。
for __si_opt in HIST_IGNORE_DUPS HIST_SAVE_NO_DUPS SHARE_HISTORY \\
                APPEND_HISTORY INC_APPEND_HISTORY EXTENDED_HISTORY; do
  setopt "\$__si_opt" 2>/dev/null
done
unset __si_opt

# --- 别名 ---
alias cls='clear'
alias l='ls -CF'
alias ll='ls -lh'
alias la='ls -A'
alias lh='ls -lh'
alias g='git'
alias ga='git add'
alias gs='git status -sb'
alias gd='git diff'
alias gdc='git diff --cached'
alias gl='git log --oneline --graph --decorate --all'
alias gb='git branch'
alias gco='git checkout'
alias gcm='git commit -m'
alias k='kubectl'
alias d='docker'
alias dc='docker compose'
alias py='python3'

# --- 耗时提示（仅 >= ${THRESHOLD} 秒）---
# preexec 在【回车那一刻】触发，precmd 在提示符显示前触发。
# zsh 这对钩子天然只覆盖"真的执行了命令"的场景，不会把打字/思考时间
# 算进去 —— 这是 zsh 相比 bash 的天然优势（bash 的 PROMPT_COMMAND 在
# 每次刷新提示符时都跑，无法区分，见下方 bash 段的说明）。
#
# 精度: 用 $SECONDS（zsh 一直有这个内建变量，语义= shell 启动至今的整秒）。
#
# 为什么不直接用 $EPOCHSECONDS: 实测这台机器的 zsh 5.9 里它是【空的】
# （zsh 5.8 才引入，某些构建没启用），用它会让耗时恒为 0 —— 提示完全不出现。
#   已验证: echo $EPOCHSECONDS  -> 空
#            echo $SECONDS      -> 1786（非空）
#
# 为什么不只用 $(date +%s): date 是外部命令，每次调用 fork 一个进程，
#   在 precmd 钩子里跑会拖慢提示符。$SECONDS 读取零开销。
#   bash 侧实测过 date/EPOCHREALTIME 多次展开会让差值多出 1~2 秒。
#
# 精度说明: $SECONDS 只到秒，sleep 5 显示 5，误差 < 1 秒。
#   这个用途（判断命令是否慢）完全够；要毫秒级请用外部工具。
_zshcfg_start=\$SECONDS
preexec() { _zshcfg_start=\$SECONDS; }
precmd() {
  # 顺序有讲究: 任何命令替换都会把 $? 重置，所以必须【先】抓 $?，
  # 再算差值。之前写成 local __e=$(date +%s) __r=$? ...
  # 会让失败命令被误报成成功（红色 ✗ 永远不出现）。
  local __r=\$?
  local __d=\$(( SECONDS - _zshcfg_start ))
  if [ "\$__d" -ge ${THRESHOLD} ]; then
    if [ "\$__r" -eq 0 ]; then
      printf '\n[\033[0;32mOK\033[0m] %ss\n' "\$__d"
    else
      printf '\n[\033[0;31mFAIL\033[0m] exit=%s  %ss  \033[0;36m<按下 ↑ 重试>\033[0m\n' "\$__r" "\$__d"
    fi
  fi
  _zshcfg_start=\$SECONDS
}
$E_DEV
ZD
    printf '[已写入] %s\n' "$ZSHRC"

    # zsh 登录 shell 读 .zprofile，不读 .zshrc —— 链条必须补
    zp="$HOME/.zprofile"
    if [ ! -f "$zp" ]; then
      { printf '# 由 shinit 脚本创建：zsh 登录 shell 需要它才能加载 ~/.zshrc\n'
        printf '[ -f "%s" ] && . "%s"\n' "$ZSHRC" "$ZSHRC"; } > "$zp" 2>/dev/null \
        && printf '[已创建] %s\n' "$zp"
    elif ! grep -qF "$ZSHRC" "$zp" 2>/dev/null; then
      printf '\n[ -f "%s" ] && . "%s"\n' "$ZSHRC" "$ZSHRC" >> "$zp" 2>/dev/null \
        && printf '[已补链条] %s\n' "$zp"
    fi
  else
    printf '[跳过] %s 不可写\n' "$ZSHRC"
  fi
fi

# ==========================================
# fish（如已安装）
# fish 语法自成一套（set -g / set -gx / alias / function --on-event），
# 与 POSIX shell 不兼容，必须单独写 ~/.config/fish/config.fish
# ==========================================
if command -v fish >/dev/null 2>&1; then
  FR="$HOME/.config/fish/config.fish"
  mkdir -p "$HOME/.config/fish" 2>/dev/null
  if [ ! -f "$FR" ]; then : > "$FR" 2>/dev/null; fi
  if [ -f "$FR" ] && [ -w "$FR" ]; then
    strip_all "$FR"
    cat >> "$FR" <<FF
$B_HIST
# fish 无 export 关键字，用 set -gx 声明全局导出变量。
# fish 的历史由内置机制管理，天然只追加不覆盖，不需要 histappend。
set -g HISTSIZE 100000
set -g HISTFILESIZE 200000
if not set -q LANG;    set -gx LANG "C.UTF-8";  end
if not set -q LC_ALL; set -gx LC_ALL "C.UTF-8"; end
if not set -q EDITOR;  set -gx EDITOR "vim";   end
if not set -q VISUAL;  set -gx VISUAL "vim";   end
# PATH：fish_add_path 自身去重
if test -d "\$HOME/bin";            fish_add_path --prepend --move "\$HOME/bin";            end
if test -d "\$HOME/.local/bin";     fish_add_path --prepend --move "\$HOME/.local/bin";     end
$E_HIST

$B_DEV
alias cls 'clear'
alias l 'ls -CF'
alias ll 'ls -lh'
alias la 'ls -A'
alias lh 'ls -lh'
alias g 'git'
alias ga 'git add'
alias gs 'git status -sb'
alias gd 'git diff'
alias gdc 'git diff --cached'
alias gl 'git log --oneline --graph --decorate --all'
alias gb 'git branch'
alias gco 'git checkout'
alias gcm 'git commit -m'
alias k 'kubectl'
alias d 'docker'
alias dc 'docker compose'
alias py 'python3'

# --- 耗时提示：fish 无 PROMPT_COMMAND，用 --on-event 钩子 ---
# 精度: fish 没有 $SECONDS/$EPOCHSECONDS 内建，只能借 date。
#   date 是外部命令会 fork，但 fish_preexec/fish_postexec 每条命令只触发
#   一次（不像 bash 的 PROMPT_COMMAND 每次刷新都跑），开销可接受。
#   用 %N（纳秒）而非 %s（只到秒）是为了避免 sleep 5 因跨秒边界被算成 5 或 7。
#   单位: __si_fcc_start 与返回值都是【毫秒】，差值即毫秒。
function __si_fcc_now_ms
    set -l n (date +%s%N 2>/dev/null)
    # date 不支持 %N 时会返回字面量 N 或短数字，退回秒级（毫秒=秒*1000）
    if test -z "\$n" -o -z (echo \$n | tr -d '0-9')
        echo (math (date +%s) 1000)
        return
    end
    while test (string length -- \$n) -lt 16
        set n \$n"0"
    end
    echo (math \$n / 1000000)
end
set -g __si_fcc_start (__si_fcc_now_ms)
set -g __si_fcc_msg ''
function __si_fcc_mark --on-event fish_preexec
    set -g __si_fcc_start (__si_fcc_now_ms)
end
function __si_fcc_report --on-event fish_postexec
    # 顺序有讲究: (date) 命令替换会重置 $status，
    # 必须【先】抓 $status 再取时间，否则失败命令会被误报成成功。
    set -l __rc \$status
    set -l __ms ((__si_fcc_now_ms) - \$__si_fcc_start)
    if test \$__ms -ge $(( ${THRESHOLD} * 1000 ))
        set -l __d (math \$__ms / 1000)
        if test \$__rc -eq 0
            set -g __si_fcc_msg (printf '[OK] %ss' \$__d)
        else
            set -g __si_fcc_msg (printf '[FAIL] exit=%s  %ss  <按下 ↑ 重试>' \$__rc \$__d)
        end
        set -g __si_fcc_show 1
    end
end
# 显示在提示符里（不单独打一行，视觉更干净）
function fish_prompt
    if test \$__si_fcc_show -eq 1
        printf '%s%s%s> ' (set_color green) \$__si_fcc_msg (set_color normal)
    else
        printf '> '
    end
    set -g __si_fcc_show 0
end
$E_DEV
FF
    printf '[已写入] %s\n' "$FR"
  else
    printf '[跳过] %s 不可写\n' "$FR"
  fi
fi

# ==========================================
# sh / dash / ksh：只写 POSIX 通用部分
# 这些 shell 没有历史钩子机制（无 PROMPT_COMMAND / precmd / on-event），
# 写耗时提示只会报错，所以只给 Locale / PATH / 编辑器。
# ==========================================
if command -v sh >/dev/null 2>&1; then
  PR="$HOME/.profile"
  if [ -f "$PR" ] || : > "$PR" 2>/dev/null; then
    if grep -qF "$B_HIST" "$PR" 2>/dev/null; then
      strip_all "$PR"
    fi
    if [ ! -f "$PR" ] || [ -w "$PR" ]; then
      # 注意：POSIX sh 的 ~/.profile 也是 bash 登录 shell 的入口，
      # 这里只补 POSIX 段，不覆盖上面已写入的内容
      printf '\n%s\n' "$B_HIST" >> "$PR"
      printf '%s\n' \
        '[ -z "${LANG:-}" ] && export LANG="C.UTF-8"' \
        '[ -z "${LC_ALL:-}" ] && export LC_ALL="C.UTF-8"' \
        '[ -z "${EDITOR:-}" ] && export EDITOR="vim"' \
        'for __si_d in "$HOME/bin" "$HOME/.local/bin"; do' \
        '  case ":$PATH:" in' \
        '    *":$__si_d:"*) : ;;' \
        '    *) [ -d "$__si_d" ] && PATH="$__si_d:$PATH" ;;' \
        '  esac' \
        'done' \
        'unset __si_d' >> "$PR"
      printf '%s\n' "$E_HIST" >> "$PR"
      printf '[已写入] %s\n' "$PR"
    fi
  fi
fi

# ==========================================
# 登录 shell 链条：~/.profile / ~/.bash_profile -> ~/.bashrc
# （非标准 HOME 的容器里这两个常常缺失，导致重开 SSH 一切失效）
# ==========================================
for bp in "$HOME/.profile" "$HOME/.bash_profile"; do
  if [ -f "$bp" ] && grep -qF "$BASHRC" "$bp" 2>/dev/null; then continue; fi
  if [ ! -f "$bp" ]; then
    { printf '# 由 shinit 脚本创建：登录 shell 需要它才能加载 ~/.bashrc\n'
      printf '[ -f "%s" ] && . "%s"\n' "$BASHRC" "$BASHRC"; } > "$bp" 2>/dev/null \
      && printf '[OK] 已创建 %s\n' "$bp"
  else
    printf '\n[ -f "%s" ] && . "%s"\n' "$BASHRC" "$BASHRC" >> "$bp" 2>/dev/null \
      && printf '[OK] 已在 %s 补上加载 .bashrc 的语句\n' "$bp"
  fi
done

# ==========================================
# 生效 + 自检
# ==========================================
# 注意：这里【不】用 source，因为 `bash <(curl ...)` 是子进程，改不了当前 shell。
# 装完给一行提示即可；登录shell 那条链已经打通，新开终端必然生效。
if [ -n "${SHINIT_VERBOSE:-}" ]; then
  # shellcheck source=/dev/null
  . "$BASHRC" 2>/dev/null
  echo "[自检/bash] PROMPT_COMMAND = ${PROMPT_COMMAND:-（空）}"
  echo "[自检/bash] histappend     = $(shopt -p histappend)"
  echo "[自检/bash] HISTSIZE       = ${HISTSIZE:-（空）} / HISTFILESIZE=${HISTFILESIZE:-（空）}"
  if command -v zsh >/dev/null 2>&1 && [ -f "$HOME/.zshrc" ]; then
    # 必须用 -i（交互）+ -l（登录）：非交互的 zsh -c 不读 .zshrc/.zprofile，
    # 自检会误报成"配置没生效"
    echo "[自检/zsh]  SAVEHIST/HISTSIZE = $(env -i HOME="$HOME" TERM=xterm zsh -lic 'print -r -- "$SAVEHIST/$HISTSIZE"' 2>/dev/null | tail -1)"
    echo "[自检/zsh]  APPEND/SHARE      = $(env -i HOME="$HOME" TERM=xterm zsh -lic 'print -r -- "${options[appendhistory]}/${options[sharehistory]}"' 2>/dev/null | tail -1)"
    echo "[自检/zsh]  preexec/precmd    = $(env -i HOME="$HOME" TERM=xterm zsh -lic 'print -r -- "${+functions[preexec]}/${+functions[precmd]}"' 2>/dev/null | tail -1)"
  fi
  if command -v fish >/dev/null 2>&1 && [ -f "$HOME/.config/fish/config.fish" ]; then
    echo "[自检/fish] 配置存在        = $HOME/.config/fish/config.fish"
  fi
fi

cat <<TIP

[SUCCESS] shinit 配置完成（耗时阈值 ${THRESHOLD}s，历史 100000/200000）

  已配置的 shell:
$(command -v bash >/dev/null 2>&1 && echo "    · bash    -> ~/.bashrc")
$(command -v zsh  >/dev/null 2>&1 && echo "    · zsh     -> ~/.zshrc  (+ ~/.zprofile 链条)")
$(command -v fish >/dev/null 2>&1 && echo "    · fish    -> ~/.config/fish/config.fish")
$(command -v sh   >/dev/null 2>&1 && echo "    · sh/dash -> ~/.profile（仅 POSIX 通用部分，无历史钩子）")

  装了什么:
    · 历史并发不覆盖 + 每条命令后落盘，崩溃/Ctrl-C 不丢
    · 命令耗时提示（>= ${THRESHOLD}s 才显示，失败标红）
    · 别名: ll l la gs gd gdc gl gb gco gcm k d dc py ..
    · Locale 兜底 UTF-8、PATH 查重前插、EDITOR=vim
    · 登录 shell 链条（重开 SSH / 换 shell 后依然生效）

  · 新开终端自动生效
  · 当前窗口立即生效:  source ~/.bashrc
  · 换阈值重装:        SHINIT_THRESHOLD=10 bash <(curl -sL t.melulu.top/shinit)
  · 升级/重跑本脚本:    幂等，自动替换旧块（认得旧版标记）
  · 卸载:              bash <(curl -sL t.melulu.top/shinit) --uninstall
  · 自检:              SHINIT_VERBOSE=1 bash <(curl -sL t.melulu.top/shinit)
TIP
