#!/usr/bin/env bash
# extract-conventions.sh
#
# Phase 1c helper for /multi-agent:analysis.
# Scans a repo and emits 12 convention buckets as a single JSON object on stdout.
#
# Usage:
#   extract-conventions.sh <repo-path> <platform>
# Platform: ios | android | backend | frontend
#
# Output schema (per bucket):
#   { pattern, example, confidence, evidenceFiles[], alternativeCandidates[] }
# confidence: high (>=5) | medium (3-4) | low (2 or mixed) | none

set -euo pipefail

REPO_PATH="${1:-}"
PLATFORM="${2:-}"

if [ -z "$REPO_PATH" ] || [ -z "$PLATFORM" ]; then
  echo "usage: extract-conventions.sh <repo-path> <platform>" >&2
  exit 1
fi

if [ ! -d "$REPO_PATH" ]; then
  echo "extract-conventions: repo path not found: $REPO_PATH" >&2
  exit 1
fi

case "$PLATFORM" in
  ios|android|backend|frontend) ;;
  *)
    echo "extract-conventions: invalid platform: $PLATFORM (expected ios|android|backend|frontend)" >&2
    exit 1
    ;;
esac

if ! command -v jq >/dev/null 2>&1; then
  echo "extract-conventions: jq is required but not installed" >&2
  exit 1
fi

# count_matches: single-line numeric grep -c. The bare `grep -c || echo 0`
# idiom emits "0" twice on zero matches, which turns the $((...)) counter
# arithmetic below into a silent syntax error that zeroes the bucket.
# shellcheck source=count-lib.sh disable=SC1091
. "$(cd "$(dirname "$0")" && pwd)/count-lib.sh"

# ---- global timing budget -----------------------------------------------------
SCRIPT_START_EPOCH=$(date +%s)
GLOBAL_BUDGET_SEC=180
# Bucket-level soft timeout. Buckets typically complete sub-second; 10s is
# generous slack. Lowered from 30 to fail fast on pathological scan trees
# while still leaving headroom for cold caches.
BUCKET_TIMEOUT_SEC=10

budget_exhausted() {
  local now elapsed
  now=$(date +%s)
  elapsed=$((now - SCRIPT_START_EPOCH))
  [ "$elapsed" -ge "$GLOBAL_BUDGET_SEC" ]
}

# ---- skip dirs ----------------------------------------------------------------
SKIP_PRUNE_ARGS=(
  -path '*/.worktrees' -prune -o
  -path '*/.build' -prune -o
  -path '*/DerivedData' -prune -o
  -path '*/Pods' -prune -o
  -path '*/node_modules' -prune -o
  -path '*/.next' -prune -o
  -path '*/build' -prune -o
  -path '*/.gradle' -prune -o
  -path '*/vendor' -prune -o
  -path '*/.git' -prune -o
)

# ---- scan roots per platform --------------------------------------------------
# Honours two override channels so callers can widen the scan tree without
# touching the script:
#   1. $EXTRACT_CONV_EXTRA_ROOTS  whitespace-separated list of extra directories
#                                  (resolved relative to $REPO_PATH).
#   2. $REPO_PATH/.gitmodules     each registered submodule path is auto-added,
#                                  enabling multi-repo convention extraction.
scan_roots() {
  local roots=()
  case "$PLATFORM" in
    ios)
      for d in Domains Common Core CrossDomains App Features Modules; do
        [ -d "$REPO_PATH/$d" ] && roots+=("$REPO_PATH/$d")
      done
      ;;
    android)
      for d in app/src feature core common features; do
        [ -d "$REPO_PATH/$d" ] && roots+=("$REPO_PATH/$d")
      done
      ;;
    backend)
      for d in src api services app domain; do
        [ -d "$REPO_PATH/$d" ] && roots+=("$REPO_PATH/$d")
      done
      ;;
    frontend)
      for d in src app components features lib hooks pages; do
        [ -d "$REPO_PATH/$d" ] && roots+=("$REPO_PATH/$d")
      done
      ;;
  esac

  # Env override: whitespace-separated extra roots.
  if [ -n "${EXTRACT_CONV_EXTRA_ROOTS:-}" ]; then
    local extra
    for extra in $EXTRACT_CONV_EXTRA_ROOTS; do
      [ -d "$REPO_PATH/$extra" ] && roots+=("$REPO_PATH/$extra")
    done
  fi

  # Auto-include submodule paths declared in .gitmodules.
  if [ -f "$REPO_PATH/.gitmodules" ] && command -v git >/dev/null 2>&1; then
    local sub_path
    while IFS= read -r sub_path; do
      [ -z "$sub_path" ] && continue
      [ -d "$REPO_PATH/$sub_path" ] && roots+=("$REPO_PATH/$sub_path")
    done < <(git -C "$REPO_PATH" config --file .gitmodules --get-regexp '^submodule\..*\.path$' 2>/dev/null | awk '{print $2}')
  fi

  printf '%s\n' "${roots[@]}"
}

# Build a `find` command that walks scan roots with skip prunes applied.
# Caller passes additional expressions after the prune chain.
# macOS ships bash 3.2 which lacks `mapfile`, so we collect into an array
# via the legacy while-read pattern.
run_find() {
  local roots=()
  local r
  while IFS= read -r r; do
    [ -n "$r" ] && roots+=("$r")
  done < <(scan_roots)
  if [ "${#roots[@]}" -eq 0 ]; then
    return 0
  fi
  # No explicit -print anywhere in this expression means find falls back to
  # its own implicit print on the WHOLE expression - which fires for every
  # true branch of the -prune OR-chain too, including the pruned directories
  # themselves (their contents are still correctly skipped; the dir path
  # itself was not). An explicit -print, implicit-AND'd onto the caller's own
  # criteria as the OR-chain's last branch, only fires on that branch.
  find "${roots[@]}" \
    "${SKIP_PRUNE_ARGS[@]}" \
    "$@" -print 2>/dev/null || true
}

# Count how many of the newline-separated paths in $1 contain a line matching
# grep pattern $2. `xargs grep -l` here used to word-split on whitespace by
# default, so any path with a space in it was silently mis-scanned as several
# nonexistent partial paths (a false-negative on real matches, not an error -
# stderr was already redirected to /dev/null). Reading line-by-line handles
# that correctly since each line is one whole path regardless of embedded
# spaces.
count_grep_hits() {
  local list="$1" pattern="$2" n=0 f
  while IFS= read -r f; do
    [ -z "$f" ] && continue
    grep -lq "$pattern" "$f" 2>/dev/null && n=$((n + 1))
  done <<< "$list"
  printf '%s' "$n"
}

# ---- confidence classifier ----------------------------------------------------
confidence_for() {
  local count="$1"
  if [ "$count" -ge 5 ]; then echo "high"
  elif [ "$count" -ge 3 ]; then echo "medium"
  elif [ "$count" -ge 2 ]; then echo "low"
  elif [ "$count" -ge 1 ]; then echo "low"
  else echo "none"; fi
}

# ---- bucket emitter -----------------------------------------------------------
# emit_bucket pattern example confidence evidence_json alternatives_json
emit_bucket() {
  local pattern="$1" example="$2" confidence="$3" evidence_json="$4" alts_json="$5"
  jq -n \
    --arg pattern "$pattern" \
    --arg example "$example" \
    --arg confidence "$confidence" \
    --argjson evidenceFiles "$evidence_json" \
    --argjson alternativeCandidates "$alts_json" \
    '{pattern:$pattern, example:$example, confidence:$confidence, evidenceFiles:$evidenceFiles, alternativeCandidates:$alternativeCandidates}'
}

empty_bucket() {
  emit_bucket "" "" "none" "[]" "[]"
}

# Convert a newline-delimited list of file paths into a JSON array (max 5 items).
files_to_json() {
  local input="$1"
  if [ -z "$input" ]; then echo "[]"; return; fi
  printf '%s\n' "$input" | head -5 | jq -R . | jq -s .
}

# Top-N suffix frequency from a stream of file basenames.
# stdin: basenames (one per line)
# arg1: regex (POSIX ERE) capturing the suffix in group 1
# stdout: lines "count<TAB>suffix" sorted desc
suffix_freq() {
  local re="$1"
  grep -Eo "$re" 2>/dev/null \
    | sort \
    | uniq -c \
    | sort -rn \
    | sed -E 's/^ *([0-9]+) +/\1\t/'
}

# Run a bucket function with a soft timeout. We wrap the function in a subshell
# and use a watchdog so we stay compatible with macOS bash 3.2.
run_bucket_with_timeout() {
  local fn="$1"
  if budget_exhausted; then
    empty_bucket
    return 0
  fi
  local tmp_out
  tmp_out=$(mktemp -t convbucket.XXXXXX)
  (
    # Buckets do heuristic scanning; transient nonzero exits from grep -c with
    # zero matches must not abort the bucket. Relax errexit/nounset/pipefail
    # inside the subshell so each bucket can decide its own fallback.
    set +eu +o pipefail
    "$fn" >"$tmp_out" 2>/dev/null
  ) &
  local pid=$!
  local waited=0
  while kill -0 "$pid" 2>/dev/null; do
    if [ "$waited" -ge "$BUCKET_TIMEOUT_SEC" ]; then
      kill "$pid" 2>/dev/null || true
      sleep 1
      kill -9 "$pid" 2>/dev/null || true
      cat <<'EOF'
{"pattern":"","example":"","confidence":"low","evidenceFiles":[],"alternativeCandidates":[],"fetchWarning":"timeout"}
EOF
      rm -f "$tmp_out"
      return 0
    fi
    sleep 1
    waited=$((waited + 1))
  done
  wait "$pid" 2>/dev/null || true
  if [ -s "$tmp_out" ]; then
    cat "$tmp_out"
  else
    empty_bucket
  fi
  rm -f "$tmp_out"
}

# =============================================================================
# C1 - folderStructure
# =============================================================================
bucket_folder_structure() {
  local evidence example pattern count
  local roots=()
  local r
  while IFS= read -r r; do
    [ -n "$r" ] && roots+=("$r")
  done < <(scan_roots)
  if [ "${#roots[@]}" -eq 0 ]; then
    empty_bucket
    return
  fi

  case "$PLATFORM" in
    ios)
      # Look for feature folders under Domains/*/Sources/*/Screens/*
      local deep
      deep=$(run_find -type d -name 'Screens' 2>/dev/null | head -20)
      if [ -n "$deep" ]; then
        local screen_dir
        screen_dir=$(printf '%s\n' "$deep" | head -1)
        local first_feature
        first_feature=$(find "$screen_dir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | head -1)
        if [ -n "$first_feature" ]; then
          local layers
          layers=$(find "$first_feature" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | awk -F/ '{print $NF}' | sort -u | tr '\n' ',' | sed 's/,$//')
          example="${first_feature#$REPO_PATH/}/{${layers}}"
          if printf '%s' "$layers" | grep -qE 'Presentation|Domain|Data'; then
            pattern="feature-first + clean architecture (Domain/Data/Presentation)"
          else
            pattern="feature-first nested layout"
          fi
          count=$(printf '%s\n' "$deep" | wc -l | tr -d ' ')
          evidence=$(printf '%s\n' "$deep" | sed "s|^$REPO_PATH/||")
          emit_bucket "$pattern" "$example" "$(confidence_for "$count")" "$(files_to_json "$evidence")" "[]"
          return
        fi
      fi
      # Fallback: any feature-like folder
      local features
      features=$(run_find -type d \( -name 'Features' -o -name 'Modules' \) 2>/dev/null | head -10)
      if [ -n "$features" ]; then
        local first
        first=$(printf '%s\n' "$features" | head -1)
        example="${first#$REPO_PATH/}/<FeatureName>/"
        count=$(printf '%s\n' "$features" | wc -l | tr -d ' ')
        emit_bucket "feature-first flat layout" "$example" "$(confidence_for "$count")" "$(files_to_json "$features")" "[]"
        return
      fi
      empty_bucket
      ;;
    android)
      local feats
      feats=$(run_find -type d -name 'feature' 2>/dev/null | head -10)
      if [ -z "$feats" ]; then
        feats=$(run_find -type d -path '*/src/main/java/*' 2>/dev/null | head -10)
      fi
      if [ -n "$feats" ]; then
        local first
        first=$(printf '%s\n' "$feats" | head -1)
        example="${first#$REPO_PATH/}/<feature>/{data,domain,presentation}/"
        count=$(printf '%s\n' "$feats" | wc -l | tr -d ' ')
        emit_bucket "feature-first multi-module" "$example" "$(confidence_for "$count")" "$(files_to_json "$feats")" "[]"
      else
        empty_bucket
      fi
      ;;
    backend)
      local routers services
      routers=$(run_find -type d \( -name 'routers' -o -name 'api' -o -name 'controllers' \) 2>/dev/null | head -10)
      services=$(run_find -type d \( -name 'services' -o -name 'domain' \) 2>/dev/null | head -10)
      local combined
      combined=$(printf '%s\n%s\n' "$routers" "$services" | grep -v '^$' || true)
      if [ -n "$combined" ]; then
        local first
        first=$(printf '%s\n' "$combined" | head -1)
        example="${first#$REPO_PATH/}/<resource>.py"
        count=$(printf '%s\n' "$combined" | wc -l | tr -d ' ')
        emit_bucket "layered (routers + services + repositories)" "$example" "$(confidence_for "$count")" "$(files_to_json "$combined")" "[]"
      else
        empty_bucket
      fi
      ;;
    frontend)
      local feats
      feats=$(run_find -type d -name 'features' 2>/dev/null | head -10)
      if [ -z "$feats" ]; then
        feats=$(run_find -type d \( -name 'components' -o -name 'pages' -o -name 'app' \) 2>/dev/null | head -10)
      fi
      if [ -n "$feats" ]; then
        local first
        first=$(printf '%s\n' "$feats" | head -1)
        example="${first#$REPO_PATH/}/<feature>/"
        count=$(printf '%s\n' "$feats" | wc -l | tr -d ' ')
        emit_bucket "feature-first src layout" "$example" "$(confidence_for "$count")" "$(files_to_json "$feats")" "[]"
      else
        empty_bucket
      fi
      ;;
  esac
}

# =============================================================================
# Naming-suffix buckets share a helper.
# pick_naming pattern_regex example_suffix_label fallback_suffixes...
# =============================================================================
# arg1: regex on basename (group 1 = suffix)
# arg2: file extension glob (e.g. '*.swift')
# arg3..: candidate suffixes to count
emit_naming_bucket() {
  local ext="$1"; shift
  local regex="$1"; shift
  local candidates=("$@")

  local files
  files=$(run_find -type f -name "$ext" 2>/dev/null || true)
  if [ -z "$files" ]; then
    empty_bucket
    return
  fi

  local basenames
  basenames=$(printf '%s\n' "$files" | awk -F/ '{print $NF}' || true)

  # Count occurrences for each candidate.
  local best_count=0
  local best_suffix=""
  local alt_list=()
  local total=0
  for s in "${candidates[@]}"; do
    local c
    c=$(printf '%s\n' "$basenames" | grep -cE "${s}\\.${ext##*.}$" 2>/dev/null || true)
    c=${c:-0}
    if [ "$c" -gt "$best_count" ]; then
      if [ -n "$best_suffix" ] && [ "$best_count" -gt 0 ]; then
        alt_list+=("$best_suffix")
      fi
      best_count="$c"
      best_suffix="$s"
    elif [ "$c" -gt 0 ]; then
      alt_list+=("$s")
    fi
    total=$((total + c))
  done

  if [ "$best_count" -eq 0 ]; then
    empty_bucket
    return
  fi

  # Sample evidence files matching the best suffix.
  local sample
  sample=$(printf '%s\n' "$files" | grep -E "/[^/]*${best_suffix}\\.${ext##*.}$" 2>/dev/null | head -5 | sed "s|^$REPO_PATH/||" || true)

  # Build alternatives JSON.
  local alts_json="[]"
  if [ "${#alt_list[@]}" -gt 0 ]; then
    alts_json=$(printf '%s\n' "${alt_list[@]}" | jq -R . | jq -s 'unique')
  fi

  # Confidence based on best count (not total).
  local conf
  conf=$(confidence_for "$best_count")

  emit_bucket "$best_suffix suffix" "Foo${best_suffix}.${ext##*.}" "$conf" "$(files_to_json "$sample")" "$alts_json"
}

# =============================================================================
# C2 - stateHolderNaming
# =============================================================================
bucket_state_holder() {
  case "$PLATFORM" in
    ios)
      emit_naming_bucket "*.swift" "" "ViewModel" "Presenter" "Store"
      ;;
    android)
      emit_naming_bucket "*.kt" "" "ViewModel" "StateHolder"
      ;;
    backend)
      empty_bucket
      ;;
    frontend)
      # Look for hooks (useXxx) or stores
      local files
      files=$(run_find -type f \( -name '*.ts' -o -name '*.tsx' -o -name '*.js' -o -name '*.jsx' \) 2>/dev/null || true)
      if [ -z "$files" ]; then empty_bucket; return; fi
      local hook_count store_count
      hook_count=$(printf '%s\n' "$files" | awk -F/ '{print $NF}' | grep -cE '^use[A-Z][A-Za-z0-9]*\.(ts|tsx|js|jsx)$' || true)
      store_count=$(printf '%s\n' "$files" | awk -F/ '{print $NF}' | grep -cE 'Store\.(ts|tsx|js|jsx)$' || true)
      hook_count=${hook_count:-0}; store_count=${store_count:-0}
      local pattern example sample
      if [ "$hook_count" -ge "$store_count" ] && [ "$hook_count" -gt 0 ]; then
        pattern="custom hook (useFoo)"
        example="useFoo.ts"
        sample=$(printf '%s\n' "$files" | grep -E '/use[A-Z][A-Za-z0-9]*\.(ts|tsx|js|jsx)$' | head -5 | sed "s|^$REPO_PATH/||")
        emit_bucket "$pattern" "$example" "$(confidence_for "$hook_count")" "$(files_to_json "$sample")" '["FooStore"]'
      elif [ "$store_count" -gt 0 ]; then
        pattern="Store suffix"
        example="FooStore.ts"
        sample=$(printf '%s\n' "$files" | grep -E 'Store\.(ts|tsx|js|jsx)$' | head -5 | sed "s|^$REPO_PATH/||")
        emit_bucket "$pattern" "$example" "$(confidence_for "$store_count")" "$(files_to_json "$sample")" '["useFoo"]'
      else
        empty_bucket
      fi
      ;;
  esac
}

# =============================================================================
# C2 - viewNaming
# =============================================================================
bucket_view_naming() {
  case "$PLATFORM" in
    ios)
      emit_naming_bucket "*.swift" "" "View" "Screen" "ViewController"
      ;;
    android)
      emit_naming_bucket "*.kt" "" "Screen" "Fragment" "Activity"
      ;;
    backend)
      empty_bucket
      ;;
    frontend)
      local files
      files=$(run_find -type f \( -name '*.tsx' -o -name '*.jsx' \) 2>/dev/null || true)
      if [ -z "$files" ]; then empty_bucket; return; fi
      local page_count screen_count plain_count
      page_count=$(printf '%s\n' "$files" | awk -F/ '{print $NF}' | grep -cE 'Page\.(tsx|jsx)$' || true)
      screen_count=$(printf '%s\n' "$files" | awk -F/ '{print $NF}' | grep -cE 'Screen\.(tsx|jsx)$' || true)
      plain_count=$(printf '%s\n' "$files" | awk -F/ '{print $NF}' | grep -cE '^[A-Z][A-Za-z0-9]+\.(tsx|jsx)$' || true)
      page_count=${page_count:-0}; screen_count=${screen_count:-0}; plain_count=${plain_count:-0}
      if [ "$page_count" -ge "$screen_count" ] && [ "$page_count" -ge "$plain_count" ] && [ "$page_count" -gt 0 ]; then
        local sample
        sample=$(printf '%s\n' "$files" | grep -E 'Page\.(tsx|jsx)$' | head -5 | sed "s|^$REPO_PATH/||")
        emit_bucket "Page suffix" "FooPage.tsx" "$(confidence_for "$page_count")" "$(files_to_json "$sample")" '["FooScreen","Foo"]'
      elif [ "$screen_count" -ge "$plain_count" ] && [ "$screen_count" -gt 0 ]; then
        local sample
        sample=$(printf '%s\n' "$files" | grep -E 'Screen\.(tsx|jsx)$' | head -5 | sed "s|^$REPO_PATH/||")
        emit_bucket "Screen suffix" "FooScreen.tsx" "$(confidence_for "$screen_count")" "$(files_to_json "$sample")" '["FooPage","Foo"]'
      elif [ "$plain_count" -gt 0 ]; then
        local sample
        sample=$(printf '%s\n' "$files" | grep -E '/[A-Z][A-Za-z0-9]+\.(tsx|jsx)$' | head -5 | sed "s|^$REPO_PATH/||")
        emit_bucket "PascalCase no suffix" "Foo.tsx" "$(confidence_for "$plain_count")" "$(files_to_json "$sample")" '["FooPage","FooScreen"]'
      else
        empty_bucket
      fi
      ;;
  esac
}

# =============================================================================
# C2 - navigatorNaming
# =============================================================================
bucket_navigator_naming() {
  case "$PLATFORM" in
    ios)
      emit_naming_bucket "*.swift" "" "Coordinator" "Router" "Navigator"
      ;;
    android)
      emit_naming_bucket "*.kt" "" "Navigator" "Destination"
      ;;
    backend)
      empty_bucket
      ;;
    frontend)
      empty_bucket
      ;;
  esac
}

# =============================================================================
# C2 - useCaseNaming
# =============================================================================
bucket_usecase_naming() {
  case "$PLATFORM" in
    ios)
      emit_naming_bucket "*.swift" "" "UseCase" "Interactor"
      ;;
    android)
      emit_naming_bucket "*.kt" "" "UseCase" "Interactor"
      ;;
    backend)
      emit_naming_bucket "*.py" "" "Service" "Handler"
      ;;
    frontend)
      # useFooQuery / useFooMutation as a proxy
      local files cnt sample
      files=$(run_find -type f \( -name '*.ts' -o -name '*.tsx' \) 2>/dev/null || true)
      if [ -z "$files" ]; then empty_bucket; return; fi
      cnt=$(printf '%s\n' "$files" | awk -F/ '{print $NF}' | grep -cE '^use[A-Z][A-Za-z0-9]*(Query|Mutation)\.(ts|tsx)$' || true)
      cnt=${cnt:-0}
      if [ "$cnt" -gt 0 ]; then
        sample=$(printf '%s\n' "$files" | grep -E '/use[A-Z][A-Za-z0-9]*(Query|Mutation)\.(ts|tsx)$' | head -5 | sed "s|^$REPO_PATH/||")
        emit_bucket "useFooQuery / useFooMutation" "useFooQuery.ts" "$(confidence_for "$cnt")" "$(files_to_json "$sample")" "[]"
      else
        empty_bucket
      fi
      ;;
  esac
}

# =============================================================================
# C2 - repositoryNaming
# =============================================================================
bucket_repository_naming() {
  case "$PLATFORM" in
    ios)
      emit_naming_bucket "*.swift" "" "Repository" "DataSource"
      ;;
    android)
      emit_naming_bucket "*.kt" "" "RepositoryImpl" "Repository"
      ;;
    backend)
      emit_naming_bucket "*.py" "" "Repository" "Dao"
      ;;
    frontend)
      emit_naming_bucket "*.ts" "" "Api" "Client"
      ;;
  esac
}

# =============================================================================
# C2 - dtoNaming
# =============================================================================
bucket_dto_naming() {
  case "$PLATFORM" in
    ios)
      emit_naming_bucket "*.swift" "" "ResponseDTO" "RequestDTO" "DTO" "Response" "Request"
      ;;
    android)
      emit_naming_bucket "*.kt" "" "Dto" "Response" "Request"
      ;;
    backend)
      emit_naming_bucket "*.py" "" "Response" "Request" "In" "Out"
      ;;
    frontend)
      emit_naming_bucket "*.ts" "" "Dto" "Response" "Request"
      ;;
  esac
}

# =============================================================================
# C3 - uiStateModel
# =============================================================================
bucket_ui_state_model() {
  local pattern="" example="" sample=""
  local count_enum=0 count_single=0 count_multi=0
  case "$PLATFORM" in
    ios)
      local vm_files
      vm_files=$(run_find -type f -name '*ViewModel.swift' 2>/dev/null | head -50 || true)
      if [ -z "$vm_files" ]; then empty_bucket; return; fi
      while IFS= read -r f; do
        [ -z "$f" ] && continue
        if grep -qE 'enum +[A-Z][A-Za-z0-9]*(UIState|UiState|State)' "$f" 2>/dev/null; then
          count_enum=$((count_enum + 1))
        elif [ "$(grep -cE '@Published' "$f" 2>/dev/null || true)" -ge 3 ]; then
          count_multi=$((count_multi + 1))
        elif [ "$(grep -cE '@Published' "$f" 2>/dev/null || true)" -ge 1 ]; then
          count_single=$((count_single + 1))
        fi
      done <<< "$vm_files"
      ;;
    android)
      local vm_files
      vm_files=$(run_find -type f -name '*ViewModel.kt' 2>/dev/null | head -50 || true)
      if [ -z "$vm_files" ]; then empty_bucket; return; fi
      while IFS= read -r f; do
        [ -z "$f" ] && continue
        if grep -qE 'sealed +(interface|class) +[A-Z][A-Za-z0-9]*(UiState|UIState|State)' "$f" 2>/dev/null; then
          count_enum=$((count_enum + 1))
        elif grep -qE 'data class +[A-Z][A-Za-z0-9]*(UiState|UIState|State)' "$f" 2>/dev/null; then
          count_single=$((count_single + 1))
        elif [ "$(grep -cE 'MutableStateFlow' "$f" 2>/dev/null || true)" -ge 2 ]; then
          count_multi=$((count_multi + 1))
        fi
      done <<< "$vm_files"
      ;;
    backend)
      empty_bucket
      return
      ;;
    frontend)
      local hook_files
      hook_files=$(run_find -type f \( -name 'use*.ts' -o -name 'use*.tsx' \) 2>/dev/null | head -50 || true)
      if [ -z "$hook_files" ]; then empty_bucket; return; fi
      while IFS= read -r f; do
        [ -z "$f" ] && continue
        if grep -qE 'type +[A-Z][A-Za-z0-9]*State *=' "$f" 2>/dev/null; then
          count_enum=$((count_enum + 1))
        elif [ "$(grep -cE 'useState\(' "$f" 2>/dev/null || true)" -ge 3 ]; then
          count_multi=$((count_multi + 1))
        elif [ "$(grep -cE 'useState\(' "$f" 2>/dev/null || true)" -ge 1 ]; then
          count_single=$((count_single + 1))
        fi
      done <<< "$hook_files"
      ;;
  esac

  local total=$((count_enum + count_single + count_multi))
  if [ "$total" -eq 0 ]; then empty_bucket; return; fi
  if [ "$count_enum" -ge "$count_single" ] && [ "$count_enum" -ge "$count_multi" ]; then
    pattern="sealed-enum"
    example="enum FooUIState { case idle, loading, success(Foo), failure(Error) }"
    emit_bucket "$pattern" "$example" "$(confidence_for "$count_enum")" "[]" '["single-state-object","multiple-observables"]'
  elif [ "$count_single" -ge "$count_multi" ]; then
    pattern="single-state-object"
    example="struct FooUIState { var isLoading: Bool; var items: [Foo]; var error: String? }"
    emit_bucket "$pattern" "$example" "$(confidence_for "$count_single")" "[]" '["sealed-enum","multiple-observables"]'
  else
    pattern="multiple-observables"
    example="@Published var isLoading: Bool; @Published var items: [Foo]; @Published var error: String?"
    emit_bucket "$pattern" "$example" "$(confidence_for "$count_multi")" "[]" '["sealed-enum","single-state-object"]'
  fi
}

# =============================================================================
# C4 - testMethodNaming
# =============================================================================
bucket_test_method_naming() {
  local test_files
  case "$PLATFORM" in
    ios)
      test_files=$(run_find -type f \( -name '*Tests.swift' -o -name '*Test.swift' \) 2>/dev/null | head -20 || true)
      ;;
    android)
      test_files=$(run_find -type f \( -name '*Test.kt' -o -name '*Tests.kt' \) 2>/dev/null | head -20 || true)
      ;;
    backend)
      test_files=$(run_find -type f -name 'test_*.py' 2>/dev/null | head -20 || true)
      ;;
    frontend)
      test_files=$(run_find -type f \( -name '*.test.ts' -o -name '*.test.tsx' -o -name '*.spec.ts' -o -name '*.spec.tsx' \) 2>/dev/null | head -20 || true)
      ;;
  esac

  if [ -z "$test_files" ]; then empty_bucket; return; fi

  local underscore_count=0 camel_count=0 backtick_count=0 it_count=0
  while IFS= read -r f; do
    [ -z "$f" ] && continue
    case "$PLATFORM" in
      ios|android)
        underscore_count=$((underscore_count + $(count_matches -E 'func +test[A-Za-z0-9]+_[A-Za-z0-9_]+' "$f")))
        underscore_count=$((underscore_count + $(count_matches -E 'fun +[A-Za-z0-9]+_[A-Za-z0-9_]+_[A-Za-z0-9_]+ *\(' "$f")))
        camel_count=$((camel_count + $(count_matches -E 'func +test[A-Z][A-Za-z0-9]+ *\(' "$f")))
        backtick_count=$((backtick_count + $(count_matches -E '`[^`]*`\s*\(' "$f")))
        ;;
      backend)
        underscore_count=$((underscore_count + $(count_matches -E '^def +test_[a-z0-9_]+' "$f")))
        ;;
      frontend)
        it_count=$((it_count + $(count_matches -E "(it|test)\\(['\"\`]" "$f")))
        ;;
    esac
  done <<< "$test_files"

  local evidence
  evidence=$(printf '%s\n' "$test_files" | head -5 | sed "s|^$REPO_PATH/||")
  local evidence_json
  evidence_json=$(files_to_json "$evidence")

  case "$PLATFORM" in
    ios|android)
      if [ "$underscore_count" -ge "$camel_count" ] && [ "$underscore_count" -ge "$backtick_count" ] && [ "$underscore_count" -gt 0 ]; then
        emit_bucket "snake_separator (testFooBar_returnsBaz)" "testLogin_withInvalidEmail_showsError" "$(confidence_for "$underscore_count")" "$evidence_json" '["camelCase","backtick-sentences"]'
      elif [ "$backtick_count" -ge "$camel_count" ] && [ "$backtick_count" -gt 0 ]; then
        emit_bucket "backtick sentences" '`when foo bar then baz`()' "$(confidence_for "$backtick_count")" "$evidence_json" '["snake_separator","camelCase"]'
      elif [ "$camel_count" -gt 0 ]; then
        emit_bucket "camelCase (testFooBarReturnsBaz)" "testLoginInvalidEmailShowsError" "$(confidence_for "$camel_count")" "$evidence_json" '["snake_separator","backtick-sentences"]'
      else
        empty_bucket
      fi
      ;;
    backend)
      if [ "$underscore_count" -gt 0 ]; then
        emit_bucket "snake_case (test_foo_bar_returns_baz)" "test_login_with_invalid_email_returns_error" "$(confidence_for "$underscore_count")" "$evidence_json" "[]"
      else
        empty_bucket
      fi
      ;;
    frontend)
      if [ "$it_count" -gt 0 ]; then
        emit_bucket "it/test sentence" "it('returns error when email invalid', ...)" "$(confidence_for "$it_count")" "$evidence_json" '["describe-it","test-only"]'
      else
        empty_bucket
      fi
      ;;
  esac
}

# =============================================================================
# C5 - accessibilityIdentifier
# =============================================================================
bucket_accessibility_identifier() {
  case "$PLATFORM" in
    ios)
      # Look for a UITestingIdentifiers registry first.
      local registry
      registry=$(run_find -type f \( -name 'UITestingIdentifiers*.swift' -o -name '*TestingIdentifiers*.swift' \) 2>/dev/null | head -3 || true)
      local view_files
      view_files=$(run_find -type f -name '*View.swift' 2>/dev/null | head -10 || true)
      local sample_ids
      if [ -n "$registry" ]; then
        sample_ids=$(printf '%s\n' "$registry" | while IFS= read -r f; do
          [ -n "$f" ] && grep -hEo '"[a-zA-Z0-9_.\-]+"' "$f" 2>/dev/null
        done | head -20 || true)
      fi
      if [ -z "$sample_ids" ] && [ -n "$view_files" ]; then
        sample_ids=$(printf '%s\n' "$view_files" | while IFS= read -r f; do
          [ -n "$f" ] && grep -hEo 'accessibilityIdentifier *\([^)]*\)' "$f" 2>/dev/null
        done | head -20 || true)
      fi
      if [ -z "$sample_ids" ]; then empty_bucket; return; fi

      local dot_count snake_count camel_count
      dot_count=$(printf '%s\n' "$sample_ids" | count_matches -E '"[a-z]+(\.[a-zA-Z]+)+')
      snake_count=$(printf '%s\n' "$sample_ids" | count_matches -E '"[a-z]+_[a-z_]+')
      camel_count=$(printf '%s\n' "$sample_ids" | count_matches -E '"[a-z]+[A-Z][a-zA-Z]+')
      dot_count=${dot_count:-0}; snake_count=${snake_count:-0}; camel_count=${camel_count:-0}

      local evidence_json
      evidence_json=$(files_to_json "$(printf '%s\n' "${registry:-$view_files}" | head -3 | sed "s|^$REPO_PATH/||")")
      if [ "$dot_count" -ge "$snake_count" ] && [ "$dot_count" -ge "$camel_count" ] && [ "$dot_count" -gt 0 ]; then
        emit_bucket "dot notation (feature.element)" "checkin.passengerList.continueButton" "$(confidence_for "$dot_count")" "$evidence_json" '["snake_prefixed","camelCase"]'
      elif [ "$snake_count" -ge "$camel_count" ] && [ "$snake_count" -gt 0 ]; then
        emit_bucket "snake_case prefix" "tk_checkin_continue_button" "$(confidence_for "$snake_count")" "$evidence_json" '["dot-notation","camelCase"]'
      elif [ "$camel_count" -gt 0 ]; then
        emit_bucket "camelCase" "checkinContinueButton" "$(confidence_for "$camel_count")" "$evidence_json" '["dot-notation","snake_prefixed"]'
      else
        empty_bucket
      fi
      ;;
    android)
      local view_files
      view_files=$(run_find -type f -name '*.kt' 2>/dev/null | head -200 || true)
      if [ -z "$view_files" ]; then empty_bucket; return; fi
      local ids
      ids=$(printf '%s\n' "$view_files" | while IFS= read -r f; do
        [ -n "$f" ] && grep -hEo 'testTag *\("[^"]+"\)' "$f" 2>/dev/null
      done | head -30 || true)
      if [ -z "$ids" ]; then empty_bucket; return; fi
      local snake_count kebab_count camel_count
      snake_count=$(printf '%s\n' "$ids" | count_matches -E '"[a-z]+_[a-z_]+')
      kebab_count=$(printf '%s\n' "$ids" | count_matches -E '"[a-z]+-[a-z\-]+')
      camel_count=$(printf '%s\n' "$ids" | count_matches -E '"[a-z]+[A-Z][a-zA-Z]+')
      snake_count=${snake_count:-0}; kebab_count=${kebab_count:-0}; camel_count=${camel_count:-0}
      if [ "$snake_count" -ge "$kebab_count" ] && [ "$snake_count" -ge "$camel_count" ] && [ "$snake_count" -gt 0 ]; then
        emit_bucket "snake_case testTag" "checkin_continue_button" "$(confidence_for "$snake_count")" "[]" '["kebab","camelCase"]'
      elif [ "$kebab_count" -ge "$camel_count" ] && [ "$kebab_count" -gt 0 ]; then
        emit_bucket "kebab-case testTag" "checkin-continue-button" "$(confidence_for "$kebab_count")" "[]" '["snake","camelCase"]'
      elif [ "$camel_count" -gt 0 ]; then
        emit_bucket "camelCase testTag" "checkinContinueButton" "$(confidence_for "$camel_count")" "[]" '["snake","kebab"]'
      else
        empty_bucket
      fi
      ;;
    backend)
      # OpenAPI operationId style
      local spec_files
      spec_files=$(run_find -type f \( -name 'openapi*.yaml' -o -name 'openapi*.yml' -o -name 'openapi*.json' \) 2>/dev/null | head -3 || true)
      if [ -z "$spec_files" ]; then empty_bucket; return; fi
      local op_ids
      op_ids=$(printf '%s\n' "$spec_files" | while IFS= read -r f; do
        [ -n "$f" ] && grep -hEo 'operationId: *[A-Za-z0-9_]+' "$f" 2>/dev/null
      done | head -50 || true)
      if [ -z "$op_ids" ]; then empty_bucket; return; fi
      local camel_count snake_count
      camel_count=$(printf '%s\n' "$op_ids" | count_matches -E '[a-z]+[A-Z][A-Za-z]+')
      snake_count=$(printf '%s\n' "$op_ids" | count_matches -E '[a-z]+_[a-z_]+')
      camel_count=${camel_count:-0}; snake_count=${snake_count:-0}
      if [ "$camel_count" -ge "$snake_count" ] && [ "$camel_count" -gt 0 ]; then
        emit_bucket "camelCase operationId" "createFoo" "$(confidence_for "$camel_count")" "[]" '["snake_case"]'
      elif [ "$snake_count" -gt 0 ]; then
        emit_bucket "snake_case operationId" "create_foo" "$(confidence_for "$snake_count")" "[]" '["camelCase"]'
      else
        empty_bucket
      fi
      ;;
    frontend)
      local files ids
      files=$(run_find -type f \( -name '*.tsx' -o -name '*.jsx' \) 2>/dev/null | head -100 || true)
      if [ -z "$files" ]; then empty_bucket; return; fi
      ids=$(printf '%s\n' "$files" | while IFS= read -r f; do
        [ -n "$f" ] && grep -hEo 'data-testid="[^"]+"' "$f" 2>/dev/null
      done | head -30 || true)
      if [ -z "$ids" ]; then empty_bucket; return; fi
      local kebab_count camel_count snake_count
      kebab_count=$(printf '%s\n' "$ids" | count_matches -E '"[a-z]+-[a-z\-]+')
      camel_count=$(printf '%s\n' "$ids" | count_matches -E '"[a-z]+[A-Z][a-zA-Z]+')
      snake_count=$(printf '%s\n' "$ids" | count_matches -E '"[a-z]+_[a-z_]+')
      kebab_count=${kebab_count:-0}; camel_count=${camel_count:-0}; snake_count=${snake_count:-0}
      if [ "$kebab_count" -ge "$camel_count" ] && [ "$kebab_count" -ge "$snake_count" ] && [ "$kebab_count" -gt 0 ]; then
        emit_bucket "kebab-case data-testid" 'data-testid="checkin-continue-button"' "$(confidence_for "$kebab_count")" "[]" '["camelCase","snake"]'
      elif [ "$camel_count" -ge "$snake_count" ] && [ "$camel_count" -gt 0 ]; then
        emit_bucket "camelCase data-testid" 'data-testid="checkinContinueButton"' "$(confidence_for "$camel_count")" "[]" '["kebab","snake"]'
      elif [ "$snake_count" -gt 0 ]; then
        emit_bucket "snake_case data-testid" 'data-testid="checkin_continue_button"' "$(confidence_for "$snake_count")" "[]" '["kebab","camelCase"]'
      else
        empty_bucket
      fi
      ;;
  esac
}

# =============================================================================
# C6 - localizationKey
# =============================================================================
bucket_localization_key() {
  local keys=""
  case "$PLATFORM" in
    ios)
      local strings_files xcstrings_files
      # An earlier version ANDed -name onto the FRONT of the prune chain
      # (`-name X SKIP_PRUNE_ARGS...`), which requires every -prune branch to
      # ALSO be named "Localizable.strings" - a directory prune can never
      # satisfy that, so no prune ever fired and the search walked into
      # Pods/.build/node_modules looking for vendored copies. Prunes must
      # stay their own independent OR-chain, ahead of (not ANDed with) the
      # real search criteria. The xcstrings search had no prune chain at all.
      strings_files=$(find "$REPO_PATH" "${SKIP_PRUNE_ARGS[@]}" -name 'Localizable.strings' -type f -print 2>/dev/null | head -5 || true)
      xcstrings_files=$(find "$REPO_PATH" "${SKIP_PRUNE_ARGS[@]}" -name 'Localizable.xcstrings' -type f -print 2>/dev/null | head -3 || true)
      if [ -n "$strings_files" ]; then
        keys=$(printf '%s\n' "$strings_files" | while IFS= read -r f; do
          [ -n "$f" ] && grep -hEo '^"[^"]+"' "$f" 2>/dev/null | tr -d '"'
        done | head -200 || true)
      elif [ -n "$xcstrings_files" ]; then
        keys=$(printf '%s\n' "$xcstrings_files" | while IFS= read -r f; do
          [ -n "$f" ] && jq -r '.strings | keys[]' "$f" 2>/dev/null
        done | head -200 || true)
      fi
      ;;
    android)
      local xml_files
      xml_files=$(run_find -type f -name 'strings.xml' 2>/dev/null | head -5 || true)
      if [ -n "$xml_files" ]; then
        keys=$(printf '%s\n' "$xml_files" | while IFS= read -r f; do
          [ -n "$f" ] && grep -hEo 'name="[^"]+"' "$f" 2>/dev/null | sed 's/name="//; s/"$//'
        done | head -200 || true)
      fi
      ;;
    backend)
      empty_bucket; return ;;
    frontend)
      local json_files
      json_files=$(run_find -type f -path '*i18n*' -name '*.json' 2>/dev/null | head -5 || true)
      if [ -n "$json_files" ]; then
        keys=$(printf '%s\n' "$json_files" | while IFS= read -r f; do
          [ -n "$f" ] && jq -r 'paths(scalars) | join(".")' "$f" 2>/dev/null
        done | head -200 || true)
      fi
      ;;
  esac

  if [ -z "$keys" ]; then empty_bucket; return; fi

  local dot_count snake_count camel_count
  dot_count=$(printf '%s\n' "$keys" | count_matches -E '^[A-Za-z][A-Za-z0-9]*(\.[A-Za-z][A-Za-z0-9]*)+')
  snake_count=$(printf '%s\n' "$keys" | count_matches -E '^[a-z][a-z0-9]*(_[a-z0-9]+)+')
  camel_count=$(printf '%s\n' "$keys" | count_matches -E '^[a-z][a-zA-Z0-9]*[A-Z][a-zA-Z0-9]*')
  dot_count=${dot_count:-0}; snake_count=${snake_count:-0}; camel_count=${camel_count:-0}

  if [ "$dot_count" -ge "$snake_count" ] && [ "$dot_count" -ge "$camel_count" ] && [ "$dot_count" -gt 0 ]; then
    emit_bucket "hierarchical dot notation" "Checkin.PassengerFlight.Title" "$(confidence_for "$dot_count")" "[]" '["flat_snake","domain_prefix_camel"]'
  elif [ "$snake_count" -ge "$camel_count" ] && [ "$snake_count" -gt 0 ]; then
    emit_bucket "flat snake_case" "checkin_passenger_flight_title" "$(confidence_for "$snake_count")" "[]" '["dot_notation","camelCase"]'
  elif [ "$camel_count" -gt 0 ]; then
    emit_bucket "domain prefix camelCase" "checkinPassengerFlightTitle" "$(confidence_for "$camel_count")" "[]" '["dot_notation","snake"]'
  else
    empty_bucket
  fi
}

# =============================================================================
# C7 - diRegistration
# =============================================================================
bucket_di_registration() {
  local files count pattern example sample
  case "$PLATFORM" in
    ios)
      local configurators
      configurators=$(run_find -type f \( -name '*DependencyConfigurator.swift' -o -name '*Assembly.swift' -o -name '*Module.swift' \) 2>/dev/null | head -20 || true)
      local resolver_count factory_count manual_count swinject_count
      manual_count=0; resolver_count=0; factory_count=0; swinject_count=0
      if [ -n "$configurators" ]; then
        manual_count=$(printf '%s\n' "$configurators" | count_matches 'DependencyConfigurator')
      fi
      files=$(run_find -type f -name '*.swift' 2>/dev/null || true)
      if [ -n "$files" ]; then
        resolver_count=$(count_grep_hits "$files" 'Resolver.register')
        factory_count=$(count_grep_hits "$files" '@Injected\|Container.shared')
        swinject_count=$(count_grep_hits "$files" 'Swinject\|Container()')
      fi
      manual_count=${manual_count:-0}; resolver_count=${resolver_count:-0}; factory_count=${factory_count:-0}; swinject_count=${swinject_count:-0}

      sample=$(printf '%s\n' "$configurators" | head -3 | sed "s|^$REPO_PATH/||")
      if [ "$manual_count" -ge "$resolver_count" ] && [ "$manual_count" -ge "$factory_count" ] && [ "$manual_count" -ge "$swinject_count" ] && [ "$manual_count" -gt 0 ]; then
        emit_bucket "manual *DependencyConfigurator" "FooDependencyConfigurator.swift" "$(confidence_for "$manual_count")" "$(files_to_json "$sample")" '["Resolver","Factory","Swinject"]'
      elif [ "$resolver_count" -ge "$factory_count" ] && [ "$resolver_count" -ge "$swinject_count" ] && [ "$resolver_count" -gt 0 ]; then
        emit_bucket "Resolver" "Resolver.register { FooViewModel() }" "$(confidence_for "$resolver_count")" "[]" '["manual-configurator","Factory","Swinject"]'
      elif [ "$factory_count" -ge "$swinject_count" ] && [ "$factory_count" -gt 0 ]; then
        emit_bucket "Factory" "@Injected(\\Container.fooService)" "$(confidence_for "$factory_count")" "[]" '["manual-configurator","Resolver","Swinject"]'
      elif [ "$swinject_count" -gt 0 ]; then
        emit_bucket "Swinject" "container.register(FooService.self)" "$(confidence_for "$swinject_count")" "[]" '["manual-configurator","Resolver","Factory"]'
      else
        empty_bucket
      fi
      ;;
    android)
      files=$(run_find -type f -name '*.kt' 2>/dev/null || true)
      if [ -z "$files" ]; then empty_bucket; return; fi
      local hilt_count koin_count dagger_count
      hilt_count=$(count_grep_hits "$files" '@Module\|@InstallIn\|@HiltViewModel')
      koin_count=$(count_grep_hits "$files" 'koinViewModel\|module {')
      dagger_count=$(count_grep_hits "$files" '@Component\|@Subcomponent')
      hilt_count=${hilt_count:-0}; koin_count=${koin_count:-0}; dagger_count=${dagger_count:-0}
      if [ "$hilt_count" -ge "$koin_count" ] && [ "$hilt_count" -ge "$dagger_count" ] && [ "$hilt_count" -gt 0 ]; then
        emit_bucket "Hilt" "@Module @InstallIn(SingletonComponent::class)" "$(confidence_for "$hilt_count")" "[]" '["Koin","Dagger"]'
      elif [ "$koin_count" -ge "$dagger_count" ] && [ "$koin_count" -gt 0 ]; then
        emit_bucket "Koin" "module { viewModel { FooViewModel(get()) } }" "$(confidence_for "$koin_count")" "[]" '["Hilt","Dagger"]'
      elif [ "$dagger_count" -gt 0 ]; then
        emit_bucket "Dagger" "@Component(modules = [FooModule::class])" "$(confidence_for "$dagger_count")" "[]" '["Hilt","Koin"]'
      else
        empty_bucket
      fi
      ;;
    backend)
      files=$(run_find -type f -name '*.py' 2>/dev/null || true)
      if [ -z "$files" ]; then empty_bucket; return; fi
      local depends_count dishka_count di_count
      depends_count=$(count_grep_hits "$files" 'Depends(')
      dishka_count=$(count_grep_hits "$files" 'dishka')
      di_count=$(count_grep_hits "$files" 'dependency_injector')
      depends_count=${depends_count:-0}; dishka_count=${dishka_count:-0}; di_count=${di_count:-0}
      if [ "$depends_count" -ge "$dishka_count" ] && [ "$depends_count" -ge "$di_count" ] && [ "$depends_count" -gt 0 ]; then
        emit_bucket "FastAPI Depends" "def handler(svc: FooService = Depends(get_service))" "$(confidence_for "$depends_count")" "[]" '["dishka","dependency_injector"]'
      elif [ "$dishka_count" -ge "$di_count" ] && [ "$dishka_count" -gt 0 ]; then
        emit_bucket "dishka" "container = make_async_container(FooProvider())" "$(confidence_for "$dishka_count")" "[]" '["FastAPI Depends","dependency_injector"]'
      elif [ "$di_count" -gt 0 ]; then
        emit_bucket "dependency_injector" "providers.Factory(FooService)" "$(confidence_for "$di_count")" "[]" '["FastAPI Depends","dishka"]'
      else
        empty_bucket
      fi
      ;;
    frontend)
      files=$(run_find -type f \( -name '*.tsx' -o -name '*.ts' \) 2>/dev/null || true)
      if [ -z "$files" ]; then empty_bucket; return; fi
      local context_count zustand_count hook_count
      context_count=$(count_grep_hits "$files" 'createContext\|\.Provider')
      zustand_count=$(count_grep_hits "$files" "from 'zustand'\\|from \"zustand\"")
      hook_count=$(count_grep_hits "$files" 'export function use[A-Z]')
      context_count=${context_count:-0}; zustand_count=${zustand_count:-0}; hook_count=${hook_count:-0}
      if [ "$context_count" -ge "$zustand_count" ] && [ "$context_count" -ge "$hook_count" ] && [ "$context_count" -gt 0 ]; then
        emit_bucket "React Context Provider" "const FooContext = createContext<Foo>(...)" "$(confidence_for "$context_count")" "[]" '["zustand","custom-hook-factory"]'
      elif [ "$zustand_count" -ge "$hook_count" ] && [ "$zustand_count" -gt 0 ]; then
        emit_bucket "Zustand store" "const useFooStore = create<FooState>(...)" "$(confidence_for "$zustand_count")" "[]" '["context","custom-hook-factory"]'
      elif [ "$hook_count" -gt 0 ]; then
        emit_bucket "custom hook factory" "export function useFoo() {...}" "$(confidence_for "$hook_count")" "[]" '["context","zustand"]'
      else
        empty_bucket
      fi
      ;;
  esac
}

# =============================================================================
# Assemble final JSON
# =============================================================================

# =============================================================================
# C13 - sharedUtilities: the bind-don't-rebuild inventory
# =============================================================================
# What shared machinery already exists OUTSIDE screen slices - formatter families,
# validation rule types + per-module rule facades, design-token namespaces. A dev
# phase that sees a non-empty bucket binds these instead of hand-rolling a
# duplicate; the counts say how settled each family is.
bucket_shared_utilities() {
  local ext screens_seg
  case "$PLATFORM" in
    ios)     ext='*.swift' ; screens_seg='/Screens/' ;;
    android) ext='*.kt'    ; screens_seg='/screens/' ;;
    *)       empty_bucket; return ;;
  esac
  local files
  files=$(run_find -type f -name "$ext" 2>/dev/null | grep -v "$screens_seg" | grep -viE '/tests?/|/generated/' || true)
  if [ -z "$files" ]; then empty_bucket; return; fi
  local fmt rules facades tokens
  fmt=$(printf '%s
' "$files"    | grep -cE '(Formatter|Format)\.(swift|kt)$' || true)
  rules=$(printf '%s
' "$files"  | grep -cE '[A-Za-z]Rule\.(swift|kt)$' || true)
  facades=$(printf '%s
' "$files"| grep -cE '(FormRules|ValidationRules)\.(swift|kt)$' || true)
  tokens=$(printf '%s
' "$files" | grep -cE '(Tokens?|Spacing|Radius|Typography)[A-Za-z]*\.(swift|kt)$' || true)
  fmt=${fmt:-0}; rules=${rules:-0}; facades=${facades:-0}; tokens=${tokens:-0}
  local total=$((fmt + rules + facades + tokens))
  if [ "$total" -eq 0 ]; then empty_bucket; return; fi
  local sample
  sample=$(printf '%s
' "$files" | grep -E '(Formatter|Format|Rule|FormRules|ValidationRules|Tokens?|Spacing|Radius|Typography)[A-Za-z]*\.(swift|kt)$' | head -5 | sed "s|^$REPO_PATH/||")
  emit_bucket "formatters:$fmt rules:$rules facades:$facades tokens:$tokens"     "$(printf '%s
' "$sample" | head -1)"     "$(confidence_for "$total")"     "$(files_to_json "$sample")" "[]"
}

folderStructure=$(run_bucket_with_timeout bucket_folder_structure)
stateHolderNaming=$(run_bucket_with_timeout bucket_state_holder)
viewNaming=$(run_bucket_with_timeout bucket_view_naming)
navigatorNaming=$(run_bucket_with_timeout bucket_navigator_naming)
useCaseNaming=$(run_bucket_with_timeout bucket_usecase_naming)
repositoryNaming=$(run_bucket_with_timeout bucket_repository_naming)
dtoNaming=$(run_bucket_with_timeout bucket_dto_naming)
uiStateModel=$(run_bucket_with_timeout bucket_ui_state_model)
testMethodNaming=$(run_bucket_with_timeout bucket_test_method_naming)
accessibilityIdentifier=$(run_bucket_with_timeout bucket_accessibility_identifier)
localizationKey=$(run_bucket_with_timeout bucket_localization_key)
diRegistration=$(run_bucket_with_timeout bucket_di_registration)
sharedUtilities=$(run_bucket_with_timeout bucket_shared_utilities)

jq -n \
  --argjson folderStructure "$folderStructure" \
  --argjson stateHolderNaming "$stateHolderNaming" \
  --argjson viewNaming "$viewNaming" \
  --argjson navigatorNaming "$navigatorNaming" \
  --argjson useCaseNaming "$useCaseNaming" \
  --argjson repositoryNaming "$repositoryNaming" \
  --argjson dtoNaming "$dtoNaming" \
  --argjson uiStateModel "$uiStateModel" \
  --argjson testMethodNaming "$testMethodNaming" \
  --argjson accessibilityIdentifier "$accessibilityIdentifier" \
  --argjson localizationKey "$localizationKey" \
  --argjson diRegistration "$diRegistration" \
  --argjson sharedUtilities "$sharedUtilities" \
  '{
    folderStructure: $folderStructure,
    stateHolderNaming: $stateHolderNaming,
    viewNaming: $viewNaming,
    navigatorNaming: $navigatorNaming,
    useCaseNaming: $useCaseNaming,
    repositoryNaming: $repositoryNaming,
    dtoNaming: $dtoNaming,
    uiStateModel: $uiStateModel,
    testMethodNaming: $testMethodNaming,
    accessibilityIdentifier: $accessibilityIdentifier,
    localizationKey: $localizationKey,
    diRegistration: $diRegistration,
    sharedUtilities: $sharedUtilities
  }'

exit 0
