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

VERSION="0.1.8" # x-release-please-version

# アニメーション中の中断でカーソルが非表示のままにならないよう、EXIT で復元
trap 'printf "\033[?25h" 2>/dev/null' EXIT

# カラー出力の設定（非TTY / NO_COLOR 環境では無効化）
USE_COLOR=false
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
  USE_COLOR=true
  BOLD='\033[1m'
  GREEN='\033[32m'
  DIM='\033[2m'
  RESET='\033[0m'
else
  BOLD='' GREEN='' DIM='' RESET=''
fi

# HSL (hue 0-360) から ANSI truecolor エスケープを生成
# 彩度・明度は固定 (S=80%, L=65%) でレインボー向けに最適化
hsl_fg() {
  local h=$1
  # HSL to RGB (簡易版: 6セクターに分けて線形補間)
  local s=80 l=65
  local c=$(((100 - (2 * l - 100 > 0 ? 2 * l - 100 : 100 - 2 * l)) * s / 100))
  local h_section=$((h / 60))
  local h_remainder=$((h % 60))
  local x=$((c * (60 - (h_remainder > 30 ? h_remainder * 2 - 60 : 60 - h_remainder * 2)) / 60))
  local m=$(((l * 255 / 100) - (c * 255 / 200)))
  local r g b
  case $h_section in
  0) r=$((c * 255 / 100 + m)) g=$((x * 255 / 100 + m)) b=$m ;;
  1) r=$((x * 255 / 100 + m)) g=$((c * 255 / 100 + m)) b=$m ;;
  2) r=$m g=$((c * 255 / 100 + m)) b=$((x * 255 / 100 + m)) ;;
  3) r=$m g=$((x * 255 / 100 + m)) b=$((c * 255 / 100 + m)) ;;
  4) r=$((x * 255 / 100 + m)) g=$m b=$((c * 255 / 100 + m)) ;;
  *) r=$((c * 255 / 100 + m)) g=$m b=$((x * 255 / 100 + m)) ;;
  esac
  # clamp 0-255
  if [ "$r" -lt 0 ]; then r=0; elif [ "$r" -gt 255 ]; then r=255; fi
  if [ "$g" -lt 0 ]; then g=0; elif [ "$g" -gt 255 ]; then g=255; fi
  if [ "$b" -lt 0 ]; then b=0; elif [ "$b" -gt 255 ]; then b=255; fi
  printf '\033[38;2;%d;%d;%dm' "$r" "$g" "$b"
}

# テキストをレインボーアニメーションで表示 (TTY時のみ)
rainbow_animate() {
  local text="$1"
  local len=${#text}
  if [ "$USE_COLOR" = false ] || [ "$len" -eq 0 ]; then
    printf '  %s' "$text"
    return
  fi
  # カーソルを非表示
  printf '\033[?25l'
  local frames=20
  local frame=0
  while [ "$frame" -lt "$frames" ]; do
    printf '\r  '
    local i=0
    local offset=$((frame * 18))
    while [ "$i" -lt "$len" ]; do
      local char="${text:$i:1}"
      if [ "$char" = ' ' ]; then
        printf ' '
      else
        local hue=$(((i * 360 / len - offset + 3600) % 360))
        printf '%s%s' "$(hsl_fg "$hue")" "$char"
      fi
      i=$((i + 1))
    done
    printf '\033[0m'
    sleep 0.04
    frame=$((frame + 1))
  done
  # カーソルを再表示
  printf '\033[?25h'
}

# テキストを緑ベースでキラキラ光らせるアニメーション (TTY時のみ)
sparkle_animate() {
  local text="$1"
  local len=${#text}
  if [ "$USE_COLOR" = false ] || [ "$len" -eq 0 ]; then
    printf '  %s' "$text"
    return
  fi
  printf '\033[?25l'
  local frames=25
  local frame=0
  while [ "$frame" -lt "$frames" ]; do
    printf '\r  '
    local i=0
    while [ "$i" -lt "$len" ]; do
      local char="${text:$i:1}"
      if [ "$char" = ' ' ]; then
        printf ' '
      else
        # ランダムに明度を変える (40-100%)、一部の文字を白に近づけて「キラッ」
        local rand=$((RANDOM % 100))
        local lightness
        if [ "$rand" -lt 10 ]; then
          # 10% の確率で白に近い光 (明度 85-95%)
          lightness=$((85 + RANDOM % 11))
        elif [ "$rand" -lt 25 ]; then
          # 15% の確率で明るい緑 (明度 60-75%)
          lightness=$((60 + RANDOM % 16))
        else
          # 通常の緑 (明度 35-50%)
          lightness=$((35 + RANDOM % 16))
        fi
        # 緑 (hue=120) 固定、彩度80%、明度可変
        local s=80 l=$lightness
        local c=$(((100 - (2 * l - 100 > 0 ? 2 * l - 100 : 100 - 2 * l)) * s / 100))
        local x=0
        local m=$(((l * 255 / 100) - (c * 255 / 200)))
        local r=$m g=$((c * 255 / 100 + m)) b=$m
        if [ "$r" -lt 0 ]; then r=0; elif [ "$r" -gt 255 ]; then r=255; fi
        if [ "$g" -lt 0 ]; then g=0; elif [ "$g" -gt 255 ]; then g=255; fi
        if [ "$b" -lt 0 ]; then b=0; elif [ "$b" -gt 255 ]; then b=255; fi
        printf '\033[38;2;%d;%d;%dm%s' "$r" "$g" "$b" "$char"
      fi
      i=$((i + 1))
    done
    printf '\033[0m'
    sleep 0.05
    frame=$((frame + 1))
  done
  printf '\033[?25h'
}

DRY_RUN=false

case "${1:-}" in
-v | --version)
  echo "git-harvest v$VERSION"
  exit 0
  ;;
-n | --dry-run)
  DRY_RUN=true
  ;;
-h | --help)
  cat <<'HELP'
git-harvest - Clean up merged 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
HELP
  exit 0
  ;;
esac

# リモートのデフォルトブランチ名を取得する (例: 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 が削除可能かチェックする
can_remove_worktree() {
  local wt="$1"
  # メインワーキングツリーは削除不可
  local main_wt
  main_wt=$(git worktree list --porcelain | grep -m1 '^worktree ' | sed 's/^worktree //')
  if [ "$wt" = "$main_wt" ]; then return 1; fi
  # 未コミットの変更がある場合は削除不可
  if ! git -C "$wt" diff --quiet HEAD 2>/dev/null; then return 1; fi
  if [ -n "$(git -C "$wt" ls-files --others --exclude-standard 2>/dev/null)" ]; then return 1; fi
  return 0
}

# マージ済みブランチに紐づく worktree を削除する
cleanup_worktrees() {
  local base="$1"
  local merged_branches="$2"
  local found=false

  # 全 worktree のパスを取得してループ
  while read -r wt; do
    # その worktree が指しているブランチ名を取得
    branch=$(git -C "$wt" rev-parse --abbrev-ref HEAD 2>/dev/null) || continue
    [ "$branch" = "$base" ] && continue
    if echo "$merged_branches" | grep -qw "$branch"; then
      if { [ "$DRY_RUN" = true ] && can_remove_worktree "$wt"; } || { [ "$DRY_RUN" = false ] && git worktree remove "$wt" 2>/dev/null; }; then
        if [ "$found" = false ]; then
          printf "\n  Worktrees\n"
          found=true
        fi
        if [ "$DRY_RUN" = true ]; then
          printf "    [WILL DELETE] %s\n" "$wt"
        else
          printf "    [DELETED] %s\n" "$wt"
        fi
      fi
    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}$"
}

# マージ済みローカルブランチを削除する
cleanup_branches() {
  local base="$1"
  local merged_branches="$2"
  local found=false

  while read -r branch; do
    [ -z "$branch" ] && continue
    [ "$branch" = "$base" ] && continue
    # dry-run: worktree にチェックアウト中のブランチは削除できないのでスキップ
    if [ "$DRY_RUN" = true ] && is_checked_out_in_worktree "$branch"; then continue; fi
    if { [ "$DRY_RUN" = true ] && git rev-parse --verify "$branch" >/dev/null 2>&1; } || { [ "$DRY_RUN" = false ] && git branch -D "$branch" >/dev/null 2>&1; }; then
      if [ "$found" = false ]; then
        printf "\n  Branches\n"
        found=true
      fi
      if [ "$DRY_RUN" = true ]; then
        printf "  [WILL DELETE] %s\n" "$branch"
      else
        printf "  [DELETED] %s\n" "$branch"
      fi
    fi
  done <<<"$merged_branches"
  # リモートで削除済みの追跡ブランチを整理
  if [ "$DRY_RUN" = false ]; then git fetch --prune || true; fi
}

main() {
  local base
  base=$(default_branch)

  if [ "$DRY_RUN" = true ]; then
    printf "\n  Dry run mode - nothing will be deleted\n"
  fi
  printf '\n'
  sparkle_animate "git harvest"
  printf '\n'

  # デフォルトブランチにマージ済みのローカルブランチ一覧を取得
  # git branch --merged は squash merge を検出できないため、
  # commit-tree で仮想 squash コミットを作成し git cherry で比較する
  local merged_branches
  merged_branches=$(
    while read -r branch; do
      [ "$branch" = "$base" ] && continue
      # 通常マージ: ブランチが base の祖先であれば完全マージ済み
      if git merge-base --is-ancestor "$branch" "$base" 2>/dev/null; then
        echo "$branch"
        continue
      fi
      # squash マージ: commit-tree で仮想 squash コミットを作成し git cherry で比較
      local merge_base
      merge_base=$(git merge-base "$base" "$branch" 2>/dev/null) || continue
      local squash_commit
      squash_commit=$(git commit-tree "$branch^{tree}" -p "$merge_base" -m "_" 2>/dev/null) || continue
      if [ "$(git cherry "$base" "$squash_commit" 2>/dev/null | grep -c '^+')" -eq 0 ]; then
        echo "$branch"
      fi
    done < <(git branch | sed 's/^[*+ ]*//')
  )

  if [ -z "$merged_branches" ]; then
    printf "  Nothing to harvest. All clean!\n\n"
    exit 0
  fi

  # worktree が参照中のブランチは削除できないため、worktree を先に削除する
  cleanup_worktrees "$base" "$merged_branches"
  cleanup_branches "$base" "$merged_branches"

  # サマリー表示
  printf "\n\n"
  rainbow_animate "Harvested!"
  printf '\n\n\n'
}

main
