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

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
cd "${repo_root}"

readonly shard_names=(
  "api-core"
  "api-support"
  "store-domains"
  "foundations-and-data"
  "coredb-and-tidb"
  "store-app-data"
  "store-org-data"
  "store-task-data"
  "meshx-api-runtime"
  "meshx-domain-data"
)


cleanup_ci_leftovers() {
  if [[ "${AGENTTEAM_CI_CLEANUP_LEFTOVERS:-}" != "1" ]]; then
    return 0
  fi
  if ! command -v node >/dev/null 2>&1; then
    echo "node is not available; skipping CI leftovers cleanup" >&2
    return 0
  fi
  node scripts/cleanup-leftovers.mjs || true
}

usage() {
  cat <<'EOF'
Usage:
  AGENTTEAM_TIDB_MIGRATION_BASE_REF=<full-target-commit> scripts/ci-backend-required.sh validate
  scripts/ci-backend-required.sh list
  scripts/ci-backend-required.sh run <shard-name>
  scripts/ci-backend-required.sh go-e2e
EOF
}

shard_packages() {
  local shard_name="$1"

  case "${shard_name}" in
    api-core)
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/cmd/agentteam-e2e-server
github.com/tidbcloud/AgentTeam/cmd/agentteam-gateway-server
github.com/tidbcloud/AgentTeam/cmd/agentteam-route-contract
github.com/tidbcloud/AgentTeam/cmd/agentteam-server
github.com/tidbcloud/AgentTeam/internal/api
github.com/tidbcloud/AgentTeam/internal/api/channel
github.com/tidbcloud/AgentTeam/internal/api/daemon
github.com/tidbcloud/AgentTeam/internal/api/extension
github.com/tidbcloud/AgentTeam/internal/api/im
github.com/tidbcloud/AgentTeam/internal/api/im/fanout
github.com/tidbcloud/AgentTeam/internal/api/knowledgeqa
github.com/tidbcloud/AgentTeam/internal/api/identity/lookup
github.com/tidbcloud/AgentTeam/internal/api/serviceaccount
github.com/tidbcloud/AgentTeam/internal/api/workspace
github.com/tidbcloud/AgentTeam/internal/forwarder
github.com/tidbcloud/AgentTeam/internal/gateway
github.com/tidbcloud/AgentTeam/internal/meshxassets
github.com/tidbcloud/AgentTeam/internal/router
github.com/tidbcloud/AgentTeam/internal/server
github.com/tidbcloud/AgentTeam/internal/schedule
github.com/tidbcloud/AgentTeam/internal/schedule/manage
github.com/tidbcloud/AgentTeam/internal/trigger
github.com/tidbcloud/AgentTeam/internal/service/agentruntime
github.com/tidbcloud/AgentTeam/internal/service/agentapproval
github.com/tidbcloud/AgentTeam/internal/service/agentmemory
github.com/tidbcloud/AgentTeam/internal/service/app/installation
github.com/tidbcloud/AgentTeam/internal/service/approvalapp
github.com/tidbcloud/AgentTeam/internal/service/appruntime
github.com/tidbcloud/AgentTeam/internal/service/channeladmin
github.com/tidbcloud/AgentTeam/internal/service/collaborator
github.com/tidbcloud/AgentTeam/internal/service/knowledgeqa
github.com/tidbcloud/AgentTeam/internal/service/message
github.com/tidbcloud/AgentTeam/internal/service/role/execution
github.com/tidbcloud/AgentTeam/internal/service/serviceaccount
github.com/tidbcloud/AgentTeam/internal/service/team/roleproposal
github.com/tidbcloud/AgentTeam/internal/service/teamworkflow
github.com/tidbcloud/AgentTeam/internal/service/workspace
github.com/tidbcloud/AgentTeam/internal/workspace/owner
github.com/tidbcloud/AgentTeam/internal/workspace/router
EOF
      ;;
    api-support)
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/books/_infra/scripts/plancheck
github.com/tidbcloud/AgentTeam/kits/production-deploy-app/cmd/production-deploy-app
github.com/tidbcloud/AgentTeam/cmd/agentteam-schema
github.com/tidbcloud/AgentTeam/cmd/agentteam-tidb-admin
github.com/tidbcloud/AgentTeam/cmd/tidb-template-preparer
github.com/tidbcloud/AgentTeam/internal/api/admin
github.com/tidbcloud/AgentTeam/internal/api/agent
github.com/tidbcloud/AgentTeam/internal/api/app
github.com/tidbcloud/AgentTeam/internal/api/auth
github.com/tidbcloud/AgentTeam/internal/api/billing
github.com/tidbcloud/AgentTeam/internal/api/dto
github.com/tidbcloud/AgentTeam/internal/api/dto/converter
github.com/tidbcloud/AgentTeam/internal/api/llm
github.com/tidbcloud/AgentTeam/internal/api/media
github.com/tidbcloud/AgentTeam/internal/api/onboarding
github.com/tidbcloud/AgentTeam/internal/api/provider
github.com/tidbcloud/AgentTeam/internal/api/public
github.com/tidbcloud/AgentTeam/internal/api/schedule
github.com/tidbcloud/AgentTeam/internal/api/testkit
github.com/tidbcloud/AgentTeam/internal/api/testkit/httputil
github.com/tidbcloud/AgentTeam/internal/api/trigger
github.com/tidbcloud/AgentTeam/internal/api/webagent
github.com/tidbcloud/AgentTeam/internal/api/workflow/binding
github.com/tidbcloud/AgentTeam/internal/api/workflow/invocation
github.com/tidbcloud/AgentTeam/internal/api/workflow/plan
github.com/tidbcloud/AgentTeam/internal/api/workflow/run
github.com/tidbcloud/AgentTeam/internal/archive
github.com/tidbcloud/AgentTeam/internal/billing/alipay
github.com/tidbcloud/AgentTeam/internal/config
github.com/tidbcloud/AgentTeam/internal/extension
github.com/tidbcloud/AgentTeam/internal/extension/bootstrap
github.com/tidbcloud/AgentTeam/internal/extension/connection
github.com/tidbcloud/AgentTeam/internal/extension/providers/arxiv
github.com/tidbcloud/AgentTeam/internal/extension/binding
github.com/tidbcloud/AgentTeam/internal/extension/providers/brave
github.com/tidbcloud/AgentTeam/internal/extension/egress
github.com/tidbcloud/AgentTeam/internal/extension/providers/gitea
github.com/tidbcloud/AgentTeam/internal/extension/providers/github
github.com/tidbcloud/AgentTeam/internal/extension/providers/hackernews
github.com/tidbcloud/AgentTeam/internal/extension/providers/jenkins
github.com/tidbcloud/AgentTeam/internal/extension/providers/jin10
github.com/tidbcloud/AgentTeam/internal/extension/providers/pkulaw
github.com/tidbcloud/AgentTeam/internal/extension/providers/aifin
github.com/tidbcloud/AgentTeam/internal/extension/providers/notion
github.com/tidbcloud/AgentTeam/internal/extension/providers
github.com/tidbcloud/AgentTeam/internal/extension/providers/mail
github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/mailapi
github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/connection
github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/protocol
github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/mailruntime
github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/mailstore
github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/token
github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/transport
github.com/tidbcloud/AgentTeam/internal/extension/registry
github.com/tidbcloud/AgentTeam/internal/extension/runner
github.com/tidbcloud/AgentTeam/internal/extension/runner/mcp
github.com/tidbcloud/AgentTeam/internal/extension/providers/dingtalkbot
github.com/tidbcloud/AgentTeam/internal/extension/providers/feishubot
github.com/tidbcloud/AgentTeam/internal/extension/providers/tianyancha
github.com/tidbcloud/AgentTeam/internal/extension/providers/todoist
github.com/tidbcloud/AgentTeam/internal/extension/providers/wecombot
github.com/tidbcloud/AgentTeam/internal/extension/providers/ones
github.com/tidbcloud/AgentTeam/internal/extension/providers/hotnews
github.com/tidbcloud/AgentTeam/internal/extension/providers/searchengine
github.com/tidbcloud/AgentTeam/internal/extension/providers/searchengine/runtime
github.com/tidbcloud/AgentTeam/internal/extension/providers/searxng
github.com/tidbcloud/AgentTeam/internal/extension/providers/serper
github.com/tidbcloud/AgentTeam/internal/extension/providers/qichacha
github.com/tidbcloud/AgentTeam/internal/extension/providers/tavily
github.com/tidbcloud/AgentTeam/internal/extension/providers/tushare
github.com/tidbcloud/AgentTeam/internal/extension/providers/alphavantage
github.com/tidbcloud/AgentTeam/internal/extension/providers/financialmodelingprep
github.com/tidbcloud/AgentTeam/internal/mailer
github.com/tidbcloud/AgentTeam/internal/observability
github.com/tidbcloud/AgentTeam/internal/observability/debughttp
github.com/tidbcloud/AgentTeam/internal/observability/logging
github.com/tidbcloud/AgentTeam/internal/observability/metrics
github.com/tidbcloud/AgentTeam/internal/observability/tracing
github.com/tidbcloud/AgentTeam/internal/objectstorage
github.com/tidbcloud/AgentTeam/internal/sms
github.com/tidbcloud/AgentTeam/internal/oidc
github.com/tidbcloud/AgentTeam/internal/passwords
github.com/tidbcloud/AgentTeam/internal/pinyinsearch
github.com/tidbcloud/AgentTeam/internal/runtimecontrol
github.com/tidbcloud/AgentTeam/internal/service/serviceevent
github.com/tidbcloud/AgentTeam/internal/service/authflow
github.com/tidbcloud/AgentTeam/internal/service/maas
github.com/tidbcloud/AgentTeam/internal/service/organization
github.com/tidbcloud/AgentTeam/internal/service/prwatch
github.com/tidbcloud/AgentTeam/internal/service/role/agents
github.com/tidbcloud/AgentTeam/internal/service/providertokens
github.com/tidbcloud/AgentTeam/internal/service/repotokens
github.com/tidbcloud/AgentTeam/internal/service/resource/access
github.com/tidbcloud/AgentTeam/internal/service/resource/activity
github.com/tidbcloud/AgentTeam/internal/service/resource/repos
github.com/tidbcloud/AgentTeam/internal/versioning
EOF
      ;;
    store-domains)
      # Heavy tests: packages whose recent required-job runtime exceeded 60s.
      # Keep this group alphabetical and reclassify it from [backend:timing] output.
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/internal/store/agent
github.com/tidbcloud/AgentTeam/internal/store/agentops
github.com/tidbcloud/AgentTeam/internal/store/extension
github.com/tidbcloud/AgentTeam/internal/store/im
github.com/tidbcloud/AgentTeam/internal/store/review
github.com/tidbcloud/AgentTeam/internal/store/rewind
github.com/tidbcloud/AgentTeam/internal/store/role/execution
github.com/tidbcloud/AgentTeam/internal/store/schedule
github.com/tidbcloud/AgentTeam/internal/store/serviceaccount
github.com/tidbcloud/AgentTeam/internal/store/serviceactor
github.com/tidbcloud/AgentTeam/internal/store/workflow/invocation
github.com/tidbcloud/AgentTeam/internal/store/workflow/run
EOF
      # Normal tests: all remaining store-domain packages.
      # Keep this group alphabetical.
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/internal/store/access
github.com/tidbcloud/AgentTeam/internal/store/admin
github.com/tidbcloud/AgentTeam/internal/store/approval
github.com/tidbcloud/AgentTeam/internal/store/attachment
github.com/tidbcloud/AgentTeam/internal/store/channel
github.com/tidbcloud/AgentTeam/internal/store/channeladmin
github.com/tidbcloud/AgentTeam/internal/store/identity
github.com/tidbcloud/AgentTeam/internal/store/knowledgeqa
github.com/tidbcloud/AgentTeam/internal/store/message/testdelivery
github.com/tidbcloud/AgentTeam/internal/store/onboarding
github.com/tidbcloud/AgentTeam/internal/store/plan
github.com/tidbcloud/AgentTeam/internal/store/privateagent
github.com/tidbcloud/AgentTeam/internal/store/provider
github.com/tidbcloud/AgentTeam/internal/store/providertokens
github.com/tidbcloud/AgentTeam/internal/store/reaction
github.com/tidbcloud/AgentTeam/internal/store/repocache
github.com/tidbcloud/AgentTeam/internal/store/reporting
github.com/tidbcloud/AgentTeam/internal/store/repotokens
github.com/tidbcloud/AgentTeam/internal/store/resource/activity
github.com/tidbcloud/AgentTeam/internal/store/resource/memory
github.com/tidbcloud/AgentTeam/internal/store/resource/repos
github.com/tidbcloud/AgentTeam/internal/store/resource/roles
github.com/tidbcloud/AgentTeam/internal/store/resource/skills
github.com/tidbcloud/AgentTeam/internal/store/seatbilling
github.com/tidbcloud/AgentTeam/internal/store/unread
github.com/tidbcloud/AgentTeam/internal/store/workflow/binding
github.com/tidbcloud/AgentTeam/internal/store/workflow/calendar
github.com/tidbcloud/AgentTeam/internal/store/workflow/plan
github.com/tidbcloud/AgentTeam/internal/store/workspace
EOF
      ;;
    foundations-and-data)
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/internal/core/app/capabilities
github.com/tidbcloud/AgentTeam/internal/core/authidentity
github.com/tidbcloud/AgentTeam/internal/core/domain
github.com/tidbcloud/AgentTeam/internal/core/identifiers
github.com/tidbcloud/AgentTeam/internal/core/registration
github.com/tidbcloud/AgentTeam/internal/core/rewindcaps
github.com/tidbcloud/AgentTeam/internal/core/rewindplan
github.com/tidbcloud/AgentTeam/internal/core/catalog
github.com/tidbcloud/AgentTeam/internal/core/routing
github.com/tidbcloud/AgentTeam/internal/core/runeffect
github.com/tidbcloud/AgentTeam/internal/core/secrets
github.com/tidbcloud/AgentTeam/internal/core/webhook
github.com/tidbcloud/AgentTeam/guardrails/ags
github.com/tidbcloud/AgentTeam/guardrails/message
github.com/tidbcloud/AgentTeam/internal/apr/concurrency
github.com/tidbcloud/AgentTeam/internal/apr/agentteambridge
github.com/tidbcloud/AgentTeam/internal/apr/pricing
github.com/tidbcloud/AgentTeam/internal/apr/provider/codex
github.com/tidbcloud/AgentTeam/internal/apr/provider/copilot
github.com/tidbcloud/AgentTeam/internal/apr/provider/copilot/auth
github.com/tidbcloud/AgentTeam/internal/apr/provider/copilot/state
github.com/tidbcloud/AgentTeam/internal/apr/runtime
github.com/tidbcloud/AgentTeam/internal/apr/tokenencoding
github.com/tidbcloud/AgentTeam/internal/apr/types
github.com/tidbcloud/AgentTeam/internal/devsearch
github.com/tidbcloud/AgentTeam/internal/gormtest
github.com/tidbcloud/AgentTeam/internal/knowledgeqa/protocol
github.com/tidbcloud/AgentTeam/internal/maas/capability
github.com/tidbcloud/AgentTeam/internal/maas/availability
github.com/tidbcloud/AgentTeam/internal/quota
github.com/tidbcloud/AgentTeam/internal/store/coordinator
github.com/tidbcloud/AgentTeam/internal/store/message
github.com/tidbcloud/AgentTeam/internal/store/model
github.com/tidbcloud/AgentTeam/internal/store/storeerr
github.com/tidbcloud/AgentTeam/internal/store/testfixture
github.com/tidbcloud/AgentTeam/internal/store/testkit
github.com/tidbcloud/AgentTeam/internal/unread
github.com/tidbcloud/AgentTeam/internal/workflow/errcontract
github.com/tidbcloud/AgentTeam/internal/workflow/errcontract/gen
github.com/tidbcloud/AgentTeam/internal/workflow/agentruntime
github.com/tidbcloud/AgentTeam/internal/workflow/binding
github.com/tidbcloud/AgentTeam/internal/workflow/bindingmanage
github.com/tidbcloud/AgentTeam/internal/workflow/calendar
github.com/tidbcloud/AgentTeam/internal/workflow/eventcontract
github.com/tidbcloud/AgentTeam/internal/workflow/plan/internal/expression
github.com/tidbcloud/AgentTeam/internal/workflow/invocation
github.com/tidbcloud/AgentTeam/internal/workflow/invocationmanage
github.com/tidbcloud/AgentTeam/internal/workflow/payload
github.com/tidbcloud/AgentTeam/internal/workflow/plan
github.com/tidbcloud/AgentTeam/internal/workflow/planmanage
github.com/tidbcloud/AgentTeam/internal/workflow/publicerr
github.com/tidbcloud/AgentTeam/internal/workflow/run
github.com/tidbcloud/AgentTeam/internal/workflow/runmanage
github.com/tidbcloud/AgentTeam/internal/workflow/runartifact
github.com/tidbcloud/AgentTeam/internal/workflow/runlimit
github.com/tidbcloud/AgentTeam/internal/workflow/orchestrator
github.com/tidbcloud/AgentTeam/presets
github.com/tidbcloud/AgentTeam/internal/extension/spec
github.com/tidbcloud/AgentTeam/presets/internal/locale
EOF
      ;;
    coredb-and-tidb)
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/internal/coredb
github.com/tidbcloud/AgentTeam/internal/coredb/testkit
github.com/tidbcloud/AgentTeam/internal/coredb/testkit/tidbruntime
github.com/tidbcloud/AgentTeam/internal/migrations
github.com/tidbcloud/AgentTeam/internal/tidb
EOF
      ;;
    store-app-data)
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/internal/store
github.com/tidbcloud/AgentTeam/internal/store/app/installation
github.com/tidbcloud/AgentTeam/internal/store/app/registry
github.com/tidbcloud/AgentTeam/internal/store/app/runtime
github.com/tidbcloud/AgentTeam/internal/store/app/webhooks
EOF
      ;;
    store-org-data)
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/internal/api/org
github.com/tidbcloud/AgentTeam/internal/store/organization
github.com/tidbcloud/AgentTeam/internal/store/organization/feishu
github.com/tidbcloud/AgentTeam/internal/store/organization/schema
github.com/tidbcloud/AgentTeam/internal/store/orgmembership
EOF
      ;;
    store-task-data)
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/internal/api/task
github.com/tidbcloud/AgentTeam/internal/api/team
github.com/tidbcloud/AgentTeam/internal/api/teamworkflow
github.com/tidbcloud/AgentTeam/internal/store/taskcompat
github.com/tidbcloud/AgentTeam/internal/store/tasktimeline
github.com/tidbcloud/AgentTeam/internal/store/team
github.com/tidbcloud/AgentTeam/internal/store/teamworkflow
EOF
      ;;
    meshx-api-runtime)
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/internal/agsbridge
github.com/tidbcloud/AgentTeam/internal/agsbridge/gittransport
github.com/tidbcloud/AgentTeam/internal/agsbridge/wiki
github.com/tidbcloud/AgentTeam/internal/agsbridge/workspaceurl
github.com/tidbcloud/AgentTeam/internal/inprochttp
github.com/tidbcloud/AgentTeam/internal/kbgovernance
github.com/tidbcloud/AgentTeam/internal/api/actionsrunner
github.com/tidbcloud/AgentTeam/internal/api/ags
github.com/tidbcloud/AgentTeam/internal/api/automation
github.com/tidbcloud/AgentTeam/internal/api/kb
github.com/tidbcloud/AgentTeam/internal/api/memory
github.com/tidbcloud/AgentTeam/internal/api/meshxapi
github.com/tidbcloud/AgentTeam/internal/api/providertokens
github.com/tidbcloud/AgentTeam/internal/api/repocache
github.com/tidbcloud/AgentTeam/internal/api/repotokens
github.com/tidbcloud/AgentTeam/internal/api/roles
github.com/tidbcloud/AgentTeam/internal/api/skills
github.com/tidbcloud/AgentTeam/internal/identity/trusted
github.com/tidbcloud/AgentTeam/internal/automation/blob
github.com/tidbcloud/AgentTeam/internal/database
github.com/tidbcloud/AgentTeam/internal/resource/server
github.com/tidbcloud/AgentTeam/internal/meshxruntime
github.com/tidbcloud/AgentTeam/internal/schema
github.com/tidbcloud/AgentTeam/internal/api/content
EOF
      ;;
    meshx-domain-data)
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/internal/service/actionsrunner
github.com/tidbcloud/AgentTeam/internal/service/automation
github.com/tidbcloud/AgentTeam/internal/automation/dsl
github.com/tidbcloud/AgentTeam/internal/service/automation/runtime
github.com/tidbcloud/AgentTeam/internal/service/kb
github.com/tidbcloud/AgentTeam/internal/service/knowledgeqa/runtime
github.com/tidbcloud/AgentTeam/internal/service/memory
github.com/tidbcloud/AgentTeam/internal/service/repocache
github.com/tidbcloud/AgentTeam/internal/service/roles
github.com/tidbcloud/AgentTeam/internal/service/skills
github.com/tidbcloud/AgentTeam/internal/store/automation
github.com/tidbcloud/AgentTeam/internal/store/automationcutover
github.com/tidbcloud/AgentTeam/internal/store/kb
github.com/tidbcloud/AgentTeam/internal/contentpayload
github.com/tidbcloud/AgentTeam/internal/service/content/migration
github.com/tidbcloud/AgentTeam/internal/service/content
github.com/tidbcloud/AgentTeam/internal/store/content/migration
github.com/tidbcloud/AgentTeam/internal/store/content
EOF
      ;;
    *)
      echo "unknown shard: ${shard_name}" >&2
      return 1
      ;;
  esac
}

write_declared_packages() {
  local shard_name

  for shard_name in "${shard_names[@]}"; do
    shard_packages "${shard_name}"
  done
  split_test_packages
}

split_test_packages() {
  all_split_tests | cut -f1 | LC_ALL=C sort -u
}

all_split_tests() {
  local shard_name

  for shard_name in "${shard_names[@]}"; do
    shard_split_tests "${shard_name}"
  done
}

shard_split_tests() {
  local shard_name="$1"

  case "${shard_name}" in
    api-support)
      # Extension service integration tests provision enough isolated TiDB
      # fixtures that one regular api-core batch can exceed the required
      # job's wall-clock ceiling under shared clone-slot contention. Keep the
      # package exhaustive but split it into four logical groups on the lighter
      # api-support shard. The AW job drains A before it starts W, so t.Parallel
      # cannot restore the cross-group fixture race.
      # validate_split_tests proves every top-level Test/Example matches
      # exactly one group.
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/internal/service/extensionflow	^Test[AW]
github.com/tidbcloud/AgentTeam/internal/service/extensionflow	^TestC
github.com/tidbcloud/AgentTeam/internal/service/extensionflow	^Test[BIK]
github.com/tidbcloud/AgentTeam/internal/service/extensionflow	^Test[ER]
github.com/tidbcloud/AgentTeam/internal/service/extensionflow	^Test[MPS]
github.com/tidbcloud/AgentTeam/internal/service/extensionflow	^TestT
EOF
      ;;
    coredb-and-tidb)
      # The TiDB migration/schema integration test suite was retired from this
      # repository in favor of a dedicated coverage repository; the package is
      # still compiled here (see shard_packages) but has no split test groups.
      cat <<'EOF'
EOF
      ;;
    store-app-data)
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/internal/store/app	^Test[ADGIJMOPTUW]
github.com/tidbcloud/AgentTeam/internal/store/taskboard	^Test[TM]
EOF
      ;;
    store-org-data)
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/internal/store/app	^Test[BCEFHLNRSV]
EOF
      ;;
    store-task-data)
      cat <<'EOF'
github.com/tidbcloud/AgentTeam/internal/store/taskboard	^Test[^TM]
EOF
      ;;
  esac
}

validate_split_test_package() {
  local package="$1"
  shift
  local patterns=("$@")
  local test_name pattern match_count
  local tests_file

  tests_file="$(mktemp)"
  go test -list '^(Test|Example)' "${package}" | awk '/^(Test|Example)/ { print }' > "${tests_file}"
  if [[ ! -s "${tests_file}" ]]; then
    echo "split test package ${package} has no tests or examples" >&2
    rm -f "${tests_file}"
    exit 1
  fi

  while IFS= read -r test_name; do
    match_count=0
    for pattern in "${patterns[@]}"; do
      if [[ "${test_name}" =~ ${pattern} ]]; then
        match_count=$((match_count + 1))
      fi
    done
    if [[ "${match_count}" -ne 1 ]]; then
      echo "split test ${package} ${test_name} matched ${match_count} groups, expected exactly 1" >&2
      rm -f "${tests_file}"
      exit 1
    fi
  done < "${tests_file}"

  rm -f "${tests_file}"
}

validate_split_tests() {
  local package split_package pattern
  local patterns=()

  while IFS= read -r package; do
    patterns=()
    while IFS=$'\t' read -r split_package pattern; do
      [[ "${split_package}" == "${package}" ]] || continue
      patterns+=("${pattern}")
    done < <(all_split_tests)
    validate_split_test_package "${package}" "${patterns[@]}"
  done < <(split_test_packages)
}

list_backend_packages() {
  {
    go list ./... | grep -Ev '/(node_modules|\.worktrees)(/|$)'
    list_books_infra_packages
    list_presets_packages
  } | LC_ALL=C sort -u
}

list_books_infra_packages() {
  cat <<'EOF'
github.com/tidbcloud/AgentTeam/books/_infra/scripts/plancheck
EOF
}

validate() {
  local expected_file declared_file duplicates_file
  local shard_name
  local migration_target migration_target_commit

  migration_target="${AGENTTEAM_TIDB_MIGRATION_BASE_REF:-}"
  if [[ -z "${migration_target}" ]]; then
    echo "AGENTTEAM_TIDB_MIGRATION_BASE_REF must be set to the integration target commit SHA for backend validation" >&2
    exit 1
  fi
  migration_target_commit="$(git rev-parse --verify "${migration_target}^{commit}" 2>/dev/null)" || {
    echo "AGENTTEAM_TIDB_MIGRATION_BASE_REF does not resolve to an integration target commit: ${migration_target}" >&2
    exit 1
  }
  if [[ "${migration_target}" != "${migration_target_commit}" ]]; then
    echo "AGENTTEAM_TIDB_MIGRATION_BASE_REF must be an immutable full commit SHA, not a mutable ref: ${migration_target}" >&2
    exit 1
  fi

  expected_file="$(mktemp)"
  declared_file="$(mktemp)"
  duplicates_file="$(mktemp)"

  list_backend_packages | LC_ALL=C sort > "${expected_file}"
  write_declared_packages | LC_ALL=C sort > "${declared_file}"
  LC_ALL=C sort "${declared_file}" | uniq -d > "${duplicates_file}"

  if [[ -s "${duplicates_file}" ]]; then
    echo "duplicate backend shard package assignments detected:" >&2
    cat "${duplicates_file}" >&2
    rm -f "${expected_file}" "${declared_file}" "${duplicates_file}"
    exit 1
  fi

  if ! diff -u "${expected_file}" "${declared_file}"; then
    echo "backend shard package coverage does not match go list ./..." >&2
    rm -f "${expected_file}" "${declared_file}" "${duplicates_file}"
    exit 1
  fi

  for shard_name in "${shard_names[@]}"; do
    if [[ -z "$(shard_packages "${shard_name}")" ]]; then
      echo "backend shard ${shard_name} is empty" >&2
      rm -f "${expected_file}" "${declared_file}" "${duplicates_file}"
      exit 1
    fi
  done

  validate_split_tests
  validate_nested_modules

  # This guard uses go/parser to close the complete production message writer
  # and post-commit owner call graph. Keep it in the Go-equipped backend
  # validation job rather than the language-agnostic harness-fast image.
  guardrails/message/post-commit-producers.sh --check-only
  guardrails/message/scope-message-cutover.sh
  guardrails/message/unread-compat-builders.sh
  guardrails/message/author-service-kind-contract.sh
  guardrails/message/author-service-kind-contract-test.sh
  guardrails/ags/check-authority-aware-inventory.sh
  guardrails/storage/tidb-migration-history.sh
  guardrails/task/task-timeline-write-boundary.sh
  guardrails/repository/check-no-committed-secrets.sh

  rm -f "${expected_file}" "${declared_file}" "${duplicates_file}"
}

validate_nested_modules() {
  local module
  for module in dev x/proc; do
    (
      cd "${repo_root}/${module}"
      go test ./... -run '^$'
    )
  done
}

run_nested_module_tests() {
  local module
  for module in dev x/proc; do
    (
      cd "${repo_root}/${module}"
      go test ./... -count=1
    )
  done
}

list_presets_packages() {
  cd "${repo_root}/presets" && go list ./... | grep -Ev '/(node_modules|\.worktrees)(/|$)'
}

list_shards() {
  local shard_name package_count split_count

  for shard_name in "${shard_names[@]}"; do
    package_count="$(shard_packages "${shard_name}" | wc -l | tr -d ' ')"
    split_count="$(shard_split_tests "${shard_name}" | wc -l | tr -d ' ')"
    if [[ "${split_count}" -eq 0 ]]; then
      printf '%s\t%s packages\n' "${shard_name}" "${package_count}"
    else
      printf '%s\t%s packages + %s split test groups\n' "${shard_name}" "${package_count}" "${split_count}"
    fi
  done
}

run_shard() {
  local shard_name="$1"
  local go_test_parallel="${AGENTTEAM_BACKEND_GO_TEST_P:-4}"
  local package_batch_size="${AGENTTEAM_BACKEND_PACKAGE_BATCH_SIZE:-${go_test_parallel}}"
  local go_test_timeout="${AGENTTEAM_BACKEND_GO_TEST_TIMEOUT:-20m}"
  local packages=()
  local split_tests=()
  local package_count=0
  local split_test_count=0
  local package pattern split_test

  while IFS= read -r package; do
    [[ -n "${package}" ]] || continue
    packages+=("${package}")
    package_count=$((package_count + 1))
  done < <(shard_packages "${shard_name}")
  while IFS=$'\t' read -r package pattern; do
    [[ -n "${package}" ]] || continue
    split_tests+=("${package}"$'\t'"${pattern}")
    split_test_count=$((split_test_count + 1))
  done < <(shard_split_tests "${shard_name}")
  if [[ "${package_count}" -eq 0 && "${split_test_count}" -eq 0 ]]; then
    echo "backend shard ${shard_name} has no packages" >&2
    exit 1
  fi

  export GOCACHE="${GOCACHE:-/tmp/agentteam-go-cache}"
  export PATH="${HOME}/.tiup/bin:${PATH}"
  # Keep batches at one package for these DB-heavy shards by default. All six
  # shards run in parallel in required CI, and starting 2 x 4 package binaries
  # per shard makes each package's timeout include a long wait for the same
  # machine-global TiDB clone slots. Keep `go test -p` independent from batch
  # size so dependency compilation retains the historical parallelism.
  case "${shard_name}" in
    api-core|store-domains|coredb-and-tidb|store-app-data|store-org-data|store-task-data)
      # Emit one bounded timing record per isolated database so clone wait and
      # provisioning regressions remain distinguishable from test-body cost.
      export AGENTTEAM_TIDB_TEST_TIMING="${AGENTTEAM_TIDB_TEST_TIMING:-1}"
      if [[ -n "${AGENTTEAM_BACKEND_TIDB_PACKAGE_BATCH_SIZE:-}" ]]; then
        package_batch_size="${AGENTTEAM_BACKEND_TIDB_PACKAGE_BATCH_SIZE}"
      elif [[ -z "${AGENTTEAM_BACKEND_PACKAGE_BATCH_SIZE:-}" ]]; then
        package_batch_size=1
      fi
      ;;
  esac

  local shard_job_limit="${AGENTTEAM_BACKEND_SHARD_JOB_P:-2}"
  local shard_job_dir
  local shard_job_count=0
  local shard_job_pids=()
  local shard_job_failed=0
  local shard_job_pid
  local shard_worker_index

  if [[ ! "${shard_job_limit}" =~ ^[1-9][0-9]*$ ]]; then
    echo "AGENTTEAM_BACKEND_SHARD_JOB_P must be a positive integer, got ${shard_job_limit}" >&2
    return 1
  fi
  shard_job_dir="$(mktemp -d)"

  run_shard_go_test() {
    if [[ "${AGENTTEAM_TIDB_TEST_TIMING:-}" == "1" ]]; then
      local timing_file
      local test_status
      timing_file="$(mktemp)"
      if AGENTTEAM_TIDB_TEST_TIMING_FILE="${timing_file}" go test "$@" -count=1 -p "${go_test_parallel}" -timeout="${go_test_timeout}"; then
        test_status=0
      else
        test_status=$?
      fi
      cat "${timing_file}"
      rm -f "${timing_file}"
      return "${test_status}"
    fi
    go test "$@" -count=1 -p "${go_test_parallel}" -timeout="${go_test_timeout}"
  }

  run_shard_test_job() {
    local first_status=0
    local second_status=0

    if [[ "${1:-}" == "github.com/tidbcloud/AgentTeam/internal/service/extensionflow" &&
      "${2:-}" == "-run" && "${3:-}" == '^Test[AW]' ]]; then
      # Keep the logical AW group in one shard job, but finish every A test
      # process before starting W. This makes the ordering independent of any
      # current or future t.Parallel call inside either test set.
      run_shard_go_test "$1" "-run" '^TestA' || first_status=$?
      run_shard_go_test "$1" "-run" '^TestW' || second_status=$?
      if [[ "${first_status}" -ne 0 ]]; then
        return "${first_status}"
      fi
      return "${second_status}"
    fi

    run_shard_go_test "$@"
  }

  write_shard_job() {
    local label="$1"
    local job_file
    shift

    shard_job_count=$((shard_job_count + 1))
    printf -v job_file '%s/job-%06d' "${shard_job_dir}" "${shard_job_count}"
    {
      printf '%s\n' "${label}"
      printf '%s\n' "$@"
    } >"${job_file}"
  }

  run_shard_worker() {
    local args=()
    local claimed_job=""
    local duration_seconds
    local job_file
    local job_label
    local line
    local line_number
    local run_status
    local start_elapsed
    local start_seconds
    local worker_failed=0

    while true; do
      claimed_job=""
      for job_file in "${shard_job_dir}"/job-*; do
        [[ -f "${job_file}" ]] || continue
        if mkdir "${job_file}.claimed" 2>/dev/null; then
          claimed_job="${job_file}"
          break
        fi
      done
      [[ -n "${claimed_job}" ]] || break

      args=()
      job_label=""
      line_number=0
      while IFS= read -r line; do
        if (( line_number == 0 )); then
          job_label="${line}"
        else
          args+=("${line}")
        fi
        line_number=$((line_number + 1))
      done <"${claimed_job}"

      start_elapsed="${SECONDS}"
      start_seconds="$(date +%s)"
      printf '[backend:timing] event=start shard=%q job=%q epoch_seconds=%s\n' \
        "${shard_name}" "${job_label}" "${start_seconds}"
      if run_shard_test_job "${args[@]}"; then
        run_status=0
      else
        run_status=$?
        worker_failed=1
      fi
      duration_seconds=$((SECONDS - start_elapsed))
      printf '[backend:timing] event=finish shard=%q job=%q status=%s duration_seconds=%s\n' \
        "${shard_name}" "${job_label}" "${run_status}" "${duration_seconds}"
    done

    return "${worker_failed}"
  }

  # Batch regular packages into groups of at most package_batch_size so the
  # `-p` flag parallelizes across package test binaries instead of being a
  # no-op on single-package invocations. Each group is one shard job; the job
  # worker pool bounds how many `go test` processes run concurrently, so the
  # effective test-binary concurrency is AGENTTEAM_BACKEND_SHARD_JOB_P x
  # AGENTTEAM_BACKEND_PACKAGE_BATCH_SIZE. Workers atomically claim the next job as
  # soon as they finish, avoiding the old FIFO wait that could idle a slot
  # behind an unrelated long-running package.
  local package_group=()
  local package_group_size=0
  local job_label

  for package in "${packages[@]}"; do
    package_group+=("${package}")
    package_group_size=$((package_group_size + 1))
    if (( package_group_size >= package_batch_size )); then
      job_label="packages:$(IFS=,; printf '%s' "${package_group[*]}")"
      write_shard_job "${job_label}" "${package_group[@]}"
      package_group=()
      package_group_size=0
    fi
  done
  if (( package_group_size > 0 )); then
    job_label="packages:$(IFS=,; printf '%s' "${package_group[*]}")"
    write_shard_job "${job_label}" "${package_group[@]}"
    package_group=()
    package_group_size=0
  fi
  if [[ "${split_test_count}" -ne 0 ]]; then
    for split_test in "${split_tests[@]}"; do
      package="${split_test%%$'\t'*}"
      pattern="${split_test#*$'\t'}"
      write_shard_job "split:${package}:${pattern}" "${package}" "-run" "${pattern}"
    done
  fi

  local shard_worker_count="${shard_job_limit}"
  if (( shard_worker_count > shard_job_count )); then
    shard_worker_count="${shard_job_count}"
  fi
  for ((shard_worker_index = 0; shard_worker_index < shard_worker_count; shard_worker_index++)); do
    run_shard_worker &
    shard_job_pids+=("$!")
  done
  for shard_job_pid in "${shard_job_pids[@]}"; do
    if ! wait "${shard_job_pid}"; then
      shard_job_failed=1
    fi
  done
  rm -rf "${shard_job_dir}"

  if [[ "${shard_job_failed}" -ne 0 ]]; then
    return 1
  fi
  if [[ "${shard_name}" == "api-support" ]]; then
    run_nested_module_tests
  fi
}


cgo_compiler_available() {
  local probe_dir
  probe_dir="$(mktemp -d)"
  cat > "${probe_dir}/go.mod" <<'EOF'
module cgo-probe
EOF
  cat > "${probe_dir}/main.go" <<'EOF'
package main

/*
#include <stdlib.h>
*/
import "C"

func main() { C.free(nil) }
EOF
  (cd "${probe_dir}" && CGO_ENABLED=1 go build . >/dev/null 2>&1)
  local status="$?"
  rm -rf "${probe_dir}"
  return "${status}"
}

run_go_e2e() {
  local go_test_parallel="${AGENTTEAM_BACKEND_GO_E2E_P:-4}"
  local go_test_timeout="${AGENTTEAM_BACKEND_GO_E2E_TIMEOUT:-20m}"

  export GOCACHE="${GOCACHE:-/tmp/agentteam-go-cache}"
  export PATH="${HOME}/.tiup/bin:${PATH}"
  # Some e2e transitive dependencies require cgo even when the surrounding
  # backend CI environment sets CGO_ENABLED=0 for ordinary backend packages.
  # Some backend required workers intentionally omit a C compiler; treat that
  # as an environment skip instead of failing the whole backend gate.
  if ! cgo_compiler_available; then
    echo "skipping go e2e: no working cgo C compiler available" >&2
    return 0
  fi
  export CGO_ENABLED=1

  go test -tags=e2e ./e2e -count=1 -p "${go_test_parallel}" -timeout="${go_test_timeout}"
}

main() {
  cleanup_ci_leftovers

  local command="${1:-}"
  case "${command}" in
    validate)
      validate
      ;;
    list)
      list_shards
      ;;
    run)
      if [[ "$#" -ne 2 ]]; then
        usage >&2
        exit 1
      fi
      run_shard "$2"
      ;;
    go-e2e)
      run_go_e2e
      ;;
    *)
      usage >&2
      exit 1
      ;;
  esac
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  main "$@"
fi
