import { api } from '@renderer/api';
import { mergeTeamMessages } from '@renderer/utils/mergeTeamMessages';
import {
  buildOpenCodeRuntimeDeliveryDiagnostics,
  isOpenCodeRuntimeDeliveryHardUxFailure,
} from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics';
import { normalizePath } from '@renderer/utils/pathNormalize';
import {
  buildTaskChangePresenceKey,
  buildTaskChangeRequestOptions,
  canDisplayTaskChangesForOptions,
  type TaskChangeRequestOptions,
} from '@renderer/utils/taskChangeRequest';
import { toMessageKey } from '@renderer/utils/teamMessageKey';
import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext';
import { IpcError, unwrapIpc } from '@renderer/utils/unwrapIpc';
import { stripAgentBlocks } from '@shared/constants/agentBlocks';
import { getMemberColorByName } from '@shared/constants/memberColors';
import { DEFAULT_TEAM_GRAPH_LAYOUT_MODE } from '@shared/constants/teamGraphLayoutMode';
import { DEFAULT_TOOL_APPROVAL_SETTINGS } from '@shared/types/team';
import { isLeadMember } from '@shared/utils/leadDetection';
import { createLogger } from '@shared/utils/logger';
import { migrateProviderBackendId } from '@shared/utils/providerBackend';
import { formatTaskDisplayLabel } from '@shared/utils/taskIdentity';
import { buildTeamGraphDefaultLayoutSeed } from '@shared/utils/teamGraphDefaultLayout';
import { getStableTeamOwnerId } from '@shared/utils/teamStableOwnerId';
import {
  getTeamTaskWorkflowColumn,
  isTeamTaskFinalForCompletionNotification,
  isTeamTaskNeedsFixActionable,
} from '@shared/utils/teamTaskState';

import { noteTeamRefreshFanout } from '../teamRefreshFanoutDiagnostics';
import { getWorktreeNavigationState } from '../utils/stateResetHelpers';

import type { AppState } from '../types';
import type { GraphLayoutMode, GraphOwnerSlotAssignment } from '@claude-teams/agent-graph';
import type { AppConfig } from '@renderer/types/data';
import type { TeamMessagesPanelMode } from '@renderer/types/teamMessagesPanelMode';
import type { OpenCodeRuntimeDeliveryDebugDetails } from '@renderer/utils/openCodeRuntimeDeliveryDiagnostics';
import type {
  ActiveToolCall,
  AddMemberRequest,
  AddTaskCommentRequest,
  CreateTaskRequest,
  CrossTeamSendRequest,
  EffortLevel,
  GlobalTask,
  InboxMessage,
  KanbanColumnId,
  LeadActivityState,
  LeadContextUsage,
  MemberActivityMetaEntry,
  MemberSpawnStatusEntry,
  MemberSpawnStatusesSnapshot,
  NotificationTarget,
  PersistedTeamLaunchSummary,
  ResolvedTeamMember,
  RetryFailedOpenCodeSecondaryLanesResult,
  SendMessageRequest,
  SendMessageResult,
  TaskChangePresenceState,
  TaskComment,
  TeamAgentRuntimeEntry,
  TeamAgentRuntimeResourceSample,
  TeamAgentRuntimeSnapshot,
  TeamCreateRequest,
  TeamGetDataOptions,
  TeamLaunchRequest,
  TeamMemberActivityMeta,
  TeamMemberSnapshot,
  TeamProviderId,
  TeamProvisioningProgress,
  TeamSummary,
  TeamTask,
  TeamTaskStatus,
  TeamViewSnapshot,
  ToolApprovalRequest,
  ToolApprovalSettings,
  UpdateKanbanPatch,
} from '@shared/types';
import type { StateCreator } from 'zustand';

const GRAPH_STABLE_SLOT_LAYOUT_VERSION = 'stable-slots-v1' as const;
const DISABLE_PERSISTED_TEAM_GRAPH_SLOT_ASSIGNMENTS = true;
const logger = createLogger('teamSlice');

const TEAM_GET_DATA_TIMEOUT_MS = 30_000;
const TEAM_FETCH_TIMEOUT_MS = 30_000;
const MEMBER_SPAWN_STATUSES_IPC_RETRY_BACKOFF_MS = 5_000;
const TEAM_REFRESH_BURST_WINDOW_MS = 4_000;
const MEMBER_SPAWN_UI_EQUAL_WARN_THROTTLE_MS = 2_000;
const POST_PAINT_TEAM_ENRICHMENT_FALLBACK_MS = 500;
const inFlightTeamDataRequests = new Map<string, Promise<TeamViewSnapshot>>();
const inFlightRefreshTeamDataCalls = new Map<string, Set<symbol>>();
const pendingFreshTeamDataRefreshes = new Set<string>();
const queuedFullTeamDataRefreshesAfterThin = new Set<string>();
interface PostPaintHandle {
  rafId?: number;
  timerId?: ReturnType<typeof setTimeout>;
  fallbackTimerId?: ReturnType<typeof setTimeout>;
  cancelled: boolean;
  ran: boolean;
}
const postPaintTeamEnrichmentTimers = new Map<string, PostPaintHandle>();
const inFlightTeamMessagesHeadRequests = new Map<string, Promise<RefreshTeamMessagesHeadResult>>();
const inFlightTeamMessagesOlderRequests = new Map<string, Promise<void>>();
const queuedTeamMessagesHeadRefreshesAfterOlder = new Map<
  string,
  Promise<RefreshTeamMessagesHeadResult>
>();
const pendingFreshTeamMessagesHeadRefreshes = new Set<string>();
const inFlightTeamMemberActivityMetaRequests = new Map<string, Promise<void>>();
const pendingFreshTeamMemberActivityMetaRefreshes = new Set<string>();
const pendingTeamPendingReplyRefreshTimers = new Map<string, ReturnType<typeof setTimeout>>();
const activeTeamPendingReplyWaitSourceIdsByTeam = new Map<string, Set<string>>();
const lastResolvedTeamDataRefreshAtByTeam = new Map<string, number>();
const teamLocalStateEpochByTeam = new Map<string, number>();
let inFlightGlobalTasksRefresh: Promise<void> | null = null;
let pendingFreshGlobalTasksRefresh = false;
const memberSpawnStatusesIpcBackoffUntilByTeam = new Map<string, number>();
const teamRefreshBurstDiagnostics = new Map<
  string,
  { windowStartedAt: number; count: number; lastWarnAt: number }
>();
const memberSpawnUiEqualLastWarnAtByTeam = new Map<string, number>();
interface RefreshTeamDataOptions {
  withDedup?: boolean;
}

type TeamDataSnapshotMode = 'full' | 'thin';

function normalizeTeamGetDataOptions(options?: TeamGetDataOptions): TeamGetDataOptions | undefined {
  return options?.includeMemberBranches === false ? { includeMemberBranches: false } : undefined;
}

function shouldIncludeMemberBranches(options?: TeamGetDataOptions): boolean {
  return normalizeTeamGetDataOptions(options)?.includeMemberBranches !== false;
}

function getTeamDataSnapshotMode(options?: TeamGetDataOptions): TeamDataSnapshotMode {
  return shouldIncludeMemberBranches(options) ? 'full' : 'thin';
}

function getTeamDataRequestKey(teamName: string, options?: TeamGetDataOptions): string {
  const normalizedOptions = normalizeTeamGetDataOptions(options);
  return `${teamName}\u0000mode:${getTeamDataSnapshotMode(normalizedOptions)}`;
}

function getTeamDataRequestLabel(teamName: string, options?: TeamGetDataOptions): string {
  const normalizedOptions = normalizeTeamGetDataOptions(options);
  return `team:getData(${teamName},mode=${getTeamDataSnapshotMode(normalizedOptions)})`;
}

function getFullTeamDataRequestKey(teamName: string): string {
  return getTeamDataRequestKey(teamName);
}

function getThinTeamDataRequestKey(teamName: string): string {
  return getTeamDataRequestKey(teamName, { includeMemberBranches: false });
}

function hasFullTeamDataRequestForTeam(teamName: string): boolean {
  return inFlightTeamDataRequests.has(getFullTeamDataRequestKey(teamName));
}

function hasThinTeamDataRequestForTeam(teamName: string): boolean {
  return inFlightTeamDataRequests.has(getThinTeamDataRequestKey(teamName));
}

function clearTeamDataRequestsForTeam(teamName: string): void {
  const prefix = `${teamName}\u0000`;
  for (const key of inFlightTeamDataRequests.keys()) {
    if (key.startsWith(prefix)) {
      inFlightTeamDataRequests.delete(key);
    }
  }
}

type TeamGraphSlotAssignments = Record<string, GraphOwnerSlotAssignment>;
type TeamGraphMemberSeedInput = Pick<TeamMemberSnapshot, 'name' | 'agentId' | 'removedAt'>;
type TeamGraphConfigMemberSeedInput = Pick<
  NonNullable<TeamViewSnapshot['config']['members']>[number],
  'name' | 'agentId' | 'removedAt'
>;
interface TeamGraphLayoutSessionState {
  mode: 'default' | 'manual';
  signature: string | null;
}

export function isTeamDataRefreshPending(teamName: string): boolean {
  return (
    hasFullTeamDataRequestForTeam(teamName) ||
    (inFlightRefreshTeamDataCalls.get(teamName)?.size ?? 0) > 0 ||
    pendingFreshTeamDataRefreshes.has(teamName) ||
    queuedFullTeamDataRefreshesAfterThin.has(teamName)
  );
}

export function getLastResolvedTeamDataRefreshAt(teamName: string): number | undefined {
  return lastResolvedTeamDataRefreshAtByTeam.get(teamName);
}

export function hasActiveTeamPendingReplyWait(teamName: string): boolean {
  return (activeTeamPendingReplyWaitSourceIdsByTeam.get(teamName)?.size ?? 0) > 0;
}

export function getActiveTeamPendingReplyWaits(): Set<string> {
  return new Set(
    Array.from(activeTeamPendingReplyWaitSourceIdsByTeam.entries())
      .filter(([, sourceIds]) => sourceIds.size > 0)
      .map(([teamName]) => teamName)
  );
}

export function __resetTeamSliceModuleStateForTests(): void {
  inFlightTeamDataRequests.clear();
  inFlightRefreshTeamDataCalls.clear();
  pendingFreshTeamDataRefreshes.clear();
  queuedFullTeamDataRefreshesAfterThin.clear();
  for (const teamName of postPaintTeamEnrichmentTimers.keys()) {
    cancelPostPaintTeamEnrichments(teamName);
  }
  postPaintTeamEnrichmentTimers.clear();
  inFlightTeamMessagesHeadRequests.clear();
  inFlightTeamMessagesOlderRequests.clear();
  queuedTeamMessagesHeadRefreshesAfterOlder.clear();
  pendingFreshTeamMessagesHeadRefreshes.clear();
  inFlightTeamMemberActivityMetaRequests.clear();
  pendingFreshTeamMemberActivityMetaRefreshes.clear();
  for (const timer of pendingTeamPendingReplyRefreshTimers.values()) {
    clearTimeout(timer);
  }
  pendingTeamPendingReplyRefreshTimers.clear();
  activeTeamPendingReplyWaitSourceIdsByTeam.clear();
  lastResolvedTeamDataRefreshAtByTeam.clear();
  teamLocalStateEpochByTeam.clear();
  memberSpawnStatusesIpcBackoffUntilByTeam.clear();
  teamRefreshBurstDiagnostics.clear();
  memberSpawnUiEqualLastWarnAtByTeam.clear();
  resolvedMembersSelectorCache.clear();
  resolvedMemberSelectorCache.clear();
  mergedMessagesSelectorCache.clear();
  memberMessagesSelectorCache.clear();
}

function clearTeamScopedSelectorCaches(teamName: string): void {
  resolvedMembersSelectorCache.delete(teamName);
  mergedMessagesSelectorCache.delete(teamName);

  const teamScopedPrefix = `${teamName}:`;
  for (const key of resolvedMemberSelectorCache.keys()) {
    if (key.startsWith(teamScopedPrefix)) {
      resolvedMemberSelectorCache.delete(key);
    }
  }
  for (const key of memberMessagesSelectorCache.keys()) {
    if (key.startsWith(teamScopedPrefix)) {
      memberMessagesSelectorCache.delete(key);
    }
  }
}

function clearTeamScopedTransientState(teamName: string): void {
  clearTeamDataRequestsForTeam(teamName);
  inFlightRefreshTeamDataCalls.delete(teamName);
  pendingFreshTeamDataRefreshes.delete(teamName);
  queuedFullTeamDataRefreshesAfterThin.delete(teamName);
  cancelPostPaintTeamEnrichments(teamName);
  inFlightTeamMessagesHeadRequests.delete(teamName);
  inFlightTeamMessagesOlderRequests.delete(teamName);
  queuedTeamMessagesHeadRefreshesAfterOlder.delete(teamName);
  pendingFreshTeamMessagesHeadRefreshes.delete(teamName);
  inFlightTeamMemberActivityMetaRequests.delete(teamName);
  pendingFreshTeamMemberActivityMetaRefreshes.delete(teamName);
  lastResolvedTeamDataRefreshAtByTeam.delete(teamName);
  memberSpawnStatusesIpcBackoffUntilByTeam.delete(teamName);
  teamRefreshBurstDiagnostics.delete(teamName);
  memberSpawnUiEqualLastWarnAtByTeam.delete(teamName);
  clearTeamScopedSelectorCaches(teamName);
}

function collectTeamScopedVisibleLoadingResets(
  state: Pick<
    TeamSlice,
    'teamMessagesByName' | 'selectedTeamName' | 'selectedTeamLoading' | 'selectedTeamError'
  >,
  teamName: string
): Partial<TeamSlice> {
  const nextTeamMessagesEntry = state.teamMessagesByName[teamName];
  const nextTeamMessagesByName =
    nextTeamMessagesEntry &&
    (nextTeamMessagesEntry.loadingHead || nextTeamMessagesEntry.loadingOlder)
      ? {
          ...state.teamMessagesByName,
          [teamName]: {
            ...nextTeamMessagesEntry,
            loadingHead: false,
            loadingOlder: false,
          },
        }
      : null;

  const shouldResetSelectedSurface =
    state.selectedTeamName === teamName &&
    (state.selectedTeamLoading || state.selectedTeamError != null);

  return {
    ...(nextTeamMessagesByName ? { teamMessagesByName: nextTeamMessagesByName } : {}),
    ...(shouldResetSelectedSurface
      ? {
          selectedTeamLoading: false,
          selectedTeamError: null,
        }
      : {}),
  };
}

function omitTeamKey<T>(record: Record<string, T>, teamName: string): Record<string, T> | null {
  if (!(teamName in record)) {
    return null;
  }
  const next = { ...record };
  delete next[teamName];
  return next;
}

function collectTeamScopedStateRemovals(
  state: Pick<
    TeamSlice,
    | 'provisioningRuns'
    | 'teamDataCacheByName'
    | 'teamAgentRuntimeByTeam'
    | 'teamMessagesByName'
    | 'memberActivityMetaByTeam'
    | 'provisioningSnapshotByTeam'
    | 'currentProvisioningRunIdByTeam'
    | 'currentRuntimeRunIdByTeam'
    | 'provisioningStartedAtFloorByTeam'
    | 'leadActivityByTeam'
    | 'leadContextByTeam'
    | 'activeTaskLogActivityByTeam'
    | 'activeToolsByTeam'
    | 'finishedVisibleByTeam'
    | 'toolHistoryByTeam'
    | 'memberSpawnStatusesByTeam'
    | 'memberSpawnSnapshotsByTeam'
    | 'provisioningErrorByTeam'
  >,
  teamName: string
): Partial<TeamSlice> {
  const nextProvisioningRuns = Object.fromEntries(
    Object.entries(state.provisioningRuns).filter(([, run]) => run.teamName !== teamName)
  ) as Record<string, TeamProvisioningProgress>;
  const nextTeamDataCache = omitTeamKey(state.teamDataCacheByName, teamName);
  const nextTeamAgentRuntime = omitTeamKey(state.teamAgentRuntimeByTeam, teamName);
  const nextTeamMessages = omitTeamKey(state.teamMessagesByName, teamName);
  const nextMemberActivityMeta = omitTeamKey(state.memberActivityMetaByTeam, teamName);
  const nextProvisioningSnapshot = omitTeamKey(state.provisioningSnapshotByTeam, teamName);
  const nextCurrentProvisioningRunId = omitTeamKey(state.currentProvisioningRunIdByTeam, teamName);
  const nextCurrentRuntimeRunId = omitTeamKey(state.currentRuntimeRunIdByTeam, teamName);
  const nextProvisioningStartedAtFloor = omitTeamKey(
    state.provisioningStartedAtFloorByTeam,
    teamName
  );
  const nextLeadActivity = omitTeamKey(state.leadActivityByTeam, teamName);
  const nextLeadContext = omitTeamKey(state.leadContextByTeam, teamName);
  const nextActiveTaskLogActivity = omitTeamKey(state.activeTaskLogActivityByTeam, teamName);
  const nextActiveTools = omitTeamKey(state.activeToolsByTeam, teamName);
  const nextFinishedVisible = omitTeamKey(state.finishedVisibleByTeam, teamName);
  const nextToolHistory = omitTeamKey(state.toolHistoryByTeam, teamName);
  const nextMemberSpawnStatuses = omitTeamKey(state.memberSpawnStatusesByTeam, teamName);
  const nextMemberSpawnSnapshots = omitTeamKey(state.memberSpawnSnapshotsByTeam, teamName);
  const nextProvisioningErrors = omitTeamKey(state.provisioningErrorByTeam, teamName);

  return {
    ...(Object.keys(nextProvisioningRuns).length !== Object.keys(state.provisioningRuns).length
      ? { provisioningRuns: nextProvisioningRuns }
      : {}),
    ...(nextTeamDataCache ? { teamDataCacheByName: nextTeamDataCache } : {}),
    ...(nextTeamAgentRuntime ? { teamAgentRuntimeByTeam: nextTeamAgentRuntime } : {}),
    ...(nextTeamMessages ? { teamMessagesByName: nextTeamMessages } : {}),
    ...(nextMemberActivityMeta ? { memberActivityMetaByTeam: nextMemberActivityMeta } : {}),
    ...(nextProvisioningSnapshot ? { provisioningSnapshotByTeam: nextProvisioningSnapshot } : {}),
    ...(nextCurrentProvisioningRunId
      ? { currentProvisioningRunIdByTeam: nextCurrentProvisioningRunId }
      : {}),
    ...(nextCurrentRuntimeRunId ? { currentRuntimeRunIdByTeam: nextCurrentRuntimeRunId } : {}),
    ...(nextProvisioningStartedAtFloor
      ? { provisioningStartedAtFloorByTeam: nextProvisioningStartedAtFloor }
      : {}),
    ...(nextLeadActivity ? { leadActivityByTeam: nextLeadActivity } : {}),
    ...(nextLeadContext ? { leadContextByTeam: nextLeadContext } : {}),
    ...(nextActiveTaskLogActivity
      ? { activeTaskLogActivityByTeam: nextActiveTaskLogActivity }
      : {}),
    ...(nextActiveTools ? { activeToolsByTeam: nextActiveTools } : {}),
    ...(nextFinishedVisible ? { finishedVisibleByTeam: nextFinishedVisible } : {}),
    ...(nextToolHistory ? { toolHistoryByTeam: nextToolHistory } : {}),
    ...(nextMemberSpawnStatuses ? { memberSpawnStatusesByTeam: nextMemberSpawnStatuses } : {}),
    ...(nextMemberSpawnSnapshots ? { memberSpawnSnapshotsByTeam: nextMemberSpawnSnapshots } : {}),
    ...(nextProvisioningErrors ? { provisioningErrorByTeam: nextProvisioningErrors } : {}),
  };
}

function buildTeamScopedProgressTombstones(
  state: Pick<
    TeamSlice,
    | 'currentProvisioningRunIdByTeam'
    | 'currentRuntimeRunIdByTeam'
    | 'ignoredProvisioningRunIds'
    | 'ignoredRuntimeRunIds'
    | 'provisioningStartedAtFloorByTeam'
  >,
  teamName: string,
  floor: string
): Pick<
  TeamSlice,
  'ignoredProvisioningRunIds' | 'ignoredRuntimeRunIds' | 'provisioningStartedAtFloorByTeam'
> {
  const nextIgnoredProvisioningRunIds = { ...state.ignoredProvisioningRunIds };
  const nextIgnoredRuntimeRunIds = { ...state.ignoredRuntimeRunIds };

  const currentProvisioningRunId = state.currentProvisioningRunIdByTeam[teamName];
  const currentRuntimeRunId = state.currentRuntimeRunIdByTeam[teamName];
  if (currentProvisioningRunId) {
    nextIgnoredProvisioningRunIds[currentProvisioningRunId] = teamName;
  }
  if (currentRuntimeRunId) {
    nextIgnoredRuntimeRunIds[currentRuntimeRunId] = teamName;
  }

  return {
    ignoredProvisioningRunIds: nextIgnoredProvisioningRunIds,
    ignoredRuntimeRunIds: nextIgnoredRuntimeRunIds,
    provisioningStartedAtFloorByTeam: {
      ...state.provisioningStartedAtFloorByTeam,
      [teamName]: floor,
    },
  };
}

function captureTeamLocalStateEpoch(teamName: string): number {
  return teamLocalStateEpochByTeam.get(teamName) ?? 0;
}

function isTeamLocalStateEpochCurrent(teamName: string, epoch: number): boolean {
  return captureTeamLocalStateEpoch(teamName) === epoch;
}

function invalidateTeamLocalStateEpoch(teamName: string): void {
  teamLocalStateEpochByTeam.set(teamName, captureTeamLocalStateEpoch(teamName) + 1);
}

function beginInFlightTeamDataRefresh(teamName: string): symbol {
  const token = Symbol(teamName);
  const existing = inFlightRefreshTeamDataCalls.get(teamName);
  if (existing) {
    existing.add(token);
    return token;
  }
  inFlightRefreshTeamDataCalls.set(teamName, new Set([token]));
  return token;
}

function endInFlightTeamDataRefresh(teamName: string, token: symbol): void {
  const existing = inFlightRefreshTeamDataCalls.get(teamName);
  if (!existing) {
    return;
  }
  existing.delete(token);
  if (existing.size === 0) {
    inFlightRefreshTeamDataCalls.delete(teamName);
  }
}

function cancelPostPaintTeamEnrichments(teamName: string): void {
  const handle = postPaintTeamEnrichmentTimers.get(teamName);
  if (!handle) {
    return;
  }

  handle.cancelled = true;
  if (
    handle.rafId !== undefined &&
    typeof window !== 'undefined' &&
    typeof window.cancelAnimationFrame === 'function'
  ) {
    window.cancelAnimationFrame(handle.rafId);
  }
  if (handle.timerId !== undefined) {
    clearTimeout(handle.timerId);
  }
  if (handle.fallbackTimerId !== undefined) {
    clearTimeout(handle.fallbackTimerId);
  }
  postPaintTeamEnrichmentTimers.delete(teamName);
}

function scheduleAfterPaint(run: () => void): PostPaintHandle {
  const handle: PostPaintHandle = {
    cancelled: false,
    ran: false,
  };

  const runOnce = (): void => {
    if (handle.cancelled || handle.ran) {
      return;
    }
    handle.ran = true;

    if (
      handle.rafId !== undefined &&
      typeof window !== 'undefined' &&
      typeof window.cancelAnimationFrame === 'function'
    ) {
      window.cancelAnimationFrame(handle.rafId);
      handle.rafId = undefined;
    }
    if (handle.timerId !== undefined) {
      clearTimeout(handle.timerId);
      handle.timerId = undefined;
    }
    if (handle.fallbackTimerId !== undefined) {
      clearTimeout(handle.fallbackTimerId);
      handle.fallbackTimerId = undefined;
    }

    run();
  };

  const scheduleTimer = (): void => {
    handle.timerId = setTimeout(runOnce, 0);
  };

  handle.fallbackTimerId = setTimeout(runOnce, POST_PAINT_TEAM_ENRICHMENT_FALLBACK_MS);

  if (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function') {
    handle.rafId = window.requestAnimationFrame(() => {
      handle.rafId = undefined;
      scheduleTimer();
    });
    return handle;
  }

  scheduleTimer();
  return handle;
}

function drainQueuedFullRefreshAfterThinSettles(teamName: string, get: () => TeamSlice): void {
  if (!queuedFullTeamDataRefreshesAfterThin.delete(teamName)) {
    return;
  }
  void get().refreshTeamData(teamName, { withDedup: true });
}

function isSelectedTeamLoadStillCurrent(
  get: () => TeamSlice,
  teamName: string,
  requestNonce: number,
  teamStateEpoch: number
): boolean {
  const state = get();
  return (
    isTeamLocalStateEpochCurrent(teamName, teamStateEpoch) &&
    state.selectedTeamName === teamName &&
    state.selectedTeamLoadNonce === requestNonce &&
    state.selectedTeamData?.teamName === teamName
  );
}

function schedulePostPaintTeamEnrichments(params: {
  teamName: string;
  requestNonce: number;
  teamStateEpoch: number;
  get: () => TeamSlice;
}): void {
  const { teamName, requestNonce, teamStateEpoch, get } = params;

  cancelPostPaintTeamEnrichments(teamName);

  const handle = scheduleAfterPaint(() => {
    if (postPaintTeamEnrichmentTimers.get(teamName) !== handle) {
      return;
    }
    postPaintTeamEnrichmentTimers.delete(teamName);

    void (async () => {
      if (!isTeamLocalStateEpochCurrent(teamName, teamStateEpoch)) {
        queuedFullTeamDataRefreshesAfterThin.delete(teamName);
        return;
      }

      const state = get();
      if (state.selectedTeamName !== teamName) {
        drainQueuedFullRefreshAfterThinSettles(teamName, get);
        return;
      }

      if (state.selectedTeamLoadNonce !== requestNonce) {
        return;
      }

      if (state.selectedTeamData?.teamName !== teamName) {
        queuedFullTeamDataRefreshesAfterThin.delete(teamName);
        return;
      }

      if (queuedFullTeamDataRefreshesAfterThin.delete(teamName)) {
        void get().refreshTeamData(teamName, { withDedup: true });
      }

      try {
        const headResult = await get().refreshTeamMessagesHead(teamName);
        if (!isSelectedTeamLoadStillCurrent(get, teamName, requestNonce, teamStateEpoch)) {
          return;
        }
        if (headResult.feedChanged || isMemberActivityMetaStale(get(), teamName)) {
          await get().refreshMemberActivityMeta(teamName);
        }
      } catch (error) {
        logger.debug(
          `post-paint team enrichments skipped team=${teamName} error=${
            error instanceof Error ? error.message : String(error)
          }`
        );
      }
    })();
  });

  postPaintTeamEnrichmentTimers.set(teamName, handle);
}

export function __getTeamScopedTransientStateForTests(teamName: string): {
  hasResolvedMembersSelector: boolean;
  resolvedMemberSelectorCount: number;
  hasMergedMessagesSelector: boolean;
  memberMessagesSelectorCount: number;
  hasPendingFreshTeamDataRefresh: boolean;
  hasQueuedFullTeamDataRefreshAfterThin: boolean;
  hasPostPaintTeamEnrichmentTimer: boolean;
  hasQueuedHeadRefreshAfterOlder: boolean;
  hasPendingFreshMessagesHeadRefresh: boolean;
  hasPendingFreshMemberActivityMetaRefresh: boolean;
  hasLastResolvedTeamDataRefresh: boolean;
  hasCurrentLocalStateEpoch: boolean;
  hasMemberSpawnStatusesIpcBackoff: boolean;
  hasTeamRefreshBurstDiagnostics: boolean;
  hasMemberSpawnUiEqualLastWarn: boolean;
} {
  const teamScopedPrefix = `${teamName}:`;
  let resolvedMemberSelectorCount = 0;
  let memberMessagesSelectorCount = 0;

  for (const key of resolvedMemberSelectorCache.keys()) {
    if (key.startsWith(teamScopedPrefix)) {
      resolvedMemberSelectorCount += 1;
    }
  }
  for (const key of memberMessagesSelectorCache.keys()) {
    if (key.startsWith(teamScopedPrefix)) {
      memberMessagesSelectorCount += 1;
    }
  }

  return {
    hasResolvedMembersSelector: resolvedMembersSelectorCache.has(teamName),
    resolvedMemberSelectorCount,
    hasMergedMessagesSelector: mergedMessagesSelectorCache.has(teamName),
    memberMessagesSelectorCount,
    hasPendingFreshTeamDataRefresh: pendingFreshTeamDataRefreshes.has(teamName),
    hasQueuedFullTeamDataRefreshAfterThin: queuedFullTeamDataRefreshesAfterThin.has(teamName),
    hasPostPaintTeamEnrichmentTimer: postPaintTeamEnrichmentTimers.has(teamName),
    hasQueuedHeadRefreshAfterOlder: queuedTeamMessagesHeadRefreshesAfterOlder.has(teamName),
    hasPendingFreshMessagesHeadRefresh: pendingFreshTeamMessagesHeadRefreshes.has(teamName),
    hasPendingFreshMemberActivityMetaRefresh:
      pendingFreshTeamMemberActivityMetaRefreshes.has(teamName),
    hasLastResolvedTeamDataRefresh: lastResolvedTeamDataRefreshAtByTeam.has(teamName),
    hasCurrentLocalStateEpoch: teamLocalStateEpochByTeam.has(teamName),
    hasMemberSpawnStatusesIpcBackoff: memberSpawnStatusesIpcBackoffUntilByTeam.has(teamName),
    hasTeamRefreshBurstDiagnostics: teamRefreshBurstDiagnostics.has(teamName),
    hasMemberSpawnUiEqualLastWarn: memberSpawnUiEqualLastWarnAtByTeam.has(teamName),
  };
}

function nowIso(): string {
  return new Date().toISOString();
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function isPlainObject(value: unknown): value is Record<string, unknown> {
  if (value == null || typeof value !== 'object') {
    return false;
  }
  const prototype = Object.getPrototypeOf(value);
  return prototype === Object.prototype || prototype === null;
}

function structurallySharePlainValue<T>(previous: T, next: T): T {
  if (Object.is(previous, next)) {
    return previous;
  }

  if (Array.isArray(previous) && Array.isArray(next)) {
    let changed = previous.length !== next.length;
    const result = next.map((nextItem, index) => {
      const sharedItem = structurallySharePlainValue(previous[index], nextItem);
      if (!Object.is(sharedItem, previous[index])) {
        changed = true;
      }
      return sharedItem;
    });
    return changed ? (result as T) : previous;
  }

  if (isPlainObject(previous) && isPlainObject(next)) {
    const previousRecord = previous as Record<string, unknown>;
    const nextRecord = next as Record<string, unknown>;
    const previousKeys = Object.keys(previousRecord);
    const nextKeys = Object.keys(nextRecord);
    let changed = previousKeys.length !== nextKeys.length;
    const result: Record<string, unknown> = {};

    for (const key of nextKeys) {
      if (!Object.prototype.hasOwnProperty.call(previousRecord, key)) {
        changed = true;
      }
      const sharedValue = structurallySharePlainValue(previousRecord[key], nextRecord[key]);
      if (!Object.is(sharedValue, previousRecord[key])) {
        changed = true;
      }
      result[key] = sharedValue;
    }

    return changed ? (result as T) : previous;
  }

  return next;
}

function structurallyShareTeamSnapshot(
  previous: TeamViewSnapshot | null | undefined,
  next: TeamViewSnapshot
): TeamViewSnapshot {
  if (!previous) {
    return next;
  }
  return structurallySharePlainValue(previous, next);
}

const ACTIVE_PROVISIONING_STATES = new Set([
  'validating',
  'spawning',
  'configuring',
  'assembling',
  'finalizing',
  'verifying',
]);
const TERMINAL_PROVISIONING_STATES = new Set(['ready', 'failed', 'disconnected', 'cancelled']);

function shouldIgnoreProvisioningProgressRegression(
  currentState: TeamProvisioningProgress['state'],
  nextState: TeamProvisioningProgress['state']
): boolean {
  if (currentState === 'ready') {
    return nextState !== 'ready' && nextState !== 'disconnected';
  }
  if (
    currentState === 'failed' ||
    currentState === 'cancelled' ||
    currentState === 'disconnected'
  ) {
    return nextState !== currentState;
  }
  return false;
}

function isPendingProvisioningRunId(runId: string): boolean {
  return runId.startsWith('pending:');
}

function isUnknownProvisioningRunError(error: unknown): boolean {
  const message = error instanceof Error ? error.message : String(error);
  return message.includes('Unknown runId');
}

function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
  let timer: ReturnType<typeof setTimeout> | undefined;
  const timeout = new Promise<T>((_resolve, reject) => {
    timer = setTimeout(() => {
      reject(new Error(`Timeout after ${ms}ms: ${label}`));
    }, ms);
  });
  return Promise.race([promise, timeout]).finally(() => {
    if (timer) clearTimeout(timer);
  });
}

function fetchTeamDataDeduped(
  teamName: string,
  options?: TeamGetDataOptions
): Promise<TeamViewSnapshot> {
  const normalizedOptions = normalizeTeamGetDataOptions(options);
  const key = getTeamDataRequestKey(teamName, normalizedOptions);
  const existing = inFlightTeamDataRequests.get(key);
  if (existing) {
    return existing;
  }

  const request = withTimeout(
    unwrapIpc('team:getData', () =>
      normalizedOptions === undefined
        ? api.teams.getData(teamName)
        : api.teams.getData(teamName, normalizedOptions)
    ),
    TEAM_GET_DATA_TIMEOUT_MS,
    getTeamDataRequestLabel(teamName, normalizedOptions)
  ).finally(() => {
    if (inFlightTeamDataRequests.get(key) === request) {
      inFlightTeamDataRequests.delete(key);
    }
  });

  inFlightTeamDataRequests.set(key, request);
  return request;
}

function fetchTeamDataFresh(
  teamName: string,
  options?: TeamGetDataOptions
): Promise<TeamViewSnapshot> {
  const normalizedOptions = normalizeTeamGetDataOptions(options);
  return withTimeout(
    unwrapIpc('team:getData', () =>
      normalizedOptions === undefined
        ? api.teams.getData(teamName)
        : api.teams.getData(teamName, normalizedOptions)
    ),
    TEAM_GET_DATA_TIMEOUT_MS,
    getTeamDataRequestLabel(teamName, normalizedOptions)
  );
}

function noteTeamRefreshBurst(teamName: string): number {
  const now = Date.now();
  const diagnostic = teamRefreshBurstDiagnostics.get(teamName) ?? {
    windowStartedAt: now,
    count: 0,
    lastWarnAt: 0,
  };

  if (now - diagnostic.windowStartedAt > TEAM_REFRESH_BURST_WINDOW_MS) {
    diagnostic.windowStartedAt = now;
    diagnostic.count = 0;
  }

  diagnostic.count += 1;

  teamRefreshBurstDiagnostics.set(teamName, diagnostic);
  return diagnostic.count;
}

function areLaunchSummaryCountsEqual(
  left: PersistedTeamLaunchSummary | undefined,
  right: PersistedTeamLaunchSummary | undefined
): boolean {
  if (left === right) return true;
  if (!left || !right) return left === right;
  return (
    left.confirmedCount === right.confirmedCount &&
    left.pendingCount === right.pendingCount &&
    left.failedCount === right.failedCount &&
    left.skippedCount === right.skippedCount &&
    left.runtimeAlivePendingCount === right.runtimeAlivePendingCount &&
    left.shellOnlyPendingCount === right.shellOnlyPendingCount &&
    left.runtimeProcessPendingCount === right.runtimeProcessPendingCount &&
    left.runtimeCandidatePendingCount === right.runtimeCandidatePendingCount &&
    left.noRuntimePendingCount === right.noRuntimePendingCount &&
    left.permissionPendingCount === right.permissionPendingCount
  );
}

function areExpectedMembersEqual(
  left: readonly string[] | undefined,
  right: readonly string[] | undefined
): boolean {
  if (left === right) return true;
  if (!left || !right) return left === right;
  if (left.length !== right.length) return false;
  for (let index = 0; index < left.length; index += 1) {
    if (left[index] !== right[index]) {
      return false;
    }
  }
  return true;
}

function areMemberSpawnStatusEntriesEqual(
  left: MemberSpawnStatusEntry | undefined,
  right: MemberSpawnStatusEntry | undefined
): boolean {
  if (left === right) return true;
  if (!left || !right) return left === right;
  const leftPendingPermissionIds = [...(left.pendingPermissionRequestIds ?? [])].sort();
  const rightPendingPermissionIds = [...(right.pendingPermissionRequestIds ?? [])].sort();
  // Renderer equality intentionally ignores raw timing fields that do not change
  // visible member status. This suppresses heartbeat-only churn in TeamDetailView.
  return (
    left.status === right.status &&
    left.launchState === right.launchState &&
    left.error === right.error &&
    left.hardFailureReason === right.hardFailureReason &&
    left.skippedForLaunch === right.skippedForLaunch &&
    left.skipReason === right.skipReason &&
    left.skippedAt === right.skippedAt &&
    left.livenessSource === right.livenessSource &&
    left.runtimeAlive === right.runtimeAlive &&
    left.runtimeModel === right.runtimeModel &&
    left.livenessKind === right.livenessKind &&
    left.runtimeDiagnostic === right.runtimeDiagnostic &&
    left.runtimeDiagnosticSeverity === right.runtimeDiagnosticSeverity &&
    left.bootstrapConfirmed === right.bootstrapConfirmed &&
    left.hardFailure === right.hardFailure &&
    leftPendingPermissionIds.length === rightPendingPermissionIds.length &&
    leftPendingPermissionIds.every((value, index) => value === rightPendingPermissionIds[index])
  );
}

function areMemberSpawnStatusesEqual(
  left: Record<string, MemberSpawnStatusEntry>,
  right: Record<string, MemberSpawnStatusEntry>
): boolean {
  if (left === right) return true;
  const leftKeys = Object.keys(left);
  const rightKeys = Object.keys(right);
  if (leftKeys.length !== rightKeys.length) return false;
  for (const key of leftKeys) {
    if (!(key in right)) {
      return false;
    }
    if (!areMemberSpawnStatusEntriesEqual(left[key], right[key])) {
      return false;
    }
  }
  return true;
}

function areMemberSpawnSnapshotsSemanticallyEqual(
  left: MemberSpawnStatusesSnapshot | undefined,
  right: MemberSpawnStatusesSnapshot
): boolean {
  if (!left) return false;
  return (
    left.runId === right.runId &&
    left.teamLaunchState === right.teamLaunchState &&
    left.launchPhase === right.launchPhase &&
    left.source === right.source &&
    areExpectedMembersEqual(left.expectedMembers, right.expectedMembers) &&
    areLaunchSummaryCountsEqual(left.summary, right.summary) &&
    areMemberSpawnStatusesEqual(left.statuses, right.statuses)
  );
}

function maybeLogMemberSpawnUiEqualSuppressed(
  teamName: string,
  runId: string | null | undefined
): void {
  const now = Date.now();
  const lastWarnAt = memberSpawnUiEqualLastWarnAtByTeam.get(teamName) ?? 0;
  if (now - lastWarnAt < MEMBER_SPAWN_UI_EQUAL_WARN_THROTTLE_MS) {
    return;
  }
  memberSpawnUiEqualLastWarnAtByTeam.set(teamName, now);
  logger.debug(
    `[perf] member-spawn snapshot suppressed team=${teamName} runId=${runId ?? 'none'} reason=member-spawn-ui-equal`
  );
}

function isTeamAgentRuntimeResourceSampleLike(
  value: unknown
): value is TeamAgentRuntimeResourceSample {
  return Boolean(value) && typeof value === 'object';
}

function areTeamAgentRuntimeResourceSamplesEqual(left: unknown, right: unknown): boolean {
  if (left === right) return true;
  if (!isTeamAgentRuntimeResourceSampleLike(left) || !isTeamAgentRuntimeResourceSampleLike(right)) {
    return false;
  }
  return (
    left.timestamp === right.timestamp &&
    left.cpuPercent === right.cpuPercent &&
    left.rssBytes === right.rssBytes &&
    left.primaryCpuPercent === right.primaryCpuPercent &&
    left.primaryRssBytes === right.primaryRssBytes &&
    left.childCpuPercent === right.childCpuPercent &&
    left.childRssBytes === right.childRssBytes &&
    left.processCount === right.processCount &&
    left.runtimeLoadScope === right.runtimeLoadScope &&
    left.runtimeLoadTruncated === right.runtimeLoadTruncated &&
    left.pidSource === right.pidSource &&
    left.pid === right.pid &&
    left.runtimePid === right.runtimePid
  );
}

function areTeamAgentRuntimeEntriesEqual(
  left: TeamAgentRuntimeEntry | undefined,
  right: TeamAgentRuntimeEntry | undefined
): boolean {
  if (left === right) return true;
  if (!left || !right) return left === right;
  const leftDiagnostics = Array.isArray(left.diagnostics) ? left.diagnostics : [];
  const rightDiagnostics = Array.isArray(right.diagnostics) ? right.diagnostics : [];
  const leftResourceHistory = Array.isArray(left.resourceHistory) ? left.resourceHistory : [];
  const rightResourceHistory = Array.isArray(right.resourceHistory) ? right.resourceHistory : [];
  return (
    left.memberName === right.memberName &&
    left.alive === right.alive &&
    left.restartable === right.restartable &&
    left.backendType === right.backendType &&
    left.providerId === right.providerId &&
    left.providerBackendId === right.providerBackendId &&
    left.laneId === right.laneId &&
    left.laneKind === right.laneKind &&
    left.pid === right.pid &&
    left.runtimeModel === right.runtimeModel &&
    left.rssBytes === right.rssBytes &&
    left.cpuPercent === right.cpuPercent &&
    left.primaryCpuPercent === right.primaryCpuPercent &&
    left.primaryRssBytes === right.primaryRssBytes &&
    left.childCpuPercent === right.childCpuPercent &&
    left.childRssBytes === right.childRssBytes &&
    left.processCount === right.processCount &&
    left.runtimeLoadScope === right.runtimeLoadScope &&
    left.runtimeLoadTruncated === right.runtimeLoadTruncated &&
    left.livenessKind === right.livenessKind &&
    left.pidSource === right.pidSource &&
    left.processCommand === right.processCommand &&
    left.paneId === right.paneId &&
    left.panePid === right.panePid &&
    left.paneCurrentCommand === right.paneCurrentCommand &&
    left.runtimePid === right.runtimePid &&
    left.runtimeSessionId === right.runtimeSessionId &&
    left.runtimeDiagnostic === right.runtimeDiagnostic &&
    left.runtimeDiagnosticSeverity === right.runtimeDiagnosticSeverity &&
    left.runtimeLastSeenAt === right.runtimeLastSeenAt &&
    left.historicalBootstrapConfirmed === right.historicalBootstrapConfirmed &&
    leftDiagnostics.length === rightDiagnostics.length &&
    leftDiagnostics.every((value, index) => value === rightDiagnostics[index]) &&
    leftResourceHistory.length === rightResourceHistory.length &&
    leftResourceHistory.every((value, index) =>
      areTeamAgentRuntimeResourceSamplesEqual(value, rightResourceHistory[index])
    )
  );
}

function areTeamAgentRuntimeSnapshotsEqual(
  left: TeamAgentRuntimeSnapshot | undefined,
  right: TeamAgentRuntimeSnapshot
): boolean {
  if (!left) return false;
  if (left.teamName !== right.teamName || left.runId !== right.runId) {
    return false;
  }
  const leftKeys = Object.keys(left.members);
  const rightKeys = Object.keys(right.members);
  if (leftKeys.length !== rightKeys.length) {
    return false;
  }
  for (const key of leftKeys) {
    if (!(key in right.members)) {
      return false;
    }
    if (!areTeamAgentRuntimeEntriesEqual(left.members[key], right.members[key])) {
      return false;
    }
  }
  return true;
}

function compareInboxMessagesByTimestamp(a: InboxMessage, b: InboxMessage): number {
  const aTime = Date.parse(a.timestamp);
  const bTime = Date.parse(b.timestamp);
  const aValid = Number.isFinite(aTime);
  const bValid = Number.isFinite(bTime);
  if (aValid && bValid && aTime !== bTime) {
    return aTime - bTime;
  }
  if (aValid !== bValid) {
    return aValid ? -1 : 1;
  }
  const aId = typeof a.messageId === 'string' ? a.messageId : '';
  const bId = typeof b.messageId === 'string' ? b.messageId : '';
  return aId.localeCompare(bId);
}

export interface TeamMessagesCacheEntry {
  canonicalMessages: InboxMessage[];
  optimisticMessages: InboxMessage[];
  feedRevision: string | null;
  nextCursor: string | null;
  hasMore: boolean;
  lastFetchedAt: number | null;
  loadingHead: boolean;
  loadingOlder: boolean;
  headHydrated: boolean;
}

export interface RefreshTeamMessagesHeadResult {
  feedChanged: boolean;
  headChanged: boolean;
  feedRevision: string | null;
}

const EMPTY_TEAM_MESSAGES_CACHE_ENTRY: TeamMessagesCacheEntry = {
  canonicalMessages: [],
  optimisticMessages: [],
  feedRevision: null,
  nextCursor: null,
  hasMore: false,
  lastFetchedAt: null,
  loadingHead: false,
  loadingOlder: false,
  headHydrated: false,
};

function createEmptyTeamMessagesCacheEntry(): TeamMessagesCacheEntry {
  return {
    canonicalMessages: [],
    optimisticMessages: [],
    feedRevision: null,
    nextCursor: null,
    hasMore: false,
    lastFetchedAt: null,
    loadingHead: false,
    loadingOlder: false,
    headHydrated: false,
  };
}

function getTeamMessagesCacheEntry(
  state: Pick<TeamSlice, 'teamMessagesByName'>,
  teamName: string
): TeamMessagesCacheEntry {
  return state.teamMessagesByName[teamName] ?? EMPTY_TEAM_MESSAGES_CACHE_ENTRY;
}

function upsertOptimisticTeamMessage(
  entry: TeamMessagesCacheEntry,
  message: InboxMessage
): TeamMessagesCacheEntry {
  const nextOptimistic = [...entry.optimisticMessages];
  const messageId = typeof message.messageId === 'string' ? message.messageId.trim() : '';
  if (messageId.length > 0) {
    const existingIndex = nextOptimistic.findIndex(
      (candidate) =>
        typeof candidate.messageId === 'string' && candidate.messageId.trim() === messageId
    );
    if (existingIndex >= 0) {
      nextOptimistic[existingIndex] = {
        ...nextOptimistic[existingIndex],
        ...message,
      };
    } else {
      nextOptimistic.push(message);
    }
  } else {
    nextOptimistic.push(message);
  }
  nextOptimistic.sort(compareInboxMessagesByTimestamp);
  return {
    ...entry,
    optimisticMessages: nextOptimistic,
  };
}

function areInboxMessageArraysEquivalent(
  left: readonly InboxMessage[],
  right: readonly InboxMessage[]
): boolean {
  if (left === right) return true;
  if (left.length !== right.length) return false;
  for (let index = 0; index < left.length; index += 1) {
    const leftItem = left[index];
    const rightItem = right[index];
    if (
      leftItem.messageId !== rightItem.messageId ||
      leftItem.timestamp !== rightItem.timestamp ||
      leftItem.from !== rightItem.from ||
      leftItem.to !== rightItem.to ||
      leftItem.text !== rightItem.text ||
      leftItem.summary !== rightItem.summary ||
      leftItem.read !== rightItem.read ||
      leftItem.actionMode !== rightItem.actionMode ||
      leftItem.commentId !== rightItem.commentId ||
      leftItem.relayOfMessageId !== rightItem.relayOfMessageId ||
      leftItem.source !== rightItem.source ||
      leftItem.leadSessionId !== rightItem.leadSessionId ||
      leftItem.messageKind !== rightItem.messageKind ||
      JSON.stringify(leftItem.taskRefs ?? null) !== JSON.stringify(rightItem.taskRefs ?? null)
    ) {
      return false;
    }
  }
  return true;
}

function pruneOptimisticMessages(
  optimistic: readonly InboxMessage[],
  canonical: readonly InboxMessage[]
): InboxMessage[] {
  if (optimistic.length === 0) {
    return [];
  }

  const canonicalIds = new Set(
    canonical
      .map((message) => (typeof message.messageId === 'string' ? message.messageId.trim() : ''))
      .filter((messageId) => messageId.length > 0)
  );

  return optimistic.filter((message) => {
    const messageId = typeof message.messageId === 'string' ? message.messageId.trim() : '';
    return !messageId || !canonicalIds.has(messageId);
  });
}

function clearPendingReplyRefreshTimer(teamName: string): void {
  const existingTimer = pendingTeamPendingReplyRefreshTimers.get(teamName);
  if (existingTimer == null) {
    return;
  }
  clearTimeout(existingTimer);
  pendingTeamPendingReplyRefreshTimers.delete(teamName);
}

function clearPendingReplyRefreshWaits(teamName: string): void {
  activeTeamPendingReplyWaitSourceIdsByTeam.delete(teamName);
}

function setPendingReplyRefreshEnabled(
  teamName: string,
  sourceId: string,
  enabled: boolean
): boolean {
  if (enabled) {
    const existing = activeTeamPendingReplyWaitSourceIdsByTeam.get(teamName) ?? new Set<string>();
    existing.add(sourceId);
    activeTeamPendingReplyWaitSourceIdsByTeam.set(teamName, existing);
    return true;
  }

  const existing = activeTeamPendingReplyWaitSourceIdsByTeam.get(teamName);
  if (!existing) {
    return false;
  }
  existing.delete(sourceId);
  if (existing.size === 0) {
    activeTeamPendingReplyWaitSourceIdsByTeam.delete(teamName);
    return false;
  }
  return true;
}

function getCanonicalHeadSlice(
  canonicalMessages: readonly InboxMessage[],
  headLength: number
): readonly InboxMessage[] {
  if (headLength <= 0) {
    return [];
  }
  return canonicalMessages.slice(0, headLength);
}

function extractRetainedCanonicalOlderTail(
  canonicalMessages: readonly InboxMessage[],
  freshHeadMessages: readonly InboxMessage[]
): InboxMessage[] | null {
  if (canonicalMessages.length === 0) {
    return [];
  }
  if (freshHeadMessages.length === 0) {
    return null;
  }

  const freshHeadKeys = new Set(freshHeadMessages.map((message) => toMessageKey(message)));
  let hasMessagesOutsideFreshHead = false;
  for (const message of canonicalMessages) {
    if (!freshHeadKeys.has(toMessageKey(message))) {
      hasMessagesOutsideFreshHead = true;
      break;
    }
  }
  if (!hasMessagesOutsideFreshHead) {
    return [];
  }

  const anchorKey = toMessageKey(freshHeadMessages[freshHeadMessages.length - 1]);
  const anchorIndex = canonicalMessages.findIndex((message) => toMessageKey(message) === anchorKey);
  if (anchorIndex < 0) {
    return null;
  }

  return canonicalMessages
    .slice(anchorIndex + 1)
    .filter((message) => !freshHeadKeys.has(toMessageKey(message)));
}

async function refreshTaskChangePresenceForUpdatedTask(
  getState: () => AppState,
  teamName: string,
  taskId: string
): Promise<void> {
  const state = getState();
  if (state.selectedTeamName !== teamName || !state.selectedTeamData) {
    return;
  }

  const task = state.selectedTeamData.tasks.find((candidate) => candidate.id === taskId);
  if (!task) {
    return;
  }

  const options = buildTaskChangeRequestOptions(task);
  if (!canDisplayTaskChangesForOptions(options)) {
    return;
  }

  if (
    typeof state.invalidateTaskChangePresence !== 'function' ||
    typeof state.checkTaskHasChanges !== 'function'
  ) {
    return;
  }

  const cacheKey = buildTaskChangePresenceKey(teamName, taskId, options);
  state.invalidateTaskChangePresence([cacheKey]);

  try {
    await state.checkTaskHasChanges(teamName, taskId, options);
  } catch {
    // Best-effort refresh after explicit task transition.
  }
}

async function pollProvisioningStatus(
  getState: () => TeamSlice,
  runId: string,
  opts?: { maxAttempts?: number; initialDelayMs?: number }
): Promise<void> {
  const maxAttempts = opts?.maxAttempts ?? 12;
  let delayMs = opts?.initialDelayMs ?? 150;
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const state = getState();
    const current = state.provisioningRuns[runId];
    if (current && TERMINAL_PROVISIONING_STATES.has(current.state)) {
      return;
    }
    try {
      const progress = await state.getProvisioningStatus(runId);
      if (TERMINAL_PROVISIONING_STATES.has(progress.state)) {
        return;
      }
    } catch (error) {
      if (isUnknownProvisioningRunError(error)) {
        state.clearMissingProvisioningRun(runId);
        return;
      }
      // best-effort polling; don't fail launch because status fetch is flaky
    }
    await sleep(delayMs);
    delayMs = Math.min(1500, Math.round(delayMs * 1.5));
  }
}

// --- Clarification notification tracking ---
// Native OS notifications for new inbox messages are handled in main process
// (main/index.ts → notifyNewInboxMessages). This renderer-side tracking only
// handles clarification-specific logic (e.g., marking tasks as needing user input).
const notifiedClarificationTaskKeys = new Set<string>();
const notifiedStatusChangeKeys = new Set<string>();
const notifiedCommentKeys = new Set<string>();
const notifiedCreatedTaskKeys = new Set<string>();
const notifiedAllCompletedTeams = new Set<string>();
const notifiedBlockedTaskKeys = new Set<string>();

let isFirstFetchAllTasks = true;

function detectClarificationNotifications(
  oldTasks: GlobalTask[],
  newTasks: GlobalTask[],
  notifyEnabled: boolean
): void {
  for (const task of newTasks) {
    const key = `${task.teamName}:${task.id}`;
    if (task.needsClarification === 'user') {
      const oldTask = oldTasks.find((t) => t.teamName === task.teamName && t.id === task.id);
      if (oldTask?.needsClarification !== 'user' && !notifiedClarificationTaskKeys.has(key)) {
        notifiedClarificationTaskKeys.add(key);
        // Always store in-app; suppress OS toast when per-type toggle is off
        fireClarificationNotification(task, !notifyEnabled);
      }
    } else {
      notifiedClarificationTaskKeys.delete(key);
    }
  }
}

function fireClarificationNotification(task: GlobalTask, suppressToast: boolean): void {
  // Delegate to main process for native OS notification (cross-platform, no permission needed)
  const latestComment = task.comments?.length ? task.comments[task.comments.length - 1] : undefined;
  const rawBody =
    latestComment?.text || task.description || `${formatTaskDisplayLabel(task)}: ${task.subject}`;
  const body = stripAgentBlocks(rawBody).trim();

  void api.teams
    ?.showMessageNotification({
      teamName: task.teamName,
      teamDisplayName: task.teamDisplayName,
      from: latestComment?.author || 'team-lead',
      to: 'user',
      summary: `Clarification needed — Task ${formatTaskDisplayLabel(task)}`,
      body,
      teamEventType: 'task_clarification',
      dedupeKey: `clarification:${task.teamName}:${task.id}:${task.updatedAt ?? Date.now()}`,
      target: {
        kind: 'task',
        teamName: task.teamName,
        taskId: task.id,
        commentId: latestComment?.id,
        focus: 'comments',
      },
      suppressToast,
    })
    .catch(() => undefined);
}

function detectStatusChangeNotifications(
  oldTasks: GlobalTask[],
  newTasks: GlobalTask[],
  config: AppConfig | null,
  teamByName: Record<string, TeamSummary>
): void {
  const statusChangeEnabled =
    !!config?.notifications?.notifyOnStatusChange && !!config.notifications.enabled;
  const statuses = config?.notifications?.statusChangeStatuses ?? ['in_progress', 'completed'];
  if (statuses.length === 0) return;

  const onlySolo = config?.notifications?.statusChangeOnlySolo ?? true;

  for (const task of newTasks) {
    const oldTask = oldTasks.find((t) => t.teamName === task.teamName && t.id === task.id);
    if (!oldTask) continue;

    // Detect kanbanColumn change to 'approved' (status stays 'completed', column changes)
    const taskKanbanColumn = getTeamTaskWorkflowColumn(task);
    const oldTaskKanbanColumn = getTeamTaskWorkflowColumn(oldTask);
    const becameApproved = taskKanbanColumn === 'approved' && oldTaskKanbanColumn !== 'approved';
    const becameReview = taskKanbanColumn === 'review' && oldTaskKanbanColumn !== 'review';
    const becameNeedsFix =
      isTeamTaskNeedsFixActionable(task) && !isTeamTaskNeedsFixActionable(oldTask);

    const statusChanged = oldTask.status !== task.status;
    if (!statusChanged && !becameApproved && !becameReview && !becameNeedsFix) continue;

    if (onlySolo) {
      const team = teamByName[task.teamName];
      if (team && team.memberCount > 0) continue;
    }

    // Resolve the effective status for notification matching
    const effectiveStatus = becameApproved
      ? 'approved'
      : becameReview
        ? 'review'
        : becameNeedsFix
          ? 'needsFix'
          : task.status;
    if (!statuses.includes(effectiveStatus)) continue;

    const key = `${task.teamName}:${task.id}:${effectiveStatus}`;
    if (notifiedStatusChangeKeys.has(key)) continue;
    notifiedStatusChangeKeys.add(key);

    const fromLabel = becameApproved ? 'Completed' : becameReview ? 'Completed' : oldTask.status;
    fireStatusChangeNotification(
      task,
      fromLabel,
      becameApproved
        ? 'approved'
        : becameReview
          ? 'review'
          : becameNeedsFix
            ? 'needsFix'
            : undefined,
      !statusChangeEnabled
    );
  }
}

function fireStatusChangeNotification(
  task: GlobalTask,
  fromStatus: string,
  overrideToStatus?: string,
  suppressToast?: boolean
): void {
  const statusLabels: Record<string, string> = {
    pending: 'Pending',
    in_progress: 'In Progress',
    completed: 'Completed',
    deleted: 'Deleted',
    review: 'Review',
    needsFix: 'Needs Fixes',
    approved: 'Approved',
  };
  const from = statusLabels[fromStatus] ?? fromStatus;
  const toStatus = overrideToStatus ?? task.status;
  const to = statusLabels[toStatus] ?? toStatus;

  void api.teams
    ?.showMessageNotification({
      teamName: task.teamName,
      teamDisplayName: task.teamDisplayName,
      from: task.owner ?? 'system',
      to: 'user',
      summary: `Task ${formatTaskDisplayLabel(task)}: ${from} → ${to}`,
      body: task.subject,
      teamEventType: 'task_status_change',
      dedupeKey: `status:${task.teamName}:${task.id}:${fromStatus}:${toStatus}:${task.updatedAt ?? Date.now()}`,
      target: {
        kind: 'task',
        teamName: task.teamName,
        taskId: task.id,
        focus: 'status',
      },
      suppressToast,
    })
    .catch(() => undefined);
}

function detectTaskCommentNotifications(
  oldTasks: GlobalTask[],
  newTasks: GlobalTask[],
  notifyEnabled: boolean
): void {
  const oldTaskMap = new Map(oldTasks.map((t) => [`${t.teamName}:${t.id}`, t]));

  for (const task of newTasks) {
    const mapKey = `${task.teamName}:${task.id}`;
    const oldTask = oldTaskMap.get(mapKey);
    const oldCommentCount = oldTask?.comments?.length ?? 0;
    const newCommentCount = task.comments?.length ?? 0;

    if (newCommentCount <= oldCommentCount) continue;

    const newComments = (task.comments ?? []).slice(oldCommentCount);
    for (const comment of newComments) {
      // Don't notify about user's own comments
      if (comment.author === 'user') continue;

      const key = `${task.teamName}:${task.id}:${comment.id}`;
      if (notifiedCommentKeys.has(key)) continue;
      notifiedCommentKeys.add(key);

      if (comment.type === 'review_request') {
        fireTaskReviewRequestedNotification(task, comment, !notifyEnabled);
        continue;
      }
      if (comment.type === 'review_approved') continue;

      fireTaskCommentNotification(task, comment, !notifyEnabled);
    }
  }
}

function fireTaskCommentNotification(
  task: GlobalTask,
  comment: Pick<TaskComment, 'author' | 'text' | 'id'>,
  suppressToast: boolean
): void {
  // Double-check: never notify about user's own comments
  if (comment.author === 'user') return;

  const stripped = stripAgentBlocks(comment.text).trim();
  const preview = stripped.length > 100 ? stripped.slice(0, 100) + '...' : stripped;

  void api.teams
    ?.showMessageNotification({
      teamName: task.teamName,
      teamDisplayName: task.teamDisplayName,
      from: comment.author,
      to: 'user',
      summary: `Comment on ${formatTaskDisplayLabel(task)}: ${task.subject}`,
      body: preview,
      teamEventType: 'task_comment',
      dedupeKey: `comment:${task.teamName}:${task.id}:${comment.id}`,
      target: {
        kind: 'task',
        teamName: task.teamName,
        taskId: task.id,
        commentId: comment.id,
        focus: 'comments',
      },
      suppressToast,
    })
    .catch(() => undefined);
}

function fireTaskReviewRequestedNotification(
  task: GlobalTask,
  comment: Pick<TaskComment, 'author' | 'text' | 'id'>,
  suppressToast: boolean
): void {
  const stripped = stripAgentBlocks(comment.text).trim();
  const preview = stripped.length > 100 ? stripped.slice(0, 100) + '...' : stripped;

  void api.teams
    ?.showMessageNotification({
      teamName: task.teamName,
      teamDisplayName: task.teamDisplayName,
      from: comment.author,
      to: 'user',
      summary: `Review requested ${formatTaskDisplayLabel(task)}: ${task.subject}`,
      body: preview || task.subject,
      teamEventType: 'task_review_requested',
      dedupeKey: `review-request:${task.teamName}:${task.id}:${comment.id}`,
      target: {
        kind: 'task',
        teamName: task.teamName,
        taskId: task.id,
        commentId: comment.id,
        focus: 'review',
      },
      suppressToast,
    })
    .catch(() => undefined);
}

function detectBlockedTaskNotifications(
  oldTasks: GlobalTask[],
  newTasks: GlobalTask[],
  notifyEnabled: boolean
): void {
  const oldTaskMap = new Map(oldTasks.map((task) => [`${task.teamName}:${task.id}`, task]));

  for (const task of newTasks) {
    const oldTask = oldTaskMap.get(`${task.teamName}:${task.id}`);
    const oldBlockedBy = new Set(oldTask?.blockedBy?.filter(Boolean) ?? []);
    const newBlockedBy = Array.from(new Set(task.blockedBy?.filter(Boolean) ?? []));
    const taskKeyPrefix = `${task.teamName}:${task.id}:`;
    const key = `${taskKeyPrefix}${[...newBlockedBy].sort().join(',')}`;
    const addedBlockedBy = newBlockedBy.filter((id) => !oldBlockedBy.has(id));

    for (const existingKey of Array.from(notifiedBlockedTaskKeys)) {
      if (existingKey.startsWith(taskKeyPrefix) && existingKey !== key) {
        notifiedBlockedTaskKeys.delete(existingKey);
      }
    }

    if (newBlockedBy.length > 0 && addedBlockedBy.length > 0) {
      if (notifiedBlockedTaskKeys.has(key)) continue;
      notifiedBlockedTaskKeys.add(key);
      fireTaskBlockedNotification(task, newBlockedBy, !notifyEnabled);
    } else if (newBlockedBy.length === 0) {
      for (const existingKey of Array.from(notifiedBlockedTaskKeys)) {
        if (existingKey.startsWith(taskKeyPrefix)) {
          notifiedBlockedTaskKeys.delete(existingKey);
        }
      }
    }
  }
}

function fireTaskBlockedNotification(
  task: GlobalTask,
  blockedBy: readonly string[],
  suppressToast: boolean
): void {
  const blockerRefs = blockedBy.map((id) => formatTaskDisplayLabel({ id })).join(', ');

  void api.teams
    ?.showMessageNotification({
      teamName: task.teamName,
      teamDisplayName: task.teamDisplayName,
      from: task.owner ?? 'system',
      to: 'user',
      summary: `Blocked ${formatTaskDisplayLabel(task)}: ${task.subject}`,
      body: blockerRefs ? `Blocked by ${blockerRefs}` : task.subject,
      teamEventType: 'task_blocked',
      dedupeKey: `blocked:${task.teamName}:${task.id}:${blockedBy.join(',')}`,
      target: {
        kind: 'task',
        teamName: task.teamName,
        taskId: task.id,
        focus: 'detail',
      },
      suppressToast,
    })
    .catch(() => undefined);
}

function detectTaskCreatedNotifications(
  oldTasks: GlobalTask[],
  newTasks: GlobalTask[],
  notifyEnabled: boolean
): void {
  const oldTaskKeys = new Set(oldTasks.map((t) => `${t.teamName}:${t.id}`));

  for (const task of newTasks) {
    const key = `${task.teamName}:${task.id}`;
    if (oldTaskKeys.has(key)) continue;
    if (notifiedCreatedTaskKeys.has(key)) continue;
    notifiedCreatedTaskKeys.add(key);

    fireTaskCreatedNotification(task, !notifyEnabled);
  }
}

function fireTaskCreatedNotification(task: GlobalTask, suppressToast: boolean): void {
  void api.teams
    ?.showMessageNotification({
      teamName: task.teamName,
      teamDisplayName: task.teamDisplayName,
      from: task.owner ?? 'system',
      to: 'user',
      summary: `New task ${formatTaskDisplayLabel(task)}: ${task.subject}`,
      body: stripAgentBlocks(task.description || task.subject).trim(),
      teamEventType: 'task_created',
      dedupeKey: `created:${task.teamName}:${task.id}`,
      target: {
        kind: 'task',
        teamName: task.teamName,
        taskId: task.id,
        focus: 'detail',
      },
      suppressToast,
    })
    .catch(() => undefined);
}

function detectAllTasksCompletedNotification(
  oldTasks: GlobalTask[],
  newTasks: GlobalTask[],
  notifyEnabled: boolean
): void {
  // Group tasks by team
  const teamTasks = new Map<string, GlobalTask[]>();
  for (const task of newTasks) {
    const list = teamTasks.get(task.teamName) ?? [];
    list.push(task);
    teamTasks.set(task.teamName, list);
  }

  for (const [teamName, tasks] of teamTasks) {
    if (tasks.length === 0) continue;
    const allCompleted = tasks.every(isTeamTaskFinalForCompletionNotification);
    if (!allCompleted) {
      // Reset so we can notify again if tasks become all-completed later
      notifiedAllCompletedTeams.delete(teamName);
      continue;
    }
    if (notifiedAllCompletedTeams.has(teamName)) continue;

    // Check that at least one task was NOT completed before (real transition)
    const oldTeamTasks = oldTasks.filter((t) => t.teamName === teamName);
    const wasAlreadyAllCompleted =
      oldTeamTasks.length > 0 && oldTeamTasks.every(isTeamTaskFinalForCompletionNotification);
    if (wasAlreadyAllCompleted) {
      notifiedAllCompletedTeams.add(teamName);
      continue;
    }

    notifiedAllCompletedTeams.add(teamName);
    fireAllTasksCompletedNotification(tasks[0], tasks.length, !notifyEnabled);
  }
}

function fireAllTasksCompletedNotification(
  sampleTask: GlobalTask,
  taskCount: number,
  suppressToast: boolean
): void {
  void api.teams
    ?.showMessageNotification({
      teamName: sampleTask.teamName,
      teamDisplayName: sampleTask.teamDisplayName,
      from: 'system',
      to: 'user',
      summary: `All ${taskCount} tasks completed`,
      body: `All tasks in team "${sampleTask.teamDisplayName}" are done`,
      teamEventType: 'all_tasks_completed',
      dedupeKey: `all-done:${sampleTask.teamName}:${Date.now()}`,
      target: {
        kind: 'team',
        teamName: sampleTask.teamName,
        section: 'tasks',
      },
      suppressToast,
    })
    .catch(() => undefined);
}

function collectTaskChangeInvalidationState(
  teamName: string,
  prevTasks: TeamViewSnapshot['tasks'],
  nextTasks: TeamViewSnapshot['tasks']
): { cacheKeys: string[]; taskIds: string[] } {
  const nextKeys = new Set(
    nextTasks.map((task) =>
      buildTaskChangePresenceKey(teamName, task.id, buildTaskChangeRequestOptions(task))
    )
  );
  const invalidationKeys: string[] = [];
  const invalidationTaskIds = new Set<string>();
  for (const task of prevTasks) {
    const previousKey = buildTaskChangePresenceKey(
      teamName,
      task.id,
      buildTaskChangeRequestOptions(task)
    );
    if (!nextKeys.has(previousKey)) {
      invalidationKeys.push(previousKey);
      invalidationTaskIds.add(task.id);
    }
  }
  return {
    cacheKeys: invalidationKeys,
    taskIds: [...invalidationTaskIds],
  };
}

function preserveKnownTaskChangePresence(
  teamName: string,
  prevTasks: TeamViewSnapshot['tasks'] | null | undefined,
  nextTasks: TeamViewSnapshot['tasks']
): TeamViewSnapshot['tasks'] {
  if (!Array.isArray(prevTasks) || prevTasks.length === 0 || nextTasks.length === 0) {
    return nextTasks;
  }

  const prevTaskById = new Map(prevTasks.map((task) => [task.id, task]));
  let changed = false;

  const mergedTasks = nextTasks.map((task) => {
    if (task.changePresence && task.changePresence !== 'unknown') {
      return task;
    }

    const previousTask = prevTaskById.get(task.id);
    if (!previousTask?.changePresence || previousTask.changePresence === 'unknown') {
      return task;
    }

    const previousKey = buildTaskChangePresenceKey(
      teamName,
      previousTask.id,
      buildTaskChangeRequestOptions(previousTask)
    );
    const nextKey = buildTaskChangePresenceKey(
      teamName,
      task.id,
      buildTaskChangeRequestOptions(task)
    );
    if (previousKey !== nextKey) {
      return task;
    }

    changed = true;
    return {
      ...task,
      changePresence: previousTask.changePresence,
    };
  });

  return changed ? mergedTasks : nextTasks;
}

function mapSendMessageError(error: unknown): string {
  const message =
    error instanceof IpcError ? error.message : error instanceof Error ? error.message : '';
  if (message.includes('Failed to verify inbox write')) {
    return 'Message was written but not verified (race). Please try again.';
  }
  return message || 'Failed to send message';
}

function mapReviewError(error: unknown): string {
  const message =
    error instanceof IpcError ? error.message : error instanceof Error ? error.message : '';
  if (message.includes('Task status update verification failed')) {
    return 'Failed to update task status (possible agent conflict).';
  }
  return message || 'Failed to perform review action';
}

export interface GlobalTaskDetailState {
  teamName: string;
  taskId: string;
  commentId?: string;
}

export interface PendingMemberProfileState {
  teamName?: string;
  memberName: string;
  focus?: 'profile' | 'messages' | 'logs';
}

type TeamSectionTarget = NonNullable<Extract<NotificationTarget, { kind: 'team' }>['section']>;

export interface PendingTeamSectionFocusState {
  teamName: string;
  section: TeamSectionTarget;
}

/** Per-team launch parameters shown in the header badge. */
export interface TeamLaunchParams {
  providerId?: TeamProviderId;
  providerBackendId?: string;
  model?: string; // 'opus' | 'sonnet' | 'haiku'
  effort?: EffortLevel;
  fastMode?: 'inherit' | 'on' | 'off';
  limitContext?: boolean;
}

const resolvedMembersSelectorCache = new Map<
  string,
  {
    snapshotRef: TeamViewSnapshot['members'];
    configMembersRef: TeamViewSnapshot['config']['members'] | undefined;
    summaryRef: TeamSummary | undefined;
    tasksRef: TeamViewSnapshot['tasks'] | undefined;
    metaMembersRef: TeamMemberActivityMeta['members'] | undefined;
    result: ResolvedTeamMember[];
  }
>();
const resolvedMemberSelectorCache = new Map<
  string,
  {
    snapshotMemberRef: TeamMemberSnapshot | undefined;
    metaEntryRef: MemberActivityMetaEntry | undefined;
    result: ResolvedTeamMember | null;
  }
>();
const mergedMessagesSelectorCache = new Map<
  string,
  {
    canonicalRef: InboxMessage[];
    optimisticRef: InboxMessage[];
    result: InboxMessage[];
  }
>();
const EMPTY_TEAM_MEMBER_SNAPSHOTS: TeamMemberSnapshot[] = [];
const EMPTY_TEAM_TASKS: TeamViewSnapshot['tasks'] = [];
const memberMessagesSelectorCache = new Map<
  string,
  {
    messagesRef: InboxMessage[];
    result: InboxMessage[];
  }
>();

function resolveMemberStatus(
  snapshot: TeamMemberSnapshot,
  activity: MemberActivityMetaEntry | undefined
): ResolvedTeamMember['status'] {
  if (activity?.latestAuthoredMessageSignalsTermination) {
    return 'terminated';
  }

  if (!activity?.lastAuthoredMessageAt) {
    return snapshot.currentTaskId ? 'active' : 'idle';
  }

  const ageMs = Date.now() - Date.parse(activity.lastAuthoredMessageAt);
  if (Number.isNaN(ageMs)) {
    return 'unknown';
  }
  if (ageMs < 5 * 60 * 1000) {
    return 'active';
  }
  return 'idle';
}

function buildResolvedMembers(
  snapshots: readonly TeamMemberSnapshot[],
  meta: TeamMemberActivityMeta | undefined
): ResolvedTeamMember[] {
  return snapshots.map((member) => buildResolvedMember(member, meta?.members[member.name]));
}

function isDisplayableFallbackCurrentTask(task: TeamViewSnapshot['tasks'][number]): boolean {
  return (
    task.status === 'in_progress' &&
    getTeamTaskWorkflowColumn(task) !== 'review' &&
    !isTeamTaskFinalForCompletionNotification(task)
  );
}

function buildConfigFallbackMemberSnapshots(snapshot: TeamViewSnapshot): TeamMemberSnapshot[] {
  const configMembers = snapshot.config.members ?? [];
  const hasConfiguredTeammate = configMembers.some((member) => {
    const name = member.name?.trim();
    return Boolean(name) && !member.removedAt && !isLeadMember(member);
  });
  if (!hasConfiguredTeammate) {
    return [];
  }

  const seenNames = new Set<string>();
  const fallbackMembers: TeamMemberSnapshot[] = [];
  for (const member of configMembers) {
    const name = member.name?.trim();
    if (!name) continue;
    const key = name.toLowerCase();
    if (seenNames.has(key)) continue;
    seenNames.add(key);

    const ownedTasks = snapshot.tasks.filter((task) => task.owner === name);
    const currentTask = ownedTasks.find(isDisplayableFallbackCurrentTask);
    fallbackMembers.push({
      name,
      agentId: member.agentId,
      currentTaskId: currentTask?.id ?? null,
      taskCount: ownedTasks.length,
      color: member.color ?? getMemberColorByName(name),
      agentType: member.agentType,
      role: member.role,
      workflow: member.workflow,
      isolation: member.isolation,
      providerId: member.providerId,
      providerBackendId: member.providerBackendId,
      model: member.model,
      effort: member.effort,
      selectedFastMode: member.fastMode,
      cwd: member.cwd,
      removedAt: member.removedAt,
    });
  }

  return fallbackMembers;
}

function getActiveRawTeammateNameKeys(snapshot: TeamViewSnapshot | null | undefined): string[] {
  if (!snapshot) {
    return [];
  }
  const names = new Set<string>();
  for (const member of snapshot.members) {
    const name = member.name.trim();
    const key = name.toLowerCase();
    if (!name || key === 'user' || member.removedAt || isLeadMember(member)) {
      continue;
    }
    names.add(key);
  }
  return Array.from(names).sort((left, right) => left.localeCompare(right));
}

function hasActiveRawTeammateRoster(snapshot: TeamViewSnapshot | null | undefined): boolean {
  return getActiveRawTeammateNameKeys(snapshot).length > 0;
}

function hasRemovedRawMemberRoster(snapshot: TeamViewSnapshot | null | undefined): boolean {
  return Boolean(snapshot?.members.some((member) => member.removedAt));
}

function hasConfigTeammateRoster(snapshot: TeamViewSnapshot | null | undefined): boolean {
  return Boolean(
    snapshot?.config.members?.some((member) => {
      const name = member.name?.trim();
      return Boolean(name) && !member.removedAt && !isLeadMember(member);
    })
  );
}

interface SummaryFallbackMemberSource {
  name: string;
  agentId?: string;
  role?: string;
  color?: string;
}

function normalizeSummaryTeammateName(
  name: string | undefined | null,
  leadName?: string
): string | null {
  const trimmed = name?.trim();
  const normalizedName = trimmed?.toLowerCase();
  const normalizedLeadName = leadName?.trim().toLowerCase();
  if (
    !trimmed ||
    normalizedName === 'user' ||
    isLeadMember({ name: trimmed }) ||
    (normalizedLeadName && normalizedName === normalizedLeadName)
  ) {
    return null;
  }
  return trimmed;
}

function getSummaryRosterTeammateSources(summary: TeamSummary): SummaryFallbackMemberSource[] {
  const seenNames = new Set<string>();
  const sources: SummaryFallbackMemberSource[] = [];
  for (const member of summary.members ?? []) {
    const name = normalizeSummaryTeammateName(member.name, summary.leadName);
    if (!name) {
      continue;
    }
    const key = name.toLowerCase();
    if (seenNames.has(key)) {
      continue;
    }
    seenNames.add(key);
    sources.push({
      name,
      agentId: member.agentId,
      role: member.role,
      color: member.color,
    });
  }
  return sources;
}

function shouldUseSummaryLaunchTeammateSources(summary: TeamSummary): boolean {
  return (
    summary.partialLaunchFailure === true ||
    summary.teamLaunchState === 'partial_failure' ||
    summary.teamLaunchState === 'partial_pending' ||
    summary.teamLaunchState === 'partial_skipped'
  );
}

function getSummaryLaunchTeammateSources(summary: TeamSummary): SummaryFallbackMemberSource[] {
  if (!shouldUseSummaryLaunchTeammateSources(summary)) {
    return [];
  }

  const seenNames = new Set<string>();
  const sources: SummaryFallbackMemberSource[] = [];
  for (const rawName of [...(summary.missingMembers ?? []), ...(summary.skippedMembers ?? [])]) {
    const name = normalizeSummaryTeammateName(rawName, summary.leadName);
    if (!name) {
      continue;
    }
    const key = name.toLowerCase();
    if (seenNames.has(key)) {
      continue;
    }
    seenNames.add(key);
    sources.push({ name });
  }
  return sources;
}

function getSummaryLaunchTeammateNameKeys(summary: TeamSummary): string[] {
  return getSummaryLaunchTeammateSources(summary)
    .map((member) => member.name.toLowerCase())
    .sort((left, right) => left.localeCompare(right));
}

function getSummaryTeammateNameKeys(summary: TeamSummary): string[] {
  const rosterNames = getSummaryRosterTeammateSources(summary)
    .map((member) => member.name.toLowerCase())
    .sort((left, right) => left.localeCompare(right));
  if (rosterNames.length > 0) {
    return rosterNames;
  }

  const launchNames = getSummaryLaunchTeammateNameKeys(summary);
  const expectedCount = summary.expectedMemberCount ?? summary.memberCount;
  if (expectedCount > 0 && launchNames.length === expectedCount) {
    return launchNames;
  }
  return [];
}

function getSummaryFallbackTeammateSources(summary: TeamSummary): SummaryFallbackMemberSource[] {
  return getSummaryRosterTeammateSources(summary);
}

function areNameKeyListsEqual(left: readonly string[], right: readonly string[]): boolean {
  return left.length === right.length && left.every((name, index) => name === right[index]);
}

function summaryConfirmsActiveTeammateRoster(
  current: TeamViewSnapshot,
  summary: TeamSummary
): boolean {
  if ((summary.expectedMemberCount ?? summary.memberCount) <= 0) {
    return false;
  }

  const currentNames = getActiveRawTeammateNameKeys(current);
  const summaryNames = getSummaryTeammateNameKeys(summary);
  if (summaryNames.length === 0 || summaryNames.length !== currentNames.length) {
    return false;
  }

  return areNameKeyListsEqual(summaryNames, currentNames);
}

function buildSummaryFallbackMemberSnapshots(
  snapshot: TeamViewSnapshot,
  summary: TeamSummary | undefined
): TeamMemberSnapshot[] {
  if (!summary) {
    return [];
  }
  const summaryMembers = getSummaryFallbackTeammateSources(summary);
  if (summaryMembers.length === 0) {
    return [];
  }

  const seenNames = new Set<string>();
  const buildSnapshot = (
    name: string,
    source?: { agentId?: string; role?: string; color?: string },
    lead = false
  ): TeamMemberSnapshot | null => {
    const trimmed = name.trim();
    if (!trimmed) return null;
    const key = trimmed.toLowerCase();
    if (seenNames.has(key)) return null;
    seenNames.add(key);

    const ownedTasks = snapshot.tasks.filter((task) => task.owner === trimmed);
    const currentTask = ownedTasks.find(isDisplayableFallbackCurrentTask);
    return {
      name: trimmed,
      agentId: source?.agentId,
      currentTaskId: currentTask?.id ?? null,
      taskCount: ownedTasks.length,
      color: source?.color ?? getMemberColorByName(trimmed),
      agentType: lead ? 'team-lead' : undefined,
      role: source?.role ?? (lead ? 'Team Lead' : undefined),
    };
  };

  const teammates = summaryMembers.flatMap((member) => {
    const item = buildSnapshot(member.name, member);
    return item ? [item] : [];
  });
  if (teammates.length === 0) {
    return [];
  }

  const existingLead = snapshot.members.find((member) => !member.removedAt && isLeadMember(member));
  if (existingLead) {
    return [existingLead, ...teammates];
  }

  const configuredLead = snapshot.config.members?.find(
    (member) => !member.removedAt && isLeadMember(member)
  );
  const leadName = configuredLead?.name?.trim() || summary.leadName?.trim();
  const lead = leadName
    ? buildSnapshot(
        leadName,
        {
          agentId: configuredLead?.agentId,
          role: configuredLead?.role,
          color: configuredLead?.color ?? summary.leadColor,
        },
        true
      )
    : null;

  return lead ? [lead, ...teammates] : teammates;
}

function getResolvableMemberSnapshots(
  snapshot: TeamViewSnapshot,
  summary?: TeamSummary
): readonly TeamMemberSnapshot[] {
  if (
    snapshot.members.length > 0 &&
    (hasActiveRawTeammateRoster(snapshot) || hasRemovedRawMemberRoster(snapshot))
  ) {
    return snapshot.members;
  }

  const configFallbackMembers = buildConfigFallbackMemberSnapshots(snapshot);
  if (configFallbackMembers.length > 0) {
    return configFallbackMembers;
  }

  const summaryFallbackMembers = buildSummaryFallbackMemberSnapshots(snapshot, summary);
  if (summaryFallbackMembers.length > 0) {
    return summaryFallbackMembers;
  }

  return snapshot.members;
}

function shouldPreserveSelectedTeamSnapshot(
  current: TeamViewSnapshot | null,
  baseline: TeamViewSnapshot | null | undefined,
  incoming: TeamViewSnapshot,
  summary: TeamSummary | undefined
): boolean {
  if (!current || !hasActiveRawTeammateRoster(current)) {
    return false;
  }
  if (
    hasActiveRawTeammateRoster(incoming) ||
    hasRemovedRawMemberRoster(incoming) ||
    hasConfigTeammateRoster(incoming)
  ) {
    return false;
  }
  const currentNames = getActiveRawTeammateNameKeys(current);
  if (
    current !== baseline &&
    !areNameKeyListsEqual(currentNames, getActiveRawTeammateNameKeys(baseline))
  ) {
    return true;
  }
  if (summary) {
    return summaryConfirmsActiveTeammateRoster(current, summary);
  }

  return false;
}

function buildResolvedMember(
  snapshot: TeamMemberSnapshot,
  activity: MemberActivityMetaEntry | undefined
): ResolvedTeamMember {
  return {
    ...snapshot,
    status: resolveMemberStatus(snapshot, activity),
    messageCount: activity?.messageCountExact ?? 0,
    lastActiveAt: activity?.lastAuthoredMessageAt ?? null,
  };
}

function areMemberActivityMetaEntriesEqual(
  left: MemberActivityMetaEntry | undefined,
  right: MemberActivityMetaEntry
): boolean {
  if (!left) {
    return false;
  }
  return (
    left.memberName === right.memberName &&
    left.lastAuthoredMessageAt === right.lastAuthoredMessageAt &&
    left.messageCountExact === right.messageCountExact &&
    left.latestAuthoredMessageSignalsTermination === right.latestAuthoredMessageSignalsTermination
  );
}

function structurallyShareMemberActivityFacts(
  previous: Record<string, MemberActivityMetaEntry> | undefined,
  next: Record<string, MemberActivityMetaEntry>
): Record<string, MemberActivityMetaEntry> {
  if (!previous) {
    return next;
  }

  const nextKeys = Object.keys(next);
  const previousKeys = Object.keys(previous);
  let changed = nextKeys.length !== previousKeys.length;
  const shared: Record<string, MemberActivityMetaEntry> = {};

  for (const key of nextKeys) {
    const nextEntry = next[key];
    const previousEntry = previous[key];
    if (!areMemberActivityMetaEntriesEqual(previousEntry, nextEntry)) {
      changed = true;
      shared[key] = nextEntry;
      continue;
    }
    shared[key] = previousEntry;
  }

  return changed ? shared : previous;
}

type TeamDataSelectorState = Pick<
  TeamSlice,
  'teamDataCacheByName' | 'selectedTeamName' | 'selectedTeamData'
>;

export function selectTeamDataForName(
  state: TeamDataSelectorState,
  teamName: string | null | undefined
): TeamViewSnapshot | null {
  if (!teamName) {
    return null;
  }
  if (state.selectedTeamName === teamName && state.selectedTeamData) {
    return state.selectedTeamData;
  }
  return (
    state.teamDataCacheByName[teamName] ??
    (state.selectedTeamName === teamName ? state.selectedTeamData : null)
  );
}

type ResolvedMemberSelectorState = Pick<
  TeamSlice,
  'teamDataCacheByName' | 'selectedTeamName' | 'selectedTeamData' | 'memberActivityMetaByTeam'
> &
  Partial<Pick<TeamSlice, 'teamByName'>>;

function migrateStableSlotAssignmentsForMembers(
  assignments: TeamGraphSlotAssignments | undefined,
  members: readonly TeamGraphMemberSeedInput[]
): { assignments: TeamGraphSlotAssignments; changed: boolean } {
  const nextAssignments: TeamGraphSlotAssignments = { ...(assignments ?? {}) };
  let changed = false;

  for (const member of members) {
    const fallbackKey = member.name.trim();
    const stableOwnerId = getStableTeamOwnerId(member);
    const fallbackAssignment = nextAssignments[fallbackKey];
    const stableAssignment = nextAssignments[stableOwnerId];

    if (stableOwnerId !== fallbackKey && fallbackAssignment && !stableAssignment) {
      nextAssignments[stableOwnerId] = fallbackAssignment;
      delete nextAssignments[fallbackKey];
      changed = true;
      continue;
    }

    if (stableOwnerId !== fallbackKey && fallbackAssignment && stableAssignment) {
      delete nextAssignments[fallbackKey];
      changed = true;
    }
  }

  return { assignments: nextAssignments, changed };
}

export function selectResolvedMembersForTeamName(
  state: ResolvedMemberSelectorState,
  teamName: string | null | undefined
): ResolvedTeamMember[] {
  const snapshot = selectTeamDataForName(state, teamName);
  if (!snapshot || !teamName) {
    return [];
  }

  const meta = state.memberActivityMetaByTeam[teamName];
  const metaMembers = meta?.members;
  const shouldUseMemberFallback =
    snapshot.members.length === 0 ||
    (!hasActiveRawTeammateRoster(snapshot) && !hasRemovedRawMemberRoster(snapshot));
  const configMembersRef = shouldUseMemberFallback ? snapshot.config.members : undefined;
  const summaryRef = shouldUseMemberFallback ? state.teamByName?.[teamName] : undefined;
  const tasksRef = shouldUseMemberFallback ? snapshot.tasks : undefined;
  const cached = resolvedMembersSelectorCache.get(teamName);
  if (
    cached?.snapshotRef === snapshot.members &&
    cached.configMembersRef === configMembersRef &&
    cached.summaryRef === summaryRef &&
    cached.tasksRef === tasksRef &&
    cached.metaMembersRef === metaMembers
  ) {
    return cached.result;
  }

  const result = buildResolvedMembers(getResolvableMemberSnapshots(snapshot, summaryRef), meta);
  resolvedMembersSelectorCache.set(teamName, {
    snapshotRef: snapshot.members,
    configMembersRef,
    summaryRef,
    tasksRef,
    metaMembersRef: metaMembers,
    result,
  });
  return result;
}

export function selectResolvedMemberForTeamName(
  state: ResolvedMemberSelectorState,
  teamName: string | null | undefined,
  memberName: string | null | undefined
): ResolvedTeamMember | null {
  const snapshot = selectTeamDataForName(state, teamName);
  if (!snapshot || !teamName || !memberName) {
    return null;
  }

  const snapshotMember = getResolvableMemberSnapshots(snapshot, state.teamByName?.[teamName]).find(
    (member) => member.name === memberName
  );
  if (!snapshotMember) {
    return null;
  }

  const metaEntry = state.memberActivityMetaByTeam[teamName]?.members[memberName];
  const cacheKey = `${teamName}:${memberName}`;
  const cached = resolvedMemberSelectorCache.get(cacheKey);
  if (cached?.snapshotMemberRef === snapshotMember && cached.metaEntryRef === metaEntry) {
    return cached.result;
  }

  const result = buildResolvedMember(snapshotMember, metaEntry);
  resolvedMemberSelectorCache.set(cacheKey, {
    snapshotMemberRef: snapshotMember,
    metaEntryRef: metaEntry,
    result,
  });
  return result;
}

export function selectTeamMemberSnapshotsForName(
  state: TeamDataSelectorState,
  teamName: string | null | undefined
): TeamViewSnapshot['members'] {
  return selectTeamDataForName(state, teamName)?.members ?? EMPTY_TEAM_MEMBER_SNAPSHOTS;
}

export function selectTeamTasksForName(
  state: TeamDataSelectorState,
  teamName: string | null | undefined
): TeamViewSnapshot['tasks'] {
  return selectTeamDataForName(state, teamName)?.tasks ?? EMPTY_TEAM_TASKS;
}

export function selectTeamIsAliveForName(
  state: TeamDataSelectorState,
  teamName: string | null | undefined
): boolean | undefined {
  return selectTeamDataForName(state, teamName)?.isAlive;
}

export function selectTeamMessages(
  state: Pick<TeamSlice, 'teamMessagesByName'>,
  teamName: string | null | undefined
): InboxMessage[] {
  if (!teamName) {
    return [];
  }

  const entry = getTeamMessagesCacheEntry(state, teamName);
  const cached = mergedMessagesSelectorCache.get(teamName);
  if (
    cached?.canonicalRef === entry.canonicalMessages &&
    cached.optimisticRef === entry.optimisticMessages
  ) {
    return cached.result;
  }

  const result = mergeTeamMessages(entry.canonicalMessages, entry.optimisticMessages);
  mergedMessagesSelectorCache.set(teamName, {
    canonicalRef: entry.canonicalMessages,
    optimisticRef: entry.optimisticMessages,
    result,
  });
  return result;
}

export function selectMemberMessagesForTeamMember(
  state: Pick<TeamSlice, 'teamMessagesByName'>,
  teamName: string | null | undefined,
  memberName: string | null | undefined
): InboxMessage[] {
  if (!teamName || !memberName) {
    return [];
  }

  const messages = selectTeamMessages(state, teamName);
  const cacheKey = `${teamName}:${memberName}`;
  const cached = memberMessagesSelectorCache.get(cacheKey);
  if (cached?.messagesRef === messages) {
    return cached.result;
  }

  const result = messages.filter(
    (message) => message.from === memberName || message.to === memberName
  );
  memberMessagesSelectorCache.set(cacheKey, {
    messagesRef: messages,
    result,
  });
  return result;
}

function isMemberActivityMetaStale(
  state: Pick<TeamSlice, 'memberActivityMetaByTeam' | 'teamMessagesByName'>,
  teamName: string
): boolean {
  const meta = state.memberActivityMetaByTeam[teamName];
  const feedRevision = getTeamMessagesCacheEntry(state, teamName).feedRevision;
  if (!meta) {
    return true;
  }
  if (!feedRevision) {
    return false;
  }
  return meta.feedRevision !== feedRevision;
}

function seedStableSlotAssignmentsForMembers(
  assignments: TeamGraphSlotAssignments,
  members: readonly TeamGraphMemberSeedInput[],
  configMembers: readonly TeamGraphConfigMemberSeedInput[] = []
): { assignments: TeamGraphSlotAssignments; changed: boolean } {
  const defaultSeed = buildTeamGraphDefaultLayoutSeed(members, configMembers);
  if (
    defaultSeed.orderedVisibleOwnerIds.length === 0 ||
    Object.keys(defaultSeed.assignments).length === 0
  ) {
    return { assignments, changed: false };
  }

  const visibleStableOwnerIds = defaultSeed.orderedVisibleOwnerIds;
  const hasAnyVisibleAssignments = visibleStableOwnerIds.some(
    (stableOwnerId) => assignments[stableOwnerId] != null
  );
  if (hasAnyVisibleAssignments) {
    return { assignments, changed: false };
  }

  const nextAssignments: TeamGraphSlotAssignments = { ...assignments };
  visibleStableOwnerIds.forEach((stableOwnerId) => {
    nextAssignments[stableOwnerId] = defaultSeed.assignments[stableOwnerId]!;
  });

  return { assignments: nextAssignments, changed: true };
}

function areTeamGraphSlotAssignmentsEqual(
  left: TeamGraphSlotAssignments | undefined,
  right: TeamGraphSlotAssignments | undefined
): boolean {
  const leftEntries = Object.entries(left ?? {});
  const rightEntries = Object.entries(right ?? {});
  if (leftEntries.length !== rightEntries.length) {
    return false;
  }

  for (const [stableOwnerId, leftAssignment] of leftEntries) {
    const rightAssignment = right?.[stableOwnerId];
    if (
      rightAssignment?.ringIndex !== leftAssignment.ringIndex ||
      rightAssignment.sectorIndex !== leftAssignment.sectorIndex
    ) {
      return false;
    }
  }

  return true;
}

function normalizeTeamGraphSlotAssignmentsForVisibleOwners(
  assignments: TeamGraphSlotAssignments | undefined,
  visibleOwnerIds: readonly string[]
): TeamGraphSlotAssignments {
  if (visibleOwnerIds.length === 0 || !assignments) {
    return {};
  }

  const normalizedAssignments: TeamGraphSlotAssignments = {};
  for (const stableOwnerId of visibleOwnerIds) {
    const assignment = assignments[stableOwnerId];
    if (!assignment) {
      continue;
    }
    normalizedAssignments[stableOwnerId] = assignment;
  }
  return normalizeLegacySixRowOrbitAssignments(normalizedAssignments, visibleOwnerIds);
}

function normalizeLegacySixRowOrbitAssignments(
  assignments: TeamGraphSlotAssignments,
  visibleOwnerIds: readonly string[]
): TeamGraphSlotAssignments {
  if (visibleOwnerIds.length !== 6) {
    return assignments;
  }

  const visibleAssignments = visibleOwnerIds.flatMap((stableOwnerId) => {
    const assignment = assignments[stableOwnerId];
    return assignment ? [assignment] : [];
  });
  const hasLegacyTwoRowBottomMarker = visibleAssignments.some(
    (assignment) => assignment.ringIndex === 1 && assignment.sectorIndex === 2
  );
  let changed = false;
  const normalizedAssignments: TeamGraphSlotAssignments = { ...assignments };

  for (const stableOwnerId of visibleOwnerIds) {
    const assignment = normalizedAssignments[stableOwnerId];
    if (!assignment) {
      continue;
    }

    if (
      hasLegacyTwoRowBottomMarker &&
      assignment.ringIndex === 1 &&
      assignment.sectorIndex >= 0 &&
      assignment.sectorIndex < 3
    ) {
      normalizedAssignments[stableOwnerId] = {
        ringIndex: 2,
        sectorIndex: assignment.sectorIndex,
      };
      changed = true;
      continue;
    }

    if (assignment.ringIndex === 0 && assignment.sectorIndex >= 3 && assignment.sectorIndex < 6) {
      normalizedAssignments[stableOwnerId] = {
        ringIndex: 2,
        sectorIndex: assignment.sectorIndex - 3,
      };
      changed = true;
    }
  }

  return changed ? normalizedAssignments : assignments;
}

function pruneTeamGraphSlotAssignmentsForVisibleOwners(
  assignments: TeamGraphSlotAssignments | undefined,
  visibleOwnerIds: readonly string[]
): TeamGraphSlotAssignments | undefined {
  const normalizedAssignments = normalizeTeamGraphSlotAssignmentsForVisibleOwners(
    assignments,
    visibleOwnerIds
  );
  return Object.keys(normalizedAssignments).length > 0 ? normalizedAssignments : undefined;
}

function normalizeTeamGraphGridOwnerOrder(
  order: readonly string[] | undefined,
  visibleOwnerIds: readonly string[]
): string[] {
  const visibleOwnerIdSet = new Set(visibleOwnerIds);
  const normalizedOrder: string[] = [];
  const seenOwnerIds = new Set<string>();

  for (const stableOwnerId of order ?? []) {
    if (!visibleOwnerIdSet.has(stableOwnerId) || seenOwnerIds.has(stableOwnerId)) {
      continue;
    }
    normalizedOrder.push(stableOwnerId);
    seenOwnerIds.add(stableOwnerId);
  }

  for (const stableOwnerId of visibleOwnerIds) {
    if (seenOwnerIds.has(stableOwnerId)) {
      continue;
    }
    normalizedOrder.push(stableOwnerId);
    seenOwnerIds.add(stableOwnerId);
  }

  return normalizedOrder;
}

export function getDefaultTeamGraphSlotAssignmentsForMembers(
  members: readonly TeamGraphMemberSeedInput[],
  configMembers: readonly TeamGraphConfigMemberSeedInput[] = []
): TeamGraphSlotAssignments {
  return buildTeamGraphDefaultLayoutSeed(members, configMembers).assignments;
}

export function isTeamGraphSlotPersistenceDisabled(): boolean {
  return DISABLE_PERSISTED_TEAM_GRAPH_SLOT_ASSIGNMENTS;
}

function isVisibleInActiveTeamSurface(
  state: Pick<AppState, 'paneLayout'>,
  teamName: string | null | undefined
): boolean {
  if (!teamName) {
    return false;
  }
  return state.paneLayout.panes.some((pane) => {
    if (!pane.activeTabId) {
      return false;
    }
    const activeTab = pane.tabs.find((tab) => tab.id === pane.activeTabId);
    return (
      (activeTab?.type === 'team' || activeTab?.type === 'graph') && activeTab.teamName === teamName
    );
  });
}

function shouldInvalidateCachedTeamDataForError(teamName: string, message: string): boolean {
  return (
    message === 'TEAM_DRAFT' ||
    message.includes('TEAM_DRAFT') ||
    message === `Team not found: ${teamName}` ||
    message === 'Team config not found'
  );
}

export interface TeamSlice {
  teams: TeamSummary[];
  /** O(1) lookup to avoid array scans in render-hot paths */
  teamByName: Record<string, TeamSummary>;
  /** O(1) lookup: sessionId -> owning team (lead + history) */
  teamBySessionId: Record<string, TeamSummary>;
  /** Centralized git branch cache: normalizedPath → branch name | null */
  branchByPath: Record<string, string | null>;
  teamsLoading: boolean;
  teamsError: string | null;
  globalTasks: GlobalTask[];
  globalTasksLoading: boolean;
  globalTasksInitialized: boolean;
  globalTasksError: string | null;
  globalTaskDetail: GlobalTaskDetailState | null;
  openGlobalTaskDetail: (teamName: string, taskId: string, commentId?: string) => void;
  closeGlobalTaskDetail: () => void;
  /** Set by MemberHoverCard to signal TeamDetailView to open MemberDetailDialog */
  pendingMemberProfile: PendingMemberProfileState | null;
  openMemberProfile: (
    memberName: string,
    teamName?: string,
    focus?: PendingMemberProfileState['focus']
  ) => void;
  closeMemberProfile: () => void;
  pendingTeamSectionFocus: PendingTeamSectionFocusState | null;
  focusTeamSection: (teamName: string, section: TeamSectionTarget) => void;
  clearTeamSectionFocus: () => void;
  /** Set by GlobalTaskDetailDialog to signal TeamDetailView to open ChangeReviewDialog */
  pendingReviewRequest: {
    taskId: string;
    filePath?: string;
    requestOptions: TaskChangeRequestOptions;
  } | null;
  setPendingReviewRequest: (
    req: { taskId: string; filePath?: string; requestOptions: TaskChangeRequestOptions } | null
  ) => void;
  selectedTeamName: string | null;
  selectedTeamData: TeamViewSnapshot | null;
  /** Team-scoped detailed cache used by multi-pane views like agent graph. */
  teamDataCacheByName: Record<string, TeamViewSnapshot>;
  slotLayoutVersion: string;
  graphLayoutModeByTeam: Record<string, GraphLayoutMode>;
  gridOwnerOrderByTeam: Record<string, string[]>;
  slotAssignmentsByTeam: Record<string, TeamGraphSlotAssignments>;
  teamMessagesByName: Record<string, TeamMessagesCacheEntry>;
  memberActivityMetaByTeam: Record<string, TeamMemberActivityMeta>;
  graphLayoutSessionByTeam: Record<string, TeamGraphLayoutSessionState>;
  selectedTeamLoading: boolean;
  selectedTeamLoadNonce: number;
  selectedTeamError: string | null;
  sendingMessage: boolean;
  sendMessageError: string | null;
  sendMessageWarning: string | null;
  sendMessageDebugDetails: OpenCodeRuntimeDeliveryDebugDetails | null;
  lastSendMessageResult: SendMessageResult | null;
  clearSendMessageRuntimeDiagnostics: (messageId?: string | null) => void;
  refreshSendMessageRuntimeDeliveryStatus: (
    teamName: string,
    input: string | { messageId: string; statusMessageId?: string | null }
  ) => Promise<void>;
  reviewActionError: string | null;
  provisioningRuns: Record<string, TeamProvisioningProgress>;
  /** Synthetic TeamSummary snapshots for teams currently being provisioned (before config.json exists). */
  provisioningSnapshotByTeam: Record<string, TeamSummary>;
  currentProvisioningRunIdByTeam: Record<string, string | null>;
  currentRuntimeRunIdByTeam: Record<string, string | null>;
  /** Runs explicitly cleared after Unknown runId polling; late events/progress for them are ignored. */
  ignoredProvisioningRunIds: Record<string, string>;
  /** Runtime runs explicitly tombstoned after stop/offline so late events cannot resurrect UI state. */
  ignoredRuntimeRunIds: Record<string, string>;
  /**
   * Per-team lower bound for provisioning progress timestamps.
   * Used to ignore late progress events from a previous run after stop→launch.
   */
  provisioningStartedAtFloorByTeam: Record<string, string>;
  leadActivityByTeam: Record<string, LeadActivityState>;
  leadContextByTeam: Record<string, LeadContextUsage>;
  activeTaskLogActivityByTeam: Record<string, Record<string, true>>;
  activeToolsByTeam: Record<string, Record<string, Record<string, ActiveToolCall>>>;
  finishedVisibleByTeam: Record<string, Record<string, Record<string, ActiveToolCall>>>;
  toolHistoryByTeam: Record<string, Record<string, ActiveToolCall[]>>;
  /** Per-team per-member spawn statuses during team provisioning/launch. */
  memberSpawnStatusesByTeam: Record<string, Record<string, MemberSpawnStatusEntry>>;
  memberSpawnSnapshotsByTeam: Record<string, MemberSpawnStatusesSnapshot>;
  teamAgentRuntimeByTeam: Record<string, TeamAgentRuntimeSnapshot>;
  fetchMemberSpawnStatuses: (teamName: string) => Promise<void>;
  fetchTeamAgentRuntime: (teamName: string) => Promise<void>;
  provisioningErrorByTeam: Record<string, string | null>;
  clearProvisioningError: (teamName?: string) => void;
  /** Per-team launch parameters (model, effort, extended context) — persisted in localStorage. */
  launchParamsByTeam: Record<string, TeamLaunchParams>;
  kanbanFilterQuery: string | null;
  provisioningProgressUnsubscribe: (() => void) | null;
  fetchBranches: (paths: string[]) => Promise<void>;
  fetchTeams: () => Promise<void>;
  fetchAllTasks: () => Promise<void>;
  openTeamsTab: () => void;
  openTeamTab: (teamName: string, projectPath?: string, taskId?: string) => void;
  clearKanbanFilter: () => void;
  ensureTeamGraphSlotAssignments: (
    teamName: string,
    members: readonly TeamGraphMemberSeedInput[],
    configMembers?: readonly TeamGraphConfigMemberSeedInput[]
  ) => void;
  setTeamGraphOwnerSlotAssignment: (
    teamName: string,
    stableOwnerId: string,
    assignment: GraphOwnerSlotAssignment
  ) => void;
  commitTeamGraphOwnerSlotDrop: (
    teamName: string,
    stableOwnerId: string,
    assignment: GraphOwnerSlotAssignment,
    displacedStableOwnerId?: string,
    displacedAssignment?: GraphOwnerSlotAssignment
  ) => void;
  setTeamGraphLayoutMode: (teamName: string, mode: GraphLayoutMode) => void;
  swapTeamGraphGridOwners: (
    teamName: string,
    stableOwnerId: string,
    targetStableOwnerId: string
  ) => void;
  swapTeamGraphOwnerSlots: (
    teamName: string,
    stableOwnerId: string,
    otherStableOwnerId: string
  ) => void;
  clearTeamGraphSlotAssignments: (teamName?: string) => void;
  resetTeamGraphSlotAssignmentsToDefaults: (teamName: string) => void;
  setSelectedTeamTaskChangePresence: (
    teamName: string,
    taskId: string,
    presence: TaskChangePresenceState
  ) => void;
  refreshTeamChangePresence: (teamName: string) => Promise<void>;
  selectTeam: (
    teamName: string,
    opts?: { skipProjectAutoSelect?: boolean; allowReloadWhileProvisioning?: boolean }
  ) => Promise<void>;
  refreshTeamData: (teamName: string, opts?: RefreshTeamDataOptions) => Promise<void>;
  refreshTeamMessagesHead: (teamName: string) => Promise<RefreshTeamMessagesHeadResult>;
  loadOlderTeamMessages: (teamName: string) => Promise<void>;
  refreshMemberActivityMeta: (teamName: string) => Promise<void>;
  syncTeamPendingReplyRefresh: (
    teamName: string,
    sourceId: string,
    enabled: boolean,
    delayMs?: number
  ) => void;
  sendTeamMessage: (teamName: string, request: SendMessageRequest) => Promise<SendMessageResult>;
  crossTeamTargets: {
    teamName: string;
    displayName: string;
    description?: string;
    color?: string;
    leadName?: string;
    leadColor?: string;
    isOnline?: boolean;
  }[];
  crossTeamTargetsLoading: boolean;
  fetchCrossTeamTargets: () => Promise<void>;
  sendCrossTeamMessage: (request: CrossTeamSendRequest) => Promise<void>;
  requestReview: (teamName: string, taskId: string) => Promise<void>;
  updateKanban: (teamName: string, taskId: string, patch: UpdateKanbanPatch) => Promise<void>;
  updateKanbanColumnOrder: (
    teamName: string,
    columnId: KanbanColumnId,
    orderedTaskIds: string[]
  ) => Promise<void>;
  createTeamTask: (teamName: string, request: CreateTaskRequest) => Promise<TeamTask>;
  startTask: (teamName: string, taskId: string) => Promise<{ notifiedOwner: boolean }>;
  startTaskByUser: (teamName: string, taskId: string) => Promise<{ notifiedOwner: boolean }>;
  updateTaskStatus: (teamName: string, taskId: string, status: TeamTaskStatus) => Promise<void>;
  updateTaskOwner: (teamName: string, taskId: string, owner: string | null) => Promise<void>;
  updateTaskFields: (
    teamName: string,
    taskId: string,
    fields: { subject?: string; description?: string }
  ) => Promise<void>;
  addingComment: boolean;
  addCommentError: string | null;
  addTaskComment: (
    teamName: string,
    taskId: string,
    request: AddTaskCommentRequest
  ) => Promise<TaskComment>;
  addMember: (teamName: string, request: AddMemberRequest) => Promise<void>;
  restartMember: (teamName: string, memberName: string) => Promise<void>;
  skipMemberForLaunch: (teamName: string, memberName: string) => Promise<void>;
  removeMember: (teamName: string, memberName: string) => Promise<void>;
  updateMemberRole: (
    teamName: string,
    memberName: string,
    role: string | undefined
  ) => Promise<void>;
  retryFailedOpenCodeSecondaryLanes: (
    teamName: string
  ) => Promise<RetryFailedOpenCodeSecondaryLanesResult>;
  addTaskRelationship: (
    teamName: string,
    taskId: string,
    targetId: string,
    type: 'blockedBy' | 'blocks' | 'related'
  ) => Promise<void>;
  removeTaskRelationship: (
    teamName: string,
    taskId: string,
    targetId: string,
    type: 'blockedBy' | 'blocks' | 'related'
  ) => Promise<void>;
  setTaskNeedsClarification: (
    teamName: string,
    taskId: string,
    value: 'lead' | 'user' | null
  ) => Promise<void>;
  saveTaskAttachment: (
    teamName: string,
    taskId: string,
    file: { name: string; type: string; base64: string }
  ) => Promise<void>;
  deleteTaskAttachment: (
    teamName: string,
    taskId: string,
    attachmentId: string,
    mimeType: string
  ) => Promise<void>;
  getTaskAttachmentData: (
    teamName: string,
    taskId: string,
    attachmentId: string,
    mimeType: string
  ) => Promise<string | null>;
  deletedTasks: TeamTask[];
  deletedTasksLoading: boolean;
  softDeleteTask: (teamName: string, taskId: string) => Promise<void>;
  restoreTask: (teamName: string, taskId: string) => Promise<void>;
  fetchDeletedTasks: (teamName: string) => Promise<void>;
  deleteTeam: (teamName: string) => Promise<void>;
  restoreTeam: (teamName: string) => Promise<void>;
  permanentlyDeleteTeam: (teamName: string) => Promise<void>;
  createTeam: (request: TeamCreateRequest) => Promise<string>;
  launchTeam: (request: TeamLaunchRequest) => Promise<string>;
  cancelProvisioning: (runId: string) => Promise<void>;
  getProvisioningStatus: (runId: string) => Promise<TeamProvisioningProgress>;
  clearMissingProvisioningRun: (runId: string) => void;
  onProvisioningProgress: (progress: TeamProvisioningProgress) => void;
  subscribeProvisioningProgress: () => void;
  unsubscribeProvisioningProgress: () => void;
  pendingApprovals: ToolApprovalRequest[];
  /** Resolved permission approvals: request_id → allowed (true/false). Used for noise row icons. */
  resolvedApprovals: Map<string, boolean>;
  toolApprovalSettings: ToolApprovalSettings;
  updateToolApprovalSettings: (
    patch: Partial<ToolApprovalSettings>,
    forTeam?: string
  ) => Promise<void>;
  respondToToolApproval: (
    teamName: string,
    runId: string,
    requestId: string,
    allow: boolean,
    message?: string
  ) => Promise<void>;

  // Messages panel UI state
  messagesPanelMode: TeamMessagesPanelMode;
  messagesPanelWidth: number;
  sidebarLogsHeight: number;
  setMessagesPanelMode: (mode: TeamMessagesPanelMode) => void;
  setMessagesPanelWidth: (width: number) => void;
  setSidebarLogsHeight: (height: number) => void;
}

// --- Per-team launch params persistence ---
const LAUNCH_PARAMS_PREFIX = 'team:launchParams:';

export function getCurrentProvisioningProgressForTeam(
  state: Pick<TeamSlice, 'currentProvisioningRunIdByTeam' | 'provisioningRuns'>,
  teamName: string
): TeamProvisioningProgress | null {
  const currentRunId = state.currentProvisioningRunIdByTeam[teamName];
  return currentRunId ? (state.provisioningRuns[currentRunId] ?? null) : null;
}

export function isTeamProvisioningActive(
  state: Pick<TeamSlice, 'currentProvisioningRunIdByTeam' | 'provisioningRuns'>,
  teamName: string
): boolean {
  const current = getCurrentProvisioningProgressForTeam(state, teamName);
  return current != null && ACTIVE_PROVISIONING_STATES.has(current.state);
}

function loadAllLaunchParams(): Record<string, TeamLaunchParams> {
  const result: Record<string, TeamLaunchParams> = {};
  try {
    for (let i = 0; i < localStorage.length; i++) {
      const key = localStorage.key(i);
      if (key?.startsWith(LAUNCH_PARAMS_PREFIX)) {
        const teamName = key.slice(LAUNCH_PARAMS_PREFIX.length);
        const parsed = JSON.parse(localStorage.getItem(key)!) as TeamLaunchParams;
        if (parsed && typeof parsed === 'object') {
          result[teamName] = parsed;
        }
      }
    }
  } catch {
    // ignore — best-effort restore
  }
  return result;
}

function saveLaunchParams(teamName: string, params: TeamLaunchParams): void {
  try {
    localStorage.setItem(LAUNCH_PARAMS_PREFIX + teamName, JSON.stringify(params));
  } catch {
    // ignore — best-effort persist
  }
}

/**
 * Extract the base model name from the raw model string sent to CLI.
 * E.g. 'opus[1m]' → 'opus', 'sonnet' → 'sonnet', undefined → undefined.
 */
function extractBaseModel(raw?: string, providerId?: TeamProviderId): string | undefined {
  return extractProviderScopedBaseModel(raw, providerId);
}

function buildLaunchParamsFromRuntimeRequest(
  request: Pick<
    TeamCreateRequest,
    'providerId' | 'providerBackendId' | 'model' | 'effort' | 'fastMode' | 'limitContext'
  >,
  fallback?: TeamLaunchParams
): TeamLaunchParams {
  const providerId = request.providerId ?? fallback?.providerId ?? 'anthropic';
  const providerChanged =
    request.providerId != null &&
    fallback?.providerId != null &&
    request.providerId !== fallback.providerId;
  const hasModel = Object.hasOwn(request, 'model');
  const baseModel =
    hasModel && typeof request.model === 'string'
      ? extractBaseModel(request.model, providerId)
      : undefined;
  const rawProviderBackendId = Object.hasOwn(request, 'providerBackendId')
    ? request.providerBackendId
    : providerChanged
      ? undefined
      : fallback?.providerBackendId;
  return {
    providerId,
    providerBackendId: migrateProviderBackendId(providerId, rawProviderBackendId),
    model: hasModel
      ? baseModel || 'default'
      : (providerChanged ? undefined : fallback?.model) || 'default',
    effort: Object.hasOwn(request, 'effort')
      ? request.effort
      : providerChanged
        ? undefined
        : fallback?.effort,
    fastMode: Object.hasOwn(request, 'fastMode')
      ? request.fastMode
      : providerChanged
        ? undefined
        : fallback?.fastMode,
    limitContext:
      typeof request.limitContext === 'boolean'
        ? request.limitContext
        : providerChanged
          ? false
          : (fallback?.limitContext ?? false),
  };
}

function areTeamLaunchParamsEqual(
  left: TeamLaunchParams | undefined,
  right: TeamLaunchParams | undefined
): boolean {
  if (left === right) return true;
  if (!left || !right) return false;
  return (
    left.providerId === right.providerId &&
    left.providerBackendId === right.providerBackendId &&
    left.model === right.model &&
    left.effort === right.effort &&
    left.fastMode === right.fastMode &&
    left.limitContext === right.limitContext
  );
}

const TOOL_APPROVAL_PREFIX = 'team:toolApprovalSettings:';

function parseToolApprovalSettings(raw: string | null): ToolApprovalSettings {
  if (!raw) return DEFAULT_TOOL_APPROVAL_SETTINGS;
  try {
    const parsed = JSON.parse(raw) as Record<string, unknown>;
    const d = DEFAULT_TOOL_APPROVAL_SETTINGS;
    return {
      autoAllowAll: typeof parsed.autoAllowAll === 'boolean' ? parsed.autoAllowAll : d.autoAllowAll,
      autoAllowFileEdits:
        typeof parsed.autoAllowFileEdits === 'boolean'
          ? parsed.autoAllowFileEdits
          : d.autoAllowFileEdits,
      autoAllowSafeBash:
        typeof parsed.autoAllowSafeBash === 'boolean'
          ? parsed.autoAllowSafeBash
          : d.autoAllowSafeBash,
      timeoutAction:
        typeof parsed.timeoutAction === 'string' &&
        ['allow', 'deny', 'wait'].includes(parsed.timeoutAction)
          ? (parsed.timeoutAction as ToolApprovalSettings['timeoutAction'])
          : d.timeoutAction,
      timeoutSeconds:
        typeof parsed.timeoutSeconds === 'number' &&
        Number.isFinite(parsed.timeoutSeconds) &&
        parsed.timeoutSeconds >= 5 &&
        parsed.timeoutSeconds <= 300
          ? parsed.timeoutSeconds
          : d.timeoutSeconds,
    };
  } catch {
    return DEFAULT_TOOL_APPROVAL_SETTINGS;
  }
}

function loadToolApprovalSettingsForTeam(teamName: string): ToolApprovalSettings {
  return parseToolApprovalSettings(localStorage.getItem(TOOL_APPROVAL_PREFIX + teamName));
}

function saveToolApprovalSettingsForTeam(teamName: string, settings: ToolApprovalSettings): void {
  try {
    localStorage.setItem(TOOL_APPROVAL_PREFIX + teamName, JSON.stringify(settings));
  } catch {
    // best-effort
  }
}

/** Load global settings (legacy fallback for first load / no team selected). */
function loadToolApprovalSettings(): ToolApprovalSettings {
  return parseToolApprovalSettings(localStorage.getItem('team:toolApprovalSettings'));
}

export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set, get) => ({
  teams: [],
  teamByName: {},
  teamBySessionId: {},
  branchByPath: {},
  teamsLoading: false,
  teamsError: null,
  globalTasks: [],
  globalTasksLoading: false,
  globalTasksInitialized: false,
  globalTasksError: null,
  selectedTeamName: null,
  selectedTeamData: null,
  teamDataCacheByName: {},
  slotLayoutVersion: GRAPH_STABLE_SLOT_LAYOUT_VERSION,
  graphLayoutModeByTeam: {},
  gridOwnerOrderByTeam: {},
  slotAssignmentsByTeam: {},
  teamMessagesByName: {},
  memberActivityMetaByTeam: {},
  graphLayoutSessionByTeam: {},
  selectedTeamLoading: false,
  selectedTeamLoadNonce: 0,
  selectedTeamError: null,
  sendingMessage: false,
  sendMessageError: null,
  sendMessageWarning: null,
  sendMessageDebugDetails: null,
  lastSendMessageResult: null,
  crossTeamTargets: [],
  crossTeamTargetsLoading: false,
  reviewActionError: null,
  provisioningRuns: {},
  provisioningSnapshotByTeam: {},
  currentProvisioningRunIdByTeam: {},
  currentRuntimeRunIdByTeam: {},
  ignoredProvisioningRunIds: {},
  ignoredRuntimeRunIds: {},
  provisioningStartedAtFloorByTeam: {},
  leadActivityByTeam: {},
  leadContextByTeam: {},
  activeTaskLogActivityByTeam: {},
  activeToolsByTeam: {},
  finishedVisibleByTeam: {},
  toolHistoryByTeam: {},
  memberSpawnStatusesByTeam: {},
  memberSpawnSnapshotsByTeam: {},
  teamAgentRuntimeByTeam: {},
  provisioningErrorByTeam: {},
  clearProvisioningError: (teamName?: string) =>
    set((state) => {
      if (!teamName) {
        return { provisioningErrorByTeam: {} };
      }

      if (!(teamName in state.provisioningErrorByTeam)) {
        return {};
      }

      const nextErrors = { ...state.provisioningErrorByTeam };
      delete nextErrors[teamName];
      return { provisioningErrorByTeam: nextErrors };
    }),
  launchParamsByTeam: loadAllLaunchParams(),
  fetchMemberSpawnStatuses: async (teamName: string) => {
    if (!api.teams?.getMemberSpawnStatuses) return;
    const backoffUntil = memberSpawnStatusesIpcBackoffUntilByTeam.get(teamName) ?? 0;
    if (backoffUntil > Date.now()) {
      return;
    }
    try {
      const snapshot = await api.teams.getMemberSpawnStatuses(teamName);
      memberSpawnStatusesIpcBackoffUntilByTeam.delete(teamName);
      set((prev) => {
        if (snapshot.runId != null && prev.ignoredRuntimeRunIds[snapshot.runId] === teamName) {
          return {};
        }

        if (
          prev.currentRuntimeRunIdByTeam[teamName] == null &&
          prev.leadActivityByTeam[teamName] === 'offline' &&
          snapshot.runId != null
        ) {
          return {};
        }

        if (
          snapshot.runId != null &&
          prev.currentRuntimeRunIdByTeam[teamName] != null &&
          prev.currentRuntimeRunIdByTeam[teamName] !== snapshot.runId
        ) {
          return {};
        }

        const nextCurrentRuntimeRunIdByTeam =
          snapshot.runId == null || prev.currentRuntimeRunIdByTeam[teamName] != null
            ? prev.currentRuntimeRunIdByTeam
            : {
                ...prev.currentRuntimeRunIdByTeam,
                [teamName]: snapshot.runId,
              };
        // Keep same-team ignored runtime tombstones intact here.
        // Member-spawn snapshots do not carry a run start time, so clearing older
        // ignored ids can reopen stale zombie snapshots during create/launch churn.
        const previousSnapshot = prev.memberSpawnSnapshotsByTeam[teamName];
        const snapshotChanged = !areMemberSpawnSnapshotsSemanticallyEqual(
          previousSnapshot,
          snapshot
        );

        if (!snapshotChanged) {
          maybeLogMemberSpawnUiEqualSuppressed(teamName, snapshot.runId);
          if (nextCurrentRuntimeRunIdByTeam === prev.currentRuntimeRunIdByTeam) {
            return {};
          }

          return {
            currentRuntimeRunIdByTeam: nextCurrentRuntimeRunIdByTeam,
          };
        }

        return {
          currentRuntimeRunIdByTeam: nextCurrentRuntimeRunIdByTeam,
          memberSpawnStatusesByTeam: {
            ...prev.memberSpawnStatusesByTeam,
            [teamName]: snapshot.statuses,
          },
          memberSpawnSnapshotsByTeam: {
            ...prev.memberSpawnSnapshotsByTeam,
            [teamName]: snapshot,
          },
        };
      });
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      if (message.includes("No handler registered for 'team:memberSpawnStatuses'")) {
        memberSpawnStatusesIpcBackoffUntilByTeam.set(
          teamName,
          Date.now() + MEMBER_SPAWN_STATUSES_IPC_RETRY_BACKOFF_MS
        );
      }
      // ignore — spawn statuses are best-effort
    }
  },
  fetchTeamAgentRuntime: async (teamName: string) => {
    if (!api.teams?.getTeamAgentRuntime) return;
    try {
      const snapshot = await api.teams.getTeamAgentRuntime(teamName);
      set((prev) => {
        if (snapshot.runId != null && prev.ignoredRuntimeRunIds[snapshot.runId] === teamName) {
          return {};
        }
        if (
          snapshot.runId != null &&
          prev.currentRuntimeRunIdByTeam[teamName] != null &&
          prev.currentRuntimeRunIdByTeam[teamName] !== snapshot.runId
        ) {
          return {};
        }
        const previousSnapshot = prev.teamAgentRuntimeByTeam[teamName];
        if (areTeamAgentRuntimeSnapshotsEqual(previousSnapshot, snapshot)) {
          return {};
        }
        return {
          teamAgentRuntimeByTeam: {
            ...prev.teamAgentRuntimeByTeam,
            [teamName]: snapshot,
          },
        };
      });
    } catch {
      // ignore — runtime snapshots are best-effort
    }
  },
  kanbanFilterQuery: null,
  globalTaskDetail: null,
  pendingMemberProfile: null,
  pendingTeamSectionFocus: null,
  openMemberProfile: (
    memberName: string,
    teamName?: string,
    focus?: PendingMemberProfileState['focus']
  ) => set({ pendingMemberProfile: { memberName, teamName, focus } }),
  closeMemberProfile: () => set({ pendingMemberProfile: null }),
  focusTeamSection: (teamName: string, section: TeamSectionTarget) =>
    set({ pendingTeamSectionFocus: { teamName, section } }),
  clearTeamSectionFocus: () => set({ pendingTeamSectionFocus: null }),
  pendingReviewRequest: null,
  setPendingReviewRequest: (req) => set({ pendingReviewRequest: req }),
  openGlobalTaskDetail: (teamName: string, taskId: string, commentId?: string) => {
    set({ globalTaskDetail: { teamName, taskId, commentId } });
  },
  closeGlobalTaskDetail: () => set({ globalTaskDetail: null }),
  addingComment: false,
  addCommentError: null,
  provisioningProgressUnsubscribe: null,
  deletedTasks: [],
  deletedTasksLoading: false,
  pendingApprovals: [],
  resolvedApprovals: new Map(),
  toolApprovalSettings: loadToolApprovalSettings(),

  // Messages panel UI state
  messagesPanelMode: 'sidebar' as const,
  messagesPanelWidth: 340,
  sidebarLogsHeight: 213,
  setMessagesPanelMode: (mode: TeamMessagesPanelMode) => set({ messagesPanelMode: mode }),
  setMessagesPanelWidth: (width: number) => set({ messagesPanelWidth: width }),
  setSidebarLogsHeight: (height: number) => set({ sidebarLogsHeight: height }),

  fetchBranches: async (paths: string[]) => {
    const entries = await Promise.all(
      paths.map(async (p) => {
        try {
          const branch = await api.teams.getProjectBranch(p);
          return [normalizePath(p), branch] as const;
        } catch {
          return [normalizePath(p), null] as const;
        }
      })
    );
    const results: Record<string, string | null> = Object.fromEntries(entries);
    if (Object.keys(results).length > 0) {
      set((state) => {
        let changed = false;
        for (const [key, value] of Object.entries(results)) {
          if (state.branchByPath[key] !== value) {
            changed = true;
            break;
          }
        }
        if (!changed) {
          return {};
        }
        return { branchByPath: { ...state.branchByPath, ...results } };
      });
    }
  },

  fetchTeams: async () => {
    // Guard: prevent concurrent fetches (component mount + centralized init chain).
    // Only effective during initial load (when teamsLoading is set to true below).
    // Refreshes are already serialized by the throttle timer in onTeamChange.
    if (get().teamsLoading) return;
    // Only show loading spinner on initial load — avoids flickering when refreshing
    const isInitialLoad = get().teams.length === 0;
    if (isInitialLoad) {
      set({ teamsLoading: true, teamsError: null });
    }
    try {
      const teams = await withTimeout(
        unwrapIpc('team:list', () => api.teams.list()),
        TEAM_FETCH_TIMEOUT_MS,
        'fetchTeams'
      );
      const teamByName: Record<string, TeamSummary> = {};
      const teamBySessionId: Record<string, TeamSummary> = {};
      for (const team of teams) {
        teamByName[team.teamName] = team;
        if (team.leadSessionId) {
          teamBySessionId[team.leadSessionId] = team;
        }
        if (Array.isArray(team.sessionHistory)) {
          for (const sid of team.sessionHistory) {
            if (typeof sid === 'string' && sid) {
              teamBySessionId[sid] = team;
            }
          }
        }
      }
      // Atomic update: set teams AND clean up provisioning snapshots in one call
      // to prevent any render cycle with duplicate cards.
      set((state) => {
        const nextSnapshots = { ...state.provisioningSnapshotByTeam };
        for (const team of teams) {
          delete nextSnapshots[team.teamName];
        }
        return {
          teams,
          teamByName,
          teamBySessionId,
          teamsLoading: false,
          teamsError: null,
          provisioningSnapshotByTeam: nextSnapshots,
        };
      });
    } catch (error) {
      // On refresh failure, keep existing teams visible
      set({
        teamsLoading: false,
        teamsError: isInitialLoad
          ? error instanceof IpcError
            ? error.message
            : error instanceof Error
              ? error.message
              : 'Failed to fetch teams'
          : null,
      });
    }
  },

  fetchAllTasks: async () => {
    if (inFlightGlobalTasksRefresh) {
      pendingFreshGlobalTasksRefresh = true;
      await inFlightGlobalTasksRefresh;
      return;
    }

    const runRefresh = async (): Promise<void> => {
      do {
        pendingFreshGlobalTasksRefresh = false;

        // Show skeleton only on the very first fetch — not on subsequent refreshes
        // even when the task list is empty (avoids flickering skeleton on every watcher event).
        const isInitialLoad = !get().globalTasksInitialized;
        if (isInitialLoad) {
          set({ globalTasksLoading: true, globalTasksError: null });
        }
        const oldTasks = get().globalTasks;
        const wasFirst = isFirstFetchAllTasks;
        isFirstFetchAllTasks = false;
        try {
          const tasks = await withTimeout(
            unwrapIpc('team:getAllTasks', () => api.teams.getAllTasks()),
            TEAM_FETCH_TIMEOUT_MS,
            'fetchAllTasks'
          );
          if (!wasFirst) {
            const notifyOnClarifications =
              get().appConfig?.notifications?.notifyOnClarifications ?? true;
            detectClarificationNotifications(oldTasks, tasks, notifyOnClarifications);
            detectBlockedTaskNotifications(oldTasks, tasks, notifyOnClarifications);
            detectStatusChangeNotifications(oldTasks, tasks, get().appConfig, get().teamByName);
            const notifyOnTaskComments =
              get().appConfig?.notifications?.notifyOnTaskComments ?? true;
            detectTaskCommentNotifications(oldTasks, tasks, notifyOnTaskComments);
            const notifyOnTaskCreated = get().appConfig?.notifications?.notifyOnTaskCreated ?? true;
            detectTaskCreatedNotifications(oldTasks, tasks, notifyOnTaskCreated);
            const notifyOnAllCompleted =
              get().appConfig?.notifications?.notifyOnAllTasksCompleted ?? true;
            detectAllTasksCompletedNotification(oldTasks, tasks, notifyOnAllCompleted);
          } else {
            // Initial load — seed the Sets to prevent false notifications on next update
            for (const task of tasks) {
              if (task.needsClarification === 'user') {
                notifiedClarificationTaskKeys.add(`${task.teamName}:${task.id}`);
              }
              if ((task.blockedBy?.length ?? 0) > 0) {
                notifiedBlockedTaskKeys.add(
                  `${task.teamName}:${task.id}:${(task.blockedBy ?? []).join(',')}`
                );
              }
              notifiedStatusChangeKeys.add(`${task.teamName}:${task.id}:${task.status}`);
              if (isTeamTaskNeedsFixActionable(task)) {
                notifiedStatusChangeKeys.add(`${task.teamName}:${task.id}:needsFix`);
              }
              if (getTeamTaskWorkflowColumn(task) === 'approved') {
                notifiedStatusChangeKeys.add(`${task.teamName}:${task.id}:approved`);
              }
              if (getTeamTaskWorkflowColumn(task) === 'review') {
                notifiedStatusChangeKeys.add(`${task.teamName}:${task.id}:review`);
              }
              // Seed comment keys to prevent false notifications
              for (const comment of task.comments ?? []) {
                notifiedCommentKeys.add(`${task.teamName}:${task.id}:${comment.id}`);
              }
              // Seed created task keys to prevent false notifications
              notifiedCreatedTaskKeys.add(`${task.teamName}:${task.id}`);
            }
            // Seed all-completed teams
            const teamTasksMap = new Map<string, GlobalTask[]>();
            for (const task of tasks) {
              const list = teamTasksMap.get(task.teamName) ?? [];
              list.push(task);
              teamTasksMap.set(task.teamName, list);
            }
            for (const [teamName, teamTasks] of teamTasksMap) {
              if (teamTasks.every(isTeamTaskFinalForCompletionNotification)) {
                notifiedAllCompletedTeams.add(teamName);
              }
            }
          }

          set({
            globalTasks: tasks,
            globalTasksLoading: false,
            globalTasksInitialized: true,
            globalTasksError: null,
          });
        } catch (error) {
          set({
            globalTasksLoading: false,
            globalTasksInitialized: true,
            globalTasksError: isInitialLoad
              ? error instanceof IpcError
                ? error.message
                : error instanceof Error
                  ? error.message
                  : 'Failed to fetch tasks'
              : null,
          });
        }
      } while (pendingFreshGlobalTasksRefresh);
    };

    const request = runRefresh().finally(() => {
      if (inFlightGlobalTasksRefresh === request) {
        inFlightGlobalTasksRefresh = null;
      }
    });
    inFlightGlobalTasksRefresh = request;
    await request;
  },

  openTeamsTab: () => {
    const state = get();
    const focusedPane = state.paneLayout.panes.find((p) => p.id === state.paneLayout.focusedPaneId);
    const teamsTab = focusedPane?.tabs.find((tab) => tab.type === 'teams');
    if (teamsTab) {
      state.setActiveTab(teamsTab.id);
      return;
    }

    state.openTab({
      type: 'teams',
      label: 'Teams',
    });
  },

  openTeamTab: (teamName: string, projectPath?: string, _taskId?: string) => {
    if (!teamName.trim()) {
      return;
    }

    // If projectPath is provided, immediately select the matching project in the sidebar.
    // This avoids a race condition where config.json hasn't been updated with projectPath yet.
    if (projectPath) {
      const stateForProject = get();
      const normalizedPath = normalizePath(projectPath);
      const matchingProject = stateForProject.projects.find(
        (p) => normalizePath(p.path) === normalizedPath
      );
      if (matchingProject && stateForProject.selectedProjectId !== matchingProject.id) {
        stateForProject.selectProject(matchingProject.id);
      }
    }

    const state = get();
    // Use display name from teams list or selected team data if available
    const teamSummary = state.teamByName[teamName];
    const selectedTeamDisplayName =
      state.selectedTeamName === teamName ? state.selectedTeamData?.config.name : undefined;
    const displayName = teamSummary?.displayName || selectedTeamDisplayName || teamName;

    const allTabs = state.getAllPaneTabs();
    const existing = allTabs.find((tab) => tab.type === 'team' && tab.teamName === teamName);
    if (existing) {
      state.setActiveTab(existing.id);
      // Sync label in case display name changed
      if (existing.label !== displayName) {
        state.updateTabLabel(existing.id, displayName);
      }
    } else {
      state.openTab({
        type: 'team',
        label: displayName,
        teamName,
      });
    }
  },

  clearKanbanFilter: () => {
    set({ kanbanFilterQuery: null });
  },

  ensureTeamGraphSlotAssignments: (teamName, members, configMembers = []) => {
    set((state) => {
      const nextState: Partial<TeamSlice> = {};
      let changed = false;

      let nextSlotAssignmentsByTeam = state.slotAssignmentsByTeam;
      let nextGraphLayoutSessionByTeam = state.graphLayoutSessionByTeam;
      if (state.slotLayoutVersion !== GRAPH_STABLE_SLOT_LAYOUT_VERSION) {
        nextState.slotLayoutVersion = GRAPH_STABLE_SLOT_LAYOUT_VERSION;
        nextSlotAssignmentsByTeam = {};
        nextGraphLayoutSessionByTeam = {};
        changed = true;
      }

      const defaultSeed = buildTeamGraphDefaultLayoutSeed(members, configMembers);
      const visibleAssignments = pruneTeamGraphSlotAssignmentsForVisibleOwners(
        nextSlotAssignmentsByTeam[teamName],
        defaultSeed.orderedVisibleOwnerIds
      );
      const currentSession = nextGraphLayoutSessionByTeam[teamName];

      if (DISABLE_PERSISTED_TEAM_GRAPH_SLOT_ASSIGNMENTS) {
        if (currentSession?.mode === 'manual') {
          if (
            !areTeamGraphSlotAssignmentsEqual(
              nextSlotAssignmentsByTeam[teamName],
              visibleAssignments
            )
          ) {
            nextSlotAssignmentsByTeam = { ...nextSlotAssignmentsByTeam };
            if (visibleAssignments) {
              nextSlotAssignmentsByTeam[teamName] = visibleAssignments;
            } else {
              delete nextSlotAssignmentsByTeam[teamName];
            }
            changed = true;
          }
        } else {
          if (
            !areTeamGraphSlotAssignmentsEqual(
              nextSlotAssignmentsByTeam[teamName],
              visibleAssignments
            ) ||
            !areTeamGraphSlotAssignmentsEqual(visibleAssignments, defaultSeed.assignments)
          ) {
            nextSlotAssignmentsByTeam = { ...nextSlotAssignmentsByTeam };
            if (Object.keys(defaultSeed.assignments).length === 0) {
              delete nextSlotAssignmentsByTeam[teamName];
            } else {
              nextSlotAssignmentsByTeam[teamName] = defaultSeed.assignments;
            }
            changed = true;
          }
          if (
            currentSession?.mode !== 'default' ||
            currentSession?.signature !== defaultSeed.signature
          ) {
            nextGraphLayoutSessionByTeam = {
              ...nextGraphLayoutSessionByTeam,
              [teamName]: {
                mode: 'default',
                signature: defaultSeed.signature,
              },
            };
            changed = true;
          }
        }

        if (!changed) {
          return {};
        }

        nextState.slotAssignmentsByTeam = nextSlotAssignmentsByTeam;
        nextState.graphLayoutSessionByTeam = nextGraphLayoutSessionByTeam;
        return nextState;
      }

      const currentAssignments = nextSlotAssignmentsByTeam[teamName];
      const migrated = migrateStableSlotAssignmentsForMembers(currentAssignments, members);
      const seeded = seedStableSlotAssignmentsForMembers(
        migrated.assignments,
        members,
        configMembers
      );
      if (migrated.changed || seeded.changed) {
        nextSlotAssignmentsByTeam = {
          ...nextSlotAssignmentsByTeam,
          [teamName]: seeded.assignments,
        };
        changed = true;
      }

      if (!changed) {
        return {};
      }

      nextState.slotAssignmentsByTeam = nextSlotAssignmentsByTeam;
      if (nextGraphLayoutSessionByTeam !== state.graphLayoutSessionByTeam) {
        nextState.graphLayoutSessionByTeam = nextGraphLayoutSessionByTeam;
      }
      return nextState;
    });
  },

  setTeamGraphOwnerSlotAssignment: (teamName, stableOwnerId, assignment) => {
    set((state) => {
      const currentAssignments = state.slotAssignmentsByTeam[teamName] ?? {};
      const existing = currentAssignments[stableOwnerId];
      const occupiedByOther = Object.entries(currentAssignments).find(
        ([otherStableOwnerId, otherAssignment]) =>
          otherStableOwnerId !== stableOwnerId &&
          otherAssignment.ringIndex === assignment.ringIndex &&
          otherAssignment.sectorIndex === assignment.sectorIndex
      );
      if (
        existing?.ringIndex === assignment.ringIndex &&
        existing?.sectorIndex === assignment.sectorIndex &&
        state.slotLayoutVersion === GRAPH_STABLE_SLOT_LAYOUT_VERSION
      ) {
        return {};
      }
      if (occupiedByOther) {
        logger.warn(
          `[graph-layout] refusing occupied slot assignment team=${teamName} owner=${stableOwnerId} target=${assignment.ringIndex}:${assignment.sectorIndex} occupiedBy=${occupiedByOther[0]}`
        );
        return {};
      }

      return {
        slotLayoutVersion: GRAPH_STABLE_SLOT_LAYOUT_VERSION,
        slotAssignmentsByTeam: {
          ...state.slotAssignmentsByTeam,
          [teamName]: {
            ...currentAssignments,
            [stableOwnerId]: assignment,
          },
        },
        graphLayoutSessionByTeam: {
          ...state.graphLayoutSessionByTeam,
          [teamName]: {
            mode: 'manual',
            signature: state.graphLayoutSessionByTeam[teamName]?.signature ?? null,
          },
        },
      };
    });
  },

  commitTeamGraphOwnerSlotDrop: (
    teamName,
    stableOwnerId,
    assignment,
    displacedStableOwnerId,
    displacedAssignment
  ) => {
    set((state) => {
      const currentAssignments = state.slotAssignmentsByTeam[teamName] ?? {};
      const existing = currentAssignments[stableOwnerId];
      const nextAssignments: TeamGraphSlotAssignments = {
        ...currentAssignments,
        [stableOwnerId]: assignment,
      };

      if (
        existing?.ringIndex === assignment.ringIndex &&
        existing?.sectorIndex === assignment.sectorIndex &&
        !displacedStableOwnerId &&
        state.slotLayoutVersion === GRAPH_STABLE_SLOT_LAYOUT_VERSION
      ) {
        return {};
      }

      if (displacedStableOwnerId && displacedAssignment) {
        nextAssignments[displacedStableOwnerId] = displacedAssignment;
      }

      const occupiedByConflict = Object.entries(nextAssignments).find(
        ([ownerId, nextAssignment]) => {
          if (ownerId === stableOwnerId || ownerId === displacedStableOwnerId) {
            return false;
          }
          return (
            (nextAssignment.ringIndex === assignment.ringIndex &&
              nextAssignment.sectorIndex === assignment.sectorIndex) ||
            (nextAssignment.ringIndex === displacedAssignment?.ringIndex &&
              nextAssignment.sectorIndex === displacedAssignment.sectorIndex)
          );
        }
      );

      if (occupiedByConflict) {
        logger.warn(
          `[graph-layout] refusing slot drop team=${teamName} owner=${stableOwnerId} target=${assignment.ringIndex}:${assignment.sectorIndex} conflict=${occupiedByConflict[0]}`
        );
        return {};
      }

      return {
        slotLayoutVersion: GRAPH_STABLE_SLOT_LAYOUT_VERSION,
        slotAssignmentsByTeam: {
          ...state.slotAssignmentsByTeam,
          [teamName]: nextAssignments,
        },
        graphLayoutSessionByTeam: {
          ...state.graphLayoutSessionByTeam,
          [teamName]: {
            mode: 'manual',
            signature: state.graphLayoutSessionByTeam[teamName]?.signature ?? null,
          },
        },
      };
    });
  },

  setTeamGraphLayoutMode: (teamName, mode) => {
    set((state) => {
      if ((state.graphLayoutModeByTeam[teamName] ?? DEFAULT_TEAM_GRAPH_LAYOUT_MODE) === mode) {
        return {};
      }

      return {
        graphLayoutModeByTeam: {
          ...state.graphLayoutModeByTeam,
          [teamName]: mode,
        },
      };
    });
  },

  swapTeamGraphGridOwners: (teamName, stableOwnerId, targetStableOwnerId) => {
    if (stableOwnerId === targetStableOwnerId) {
      return;
    }

    set((state) => {
      const teamData = selectTeamDataForName(state, teamName);
      const fallbackVisibleOwnerIds = [...(state.gridOwnerOrderByTeam[teamName] ?? [])];
      for (const ownerId of [stableOwnerId, targetStableOwnerId]) {
        if (!fallbackVisibleOwnerIds.includes(ownerId)) {
          fallbackVisibleOwnerIds.push(ownerId);
        }
      }
      const visibleOwnerIds = teamData
        ? buildTeamGraphDefaultLayoutSeed(teamData.members, teamData.config.members ?? [])
            .orderedVisibleOwnerIds
        : fallbackVisibleOwnerIds;
      const normalizedOrder = normalizeTeamGraphGridOwnerOrder(
        state.gridOwnerOrderByTeam[teamName],
        visibleOwnerIds
      );
      const stableOwnerIndex = normalizedOrder.indexOf(stableOwnerId);
      const targetOwnerIndex = normalizedOrder.indexOf(targetStableOwnerId);

      if (stableOwnerIndex < 0 || targetOwnerIndex < 0) {
        return {};
      }

      const nextOrder = [...normalizedOrder];
      nextOrder[stableOwnerIndex] = targetStableOwnerId;
      nextOrder[targetOwnerIndex] = stableOwnerId;

      return {
        gridOwnerOrderByTeam: {
          ...state.gridOwnerOrderByTeam,
          [teamName]: nextOrder,
        },
      };
    });
  },

  swapTeamGraphOwnerSlots: (teamName, stableOwnerId, otherStableOwnerId) => {
    if (stableOwnerId === otherStableOwnerId) {
      return;
    }

    set((state) => {
      const currentAssignments = state.slotAssignmentsByTeam[teamName] ?? {};
      const left = currentAssignments[stableOwnerId];
      const right = currentAssignments[otherStableOwnerId];
      if (!left || !right) {
        return {};
      }

      return {
        slotLayoutVersion: GRAPH_STABLE_SLOT_LAYOUT_VERSION,
        slotAssignmentsByTeam: {
          ...state.slotAssignmentsByTeam,
          [teamName]: {
            ...currentAssignments,
            [stableOwnerId]: right,
            [otherStableOwnerId]: left,
          },
        },
        graphLayoutSessionByTeam: {
          ...state.graphLayoutSessionByTeam,
          [teamName]: {
            mode: 'manual',
            signature: state.graphLayoutSessionByTeam[teamName]?.signature ?? null,
          },
        },
      };
    });
  },

  clearTeamGraphSlotAssignments: (teamName) => {
    set((state) => {
      if (!teamName) {
        if (
          Object.keys(state.slotAssignmentsByTeam).length === 0 &&
          state.slotLayoutVersion === GRAPH_STABLE_SLOT_LAYOUT_VERSION &&
          Object.keys(state.graphLayoutSessionByTeam).length === 0
        ) {
          return {};
        }
        return {
          slotLayoutVersion: GRAPH_STABLE_SLOT_LAYOUT_VERSION,
          slotAssignmentsByTeam: {},
          graphLayoutSessionByTeam: {},
        };
      }

      if (
        !(teamName in state.slotAssignmentsByTeam) &&
        !(teamName in state.graphLayoutSessionByTeam)
      ) {
        return {};
      }

      const nextAssignmentsByTeam = { ...state.slotAssignmentsByTeam };
      const nextGraphLayoutSessionByTeam = { ...state.graphLayoutSessionByTeam };
      delete nextAssignmentsByTeam[teamName];
      delete nextGraphLayoutSessionByTeam[teamName];
      return {
        slotLayoutVersion: GRAPH_STABLE_SLOT_LAYOUT_VERSION,
        slotAssignmentsByTeam: nextAssignmentsByTeam,
        graphLayoutSessionByTeam: nextGraphLayoutSessionByTeam,
      };
    });
  },

  resetTeamGraphSlotAssignmentsToDefaults: (teamName) => {
    set((state) => {
      if (!DISABLE_PERSISTED_TEAM_GRAPH_SLOT_ASSIGNMENTS) {
        const currentAssignments = state.slotAssignmentsByTeam[teamName];
        if (!currentAssignments || Object.keys(currentAssignments).length === 0) {
          return {};
        }

        const nextAssignmentsByTeam = { ...state.slotAssignmentsByTeam };
        delete nextAssignmentsByTeam[teamName];
        return {
          slotLayoutVersion: GRAPH_STABLE_SLOT_LAYOUT_VERSION,
          slotAssignmentsByTeam: nextAssignmentsByTeam,
        };
      }

      const teamData = selectTeamDataForName(state, teamName);
      const defaultSeed = teamData
        ? buildTeamGraphDefaultLayoutSeed(teamData.members, teamData.config.members ?? [])
        : { orderedVisibleOwnerIds: [], signature: null, assignments: {} };
      const currentAssignments = state.slotAssignmentsByTeam[teamName];
      const currentSession = state.graphLayoutSessionByTeam[teamName];

      if (
        areTeamGraphSlotAssignmentsEqual(currentAssignments, defaultSeed.assignments) &&
        currentSession?.mode === 'default' &&
        currentSession.signature === defaultSeed.signature
      ) {
        return {};
      }

      const nextAssignmentsByTeam = { ...state.slotAssignmentsByTeam };
      if (Object.keys(defaultSeed.assignments).length === 0) {
        delete nextAssignmentsByTeam[teamName];
      } else {
        nextAssignmentsByTeam[teamName] = defaultSeed.assignments;
      }

      return {
        slotLayoutVersion: GRAPH_STABLE_SLOT_LAYOUT_VERSION,
        slotAssignmentsByTeam: nextAssignmentsByTeam,
        graphLayoutSessionByTeam: {
          ...state.graphLayoutSessionByTeam,
          [teamName]: {
            mode: 'default',
            signature: defaultSeed.signature,
          },
        },
      };
    });
  },

  setSelectedTeamTaskChangePresence: (teamName, taskId, presence) => {
    set((state) => {
      const currentTeamData = selectTeamDataForName(state, teamName);
      let cacheChanged = false;
      const nextTeamData = currentTeamData
        ? {
            ...currentTeamData,
            tasks: currentTeamData.tasks.map((task) => {
              if (task.id !== taskId || task.changePresence === presence) {
                return task;
              }
              cacheChanged = true;
              return { ...task, changePresence: presence };
            }),
          }
        : null;

      let globalChanged = false;
      const nextGlobalTasks = state.globalTasks.map((task) => {
        if (task.teamName !== teamName || task.id !== taskId || task.changePresence === presence) {
          return task;
        }
        globalChanged = true;
        return { ...task, changePresence: presence };
      });

      if (!cacheChanged && !globalChanged) {
        return {};
      }

      return {
        ...(cacheChanged && nextTeamData
          ? {
              teamDataCacheByName: {
                ...state.teamDataCacheByName,
                [teamName]: nextTeamData,
              },
            }
          : {}),
        ...(cacheChanged && state.selectedTeamName === teamName && nextTeamData
          ? { selectedTeamData: nextTeamData }
          : {}),
        ...(globalChanged ? { globalTasks: nextGlobalTasks } : {}),
      };
    });
  },

  refreshTeamChangePresence: async (teamName: string) => {
    const currentTeamData = selectTeamDataForName(get(), teamName);
    if (!currentTeamData) {
      return;
    }

    try {
      const presenceByTaskId = await unwrapIpc('team:getTaskChangePresence', () =>
        api.teams.getTaskChangePresence(teamName)
      );

      set((state) => {
        const teamData = selectTeamDataForName(state, teamName);
        if (!teamData) {
          return {};
        }

        let changed = false;
        const nextTasks = teamData.tasks.map((task) => {
          const nextPresence = presenceByTaskId[task.id] ?? 'unknown';
          if (task.changePresence === nextPresence) {
            return task;
          }
          changed = true;
          return { ...task, changePresence: nextPresence };
        });

        if (!changed) {
          return {};
        }

        const nextTeamData = {
          ...teamData,
          tasks: nextTasks,
        };

        return {
          teamDataCacheByName: {
            ...state.teamDataCacheByName,
            [teamName]: nextTeamData,
          },
          ...(state.selectedTeamName === teamName ? { selectedTeamData: nextTeamData } : {}),
        };
      });
    } catch {
      // best-effort lightweight refresh; keep current UI state on failure
    }
  },

  selectTeam: async (teamName: string, opts) => {
    const teamStateEpoch = captureTeamLocalStateEpoch(teamName);
    const allowReloadWhileProvisioning = opts?.allowReloadWhileProvisioning === true;
    // Guard: prevent duplicate in-flight fetches for the same team.
    // GlobalTaskDetailDialog + tab navigation can call selectTeam() in quick succession.
    if (
      get().selectedTeamLoading &&
      get().selectedTeamName === teamName &&
      !allowReloadWhileProvisioning
    ) {
      return;
    }
    const requestNonce = get().selectedTeamLoadNonce + 1;
    const previousData = selectTeamDataForName(get(), teamName);

    cancelPostPaintTeamEnrichments(teamName);

    // Repoint selection synchronously to the new team's cached snapshot when available.
    // Never keep the previous team's snapshot attached to a newly selected team.
    set({
      selectedTeamName: teamName,
      selectedTeamData: previousData,
      selectedTeamLoading: true,
      selectedTeamLoadNonce: requestNonce,
      selectedTeamError: null,
      reviewActionError: null,
      // Load per-team tool approval settings
      toolApprovalSettings: loadToolApprovalSettingsForTeam(teamName),
    });

    try {
      const data = await fetchTeamDataDeduped(teamName, {
        includeMemberBranches: false,
      });
      if (!isTeamLocalStateEpochCurrent(teamName, teamStateEpoch)) {
        queuedFullTeamDataRefreshesAfterThin.delete(teamName);
        return;
      }
      // Stale check: user may have switched to another team during the async call
      const stateAfterLoad = get();
      if (stateAfterLoad.selectedTeamName !== teamName) {
        drainQueuedFullRefreshAfterThinSettles(teamName, get);
        return;
      }
      if (stateAfterLoad.selectedTeamLoadNonce !== requestNonce) {
        return;
      }
      // Eagerly patch teamByName with color/displayName from detailed data
      // so that tab color renders immediately without waiting for fetchTeams()
      const prevByName = get().teamByName;
      const existingEntry = prevByName[teamName];
      const configColor = data.config.color;
      if (configColor && (!existingEntry || existingEntry?.color !== configColor)) {
        const patched: TeamSummary = existingEntry
          ? { ...existingEntry, color: configColor, displayName: data.config.name || teamName }
          : {
              teamName,
              displayName: data.config.name || teamName,
              description: data.config.description ?? '',
              color: configColor,
              memberCount: data.members.length,
              taskCount: 0,
              lastActivity: null,
            };
        set({ teamByName: { ...prevByName, [teamName]: patched } });
      }

      let committedTeamData: TeamViewSnapshot = data;
      set((state) => {
        if (
          state.selectedTeamName === teamName &&
          shouldPreserveSelectedTeamSnapshot(
            state.selectedTeamData,
            previousData,
            data,
            state.teamByName[teamName]
          )
        ) {
          const preservedTeamData = state.selectedTeamData;
          committedTeamData = preservedTeamData ?? data;
          const nextCache =
            preservedTeamData && state.teamDataCacheByName[teamName] !== preservedTeamData
              ? {
                  ...state.teamDataCacheByName,
                  [teamName]: preservedTeamData,
                }
              : state.teamDataCacheByName;

          return {
            selectedTeamName: teamName,
            selectedTeamData: preservedTeamData,
            teamDataCacheByName: nextCache,
            selectedTeamLoading: false,
            selectedTeamError: null,
          };
        }

        const previousForProjection = selectTeamDataForName(state, teamName) ?? previousData;
        const projectedTeamData = previousForProjection
          ? {
              ...data,
              tasks: preserveKnownTaskChangePresence(
                teamName,
                previousForProjection.tasks,
                data.tasks
              ),
            }
          : data;
        const nextTeamData = structurallyShareTeamSnapshot(
          previousForProjection,
          projectedTeamData
        );
        committedTeamData = nextTeamData;
        const nextCache =
          state.teamDataCacheByName[teamName] === nextTeamData
            ? state.teamDataCacheByName
            : {
                ...state.teamDataCacheByName,
                [teamName]: nextTeamData,
              };

        return {
          selectedTeamName: teamName,
          selectedTeamData: nextTeamData,
          teamDataCacheByName: nextCache,
          selectedTeamLoading: false,
          selectedTeamError: null,
        };
      });
      lastResolvedTeamDataRefreshAtByTeam.set(teamName, Date.now());

      try {
        const invalidationState = previousData
          ? collectTaskChangeInvalidationState(
              teamName,
              previousData.tasks,
              committedTeamData.tasks
            )
          : { cacheKeys: [], taskIds: [] };
        if (invalidationState.cacheKeys.length > 0) {
          get().invalidateTaskChangePresence(invalidationState.cacheKeys);
        }
        if (invalidationState.taskIds.length > 0) {
          void api.review
            .invalidateTaskChangeSummaries(teamName, invalidationState.taskIds)
            .catch(() => undefined);
        }

        // Sync tab label with the team's display name from config.
        const displayName = committedTeamData.config.name || teamName;
        const allTabs = get().getAllPaneTabs();
        const relatedTabs = allTabs.filter(
          (tab) => (tab.type === 'team' || tab.type === 'graph') && tab.teamName === teamName
        );
        for (const tab of relatedTabs) {
          const nextLabel = tab.type === 'graph' ? `${displayName} Graph` : displayName;
          if (tab.label !== nextLabel) {
            get().updateTabLabel(tab.id, nextLabel);
          }
        }

        // Auto-select the project associated with this team's cwd/projectPath.
        // Must search both flat projects and grouped repositoryGroups/worktrees
        // because the default viewMode is 'grouped' and flat projects may be empty.
        const projectPath = committedTeamData.config.projectPath;
        if (
          !opts?.skipProjectAutoSelect &&
          projectPath &&
          isSelectedTeamLoadStillCurrent(get, teamName, requestNonce, teamStateEpoch)
        ) {
          const state = get();
          const normalizedTeamPath = normalizePath(projectPath);

          // 1. Try flat projects list
          const matchingProject = state.projects.find(
            (p) => normalizePath(p.path) === normalizedTeamPath
          );
          if (matchingProject && state.selectedProjectId !== matchingProject.id) {
            state.selectProject(matchingProject.id);
          } else if (!matchingProject) {
            // 2. Try grouped view: search worktrees across all repository groups
            for (const repo of state.repositoryGroups) {
              const matchingWorktree = repo.worktrees.find(
                (wt) => normalizePath(wt.path) === normalizedTeamPath
              );
              if (matchingWorktree) {
                if (state.selectedWorktreeId !== matchingWorktree.id) {
                  set(getWorktreeNavigationState(repo.id, matchingWorktree.id));
                  void get().fetchSessionsInitial(matchingWorktree.id);
                }
                break;
              }
            }
          }
        }
      } catch (error) {
        logger.debug(
          `selectTeam(${teamName}) post-structural sync work failed: ${
            error instanceof Error ? error.message : String(error)
          }`
        );
      }

      try {
        schedulePostPaintTeamEnrichments({
          teamName,
          requestNonce,
          teamStateEpoch,
          get,
        });
      } catch (error) {
        logger.debug(
          `selectTeam(${teamName}) failed to schedule post-paint enrichments: ${
            error instanceof Error ? error.message : String(error)
          }`
        );
      }
    } catch (error) {
      if (!isTeamLocalStateEpochCurrent(teamName, teamStateEpoch)) {
        queuedFullTeamDataRefreshesAfterThin.delete(teamName);
        return;
      }
      // If provisioning is in progress for this team, stay in loading state;
      // file watcher / progress callback will refresh once config is written.
      const currentState = get();
      if (currentState.selectedTeamName !== teamName) {
        queuedFullTeamDataRefreshesAfterThin.delete(teamName);
        return;
      }
      if (currentState.selectedTeamLoadNonce !== requestNonce) {
        return;
      }
      queuedFullTeamDataRefreshesAfterThin.delete(teamName);
      const isProvisioning = isTeamProvisioningActive(currentState, teamName);
      const existingSelectedTeamData =
        currentState.selectedTeamData?.teamName === teamName ? currentState.selectedTeamData : null;

      const msg = error instanceof Error ? error.message : String(error);
      // IPC can report provisioning state explicitly.
      if (msg === 'TEAM_PROVISIONING' || (msg.includes('TEAM_PROVISIONING') && isProvisioning)) {
        if (existingSelectedTeamData) {
          set({
            selectedTeamLoading: false,
            selectedTeamData: existingSelectedTeamData,
            selectedTeamError: null,
          });
          return;
        }
        set({
          selectedTeamLoading: true,
          selectedTeamData: null,
          selectedTeamError: null,
        });
        return;
      }

      // Draft team: team.meta.json exists but config.json doesn't (provisioning failed)
      if (msg === 'TEAM_DRAFT' || msg.includes('TEAM_DRAFT')) {
        set({
          selectedTeamLoading: false,
          selectedTeamData: null,
          selectedTeamError: 'TEAM_DRAFT',
        });
        return;
      }

      const message =
        error instanceof IpcError
          ? error.message
          : error instanceof Error
            ? error.message
            : 'Failed to fetch team data';
      if (existingSelectedTeamData) {
        set({
          selectedTeamLoading: false,
          selectedTeamData: existingSelectedTeamData,
          selectedTeamError: null,
        });
        return;
      }
      set({
        selectedTeamLoading: false,
        selectedTeamData: null,
        selectedTeamError: message,
      });
    }
  },

  refreshTeamData: async (teamName: string, opts?: RefreshTeamDataOptions) => {
    const fullKey = getFullTeamDataRequestKey(teamName);
    const reusedInFlightRequest = opts?.withDedup === true && inFlightTeamDataRequests.has(fullKey);
    const queuedBehindThinRequest =
      opts?.withDedup === true && !reusedInFlightRequest && hasThinTeamDataRequestForTeam(teamName);

    if (queuedBehindThinRequest) {
      queuedFullTeamDataRefreshesAfterThin.add(teamName);
      logger.debug(`refreshTeamData(${teamName}) queued behind thin team:getData`);
      return;
    }

    const teamStateEpoch = captureTeamLocalStateEpoch(teamName);
    const refreshToken = beginInFlightTeamDataRefresh(teamName);
    // Silent refresh — update data without showing loading skeleton.
    // Only selectTeam() sets loading: true (for initial load).
    noteTeamRefreshBurst(teamName);
    if (reusedInFlightRequest) {
      pendingFreshTeamDataRefreshes.add(teamName);
    }
    try {
      const previousData = selectTeamDataForName(get(), teamName);
      const data = opts?.withDedup
        ? await fetchTeamDataDeduped(teamName)
        : await fetchTeamDataFresh(teamName);
      if (!isTeamLocalStateEpochCurrent(teamName, teamStateEpoch)) {
        return;
      }
      const projectedTeamData = previousData
        ? {
            ...data,
            tasks: preserveKnownTaskChangePresence(teamName, previousData.tasks, data.tasks),
          }
        : data;
      const nextTeamData = structurallyShareTeamSnapshot(previousData, projectedTeamData);
      set((state) => {
        const nextCache =
          state.teamDataCacheByName[teamName] === nextTeamData
            ? state.teamDataCacheByName
            : {
                ...state.teamDataCacheByName,
                [teamName]: nextTeamData,
              };

        const selectedState =
          state.selectedTeamName === teamName
            ? {
                selectedTeamData: nextTeamData,
                selectedTeamError: null,
              }
            : {};

        if (
          nextCache === state.teamDataCacheByName &&
          (state.selectedTeamName !== teamName ||
            (state.selectedTeamData === nextTeamData && state.selectedTeamError == null))
        ) {
          return {};
        }

        return {
          teamDataCacheByName: nextCache,
          ...selectedState,
        };
      });
      lastResolvedTeamDataRefreshAtByTeam.set(teamName, Date.now());
      const invalidationState = previousData
        ? collectTaskChangeInvalidationState(teamName, previousData.tasks, data.tasks)
        : { cacheKeys: [], taskIds: [] };
      if (invalidationState.cacheKeys.length > 0) {
        get().invalidateTaskChangePresence(invalidationState.cacheKeys);
      }
      if (invalidationState.taskIds.length > 0) {
        await api.review.invalidateTaskChangeSummaries(teamName, invalidationState.taskIds);
      }
    } catch (error) {
      if (!isTeamLocalStateEpochCurrent(teamName, teamStateEpoch)) {
        return;
      }
      const msg =
        error instanceof IpcError
          ? error.message
          : error instanceof Error
            ? error.message
            : 'Failed to refresh team data';

      // During provisioning, team:getData may not be readable yet.
      // Preserve existing data instead of showing a fatal error.
      if (msg === 'TEAM_PROVISIONING' || msg.includes('TEAM_PROVISIONING')) {
        logger.debug(`refreshTeamData(${teamName}) skipped: team is still provisioning`);
        if (get().selectedTeamName === teamName) {
          set({ selectedTeamError: null });
        }
        return;
      }

      if (shouldInvalidateCachedTeamDataForError(teamName, msg)) {
        set((state) => {
          const nextCache = state.teamDataCacheByName[teamName]
            ? { ...state.teamDataCacheByName }
            : null;
          if (nextCache) {
            delete nextCache[teamName];
          }
          if (state.selectedTeamName !== teamName && !nextCache) {
            return {};
          }
          return {
            ...(nextCache ? { teamDataCacheByName: nextCache } : {}),
            ...(state.selectedTeamName === teamName
              ? {
                  selectedTeamLoading: false,
                  selectedTeamData: null,
                  selectedTeamError:
                    msg === 'TEAM_DRAFT' || msg.includes('TEAM_DRAFT') ? 'TEAM_DRAFT' : msg,
                }
              : {}),
          };
        });
        return;
      }

      if (get().selectedTeamName !== teamName) {
        return;
      }

      logger.warn(`refreshTeamData(${teamName}) failed: ${msg}`);

      // Non-destructive: if we already have data, keep it visible.
      // Only set error when there's nothing to show.
      if (get().selectedTeamData) {
        logger.debug(`refreshTeamData(${teamName}) preserving existing data after transient error`);
        set({ selectedTeamError: null });
        return;
      }
      set({ selectedTeamError: msg });
    } finally {
      endInFlightTeamDataRefresh(teamName, refreshToken);
      if (reusedInFlightRequest && pendingFreshTeamDataRefreshes.delete(teamName)) {
        void get().refreshTeamData(teamName);
      }
    }
  },

  refreshTeamMessagesHead: async (teamName: string) => {
    const existingRequest = inFlightTeamMessagesHeadRequests.get(teamName);
    if (existingRequest) {
      pendingFreshTeamMessagesHeadRefreshes.add(teamName);
      return existingRequest;
    }
    const queuedAfterOlder = queuedTeamMessagesHeadRefreshesAfterOlder.get(teamName);
    if (queuedAfterOlder) {
      return queuedAfterOlder;
    }

    const existingOlderRequest = inFlightTeamMessagesOlderRequests.get(teamName);
    if (existingOlderRequest) {
      const queuedEpoch = captureTeamLocalStateEpoch(teamName);
      const queuedRequest: Promise<RefreshTeamMessagesHeadResult> = existingOlderRequest
        .then(() => {
          if (!isTeamLocalStateEpochCurrent(teamName, queuedEpoch)) {
            return {
              feedChanged: false,
              headChanged: false,
              feedRevision: null,
            };
          }
          if (queuedTeamMessagesHeadRefreshesAfterOlder.get(teamName) === queuedRequest) {
            queuedTeamMessagesHeadRefreshesAfterOlder.delete(teamName);
          } else {
            return {
              feedChanged: false,
              headChanged: false,
              feedRevision: null,
            };
          }
          return get().refreshTeamMessagesHead(teamName);
        })
        .finally(() => {
          if (queuedTeamMessagesHeadRefreshesAfterOlder.get(teamName) === queuedRequest) {
            queuedTeamMessagesHeadRefreshesAfterOlder.delete(teamName);
          }
        });
      queuedTeamMessagesHeadRefreshesAfterOlder.set(teamName, queuedRequest);
      return queuedRequest;
    }

    const requestRef: { current: Promise<RefreshTeamMessagesHeadResult> | null } = {
      current: null,
    };
    requestRef.current = (async (): Promise<RefreshTeamMessagesHeadResult> => {
      const teamStateEpoch = captureTeamLocalStateEpoch(teamName);
      set((state) => ({
        teamMessagesByName: {
          ...state.teamMessagesByName,
          [teamName]: {
            ...getTeamMessagesCacheEntry(state, teamName),
            loadingHead: true,
          },
        },
      }));

      try {
        const page = await unwrapIpc('team:getMessagesPage', () =>
          api.teams.getMessagesPage(teamName, { limit: 50 })
        );
        if (!isTeamLocalStateEpochCurrent(teamName, teamStateEpoch)) {
          return {
            feedChanged: false,
            headChanged: false,
            feedRevision: null,
          };
        }

        const previousEntry = getTeamMessagesCacheEntry(get(), teamName);
        const feedChanged =
          !previousEntry.headHydrated || previousEntry.feedRevision !== page.feedRevision;
        const previousHeadSlice = getCanonicalHeadSlice(
          previousEntry.canonicalMessages,
          page.messages.length
        );
        const headChanged = !areInboxMessageArraysEquivalent(previousHeadSlice, page.messages);

        set((state) => {
          const current = getTeamMessagesCacheEntry(state, teamName);
          const retainedOlderTail = extractRetainedCanonicalOlderTail(
            current.canonicalMessages,
            page.messages
          );
          const preserveLoadedOlderTail =
            Array.isArray(retainedOlderTail) && retainedOlderTail.length > 0;
          const nextCanonical = headChanged
            ? preserveLoadedOlderTail
              ? mergeTeamMessages(retainedOlderTail, page.messages)
              : page.messages
            : current.canonicalMessages;
          const nextOptimistic = pruneOptimisticMessages(current.optimisticMessages, nextCanonical);
          const nextEntry: TeamMessagesCacheEntry = {
            ...current,
            canonicalMessages: nextCanonical,
            optimisticMessages: nextOptimistic,
            feedRevision: page.feedRevision,
            nextCursor: preserveLoadedOlderTail ? current.nextCursor : page.nextCursor,
            hasMore: preserveLoadedOlderTail ? current.hasMore : page.hasMore,
            lastFetchedAt: Date.now(),
            loadingHead: false,
            headHydrated: true,
          };
          return {
            teamMessagesByName: {
              ...state.teamMessagesByName,
              [teamName]: nextEntry,
            },
          };
        });

        return {
          feedChanged,
          headChanged,
          feedRevision: page.feedRevision,
        };
      } catch (error) {
        if (!isTeamLocalStateEpochCurrent(teamName, teamStateEpoch)) {
          return {
            feedChanged: false,
            headChanged: false,
            feedRevision: null,
          };
        }
        set((state) => ({
          teamMessagesByName: {
            ...state.teamMessagesByName,
            [teamName]: {
              ...getTeamMessagesCacheEntry(state, teamName),
              loadingHead: false,
            },
          },
        }));
        throw error;
      } finally {
        if (inFlightTeamMessagesHeadRequests.get(teamName) === requestRef.current) {
          inFlightTeamMessagesHeadRequests.delete(teamName);
          if (pendingFreshTeamMessagesHeadRefreshes.delete(teamName)) {
            void get().refreshTeamMessagesHead(teamName);
          }
        }
      }
    })();

    const request = requestRef.current;
    inFlightTeamMessagesHeadRequests.set(teamName, request);
    return request;
  },

  loadOlderTeamMessages: async (teamName: string) => {
    const requestedEpoch = captureTeamLocalStateEpoch(teamName);
    const existingRequest = inFlightTeamMessagesOlderRequests.get(teamName);
    if (existingRequest) {
      return existingRequest;
    }

    const existingHeadRequest = inFlightTeamMessagesHeadRequests.get(teamName);
    if (existingHeadRequest) {
      await existingHeadRequest;
      if (!isTeamLocalStateEpochCurrent(teamName, requestedEpoch)) {
        return;
      }
    }

    let entry = getTeamMessagesCacheEntry(get(), teamName);
    if (!entry.headHydrated) {
      await get().refreshTeamMessagesHead(teamName);
      if (!isTeamLocalStateEpochCurrent(teamName, requestedEpoch)) {
        return;
      }
      entry = getTeamMessagesCacheEntry(get(), teamName);
    }

    if (!entry.headHydrated || !entry.nextCursor || entry.loadingOlder || entry.loadingHead) {
      return;
    }

    const requestRef: { current: Promise<void> | null } = { current: null };
    requestRef.current = (async (): Promise<void> => {
      const teamStateEpoch = captureTeamLocalStateEpoch(teamName);
      set((state) => ({
        teamMessagesByName: {
          ...state.teamMessagesByName,
          [teamName]: {
            ...getTeamMessagesCacheEntry(state, teamName),
            loadingOlder: true,
          },
        },
      }));

      try {
        const baseFeedRevision = entry.feedRevision;
        const page = await unwrapIpc('team:getMessagesPage', () =>
          api.teams.getMessagesPage(teamName, {
            cursor: entry.nextCursor,
            limit: 50,
          })
        );
        if (!isTeamLocalStateEpochCurrent(teamName, teamStateEpoch)) {
          return;
        }

        const current = getTeamMessagesCacheEntry(get(), teamName);
        if (current.feedRevision !== baseFeedRevision) {
          set((state) => ({
            teamMessagesByName: {
              ...state.teamMessagesByName,
              [teamName]: {
                ...getTeamMessagesCacheEntry(state, teamName),
                loadingOlder: false,
              },
            },
          }));
          await get().refreshTeamMessagesHead(teamName);
          return;
        }

        if (current.feedRevision && current.feedRevision !== page.feedRevision) {
          set((state) => ({
            teamMessagesByName: {
              ...state.teamMessagesByName,
              [teamName]: {
                ...getTeamMessagesCacheEntry(state, teamName),
                loadingOlder: false,
              },
            },
          }));
          await get().refreshTeamMessagesHead(teamName);
          return;
        }

        set((state) => {
          const liveEntry = getTeamMessagesCacheEntry(state, teamName);
          const mergedCanonical = mergeTeamMessages(liveEntry.canonicalMessages, page.messages);
          return {
            teamMessagesByName: {
              ...state.teamMessagesByName,
              [teamName]: {
                ...liveEntry,
                canonicalMessages: mergedCanonical,
                nextCursor: page.nextCursor,
                hasMore: page.hasMore,
                feedRevision: page.feedRevision,
                loadingOlder: false,
              },
            },
          };
        });
      } catch {
        if (!isTeamLocalStateEpochCurrent(teamName, teamStateEpoch)) {
          return;
        }
        set((state) => ({
          teamMessagesByName: {
            ...state.teamMessagesByName,
            [teamName]: {
              ...getTeamMessagesCacheEntry(state, teamName),
              loadingOlder: false,
            },
          },
        }));
      } finally {
        if (inFlightTeamMessagesOlderRequests.get(teamName) === requestRef.current) {
          inFlightTeamMessagesOlderRequests.delete(teamName);
        }
      }
    })();

    const request = requestRef.current;
    inFlightTeamMessagesOlderRequests.set(teamName, request);
    return request;
  },

  refreshMemberActivityMeta: async (teamName: string) => {
    const entry = getTeamMessagesCacheEntry(get(), teamName);
    if (!entry.headHydrated) {
      return;
    }

    const existingRequest = inFlightTeamMemberActivityMetaRequests.get(teamName);
    if (existingRequest) {
      pendingFreshTeamMemberActivityMetaRefreshes.add(teamName);
      return existingRequest;
    }

    const requestRef: { current: Promise<void> | null } = { current: null };
    requestRef.current = (async (): Promise<void> => {
      const teamStateEpoch = captureTeamLocalStateEpoch(teamName);
      try {
        const meta = await unwrapIpc('team:getMemberActivityMeta', () =>
          api.teams.getMemberActivityMeta(teamName)
        );
        if (!isTeamLocalStateEpochCurrent(teamName, teamStateEpoch)) {
          return;
        }

        set((state) => {
          const currentFeedRevision = getTeamMessagesCacheEntry(state, teamName).feedRevision;
          if (currentFeedRevision && meta.feedRevision !== currentFeedRevision) {
            return {};
          }
          const existing = state.memberActivityMetaByTeam[teamName];
          if (existing?.feedRevision === meta.feedRevision) {
            return {};
          }
          const sharedMembers = structurallyShareMemberActivityFacts(
            existing?.members,
            meta.members
          );
          const nextMeta =
            existing?.members === sharedMembers &&
            existing.feedRevision === meta.feedRevision &&
            existing.computedAt === meta.computedAt
              ? existing
              : {
                  ...meta,
                  members: sharedMembers,
                };
          return {
            memberActivityMetaByTeam: {
              ...state.memberActivityMetaByTeam,
              [teamName]: nextMeta,
            },
          };
        });
      } catch (error) {
        if (!isTeamLocalStateEpochCurrent(teamName, teamStateEpoch)) {
          return;
        }
        throw error;
      } finally {
        if (inFlightTeamMemberActivityMetaRequests.get(teamName) === requestRef.current) {
          inFlightTeamMemberActivityMetaRequests.delete(teamName);
          if (pendingFreshTeamMemberActivityMetaRefreshes.delete(teamName)) {
            void get().refreshMemberActivityMeta(teamName);
          }
        }
      }
    })();

    const request = requestRef.current;
    inFlightTeamMemberActivityMetaRequests.set(teamName, request);
    return request;
  },

  syncTeamPendingReplyRefresh: (
    teamName: string,
    sourceId: string,
    enabled: boolean,
    delayMs = 10_000
  ) => {
    clearPendingReplyRefreshTimer(teamName);
    const shouldKeepRefreshActive = setPendingReplyRefreshEnabled(teamName, sourceId, enabled);
    if (!shouldKeepRefreshActive) {
      return;
    }

    const timer = setTimeout(() => {
      if (pendingTeamPendingReplyRefreshTimers.get(teamName) !== timer) {
        return;
      }
      pendingTeamPendingReplyRefreshTimers.delete(teamName);
      void (async () => {
        try {
          const headResult = await get().refreshTeamMessagesHead(teamName);
          if (headResult.feedChanged || isMemberActivityMetaStale(get(), teamName)) {
            await get().refreshMemberActivityMeta(teamName);
          }
        } catch {
          // Best-effort delayed refresh while waiting for replies.
        }
      })();
    }, delayMs);

    pendingTeamPendingReplyRefreshTimers.set(teamName, timer);
  },

  updateKanban: async (teamName: string, taskId: string, patch: UpdateKanbanPatch) => {
    try {
      set({ reviewActionError: null });
      await unwrapIpc('team:updateKanban', () => api.teams.updateKanban(teamName, taskId, patch));
      await get().refreshTeamData(teamName);
    } catch (error) {
      set({
        reviewActionError: mapReviewError(error),
      });
      throw error;
    }
  },

  updateKanbanColumnOrder: async (
    teamName: string,
    columnId: KanbanColumnId,
    orderedTaskIds: string[]
  ) => {
    await unwrapIpc('team:updateKanbanColumnOrder', () =>
      api.teams.updateKanbanColumnOrder(teamName, columnId, orderedTaskIds)
    );
    await get().refreshTeamData(teamName);
  },

  sendTeamMessage: async (teamName: string, request: SendMessageRequest) => {
    set({
      sendingMessage: true,
      sendMessageError: null,
      sendMessageWarning: null,
      sendMessageDebugDetails: null,
      lastSendMessageResult: null,
    });
    try {
      const result = await unwrapIpc('team:sendMessage', () =>
        api.teams.sendMessage(teamName, request)
      );
      const runtimeDeliveryFailed = isOpenCodeRuntimeDeliveryHardUxFailure(result.runtimeDelivery);
      const runtimeDeliveryDiagnostics = buildOpenCodeRuntimeDeliveryDiagnostics(result);
      const optimisticMessage: InboxMessage = {
        from: request.from ?? 'user',
        to: request.to ?? request.member,
        text: request.text,
        timestamp: request.timestamp ?? nowIso(),
        read: true,
        taskRefs: request.taskRefs?.length ? request.taskRefs : undefined,
        actionMode: request.actionMode,
        summary: request.summary,
        color: request.color,
        messageId: result.messageId,
        relayOfMessageId: request.relayOfMessageId,
        source: request.source ?? 'user_sent',
        attachments: request.attachments?.length ? request.attachments : undefined,
        leadSessionId: request.leadSessionId,
        conversationId: request.conversationId,
        replyToConversationId: request.replyToConversationId,
        toolSummary: request.toolSummary,
        toolCalls: request.toolCalls,
        messageKind: request.messageKind,
        slashCommand: request.slashCommand,
        commandOutput: request.commandOutput,
      };
      set((state) => ({
        sendingMessage: false,
        sendMessageError: null,
        sendMessageWarning: runtimeDeliveryDiagnostics.warning,
        sendMessageDebugDetails: runtimeDeliveryDiagnostics.debugDetails,
        lastSendMessageResult: runtimeDeliveryFailed ? null : result,
        teamMessagesByName: {
          ...state.teamMessagesByName,
          [teamName]: upsertOptimisticTeamMessage(
            getTeamMessagesCacheEntry(state, teamName),
            optimisticMessage
          ),
        },
      }));
      await get().refreshTeamMessagesHead(teamName);
      return result;
    } catch (error) {
      set({
        sendingMessage: false,
        lastSendMessageResult: null,
        sendMessageWarning: null,
        sendMessageDebugDetails: null,
        sendMessageError: mapSendMessageError(error),
      });
      throw error;
    }
  },

  clearSendMessageRuntimeDiagnostics: (messageId?: string | null) => {
    set((state) => {
      if (messageId && state.sendMessageDebugDetails?.messageId !== messageId) {
        return {};
      }
      if (!state.sendMessageWarning && !state.sendMessageDebugDetails) {
        return {};
      }
      return {
        sendMessageWarning: null,
        sendMessageDebugDetails: null,
      };
    });
  },

  refreshSendMessageRuntimeDeliveryStatus: async (teamName, input) => {
    const normalizedMessageId = typeof input === 'string' ? input.trim() : input.messageId.trim();
    const statusMessageId =
      typeof input === 'string'
        ? normalizedMessageId
        : input.statusMessageId?.trim() || normalizedMessageId;
    if (!normalizedMessageId) return;
    if (get().sendMessageDebugDetails?.messageId !== normalizedMessageId) return;
    let status = await unwrapIpc('team:getOpenCodeRuntimeDeliveryStatus', () =>
      api.teams.getOpenCodeRuntimeDeliveryStatus(teamName, statusMessageId)
    );
    if (!status) return;
    if (statusMessageId !== normalizedMessageId) {
      const blockerUserVisibleState = status.userVisibleImpact?.state;
      const blockerStillChecking =
        blockerUserVisibleState !== undefined
          ? blockerUserVisibleState === 'checking'
          : status.responsePending === true;
      if (!blockerStillChecking) {
        const ownStatus = await unwrapIpc('team:getOpenCodeRuntimeDeliveryStatus', () =>
          api.teams.getOpenCodeRuntimeDeliveryStatus(teamName, normalizedMessageId)
        );
        if (!ownStatus) return;
        status = ownStatus;
      }
    }
    const diagnostics = buildOpenCodeRuntimeDeliveryDiagnostics({
      deliveredToInbox: true,
      messageId: normalizedMessageId,
      runtimeDelivery: status,
    });
    set((state) => {
      if (state.sendMessageDebugDetails?.messageId !== normalizedMessageId) {
        return {};
      }
      return {
        sendMessageWarning: diagnostics.warning,
        sendMessageDebugDetails: diagnostics.debugDetails,
      };
    });
  },

  fetchCrossTeamTargets: async () => {
    set({ crossTeamTargetsLoading: true });
    try {
      const targets = await api.crossTeam.listTargets();
      set({ crossTeamTargets: targets, crossTeamTargetsLoading: false });
    } catch (error) {
      logger.error('fetchCrossTeamTargets failed', error);
      set({ crossTeamTargets: [], crossTeamTargetsLoading: false });
    }
  },

  sendCrossTeamMessage: async (request: CrossTeamSendRequest) => {
    set({
      sendingMessage: true,
      sendMessageError: null,
      sendMessageWarning: null,
      sendMessageDebugDetails: null,
      lastSendMessageResult: null,
    });
    try {
      const result = await api.crossTeam.send(request);
      set({
        sendingMessage: false,
        sendMessageError: null,
        sendMessageWarning: null,
        sendMessageDebugDetails: null,
        lastSendMessageResult: {
          messageId: result.messageId,
          deliveredToInbox: result.deliveredToInbox,
          deduplicated: result.deduplicated,
        },
      });
      await get().refreshTeamMessagesHead(request.fromTeam);
    } catch (error) {
      set({
        sendingMessage: false,
        lastSendMessageResult: null,
        sendMessageWarning: null,
        sendMessageDebugDetails: null,
        sendMessageError: mapSendMessageError(error),
      });
    }
  },

  requestReview: async (teamName: string, taskId: string) => {
    try {
      set({ reviewActionError: null });
      await unwrapIpc('team:requestReview', () => api.teams.requestReview(teamName, taskId));
      await get().refreshTeamData(teamName);
      void refreshTaskChangePresenceForUpdatedTask(get, teamName, taskId);
    } catch (error) {
      set({
        reviewActionError: mapReviewError(error),
      });
      throw error;
    }
  },

  createTeamTask: async (teamName: string, request: CreateTaskRequest) => {
    const task = await unwrapIpc('team:createTask', () => api.teams.createTask(teamName, request));
    await get().refreshTeamData(teamName);
    return task;
  },

  startTask: async (teamName: string, taskId: string) => {
    const result = await unwrapIpc('team:startTask', () => api.teams.startTask(teamName, taskId));
    await get().refreshTeamData(teamName);
    void refreshTaskChangePresenceForUpdatedTask(get, teamName, taskId);
    return result;
  },

  startTaskByUser: async (teamName: string, taskId: string) => {
    const result = await unwrapIpc('team:startTaskByUser', () =>
      api.teams.startTaskByUser(teamName, taskId)
    );
    await get().refreshTeamData(teamName);
    void refreshTaskChangePresenceForUpdatedTask(get, teamName, taskId);
    return result;
  },

  updateTaskStatus: async (teamName: string, taskId: string, status: TeamTaskStatus) => {
    await unwrapIpc('team:updateTaskStatus', () =>
      api.teams.updateTaskStatus(teamName, taskId, status)
    );
    await get().refreshTeamData(teamName);
    void refreshTaskChangePresenceForUpdatedTask(get, teamName, taskId);
  },

  updateTaskOwner: async (teamName: string, taskId: string, owner: string | null) => {
    await unwrapIpc('team:updateTaskOwner', () =>
      api.teams.updateTaskOwner(teamName, taskId, owner)
    );
    await get().refreshTeamData(teamName);
  },

  updateTaskFields: async (
    teamName: string,
    taskId: string,
    fields: { subject?: string; description?: string }
  ) => {
    await unwrapIpc('team:updateTaskFields', () =>
      api.teams.updateTaskFields(teamName, taskId, fields)
    );
    await get().refreshTeamData(teamName);
  },

  addTaskRelationship: async (teamName, taskId, targetId, type) => {
    await unwrapIpc('team:addTaskRelationship', () =>
      api.teams.addTaskRelationship(teamName, taskId, targetId, type)
    );
    await get().refreshTeamData(teamName);
  },

  removeTaskRelationship: async (teamName, taskId, targetId, type) => {
    await unwrapIpc('team:removeTaskRelationship', () =>
      api.teams.removeTaskRelationship(teamName, taskId, targetId, type)
    );
    await get().refreshTeamData(teamName);
  },

  setTaskNeedsClarification: async (teamName, taskId, value) => {
    await unwrapIpc('team:setTaskClarification', () =>
      api.teams.setTaskClarification(teamName, taskId, value)
    );
    await get().refreshTeamData(teamName);
    await get().fetchAllTasks();
  },

  saveTaskAttachment: async (teamName, taskId, file) => {
    const id = crypto.randomUUID();
    await unwrapIpc('team:saveTaskAttachment', () =>
      api.teams.saveTaskAttachment(teamName, taskId, id, file.name, file.type, file.base64)
    );
    await get().refreshTeamData(teamName);
  },

  deleteTaskAttachment: async (teamName, taskId, attachmentId, mimeType) => {
    await unwrapIpc('team:deleteTaskAttachment', () =>
      api.teams.deleteTaskAttachment(teamName, taskId, attachmentId, mimeType)
    );
    await get().refreshTeamData(teamName);
  },

  getTaskAttachmentData: async (teamName, taskId, attachmentId, mimeType) => {
    return unwrapIpc('team:getTaskAttachment', () =>
      api.teams.getTaskAttachment(teamName, taskId, attachmentId, mimeType)
    );
  },

  addTaskComment: async (teamName, taskId, request) => {
    set({ addingComment: true, addCommentError: null });
    try {
      const comment = await unwrapIpc('team:addTaskComment', () =>
        api.teams.addTaskComment(teamName, taskId, request)
      );
      set({ addingComment: false });
      await get().refreshTeamData(teamName);
      return comment;
    } catch (error) {
      const msg = error instanceof Error ? error.message : 'Failed to add comment';
      set({ addingComment: false, addCommentError: msg });
      throw error;
    }
  },

  addMember: async (teamName: string, request: AddMemberRequest) => {
    await unwrapIpc('team:addMember', () => api.teams.addMember(teamName, request));
    await get().refreshTeamData(teamName);
  },

  restartMember: async (teamName: string, memberName: string) => {
    try {
      await unwrapIpc('team:restartMember', () => api.teams.restartMember(teamName, memberName));
    } finally {
      await Promise.allSettled([
        get().refreshTeamMessagesHead(teamName),
        get().fetchMemberSpawnStatuses(teamName),
        get().fetchTeamAgentRuntime(teamName),
      ]);
    }
  },

  retryFailedOpenCodeSecondaryLanes: async (teamName: string) => {
    try {
      return await unwrapIpc('team:retryFailedOpenCodeSecondaryLanes', () =>
        api.teams.retryFailedOpenCodeSecondaryLanes(teamName)
      );
    } finally {
      await Promise.allSettled([
        get().fetchMemberSpawnStatuses(teamName),
        get().fetchTeamAgentRuntime(teamName),
      ]);
    }
  },

  skipMemberForLaunch: async (teamName: string, memberName: string) => {
    try {
      await unwrapIpc('team:skipMemberForLaunch', () =>
        api.teams.skipMemberForLaunch(teamName, memberName)
      );
    } finally {
      await Promise.allSettled([
        get().fetchMemberSpawnStatuses(teamName),
        get().fetchTeamAgentRuntime(teamName),
        get().fetchTeams(),
      ]);
    }
  },

  removeMember: async (teamName: string, memberName: string) => {
    await unwrapIpc('team:removeMember', () => api.teams.removeMember(teamName, memberName));
    await get().refreshTeamData(teamName);
  },

  updateMemberRole: async (teamName: string, memberName: string, role: string | undefined) => {
    await unwrapIpc('team:updateMemberRole', () =>
      api.teams.updateMemberRole(teamName, memberName, role)
    );
    await get().refreshTeamData(teamName);
  },

  softDeleteTask: async (teamName: string, taskId: string) => {
    await unwrapIpc('team:softDeleteTask', () => api.teams.softDeleteTask(teamName, taskId));
    await get().refreshTeamData(teamName);
    await get().fetchDeletedTasks(teamName);
  },

  restoreTask: async (teamName: string, taskId: string) => {
    await unwrapIpc('team:restoreTask', () => api.teams.restoreTask(teamName, taskId));
    await get().refreshTeamData(teamName);
    await get().fetchDeletedTasks(teamName);
  },

  fetchDeletedTasks: async (teamName: string) => {
    set({ deletedTasksLoading: true });
    try {
      const tasks = await unwrapIpc('team:getDeletedTasks', () =>
        api.teams.getDeletedTasks(teamName)
      );
      set({ deletedTasks: tasks, deletedTasksLoading: false });
    } catch (error) {
      logger.error('Failed to fetch deleted tasks:', error);
      set({ deletedTasks: [], deletedTasksLoading: false });
    }
  },

  deleteTeam: async (teamName: string) => {
    await unwrapIpc('team:deleteTeam', () => api.teams.deleteTeam(teamName));
    invalidateTeamLocalStateEpoch(teamName);
    clearPendingReplyRefreshTimer(teamName);
    clearPendingReplyRefreshWaits(teamName);
    clearTeamScopedTransientState(teamName);
    set((state) => {
      const clearedState = collectTeamScopedStateRemovals(state, teamName);
      const tombstones = buildTeamScopedProgressTombstones(state, teamName, nowIso());
      if (state.selectedTeamName === teamName) {
        return {
          selectedTeamName: null,
          selectedTeamData: null,
          selectedTeamLoading: false,
          selectedTeamError: null,
          ...clearedState,
          ...tombstones,
        };
      }
      return {
        ...clearedState,
        ...tombstones,
      };
    });
    await get().fetchTeams();
    await get().fetchAllTasks();
  },

  restoreTeam: async (teamName: string) => {
    await unwrapIpc('team:restoreTeam', () => api.teams.restoreTeam(teamName));
    invalidateTeamLocalStateEpoch(teamName);
    clearPendingReplyRefreshTimer(teamName);
    clearPendingReplyRefreshWaits(teamName);
    clearTeamScopedTransientState(teamName);
    set((state) => {
      const clearedState = collectTeamScopedStateRemovals(state, teamName);
      const tombstones = buildTeamScopedProgressTombstones(state, teamName, nowIso());
      if (Object.keys(clearedState).length === 0) {
        return tombstones;
      }
      return {
        ...clearedState,
        ...tombstones,
      };
    });
    await get().fetchTeams();
    await get().fetchAllTasks();
  },

  permanentlyDeleteTeam: async (teamName: string) => {
    await unwrapIpc('team:permanentlyDeleteTeam', () => api.teams.permanentlyDeleteTeam(teamName));
    invalidateTeamLocalStateEpoch(teamName);
    clearPendingReplyRefreshTimer(teamName);
    clearPendingReplyRefreshWaits(teamName);
    clearTeamScopedTransientState(teamName);
    const state = get();
    const clearedState = collectTeamScopedStateRemovals(state, teamName);
    const tombstones = buildTeamScopedProgressTombstones(state, teamName, nowIso());
    if (state.selectedTeamName === teamName) {
      set({
        selectedTeamName: null,
        selectedTeamData: null,
        selectedTeamError: null,
        ...clearedState,
        ...tombstones,
      });
    } else if (Object.keys(clearedState).length > 0) {
      set({
        ...clearedState,
        ...tombstones,
      });
    } else {
      set(tombstones);
    }
    await get().fetchTeams();
    await get().fetchAllTasks();
  },

  createTeam: async (request: TeamCreateRequest) => {
    // Ensure provisioning progress subscription is active (defensive).
    get().subscribeProvisioningProgress();
    invalidateTeamLocalStateEpoch(request.teamName);
    clearPendingReplyRefreshTimer(request.teamName);
    clearPendingReplyRefreshWaits(request.teamName);
    clearTeamScopedTransientState(request.teamName);

    // Establish a per-team floor so late events from a previous run can't override UI.
    const floor = nowIso();
    set((state) => ({
      provisioningStartedAtFloorByTeam: {
        ...state.provisioningStartedAtFloorByTeam,
        [request.teamName]: floor,
      },
    }));

    // Clear stale provisioning runs for this team so the banner starts fresh
    set((state) => {
      const cleaned = { ...state.provisioningRuns };
      for (const [runId, run] of Object.entries(cleaned)) {
        if (run.teamName === request.teamName) {
          delete cleaned[runId];
        }
      }
      const nextErrors = { ...state.provisioningErrorByTeam };
      delete nextErrors[request.teamName];
      const nextSpawnStatuses = { ...state.memberSpawnStatusesByTeam };
      delete nextSpawnStatuses[request.teamName];
      const nextSpawnSnapshots = { ...state.memberSpawnSnapshotsByTeam };
      delete nextSpawnSnapshots[request.teamName];
      const nextRuntime = { ...state.teamAgentRuntimeByTeam };
      delete nextRuntime[request.teamName];
      const nextActiveTools = { ...state.activeToolsByTeam };
      delete nextActiveTools[request.teamName];
      const nextFinishedVisible = { ...state.finishedVisibleByTeam };
      delete nextFinishedVisible[request.teamName];
      const nextToolHistory = { ...state.toolHistoryByTeam };
      delete nextToolHistory[request.teamName];
      const nextRuntimeRunIdByTeam = { ...state.currentRuntimeRunIdByTeam };
      const previousRuntimeRunId = nextRuntimeRunIdByTeam[request.teamName];
      delete nextRuntimeRunIdByTeam[request.teamName];
      const nextIgnoredRuntimeRunIds = previousRuntimeRunId
        ? {
            ...state.ignoredRuntimeRunIds,
            [previousRuntimeRunId]: request.teamName,
          }
        : state.ignoredRuntimeRunIds;
      const visibleLoadingResets = collectTeamScopedVisibleLoadingResets(state, request.teamName);
      return {
        provisioningRuns: cleaned,
        provisioningErrorByTeam: nextErrors,
        memberSpawnStatusesByTeam: nextSpawnStatuses,
        memberSpawnSnapshotsByTeam: nextSpawnSnapshots,
        teamAgentRuntimeByTeam: nextRuntime,
        activeToolsByTeam: nextActiveTools,
        finishedVisibleByTeam: nextFinishedVisible,
        toolHistoryByTeam: nextToolHistory,
        currentRuntimeRunIdByTeam: nextRuntimeRunIdByTeam,
        ignoredProvisioningRunIds: state.ignoredProvisioningRunIds,
        ignoredRuntimeRunIds: nextIgnoredRuntimeRunIds,
        ...visibleLoadingResets,
      };
    });

    // Optimistic progress entry: ensures banner shows even if IPC progress is delayed/missed.
    const pendingRunId = `pending:${request.teamName}:${Date.now()}`;
    set((state) => ({
      provisioningRuns: {
        ...state.provisioningRuns,
        [pendingRunId]: {
          runId: pendingRunId,
          teamName: request.teamName,
          state: 'spawning',
          message: 'Starting Claude CLI process...',
          startedAt: floor,
          updatedAt: floor,
        },
      },
      currentProvisioningRunIdByTeam: {
        ...state.currentProvisioningRunIdByTeam,
        [request.teamName]: pendingRunId,
      },
      // Synthetic card for the team list — visible until fetchTeams() picks up the real team.
      provisioningSnapshotByTeam: {
        ...state.provisioningSnapshotByTeam,
        [request.teamName]: {
          teamName: request.teamName,
          displayName: request.displayName || request.teamName,
          description: request.description || '',
          color: request.color,
          memberCount: request.members.length,
          members: request.members.map((m) => ({ name: m.name, role: m.role })),
          taskCount: 0,
          lastActivity: null,
          projectPath: request.cwd || undefined,
        },
      },
    }));
    const optimisticLaunchParams = buildLaunchParamsFromRuntimeRequest(request);
    const previousLaunchParams = get().launchParamsByTeam[request.teamName];
    set((state) => ({
      launchParamsByTeam: {
        ...state.launchParamsByTeam,
        [request.teamName]: optimisticLaunchParams,
      },
    }));
    // Initialize per-team tool approval settings based on skipPermissions flag
    const initialSettings: ToolApprovalSettings =
      request.skipPermissions === false
        ? DEFAULT_TOOL_APPROVAL_SETTINGS
        : { ...DEFAULT_TOOL_APPROVAL_SETTINGS, autoAllowAll: true };
    saveToolApprovalSettingsForTeam(request.teamName, initialSettings);
    set({ toolApprovalSettings: initialSettings });
    try {
      if (typeof api.teams.createTeam !== 'function') {
        throw new Error(
          'Current preload version does not support team:create. Restart the dev app.'
        );
      }
      const response = await unwrapIpc('team:create', () => api.teams.createTeam(request));

      saveLaunchParams(request.teamName, optimisticLaunchParams);
      set((state) => ({
        launchParamsByTeam: {
          ...state.launchParamsByTeam,
          [request.teamName]: optimisticLaunchParams,
        },
      }));

      set((state) => {
        const nextRuns = { ...state.provisioningRuns };
        const pendingRun = nextRuns[pendingRunId];
        const realProgressAlreadyExists = response.runId in nextRuns;
        if (pendingRun) {
          delete nextRuns[pendingRunId];
          // Only use pending data as fallback if real progress events haven't arrived yet.
          // This prevents overwriting real progress (e.g. 'assembling') with stale pending data ('spawning')
          // when the invoke response arrives before IPC progress events.
          if (!realProgressAlreadyExists) {
            nextRuns[response.runId] = { ...pendingRun, runId: response.runId };
          }
        }
        return {
          provisioningRuns: nextRuns,
          currentProvisioningRunIdByTeam: {
            ...state.currentProvisioningRunIdByTeam,
            [request.teamName]: response.runId,
          },
          currentRuntimeRunIdByTeam: {
            ...state.currentRuntimeRunIdByTeam,
            [request.teamName]: response.runId,
          },
        };
      });
      try {
        await get().getProvisioningStatus(response.runId);
      } catch {
        // ignore — polling below will retry
      }
      void pollProvisioningStatus(get, response.runId);
      return response.runId;
    } catch (error) {
      const message =
        error instanceof IpcError
          ? error.message
          : error instanceof Error
            ? error.message
            : 'Failed to create team';
      set((state) => {
        const nextRuns = { ...state.provisioningRuns };
        delete nextRuns[pendingRunId];
        const nextCurrentRunIdByTeam = { ...state.currentProvisioningRunIdByTeam };
        if (nextCurrentRunIdByTeam[request.teamName] === pendingRunId) {
          delete nextCurrentRunIdByTeam[request.teamName];
        }
        const nextLaunchParamsByTeam = { ...state.launchParamsByTeam };
        if (
          areTeamLaunchParamsEqual(nextLaunchParamsByTeam[request.teamName], optimisticLaunchParams)
        ) {
          if (previousLaunchParams) {
            nextLaunchParamsByTeam[request.teamName] = previousLaunchParams;
          } else {
            delete nextLaunchParamsByTeam[request.teamName];
          }
        }
        return {
          provisioningRuns: nextRuns,
          currentProvisioningRunIdByTeam: nextCurrentRunIdByTeam,
          launchParamsByTeam: nextLaunchParamsByTeam,
          provisioningErrorByTeam: {
            ...state.provisioningErrorByTeam,
            [request.teamName]: message,
          },
        };
      });
      throw error;
    }
  },

  launchTeam: async (request: TeamLaunchRequest) => {
    // Ensure provisioning progress subscription is active (defensive).
    get().subscribeProvisioningProgress();
    invalidateTeamLocalStateEpoch(request.teamName);
    clearPendingReplyRefreshTimer(request.teamName);
    clearPendingReplyRefreshWaits(request.teamName);
    clearTeamScopedTransientState(request.teamName);

    // Establish a per-team floor so late events from a previous run can't override UI.
    const floor = nowIso();
    set((state) => ({
      provisioningStartedAtFloorByTeam: {
        ...state.provisioningStartedAtFloorByTeam,
        [request.teamName]: floor,
      },
    }));

    // Clear stale provisioning runs for this team so the banner starts fresh
    set((state) => {
      const cleaned = { ...state.provisioningRuns };
      for (const [runId, run] of Object.entries(cleaned)) {
        if (run.teamName === request.teamName) {
          delete cleaned[runId];
        }
      }
      const nextErrors = { ...state.provisioningErrorByTeam };
      delete nextErrors[request.teamName];
      const nextSpawnStatuses = { ...state.memberSpawnStatusesByTeam };
      delete nextSpawnStatuses[request.teamName];
      const nextSpawnSnapshots = { ...state.memberSpawnSnapshotsByTeam };
      delete nextSpawnSnapshots[request.teamName];
      const nextRuntime = { ...state.teamAgentRuntimeByTeam };
      delete nextRuntime[request.teamName];
      const nextActiveTools = { ...state.activeToolsByTeam };
      delete nextActiveTools[request.teamName];
      const nextFinishedVisible = { ...state.finishedVisibleByTeam };
      delete nextFinishedVisible[request.teamName];
      const nextToolHistory = { ...state.toolHistoryByTeam };
      delete nextToolHistory[request.teamName];
      const nextRuntimeRunIdByTeam = { ...state.currentRuntimeRunIdByTeam };
      const previousRuntimeRunId = nextRuntimeRunIdByTeam[request.teamName];
      delete nextRuntimeRunIdByTeam[request.teamName];
      const nextIgnoredRuntimeRunIds = previousRuntimeRunId
        ? {
            ...state.ignoredRuntimeRunIds,
            [previousRuntimeRunId]: request.teamName,
          }
        : state.ignoredRuntimeRunIds;
      const visibleLoadingResets = collectTeamScopedVisibleLoadingResets(state, request.teamName);
      return {
        provisioningRuns: cleaned,
        provisioningErrorByTeam: nextErrors,
        memberSpawnStatusesByTeam: nextSpawnStatuses,
        memberSpawnSnapshotsByTeam: nextSpawnSnapshots,
        teamAgentRuntimeByTeam: nextRuntime,
        activeToolsByTeam: nextActiveTools,
        finishedVisibleByTeam: nextFinishedVisible,
        toolHistoryByTeam: nextToolHistory,
        currentRuntimeRunIdByTeam: nextRuntimeRunIdByTeam,
        ignoredProvisioningRunIds: state.ignoredProvisioningRunIds,
        ignoredRuntimeRunIds: nextIgnoredRuntimeRunIds,
        ...visibleLoadingResets,
      };
    });

    // Optimistic progress entry: ensures banner shows even if IPC progress is delayed/missed.
    const pendingRunId = `pending:${request.teamName}:${Date.now()}`;
    set((state) => ({
      provisioningRuns: {
        ...state.provisioningRuns,
        [pendingRunId]: {
          runId: pendingRunId,
          teamName: request.teamName,
          state: 'spawning',
          message: 'Starting Claude CLI process...',
          startedAt: floor,
          updatedAt: floor,
        },
      },
      currentProvisioningRunIdByTeam: {
        ...state.currentProvisioningRunIdByTeam,
        [request.teamName]: pendingRunId,
      },
    }));
    const previousLaunchParams = get().launchParamsByTeam[request.teamName];
    const optimisticLaunchParams = buildLaunchParamsFromRuntimeRequest(
      request,
      previousLaunchParams
    );
    set((state) => ({
      launchParamsByTeam: {
        ...state.launchParamsByTeam,
        [request.teamName]: optimisticLaunchParams,
      },
    }));
    // Initialize per-team tool approval settings based on skipPermissions flag
    {
      const launchSettings: ToolApprovalSettings =
        request.skipPermissions === false
          ? DEFAULT_TOOL_APPROVAL_SETTINGS
          : { ...DEFAULT_TOOL_APPROVAL_SETTINGS, autoAllowAll: true };
      saveToolApprovalSettingsForTeam(request.teamName, launchSettings);
      set({ toolApprovalSettings: launchSettings });
    }
    try {
      const response = await unwrapIpc('team:launch', () => api.teams.launchTeam(request));

      saveLaunchParams(request.teamName, optimisticLaunchParams);
      set((state) => ({
        launchParamsByTeam: {
          ...state.launchParamsByTeam,
          [request.teamName]: optimisticLaunchParams,
        },
      }));

      set((state) => {
        const nextRuns = { ...state.provisioningRuns };
        const pendingRun = nextRuns[pendingRunId];
        const realProgressAlreadyExists = response.runId in nextRuns;
        if (pendingRun) {
          delete nextRuns[pendingRunId];
          // Only use pending data as fallback if real progress events haven't arrived yet.
          // This prevents overwriting real progress (e.g. 'assembling') with stale pending data ('spawning')
          // when the invoke response arrives before IPC progress events.
          if (!realProgressAlreadyExists) {
            nextRuns[response.runId] = { ...pendingRun, runId: response.runId };
          }
        }
        return {
          provisioningRuns: nextRuns,
          currentProvisioningRunIdByTeam: {
            ...state.currentProvisioningRunIdByTeam,
            [request.teamName]: response.runId,
          },
          currentRuntimeRunIdByTeam: {
            ...state.currentRuntimeRunIdByTeam,
            [request.teamName]: response.runId,
          },
        };
      });
      try {
        await get().getProvisioningStatus(response.runId);
      } catch {
        // ignore — polling below will retry
      }
      void pollProvisioningStatus(get, response.runId);
      return response.runId;
    } catch (error) {
      const message =
        error instanceof IpcError
          ? error.message
          : error instanceof Error
            ? error.message
            : 'Failed to launch team';
      set((state) => {
        const nextRuns = { ...state.provisioningRuns };
        delete nextRuns[pendingRunId];
        const nextCurrentRunIdByTeam = { ...state.currentProvisioningRunIdByTeam };
        if (nextCurrentRunIdByTeam[request.teamName] === pendingRunId) {
          delete nextCurrentRunIdByTeam[request.teamName];
        }
        const nextLaunchParamsByTeam = { ...state.launchParamsByTeam };
        if (
          areTeamLaunchParamsEqual(nextLaunchParamsByTeam[request.teamName], optimisticLaunchParams)
        ) {
          if (previousLaunchParams) {
            nextLaunchParamsByTeam[request.teamName] = previousLaunchParams;
          } else {
            delete nextLaunchParamsByTeam[request.teamName];
          }
        }
        return {
          provisioningRuns: nextRuns,
          currentProvisioningRunIdByTeam: nextCurrentRunIdByTeam,
          launchParamsByTeam: nextLaunchParamsByTeam,
          provisioningErrorByTeam: {
            ...state.provisioningErrorByTeam,
            [request.teamName]: message,
          },
        };
      });
      throw error;
    }
  },

  getProvisioningStatus: async (runId: string) => {
    const progress = await unwrapIpc('team:provisioningStatus', () =>
      api.teams.getProvisioningStatus(runId)
    );
    get().onProvisioningProgress(progress);
    return progress;
  },

  clearMissingProvisioningRun: (runId: string) => {
    set((state) => {
      const existing = state.provisioningRuns[runId];
      if (!existing) {
        return {};
      }

      const nextRuns = { ...state.provisioningRuns };
      delete nextRuns[runId];

      const nextCurrentRunIdByTeam = { ...state.currentProvisioningRunIdByTeam };
      const isCanonicalRun = nextCurrentRunIdByTeam[existing.teamName] === runId;
      if (isCanonicalRun) {
        delete nextCurrentRunIdByTeam[existing.teamName];
      }
      const nextRuntimeRunIdByTeam = { ...state.currentRuntimeRunIdByTeam };
      if (nextRuntimeRunIdByTeam[existing.teamName] === runId) {
        delete nextRuntimeRunIdByTeam[existing.teamName];
      }
      const nextIgnoredRunIds = {
        ...state.ignoredProvisioningRunIds,
        [runId]: existing.teamName,
      };
      const nextIgnoredRuntimeRunIds =
        state.currentRuntimeRunIdByTeam[existing.teamName] === runId
          ? {
              ...state.ignoredRuntimeRunIds,
              [runId]: existing.teamName,
            }
          : state.ignoredRuntimeRunIds;

      const nextSpawnStatuses = { ...state.memberSpawnStatusesByTeam };
      const nextSpawnSnapshots = { ...state.memberSpawnSnapshotsByTeam };
      const nextRuntime = { ...state.teamAgentRuntimeByTeam };
      if (isCanonicalRun) {
        delete nextSpawnStatuses[existing.teamName];
        delete nextSpawnSnapshots[existing.teamName];
        delete nextRuntime[existing.teamName];
      }
      const nextActiveTools = { ...state.activeToolsByTeam };
      const nextFinishedVisible = { ...state.finishedVisibleByTeam };
      const nextToolHistory = { ...state.toolHistoryByTeam };
      if (isCanonicalRun) {
        delete nextActiveTools[existing.teamName];
        delete nextFinishedVisible[existing.teamName];
        delete nextToolHistory[existing.teamName];
      }

      return {
        provisioningRuns: nextRuns,
        currentProvisioningRunIdByTeam: nextCurrentRunIdByTeam,
        currentRuntimeRunIdByTeam: nextRuntimeRunIdByTeam,
        memberSpawnStatusesByTeam: nextSpawnStatuses,
        memberSpawnSnapshotsByTeam: nextSpawnSnapshots,
        teamAgentRuntimeByTeam: nextRuntime,
        activeToolsByTeam: nextActiveTools,
        finishedVisibleByTeam: nextFinishedVisible,
        toolHistoryByTeam: nextToolHistory,
        ignoredProvisioningRunIds: nextIgnoredRunIds,
        ignoredRuntimeRunIds: nextIgnoredRuntimeRunIds,
      };
    });
  },

  cancelProvisioning: async (runId: string) => {
    await unwrapIpc('team:cancelProvisioning', () => api.teams.cancelProvisioning(runId));
  },

  onProvisioningProgress: (progress: TeamProvisioningProgress) => {
    if (get().ignoredProvisioningRunIds[progress.runId] === progress.teamName) {
      return;
    }
    if (get().ignoredRuntimeRunIds[progress.runId] === progress.teamName) {
      return;
    }

    const floor = get().provisioningStartedAtFloorByTeam[progress.teamName];
    if (floor && progress.startedAt < floor) {
      // Ignore late progress from a previous run (common after stop→launch).
      return;
    }

    const currentRunId = get().currentProvisioningRunIdByTeam[progress.teamName];
    const existingProgress = get().provisioningRuns[progress.runId];
    const becameConfigReady =
      progress.configReady === true && existingProgress?.configReady !== true;
    const isDuplicateProgress =
      existingProgress?.updatedAt === progress.updatedAt &&
      existingProgress?.state === progress.state &&
      existingProgress?.message === progress.message &&
      existingProgress?.error === progress.error &&
      existingProgress?.pid === progress.pid;
    if (isDuplicateProgress && currentRunId === progress.runId) {
      return;
    }
    if (
      existingProgress &&
      currentRunId === progress.runId &&
      shouldIgnoreProvisioningProgressRegression(existingProgress.state, progress.state)
    ) {
      return;
    }

    set((state) => {
      const nextRuns: Record<string, TeamProvisioningProgress> = {
        ...state.provisioningRuns,
      };
      const nextCurrentRunIdByTeam = { ...state.currentProvisioningRunIdByTeam };
      const previousCurrentRunId = nextCurrentRunIdByTeam[progress.teamName];
      let isCanonicalRun = false;
      if (!previousCurrentRunId || previousCurrentRunId === progress.runId) {
        nextCurrentRunIdByTeam[progress.teamName] = progress.runId;
        isCanonicalRun = true;
      } else if (
        isPendingProvisioningRunId(previousCurrentRunId) &&
        !isPendingProvisioningRunId(progress.runId)
      ) {
        delete nextRuns[previousCurrentRunId];
        nextCurrentRunIdByTeam[progress.teamName] = progress.runId;
        isCanonicalRun = true;
      }
      if (!previousCurrentRunId) {
        isCanonicalRun = true;
      }
      if (!isCanonicalRun) {
        if (!(progress.runId in state.provisioningRuns)) {
          return {};
        }
        delete nextRuns[progress.runId];
        return { provisioningRuns: nextRuns };
      }

      nextRuns[progress.runId] = progress;
      for (const [runId, run] of Object.entries(nextRuns)) {
        if (runId !== progress.runId && run.teamName === progress.teamName) {
          delete nextRuns[runId];
        }
      }

      const nextErrors = { ...state.provisioningErrorByTeam };
      if (progress.state === 'failed') {
        nextErrors[progress.teamName] = progress.error ?? progress.message;
      } else {
        delete nextErrors[progress.teamName];
      }
      // Clean up provisioning snapshot on terminal failure states
      const nextSnapshots =
        progress.state === 'failed' || progress.state === 'cancelled'
          ? (() => {
              const s = { ...state.provisioningSnapshotByTeam };
              delete s[progress.teamName];
              return s;
            })()
          : state.provisioningSnapshotByTeam;
      return {
        provisioningRuns: nextRuns,
        currentProvisioningRunIdByTeam: nextCurrentRunIdByTeam,
        currentRuntimeRunIdByTeam: {
          ...state.currentRuntimeRunIdByTeam,
          [progress.teamName]: progress.runId,
        },
        provisioningErrorByTeam: nextErrors,
        provisioningSnapshotByTeam: nextSnapshots,
      };
    });

    const isCanonicalRun =
      get().currentProvisioningRunIdByTeam[progress.teamName] === progress.runId;
    let hydratedVisibleTeam = false;

    if (isCanonicalRun && becameConfigReady) {
      const state = get();
      if (isVisibleInActiveTeamSurface(state, progress.teamName)) {
        const willSelectTeam =
          state.selectedTeamName === progress.teamName && state.selectedTeamData == null;
        noteTeamRefreshFanout({
          teamName: progress.teamName,
          surface: 'provisioning-progress',
          phase: 'scheduled',
          reason: 'provisioning:config-ready',
          operation: willSelectTeam ? 'selectTeam' : 'refreshTeamData',
          selected: state.selectedTeamName === progress.teamName,
          visible: true,
        });
        if (state.selectedTeamName === progress.teamName && state.selectedTeamData == null) {
          void state.selectTeam(progress.teamName, { allowReloadWhileProvisioning: true });
        } else {
          void state.refreshTeamData(progress.teamName, { withDedup: true });
        }
        hydratedVisibleTeam = true;
      }
    }

    if (isCanonicalRun && TERMINAL_PROVISIONING_STATES.has(progress.state)) {
      set((prev) => {
        const next = { ...prev.memberSpawnStatusesByTeam };
        const nextSnapshots = { ...prev.memberSpawnSnapshotsByTeam };
        const nextRuntime = { ...prev.teamAgentRuntimeByTeam };
        const currentStatuses = next[progress.teamName];
        if (!currentStatuses) {
          if (progress.state !== 'ready') {
            delete nextRuntime[progress.teamName];
          }
          return {
            memberSpawnStatusesByTeam: next,
            memberSpawnSnapshotsByTeam: nextSnapshots,
            teamAgentRuntimeByTeam: nextRuntime,
          };
        }
        if (progress.state === 'ready') {
          next[progress.teamName] = currentStatuses;
          return {
            memberSpawnStatusesByTeam: next,
            memberSpawnSnapshotsByTeam: nextSnapshots,
            teamAgentRuntimeByTeam: nextRuntime,
          };
        }
        const retainedStatuses = Object.fromEntries(
          Object.entries(currentStatuses).filter(([, entry]) => entry.status === 'error')
        );
        if (Object.keys(retainedStatuses).length > 0) {
          next[progress.teamName] = retainedStatuses;
        } else {
          delete next[progress.teamName];
          delete nextSnapshots[progress.teamName];
        }
        delete nextRuntime[progress.teamName];
        return {
          memberSpawnStatusesByTeam: next,
          memberSpawnSnapshotsByTeam: nextSnapshots,
          teamAgentRuntimeByTeam: nextRuntime,
        };
      });
    }

    if (isCanonicalRun && (progress.state === 'ready' || progress.state === 'disconnected')) {
      const terminalReason =
        progress.state === 'ready'
          ? 'provisioning:terminal-ready'
          : 'provisioning:terminal-disconnected';
      noteTeamRefreshFanout({
        teamName: progress.teamName,
        surface: 'provisioning-progress',
        phase: 'scheduled',
        reason: terminalReason,
        operation: 'fetchTeams',
      });
      void get().fetchTeams();
      if (hydratedVisibleTeam) {
        noteTeamRefreshFanout({
          teamName: progress.teamName,
          surface: 'provisioning-progress',
          phase: 'skipped',
          reason: 'provisioning:already-hydrated-visible-team',
          operation: 'refreshTeamData',
          visible: true,
        });
        return;
      }

      const state = get();
      if (!isVisibleInActiveTeamSurface(state, progress.teamName)) {
        return;
      }

      // If the user already opened the team tab, reload team data now that
      // config.json is guaranteed to exist.
      noteTeamRefreshFanout({
        teamName: progress.teamName,
        surface: 'provisioning-progress',
        phase: 'scheduled',
        reason: terminalReason,
        operation: state.selectedTeamName === progress.teamName ? 'selectTeam' : 'refreshTeamData',
        selected: state.selectedTeamName === progress.teamName,
        visible: true,
      });
      if (state.selectedTeamName === progress.teamName) {
        void state.selectTeam(progress.teamName);
      } else {
        void state.refreshTeamData(progress.teamName, { withDedup: true });
      }
    }
  },

  subscribeProvisioningProgress: () => {
    const existing = get().provisioningProgressUnsubscribe;
    if (existing) {
      return;
    }
    if (!api.teams?.onProvisioningProgress) {
      return;
    }
    const unsubscribe = api.teams.onProvisioningProgress((_event, progress) => {
      get().onProvisioningProgress(progress);
    });
    set({ provisioningProgressUnsubscribe: unsubscribe });
  },

  updateToolApprovalSettings: async (patch, forTeam) => {
    const teamName = forTeam ?? get().selectedTeamName;
    const current = get().toolApprovalSettings;
    const merged = { ...current, ...patch };
    set({ toolApprovalSettings: merged });
    // Save per-team if a team is selected, otherwise global fallback
    if (teamName) {
      saveToolApprovalSettingsForTeam(teamName, merged);
    } else {
      localStorage.setItem('team:toolApprovalSettings', JSON.stringify(merged));
    }
    try {
      await api.teams.updateToolApprovalSettings(teamName ?? '__global__', merged);
    } catch (err) {
      logger.warn('Failed to sync tool approval settings to main:', err);
    }
  },

  respondToToolApproval: async (teamName, runId, requestId, allow, message) => {
    try {
      await api.teams.respondToToolApproval(teamName, runId, requestId, allow, message);
      // Remove ONLY after successful IPC, by runId+requestId pair
      set((s) => {
        const next = new Map(s.resolvedApprovals);
        next.set(requestId, allow);
        return {
          pendingApprovals: s.pendingApprovals.filter(
            (a) => !(a.runId === runId && a.requestId === requestId)
          ),
          resolvedApprovals: next,
        };
      });
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      logger.error(`respondToToolApproval failed for ${teamName}/${requestId}: ${msg}`);
      // Surface the error so ToolApprovalSheet can show feedback
      throw err;
    }
  },

  unsubscribeProvisioningProgress: () => {
    const unsubscribe = get().provisioningProgressUnsubscribe;
    if (unsubscribe) {
      unsubscribe();
      set({ provisioningProgressUnsubscribe: null });
    }
  },
});
