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

VERSION="0.1.23" # 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時間（秒）
SPARKLE_FRAMES=30       # sparkle_animate のデフォルトフレーム数
SPARKLE_INTERVAL=0.05   # sparkle/rainbow 共通の sleep 間隔（秒）

# アニメーション中の中断でカーソルが非表示のままにならないよう、EXIT で復元
# 非TTY環境では何もしない（stdout を汚さない）
trap 'rainbow_animate_stop; [ -t 1 ] && printf "\033[?25h" 2>/dev/null || true' EXIT

# カラー出力の設定（非TTY / NO_COLOR 環境では無効化）
RAINBOW_PID=""
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, lightness 0-100) から ANSI truecolor エスケープを生成
# 彩度は固定 (S=80%)
hsl_fg() {
  local h=$1
  local l=${2:-65}
  # HSL to RGB (簡易版: 6セクターに分けて線形補間)
  local s=80
  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
  if [ $((h_section % 2)) -eq 0 ]; then
    x=$((c * h_remainder / 60))
  else
    x=$((c * (60 - h_remainder) / 60))
  fi
  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"
}

# "git harvest" の各文字に割り当てる固定色相 (ultrathink 風レインボー)
# g=0, i=36, t=72, ' '=-, h=108, a=144, r=180, v=216, e=252, s=288, t=324
HEADER_TEXT="git harvest"
HEADER_HUES="0 36 72 -1 108 144 180 216 252 288 324"

# キラキラの1フレームを描画 (text, hues)
# hues はスペース区切りの色相リスト (-1 = スペース)
sparkle_frame() {
  local text="$1" hues="$2"
  printf '\r'
  local idx=0
  for hue in $hues; do
    if [ "$hue" = "-1" ]; then
      printf ' '
    else
      local rand=$((RANDOM % 100)) lightness
      if [ "$rand" -lt 10 ]; then
        lightness=$((85 + RANDOM % 11))
      elif [ "$rand" -lt 25 ]; then
        lightness=$((60 + RANDOM % 16))
      else
        lightness=$((35 + RANDOM % 16))
      fi
      hsl_fg "$hue" "$lightness"
      printf '%s' "${text:$idx:1}"
    fi
    idx=$((idx + 1))
  done
  printf '\033[0m'
}

# レインボーキラキラアニメーションをバックグラウンドで開始 (TTY時のみ)
rainbow_animate_start() {
  [ "$USE_COLOR" = false ] && return
  printf '\033[?25l'
  (
    while true; do
      sparkle_frame "$HEADER_TEXT" "$HEADER_HUES"
      sleep "$SPARKLE_INTERVAL"
    done
  ) &
  RAINBOW_PID=$!
}

# バックグラウンドのレインボーアニメーションを停止
rainbow_animate_stop() {
  if [ -n "$RAINBOW_PID" ]; then
    kill "$RAINBOW_PID" 2>/dev/null || true
    wait "$RAINBOW_PID" 2>/dev/null || true
    RAINBOW_PID=""
  fi
  if [ "$USE_COLOR" = true ]; then
    printf '\r\033[2K'
    printf '\033[?25h'
  fi
}

# 緑グラデーションロゴを表示 (下→上で明るくなる)
print_logo() {
  local lines=(
    ' \|/                     \|/'
    '\\|//  ~~~~~~~~~~~~~~~  \\|//'
    ' \|/        G I T        \|/'
    '  |     H A R V E S T     |'
    ' _|_______________________|_'
  )
  local colors=("120;230;120" "90;200;90" "60;170;60" "40;145;40" "20;120;20")
  printf '\n'
  if [ "$USE_COLOR" = false ]; then
    for line in "${lines[@]}"; do printf '%s\n' "$line"; done
  else
    for i in "${!lines[@]}"; do
      printf '\033[38;2;%sm%s\033[0m\n' "${colors[$i]}" "${lines[$i]}"
    done
  fi
  printf '\n'
}

# 静的レインボーヘッダーを表示
rainbow_static() {
  if [ "$USE_COLOR" = false ]; then
    printf '%s' "$HEADER_TEXT"
  else
    local idx=0
    for hue in $HEADER_HUES; do
      if [ "$hue" = "-1" ]; then
        printf ' '
      else
        hsl_fg "$hue"
        printf '%s' "${HEADER_TEXT:$idx:1}"
      fi
      idx=$((idx + 1))
    done
    printf '\033[0m'
  fi
  printf '\n'
}

# テキストをキラキラ光らせるアニメーション (TTY時のみ)
# 引数: text hue [frames] [sleep]
sparkle_animate() {
  local text="$1" hue="$2" frames="${3:-$SPARKLE_FRAMES}" interval="${4:-$SPARKLE_INTERVAL}"
  local len=${#text}
  if [ "$USE_COLOR" = false ] || [ "$len" -eq 0 ]; then
    printf '%s' "$text"
    return
  fi
  # 全文字に同じ hue を割り当てた hues 文字列を生成
  local hues="" i=0
  while [ "$i" -lt "$len" ]; do
    [ "$i" -gt 0 ] && hues="$hues "
    if [ "${text:$i:1}" = ' ' ]; then
      hues="$hues-1"
    else
      hues="$hues$hue"
    fi
    i=$((i + 1))
  done
  printf '\033[?25l'
  local frame=0
  while [ "$frame" -lt "$frames" ]; do
    sparkle_frame "$text" "$hues"
    sleep "$interval"
    frame=$((frame + 1))
  done
  printf '\033[?25h'
}

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

# インストール経路を判定し、自動更新チェックの対象かどうかを返す
# 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

# リモートのデフォルトブランチ名を取得する (例: 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
}

# 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 "\nWorktrees\n"
      found=true
    fi

    if [ "$DELETE_ALL" = true ]; then
      # --all: 全 worktree を削除対象にする
      if [ "$DRY_RUN" = true ]; then
        printf "[WILL DELETE]  %s\n" "$wt"
        DELETED_COUNT=$((DELETED_COUNT + 1))
      else
        git worktree remove --force "$wt"
        printf "[DELETED]      %s\n" "$wt"
        DELETED_COUNT=$((DELETED_COUNT + 1))
      fi
    elif echo "$merged_branches" | grep -qxF "$branch"; then
      # マージ済み: 未コミット変更があるかチェック
      if has_uncommitted_changes "$wt"; then
        printf "[GROWING]      %s (uncommitted changes)\n" "$wt"
      elif [ "$DRY_RUN" = true ]; then
        printf "[WILL DELETE]  %s\n" "$wt"
        DELETED_COUNT=$((DELETED_COUNT + 1))
      else
        git worktree remove "$wt"
        printf "[DELETED]      %s\n" "$wt"
        DELETED_COUNT=$((DELETED_COUNT + 1))
      fi
    elif echo "$no_unique_branches" | grep -qxF "$branch"; then
      printf "[GROWING]      %s (no unique commits)\n" "$wt"
    else
      printf "[GROWING]      %s (not merged)\n" "$wt"
    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 "\nBranches\n"
      found=true
    fi

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

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

  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

  if [ "$DRY_RUN" = true ]; then
    printf "\nDry run mode - nothing will be deleted\n"
  fi
  printf '\n'
  rainbow_animate_start

  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
    rainbow_animate_stop
    rainbow_static
    printf "Nothing to harvest. All clean!\n\n"
  else
    # worktree が参照中のブランチは削除できないため、worktree を先に削除する
    DELETED_COUNT=0
    rainbow_animate_stop
    rainbow_static
    cleanup_worktrees "$base" "$merged_branches" "$no_unique_branches"
    cleanup_branches "$base" "$merged_branches" "$no_unique_branches"

    # サマリー表示
    printf "\n\n"
    if [ "$DELETED_COUNT" -gt 0 ]; then
      sparkle_animate "Harvested!" 120
    else
      printf "Nothing to harvest. All growing!"
    fi
    printf '\n\n\n'
  fi

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

main
