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

repo_root_has_guardrail_files() {
  local root="$1"
  [[ -f "$root/guardrails/message/README.md" && -f "$root/scripts/ci-backend-required.sh" ]]
}

repo_root_from_candidate() {
  local candidate="$1"
  local dir root
  [[ -n "$candidate" ]] || return 1

  if [[ -f "$candidate" ]]; then
    candidate="$(readlink -f "$candidate" 2>/dev/null || printf '%s\n' "$candidate")"
    dir="$(cd "$(dirname "$candidate")" && pwd -P)" || return 1
  else
    dir="$candidate"
  fi
  [[ -d "$dir" ]] || return 1

  if root="$(git -C "$dir" rev-parse --show-toplevel 2>/dev/null)" && repo_root_has_guardrail_files "$root"; then
    printf '%s\n' "$root"
    return 0
  fi
  if root="$(cd "$dir/../.." 2>/dev/null && pwd -P)" && repo_root_has_guardrail_files "$root"; then
    printf '%s\n' "$root"
    return 0
  fi
  if root="$(cd "$dir" 2>/dev/null && pwd -P)" && repo_root_has_guardrail_files "$root"; then
    printf '%s\n' "$root"
    return 0
  fi

  return 1
}

stdin_script_path() {
  local fd0_path
  fd0_path="$(readlink "/proc/$$/fd/0" 2>/dev/null || true)"
  if [[ -n "$fd0_path" && -f "$fd0_path" ]]; then
    printf '%s\n' "$fd0_path"
  fi
}

resolve_repo_root() {
  local source_path root candidate

  if [[ -n "${AGENTTEAM_MESSAGE_UNREAD_COMPAT_GUARDRAIL_ROOT:-}" ]]; then
    printf '%s\n' "$AGENTTEAM_MESSAGE_UNREAD_COMPAT_GUARDRAIL_ROOT"
    return 0
  fi

  source_path="${BASH_SOURCE[0]:-}"
  if [[ -z "$source_path" || "$source_path" == "bash" || "$source_path" == "-" || ! -f "$source_path" ]]; then
    source_path="$(stdin_script_path)"
  fi

  for candidate in \
    "$source_path" \
    "$PWD" \
    "${WORKSPACE:-}" \
    "${GITHUB_WORKSPACE:-}" \
    "${CI_PROJECT_DIR:-}" \
    "${BUILD_WORKSPACE_DIRECTORY:-}" \
    "${OLDPWD:-}"; do
    if root="$(repo_root_from_candidate "$candidate")"; then
      printf '%s\n' "$root"
      return 0
    fi
  done

  printf 'FAIL: unable to determine repository root for unread compatibility surface guardrail\n' >&2
  return 1
}

ROOT="$(resolve_repo_root)"
cd "$ROOT"

failures=0

fail() {
  printf 'FAIL: %s\n' "$1" >&2
  failures=$((failures + 1))
}

require_contains() {
  local file="$1"
  local pattern="$2"
  local message="$3"
  if [[ ! -f "$file" ]]; then
    fail "$message (missing $file)"
    return
  fi
  if ! grep -E -q -- "$pattern" "$file"; then
    fail "$message"
  fi
}

require_contains_text() {
  local file="$1"
  local pattern="$2"
  local message="$3"
  if [[ ! -f "$file" ]]; then
    fail "$message (missing $file)"
    return
  fi
  if ! tr '\n' ' ' <"$file" | grep -E -q -- "$pattern"; then
    fail "$message"
  fi
}

check_no_matches() {
  local label="$1"
  local pattern="$2"
  shift 2

  local grep_args=()
  local paths=()
  local arg
  for arg in "$@"; do
    if [[ "$arg" == --* ]]; then
      grep_args+=("$arg")
    else
      paths+=("$arg")
    fi
  done

  local matches
  if (( ${#grep_args[@]} > 0 )); then
    matches="$(grep -R -n -I -E --exclude='*_test.go' "${grep_args[@]}" -- "$pattern" "${paths[@]}" 2>/dev/null || true)"
  else
    matches="$(grep -R -n -I -E --exclude='*_test.go' -- "$pattern" "${paths[@]}" 2>/dev/null || true)"
  fi
  if [[ -n "$matches" ]]; then
    fail "$label"
    printf '%s\n' "$matches" >&2
  fi
}

check_matches_allowed() {
  local label="$1"
  local pattern="$2"
  local allowed_file_pattern="$3"
  shift 3

  local matches unexpected
  matches="$(grep -R -n -I -E --exclude='*_test.go' -- "$pattern" "$@" 2>/dev/null || true)"
  unexpected="$(printf '%s\n' "$matches" | sed '/^$/d' | grep -v -E "$allowed_file_pattern" || true)"
  if [[ -n "$unexpected" ]]; then
    fail "$label"
    printf '%s\n' "$unexpected" >&2
  fi
}

# Legacy database aggregation helpers may exist only behind store-owned
# compatibility/diagnostic surfaces. API, realtime, bootstrap, and web warm
# paths must not depend on them directly or via narrow interfaces.
require_contains \
  guardrails/message/README.md \
  './unread-compat-builders\.sh' \
  'message guardrail README must link the unread compatibility guardrail'

require_contains \
  scripts/ci-backend-required.sh \
  'guardrails/message/unread-compat-builders\.sh' \
  'backend required CI guardrails must run the unread compatibility guardrail'

require_contains \
  specs/message/read-path-performance-boundaries.md \
  'guardrails/message/unread-compat-builders\.sh' \
  'read-path performance spec must name the unread compatibility guardrail'

check_matches_allowed \
  'exact unread count aggregation must not be reachable from production warm-path callers' \
  '(^|[^[:alnum:]_])UnreadCountsForUser[[:space:]]*\(' \
  '^internal/store/app/app_messages\.go:' \
  internal cmd desktop scripts

check_matches_allowed \
  'workspace unread DB existence aggregation must not be reachable from production warm-path callers' \
  '(^|[^[:alnum:]_])HasUnreadForUserWorkspaces[[:space:]]*\(' \
  '^internal/store/app/app_messages\.go:' \
  internal cmd desktop scripts

check_matches_allowed \
  'exact unread SQL builder must stay store-local' \
  '(^|[^[:alnum:]_])BuildUnreadCountsForUserScopedLocalSeqQuery([^[:alnum:]_]|$)' \
  '^(internal/store/message/shared_query_builders\.go|internal/store/app/app_messages\.go):' \
  internal cmd desktop scripts

check_matches_allowed \
  'workspace unread SQL builder must stay store-local' \
  '(^|[^[:alnum:]_])BuildWorkspaceHasUnreadForUserScopedQuery([^[:alnum:]_]|$)' \
  '^(internal/store/message/shared_query_builders\.go|internal/store/app/app_messages\.go):' \
  internal cmd desktop scripts

check_no_matches \
  'legacy conversation unread summary APIs must stay removed from production code' \
  '(^|[^[:alnum:]_])ConversationSummaryForUser(Filtered)?[[:space:]]*\(' \
  internal cmd desktop scripts

check_no_matches \
  'legacy conversation unread summary SQL builders must stay removed from production code' \
  '(^|[^[:alnum:]_])BuildConversationUnreadSummaryForUserScopedQuery(Filtered)?([^[:alnum:]_]|$)' \
  internal cmd desktop scripts

check_no_matches \
  'legacy channel unread compatibility routes must stay removed from production code' \
  'channels/unread|channels/:channelId/(read|read-all|unread)|ChannelReadRequest' \
  internal/api/channel internal/api/dto internal/router web/src

check_no_matches \
  'channel unread compatibility route must not call store exact unread aggregation' \
  '(^|[^[:alnum:]_])UnreadCountsForUser[[:space:]]*\(' \
  internal/api/channel internal/router

# Route-independent first paint, workspace switcher/polling, heartbeat,
# socket reconnect, and warm refresh code may read cached/runtime unread hints,
# but must not import or invoke exact unread compatibility APIs.
warm_frontend_paths=(
  web/src/agentteam/AgentTeamApp.tsx
  web/src/agentteam/pages/WorkspaceShell.tsx
  web/src/agentteam/pages/workspace-shell/useWorkspaceOverviewData.ts
  web/src/agentteam/pages/workspace-shell/useWorkspaceSocket.ts
  web/src/agentteam/pages/workspace-shell/useWorkspaceSocketLifecycleEffects.ts
  web/src/agentteam/workspace/shared/unreadCounts.ts
  web/src/agentteam/workspace/workspaceShellLoadPolicy.ts
)

check_no_matches \
  'warm frontend paths must not call exact unread compatibility endpoints or query options' \
  'listUnreadCounts|unreadCountsQueryOptions|conversationSummaryQueryOptions|getConversationSummary|/channels/unread|channels/unread|refreshWorkspaceUnreadCounts' \
  "${warm_frontend_paths[@]}"

require_contains_text \
  web/src/agentteam/pages/workspace-shell/useWorkspaceOverviewData.ts \
  'queryKey:[[:space:]]*workspaceQueryKeys\.unreadCounts\(workspace\.id\),.*enabled:[[:space:]]*false' \
  'WorkspaceShell overview must observe cached unread hints without enabling an exact unread query'

require_contains \
  web/src/agentteam/AgentTeamApp.tsx \
  'listWorkspaces\(\{[[:space:]]*includeUnread:[[:space:]]*false,?[[:space:]]*\}\)' \
  'warm workspace polling must request workspace rows without unread recomputation'

require_contains_text \
  web/src/agentteam/workspace/shared/unreadCounts.ts \
  'export function replaceWorkspaceUnreadCounts\([^)]*counts:[[:space:]]*Record<string,[[:space:]]*number>.*setQueryData\([[:space:]]*workspaceQueryKeys\.unreadCounts\(trimmedWorkspaceId\).*export function setWorkspaceUnreadScopeCount\([^)]*count:[[:space:]]*number.*setQueryData<Record<string,[[:space:]]*number>>\([[:space:]]*workspaceQueryKeys\.unreadCounts\(trimmedWorkspaceId\).*export function clearWorkspaceUnreadScope' \
  'workspace unread store must expose snapshot replacement plus scoped stream/local mutation helpers without calling legacy unread endpoints'

check_no_matches \
  'workspace unread count cache writes outside the shared store must not return' \
  'setQueryData(<Record<string, number>>)?\([[:space:]]*workspaceQueryKeys\.unreadCounts|invalidateQueries\(\{[[:space:]]*queryKey:[[:space:]]*workspaceQueryKeys\.unreadCounts' \
  --exclude='unreadCounts.ts' --exclude='*.test.ts' web/src/agentteam/pages web/src/agentteam/workspace

if (( failures > 0 )); then
  exit 1
fi

printf 'Unread compatibility surface guardrail passed.\n'
