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

VERSION="0.1.28" # x-release-please-version
REPO="nozomiishii/git-harvest"
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/git-harvest"
CACHE_FILE="$CACHE_DIR/latest-version"
CHECK_INTERVAL=86400 # 24時間（秒）

# git-harvest のブランドカラー #C0FF39 rgb(192, 255, 57)
BRAND_COLOR='192;255;57'

# カラー出力可否（非 TTY / NO_COLOR では無効）
USE_COLOR=false
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
  USE_COLOR=true
fi

# ---------------------------------------------------------------------------
# 表示ヘルパー
# ---------------------------------------------------------------------------

# ブランドカラー (truecolor)
hi() {
  if [ "$USE_COLOR" = true ]; then
    printf '\033[38;2;%sm%s\033[0m' "$BRAND_COLOR" "$1"
  else
    printf '%s' "$1"
  fi
}

# bold
bold() {
  if [ "$USE_COLOR" = true ]; then
    printf '\033[1m%s\033[0m' "$1"
  else
    printf '%s' "$1"
  fi
}

# dim（補助情報）
dim() {
  if [ "$USE_COLOR" = true ]; then
    printf '\033[2m%s\033[0m' "$1"
  else
    printf '%s' "$1"
  fi
}

# bold + ブランドカラー (wordmark 用)
brand() {
  if [ "$USE_COLOR" = true ]; then
    printf '\033[1;38;2;%sm%s\033[0m' "$BRAND_COLOR" "$1"
  else
    printf '%s' "$1"
  fi
}

# $HOME を ~ に置換してパスを短縮
relpath() {
  local p="$1"
  case "$p" in
    "$HOME") printf '~' ;;
    "$HOME"/*) printf '~%s' "${p#"$HOME"}" ;;
    *) printf '%s' "$p" ;;
  esac
}

# 現在時刻をミリ秒で取得（portable: bash 5+ / python3 / perl / date の順）
now_ms() {
  if [ -n "${EPOCHREALTIME:-}" ]; then
    awk -v t="$EPOCHREALTIME" 'BEGIN{split(t,p,"."); printf "%d", p[1]*1000 + substr(p[2] "000",1,3)}'
  elif command -v python3 >/dev/null 2>&1; then
    python3 -c 'import time; print(int(time.time()*1000))' 2>/dev/null
  elif command -v perl >/dev/null 2>&1; then
    perl -MTime::HiRes -E 'printf("%d", Time::HiRes::time()*1000)' 2>/dev/null
  else
    echo $(($(date +%s) * 1000))
  fi
}

# 経過時間を "X.Ys" 形式で（start_ms を受け取り、現在時刻との差を返す）
elapsed_str() {
  local start_ms=$1 end_ms
  end_ms=$(now_ms)
  local diff=$((end_ms - start_ms))
  local s=$((diff / 1000))
  local ds=$(((diff % 1000) / 100))
  printf '%d.%ds' "$s" "$ds"
}

# ブランドカラー単色のロゴ
print_logo() {
  local lines=(
    ' \|/                     \|/'
    '\\|//  ~~~~~~~~~~~~~~~  \\|//'
    ' \|/        G I T        \|/'
    '  |     H A R V E S T     |'
    ' _|_______________________|_'
  )
  printf '\n'
  for line in "${lines[@]}"; do
    if [ "$USE_COLOR" = true ]; then
      printf '\033[38;2;%sm%s\033[0m\n' "$BRAND_COLOR" "$line"
    else
      printf '%s\n' "$line"
    fi
  done
  printf '\n'
}

# ブランド wordmark を表示
wordmark() {
  printf '\n%s\n' "$(brand 'git harvest')"
}

# ✓ で removed 表示
print_removed() {
  local item="$1"
  printf '  %s  %s\n' "$(hi ✓)" "$item"
}

# → で would-remove 表示
print_will_remove() {
  local item="$1"
  printf '  %s  %s\n' "$(hi →)" "$item"
}

# · で growing 表示（保護理由付き、path と reason の間に最低 2 spaces 確保）
print_growing() {
  local item="$1" reason="$2"
  local pad=$((38 - ${#item}))
  [ $pad -lt 2 ] && pad=2
  if [ "$USE_COLOR" = true ]; then
    printf '  \033[2m·  %s%*s%s\033[0m\n' "$item" "$pad" '' "$reason"
  else
    printf '  ·  %s%*s%s\n' "$item" "$pad" '' "$reason"
  fi
}

# ---------------------------------------------------------------------------
# バージョン更新チェック
# ---------------------------------------------------------------------------

# インストール経路を判定し、自動更新チェックの対象かどうかを返す
# Homebrew / npm 経由のインストールはパッケージマネージャーに任せる
should_check_update() {
  local resolved
  resolved=$(realpath "$0" 2>/dev/null || echo "$0")
  case "$resolved" in
    */Cellar/*|*/homebrew/*) return 1 ;;
    */node_modules/*)        return 1 ;;
  esac
  return 0
}

# バックグラウンドで GitHub API から最新バージョンを取得しキャッシュに保存
check_update_async() {
  should_check_update || return 0

  # TTL チェック: キャッシュが新しければスキップ
  if [ -f "$CACHE_FILE" ]; then
    local last_check now
    last_check=$(stat -c %Y "$CACHE_FILE" 2>/dev/null \
              || stat -f %m "$CACHE_FILE" 2>/dev/null || echo 0)
    now=$(date +%s)
    if (( now - last_check < CHECK_INTERVAL )); then
      return 0
    fi
  fi

  mkdir -p "$CACHE_DIR"
  # バックグラウンドで取得（メイン処理をブロックしない）
  (
    local tag
    tag=$(curl -fsSL --max-time 5 \
      "https://api.github.com/repos/${REPO}/releases/latest" 2>/dev/null \
      | grep -o '"tag_name":"[^"]*"' | head -1 \
      | sed 's/"tag_name":"v\{0,1\}\([^"]*\)"/\1/')
    if [ -n "$tag" ]; then
      echo "$tag" > "$CACHE_FILE"
    fi
  ) &
  UPDATE_CHECK_PID=$!
}

# メイン処理完了後に呼び出し、新しいバージョンがあれば stderr に通知
show_update_notification() {
  should_check_update || return 0
  [ -f "$CACHE_FILE" ] || return 0

  local latest
  latest=$(cat "$CACHE_FILE" 2>/dev/null)
  [ -z "$latest" ] && return 0
  [ "$latest" = "$VERSION" ] && return 0

  cat >&2 <<EOF

Update available: v${VERSION} -> v${latest}
Run: git-harvest --update

EOF
}

# 自分自身を最新版に置き換える
self_update() {
  local install_path
  install_path=$(realpath "$0" 2>/dev/null || echo "$0")

  # Homebrew / npm 経由の場合はパッケージマネージャーを案内
  case "$install_path" in
    */Cellar/*|*/homebrew/*)
      echo "Installed via Homebrew. Please update with:" >&2
      echo "  brew upgrade git-harvest" >&2
      exit 1
      ;;
    */node_modules/*)
      echo "Installed via npm. Please update with:" >&2
      echo "  npm update -g git-harvest" >&2
      exit 1
      ;;
  esac

  # 書き込み権限チェック
  if ! [ -w "$install_path" ]; then
    echo "Error: No write permission for $install_path" >&2
    echo "  Retry with: sudo git-harvest --update" >&2
    exit 1
  fi

  echo "Updating git-harvest..."
  local tmp
  tmp=$(mktemp)
  # 最新リリースをダウンロードして置き換え
  if curl -fsSL --max-time 30 \
    "https://github.com/${REPO}/releases/latest/download/git-harvest" \
    -o "$tmp" 2>/dev/null; then
    chmod +x "$tmp"
    mv "$tmp" "$install_path"
    rm -f "$CACHE_FILE"
    local new_version
    new_version=$("$install_path" --version)
    echo "Updated to ${new_version}"
  else
    rm -f "$tmp"
    echo "Error: Download failed" >&2
    exit 1
  fi
  exit 0
}

# ---------------------------------------------------------------------------
# 引数パース
# ---------------------------------------------------------------------------

DRY_RUN=false
DELETE_ALL=false

while [ $# -gt 0 ]; do
  case "$1" in
  -v | --version)
    echo "git-harvest v$VERSION"
    exit 0
    ;;
  logo)
    print_logo
    exit 0
    ;;
  --update)
    self_update
    ;;
  -n | --dry-run)
    DRY_RUN=true
    ;;
  --all)
    DELETE_ALL=true
    ;;
  -h | --help)
    cat <<'HELP'
git-harvest - Clean up branches and worktrees

Usage: git-harvest [options]

Options:
  -h, --help     Show this help
  -v, --version  Show version
  -n, --dry-run  Show what would be deleted without actually deleting
  --all          Delete all branches and worktrees except the default branch
  --update       Update to the latest version

Subcommands:
  logo           Show the git-harvest logo
HELP
    exit 0
    ;;
  *)
    echo "Unknown option: $1" >&2
    exit 1
    ;;
  esac
  shift
done

# ---------------------------------------------------------------------------
# git / Claude 連携
# ---------------------------------------------------------------------------

# リモートのデフォルトブランチ名を取得する (例: main, master)
# 未設定の場合はリモートから自動取得を試みる
default_branch() {
  local branch
  branch=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||')

  # 未設定の場合、リモートに問い合わせて自動設定
  if [ -z "$branch" ]; then
    git remote set-head origin --auto >/dev/null 2>&1
    branch=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||')
  fi

  if [ -z "$branch" ]; then
    echo "Could not determine default branch" >&2
    exit 1
  fi

  echo "$branch"
}

# worktree に未コミットの変更があるかチェックする (0=あり, 1=なし)
has_uncommitted_changes() {
  local wt="$1"
  if ! git -C "$wt" diff --quiet HEAD 2>/dev/null; then return 0; fi
  if ! git -C "$wt" diff --quiet --cached 2>/dev/null; then return 0; fi
  if [ -n "$(git -C "$wt" ls-files --others --exclude-standard 2>/dev/null)" ]; then return 0; fi
  return 1
}

# Claude CLI が走行中セッションを記録するディレクトリ。
# 上書き用 env: GIT_HARVEST_CLAUDE_SESSIONS_DIR (テスト / power user 用)
claude_sessions_dir() {
  echo "${GIT_HARVEST_CLAUDE_SESSIONS_DIR:-$HOME/.claude/sessions}"
}

# Claude desktop app のデータディレクトリ。複数 OS の候補を順に試す。
# 上書き用 env: GIT_HARVEST_CLAUDE_APP_DIR
# 該当なしの場合は空文字を返す。
claude_app_dir() {
  if [ -n "${GIT_HARVEST_CLAUDE_APP_DIR:-}" ]; then
    echo "$GIT_HARVEST_CLAUDE_APP_DIR"
    return
  fi
  local c
  for c in \
    "$HOME/Library/Application Support/Claude" \
    "$HOME/.config/Claude" \
    "$HOME/.local/share/Claude" \
    "${APPDATA:-}/Claude"; do
    [ -n "$c" ] && [ -d "$c" ] && { echo "$c"; return; }
  done
  echo ""
}

# パスを canonical (symlink 解決済み) に正規化する。
# git は worktree path を canonical で保持するが、JSON 内の cwd / worktreePath は
# ユーザーが指定したまま記録される可能性があるため、両者を同一表記に揃える必要がある。
# (例: macOS の /var/folders は /private/var/folders への symlink)
canonical_path() {
  local p="$1"
  [ -z "$p" ] && return
  if [ -d "$p" ]; then
    (cd "$p" 2>/dev/null && pwd -P) 2>/dev/null || echo "$p"
  else
    echo "$p"
  fi
}

# 走行中の claude プロセスがこの worktree で動いているか (0=あり, 1=なし)。
# ~/.claude/sessions/<pid>.json の cwd が canonical 一致し、pid が生存していれば true。
has_running_claude_session() {
  local wt="$1"
  local sessions_dir
  sessions_dir=$(claude_sessions_dir)
  [ -d "$sessions_dir" ] || return 1

  local wt_canon
  wt_canon=$(canonical_path "$wt")
  [ -z "$wt_canon" ] && return 1

  local f cwd cwd_canon pid
  for f in "$sessions_dir"/*.json; do
    [ -f "$f" ] || continue
    # JSON は通常 single-line だが、pretty-print された場合にも備えて key/value 間の
    # whitespace を許容する
    cwd=$(sed -nE 's/.*"cwd"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p' "$f" | head -1)
    [ -z "$cwd" ] && continue
    cwd_canon=$(canonical_path "$cwd")
    [ "$cwd_canon" = "$wt_canon" ] || continue
    pid=$(sed -nE 's/.*"pid"[[:space:]]*:[[:space:]]*([0-9]+).*/\1/p' "$f" | head -1)
    [ -z "$pid" ] && continue
    if kill -0 "$pid" 2>/dev/null; then
      return 0
    fi
  done
  return 1
}

# Claude Code app の "active" (= 未 archive) セッションがこの worktree にあるか (0=あり, 1=なし)。
# claude-code-sessions/**/local_*.json で worktreePath が canonical 一致し、
# isArchived: true でないものが1つでもあれば true。
has_active_claude_session() {
  local wt="$1"
  local app_dir
  app_dir=$(claude_app_dir)
  [ -n "$app_dir" ] || return 1
  local sessions_root="$app_dir/claude-code-sessions"
  [ -d "$sessions_root" ] || return 1

  local wt_canon
  wt_canon=$(canonical_path "$wt")
  [ -z "$wt_canon" ] && return 1

  local f wp wp_canon
  while IFS= read -r f; do
    [ -f "$f" ] || continue
    # JSON は通常 single-line だが、pretty-print された場合にも備えて key/value 間の
    # whitespace を許容する
    wp=$(sed -nE 's/.*"worktreePath"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p' "$f" | head -1)
    [ -z "$wp" ] && continue
    wp_canon=$(canonical_path "$wp")
    [ "$wp_canon" = "$wt_canon" ] || continue
    if ! grep -Eq '"isArchived"[[:space:]]*:[[:space:]]*true' "$f" 2>/dev/null; then
      return 0
    fi
  done < <(find "$sessions_root" -type f -name 'local_*.json' 2>/dev/null)
  return 1
}

# ---------------------------------------------------------------------------
# 整理ロジック
# ---------------------------------------------------------------------------

# worktree の状態を表示・削除する (DELETED_COUNT をインクリメント)
cleanup_worktrees() {
  local base="$1"
  local merged_branches="$2"
  local no_unique_branches="$3"
  local found=false

  # メインワーキングツリーのパスを取得
  local main_wt
  main_wt=$(git worktree list --porcelain | grep --color=never -m1 '^worktree ' | sed 's/^worktree //')

  # 全 worktree のパスを取得してループ
  while read -r wt; do
    # メインワーキングツリーは表示しない
    [ "$wt" = "$main_wt" ] && continue
    # その worktree が指しているブランチ名を取得
    local branch
    branch=$(git -C "$wt" rev-parse --abbrev-ref HEAD 2>/dev/null) || continue
    # デフォルトブランチの worktree は表示しない
    [ "$branch" = "$base" ] && continue

    if [ "$found" = false ]; then
      printf '\n%s\n' "$(bold Worktrees)"
      found=true
    fi

    local rel
    rel=$(relpath "$wt")

    if [ "$DELETE_ALL" = true ]; then
      # --all: 全保護判定をスキップして worktree を削除する
      if [ "$DRY_RUN" = true ]; then
        print_will_remove "$rel"
        DELETED_COUNT=$((DELETED_COUNT + 1))
      else
        git worktree remove --force "$wt"
        print_removed "$rel"
        DELETED_COUNT=$((DELETED_COUNT + 1))
      fi
    elif has_running_claude_session "$wt"; then
      print_growing "$rel" "session running"
    elif echo "$merged_branches" | grep -qxF "$branch"; then
      # マージ済み: 未コミット変更 → claude active session → 削除可能 の順に判定
      if has_uncommitted_changes "$wt"; then
        print_growing "$rel" "uncommitted changes"
      elif has_active_claude_session "$wt"; then
        print_growing "$rel" "active claude session"
      elif [ "$DRY_RUN" = true ]; then
        print_will_remove "$rel"
        DELETED_COUNT=$((DELETED_COUNT + 1))
      else
        git worktree remove "$wt"
        print_removed "$rel"
        DELETED_COUNT=$((DELETED_COUNT + 1))
      fi
    elif echo "$no_unique_branches" | grep -qxF "$branch"; then
      print_growing "$rel" "no unique commits"
    else
      print_growing "$rel" "not merged"
    fi
  done < <(git worktree list --porcelain | grep --color=never '^worktree ' | sed 's/^worktree //')
  # 既に存在しない worktree の管理情報を削除
  if [ "$DRY_RUN" = false ]; then git worktree prune; fi
}

# ブランチが worktree にチェックアウトされているかチェックする
is_checked_out_in_worktree() {
  local branch="$1"
  git worktree list --porcelain | grep -q "^branch refs/heads/${branch}$"
}

# ブランチが現在の HEAD かチェックする
is_current_head() {
  local branch="$1"
  local current
  current=$(git symbolic-ref --short HEAD 2>/dev/null) || return 1
  [ "$branch" = "$current" ]
}

# ブランチの状態を表示・削除する (DELETED_COUNT をインクリメント)
cleanup_branches() {
  local base="$1"
  local merged_branches="$2"
  local no_unique_branches="$3"
  local found=false

  # ブランチ削除では独自コミットなしも削除対象（worktree がなければ残骸）
  local deletable_branches="$merged_branches"
  if [ -n "$no_unique_branches" ]; then
    deletable_branches="${deletable_branches:+$deletable_branches
}$no_unique_branches"
  fi

  while read -r branch; do
    [ -z "$branch" ] && continue
    [ "$branch" = "$base" ] && continue

    if [ "$found" = false ]; then
      printf '\n%s\n' "$(bold Branches)"
      found=true
    fi

    if [ "$DELETE_ALL" = true ]; then
      # --all: 全ブランチを削除対象にする（事前チェック済みなのでチェックアウト中は dry-run のみ）
      if [ "$DRY_RUN" = true ]; then
        print_will_remove "$branch"
        DELETED_COUNT=$((DELETED_COUNT + 1))
      else
        git branch -D "$branch" >/dev/null 2>&1
        print_removed "$branch"
        DELETED_COUNT=$((DELETED_COUNT + 1))
      fi
    elif echo "$deletable_branches" | grep -qxF "$branch"; then
      # 削除対象: チェックアウト中かチェック
      if is_current_head "$branch" || is_checked_out_in_worktree "$branch"; then
        print_growing "$branch" "currently checked out"
      elif [ "$DRY_RUN" = true ]; then
        print_will_remove "$branch"
        DELETED_COUNT=$((DELETED_COUNT + 1))
      else
        git branch -D "$branch" >/dev/null 2>&1
        print_removed "$branch"
        DELETED_COUNT=$((DELETED_COUNT + 1))
      fi
    else
      print_growing "$branch" "not merged"
    fi
  done < <(git branch | sed 's/^[*+ ]*//' | grep -v '^(')
  # リモートで削除済みの追跡ブランチを整理
  if [ "$DRY_RUN" = false ]; then git fetch --prune >/dev/null 2>&1 || true; fi
}

# ---------------------------------------------------------------------------
# main
# ---------------------------------------------------------------------------

main() {
  # バックグラウンドでバージョンチェックを開始
  UPDATE_CHECK_PID=""
  check_update_async

  local start_ms
  start_ms=$(now_ms)

  local base
  base=$(default_branch)

  # --all の事前チェック: デフォルトブランチ以外をチェックアウト中ならエラー終了
  if [ "$DELETE_ALL" = true ] && [ "$DRY_RUN" = false ]; then
    local current
    current=$(git symbolic-ref --short HEAD 2>/dev/null) || true
    if [ -n "$current" ] && [ "$current" != "$base" ]; then
      printf "\nError: Cannot delete branch '%s' (currently checked out)\n" "$current" >&2
      printf "Run: git checkout %s && git-harvest --all\n\n" "$base" >&2
      exit 1
    fi
  fi

  wordmark

  if [ "$DRY_RUN" = true ]; then
    printf '\n%s\n' "$(dim 'Dry run mode - nothing will be deleted')"
  fi

  local all_branches
  all_branches=$(git branch | sed 's/^[*+ ]*//' | grep -v '^(')

  local merged_branches=""
  local no_unique_branches=""

  # --all の場合はマージ検出をスキップ（全ブランチが削除対象）
  if [ "$DELETE_ALL" = false ]; then
    # デフォルトブランチにマージ済みのローカルブランチ一覧を取得
    # 検出手段: 1) first-parent 上のコミット一致、2) ancestor チェック、
    # 3) 仮想 squash + git cherry、4) patch-id ベースの cherry-pick フォールバック
    local first_parent_commits
    first_parent_commits=$(git rev-list --first-parent "$base" 2>/dev/null) || true

    while read -r branch; do
      [ "$branch" = "$base" ] && continue
      # 独自コミットがないブランチ（HEAD が base の first-parent 上にある）
      local branch_head
      branch_head=$(git rev-parse "$branch" 2>/dev/null) || continue
      if echo "$first_parent_commits" | grep -q "^${branch_head}$"; then
        no_unique_branches="${no_unique_branches:+$no_unique_branches
}$branch"
        continue
      fi
      # 通常マージ: ブランチが base の祖先であれば完全マージ済み
      if git merge-base --is-ancestor "$branch" "$base" 2>/dev/null; then
        merged_branches="${merged_branches:+$merged_branches
}$branch"
        continue
      fi
      # squash マージ: commit-tree で仮想 squash コミットを作成し git cherry で比較
      # merge-base が見つからない場合（orphaned ブランチ）はスキップして cherry-pick フォールバックへ
      local merge_base
      merge_base=$(git merge-base "$base" "$branch" 2>/dev/null) || true
      if [ -n "$merge_base" ]; then
        local squash_commit
        squash_commit=$(git commit-tree "$branch^{tree}" -p "$merge_base" -m "_" 2>/dev/null) || true
        if [ -n "$squash_commit" ]; then
          local cherry_out
          cherry_out=$(git cherry "$base" "$squash_commit" 2>/dev/null) || true
          if [ -n "$cherry_out" ] && [ "$(echo "$cherry_out" | grep -c '^+')" -eq 0 ]; then
            merged_branches="${merged_branches:+$merged_branches
}$branch"
            continue
          fi
        fi
      fi
      # cherry-pick フォールバック: patch-id ベースで全コミットを比較
      # orphaned ブランチ（履歴書き換えで共通祖先なし）や仮想 squash が一致しないケースをカバー
      local cherry_pick_unique
      cherry_pick_unique=$(git log --cherry-pick --right-only --no-merges --oneline "$base"..."$branch" 2>/dev/null)
      if [ -z "$cherry_pick_unique" ]; then
        merged_branches="${merged_branches:+$merged_branches
}$branch"
      fi
    done <<<"$all_branches"
  fi

  # デフォルトブランチのみ（他にブランチも linked worktree もない）場合
  local linked_wt_count
  linked_wt_count=$(git worktree list --porcelain | grep -c '^worktree ' || true)
  linked_wt_count=$((linked_wt_count - 1)) # メインワーキングツリーを除外

  local other_branch_count=0
  while read -r b; do
    [ -n "$b" ] && [ "$b" != "$base" ] && other_branch_count=$((other_branch_count + 1))
  done <<<"$all_branches"

  if [ "$other_branch_count" -eq 0 ] && [ "$linked_wt_count" -eq 0 ]; then
    printf '\n%s\n\n' "$(dim '· Nothing to harvest. All clean.')"
  else
    # worktree が参照中のブランチは削除できないため、worktree を先に削除する
    DELETED_COUNT=0
    cleanup_worktrees "$base" "$merged_branches" "$no_unique_branches"
    cleanup_branches "$base" "$merged_branches" "$no_unique_branches"

    # サマリー表示
    printf '\n'
    if [ "$DELETED_COUNT" -gt 0 ]; then
      local item_word elapsed
      if [ "$DELETED_COUNT" -eq 1 ]; then item_word="item"; else item_word="items"; fi
      elapsed=$(elapsed_str "$start_ms")
      if [ "$DRY_RUN" = true ]; then
        printf '%s %s  %s\n\n' \
          "$(hi →)" \
          "$(bold "Would harvest $DELETED_COUNT $item_word")" \
          "$(dim "in $elapsed")"
      else
        printf '%s %s  %s\n\n' \
          "$(hi ✓)" \
          "$(bold "Harvested $DELETED_COUNT $item_word")" \
          "$(dim "in $elapsed")"
      fi
    else
      printf '%s\n\n' "$(dim '· Nothing to harvest. All growing.')"
    fi
  fi

  # バックグラウンドのバージョンチェックを待機して通知表示
  if [ -n "$UPDATE_CHECK_PID" ]; then
    wait "$UPDATE_CHECK_PID" 2>/dev/null || true
  fi
  show_update_notification
}

main
