#!/usr/bin/env bash
# say-it — speak project / product / jargon names the way engineers actually say them.
#
# Looks up the word in the community-maintained pronunciation dictionary
# (data/pronunciations.tsv) and feeds an English-like respelling to macOS
# `say`, so project names like kubectl ("koob control"), nginx ("engine X"),
# GIF ("jif") come out the way engineers actually pronounce them.
#
# Why respelling rather than IPA/phonemes? Modern macOS `say` does not parse
# `[[inpt PHON]]` markup or SSML `<phoneme>` tags — both are read as literal
# text. Respelling rides the TTS's built-in letter-to-sound rules.
set -e

VERSION="0.4.0"
VOICE="Samantha"
TIMES=3
RATE=175
SAVE=""
USE_ALT=0
ALT_IDX=1
USE_ALL=0
USE_SOLO=0
NO_DICT=0
SHOW_WHY=0
OUTPUT_JSON=0
OUTPUT_MD=0
COPY_CLIP=0
QUIET=0

# ANSI colors (disabled if NO_COLOR or not a TTY)
if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
  C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_DIM=$'\033[2m'
  C_RED=$'\033[31m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'
  C_BLUE=$'\033[34m'; C_MAGENTA=$'\033[35m'; C_CYAN=$'\033[36m'; C_WHITE=$'\033[37m'
  C_ORANGE=$'\033[38;5;208m'
else
  C_RESET=""; C_BOLD=""; C_DIM=""; C_RED=""; C_GREEN=""
  C_YELLOW=""; C_BLUE=""; C_MAGENTA=""; C_CYAN=""; C_WHITE=""; C_ORANGE=""
fi

# --- platform-aware TTS abstraction ---------------------------------------
# Detect the best available text-to-speech backend.
#   say         macOS built-in (best quality)
#   espeak-ng   Linux/BSD/anywhere via the espeak-ng package
#   espeak      legacy espeak fallback
#   powershell  Windows (cygwin/msys2/git-bash via powershell.exe)
if command -v say >/dev/null 2>&1; then TTS_BACKEND=say
elif command -v espeak-ng >/dev/null 2>&1; then TTS_BACKEND=espeak-ng
elif command -v espeak >/dev/null 2>&1; then TTS_BACKEND=espeak
elif command -v powershell.exe >/dev/null 2>&1; then TTS_BACKEND=powershell
else TTS_BACKEND=""
fi

# tts_speak <text> [rate-wpm]
# Speaks the text using the detected backend at the given rate (default $RATE).
tts_speak() {
  local _text="$1" _rate="${2:-$RATE}"
  case "$TTS_BACKEND" in
    say)        say -v "$VOICE" -r "$_rate" "$_text" 2>/dev/null ;;
    espeak-ng)  espeak-ng -v en-us -s "$_rate" "$_text" 2>/dev/null ;;
    espeak)     espeak -v en-us -s "$_rate" "$_text" 2>/dev/null ;;
    powershell)
      # PowerShell rate is -10..10; map wpm (~80–500) into that range
      local _ps_rate=$(( (_rate - 175) / 25 ))
      [[ $_ps_rate -lt -10 ]] && _ps_rate=-10
      [[ $_ps_rate -gt 10 ]] && _ps_rate=10
      local _esc; _esc=$(printf '%s' "$_text" | sed "s/'/''/g")
      powershell.exe -NoProfile -Command "Add-Type -AssemblyName System.Speech; \$s = New-Object System.Speech.Synthesis.SpeechSynthesizer; \$s.Rate = $_ps_rate; \$s.Speak('$_esc')" 2>/dev/null
      ;;
    "")
      echo "say-it: no TTS backend found. Install one:" >&2
      echo "  macOS:    built-in (\`say\`)" >&2
      echo "  Linux:    \`sudo apt install espeak-ng\` (or yum/pacman/brew)" >&2
      echo "  Windows:  PowerShell (built into modern Windows)" >&2
      return 1 ;;
  esac
}

# tts_save <text> <output-file>
# Saves the rendered audio to a file. AIFF on macOS, WAV elsewhere.
tts_save() {
  local _text="$1" _out="$2"
  case "$TTS_BACKEND" in
    say)        say -v "$VOICE" -r "$RATE" -o "$_out" "$_text" 2>/dev/null ;;
    espeak-ng)  espeak-ng -v en-us -s "$RATE" -w "$_out" "$_text" 2>/dev/null ;;
    espeak)     espeak -v en-us -s "$RATE" -w "$_out" "$_text" 2>/dev/null ;;
    *)
      echo "say-it: --output not supported on this TTS backend." >&2
      return 1 ;;
  esac
}

# audio_play <file>  — play a previously-rendered audio file.
audio_play() {
  local _f="$1"
  if command -v afplay >/dev/null 2>&1; then afplay "$_f" 2>/dev/null
  elif command -v paplay >/dev/null 2>&1; then paplay "$_f" 2>/dev/null
  elif command -v aplay >/dev/null 2>&1; then aplay -q "$_f" 2>/dev/null
  elif command -v play >/dev/null 2>&1; then play -q "$_f" 2>/dev/null
  elif command -v ffplay >/dev/null 2>&1; then ffplay -nodisp -autoexit "$_f" >/dev/null 2>&1
  else
    echo "say-it: no audio player found (need afplay/paplay/aplay/play/ffplay)." >&2
    return 1
  fi
}


usage() {
  cat <<EOF
${C_BOLD}say-it${C_RESET} — speak project / product / jargon names the way engineers actually say them.

${C_BOLD}Usage:${C_RESET}
  say-it <word>                  speak 3 times using the community dict if known
  say-it -v <voice> <word>       any macOS voice (default: Samantha, GenAm)
  say-it -n 5 <word>             repeat N times instead of 3
  say-it -r 130 <word>           slower rate (default 175 wpm)
  say-it -o out.aiff <word>      save to file instead of playing
  say-it --alt <word>            speak the first alternate reading only
  say-it --alt N <word>          speak the Nth alternate only (1-indexed)
  say-it --all <word>            play primary AND every alternate, each repeated
  say-it --solo <word>           primary only — suppress the "or: <alt>" tail
  say-it --no-dict <word>        skip the dictionary, send raw spelling to TTS
  say-it --why <word>            print the dict entry — colored, with source
  say-it --json <word>           print the dict entry as JSON (pipeable)
  say-it --md <word>             print a markdown card (paste into Slack/Notion)
  say-it --copy <word>           copy the respelling to clipboard after speaking

${C_BOLD}Subcommands:${C_RESET}
  say-it list                    list every word in the dictionary
  say-it list --category cli-tool   filter by category
  say-it search <pattern>        grep the dictionary (case-insensitive)
  say-it random                  speak a random word
  say-it daily                   speak today's pronunciation (deterministic)
  say-it quiz                    interactive game — guess the respelling
  say-it compare <word>          audibly compare primary vs alternates
  say-it diff <word1> <word2>    audibly compare two different words
  say-it teach <word>            slow, syllabified pronunciation for learners
  say-it tweet <word>            generate a tweet draft + copy to clipboard
  say-it badge <word>            print a markdown badge w/ audio link
  say-it explain <word>          print extended context for a word
  say-it history                 your recent lookups + streak
  say-it me                      pronounce your git user.name
  say-it import <file.tsv>       merge a file into your local override dict
  say-it stats                   print dictionary statistics
  say-it config                  print resolved paths / version
  say-it update                  pull the latest dict and CLI from upstream
  say-it -l                      list every macOS voice
  say-it -h                      this help

${C_BOLD}Examples:${C_RESET}
  say-it kubectl
  say-it --all GIF
  say-it --why JSON
  say-it --md Pydantic | pbcopy
  say-it tweet kubectl
  say-it quiz
EOF
}

# --- locate the dictionary ----------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DICT_CANDIDATES=(
  "${SAY_IT_DICT:-}"
  "$HOME/.config/say-it/pronunciations.local.tsv"
  "$SCRIPT_DIR/../share/say-it/pronunciations.tsv"
  "$SCRIPT_DIR/../data/pronunciations.tsv"
)
DICT=""
for c in "${DICT_CANDIDATES[@]}"; do
  if [[ -n "$c" && -f "$c" ]]; then DICT="$c"; break; fi
done

CACHE_DIR="${SAY_IT_CACHE_DIR:-$HOME/.cache/say-it}"

# --- subcommands --------------------------------------------------------------
case "${1:-}" in
  update)
    UPSTREAM="${SAY_IT_UPSTREAM:-https://github.com/anzy-renlab-ai/pronounce.git}"
    TMP_DIR="$(mktemp -d -t say-it-update.XXXXXX)"
    trap 'rm -rf "$TMP_DIR"' EXIT
    echo "${C_DIM}Cloning $UPSTREAM …${C_RESET}"
    if ! git clone --depth=1 "$UPSTREAM" "$TMP_DIR/repo" >/dev/null 2>&1; then
      echo "say-it update: git clone failed." >&2; exit 5
    fi
    echo "${C_DIM}Running installer …${C_RESET}"
    if ! (cd "$TMP_DIR/repo" && ./install.sh); then
      echo "say-it update: installer failed." >&2; exit 5
    fi
    echo "${C_GREEN}say-it: updated.${C_RESET}"
    exit 0
    ;;
  list)
    [[ -z "$DICT" ]] && { echo "say-it: dictionary not found" >&2; exit 4; }
    if [[ "${2:-}" == "--category" && -n "${3:-}" ]]; then
      awk -F'\t' -v c="$3" '!/^#/ && NF>=2 && $1 != "" && $1 != "word" && $8 == c { print $1 }' "$DICT" | sort -u
    else
      awk -F'\t' '!/^#/ && NF>=2 && $1 != "" && $1 != "word" { print $1 }' "$DICT" | sort -u
    fi
    exit 0
    ;;
  search)
    [[ -z "$DICT" ]] && { echo "say-it: dictionary not found" >&2; exit 4; }
    pat="${2:-}"
    [[ -z "$pat" ]] && { echo "say-it: usage: say-it search <pattern>" >&2; exit 2; }
    pat_lower="$(printf '%s' "$pat" | tr '[:upper:]' '[:lower:]')"
    awk -v p="$pat_lower" -F'\t' '
      !/^#/ && NF>=2 && $1 != "" && $1 != "word" {
        if (tolower($1) ~ p || tolower($10) ~ p) printf "%-22s %s\n", $1, $10
      }
    ' "$DICT"
    exit 0
    ;;
  random)
    [[ -z "$DICT" ]] && { echo "say-it: dictionary not found" >&2; exit 4; }
    word="$(awk -F'\t' '!/^#/ && NF>=2 && $1 != "" && $1 != "word" { print $1 }' "$DICT" | sort -R | head -1)"
    echo "${C_DIM}Random pick:${C_RESET} ${C_BOLD}$word${C_RESET}"
    exec "$0" "$word"
    ;;
  daily)
    [[ -z "$DICT" ]] && { echo "say-it: dictionary not found" >&2; exit 4; }
    # Deterministic: hash today's date, pick word at that index
    day="$(date +%Y%m%d)"
    idx=$(printf '%s' "$day" | shasum | awk '{print substr($1,1,8)}' | (echo "ibase=16; $(tr a-f A-F)" | bc) 2>/dev/null || echo "0")
    count=$(awk -F'\t' '!/^#/ && NF>=2 && $1 != "" && $1 != "word"' "$DICT" | wc -l | tr -d ' ')
    pick=$((idx % count + 1))
    word="$(awk -F'\t' -v n="$pick" '!/^#/ && NF>=2 && $1 != "" && $1 != "word" { i++; if (i==n) { print $1; exit } }' "$DICT")"
    echo "${C_DIM}Today's pronunciation ($(date +%Y-%m-%d)):${C_RESET} ${C_BOLD}$word${C_RESET}"
    exec "$0" "$word"
    ;;
  stats)
    [[ -z "$DICT" ]] && { echo "say-it: dictionary not found" >&2; exit 4; }
    echo "${C_BOLD}say-it dictionary — stats${C_RESET}"
    echo ""
    total=$(awk -F'\t' '!/^#/ && NF>=2 && $1 != "" && $1 != "word"' "$DICT" | wc -l | tr -d ' ')
    echo "  ${C_CYAN}Total entries:${C_RESET} ${C_BOLD}$total${C_RESET}"
    echo ""
    echo "  ${C_CYAN}By category:${C_RESET}"
    awk -F'\t' '!/^#/ && NF>=8 && $1 != "" && $1 != "word" { print $8 }' "$DICT" | sort | uniq -c | sort -rn | \
      while read -r n c; do printf "    %-20s ${C_BOLD}%4d${C_RESET}  %s\n" "$c" "$n" "$(printf '█%.0s' $(seq 1 $((n/2))))" ; done
    echo ""
    echo "  ${C_CYAN}By confidence:${C_RESET}"
    awk -F'\t' '!/^#/ && NF>=9 && $1 != "" && $1 != "word" { print $9 }' "$DICT" | sort | uniq -c | sort -rn | \
      while read -r n c; do printf "    %-22s ${C_BOLD}%4d${C_RESET}\n" "$c" "$n"; done
    echo ""
    src=$(awk -F'\t' '!/^#/ && NF>=6 && $1 != "" && $1 != "word" && $6 != ""' "$DICT" | wc -l | tr -d ' ')
    echo "  ${C_CYAN}With source URL:${C_RESET} ${C_BOLD}$src${C_RESET}/$total ($((src * 100 / total))%)"
    exit 0
    ;;
  config)
    echo "${C_BOLD}say-it config${C_RESET}"
    echo "  ${C_CYAN}version:${C_RESET}     $VERSION"
    echo "  ${C_CYAN}dict path:${C_RESET}   ${DICT:-<not found>}"
    echo "  ${C_CYAN}cache dir:${C_RESET}   $CACHE_DIR"
    echo "  ${C_CYAN}voice:${C_RESET}       $VOICE"
    echo "  ${C_CYAN}rate:${C_RESET}        $RATE wpm"
    echo "  ${C_CYAN}upstream:${C_RESET}    ${SAY_IT_UPSTREAM:-https://github.com/anzy-renlab-ai/pronounce.git}"
    exit 0
    ;;
  -i|--interactive|repl)
    [[ -z "$DICT" ]] && { echo "say-it: dictionary not found" >&2; exit 4; }
    if ! command -v say >/dev/null 2>&1; then echo "say-it: macOS \`say\` required" >&2; exit 3; fi
    echo "${C_BOLD}🔊 say-it interactive${C_RESET} ${C_DIM}(type a word + enter to play; '?' for help; ctrl-D to quit)${C_RESET}"
    while IFS= read -r -e -p "$(printf '%s>%s ' "${C_ACCENT_FG:-$C_ORANGE}" "$C_RESET")" line; do
      line="$(echo "$line" | sed 's/^ *//;s/ *$//')"
      [[ -z "$line" ]] && continue
      case "$line" in
        "?"|h|help)
          echo "  ${C_DIM}<word>${C_RESET}    play"
          echo "  ${C_DIM}!w <word>${C_RESET}  show why (entry details)"
          echo "  ${C_DIM}!r${C_RESET}        random"
          echo "  ${C_DIM}!s${C_RESET}        stats"
          echo "  ${C_DIM}q${C_RESET}         quit"
          ;;
        q|quit|exit) break ;;
        "!r") "$0" random ;;
        "!s") "$0" stats ;;
        "!w "*) "$0" --why "${line#!w }" ;;
        *) "$0" "$line" ;;
      esac
    done
    echo ""
    echo "${C_DIM}bye.${C_RESET}"
    exit 0
    ;;
  stream)
    # Read stdin, extract words present in the dict, play each in sequence.
    # bash 3.2 compat: no assoc arrays; use awk for membership + seen tracking
    [[ -z "$DICT" ]] && { echo "say-it: dictionary not found" >&2; exit 4; }
    echo "${C_DIM}Reading stdin — extracting dict words…${C_RESET}"
    cat | awk -F'\t' -v dict="$DICT" '
      BEGIN {
        while ((getline l < dict) > 0) {
          if (l ~ /^#/) continue
          n = split(l, c, "\t")
          if (n >= 2 && c[1] != "" && c[1] != "word") {
            lw = tolower(c[1]); known[lw] = c[1]; resp[lw] = c[3]
          }
        }
        close(dict)
      }
      {
        gsub(/[^A-Za-z0-9._-]+/, " ", $0)
        n = split($0, toks, " ")
        for (i = 1; i <= n; i++) {
          t = tolower(toks[i])
          if (t in known && !(t in seen)) {
            seen[t] = 1
            print known[t] "\t" resp[t]
          }
        }
      }
    ' | while IFS=$'\t' read -r word resp; do
      echo "  ▶ $word"
      [[ -z "$resp" ]] && resp="$word"
      tts_speak "$resp."
    done
    exit 0
    ;;
  doctor)
    echo "${C_BOLD}🩺 say-it doctor${C_RESET}"
    ok=0; bad=0
    check() {
      if eval "$2" >/dev/null 2>&1; then printf "  ${C_GREEN}✓${C_RESET} %s\n" "$1"; ok=$((ok+1))
      else printf "  ${C_RED}✗${C_RESET} %s\n" "$1"; bad=$((bad+1)); fi
    }
    check "TTS backend: ${TTS_BACKEND:-none}"  "[[ -n \"$TTS_BACKEND\" ]]"
    check "audio player available"           "command -v afplay || command -v paplay || command -v aplay || command -v play || command -v ffplay"
    check "host platform = $(uname -s)"       "true"
    check "dictionary file readable"     "[[ -r '$DICT' ]]"
    check "cache dir writable"           "[[ -d '$CACHE_DIR' && -w '$CACHE_DIR' ]] || mkdir -p '$CACHE_DIR'"
    check "PATH contains ~/.local/bin"   "echo \":\$PATH:\" | grep -q ':[^:]*/.local/bin:'"
    check "python3 (for did-you-mean & exports)" "command -v python3"
    check "pbcopy (for --copy / tweet)"  "command -v pbcopy"
    check "git (for \`say-it update\`)"   "command -v git"
    echo ""
    if (( bad == 0 )); then echo "${C_GREEN}$ok checks passed — you're good.${C_RESET}"
    else echo "${C_YELLOW}$ok passed, $bad failed.${C_RESET}"; fi
    exit 0
    ;;
  export)
    [[ -z "$DICT" ]] && { echo "say-it: dictionary not found" >&2; exit 4; }
    fmt="${2:-json}"
    case "$fmt" in
      json)
        python3 -c "
import csv, json, sys
keys = ['word','ipa','respelling_us','alt_ipa','alt_respelling_us','source_url','source_label','category','confidence','notes']
with open('$DICT') as f:
    out = []
    for line in f:
        if line.startswith('#') or not line.strip(): continue
        cols = line.rstrip('\n').split('\t')
        if len(cols) < 3 or cols[0] in ('', 'word'): continue
        out.append({k: (cols[i] if i < len(cols) else '') for i, k in enumerate(keys)})
print(json.dumps(out, indent=2, ensure_ascii=False))
"
        ;;
      csv)
        awk -F'\t' 'BEGIN { print "word,ipa,respelling_us,alt_ipa,alt_respelling_us,source_url,source_label,category,confidence,notes" }
          !/^#/ && NF>=3 && $1 != "" && $1 != "word" {
            for(i=1;i<=10;i++){ f=$i; gsub(/"/,"\"\"",f); printf "\"%s\"%s", f, (i<10?",":"\n") }
          }' "$DICT"
        ;;
      md)
        echo "# say-it pronunciation dictionary"
        echo ""
        echo "| Word | Respelling | IPA | Source |"
        echo "|------|-----------|-----|--------|"
        awk -F'\t' '!/^#/ && NF>=3 && $1 != "" && $1 != "word" {
          src = ($7 ? $7 : "—")
          if ($6) src = "["$7"]("$6")"
          gsub(/\|/,"\\|",$3); gsub(/\|/,"\\|",$2)
          printf "| `%s` | %s | `%s` | %s |\n", $1, $3, $2, src
        }' "$DICT"
        ;;
      *) echo "say-it export: format must be json|csv|md" >&2; exit 2 ;;
    esac
    exit 0
    ;;
  benchmark)
    [[ -z "$DICT" ]] && { echo "say-it: dictionary not found" >&2; exit 4; }
    echo "${C_BOLD}⏱  say-it benchmark${C_RESET}"
    n=10
    start=$(python3 -c 'import time; print(time.time())')
    for i in $(seq 1 $n); do
      "$0" --json kubectl >/dev/null 2>&1
    done
    end=$(python3 -c 'import time; print(time.time())')
    elapsed=$(python3 -c "print(round($end - $start, 3))")
    avg=$(python3 -c "print(round(($end - $start) / $n * 1000, 1))")
    echo "  ${C_CYAN}$n × \`--json kubectl\`:${C_RESET} ${C_BOLD}${elapsed}s${C_RESET}  (avg ${avg}ms)"
    cache_count=$(ls "$CACHE_DIR"/*.aiff 2>/dev/null | wc -l | tr -d ' ')
    echo "  ${C_CYAN}cache:${C_RESET}             ${cache_count} files"
    dict_count=$(awk -F'\t' '!/^#/ && NF>=2 && $1 != "" && $1 != "word"' "$DICT" | wc -l | tr -d ' ')
    echo "  ${C_CYAN}dict:${C_RESET}              ${dict_count} entries"
    exit 0
    ;;
  history)
    HIST_FILE="${SAY_IT_HISTORY_FILE:-$HOME/.local/share/say-it/history}"
    if [[ ! -f "$HIST_FILE" ]]; then echo "${C_DIM}No history yet.${C_RESET}"; exit 0; fi
    total=$(wc -l < "$HIST_FILE" | tr -d ' ')
    unique=$(awk '{print $2}' "$HIST_FILE" | sort -u | wc -l | tr -d ' ')
    days=$(awk '{print substr($1, 1, 10)}' "$HIST_FILE" | sort -u | wc -l | tr -d ' ')
    echo "${C_BOLD}say-it history${C_RESET}"
    echo "  ${C_CYAN}lookups${C_RESET}        $total"
    echo "  ${C_CYAN}unique words${C_RESET}   $unique"
    echo "  ${C_CYAN}active days${C_RESET}    $days"
    echo ""
    echo "  ${C_CYAN}recent:${C_RESET}"
    tail -10 "$HIST_FILE" | awk '{printf "    %s  %s\n", $1, $2}'
    exit 0
    ;;
  me)
    name="$(git config user.name 2>/dev/null || echo "$USER")"
    echo "${C_DIM}Pronouncing your git user.name: ${C_RESET}${C_BOLD}$name${C_RESET}"
    exec "$0" --no-dict "$name"
    ;;
  import)
    src="${2:-}"
    [[ -z "$src" || ! -f "$src" ]] && { echo "say-it import: file not found: $src" >&2; exit 2; }
    local_dict="$HOME/.config/say-it/pronunciations.local.tsv"
    mkdir -p "$(dirname "$local_dict")"
    if [[ ! -f "$local_dict" ]]; then
      # Copy header from main dict
      head -27 "$DICT" > "$local_dict"
    fi
    appended=0
    while IFS= read -r line; do
      [[ -z "$line" || "$line" =~ ^# ]] && continue
      word=$(printf '%s' "$line" | awk -F'\t' '{print $1}')
      [[ -z "$word" || "$word" == "word" ]] && continue
      # Skip if already in local dict
      if awk -F'\t' -v w="$word" 'tolower($1)==tolower(w) {f=1} END{exit !f}' "$local_dict" 2>/dev/null; then continue; fi
      echo "$line" >> "$local_dict"
      appended=$((appended+1))
    done < "$src"
    echo "${C_GREEN}Imported $appended entries to $local_dict${C_RESET}"
    echo "  Your local entries take precedence over the bundled dictionary."
    exit 0
    ;;
  badge)
    [[ -z "${2:-}" ]] && { echo "say-it: usage: say-it badge <word>" >&2; exit 2; }
    word="$2"
    slug="$(printf '%s' "$word" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9._-]/-/g')"
    line=$(awk -F'\t' -v w="$(printf '%s' "$word" | tr '[:upper:]' '[:lower:]')" '!/^#/ && tolower($1)==w { print; exit }' "$DICT")
    if [[ -z "$line" ]]; then
      echo "say-it badge: '$word' not in dictionary" >&2; exit 4
    fi
    resp=$(echo "$line" | awk -F'\t' '{print $3}')
    ipa=$(echo "$line" | awk -F'\t' '{print $2}')
    cat <<MD
[![🔊 $word — $resp](https://img.shields.io/badge/🔊-$word%20%E2%86%92%20$(printf '%s' "$resp" | sed 's/ /%20/g')-ff6a3d?style=flat&logo=speakerdeck&logoColor=white)](https://pronounce.renlab.ai/word/$slug)
MD
    if command -v pbcopy >/dev/null 2>&1; then
      cat <<MD | tr -d '\n' | pbcopy
[![🔊 $word — $resp](https://img.shields.io/badge/🔊-$word%20%E2%86%92%20$(printf '%s' "$resp" | sed 's/ /%20/g')-ff6a3d?style=flat&logo=speakerdeck&logoColor=white)](https://pronounce.renlab.ai/word/$slug)
MD
      [[ $QUIET -eq 0 ]] && echo "${C_DIM}(badge markdown copied to clipboard)${C_RESET}" >&2
    fi
    exit 0
    ;;
  explain)
    [[ -z "${2:-}" ]] && { echo "say-it: usage: say-it explain <word>" >&2; exit 2; }
    word="$2"
    line=$(awk -F'\t' -v w="$(printf '%s' "$word" | tr '[:upper:]' '[:lower:]')" '!/^#/ && tolower($1)==w { print; exit }' "$DICT")
    if [[ -z "$line" ]]; then
      echo "say-it explain: '$word' not in dictionary" >&2
      exit 4
    fi
    ipa=$(echo "$line" | awk -F'\t' '{print $2}')
    resp=$(echo "$line" | awk -F'\t' '{print $3}')
    alt=$(echo "$line" | awk -F'\t' '{print $5}')
    src_url=$(echo "$line" | awk -F'\t' '{print $6}')
    src_label=$(echo "$line" | awk -F'\t' '{print $7}')
    cat=$(echo "$line" | awk -F'\t' '{print $8}')
    conf=$(echo "$line" | awk -F'\t' '{print $9}')
    notes=$(echo "$line" | awk -F'\t' '{print $10}')
    echo "${C_BOLD}$word${C_RESET}"
    echo ""
    echo "${C_DIM}Most engineers say it as${C_RESET} ${C_GREEN}\"$resp\"${C_RESET} ${C_DIM}($ipa)${C_RESET}."
    if [[ -n "$alt" ]]; then
      echo ""
      IFS='|' read -ra ALTS <<< "$alt"
      if [[ ${#ALTS[@]} -gt 1 ]]; then
        echo "${C_DIM}Other readings in active use:${C_RESET}"
        for a in "${ALTS[@]}"; do echo "  • ${C_ORANGE}$a${C_RESET}"; done
      else
        echo "${C_DIM}Some also say${C_RESET} ${C_ORANGE}\"${ALTS[0]}\"${C_RESET}."
      fi
    fi
    if [[ -n "$notes" ]]; then echo ""; echo "${C_CYAN}Context:${C_RESET} $notes"; fi
    case "$conf" in
      creator-clarified) blurb="The reading above is documented by the creators / official project page" ;;
      contested) blurb="This reading is one of several in active use — the developer community hasn't converged on a single answer" ;;
      community-consensus) blurb="This is the most common reading among engineers" ;;
    esac
    [[ -n "$blurb" ]] && { echo ""; echo "${C_DIM}${blurb}.${C_RESET}"; }
    if [[ -n "$src_url" ]]; then echo ""; echo "${C_DIM}Source:${C_RESET} ${C_BLUE}$src_label${C_RESET}"; echo "        $src_url"; fi
    echo ""
    echo "${C_DIM}Category:${C_RESET} $cat${C_DIM} · Confidence:${C_RESET} $conf"
    exit 0
    ;;
  playlist)
    [[ -z "$DICT" ]] && { echo "say-it: dictionary not found" >&2; exit 4; }
    shift
    declare -a WORDS=()
    if [[ "${1:-}" == "--category" && -n "${2:-}" ]]; then
      while IFS= read -r w; do WORDS+=("$w"); done < <(awk -F'\t' -v c="$2" '!/^#/ && NF>=8 && $8==c { print $1 }' "$DICT")
      shift 2
    elif [[ "${1:-}" == "--confidence" && -n "${2:-}" ]]; then
      while IFS= read -r w; do WORDS+=("$w"); done < <(awk -F'\t' -v c="$2" '!/^#/ && NF>=9 && $9==c { print $1 }' "$DICT")
      shift 2
    elif [[ "${1:-}" == "--all" ]]; then
      while IFS= read -r w; do WORDS+=("$w"); done < <(awk -F'\t' '!/^#/ && NF>=2 && $1 != "" && $1 != "word" { print $1 }' "$DICT")
      shift
    else
      IFS=',' read -ra WORDS <<< "$1"
      shift
    fi
    [[ ${#WORDS[@]} -eq 0 ]] && { echo "say-it playlist: nothing to play" >&2; exit 2; }
    echo "${C_BOLD}🔊 playlist (${#WORDS[@]} words)${C_RESET}"
    for w in "${WORDS[@]}"; do
      resp=$(awk -F'\t' -v w="$(printf '%s' "$w" | tr '[:upper:]' '[:lower:]')" '!/^#/ && tolower($1)==w {print $3; exit}' "$DICT")
      [[ -z "$resp" ]] && resp="$w"
      echo "  ${C_CYAN}▶${C_RESET} $w  →  $resp"
      tts_speak "$resp. [[slnc 400]] $resp."
      sleep 0.4
    done
    exit 0
    ;;
  minute)
    [[ -z "$DICT" ]] && { echo "say-it: dictionary not found" >&2; exit 4; }
    echo "${C_BOLD}🔊 60-second pronunciation drill${C_RESET}  ${C_DIM}(ctrl-C anytime)${C_RESET}"
    start=$(date +%s)
    count=0
    while :; do
      now=$(date +%s)
      (( now - start >= 60 )) && break
      line=$(awk -F'\t' '!/^#/ && NF>=3 && $1 != "" && $1 != "word"' "$DICT" | sort -R | head -1)
      word=$(echo "$line" | awk -F'\t' '{print $1}')
      resp=$(echo "$line" | awk -F'\t' '{print $3}')
      count=$((count+1))
      elapsed=$((now - start))
      remain=$((60 - elapsed))
      printf "  ${C_DIM}[%2ds]${C_RESET} ${C_CYAN}%-22s${C_RESET}  %s\n" "$remain" "$word" "$resp"
      tts_speak "$resp. [[slnc 300]] $resp."
    done
    echo ""
    echo "${C_GREEN}Done.${C_RESET} ${count} words in 60s."
    exit 0
    ;;
  show)
    [[ -z "${2:-}" ]] && { echo "say-it: usage: say-it show <word>" >&2; exit 2; }
    word="$2"
    slug="$(printf '%s' "$word" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9._-]/-/g')"
    url="https://pronounce.renlab.ai/word/$slug"
    if command -v open >/dev/null 2>&1; then open "$url"
    elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$url"
    else echo "$url"; fi
    exit 0
    ;;
  serve)
    [[ -z "$DICT" ]] && { echo "say-it: dictionary not found" >&2; exit 4; }
    port="${2:-8787}"
    static_dir="$SCRIPT_DIR/../docs"
    if [[ ! -d "$static_dir" ]]; then
      echo "say-it serve: docs/ dir not found at $static_dir" >&2
      echo "(run from a repo checkout; the homebrew-installed CLI doesn't ship the site)" >&2
      exit 4
    fi
    echo "${C_BOLD}🌐 Serving $static_dir on http://localhost:$port${C_RESET}"
    echo "${C_DIM}Open http://localhost:$port/browse.html — ctrl-C to stop.${C_RESET}"
    cd "$static_dir" && python3 -m http.server "$port"
    ;;
  cheatsheet)
    cat <<EOF
${C_BOLD}🔊 say-it cheatsheet${C_RESET}

${C_CYAN}Basics${C_RESET}
  say-it ${C_BOLD}<word>${C_RESET}                   primary × 3 + "or: <alt>" tail
  say-it --solo ${C_BOLD}<word>${C_RESET}            primary only
  say-it --alt ${C_BOLD}<word>${C_RESET}             focus on first alternate
  say-it --alt 2 ${C_BOLD}<word>${C_RESET}           Nth alternate
  say-it --all ${C_BOLD}<word>${C_RESET}             primary AND every alternate

${C_CYAN}Output modes${C_RESET}
  say-it --why ${C_BOLD}<word>${C_RESET}             colored entry w/ source
  say-it --json ${C_BOLD}<word>${C_RESET}            JSON (pipe to jq)
  say-it --md ${C_BOLD}<word>${C_RESET}              markdown card
  say-it --copy ${C_BOLD}<word>${C_RESET}            copy respelling to clipboard
  say-it -o out.aiff ${C_BOLD}<word>${C_RESET}       save audio to file

${C_CYAN}Discovery${C_RESET}
  say-it list                       all words
  say-it list --category cli-tool   filter
  say-it search redis               grep dict
  say-it stats                      bar charts
  say-it random                     random pick
  say-it daily                      today's word
  say-it history                    your lookups + streak

${C_CYAN}Learn${C_RESET}
  say-it quiz                       listen-and-guess game
  say-it minute                     60s pronunciation drill
  say-it teach ${C_BOLD}<word>${C_RESET}             slow + syllabified
  say-it diff ${C_BOLD}<a> <b>${C_RESET}             audibly compare two words
  say-it compare ${C_BOLD}<word>${C_RESET}           primary vs its alternates
  say-it explain ${C_BOLD}<word>${C_RESET}           textual context
  say-it playlist kubectl,nginx     plays a sequence
  say-it playlist --category cli-tool   plays every cli-tool word

${C_CYAN}Share${C_RESET}
  say-it tweet ${C_BOLD}<word>${C_RESET}             tweet draft → clipboard
  say-it badge ${C_BOLD}<word>${C_RESET}             shields.io markdown
  say-it show ${C_BOLD}<word>${C_RESET}              open browser to word page

${C_CYAN}Power${C_RESET}
  say-it me                         your git user.name
  say-it import file.tsv            merge into local override
  say-it serve [port]               local HTTP browse server
  say-it config                     paths + version
  say-it update                     pull latest from upstream

${C_CYAN}Site shortcuts${C_RESET}
  ${C_BOLD}/${C_RESET}  search    ${C_BOLD}r${C_RESET}  random word    ${C_BOLD}space${C_RESET}  play    ${C_BOLD}?${C_RESET}  help

EOF
    exit 0
    ;;
  diff)
    word1="${2:-}"
    word2="${3:-}"
    [[ -z "$word1" || -z "$word2" ]] && { echo "say-it: usage: say-it diff <word1> <word2>" >&2; exit 2; }
    lookup_resp() {
      awk -F'\t' -v w="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" '!/^#/ && tolower($1)==w {print $3; exit}' "$DICT"
    }
    r1=$(lookup_resp "$word1"); [[ -z "$r1" ]] && r1="$word1"
    r2=$(lookup_resp "$word2"); [[ -z "$r2" ]] && r2="$word2"
    echo "${C_DIM}Comparing:${C_RESET}"
    echo "  ${C_GREEN}$word1${C_RESET}  →  $r1"
    echo "  ${C_ORANGE}$word2${C_RESET}  →  $r2"
    BODY="$r1. [[slnc 400]] $r1. [[slnc 700]] $r2. [[slnc 400]] $r2."
    tts_speak "$BODY"
    exit 0
    ;;
  teach)
    [[ -z "${2:-}" ]] && { echo "say-it: usage: say-it teach <word>" >&2; exit 2; }
    word="$2"
    line=$(awk -F'\t' -v w="$(printf '%s' "$word" | tr '[:upper:]' '[:lower:]')" '!/^#/ && tolower($1)==w { print; exit }' "$DICT")
    if [[ -z "$line" ]]; then
      echo "say-it teach: '$word' not in dictionary; sending raw spelling" >&2
      resp="$word"
    else
      resp=$(echo "$line" | awk -F'\t' '{print $3}')
    fi
    echo "${C_BOLD}Teaching mode for ${word}${C_RESET}"
    echo "${C_DIM}1. The whole word, slowly${C_RESET}"
    tts_speak "$resp." 110
    echo "${C_DIM}2. Each syllable${C_RESET}"
    # Treat each space-separated piece as a syllable
    for syl in $resp; do
      printf "  ${C_CYAN}%s${C_RESET}\n" "$syl"
      tts_speak "$syl." 90
      sleep 0.3
    done
    echo "${C_DIM}3. Normal speed, twice${C_RESET}"
    tts_speak "$resp. [[slnc 500]] $resp." 175
    exit 0
    ;;
  quiz)
    [[ -z "$DICT" ]] && { echo "say-it: dictionary not found" >&2; exit 4; }
    if ! command -v say >/dev/null 2>&1; then echo "say-it: macOS \`say\` required for quiz" >&2; exit 3; fi
    echo "${C_BOLD}🔊 say-it quiz${C_RESET} — listen and type the respelling. ctrl-C to quit."
    score=0; rounds=0
    while :; do
      rounds=$((rounds+1))
      line=$(awk -F'\t' '!/^#/ && NF>=3 && $1 != "" && $1 != "word"' "$DICT" | sort -R | head -1)
      word=$(echo "$line" | awk -F'\t' '{print $1}')
      resp=$(echo "$line" | awk -F'\t' '{print $3}')
      echo ""
      echo "${C_DIM}#$rounds — ${C_RESET}${C_CYAN}plays in 3, 2, 1…${C_RESET}"
      sleep 0.7
      tts_speak "$resp. $resp. $resp."
      printf "  guess (or type the actual word): ${C_BOLD}"
      read -r guess
      printf "${C_RESET}"
      lower_word=$(echo "$word" | tr '[:upper:]' '[:lower:]')
      lower_guess=$(echo "$guess" | tr '[:upper:]' '[:lower:]')
      lower_resp=$(echo "$resp" | tr '[:upper:]' '[:lower:]')
      if [[ "$lower_guess" == "$lower_word" ]] || [[ "$lower_guess" == "$lower_resp" ]]; then
        score=$((score+1))
        echo "  ${C_GREEN}✓ correct — $word (\"$resp\")${C_RESET}  score: ${C_BOLD}$score/$rounds${C_RESET}"
      else
        echo "  ${C_YELLOW}✗ was $word (\"$resp\")${C_RESET}  score: ${C_DIM}$score/$rounds${C_RESET}"
      fi
    done
    ;;
esac

# --- flag parsing -------------------------------------------------------------
while [[ $# -gt 0 ]]; do
  case "$1" in
    -v|--voice)    VOICE="$2"; shift 2 ;;
    -n|--times)    TIMES="$2"; shift 2 ;;
    -r|--rate)     RATE="$2"; shift 2 ;;
    -o|--output)   SAVE="$2"; shift 2 ;;
    --alt)
                   USE_ALT=1
                   if [[ "${2:-}" =~ ^[0-9]+$ ]]; then ALT_IDX="$2"; shift 2
                   else shift; fi ;;
    --all)         USE_ALL=1; shift ;;
    --solo)        USE_SOLO=1; shift ;;
    --no-dict)     NO_DICT=1; shift ;;
    --why)         SHOW_WHY=1; shift ;;
    --json)        OUTPUT_JSON=1; QUIET=1; shift ;;
    --md|--markdown) OUTPUT_MD=1; QUIET=1; shift ;;
    --copy)        COPY_CLIP=1; shift ;;
    -q|--quiet)    QUIET=1; shift ;;
    -l|--list)
      if [[ "$TTS_BACKEND" == "say" ]]; then say -v "?"; 
      elif [[ "$TTS_BACKEND" == "espeak-ng" ]]; then espeak-ng --voices=en;
      elif [[ "$TTS_BACKEND" == "espeak" ]]; then espeak --voices=en;
      else echo "say-it: no TTS backend; nothing to list." >&2; fi
      exit 0 ;;
    -h|--help)     usage; exit 0 ;;
    -V|--version)  echo "say-it $VERSION"; exit 0 ;;
    --)            shift; break ;;
    -*)            echo "say-it: unknown flag: $1" >&2; usage; exit 2 ;;
    *)             break ;;
  esac
done

# compare and tweet subcommands need a word arg from positional
SUBCMD=""
if [[ "${1:-}" == "compare" || "${1:-}" == "tweet" ]]; then
  SUBCMD="$1"
  shift
fi

[[ $# -eq 0 ]] && { usage; exit 2; }
if ! command -v say >/dev/null 2>&1; then
  echo "say-it: macOS \`say\` not found. Windows/Linux backends are on the roadmap." >&2
  exit 3
fi

WORD="$*"

# --- dictionary lookup --------------------------------------------------------
SEP=$'\x1f'
lookup() {
  local word="$1"
  local lower
  lower="$(printf '%s' "$word" | tr '[:upper:]' '[:lower:]')"
  awk -v w="$lower" -v s="$SEP" -F'\t' '
    !/^#/ && NF>=3 && $1 != "" && $1 != "word" {
      if (tolower($1) == w) {
        printf "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", $2, s, $3, s, $4, s, $5, s, $6, s, $7, s, $8, s, $9, s, $10
        exit
      }
    }
  ' "$DICT"
}

# Did-you-mean — Python difflib for proper fuzzy match
did_you_mean() {
  local word="$1"
  awk -F'\t' '!/^#/ && NF>=2 && $1 != "" && $1 != "word" { print $1 }' "$DICT" | \
    python3 -c "
import sys, difflib
w = sys.argv[1].lower()
words = [l.strip() for l in sys.stdin if l.strip()]
matches = difflib.get_close_matches(w, [x.lower() for x in words], n=3, cutoff=0.65)
# Map back to canonical case
canon = {x.lower(): x for x in words}
for m in matches: print(canon.get(m, m))
" "$word"
}

ENTRY=""
if [[ $NO_DICT -eq 0 && -n "$DICT" ]]; then
  ENTRY="$(lookup "$WORD" || true)"
fi

IPA=""; RESP=""; ALT_IPA=""; ALT_RESP=""; SRC_URL=""; SRC_LABEL=""; CATEGORY=""; CONFIDENCE=""; NOTES=""
if [[ -n "$ENTRY" ]]; then
  IFS="$SEP" read -r IPA RESP ALT_IPA ALT_RESP SRC_URL SRC_LABEL CATEGORY CONFIDENCE NOTES <<< "$ENTRY"
fi

# --- output modes (early exit for --json/--md, must precede speaking) ---------

emit_json() {
  python3 -c "
import json, sys, urllib.parse
def alt_list(s):
    return [x for x in s.split('|') if x] if s else []
word = '$WORD'
slug = ''.join(c if c.isalnum() or c in '._-' else '-' for c in word.lower())
out = {
  'word': word,
  'ipa': '$IPA',
  'respelling_us': '$RESP',
  'alt_ipa': alt_list('$ALT_IPA'),
  'alt_respelling_us': alt_list('$ALT_RESP'),
  'source_url': '$SRC_URL',
  'source_label': '$SRC_LABEL',
  'category': '$CATEGORY',
  'confidence': '$CONFIDENCE',
  'notes': '''$NOTES''',
  'url': f'https://pronounce.renlab.ai/word/{slug}' if word else None,
  'in_dict': bool('$RESP'),
}
print(json.dumps(out, indent=2, ensure_ascii=False))
"
}

emit_md() {
  cat <<MD
### How to pronounce \`$WORD\`

**$RESP** — \`$IPA\`
MD
  if [[ -n "$ALT_RESP" ]]; then
    echo ""
    echo "**Alternates:**"
    IFS='|' read -ra ALTS <<< "$ALT_RESP"
    IFS='|' read -ra ALT_IPAS <<< "$ALT_IPA"
    for i in "${!ALTS[@]}"; do
      ai="${ALT_IPAS[$i]:-}"
      echo "- $(printf '%s' "${ALTS[$i]}")${ai:+ \`$ai\`}"
    done
  fi
  if [[ -n "$NOTES" ]]; then echo ""; echo "_${NOTES}_"; fi
  if [[ -n "$SRC_URL" ]]; then
    echo ""
    echo "📎 [${SRC_LABEL:-source}]($SRC_URL)"
  fi
  echo ""
  echo "🔊 \`say-it $WORD\`  ·  🌐 https://pronounce.renlab.ai/word/$(echo "$WORD" | tr '[:upper:]' '[:lower:]')"
}

emit_tweet() {
  local text
  if [[ -n "$RESP" ]]; then
    text="TIL: $WORD is pronounced \"$RESP\""
    [[ -n "$ALT_RESP" ]] && text="$text (or \"${ALT_RESP%%|*}\")"
    text="$text 🔊"
    [[ -n "$SRC_LABEL" ]] && text="$text — ${SRC_LABEL}"
    text="$text · https://pronounce.renlab.ai/word/$(echo "$WORD" | tr '[:upper:]' '[:lower:]')"
  else
    text="How do you pronounce $WORD? https://pronounce.renlab.ai"
  fi
  printf '%s\n' "$text"
  if command -v pbcopy >/dev/null 2>&1; then
    printf '%s' "$text" | pbcopy
    [[ $QUIET -eq 0 ]] && echo "${C_DIM}(copied to clipboard — paste into x.com)${C_RESET}" >&2
  fi
}

# --- handle compare/tweet/--json/--md early -----------------------------------

if [[ "$SUBCMD" == "tweet" ]]; then
  emit_tweet
  exit 0
fi

if [[ $OUTPUT_JSON -eq 1 ]]; then
  emit_json
  exit 0
fi

if [[ $OUTPUT_MD -eq 1 ]]; then
  emit_md
  exit 0
fi

# --- show why before speaking -------------------------------------------------
print_why() {
  if [[ -n "$ENTRY" ]]; then
    local conf_color="$C_CYAN"
    case "$CONFIDENCE" in
      creator-clarified) conf_color="$C_GREEN" ;;
      contested) conf_color="$C_ORANGE" ;;
      community-consensus) conf_color="$C_BLUE" ;;
    esac
    echo "${C_BOLD}${WORD}${C_RESET}  ${C_DIM}┄${C_RESET}  ${C_GREEN}${RESP}${C_RESET}  ${C_DIM}${IPA}${C_RESET}"
    [[ -n "$ALT_RESP" ]] && echo "  ${C_DIM}or:${C_RESET}    ${C_ORANGE}$(echo "$ALT_RESP" | tr '|' '\n' | sed "s/^/  /" | tr '\n' ' ')${C_RESET}"
    [[ -n "$CATEGORY" ]] && echo "  ${C_DIM}category${C_RESET}  $CATEGORY"
    [[ -n "$CONFIDENCE" ]] && echo "  ${C_DIM}confidence${C_RESET} ${conf_color}$CONFIDENCE${C_RESET}"
    [[ -n "$SRC_LABEL" ]] && echo "  ${C_DIM}source${C_RESET}    $SRC_LABEL"
    [[ -n "$SRC_URL" ]] && echo "  ${C_DIM}url${C_RESET}       $SRC_URL"
    [[ -n "$NOTES" ]] && echo "  ${C_DIM}notes${C_RESET}     $NOTES"
  else
    echo "${C_YELLOW}$WORD: not in dictionary; sending raw spelling to TTS.${C_RESET}"
    suggestions=$(did_you_mean "$WORD")
    if [[ -n "$suggestions" ]]; then
      echo "${C_DIM}did you mean:${C_RESET}"
      while read -r s; do echo "  • ${C_CYAN}$s${C_RESET}"; done <<< "$suggestions"
    fi
  fi
}

[[ $SHOW_WHY -eq 1 ]] && print_why

# --- decide what to speak -----------------------------------------------------
PAUSE_INTRA="[[slnc 400]]"
PAUSE_INTER="[[slnc 700]]"

build_reps() {
  local resp="$1"; local n="$2"
  local out=""
  for ((i=0; i<n; i++)); do
    out+="$resp. "
    (( i < n - 1 )) && out+="$PAUSE_INTRA "
  done
  printf '%s' "$out"
}

build_audible_body() {
  local primary_speech
  if [[ -n "$RESP" ]]; then primary_speech="$RESP"; else primary_speech="$WORD"; fi

  if [[ $USE_ALT -eq 1 ]]; then
    [[ -z "$ALT_RESP" ]] && { echo "say-it: '$WORD' has no recorded alternates." >&2; exit 4; }
    IFS='|' read -ra ALTS <<< "$ALT_RESP"
    local idx=$((ALT_IDX - 1))
    (( idx < 0 || idx >= ${#ALTS[@]} )) && { echo "say-it: --alt $ALT_IDX: only ${#ALTS[@]} alternate(s) for '$WORD'" >&2; exit 4; }
    build_reps "${ALTS[$idx]}" "$TIMES"; return 0
  fi

  local body
  body="$(build_reps "$primary_speech" "$TIMES")"
  if [[ $USE_SOLO -eq 0 && -n "$ALT_RESP" ]]; then
    local rep_each=1
    [[ $USE_ALL -eq 1 ]] && rep_each=$TIMES
    IFS='|' read -ra ALTS <<< "$ALT_RESP"
    for a in "${ALTS[@]}"; do
      body+="$PAUSE_INTER or: "
      body+="$(build_reps "$a" "$rep_each")"
    done
  fi
  printf '%s' "$body"
}

# --- audio cache --------------------------------------------------------------
cache_path() {
  local body="$1"
  local key
  key="$(printf '%s\x1f%s\x1f%s\x1f%s' "$VOICE" "$RATE" "$TIMES" "$body" | shasum | awk '{print $1}')"
  printf '%s/%s.aiff' "$CACHE_DIR" "$key"
}

# --- compare subcommand: speak primary, alts in sequence with spoken labels ---
if [[ "$SUBCMD" == "compare" ]]; then
  if [[ -z "$RESP" ]]; then
    echo "say-it compare: '$WORD' not in dictionary." >&2
    suggestions=$(did_you_mean "$WORD")
    [[ -n "$suggestions" ]] && { echo "did you mean:"; echo "$suggestions"; }
    exit 4
  fi
  if [[ -z "$ALT_RESP" ]]; then
    echo "${C_DIM}'$WORD' has only one recorded reading. Playing primary × 3:${C_RESET}"
    BODY="$(build_reps "$RESP" "$TIMES")"
  else
    echo "${C_BOLD}🔊 comparing readings for '$WORD'${C_RESET}"
    echo "  ${C_GREEN}primary:${C_RESET}   $RESP"
    IFS='|' read -ra ALTS <<< "$ALT_RESP"
    for i in "${!ALTS[@]}"; do
      echo "  ${C_ORANGE}alt #$((i+1)):${C_RESET}   ${ALTS[$i]}"
    done
    BODY="primary. $PAUSE_INTRA $RESP. $RESP. $PAUSE_INTER"
    for i in "${!ALTS[@]}"; do
      BODY+="alternate $((i+1)). $PAUSE_INTRA ${ALTS[$i]}. ${ALTS[$i]}. $PAUSE_INTER"
    done
  fi
  if [[ -n "$SAVE" ]]; then
    tts_save "$BODY" "$SAVE"
    echo "Saved: $SAVE"
  else
    tts_speak "$BODY"
  fi
  exit 0
fi

# --- play / save with cache ---------------------------------------------------
BODY="$(build_audible_body)"
if [[ -n "$SAVE" ]]; then
  tts_save "$BODY" "$SAVE"
  echo "Saved: $SAVE"
else
  mkdir -p "$CACHE_DIR" 2>/dev/null || true
  CACHED="$(cache_path "$BODY")"
  if [[ -f "$CACHED" && -s "$CACHED" ]]; then
    audio_play "$CACHED"
  else
    if [[ -d "$CACHE_DIR" && -w "$CACHE_DIR" ]]; then
      tts_save "$BODY" "$CACHED"
      audio_play "$CACHED"
    else
      tts_speak "$BODY"
    fi
  fi
fi

# --- record history -----------------------------------------------------------
HIST_FILE="${SAY_IT_HISTORY_FILE:-$HOME/.local/share/say-it/history}"
if [[ "${SAY_IT_NO_HISTORY:-0}" != "1" ]]; then
  mkdir -p "$(dirname "$HIST_FILE")" 2>/dev/null
  printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%S)" "$WORD" >> "$HIST_FILE" 2>/dev/null || true
fi

# --- post-play actions --------------------------------------------------------
if [[ $COPY_CLIP -eq 1 && -n "$RESP" ]] && command -v pbcopy >/dev/null 2>&1; then
  printf '%s' "$RESP" | pbcopy
  [[ $QUIET -eq 0 ]] && echo "${C_DIM}(respelling \"$RESP\" copied to clipboard)${C_RESET}"
fi
