import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
import { emit, listen } from "@tauri-apps/api/event";
import { mockIPC, mockWindows } from "@tauri-apps/api/mocks";
import { decode, npubEncode, nsecEncode } from "nostr-tools/nip19";
import { finalizeEvent, getPublicKey } from "nostr-tools/pure";
import { parse as yamlParse } from "yaml";
import {
  mergeMockCustomHarnesses,
  handleSaveCustomHarness,
  handleDeleteCustomHarness,
} from "./e2eBridgeCustomHarnesses.ts";

import { relayClient } from "@/shared/api/relayClient";
import { activateRateLimit } from "@/shared/api/relayRateLimitGate";
import { resolveAgentParallelism } from "@/features/agents/lib/agentParallelism";
import type { ConnectionState } from "@/shared/api/relayClientShared";
import type { ChannelTemplate, RelayEvent } from "@/shared/api/types";
import { getMarkdownParseCount } from "@/shared/ui/markdown/nodeCache";
import { syncAgentTurnsFromEvents } from "@/features/agents/activeAgentTurnsStore";
import { recordTimeoutFromRejection } from "@/features/moderation/lib/timeoutStore";
import {
  injectObserverEventsForE2E,
  syncAgentObserverEvents,
} from "@/features/agents/observerRelayStore";
import {
  CUSTOM_EMOJI_SET_D_TAG,
  KIND_EMOJI_SET,
} from "@/shared/api/customEmoji";
import {
  KIND_AGENT_OBSERVER_FRAME,
  KIND_CHANNEL_THREAD_SUMMARY,
  KIND_CHANNEL_WINDOW_BOUNDS,
  KIND_DM_VISIBILITY,
  KIND_EVENT_REMINDER,
  KIND_GIT_ISSUE,
  KIND_GIT_PATCH,
  KIND_GIT_PR_UPDATE,
  KIND_GIT_PULL_REQUEST,
  KIND_GIT_STATUS_CLOSED,
  KIND_GIT_STATUS_DRAFT,
  KIND_GIT_STATUS_MERGED,
  KIND_GIT_STATUS_OPEN,
  KIND_HUDDLE_STARTED,
  KIND_MEMBER_ADDED_NOTIFICATION,
  KIND_MEMBER_REMOVED_NOTIFICATION,
  KIND_PERSONA,
  KIND_PROJECT_ANNOUNCEMENT,
  KIND_REPO_ANNOUNCEMENT,
  KIND_REPO_STATE,
  KIND_STREAM_MESSAGE_EDIT,
  KIND_SYSTEM_MESSAGE,
  KIND_TEXT_NOTE,
  KIND_USER_STATUS,
} from "@/shared/constants/kinds";
import type {
  RawAcpAuthMethodsResult,
  RawConnectAcpRuntimeResult,
} from "@/shared/api/tauriAgentAuth";
import type {
  RawAcpRuntimeCatalogEntry,
  RawInstallRuntimeResult,
  RuntimeFileConfigSubset,
} from "@/shared/api/tauri";
import {
  ensureRelayOriginFetch,
  resetMediaCaches,
} from "@/shared/lib/mediaUrl";
import { normalizePubkey } from "@/shared/lib/pubkey";
import {
  isValidLinkPreviewSnapshotCanonicalUrl,
  parseLinkPreviewSnapshots,
} from "@/shared/lib/linkPreviewSnapshot";

type TestIdentity = {
  privateKey: string;
  pubkey: string;
  username: string;
};

type MockCommandAvailability = {
  available?: boolean;
  command?: string;
  resolvedPath?: string | null;
};

export type MockManagedAgentSeed = {
  pubkey: string;
  name: string;
  avatarUrl?: string | null;
  personaId?: string | null;
  /** Harness/runtime id pin; `null` = inherit from persona (native default). */
  runtime?: string | null;
  status?: RawManagedAgent["status"];
  channelNames?: string[];
  channelIds?: string[];
  backend?: RawManagedAgent["backend"];
  lastError?: string | null;
  lastErrorCode?: number | null;
  needsRestart?: boolean;
  restartDiff?: Array<{ field: string; change: unknown }>;
  autoRestartOnConfigChange?: boolean;
  respondTo?: RawManagedAgent["respond_to"];
  respondToAllowlist?: string[];
  /** Per-agent env vars seeded into the mock store. */
  envVars?: Record<string, string>;
};

type MockManagedAgentRuntimeSeed = {
  pubkey: string;
  relayUrl: string;
  lifecycle?: MockManagedAgentRuntimeRow["lifecycle"];
};

type MockRelayAgentSeed = {
  pubkey: string;
  name: string;
  agentType?: string;
  capabilities?: string[];
  respondTo?: RawRelayAgent["respond_to"];
  respondToAllowlist?: string[];
  channelNames?: string[];
  channelIds?: string[];
  status?: PresenceStatus;
};

type MockPersonaSeed = {
  id?: string;
  displayName: string;
  avatarUrl?: string | null;
  systemPrompt: string;
  updatedAt?: string;
  isActive?: boolean;
  shared?: boolean;
  sourceTeam?: string | null;
  envVars?: Record<string, string>;
  runtime?: string | null;
  model?: string | null;
  provider?: string | null;
  namePool?: string[];
  respondTo?: "owner-only" | "allowlist" | "anyone";
  respondToAllowlist?: string[];
};

type MockTeamSeed = {
  id?: string;
  name: string;
  description?: string | null;
  personaIds: string[];
};

type MockSearchProfileSeed = {
  pubkey: string;
  displayName: string | null;
  avatarUrl?: string | null;
  nip05Handle?: string | null;
  about?: string | null;
  ownerPubkey?: string | null;
  isAgent?: boolean;
};

type MockHuddleMemberSeed = {
  pubkey: string;
  role: "owner" | "admin" | "member" | "guest" | "bot";
};

type MockHuddleSeed = {
  parentChannelId: string;
  ephemeralChannelId: string;
  huddleThreadEventId?: string | null;
  phase?: "creating" | "connected" | "active";
  members: MockHuddleMemberSeed[];
  transcriptionEnabled?: boolean;
  ttsEnabled?: boolean;
  isCreator?: boolean;
};

type E2eConfig = {
  mode?: "mock" | "relay";
  mock?: {
    /** Tauri window label exposed to the app. Defaults to the main window. */
    windowLabel?: string;
    ttsSettings?: {
      version: number;
      agentTextToSpeech: boolean;
      voicePreferences: string[];
    };
    /** Native picker boundary result for Pocket voice import tests. */
    pocketVoiceImportResult?: "success" | "cancel" | "invalid";
    /** Advertised HEAD for the first mock project without adding that branch. */
    projectHeadBranch?: string;
    /** Builderlab account returned by hosted-community onboarding. Null/omitted = signed out. */
    builderlabAuth?: {
      email?: string;
      name?: string;
      expiresAt: string;
    } | null;
    /** Optional policy returned by the native join-policy discovery command. */
    joinPolicy?: {
      terms_markdown?: string;
      privacy_markdown?: string;
      age_attestation_required: boolean;
      version: string;
    } | null;
    /** Delay Builderlab login completion so cancellation/retry UI can be tested. */
    builderlabLoginDelayMs?: number;
    /** Bound Builderlab Nostr identity. Null/omitted = not linked yet. */
    builderlabIdentity?: { npub?: string; pubkey_hex?: string } | null;
    /** Structured error returned when onboarding tries to bind the local identity. */
    builderlabBindError?: { code?: string; message?: string };
    /** Communities owned by the mocked Builderlab account. */
    builderlabCommunities?: Array<{
      id?: string;
      name?: string;
      slug?: string;
      normalized_host?: string;
      archived_at?: string | null;
    }>;
    /** Override the community returned after hosted creation. */
    builderlabCreatedCommunity?: {
      id?: string;
      name?: string;
      slug?: string;
      normalized_host?: string;
      archived_at?: string | null;
    };
    acpRuntimesCatalog?: RawAcpRuntimeCatalogEntry[];
    /** Catalog returned after a successful mocked install. */
    acpRuntimesCatalogAfterInstall?: RawAcpRuntimeCatalogEntry[];
    /** Catalog responses after install for testing later sign-in completion. */
    acpRuntimesCatalogAfterInstallSequence?: RawAcpRuntimeCatalogEntry[][];
    /** Catalog responses for successive discovery calls. The final response repeats. */
    acpRuntimesCatalogSequence?: RawAcpRuntimeCatalogEntry[][];
    acpRuntimesDelayMs?: number;
    /** When true, the catalog discovery call throws — simulates a failed query. */
    acpRuntimesError?: boolean;
    acpAuthMethods?: Record<string, RawAcpAuthMethodsResult>;
    acpAuthMethodsErrors?: Record<string, string>;
    acpAuthMethodsError?: string;
    /** When set, the `delete_custom_harness` mock command throws with this message. */
    deleteCustomHarnessError?: string;
    connectAcpRuntimeResult?: RawConnectAcpRuntimeResult;
    connectAcpRuntimeDelayMs?: number;
    connectAcpRuntimeError?: string;
    /** Catalog returned after a successful mocked connect (sign-in). */
    acpRuntimesCatalogAfterConnect?: RawAcpRuntimeCatalogEntry[];
    activePersonaIds?: string[];
    installAcpRuntimeDelayMs?: number;
    /** Live output lines the mocked install emits before it settles. */
    installAcpRuntimeOutputLines?: string[];
    installAcpRuntimeResult?: RawInstallRuntimeResult;
    /** Sequence of results for successive `install_acp_runtime` calls.
     *  Call N returns results[N]; when exhausted the last entry repeats.
     *  Takes precedence over `installAcpRuntimeResult`. */
    installAcpRuntimeResults?: RawInstallRuntimeResult[];
    /** Per-runtime install configuration keyed by runtimeId.
     *  When a runtimeId matches, its entry overrides the global
     *  installAcpRuntime* fields for that specific runtime. */
    installAcpRuntimeByRuntime?: Record<
      string,
      {
        delayMs?: number;
        result?: RawInstallRuntimeResult;
        /** Call-order sequence — same semantics as installAcpRuntimeResults. */
        results?: RawInstallRuntimeResult[];
      }
    >;
    managedAgentPrereqs?: {
      acp?: MockCommandAvailability;
      mcp?: MockCommandAvailability;
    };
    managedAgents?: MockManagedAgentSeed[];
    /** Result returned by the mocked `add_agent_to_huddle` command. */
    addAgentToHuddleResult?: {
      ephemeral_added: boolean;
      parent_added: boolean;
      parent_error: string | null;
    };
    /** When set, the mocked `add_agent_to_huddle` command throws. */
    addAgentToHuddleError?: string;
    /** Delay an invocation-time huddle snapshot to exercise hydration ordering. */
    huddleStateReadDelayMs?: number;
    /** Delay companion creation to expose the newly-started huddle handoff state. */
    openHuddleWindowDelayMs?: number;
    /** Delay the native start result after membership arrives in the channel list. */
    startHuddleReturnDelayMs?: number;
    /** Per agent+relay runtime rows for the pair-scoped lifecycle commands
     *  (`list/start/stop/restart_managed_agent_runtime`). */
    managedAgentRuntimes?: MockManagedAgentRuntimeSeed[];
    personas?: MockPersonaSeed[];
    /** Community catalog replaceable-event heads returned by relay queries. */
    personaCatalogEvents?: RelayEvent[];
    /** Outcomes for successive explicit persona share publications. */
    personaSharePublicationStatuses?: Array<"published" | "queued">;
    teams?: MockTeamSeed[];
    relayAgents?: MockRelayAgentSeed[];
    /** Reject successive relay-agent directory reads, then resume. */
    relayAgentListErrors?: (string | null)[];
    /** Native-like huddle state seeded from authoritative role-bearing membership. */
    huddle?: MockHuddleSeed;
    agentListDelayMs?: number;
    agentMemory?: RawAgentMemoryListing | Record<string, RawAgentMemoryListing>;
    addChannelMembersDelayMs?: number;
    /** Sequenced add-member failures. A string fails that call; null succeeds. */
    addChannelMembersErrors?: (string | null)[];
    channelMembersReadDelayMs?: number;
    createManagedAgentDelayMs?: number;
    channelTemplates?: ChannelTemplate[];
    channelsReadError?: string;
    /** Reject successive mock `get_channels` calls, then resume. */
    channelsReadErrors?: (string | null)[];
    /** Reject successive mock `create_channel` calls, then resume. */
    createChannelErrors?: string[];
    /** Reject successive mock `ensure_starter_channels` calls, then resume. */
    ensureStarterChannelsErrors?: string[];
    /** Reject successive mock `join_channel` calls, then resume. */
    joinChannelErrors?: string[];
    channelsReadDelayMs?: number;
    /** Return not-modified for this many reads before resuming full payloads. */
    channelsNotModifiedResponses?: number;
    /** When true, a matching knownHash returns a not-modified channel payload. */
    honorChannelsKnownHash?: boolean;
    /** Number of seeded rows in the deep-history fixture. Defaults to 600. */
    deepHistoryMessageCount?: number;
    feedReadError?: string;
    canvasReadError?: string;
    /** Delay (ms) for `apply_workspace` so e2e tests can observe the
     *  community-switch gate. 0/undefined = instant. */
    applyCommunityDelayMs?: number;
    /** Reject `clear_pending_navigation_deep_links` with this message. */
    clearPendingNavigationDeepLinksError?: string;
    openDmDelayMs?: number;
    sendMessageDelayMs?: number;
    /** Hold the media proxy at port 0 until the E2E release seam is invoked. */
    mediaProxyInitiallyUnavailable?: boolean;
    /** Hold mock send live echoes until the E2E release seam is invoked. */
    deferSendMessageLiveEcho?: boolean;
    /** Close the first channel-window live REQ; its retry is accepted. */
    closeChannelLiveSubscriptionOnce?: boolean;
    /** Reject successive kind-9 sends with these messages, then resume. */
    sendMessageErrors?: string[];
    /** Reject successive managed-agent starts, then resume. */
    startManagedAgentErrors?: string[];
    /** Delay (ms) after snapshotting a thread-replies page so E2E tests can
     *  deliver live reply/aux events while an older response is in flight. */
    threadRepliesDelayMs?: number;
    usersBatchDelayMs?: number;
    /** Delay (ms) applied to continuation channel-window requests so e2e
     *  tests can observe the in-flight prepend window. 0/undefined = instant. */
    channelWindowDelayMs?: number;
    profileReadDelayMs?: number;
    profileReadError?: string;
    /** Override whether get_profile reports a real kind:0 event. */
    profileHasEvent?: boolean;
    profileUpdateError?: string;
    profileUpdateErrors?: string[];
    linkPreviewMetadata?: {
      title: string;
      siteName: string | null;
      description: string | null;
      imageDataUrl: string | null;
      imageDomain: string | null;
      imageFetchState?: "none" | "image" | "transient_failure" | "rejected";
      imageRetryAfterMs?: number | null;
      faviconDataUrl?: string | null;
    } | null;
    linkPreviewMetadataByHref?: Record<
      string,
      {
        title: string;
        siteName: string | null;
        description: string | null;
        imageDataUrl: string | null;
        imageDomain: string | null;
        imageFetchState?: "none" | "image" | "transient_failure" | "rejected";
        imageRetryAfterMs?: number | null;
        faviconDataUrl?: string | null;
      } | null
    >;
    linkPreviewMetadataDelayMs?: number;
    /** Hold metadata until the E2E release seam is invoked. */
    deferLinkPreviewMetadata?: boolean;
    /** Simulates native cold-cache startup work before the async response. */
    linkPreviewMetadataStartBlockMs?: number;
    /** Delays link-preview snapshot media uploads so specs can exercise the
     *  composer's settle-gated disabled state before the snapshot tag is ready. */
    linkPreviewUploadDelayMs?: number;
    /** Hold link-preview uploads before mock-native cancellation registration. */
    deferLinkPreviewUploadRegistration?: boolean;
    /** Substrings of `link-preview-*` upload filenames whose `upload_media_bytes`
     *  call should reject, so specs can drive a per-media snapshot upload failure
     *  (e.g. `["link-preview-image"]` fails only the thumbnail, favicon survives). */
    linkPreviewUploadErrorFilenames?: string[];
    searchProfiles?: MockSearchProfileSeed[];
    updateAvailable?: boolean;
    updateChannelDelayMs?: number;
    updateDownloadDelayMs?: number;
    restartDelayMs?: number;
    updateVersion?: string;
    /** When false, `is_auto_update_supported` returns false (simulates a
     *  Linux .deb install where Tauri's updater cannot swap the binary).
     *  Defaults to true for all existing tests. */
    autoUpdateSupported?: boolean;
    /** Reject `plugin:opener|open_url` to exercise browser-return fallback UI. */
    openerError?: string;
    /** Delay binding signatures so specs can exercise request supersession. */
    nostrBindSignDelayMs?: number;
    /** Reject successive mock WebSocket connect attempts, then resume. */
    websocketConnectErrors?: string[];
    stallWebsocketSends?: boolean;
    userSearchDelayMs?: number;
    // NIP-IA gate inputs — see tests/helpers/bridge.ts:MockBridgeOptions for
    // semantics. These three drive the archive-button gate matrix in
    // tests/e2e/identity-archive.spec.ts; they're plumbed into:
    // - `list_archived_identities` (archivedIdentities)
    // - `resolve_oa_owner` (oaOwnerIsMe)
    // - `resetMockRelayMembers` (relayRole)
    archivedIdentities?: string[];
    // Relay's NIP-11 `self` pubkey (hex) for `get_relay_self`. A DM whose peer
    // equals this is treated as a moderation DM (composer disabled). Absent →
    // fail open (no mod-DM detection), matching the Rust command's contract.
    relaySelf?: string | null;
    oaOwnerIsMe?: boolean;
    /** Whether the mock relay advertises NIP-43 membership support. Defaults to false. */
    relayRequiresMembership?: boolean;
    /** Delay EOSE for membership snapshots after delivering the event. */
    relayMembershipEoseDelayMs?: number;
    relayRole?: "owner" | "admin" | "member" | null;
    // Descriptors returned by the mocked `pick_and_upload_media` /
    // `upload_media_bytes` commands. Lets a spec drive the attachment flow
    // (e.g. a generic PDF) without a real upload pipeline. See
    // tests/helpers/bridge.ts:MockBridgeOptions.uploadDescriptors.
    uploadDelayMs?: number;
    /** Exercise the production composer path that queues files until send. */
    deferredComposerUploads?: boolean;
    /** Delay (ms) applied to `encode_agent_snapshot_for_send` so E2E tests can
     *  observe the "preparing" phase before the upload begins. 0/undefined = instant. */
    encodeDelayMs?: number;
    /** Delay (ms) applied to `get_relay_self` so E2E tests can prove the
     *  fail-closed race: DMs are withheld while classification is unresolved. */
    relaySelfDelayMs?: number;
    /** Delay (ms) applied to `start_pairing` so pairing loading UI is observable. */
    pairingStartDelayMs?: number;
    /**
     * Sequenced results for `confirm_team_snapshot_import`. String = throw
     * with that message; null = succeed. Call N uses results[N]; last entry
     * repeats when exhausted. Follows the `nsecErrors` precedent.
     */
    teamSnapshotConfirmErrors?: (string | null)[];
    /**
     * When true, `preview_team_snapshot_import` returns a preview with
     * `hasSourceAllowlist: true` so the allowlist section renders in the
     * import dialog.
     */
    teamSnapshotPreviewHasSourceAllowlist?: boolean;
    /**
     * When set to a non-empty string, `fetch_snapshot_bytes` throws with this
     * message — lets specs prove malformed/hash/size-mismatch error paths.
     */
    snapshotFetchError?: string;
    uploadDescriptors?: RawBlobDescriptor[];
    // Seed rows returned by `list_save_subscriptions`. Each entry uses the same
    // snake_case wire shape the Rust backend returns so tests can drive the
    // LocalArchiveSettingsCard without a real SQLite database.
    agentMetricArchiveDefaultEnabled?: boolean;
    saveSubscriptions?: Array<{
      scope_type: string;
      scope_value: string;
      kinds: string; // JSON-encoded integer array, e.g. "[9,40002]"
    }>;
    // Event IDs that `get_event` should report as definitively not found.
    // Causes `useDraftRootStatus` to classify as `deleted`.
    deletedEventIds?: string[];
    // Pending community deep links (buzz://join / buzz://connect / buzz://add-community) seeded into
    // the mocked Rust-side queue. Mirrors the real queue's semantics:
    // `take_pending_community_deep_link` peeks the head and
    // `acknowledge_pending_community_deep_link` removes by id. Drives the
    // pending-invite gate and deep-link drain path in tests.
    pendingCommunityDeepLinks?: Array<{
      id: string;
      kind: "connect" | "join" | "add-community";
      relayUrl: string;
      code?: string | null;
      name?: string | null;
    }>;
    pendingNavigationDeepLinks?: Array<{
      id: string;
      kind: "channel" | "message";
      channelId: string;
      messageId?: string | null;
      threadRootId?: string | null;
    }>;
    // Entity links captured before React mounts. The app drains and acknowledges
    // this mocked Rust-side queue after installing its event listener.
    pendingEntityDeepLinks?: Array<{
      id: string;
      href: string;
    }>;
    // When true, `get_identity` returns `lost: true` until `persist_current_identity`
    // or `import_identity` is called. Drives the identity-lost recovery UX in tests.
    identityLost?: boolean;
    // When true, `get_identity` returns `locked: true` until `import_identity` is
    // called. Drives the keyring-locked screen in tests.
    identityLocked?: boolean;
    /** Delay (ms) applied to identity import so specs can observe pending navigation. */
    identityImportDelayMs?: number;
    /**
     * Global agent config returned by `get_global_agent_config`. Defaults to
     * an empty config (no provider, model, or env vars) if not specified.
     * Pass a config with a provider to test Inherit-from-global behavior.
     */
    globalAgentConfig?: {
      env_vars: Record<string, string>;
      provider: string | null;
      model: string | null;
      preferred_runtime?: string | null;
    };
    /** Explicit owner-only agent-access capability; independent of baked defaults. */
    ownerOnlyAccessBuild?: boolean;
    /** File-layer config returned by runtime id. */
    runtimeFileConfigs?: Record<string, RuntimeFileConfigSubset | null>;
    /** Baked build env returned by the display and key-name Tauri commands. */
    bakedBuildEnv?: Array<{
      key: string;
      masked: boolean;
      value: string;
    }>;
    /** Delay (ms) applied to `get_baked_build_env` so specs can observe
     *  initial render gating around build defaults. 0/undefined = instant. */
    bakedBuildEnvDelayMs?: number;
    /** Delay (ms) applied to `set_global_agent_config` so tests can observe
     *  pending save behaviour. 0/undefined = instant. Alias of
     *  `globalConfigSaveDelayMs` (kept for onboarding specs). */
    setGlobalAgentConfigDelayMs?: number;
    /** Sequenced save failures. A string rejects that call; null succeeds. */
    setGlobalAgentConfigErrors?: (string | null)[];
    /** Errors returned by successive backup verification attempts. Null succeeds. */
    backupVerificationErrors?: (string | null)[];
    /** Public identities returned by successive successful backup verifications. */
    backupVerificationPubkeys?: string[];
    /** Delay (ms) applied to backup encryption so specs can observe pending UI. */
    backupEncryptionDelayMs?: number;
    /** Native paths returned by successive backup saves. */
    backupSavePaths?: Array<string | null>;
    /**
     * When set, `get_nsec` throws with this message instead of returning the
     * mock nsec string. Use `nsecErrors` for sequenced failure/success.
     */
    nsecError?: string;
    /**
     * Sequenced results for `get_nsec`. Each element is either a string
     * (error message) or null (success — returns the default mock nsec).
     * Call N uses results[N]; when exhausted the last entry repeats.
     */
    nsecErrors?: (string | null)[];
    /**
     * The `restarted_count` returned by `set_global_agent_config`. Defaults to
     * 0 (no agents restarted). Set to a positive integer to drive the
     * "Saved. Restarted N agent(s)." status text in AgentDefaultsSettingsCard.
     */
    globalConfigRestartedCount?: number;
    /**
     * The `failed_restart_count` returned by `set_global_agent_config`. Defaults
     * to 0. Set to a positive integer to drive the "M failed to restart — check
     * the Agents tab." status text in AgentDefaultsSettingsCard.
     */
    globalConfigFailedRestartCount?: number;
    /**
     * Milliseconds to delay the mocked `set_global_agent_config` response.
     * Defaults to 0 (resolve immediately). Use to hold a save in flight so a
     * spec can interleave edits and exercise the mid-save race handling.
     */
    globalConfigSaveDelayMs?: number;
    /**
     * Override the `discover_agent_models` mock response. When set, returns
     * this catalog instead of the default per-harness model list.
     */
    discoverAgentModels?: {
      models: Array<{
        id: string;
        name: string | null;
        description?: string | null;
      }>;
      supportsSwitching: boolean;
      agentDefaultModel?: string | null;
      selectedModel?: string | null;
    };
    /**
     * When set, `discover_agent_models` throws with this message instead of
     * returning a catalog.
     */
    discoverAgentModelsError?: string;
    // Backend provider mocks for the create-agent "Run on" section. See
    // tests/helpers/bridge.ts:MockBridgeOptions for semantics.
    backendProviders?: Array<{ id: string; binaryPath: string }>;
    backendProviderProbeResult?: Record<string, unknown>;
    backendProviderProbeDelayMs?: number;
  };
  relayHttpUrl?: string;
  relayWsUrl?: string;
  autoConnectDefaultRelay?: boolean;
  identity?: TestIdentity;
};

type RawBlobDescriptor = {
  url: string;
  sha256: string;
  size: number;
  type: string;
  uploaded: number;
  dim?: string;
  blurhash?: string;
  thumb?: string;
  duration?: number;
  image?: string;
  filename?: string;
};

type RawRelayMember = {
  pubkey: string;
  role: "owner" | "admin" | "member";
  added_by: string | null;
  created_at: string;
};

type RawProfile = {
  pubkey: string;
  display_name: string | null;
  /** Kind-0 `name` field, kept separate from `display_name` so mention
   * resolution can match either alias. */
  name?: string | null;
  avatar_url: string | null;
  about: string | null;
  nip05_handle: string | null;
  owner_pubkey: string | null;
  is_agent?: boolean;
  /** Mirrors the Rust `has_profile_event` flag: true when a real kind:0 event
   * backed this profile, false for the synthesized empty fallback. */
  has_profile_event: boolean;
};

type RawUserProfileSummary = {
  display_name: string | null;
  name?: string | null;
  avatar_url: string | null;
  nip05_handle: string | null;
  owner_pubkey: string | null;
  is_agent?: boolean;
};

type RawUsersBatchResponse = {
  profiles: Record<string, RawUserProfileSummary>;
  missing: string[];
};

type RawUserSearchResult = {
  pubkey: string;
  display_name: string | null;
  avatar_url: string | null;
  nip05_handle: string | null;
  owner_pubkey: string | null;
  is_agent?: boolean;
};

type RawSearchUsersResponse = {
  users: RawUserSearchResult[];
  next_cursor?: string | null;
};

type PresenceStatus = "online" | "away" | "offline";

type RawPresenceLookup = Record<string, PresenceStatus>;

type RawChannel = {
  id: string;
  name: string;
  channel_type: "stream" | "forum" | "dm";
  visibility: "open" | "private";
  description: string;
  topic: string | null;
  purpose: string | null;
  member_count: number;
  member_pubkeys: string[];
  last_message_at: string | null;
  archived_at: string | null;
  participants: string[];
  participant_pubkeys: string[];
  ttl_seconds: number | null;
  ttl_deadline: string | null;
};

type RawChannelWithMembership = RawChannel & {
  is_member: boolean;
};

type RawChannelDetail = RawChannel & {
  created_by: string;
  created_at: string;
  updated_at: string;
  topic_set_by: string | null;
  topic_set_at: string | null;
  purpose_set_by: string | null;
  purpose_set_at: string | null;
  topic_required: boolean;
  max_members: number | null;
  nip29_group_id: string | null;
};

type RawChannelMember = {
  pubkey: string;
  role: "owner" | "admin" | "member" | "guest" | "bot";
  is_agent?: boolean;
  joined_at: string;
  display_name: string | null;
};

type RawChannelMembersResponse = {
  members: RawChannelMember[];
  next_cursor: string | null;
};

type RawAddChannelMembersResponse = {
  added: string[];
  errors: Array<{
    pubkey: string;
    error: string;
  }>;
};

type MockChannel = Omit<RawChannelDetail, "member_pubkeys"> & {
  members: RawChannelMember[];
};

type RawFeedItem = {
  id: string;
  kind: number;
  pubkey: string;
  content: string;
  created_at: number;
  channel_id: string | null;
  channel_name: string;
  // Mirrors native FeedItemInfo.channel_type (Option<String>): the Tauri
  // backend always emits the key, as `null` when unknown.
  channel_type?: string | null;
  tags: string[][];
  category: "mention" | "needs_action" | "activity" | "agent_activity";
};

type RawHomeFeedResponse = {
  feed: {
    mentions: RawFeedItem[];
    needs_action: RawFeedItem[];
    activity: RawFeedItem[];
    agent_activity: RawFeedItem[];
  };
  meta: {
    since: number;
    total: number;
    generated_at: number;
  };
};

type RawThreadSummary = {
  reply_count: number;
  descendant_count: number;
  last_reply_at: number | null;
  participants: string[];
};

type RawForumPost = {
  event_id: string;
  pubkey: string;
  content: string;
  kind: number;
  created_at: number;
  channel_id: string;
  tags: string[][];
  thread_summary: RawThreadSummary | null;
  reactions: unknown;
};

type RawForumPostsResponse = {
  messages: RawForumPost[];
  next_cursor: number | null;
};

type RawForumReply = {
  event_id: string;
  pubkey: string;
  content: string;
  kind: number;
  created_at: number;
  channel_id: string;
  tags: string[][];
  parent_event_id: string | null;
  root_event_id: string | null;
  depth: number;
  broadcast: boolean;
  reactions: unknown;
};

type RawForumThreadResponse = {
  root: RawForumPost;
  replies: RawForumReply[];
  total_replies: number;
  next_cursor: string | null;
};

type RawUserNote = {
  id: string;
  pubkey: string;
  created_at: number;
  content: string;
  tags: string[][];
};

type RawUserNotesCursor = {
  before: number;
  before_id: string;
};

type RawUserNotesResponse = {
  notes: RawUserNote[];
  next_cursor: RawUserNotesCursor | null;
};

type RawSearchHit = {
  event_id: string;
  content: string;
  kind: number;
  pubkey: string;
  channel_id: string | null;
  channel_name: string | null;
  created_at: number;
  score: number;
};

type RawSearchResponse = {
  hits: RawSearchHit[];
  found: number;
};

type RawSendChannelMessageResponse = {
  event_id: string;
  parent_event_id: string | null;
  root_event_id: string | null;
  depth: number;
  created_at: number;
};

type RawRelayAgent = {
  pubkey: string;
  name: string;
  agent_type: string;
  channels: string[];
  channel_ids: string[];
  capabilities: string[];
  status: PresenceStatus;
  respond_to?: "owner-only" | "allowlist" | "anyone";
  respond_to_allowlist?: string[];
};

type RawManagedAgent = {
  pubkey: string;
  name: string;
  persona_id: string | null;
  /** Record-level harness/runtime pin (`null` when inheriting from the persona). */
  runtime: string | null;
  relay_url: string;
  acp_command: string;
  agent_command: string;
  agent_args: string[];
  mcp_command: string;
  turn_timeout_seconds: number;
  idle_timeout_seconds: number | null;
  max_turn_duration_seconds: number | null;
  parallelism: number;
  system_prompt: string | null;
  avatar_url: string | null;
  model: string | null;
  provider?: string | null;
  env_vars?: Record<string, string>;
  status: "running" | "stopped" | "deployed" | "not_deployed";
  pid: number | null;
  created_at: string;
  updated_at: string;
  last_started_at: string | null;
  last_stopped_at: string | null;
  last_exit_code: number | null;
  last_error: string | null;
  last_error_code: number | null;
  needs_restart?: boolean;
  restart_diff?: Array<{ field: string; change: unknown }>;
  log_path: string;
  start_on_app_launch: boolean;
  auto_restart_on_config_change?: boolean;
  backend:
    | { type: "local" }
    | { type: "provider"; id: string; config: Record<string, unknown> };
  backend_agent_id: string | null;
  respond_to: "owner-only" | "allowlist" | "anyone";
  respond_to_allowlist: string[];
};

type RawCreateManagedAgentResponse = {
  agent: RawManagedAgent;
  private_key_nsec: string;
  profile_sync_error: string | null;
  spawn_error: string | null;
};

type RawManagedAgentLog = {
  content: string;
  log_path: string;
};

type RawEngramEntry = {
  slug: string;
  body: string;
  eventId: string;
  createdAt: number;
  outgoingRefs: string[];
};

type RawAgentMemoryListing = {
  core: RawEngramEntry | null;
  memories: RawEngramEntry[];
  truncated: boolean;
  fetchedAt: number;
};

type RawCommandAvailability = {
  command: string;
  resolved_path: string | null;
  available: boolean;
};

type RawManagedAgentPrereqs = {
  acp: RawCommandAvailability;
  mcp: RawCommandAvailability;
};

type RawPersona = {
  id: string;
  display_name: string;
  avatar_url: string | null;
  system_prompt: string;
  runtime?: string | null;
  model?: string | null;
  provider?: string | null;
  name_pool?: string[];
  is_builtin: boolean;
  is_active: boolean;
  shared: boolean;
  source_team?: string | null;
  catalog_source?: { owner_pubkey: string; persona_id: string } | null;
  env_vars?: Record<string, string>;
  respond_to?: string | null;
  respond_to_allowlist?: string[];
  parallelism?: number | null;
  created_at: string;
  updated_at: string;
};

type RawTeam = {
  id: string;
  name: string;
  description: string | null;
  persona_ids: string[];
  is_builtin: boolean;
  source_dir: string | null;
  is_symlink: boolean;
  symlink_target: string | null;
  version: string | null;
  created_at: string;
  updated_at: string;
};

type MockManagedAgent = RawManagedAgent & {
  private_key_nsec: string;
  log_lines: string[];
};

// Mirrors the Rust `ManagedAgentRuntimeStatus` camelCase wire shape for the
// pair-scoped lifecycle commands.
type MockManagedAgentRuntimeRow = {
  pubkey: string;
  relayUrl: string;
  localSetup: boolean;
  lifecycle:
    | "starting"
    | "listening"
    | "waking"
    | "ready"
    | "failed"
    | "stopped";
  pid: number | null;
  error: string | null;
  logPath: string | null;
};

type WsHandler = (message: unknown) => void;
const GLOBAL_MOCK_SUBSCRIPTION = "*";

type MockSubscription = {
  channelId: string;
  kinds: number[] | null;
  /** `#p` values from the REQ filters, if any — lets specs assert an
   *  owner-scoped live subscription (e.g. the observer-archive `24200`
   *  reconciliation gate) independently of channel-scoped ones. */
  ownerPubkeys: string[];
};

type MockFilter = {
  "#a"?: string[];
  "#d"?: string[];
  "#e"?: string[];
  "#h"?: string[];
  "#p"?: string[];
  authors?: string[];
  ids?: string[];
  kinds?: number[];
  limit?: number;
  since?: number;
  until?: number;
};

type MockSocket = {
  handler: WsHandler;
  subscriptions: Map<string, MockSubscription>;
};

function createMockRelayMembershipEvent(): RelayEvent {
  return createMockEvent(
    13534,
    "",
    mockRelayMembers.map((member) => ["member", member.pubkey, member.role]),
    "f".repeat(64),
  );
}

/**
 * Per-user custom emoji sets (kind:30030) the mock WS serves for
 * `listCustomEmoji` REQs. The community palette is the client-side UNION of
 * every member's own set (d=`buzz:custom-emoji`). We serve TWO member-authored
 * sets from distinct pubkeys so the e2e exercises the union/collapse path, not
 * a single relay-owned set. `:buzz:` is the stable shortcode exercised by
 * custom-emoji.spec.ts (claimed by BOTH members with different URLs, so the
 * palette must collapse it to one deterministic winner); `:narf:` and
 * `:bufo_joy:` prove a second member's distinct emoji unions in.
 */
function createMockCustomEmojiSetEvents(): RelayEvent[] {
  return [
    createMockEvent(
      KIND_EMOJI_SET,
      "",
      [
        ["d", CUSTOM_EMOJI_SET_D_TAG],
        ["emoji", "buzz", "https://example.com/e2e/buzz.png"],
        // A relay-hosted emoji whose URL matches rewriteRelayUrl()'s pattern,
        // used by the reaction guard to assert the proxy rewrite fires.
        ["emoji", REACTION_EMOJI_SHORTCODE, REACTION_EMOJI_URL],
      ],
      // The current mock identity owns this set, so the settings card's
      // "My emoji" section is non-empty and removable.
      MOCK_IDENTITY_PUBKEY,
    ),
    createMockEvent(
      KIND_EMOJI_SET,
      "",
      [
        ["d", CUSTOM_EMOJI_SET_D_TAG],
        ["emoji", "narf", "https://example.com/e2e/narf.png"],
        // member B claims :buzz: with a DIFFERENT url — unionCustomEmoji must
        // collapse it to one deterministic winner, never expose two URLs.
        ["emoji", "buzz", "https://example.com/e2e/buzz-b.png"],
        ["emoji", "bufo_joy", "https://example.com/e2e/bufo-joy.png"],
      ],
      "b".repeat(64),
    ),
  ];
}

function updateMockRelayMembershipFromAdminEvent(event: RelayEvent): boolean {
  const targetPubkey = event.tags
    .find((tag) => tag[0] === "p")?.[1]
    ?.toLowerCase();
  if (!targetPubkey) return false;

  if (event.kind === 9030) {
    const role = event.tags.find((tag) => tag[0] === "role")?.[1] ?? "member";
    if (role !== "admin" && role !== "member") return false;
    if (mockRelayMembers.some((member) => member.pubkey === targetPubkey)) {
      return true;
    }
    mockRelayMembers.push({
      pubkey: targetPubkey,
      role,
      added_by: event.pubkey,
      created_at: new Date().toISOString(),
    });
    return true;
  }

  if (event.kind === 9031) {
    mockRelayMembers = mockRelayMembers.filter(
      (member) => member.pubkey !== targetPubkey,
    );
    return true;
  }

  if (event.kind === 9032) {
    const role = event.tags.find((tag) => tag[0] === "role")?.[1];
    if (role !== "admin" && role !== "member") return false;
    mockRelayMembers = mockRelayMembers.map((member) =>
      member.pubkey === targetPubkey ? { ...member, role } : member,
    );
    return true;
  }

  return false;
}

declare global {
  interface Window {
    __BUZZ_E2E__?: E2eConfig;
    __BUZZ_E2E_COMMANDS__?: string[];
    __BUZZ_E2E_COMMAND_PAYLOADS__?: Array<{
      command: string;
      payload: unknown;
    }>;
    __BUZZ_E2E_COMMAND_LOG__?: Array<{
      command: string;
      payload: unknown;
    }>;
    /** Release a mock media proxy held at port 0 and return its ready port. */
    __BUZZ_E2E_RELEASE_MEDIA_PROXY__?: () => number;
    /** Release mock send events that were stored but withheld from live subscribers. */
    __BUZZ_E2E_RELEASE_SEND_MESSAGE_LIVE_ECHO__?: () => number;
    __BUZZ_E2E_EMIT_MEDIA_UPLOAD_PHASE__?: (input: {
      id: string;
      phase: string;
    }) => Promise<void>;
    __BUZZ_E2E_EMIT_MEDIA_UPLOAD_PROGRESS__?: (input: {
      id: string;
      sent: number;
      total: number;
    }) => Promise<void>;
    __BUZZ_E2E_EMIT_MOCK_HUDDLE_TTS_SPEAKER__?: (payload: {
      pubkey: string | null;
      level: number;
    }) => Promise<void>;
    __BUZZ_E2E_WEBVIEW_ZOOM__?: number;
    __BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__?: (input: {
      channelName: string;
      kind?: number;
    }) => boolean;
    __BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__?: (input: {
      ownerPubkey: string;
      kind: number;
    }) => boolean;
    __BUZZ_E2E_EMIT_MOCK_MESSAGE__?: (input: {
      channelName: string;
      content: string;
      parentEventId?: string | null;
      pubkey?: string;
      kind?: number;
      mentionPubkeys?: string[];
      extraTags?: string[][];
      createdAt?: number;
      /** Marks this test-only message as locally pending. */
      pending?: boolean;
      /** 64-hex id required for the event to be a valid reaction target. */
      id?: string;
    }) => RelayEvent;
    /** Prepend `count` synthetic older messages to a channel's mock store so
     *  an older-history fetch has something to paginate. Mirrors how the real
     *  relay backfills history. Returns the created events. */
    __BUZZ_E2E_PREPEND_MOCK_HISTORY__?: (input: {
      channelName: string;
      count: number;
      startIndex?: number;
      lineCount?: number;
      createdAtStart?: number;
      emit?: boolean;
    }) => RelayEvent[];
    __BUZZ_E2E_EMIT_MOCK_TYPING__?: (input: {
      channelName: string;
      createdAt?: number;
      pubkey?: string;
      threadHeadId?: string;
    }) => RelayEvent;
    __BUZZ_E2E_INVOKE_MOCK_COMMAND__?: (
      command: string,
      payload?: Record<string, unknown>,
    ) => Promise<unknown>;
    __BUZZ_E2E_EMIT_TAURI_EVENT__?: (
      event: string,
      payload: unknown,
    ) => Promise<void>;
    __BUZZ_E2E_SET_MOCK_HUDDLE_SNAPSHOT__?: (input: {
      members: MockHuddleMemberSeed[];
      transcriptionEnabled: boolean;
    }) => Promise<void>;
    __BUZZ_E2E_PUSH_MOCK_FEED_ITEM__?: (item: RawFeedItem) => RawFeedItem;
    /** Replace an existing feed item by id (or push if not found) and fire the updated event. */
    __BUZZ_E2E_REPLACE_MOCK_FEED_ITEM__?: (
      oldId: string,
      item: RawFeedItem,
    ) => RawFeedItem;
    __BUZZ_E2E_SIGNED_EVENTS__?: Array<{
      content: string;
      createdAt?: number;
      kind: number;
      tags: string[][];
    }>;
    /** Project-scoped events accepted by the mock relay. */
    __BUZZ_E2E_ACCEPTED_PROJECT_EVENTS__?: Array<{
      content: string;
      kind: number;
      tags: string[][];
    }>;
    /** Project event kinds rejected once, in order, to exercise retry flows. */
    __BUZZ_E2E_REJECT_PROJECT_EVENT_KINDS__?: number[];
    /** Makes the mock relay reject project announcements as an unknown kind. */
    __BUZZ_E2E_UNSUPPORTED_PROJECT_ANNOUNCEMENTS__?: boolean;
    /** Project event kinds accepted once but reported as failed to test lost acknowledgements. */
    __BUZZ_E2E_FAIL_PROJECT_EVENT_ACK_KINDS__?: number[];
    /**
     * Extra project events appended to the mock store on first access.
     * Use to seed standalone repositories (kind 30617) or other project-scoped
     * events before a test interaction, without mutating the fixed seed data.
     * Events must be complete RelayEvent shapes; id is a required field so that
     * id-keyed queries (e.g. lost-ACK recovery) can match them.
     */
    __BUZZ_E2E_EXTRA_PROJECT_EVENTS__?: Array<{
      id: string;
      kind: number;
      pubkey: string;
      created_at: number;
      content: string;
      tags: string[][];
    }>;
    /** Structured merge error returned by the mock native merge command. */
    __BUZZ_E2E_PROJECT_MERGE_ERROR__?: {
      code: string;
      message: string;
      recovery: {
        action: "open_terminal";
        sourceBranch: string;
        targetBranch: string;
      } | null;
    };
    /** Overrides the first mock repository owner for delegated-owner tests. */
    __BUZZ_E2E_PROJECT_OWNER_OVERRIDE__?: string;
    /** Project history kinds rejected with CLOSED for aggregate-query tests. */
    __BUZZ_E2E_REJECT_PROJECT_QUERY_KINDS__?: number[];
    /** Captured aggregate project-history filters for request-count assertions. */
    __BUZZ_E2E_PROJECT_QUERY_FILTERS__?: MockFilter[];
    __BUZZ_E2E_PROJECT_REPO_SYNC_STATUS__?: {
      local_path: string | null;
      local_branch: string | null;
      local_branches: string[];
      local_head: string | null;
      local_short_head: string | null;
      remote_branch: string | null;
      remote_head: string | null;
      remote_short_head: string | null;
      merge_base: string | null;
      ahead_count: number;
      behind_count: number;
      has_uncommitted_changes: boolean;
      has_untracked_files: boolean;
      can_push: boolean;
      push_block_reason: string | null;
      can_pull: boolean;
      pull_block_reason: string | null;
    };
    __BUZZ_E2E_SET_RELAY_CONNECTION_STATE__?: (state: ConnectionState) => void;
    __BUZZ_E2E_GET_RELAY_CONNECTION_STATE__?: () => ConnectionState;
    /** Queue deterministic mock AUTH outcomes, consumed in order. */
    __BUZZ_E2E_QUEUE_AUTH_RESPONSES__?: (
      responses: Array<{ success: boolean; message: string }>,
    ) => void;
    /** Inject CLOSED into every active mock live subscription. */
    __BUZZ_E2E_CLOSE_LIVE_SUBSCRIPTIONS__?: (reason: string) => number;
    /** Queue CLOSED responses for channel history REQs. */
    __BUZZ_E2E_QUEUE_CHANNEL_HISTORY_CLOSES__?: (reasons: string[]) => void;
    __BUZZ_E2E_SET_STALL_WEBSOCKET_SENDS__?: (stall: boolean) => void;
    __BUZZ_E2E_DISCONNECT_MOCK_WEBSOCKETS__?: () => number;
    __BUZZ_E2E_RESTART_MOCK_WEBSOCKETS__?: () => number;
    __BUZZ_E2E_SET_MOCK_WEBSOCKET_UNAVAILABLE__?: (
      unavailable: boolean,
    ) => void;
    __BUZZ_E2E_GET_WEBSOCKET_CONNECT_ATTEMPTS__?: () => number[];
    __BUZZ_E2E_ACTIVATE_RELAY_RATE_LIMIT__?: (seconds: number) => void;
    __BUZZ_E2E_RESET_WEBSOCKET_CONNECT_ATTEMPTS__?: () => void;
    __BUZZ_E2E_SET_MESH__?: (mesh: {
      admitted?: boolean;
      models?: Array<{ id: string; name: string | null }>;
      denyReason?: string;
      /** Seed the runtime slot's lifecycle state (default "off"). */
      nodeState?: "off" | "running";
      /**
       * Seed the runtime slot's role. "client" models this machine CONSUMING a
       * peer's compute — it shares the single slot and reports state:"running",
       * so the Share toggle must stay off. Drives the toggle-on regression test.
       */
      nodeMode?: "serve" | "client" | null;
      /** Seed host-side serving usage to exercise the "who's using my compute" indicator. */
      servingUsage?: Partial<{
        inflight: number;
        peakInflight: number;
        requestsServed: number;
        tokensServed: number;
        tokensPerSecond: number;
        localAttempts: number;
        remoteAttempts: number;
        endpointAttempts: number;
        peers: number;
      }>;
    }) => void;
    __BUZZ_E2E_SEED_ACTIVE_TURNS__?: (input: {
      agentPubkey: string;
      channelId: string;
      turnId: string;
      kind?: "turn_started" | "turn_completed";
    }) => void;
    __BUZZ_E2E_SEED_OBSERVER_EVENTS__?: (input: {
      agentPubkey: string;
      events: Array<{
        seq: number;
        timestamp: string;
        kind: string;
        agentIndex: number | null;
        channelId: string | null;
        sessionId: string | null;
        turnId: string | null;
        payload: unknown;
      }>;
    }) => void;
    __BUZZ_E2E_EMIT_MOCK_READ_STATE__?: (input: {
      clientId: string;
      contexts: Record<string, number>;
      createdAt: number;
      slotId: string;
    }) => unknown;
    __BUZZ_E2E_SEED_MOCK_REMINDERS__?: (reminders: RelayEvent[]) => void;
    __BUZZ_E2E_QUERY_CLIENT__?: {
      invalidateQueries: (filters: { queryKey: readonly unknown[] }) => unknown;
    };
    __BUZZ_E2E_MD_PARSE_COUNT__?: () => number;
    /**
     * Activate the community timeout store as if a send was rejected with a
     * timeout message. Lets E2E tests prove the timeout gate fires before encode.
     * Call after page load. Pass expiresAtMs (epoch ms) or 0 for unknown expiry.
     */
    __BUZZ_E2E_ACTIVATE_TIMEOUT__?: (expiresAtMs: number) => void;
    /**
     * Invalidate the channels React Query cache so E2E tests can trigger a
     * re-fetch after calling archive_channel / update_channel via
     * __BUZZ_E2E_INVOKE_MOCK_COMMAND__. Call after the mutation to make the
     * updated channel state visible to subscribers.
     */
    __BUZZ_E2E_INVALIDATE_CHANNELS__?: () => Promise<void>;
    /**
     * Directly mutate a mock channel's properties without going through a
     * command handler.  Use for E2E regressions that need to change
     * channel_type or remove isMember in a single synchronous step, then
     * follow up with __BUZZ_E2E_INVALIDATE_CHANNELS__ to flush the cache.
     *
     * Only the listed fields are writeable; omitted fields are left unchanged.
     */
    __BUZZ_E2E_MUTATE_CHANNEL__?: (opts: {
      channelId: string;
      channelType?: "stream" | "forum" | "dm";
      description?: string;
      removeMemberPubkey?: string;
    }) => void;
    /**
     * When set to an event ID string, `get_event` calls for that specific ID
     * are held in a queue and not resolved until `__BUZZ_E2E_RELEASE_GET_EVENT__()`
     * is called.  Calls for any other event ID proceed normally.  Used by the
     * cold-recovery race test to prove mid-flight feedItems updates do not
     * cancel the in-flight promise for the cold anchor specifically.
     * Set to undefined/null to disable deferral.
     */
    __BUZZ_E2E_DEFER_GET_EVENT__?: string | null;
    /** Flush all deferred `get_event` calls for the target ID.  Each queued
     *  request is resolved (or rejected) immediately.  Returns the number of
     *  requests released. */
    __BUZZ_E2E_RELEASE_GET_EVENT__?: () => number;
    /** Count of `get_event` invocations for the current defer-target ID since
     *  the last time `__BUZZ_E2E_DEFER_GET_EVENT__` was set. */
    __BUZZ_E2E_GET_EVENT_CALL_COUNT__?: number;
    /** Hold the next channel read until released. */
    __BUZZ_E2E_DEFER_NEXT_CHANNELS_READ__?: () => void;
    /** Disarm the latch and release the held channel read, if any. */
    __BUZZ_E2E_RELEASE_CHANNELS_READ__?: () => number;
    /** Number of channel reads currently held by the seam. */
    __BUZZ_E2E_CHANNELS_READ_PENDING__?: number;
    /** Release all link-preview metadata commands held by the mock bridge. */
    __BUZZ_E2E_RELEASE_LINK_PREVIEW_METADATA__?: () => number;
    /** Release link-preview uploads held before mock-native registration. */
    __BUZZ_E2E_RELEASE_LINK_PREVIEW_UPLOADS__?: () => number;
    /** Uploads that passed mock-native registration and began relay work. */
    __BUZZ_E2E_LINK_PREVIEW_UPLOAD_STARTS__?: number;
  }
}

const DEFAULT_RELAY_HTTP_URL = "http://localhost:3000";
const DEFAULT_RELAY_WS_URL = "ws://localhost:3000";

// NIP event kinds the mock reaction handlers emit.
const KIND_REACTION = 7; // NIP-25 reaction
const KIND_DELETION = 5; // NIP-09 deletion
const KIND_NIP29_DELETION = 9005;
const CHANNEL_WINDOW_AUX_KINDS = new Set([
  KIND_REACTION,
  KIND_DELETION,
  KIND_NIP29_DELETION,
  KIND_STREAM_MESSAGE_EDIT,
]);
const CHANNEL_WINDOW_AUX_DELETION_KINDS = new Set([
  KIND_DELETION,
  KIND_NIP29_DELETION,
]);

// Fake media-proxy port the mock answers for `get_media_proxy_port`, so
// `rewriteRelayUrl()` produces a real `http://127.0.0.1:<port>/media/...` src
// in e2e (instead of the `buzz-media://` fallback). The reaction guard
// asserts against this exact port.
const MOCK_MEDIA_PROXY_PORT = 54321;
let mockMediaProxyPort = MOCK_MEDIA_PROXY_PORT;

// A relay-hosted custom emoji used by the reaction guard. Its URL matches
// `rewriteRelayUrl()`'s `/media/{64-hex}.{ext}` pattern on the relay origin, so
// reacting with it exercises the proxy rewrite (unlike the `:buzz:` fixture,
// whose external example.com URL passes through unchanged).
const REACTION_EMOJI_SHORTCODE = "react";
const REACTION_EMOJI_SHA = "c".repeat(64);
const REACTION_EMOJI_URL = `${DEFAULT_RELAY_HTTP_URL}/media/${REACTION_EMOJI_SHA}.png`;

// A reaction-target message seeded into `general` with a real 64-hex event id.
// The reaction guard reacts to THIS message: getReactionTargetId() only accepts
// a 64-hex `e` tag, and the other mock seeds (and user-sent messages) use short
// non-hex ids, so they can't be reaction targets. Content is distinctive so the
// test locates its row without relying on seed ordering.
const REACTION_TARGET_EVENT_ID = "d".repeat(64);
const REACTION_TARGET_CONTENT = "React to me with a custom emoji";
// System-message reaction target id (kind:40099 join event). Distinct 64-hex
// id so it is a valid reaction target and never collides with the regular
// REACTION_TARGET_EVENT_ID.
const SYSTEM_REACTION_TARGET_EVENT_ID = "e".repeat(64);
const E2E_IDENTITY_OVERRIDE_STORAGE_KEY = "buzz:e2e-identity-override.v1";
/** Stands in for `tauri.conf.json`'s version, which no mock IPC call can read. */
const MOCK_APP_VERSION = "0.0.0-e2e";
const DEFAULT_MOCK_IDENTITY = {
  pubkey: "deadbeef".repeat(8),
  display_name: "npub1mock...",
};
const DEFAULT_REAL_IDENTITY = {
  privateKey:
    "3dbaebadb5dfd777ff25149ee230d907a15a9e1294b40b830661e65bb42f6c03",
  pubkey: "e5ebc6cdb579be112e336cc319b5989b4bb6af11786ea90dbe52b5f08d741b34",
  username: "tyler",
} satisfies TestIdentity;

const ALICE_PUBKEY =
  "953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f";
const BOB_PUBKEY =
  "bb22a5299220cad76ffd46190ccbeede8ab5dc260faa28b6e5a2cb31b9aff260";
const CHARLIE_PUBKEY =
  "554cef57437abac34522ac2c9f0490d685b72c80478cf9f7ed6f9570ee8624ea";
const OUTSIDER_PUBKEY =
  "df8e91b86fda13a9a67896df77232f7bdab2ba9c3e165378e1ba3d24c13a328e";
const PROFILE_ONLY_AGENT_PUBKEY =
  "8f83d6b7f3d74f7d933ae3a54dd8c6cc85c7f98e531c16e5a827b953441a8d67";
// A relay-classified bot agent whose declared NIP-OA owner is the mock viewer,
// but which is NOT locally managed. This is the fixture that exercises the
// sidebar's owner-gate path (`viewerIsOwner`), distinct from the local-managed
// path that `mira` (profile-only) and managed-agent fixtures cover.
const OWNED_RELAY_AGENT_PUBKEY =
  "a1b2c3d4e5f60718293a4b5c6d7e8f90112233445566778899aabbccddeeff00";
const MOCK_IDENTITY_PUBKEY = DEFAULT_MOCK_IDENTITY.pubkey;
const STARTER_GENERAL_CHANNEL_ID = "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50";
const STARTER_WELCOME_CHANNEL_ID = "5f0b1b3c-2a37-5366-9b8c-31a4b21d8e77";
const STARTER_GENERAL_CHANNEL_NAME = "general";
const STARTER_WELCOME_CHANNEL_NAME = "welcome-everyone";

// Tracks whether `persist_current_identity` or `import_identity` has cleared
// the lost flag set by `mock.identityLost`. Reset to false on each fresh page
// load (module re-evaluation), so tests start in a clean state.
let mockIdentityLostCleared = false;
// Same pattern for `mock.identityLocked`.
let mockIdentityLockedCleared = false;

// ── get_event defer/release seam ────────────────────────────────────────────
// When `window.__BUZZ_E2E_DEFER_GET_EVENT__` is set to a target event ID,
// `handleGetEvent` holds calls for that ID in this queue.  All other event IDs
// continue to resolve immediately.
// `window.__BUZZ_E2E_RELEASE_GET_EVENT__()` flushes the queue and returns the
// count of released requests, giving the race test a deterministic way to prove
// that a mid-flight feedItems update does NOT cancel the in-flight promise for
// the specific cold anchor under test.
type DeferredGetEvent = {
  resolve: (value: string) => void;
  reject: (reason: unknown) => void;
  run: () => Promise<string>;
};
let deferredGetEventQueue: DeferredGetEvent[] = [];
let deferredLinkPreviewMetadataQueue: Array<() => void> = [];
let deferredLinkPreviewUploadQueue: Array<() => void> = [];
let cancelledMediaUploadIds = new Set<string>();
let deferNextChannelsRead = false;
let deferredChannelsReadResolve: (() => void) | null = null;

const mockDisplayNames = new Map<string, string>([
  [MOCK_IDENTITY_PUBKEY, DEFAULT_MOCK_IDENTITY.display_name],
  [ALICE_PUBKEY, "alice"],
  [BOB_PUBKEY, "bob"],
  [CHARLIE_PUBKEY, "charlie"],
  [PROFILE_ONLY_AGENT_PUBKEY, "mira"],
  [OWNED_RELAY_AGENT_PUBKEY, "nadia"],
  [OUTSIDER_PUBKEY, "outsider"],
  [DEFAULT_REAL_IDENTITY.pubkey, DEFAULT_REAL_IDENTITY.username],
]);
const mockAgentPubkeys = new Set([
  ALICE_PUBKEY,
  CHARLIE_PUBKEY,
  PROFILE_ONLY_AGENT_PUBKEY,
  OWNED_RELAY_AGENT_PUBKEY,
]);
// Kind-0 `name` aliases, distinct from the display name, for exercising the
// alias-tolerant mention resolution path (e.g. a message that says "@bobby"
// while bob's display name is "bob").
const mockKind0Names = new Map<string, string>([[BOB_PUBKEY, "bobby"]]);

function isoMinutesAgo(minutesAgo: number): string {
  return new Date(Date.now() - minutesAgo * 60_000).toISOString();
}

function cloneMembers(members: RawChannelMember[]): RawChannelMember[] {
  return members.map((member) => ({ ...member }));
}

function toRawChannel(
  channel: MockChannel,
  config?: E2eConfig,
): RawChannelWithMembership {
  const currentPubkey = getMockMemberPubkey(config).toLowerCase();

  return {
    id: channel.id,
    name: channel.name,
    channel_type: channel.channel_type,
    visibility: channel.visibility,
    description: channel.description,
    topic: channel.topic,
    purpose: channel.purpose,
    member_count: channel.member_count,
    member_pubkeys: channel.members.map((member) => member.pubkey),
    last_message_at: channel.last_message_at,
    archived_at: channel.archived_at,
    participants: [...channel.participants],
    participant_pubkeys: [...channel.participant_pubkeys],
    ttl_seconds: channel.ttl_seconds ?? null,
    ttl_deadline: channel.ttl_deadline ?? null,
    is_member: channel.members.some(
      (member) => member.pubkey.toLowerCase() === currentPubkey,
    ),
  };
}

function toRawChannelDetail(
  channel: MockChannel,
  config?: E2eConfig,
): RawChannelDetail {
  return {
    ...toRawChannel(channel, config),
    created_by: channel.created_by,
    created_at: channel.created_at,
    updated_at: channel.updated_at,
    topic_set_by: channel.topic_set_by,
    topic_set_at: channel.topic_set_at,
    purpose_set_by: channel.purpose_set_by,
    purpose_set_at: channel.purpose_set_at,
    topic_required: channel.topic_required,
    max_members: channel.max_members,
    nip29_group_id: channel.nip29_group_id,
  };
}

function createMockMember(
  pubkey: string,
  role: RawChannelMember["role"],
  joinedMinutesAgo: number,
): RawChannelMember {
  return {
    pubkey,
    role,
    is_agent: role === "bot" || mockAgentPubkeys.has(pubkey),
    joined_at: isoMinutesAgo(joinedMinutesAgo),
    display_name: mockDisplayNames.get(pubkey) ?? null,
  };
}

function createMockChannel(
  seed: Omit<
    MockChannel,
    | "created_at"
    | "member_count"
    | "members"
    | "updated_at"
    | "participant_pubkeys"
    | "participants"
    | "ttl_seconds"
    | "ttl_deadline"
  > & {
    created_minutes_ago: number;
    members: RawChannelMember[];
    participant_pubkeys?: string[];
    participants?: string[];
    ttl_seconds?: number | null;
    ttl_deadline?: string | null;
    updated_minutes_ago?: number;
  },
): MockChannel {
  return {
    ...seed,
    created_at: isoMinutesAgo(seed.created_minutes_ago),
    member_count: seed.members.length,
    members: cloneMembers(seed.members),
    participant_pubkeys: [...(seed.participant_pubkeys ?? [])],
    participants: [...(seed.participants ?? [])],
    ttl_seconds: seed.ttl_seconds ?? null,
    ttl_deadline: seed.ttl_deadline ?? null,
    updated_at: isoMinutesAgo(
      seed.updated_minutes_ago ?? seed.created_minutes_ago,
    ),
  };
}

function syncMockChannel(channel: MockChannel) {
  channel.member_count = channel.members.length;

  if (channel.channel_type !== "dm") {
    return;
  }

  channel.participant_pubkeys = channel.members.map((member) => member.pubkey);
  channel.participants = channel.members.map(
    (member) => member.display_name ?? member.pubkey.slice(0, 8),
  );
}

function touchMockChannel(channel: MockChannel) {
  channel.updated_at = new Date().toISOString();
}

function normalizeParticipantPubkeys(pubkeys: string[]) {
  return [...new Set(pubkeys.map((pubkey) => pubkey.toLowerCase()))].sort();
}

function findMockDmByParticipantPubkeys(pubkeys: string[]) {
  const normalizedPubkeys = normalizeParticipantPubkeys(pubkeys);

  return (
    mockChannels.find((channel) => {
      if (channel.channel_type !== "dm") {
        return false;
      }

      const channelPubkeys = normalizeParticipantPubkeys(
        channel.participant_pubkeys,
      );

      return (
        channelPubkeys.length === normalizedPubkeys.length &&
        channelPubkeys.every(
          (pubkey, index) => pubkey === normalizedPubkeys[index],
        )
      );
    }) ?? null
  );
}

function getMockIdentity() {
  return {
    pubkey: MOCK_IDENTITY_PUBKEY,
    displayName: DEFAULT_MOCK_IDENTITY.display_name,
  };
}

function cloneProfile(profile: RawProfile): RawProfile {
  return { ...profile };
}

function cloneRelayAgent(agent: RawRelayAgent): RawRelayAgent {
  return {
    ...agent,
    channels: [...agent.channels],
    channel_ids: [...agent.channel_ids],
    capabilities: [...agent.capabilities],
  };
}

function cloneManagedAgent(agent: MockManagedAgent): RawManagedAgent {
  return {
    pubkey: agent.pubkey,
    name: agent.name,
    persona_id: agent.persona_id,
    runtime: agent.runtime ?? null,
    relay_url: agent.relay_url,
    acp_command: agent.acp_command,
    agent_command: agent.agent_command,
    agent_args: [...agent.agent_args],
    mcp_command: agent.mcp_command,
    turn_timeout_seconds: agent.turn_timeout_seconds,
    idle_timeout_seconds: agent.idle_timeout_seconds ?? null,
    max_turn_duration_seconds: agent.max_turn_duration_seconds ?? null,
    parallelism: agent.parallelism,
    system_prompt: agent.system_prompt,
    avatar_url: agent.avatar_url ?? null,
    model: agent.model,
    provider: agent.provider ?? null,
    env_vars: { ...(agent.env_vars ?? {}) },
    status: agent.status,
    pid: agent.pid,
    created_at: agent.created_at,
    updated_at: agent.updated_at,
    last_started_at: agent.last_started_at,
    last_stopped_at: agent.last_stopped_at,
    last_exit_code: agent.last_exit_code,
    last_error: agent.last_error,
    last_error_code: agent.last_error_code,
    needs_restart: agent.needs_restart ?? false,
    restart_diff: agent.restart_diff ? [...agent.restart_diff] : [],
    log_path: agent.log_path,
    start_on_app_launch: agent.start_on_app_launch,
    auto_restart_on_config_change: agent.auto_restart_on_config_change ?? true,
    backend: agent.backend ?? { type: "local" as const },
    backend_agent_id: agent.backend_agent_id ?? null,
    respond_to: agent.respond_to ?? "owner-only",
    respond_to_allowlist: agent.respond_to_allowlist
      ? [...agent.respond_to_allowlist]
      : [],
  };
}

function cloneEngramEntry(entry: RawEngramEntry): RawEngramEntry {
  return {
    ...entry,
    outgoingRefs: [...entry.outgoingRefs],
  };
}

function cloneAgentMemoryListing(
  listing: RawAgentMemoryListing,
): RawAgentMemoryListing {
  return {
    core: listing.core ? cloneEngramEntry(listing.core) : null,
    memories: listing.memories.map(cloneEngramEntry),
    truncated: listing.truncated,
    fetchedAt: listing.fetchedAt,
  };
}

function resetMockRelayMembers(config: E2eConfig | undefined) {
  const pubkey = getMockMemberPubkey(config);
  // Drive the active identity's role from `mock.relayRole` so the e2e harness
  // can exercise the NIP-IA admin gate (owner/admin → true, member/null →
  // false). Default stays `owner` to preserve existing test behavior.
  const role = config?.mock?.relayRole;
  const activeRoleMember =
    role === null
      ? null
      : {
          pubkey,
          role: role ?? "owner",
          added_by: null,
          created_at: isoMinutesAgo(120),
        };
  mockRelayMembers = [
    ...(activeRoleMember ? [activeRoleMember] : []),
    {
      pubkey: ALICE_PUBKEY,
      role: "admin",
      added_by: pubkey,
      created_at: isoMinutesAgo(90),
    },
    {
      pubkey: BOB_PUBKEY,
      role: "member",
      added_by: pubkey,
      created_at: isoMinutesAgo(60),
    },
  ];
}

function buildMockConfigSurface(pubkey: string): {
  runtimeId: string | null;
  runtimeLabel: string | null;
  isPreSpawn: boolean;
  normalized: Record<string, unknown>;
  advanced: unknown[];
  extensions: unknown[];
  sources: Record<string, unknown>;
} {
  // Goose running — mixed origins, override on model
  const gooseSurface = {
    runtimeId: "goose",
    runtimeLabel: "Goose",
    isPreSpawn: false,
    normalized: {
      model: {
        value: "gpt-4o",
        origin: "buzzExplicit",
        overriddenValue: "gpt-4o-mini",
        overriddenOrigin: "configFile",
        isRequired: false,
        writeVia: { type: "readOnly" },
      },
      provider: {
        value: "openai",
        origin: "configFile",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: {
          type: "gooseNativeConfigWrite",
          configKey: "goose.provider",
        },
      },
      mode: {
        value: "auto",
        origin: "envVar",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "respawnWithEnvVar", envKey: "GOOSE_MODE" },
      },
      thinkingEffort: {
        value: "medium",
        origin: "configFile",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: {
          type: "gooseNativeConfigWrite",
          configKey: "goose.thinkingEffort",
        },
      },
      maxOutputTokens: null,
      contextLimit: null,
      systemPrompt: null,
    },
    advanced: [
      {
        key: "active_provider",
        label: "active_provider",
        value: "openai",
        origin: "configFile",
        schemaType: { type: "string" },
        writeVia: { type: "readOnly" },
      },
    ],
    extensions: [
      { name: "developer", kind: "stdio", enabled: true },
      { name: "web_search", kind: "stdio", enabled: true },
      { name: "memory", kind: "stdio", enabled: false },
    ],
    sources: {
      acpNative: "available",
      acpConfigOptions: "available",
      envVars: "available",
      configFile: "available",
      configFilePath: "~/.config/goose/config.yaml",
      mcpConfigFilePath: "~/.config/goose/config.yaml",
    },
  };

  // Claude Code — mostly ACP-sourced
  const claudeSurface = {
    runtimeId: "claude-code",
    runtimeLabel: "Claude Code",
    isPreSpawn: false,
    normalized: {
      model: {
        value: "claude-sonnet-4-20250514",
        origin: "acpConfigOption",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "acpSetConfigOption", configId: "model" },
      },
      provider: {
        value: "anthropic",
        origin: "acpConfigOption",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "acpSetConfigOption", configId: "provider" },
      },
      mode: {
        value: "code",
        origin: "acpConfigOption",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "acpSetConfigOption", configId: "mode" },
      },
      thinkingEffort: {
        value: "high",
        origin: "acpConfigOption",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "acpSetConfigOption", configId: "thinkingEffort" },
      },
      maxOutputTokens: {
        value: "16384",
        origin: "acpConfigOption",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "acpSetConfigOption", configId: "maxOutputTokens" },
      },
      contextLimit: null,
      systemPrompt: null,
    },
    advanced: [],
    extensions: [
      { name: "filesystem", kind: "mcp", enabled: true },
      { name: "github", kind: "mcp", enabled: true },
    ],
    sources: {
      acpNative: "available",
      acpConfigOptions: "available",
      envVars: "notApplicable",
      configFile: "available",
      configFilePath: "~/.claude/settings.json",
      mcpConfigFilePath: "~/.claude.json",
    },
  };

  // Pre-spawn — model from config file, ACP fields pending
  const preSpawnSurface = {
    runtimeId: "goose",
    runtimeLabel: "Goose",
    isPreSpawn: true,
    normalized: {
      model: {
        value: "gpt-4o-mini",
        origin: "configFile",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "gooseNativeConfigWrite", configKey: "goose.model" },
      },
      provider: {
        value: "openai",
        origin: "configFile",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: {
          type: "gooseNativeConfigWrite",
          configKey: "goose.provider",
        },
      },
      mode: {
        value: null,
        origin: "acpNativeRead",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "readOnly" },
      },
      thinkingEffort: {
        value: null,
        origin: "acpNativeRead",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "readOnly" },
      },
      maxOutputTokens: null,
      contextLimit: null,
      systemPrompt: null,
    },
    advanced: [],
    extensions: [{ name: "developer", kind: "stdio", enabled: true }],
    sources: {
      acpNative: "pending",
      acpConfigOptions: "pending",
      envVars: "available",
      configFile: "available",
      configFilePath: "~/.config/goose/config.yaml",
      mcpConfigFilePath: "~/.config/goose/config.yaml",
    },
  };

  // Codex — dual-axis mode
  const codexSurface = {
    runtimeId: "codex",
    runtimeLabel: "Codex",
    isPreSpawn: false,
    normalized: {
      model: {
        value: "codex-mini",
        origin: "configFile",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "gooseNativeConfigWrite", configKey: "goose.model" },
      },
      provider: {
        value: "openai",
        origin: "configFile",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: {
          type: "gooseNativeConfigWrite",
          configKey: "goose.provider",
        },
      },
      mode: {
        value: "suggest / auto-edit",
        origin: "configFile",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "gooseNativeConfigWrite", configKey: "goose.mode" },
      },
      thinkingEffort: null,
      maxOutputTokens: null,
      contextLimit: null,
      systemPrompt: null,
    },
    advanced: [
      {
        key: "approval_policy",
        label: "Approval Policy",
        value: "unless-allow-listed",
        origin: "configFile",
        schemaType: {
          type: "enum",
          options: ["suggest", "auto-edit", "full-auto", "unless-allow-listed"],
        },
        writeVia: {
          type: "gooseNativeConfigWrite",
          configKey: "goose.approval_policy",
        },
      },
      {
        key: "sandbox_mode",
        label: "Sandbox Mode",
        value: "container",
        origin: "envVar",
        schemaType: {
          type: "enum",
          options: ["container", "host", "none"],
        },
        writeVia: { type: "respawnWithEnvVar", envKey: "GOOSE_SANDBOX_MODE" },
      },
    ],
    extensions: [
      { name: "filesystem", kind: "mcp", enabled: true },
      { name: "github", kind: "mcp", enabled: true },
    ],
    sources: {
      acpNative: "notApplicable",
      acpConfigOptions: "notApplicable",
      envVars: "available",
      configFile: "available",
      configFilePath: "~/.codex/config.toml",
      mcpConfigFilePath: "~/.codex/config.toml",
    },
  };

  // Live runtime override — a persona-linked agent whose session model was
  // switched at runtime. The live model rides over the persona baseline as a
  // secondary value WITHOUT strikethrough (the headline runtimeOverride render).
  const runtimeOverrideSurface = {
    runtimeId: "goose",
    runtimeLabel: "Goose",
    isPreSpawn: false,
    normalized: {
      model: {
        value: "claude-opus-4-20250514",
        origin: "runtimeOverride",
        overriddenValue: "gpt-4o",
        overriddenOrigin: "personaDefault",
        isRequired: false,
        writeVia: { type: "acpSetSessionModel" },
      },
      provider: {
        value: "anthropic",
        origin: "acpConfigOption",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "acpSetConfigOption", configId: "provider" },
      },
      mode: {
        value: "auto",
        origin: "envVar",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "respawnWithEnvVar", envKey: "GOOSE_MODE" },
      },
      thinkingEffort: {
        value: "high",
        origin: "configFile",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: {
          type: "gooseNativeConfigWrite",
          configKey: "goose.thinkingEffort",
        },
      },
      maxOutputTokens: null,
      contextLimit: null,
      systemPrompt: null,
    },
    advanced: [],
    extensions: [{ name: "web_search", kind: "stdio", enabled: true }],
    sources: {
      acpNative: "available",
      acpConfigOptions: "available",
      envVars: "available",
      configFile: "available",
      configFilePath: "~/.config/goose/config.yaml",
      mcpConfigFilePath: "~/.config/goose/config.yaml",
    },
  };

  // Mixed-provenance showcase — top-level rows carry different origins so the
  // panel witnesses distinct provenance labels in one frame: "Set in Buzz",
  // "Inherited from template", "From config file (...)" and
  // "From environment variable (...)".
  const multiOriginSurface = {
    runtimeId: "goose",
    runtimeLabel: "Goose",
    isPreSpawn: false,
    normalized: {
      model: {
        value: "gpt-4o",
        origin: "buzzExplicit",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "readOnly" },
      },
      provider: {
        value: "openai",
        origin: "personaDefault",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "readOnly" },
      },
      mode: {
        value: "auto",
        origin: "envVar",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: { type: "respawnWithEnvVar", envKey: "GOOSE_MODE" },
      },
      thinkingEffort: {
        value: "medium",
        origin: "configFile",
        overriddenValue: null,
        overriddenOrigin: null,
        isRequired: false,
        writeVia: {
          type: "gooseNativeConfigWrite",
          configKey: "goose.thinkingEffort",
        },
      },
      maxOutputTokens: null,
      contextLimit: null,
      systemPrompt: null,
    },
    advanced: [],
    extensions: [],
    sources: {
      acpNative: "available",
      acpConfigOptions: "available",
      envVars: "available",
      configFile: "available",
      configFilePath: "~/.config/goose/config.yaml",
      mcpConfigFilePath: "~/.config/goose/config.yaml",
    },
  };

  const buzzAgentSurface = {
    ...gooseSurface,
    runtimeId: "buzz-agent",
    runtimeLabel: "Buzz Agent",
    advanced: [],
    extensions: [],
    sources: {
      ...gooseSurface.sources,
      configFilePath: null,
      mcpConfigFilePath: null,
    },
  };

  // Map well-known test pubkeys to specific fixtures.
  // Synthetic agents are intentionally not TEST_IDENTITIES.
  const PUBKEY_MULTI_ORIGIN =
    "abc1230000000000000000000000000000000000000000000000000000000def";
  const PUBKEY_BUZZ_AGENT =
    "b0220000000000000000000000000000000000000000000000000000000000a9";

  switch (pubkey) {
    case ALICE_PUBKEY:
      return claudeSurface;
    case BOB_PUBKEY:
      return preSpawnSurface;
    case CHARLIE_PUBKEY:
      return codexSurface;
    case OUTSIDER_PUBKEY:
      return runtimeOverrideSurface;
    case PUBKEY_MULTI_ORIGIN:
      return multiOriginSurface;
    case PUBKEY_BUZZ_AGENT:
      return buzzAgentSurface;
    default:
      return gooseSurface;
  }
}

function buildSeededManagedAgent(seed: MockManagedAgentSeed): MockManagedAgent {
  const now = new Date().toISOString();
  const status = seed.status ?? "stopped";

  // Resolve agent_command and agent_args from the well-known default catalog
  // so the fixture mirrors real wire shape. Hardcoding ["acp"] for all runtimes
  // is incorrect: buzz-agent ships with no default args.
  const DEFAULT_RUNTIME_COMMAND: Record<
    string,
    { command: string; args: string[] }
  > = {
    goose: { command: "goose", args: ["acp"] },
    "buzz-agent": { command: "buzz-agent", args: [] },
    claude: { command: "claude", args: [] },
    codex: { command: "codex", args: [] },
  };
  const catalogEntry = seed.runtime
    ? DEFAULT_RUNTIME_COMMAND[seed.runtime]
    : undefined;
  const agentCommand = catalogEntry?.command ?? seed.runtime ?? "goose";
  const agentArgs = catalogEntry?.args ?? ["acp"];

  return {
    pubkey: seed.pubkey,
    name: seed.name,
    persona_id: seed.personaId ?? null,
    // Native serde always emits this key (`null` when unpinned) — the bridge
    // must mirror the wire shape, not omit the key.
    runtime: seed.runtime ?? null,
    relay_url: DEFAULT_RELAY_WS_URL,
    acp_command: "buzz-acp",
    agent_command: agentCommand,
    agent_args: agentArgs,
    mcp_command: "",
    turn_timeout_seconds: 320,
    idle_timeout_seconds: null,
    max_turn_duration_seconds: null,
    parallelism: 1,
    system_prompt: null,
    avatar_url: seed.avatarUrl ?? null,
    model: null,
    env_vars: { ...(seed.envVars ?? {}) },
    status,
    pid: status === "running" ? 42000 + mockManagedAgents.length : null,
    created_at: now,
    updated_at: now,
    last_started_at: status === "running" ? now : null,
    last_stopped_at: status === "stopped" ? now : null,
    last_exit_code: null,
    last_error: seed.lastError ?? null,
    last_error_code: seed.lastErrorCode ?? null,
    needs_restart: seed.needsRestart ?? false,
    restart_diff: seed.restartDiff ?? [],
    log_path: `/tmp/mock-agent-${seed.pubkey}.log`,
    start_on_app_launch: true,
    auto_restart_on_config_change: seed.autoRestartOnConfigChange ?? true,
    backend: seed.backend ?? { type: "local" },
    backend_agent_id: null,
    respond_to: seed.respondTo ?? "owner-only",
    respond_to_allowlist: seed.respondToAllowlist ?? [],
    private_key_nsec: `nsec1mock${seed.pubkey.slice(0, 20)}`,
    log_lines: [
      `buzz-acp starting: relay=${DEFAULT_RELAY_WS_URL} agent_pubkey=${seed.pubkey} parallelism=1`,
      "profile created; harness not started",
    ],
  };
}

function resetMockRelayAgents(config?: E2eConfig) {
  mockRelayAgents = defaultMockRelayAgents.map((agent) => ({
    ...agent,
    channels: [...agent.channels],
    channel_ids: [...agent.channel_ids],
    capabilities: [...agent.capabilities],
    respond_to_allowlist: [...(agent.respond_to_allowlist ?? [])],
  }));

  for (const seed of config?.mock?.relayAgents ?? []) {
    const channels = mockChannels.filter((channel) => {
      return (
        seed.channelIds?.includes(channel.id) ||
        seed.channelNames?.includes(channel.name)
      );
    });
    mockRelayAgents.push({
      pubkey: seed.pubkey,
      name: seed.name,
      agent_type: seed.agentType ?? "goose",
      channels: channels.map((channel) => channel.name),
      channel_ids: channels.map((channel) => channel.id),
      capabilities: seed.capabilities ?? ["messages", "channels", "mcp"],
      status: seed.status ?? "online",
      respond_to: seed.respondTo ?? "owner-only",
      respond_to_allowlist: seed.respondToAllowlist ?? [],
    });
  }
}

function resetMockManagedAgents(config?: E2eConfig) {
  mockManagedAgents = [];
  mockManagedAgentRuntimes = (config?.mock?.managedAgentRuntimes ?? []).map(
    (seed) => ({
      pubkey: seed.pubkey,
      relayUrl: seed.relayUrl,
      localSetup: true,
      lifecycle: seed.lifecycle ?? "ready",
      pid: seed.lifecycle === "stopped" ? null : 43000,
      error: null,
      logPath: null,
    }),
  );

  for (const seed of config?.mock?.managedAgents ?? []) {
    mockManagedAgents.push(buildSeededManagedAgent(seed));
    applyMockDisplayName(seed.pubkey, seed.name);
    mockAgentPubkeys.add(seed.pubkey);
    mockProfiles.set(seed.pubkey, {
      pubkey: seed.pubkey,
      display_name: seed.name,
      avatar_url: null,
      about: null,
      nip05_handle: null,
      owner_pubkey: MOCK_IDENTITY_PUBKEY,
      is_agent: true,
      has_profile_event: true,
    });
    for (const channel of mockChannels) {
      const isSeedChannel =
        seed.channelIds?.includes(channel.id) ||
        seed.channelNames?.includes(channel.name);
      if (
        !isSeedChannel ||
        channel.members.some((member) => member.pubkey === seed.pubkey)
      ) {
        continue;
      }

      channel.members.push({
        pubkey: seed.pubkey,
        role: "bot",
        is_agent: true,
        joined_at: new Date().toISOString(),
        display_name: seed.name,
      });
      syncMockChannel(channel);
      touchMockChannel(channel);
    }
  }

  syncMockRelayAgentsFromManagedAgents();
}

function resetMockPersonas(config?: E2eConfig) {
  const now = new Date().toISOString();
  const activePersonaIds = new Set(config?.mock?.activePersonaIds ?? []);
  const builtInPersonas = [
    {
      id: "builtin:fizz",
      display_name: "Fizz",
      avatar_url: null,
      system_prompt: "You are Fizz.",
    },
    {
      id: "builtin:honey",
      display_name: "Honey",
      avatar_url: null,
      system_prompt: "You are Honey.",
    },
    {
      id: "builtin:bumble",
      display_name: "Bumble",
      avatar_url: null,
      system_prompt: "You are Bumble.",
    },
  ];
  mockPersonas = builtInPersonas.map((persona) => ({
    id: persona.id,
    display_name: persona.display_name,
    avatar_url: persona.avatar_url,
    system_prompt: persona.system_prompt,
    runtime: null,
    model: null,
    provider: null,
    name_pool: [],
    is_builtin: true,
    is_active: activePersonaIds.has(persona.id),
    shared: false,
    source_team: null,
    created_at: now,
    updated_at: now,
  }));

  for (const persona of config?.mock?.personas ?? []) {
    mockPersonas.push({
      id: persona.id ?? crypto.randomUUID(),
      display_name: persona.displayName,
      avatar_url: persona.avatarUrl ?? null,
      system_prompt: persona.systemPrompt,
      runtime: persona.runtime ?? null,
      model: persona.model ?? null,
      provider: persona.provider ?? null,
      name_pool: persona.namePool ?? [],
      respond_to: persona.respondTo ?? null,
      respond_to_allowlist:
        persona.respondTo === "allowlist"
          ? [...(persona.respondToAllowlist ?? [])]
          : [],
      is_builtin: false,
      is_active: persona.isActive ?? true,
      shared: persona.shared ?? false,
      source_team: persona.sourceTeam ?? null,
      env_vars: { ...(persona.envVars ?? {}) },
      created_at: now,
      updated_at: persona.updatedAt ?? now,
    });
  }
}

function resetMockTeams(config?: E2eConfig) {
  const now = new Date().toISOString();
  mockTeams = [
    {
      id: "team-engineering-001",
      name: "Engineering",
      description: "Core engineering personas",
      persona_ids: [],
      is_builtin: false,
      source_dir: null,
      is_symlink: false,
      symlink_target: null,
      version: null,
      created_at: now,
      updated_at: now,
    },
    {
      id: "team-research-002",
      name: "Research Agents",
      description: "Directory-backed research team",
      persona_ids: [],
      is_builtin: false,
      source_dir: "/Users/dev/agents/research",
      is_symlink: false,
      symlink_target: null,
      version: "1.2.0",
      created_at: now,
      updated_at: now,
    },
    {
      id: "team-platform-003",
      name: "Platform Tools",
      description: "Symlinked platform team",
      persona_ids: [],
      is_builtin: false,
      source_dir: "/Users/dev/agents/platform",
      is_symlink: true,
      symlink_target: "/opt/shared-teams/platform",
      version: "2.0.1",
      created_at: now,
      updated_at: now,
    },
  ];

  for (const team of config?.mock?.teams ?? []) {
    mockTeams.push({
      id: team.id ?? crypto.randomUUID(),
      name: team.name,
      description: team.description ?? null,
      persona_ids: [...team.personaIds],
      is_builtin: false,
      source_dir: null,
      is_symlink: false,
      symlink_target: null,
      version: null,
      created_at: now,
      updated_at: now,
    });
  }
}

function seedMockSearchProfiles(config?: E2eConfig) {
  for (const seed of config?.mock?.searchProfiles ?? []) {
    const pubkey = seed.pubkey.toLowerCase();
    const profile = {
      pubkey,
      display_name: seed.displayName,
      avatar_url: seed.avatarUrl ?? null,
      about: seed.about ?? null,
      nip05_handle: seed.nip05Handle ?? null,
      owner_pubkey: seed.ownerPubkey ?? null,
      is_agent: seed.isAgent ?? false,
      has_profile_event: true,
    };
    mockProfiles.set(pubkey, profile);
    applyMockDisplayName(pubkey, seed.displayName);
    if (seed.isAgent) {
      mockAgentPubkeys.add(pubkey);
    }
  }
}

function getMockProfileByPubkey(pubkey: string): RawProfile | null {
  const normalizedPubkey = pubkey.toLowerCase();
  const existing = mockProfiles.get(normalizedPubkey);
  if (existing) {
    return existing;
  }

  if (!mockDisplayNames.has(normalizedPubkey)) {
    return null;
  }

  return {
    pubkey: normalizedPubkey,
    display_name: mockDisplayNames.get(normalizedPubkey) ?? null,
    name: mockKind0Names.get(normalizedPubkey) ?? null,
    avatar_url: null,
    about: null,
    nip05_handle: null,
    owner_pubkey: null,
    is_agent: mockAgentPubkeys.has(normalizedPubkey),
    has_profile_event: true,
  };
}

function listMockProfiles(): RawProfile[] {
  const pubkeys = new Set<string>([
    ...mockProfiles.keys(),
    ...mockDisplayNames.keys(),
    DEFAULT_REAL_IDENTITY.pubkey,
  ]);

  return [...pubkeys]
    .map((pubkey) => getMockProfileByPubkey(pubkey))
    .filter((profile): profile is RawProfile => profile !== null);
}

function listMockChannels(config?: E2eConfig): RawChannelWithMembership[] {
  return mockChannels.map((channel) => toRawChannel(channel, config));
}

function getMockChannel(channelId: string): MockChannel {
  const channel = mockChannels.find((candidate) => candidate.id === channelId);
  if (!channel) {
    throw new Error(`Channel ${channelId} not found.`);
  }

  return channel;
}

function getMockMemberPubkey(config: E2eConfig | undefined): string {
  return getActiveIdentity(config)?.pubkey ?? getMockIdentity().pubkey;
}

function getMockMemberDisplayName(config: E2eConfig | undefined): string {
  return getActiveIdentity(config)?.username ?? getMockIdentity().displayName;
}

function createCurrentMember(
  config: E2eConfig | undefined,
  role: RawChannelMember["role"],
): RawChannelMember {
  return {
    pubkey: getMockMemberPubkey(config),
    role,
    joined_at: new Date().toISOString(),
    display_name: getMockMemberDisplayName(config),
  };
}

const mockChannels: MockChannel[] = [
  createMockChannel({
    id: STARTER_GENERAL_CHANNEL_ID,
    name: STARTER_GENERAL_CHANNEL_NAME,
    channel_type: "stream",
    visibility: "open",
    description: "General discussion for everyone",
    topic: "Company-wide updates",
    purpose: "Coordinate day-to-day work and unblock the team.",
    last_message_at: isoMinutesAgo(5),
    archived_at: null,
    created_by: MOCK_IDENTITY_PUBKEY,
    topic_set_by: MOCK_IDENTITY_PUBKEY,
    topic_set_at: isoMinutesAgo(90),
    purpose_set_by: MOCK_IDENTITY_PUBKEY,
    purpose_set_at: isoMinutesAgo(80),
    topic_required: false,
    max_members: null,
    nip29_group_id: null,
    created_minutes_ago: 1440,
    updated_minutes_ago: 5,
    members: [
      createMockMember(MOCK_IDENTITY_PUBKEY, "owner", 1440),
      createMockMember(ALICE_PUBKEY, "admin", 1200),
      createMockMember(BOB_PUBKEY, "member", 960),
      createMockMember(PROFILE_ONLY_AGENT_PUBKEY, "member", 840),
    ],
  }),
  createMockChannel({
    id: STARTER_WELCOME_CHANNEL_ID,
    name: STARTER_WELCOME_CHANNEL_NAME,
    channel_type: "stream",
    visibility: "open",
    description: "Say hi, ask a question, or share what brought you here.",
    topic: null,
    purpose: null,
    last_message_at: null,
    archived_at: null,
    created_by: MOCK_IDENTITY_PUBKEY,
    topic_set_by: null,
    topic_set_at: null,
    purpose_set_by: null,
    purpose_set_at: null,
    topic_required: false,
    max_members: null,
    nip29_group_id: null,
    created_minutes_ago: 1440,
    updated_minutes_ago: 1440,
    members: [createMockMember(MOCK_IDENTITY_PUBKEY, "owner", 1440)],
  }),
  createMockChannel({
    id: "9dae0116-799b-5071-a0a8-fdd30a91a35d",
    name: "random",
    channel_type: "stream",
    visibility: "open",
    description: "Off-topic, fun stuff",
    topic: null,
    purpose: null,
    last_message_at: null,
    archived_at: null,
    created_by: ALICE_PUBKEY,
    topic_set_by: null,
    topic_set_at: null,
    purpose_set_by: null,
    purpose_set_at: null,
    topic_required: false,
    max_members: null,
    nip29_group_id: null,
    created_minutes_ago: 1400,
    updated_minutes_ago: 1400,
    members: [
      createMockMember(ALICE_PUBKEY, "owner", 1400),
      createMockMember(MOCK_IDENTITY_PUBKEY, "member", 1300),
      createMockMember(BOB_PUBKEY, "member", 1000),
    ],
  }),
  // Reproduces the all-replies-window regression (NIP-RS Fix A): a busy
  // single-thread channel whose top-level root has scrolled past the history
  // limit. `last_message_at` is a far-future timestamp standing in for the
  // backend's reply-inclusive MAX(created_at) — it is NEWER than any top-level
  // message the window can load, so falling back to it would advance the
  // channel marker past unread replies. Seeded as its own channel so existing
  // channels' unread state is undisturbed.
  createMockChannel({
    id: "fa11bac0-0000-4000-8000-000000000012",
    name: "all-replies",
    channel_type: "stream",
    visibility: "open",
    description: "Single-thread channel with the root past the history limit",
    topic: null,
    purpose: null,
    last_message_at: new Date("2999-01-01T00:00:00.000Z").toISOString(),
    archived_at: null,
    created_by: ALICE_PUBKEY,
    topic_set_by: null,
    topic_set_at: null,
    purpose_set_by: null,
    purpose_set_at: null,
    topic_required: false,
    max_members: null,
    nip29_group_id: null,
    created_minutes_ago: 1400,
    updated_minutes_ago: 1400,
    members: [
      createMockMember(ALICE_PUBKEY, "owner", 1400),
      createMockMember(MOCK_IDENTITY_PUBKEY, "member", 1300),
    ],
  }),
  createMockChannel({
    id: "b5e2f8a1-3c44-5912-9e67-4a8d1f2b3c4e",
    name: "design",
    channel_type: "stream",
    visibility: "open",
    description: "Design system and UX discussions with engineering partners",
    topic: null,
    purpose: null,
    last_message_at: isoMinutesAgo(120),
    archived_at: null,
    created_by: ALICE_PUBKEY,
    topic_set_by: null,
    topic_set_at: null,
    purpose_set_by: null,
    purpose_set_at: null,
    topic_required: false,
    max_members: null,
    nip29_group_id: null,
    created_minutes_ago: 1350,
    updated_minutes_ago: 120,
    members: [
      createMockMember(ALICE_PUBKEY, "owner", 1350),
      createMockMember(BOB_PUBKEY, "member", 1100),
    ],
  }),
  createMockChannel({
    id: "c6f3a9b2-4d55-5a23-bf78-5b9e2g3c5d6f",
    name: "sales",
    channel_type: "stream",
    visibility: "open",
    description: "Sales team coordination and pipeline updates",
    topic: "Q1 targets",
    purpose: null,
    last_message_at: isoMinutesAgo(30),
    archived_at: null,
    created_by: BOB_PUBKEY,
    topic_set_by: BOB_PUBKEY,
    topic_set_at: isoMinutesAgo(200),
    purpose_set_by: null,
    purpose_set_at: null,
    topic_required: false,
    max_members: null,
    nip29_group_id: null,
    created_minutes_ago: 1300,
    updated_minutes_ago: 30,
    members: [
      createMockMember(BOB_PUBKEY, "owner", 1300),
      createMockMember(CHARLIE_PUBKEY, "member", 900),
    ],
  }),
  createMockChannel({
    id: "1c7e1c02-87bb-5e88-b2da-5a7a9432d0c9",
    name: "engineering",
    channel_type: "stream",
    visibility: "open",
    description: "Engineering discussions",
    topic: "Desktop release train",
    purpose: "Track implementation details and release readiness.",
    last_message_at: isoMinutesAgo(42),
    archived_at: null,
    created_by: ALICE_PUBKEY,
    topic_set_by: ALICE_PUBKEY,
    topic_set_at: isoMinutesAgo(120),
    purpose_set_by: ALICE_PUBKEY,
    purpose_set_at: isoMinutesAgo(130),
    topic_required: false,
    max_members: null,
    nip29_group_id: null,
    created_minutes_ago: 1320,
    updated_minutes_ago: 42,
    members: [
      createMockMember(ALICE_PUBKEY, "owner", 1320),
      createMockMember(MOCK_IDENTITY_PUBKEY, "member", 1180),
      createMockMember(BOB_PUBKEY, "member", 900),
    ],
  }),
  createMockChannel({
    id: "94a444a4-c0a3-5966-ab05-530c6ddc2301",
    name: "agents",
    channel_type: "stream",
    visibility: "open",
    description: "AI agent testing and collaboration",
    topic: "Coordination board",
    purpose: "Track agent work and relay activity.",
    last_message_at: isoMinutesAgo(15),
    archived_at: null,
    created_by: MOCK_IDENTITY_PUBKEY,
    topic_set_by: MOCK_IDENTITY_PUBKEY,
    topic_set_at: isoMinutesAgo(60),
    purpose_set_by: MOCK_IDENTITY_PUBKEY,
    purpose_set_at: isoMinutesAgo(65),
    topic_required: false,
    max_members: null,
    nip29_group_id: null,
    created_minutes_ago: 1000,
    updated_minutes_ago: 15,
    members: [
      createMockMember(MOCK_IDENTITY_PUBKEY, "owner", 1000),
      createMockMember(CHARLIE_PUBKEY, "bot", 800),
      createMockMember(OWNED_RELAY_AGENT_PUBKEY, "member", 600),
    ],
  }),
  createMockChannel({
    id: "a27e1ee9-76a6-5bdf-a5d5-1d85610dad11",
    name: "watercooler",
    channel_type: "forum",
    visibility: "open",
    description: "Casual forum for async discussions",
    topic: null,
    purpose: null,
    last_message_at: null,
    archived_at: null,
    created_by: ALICE_PUBKEY,
    topic_set_by: null,
    topic_set_at: null,
    purpose_set_by: null,
    purpose_set_at: null,
    topic_required: false,
    max_members: null,
    nip29_group_id: null,
    created_minutes_ago: 900,
    updated_minutes_ago: 900,
    members: [
      createMockMember(ALICE_PUBKEY, "owner", 900),
      createMockMember(MOCK_IDENTITY_PUBKEY, "member", 750),
    ],
  }),
  createMockChannel({
    id: "1be1dcdb-4c31-5a8c-81de-ac102552ca10",
    name: "announcements",
    channel_type: "forum",
    visibility: "private",
    description: "Company announcements",
    topic: "Leadership updates",
    purpose: "Read-only announcements for the community.",
    last_message_at: null,
    archived_at: null,
    created_by: ALICE_PUBKEY,
    topic_set_by: ALICE_PUBKEY,
    topic_set_at: isoMinutesAgo(200),
    purpose_set_by: ALICE_PUBKEY,
    purpose_set_at: isoMinutesAgo(210),
    topic_required: false,
    max_members: null,
    nip29_group_id: null,
    created_minutes_ago: 880,
    updated_minutes_ago: 200,
    members: [
      createMockMember(ALICE_PUBKEY, "owner", 880),
      createMockMember(MOCK_IDENTITY_PUBKEY, "guest", 700),
    ],
  }),
  createMockChannel({
    id: "3c2d9f0a-1b44-5e77-9a21-6f8b0c4d2e91",
    name: "secret-projects",
    channel_type: "stream",
    visibility: "private",
    description: "Private project room",
    topic: "Skunkworks",
    purpose: "Coordinate confidential project work.",
    last_message_at: null,
    archived_at: null,
    created_by: ALICE_PUBKEY,
    topic_set_by: ALICE_PUBKEY,
    topic_set_at: isoMinutesAgo(120),
    purpose_set_by: ALICE_PUBKEY,
    purpose_set_at: isoMinutesAgo(130),
    topic_required: false,
    max_members: null,
    nip29_group_id: null,
    created_minutes_ago: 600,
    updated_minutes_ago: 120,
    members: [
      createMockMember(ALICE_PUBKEY, "owner", 600),
      createMockMember(MOCK_IDENTITY_PUBKEY, "member", 540),
    ],
  }),
  createMockChannel({
    id: "f48efb06-0c93-5025-aac9-2e646bb6bfa8",
    name: "alice-tyler",
    channel_type: "dm",
    visibility: "private",
    description: "DM between alice and tyler",
    topic: null,
    purpose: null,
    last_message_at: null,
    archived_at: null,
    created_by: ALICE_PUBKEY,
    topic_set_by: null,
    topic_set_at: null,
    purpose_set_by: null,
    purpose_set_at: null,
    topic_required: false,
    max_members: 2,
    nip29_group_id: null,
    created_minutes_ago: 720,
    updated_minutes_ago: 720,
    participants: ["alice", "tyler"],
    participant_pubkeys: [ALICE_PUBKEY, MOCK_IDENTITY_PUBKEY],
    members: [
      createMockMember(ALICE_PUBKEY, "member", 720),
      createMockMember(MOCK_IDENTITY_PUBKEY, "member", 720),
    ],
  }),
  createMockChannel({
    id: "7eb9f239-9393-50b0-bd76-d85eef0511c7",
    name: "bob-tyler",
    channel_type: "dm",
    visibility: "private",
    description: "DM between bob and tyler",
    topic: null,
    purpose: null,
    last_message_at: null,
    archived_at: null,
    created_by: BOB_PUBKEY,
    topic_set_by: null,
    topic_set_at: null,
    purpose_set_by: null,
    purpose_set_at: null,
    topic_required: false,
    max_members: 2,
    nip29_group_id: null,
    created_minutes_ago: 700,
    updated_minutes_ago: 700,
    participants: ["bob", "tyler"],
    participant_pubkeys: [BOB_PUBKEY, MOCK_IDENTITY_PUBKEY],
    members: [
      createMockMember(BOB_PUBKEY, "member", 700),
      createMockMember(MOCK_IDENTITY_PUBKEY, "member", 700),
    ],
  }),
  // Generic-named DM — name is "DM" so resolveChannelDisplayLabel must resolve
  // the participant display name instead of returning the raw channel name.
  // Used by agent-snapshot-send.spec.ts to prove picker/search/memgate/done
  // all use the same resolved label from useUsersBatchQuery.
  createMockChannel({
    id: "d1ec7000-d000-4000-8000-000000000001",
    name: "DM",
    channel_type: "dm",
    visibility: "private",
    description: "Generic-named DM with charlie",
    topic: null,
    purpose: null,
    last_message_at: null,
    archived_at: null,
    created_by: CHARLIE_PUBKEY,
    topic_set_by: null,
    topic_set_at: null,
    purpose_set_by: null,
    purpose_set_at: null,
    topic_required: false,
    max_members: 2,
    nip29_group_id: null,
    created_minutes_ago: 680,
    updated_minutes_ago: 680,
    participants: ["charlie", "tyler"],
    participant_pubkeys: [CHARLIE_PUBKEY, MOCK_IDENTITY_PUBKEY],
    members: [
      createMockMember(CHARLIE_PUBKEY, "member", 680),
      createMockMember(MOCK_IDENTITY_PUBKEY, "member", 680),
    ],
  }),
  // Generic-named Group DM — name "Group DM (3)" so resolveChannelDisplayLabel
  // must resolve all OTHER participants' display names (bob, charlie).
  // Used by agent-snapshot-send.spec.ts group-DM label test.
  // NOTE: participants are BOB + CHARLIE (not ALICE, which conflicts with
  // ANALYST_PUBKEY in managed-agent tests).
  createMockChannel({
    id: "d1ec7000-d000-4000-8000-000000000003",
    name: "Group DM (3)",
    channel_type: "dm",
    visibility: "private",
    description: "Generic-named group DM with bob and charlie",
    topic: null,
    purpose: null,
    last_message_at: null,
    archived_at: null,
    created_by: BOB_PUBKEY,
    topic_set_by: null,
    topic_set_at: null,
    purpose_set_by: null,
    purpose_set_at: null,
    topic_required: false,
    max_members: 3,
    nip29_group_id: null,
    created_minutes_ago: 660,
    updated_minutes_ago: 660,
    participants: ["bob", "charlie", "tyler"],
    participant_pubkeys: [BOB_PUBKEY, CHARLIE_PUBKEY, MOCK_IDENTITY_PUBKEY],
    members: [
      createMockMember(BOB_PUBKEY, "member", 660),
      createMockMember(CHARLIE_PUBKEY, "member", 660),
      createMockMember(MOCK_IDENTITY_PUBKEY, "member", 660),
    ],
  }),
  // Deep history channel for the load-older-under-virtualization E2E. Seeded
  // with more messages than CHANNEL_HISTORY_LIMIT (300) so the initial load
  // windows to the newest page and a `fetchOlder` (until-cursor) prepend has
  // genuinely older rows to add — exercising the scroll-restore anchor under
  // virtualization. Its own channel so existing channels' row-index and unread
  // assertions stay undisturbed.
  createMockChannel({
    id: "feedf00d-0000-4000-8000-000000000007",
    name: "deep-history",
    channel_type: "stream",
    visibility: "open",
    description: "Channel with paginated history for load-older tests",
    topic: null,
    purpose: null,
    last_message_at: isoMinutesAgo(1),
    archived_at: null,
    created_by: ALICE_PUBKEY,
    topic_set_by: null,
    topic_set_at: null,
    purpose_set_by: null,
    purpose_set_at: null,
    topic_required: false,
    max_members: null,
    nip29_group_id: null,
    created_minutes_ago: 2000,
    updated_minutes_ago: 1,
    members: [
      createMockMember(ALICE_PUBKEY, "owner", 2000),
      createMockMember(MOCK_IDENTITY_PUBKEY, "member", 1900),
    ],
  }),
];

const mockMessages = new Map<string, RelayEvent[]>();
const deferredSendMessageLiveEchoes: Array<{
  channelId: string;
  event: RelayEvent;
}> = [];
const mockUserStatuses: RelayEvent[] = [];
const mockReminderEvents: RelayEvent[] = [];
const mockPersonaEvents: RelayEvent[] = [];
let mockRelayMembers: RawRelayMember[] = [];
const mockSockets = new Map<number, MockSocket>();
const mockAuthResponses: Array<{ success: boolean; message: string }> = [];
const mockChannelHistoryCloses: string[] = [];
let mockWebsocketUnavailable = false;
const relayWebsocketConnectAttemptStarts: number[] = [];
let mockWebsocketSendMutexWedged = false;
let mockClosedChannelLiveSubscription = false;
const realSockets = new Map<number, WebSocket>();
let mockManagedAgents: MockManagedAgent[] = [];
let mockManagedAgentRuntimes: MockManagedAgentRuntimeRow[] = [];

// Mutable `save_subscriptions` table mirror — TEST-ONLY.
//
// Cloned from `activeConfig.mock.saveSubscriptions` at install time, then
// mutated by `create_save_subscription` / `delete_save_subscription` /
// `merge_save_subscription_kinds` / `remove_save_subscription_kind` exactly
// as the real SQLite-backed Rust commands would (see `archive/store.rs`).
// This lets E2E specs drive the default-on seeding path (start from `[]`,
// reconcile, observe a kind-24200 row appear) and toggle ON/OFF, neither
// of which an immutable seed can represent.
type MockSaveSubscriptionRow = {
  scope_type: string;
  scope_value: string;
  kinds: string; // JSON-encoded integer array, e.g. "[9,40002]"
};
let mockSaveSubscriptions: MockSaveSubscriptionRow[] = [];

function resetMockSaveSubscriptions(config: E2eConfig | undefined) {
  mockSaveSubscriptions = (config?.mock?.saveSubscriptions ?? []).map((s) => ({
    ...s,
  }));
}

function resetMockPersonaCatalogEvents(config: E2eConfig | undefined) {
  mockPersonaEvents.length = 0;
  for (const event of config?.mock?.personaCatalogEvents ?? []) {
    mockPersonaEvents.push({
      ...event,
      tags: event.tags.map((tag) => [...tag]),
    });
  }
}

// Mesh-compute mock state — TEST-ONLY.
//
// This entire module (e2eBridge.ts) is loaded only when `window.__BUZZ_E2E__`
// is set by the Playwright harness; it never runs in a shipped build. These
// handlers stub the `mesh_*` Tauri commands with the SHAPES the UI expects
// (availability, node status, preset) so the desktop UI flow can be exercised
// in a browser. They deliberately do NOT model real admission, real inference,
// or real mesh routing — those are proven by the Rust layer-2 tests and the
// on-hardware layer-1 example. Do not port any of this into production code.
type MockServingUsage = {
  inflight: number;
  peakInflight: number;
  requestsServed: number;
  tokensServed: number;
  tokensPerSecond: number;
  localAttempts: number;
  remoteAttempts: number;
  endpointAttempts: number;
  peers: number;
};

const ZERO_SERVING_USAGE: MockServingUsage = {
  inflight: 0,
  peakInflight: 0,
  requestsServed: 0,
  tokensServed: 0,
  tokensPerSecond: 0,
  localAttempts: 0,
  remoteAttempts: 0,
  endpointAttempts: 0,
  peers: 0,
};

const mockMeshState: {
  admitted: boolean;
  models: Array<{ id: string; name: string | null }>;
  activeModel: { id: string; name: string | null } | null;
  denyReason: string;
  nodeState: "off" | "running";
  nodeMode: "serve" | "client" | null;
  servingUsage: MockServingUsage;
} = {
  admitted: true,
  models: [{ id: "Gemma-4-E4B-it-Q4_K_M", name: "Gemma 4 E4B" }],
  activeModel: null,
  denyReason: "not a relay member",
  nodeState: "off",
  nodeMode: null,
  servingUsage: { ...ZERO_SERVING_USAGE },
};

function resetMockMesh() {
  mockMeshState.admitted = true;
  mockMeshState.models = [{ id: "Gemma-4-E4B-it-Q4_K_M", name: "Gemma 4 E4B" }];
  mockMeshState.activeModel = null;
  mockMeshState.denyReason = "not a relay member";
  mockMeshState.nodeState = "off";
  mockMeshState.nodeMode = null;
  mockMeshState.servingUsage = { ...ZERO_SERVING_USAGE };
}
let mockPersonas: RawPersona[] = [];
let mockTeams: RawTeam[] = [];

type MockHuddleState = {
  phase: "creating" | "connected" | "active";
  parent_channel_id: string;
  ephemeral_channel_id: string;
  huddle_thread_event_id: string | null;
  participants: string[];
  agent_pubkeys: string[];
  agent_voice_settings: Record<string, { enabled: boolean; voice_key: string }>;
  tts_enabled: boolean;
  transcription_enabled: boolean;
  is_creator: boolean;
  voice_input_mode: "push_to_talk" | "voice_activity";
};

type PersistedMockHuddle = {
  members: MockHuddleMemberSeed[];
  state: MockHuddleState;
};

const MOCK_HUDDLE_STORAGE_KEY = "buzz.e2e.mock-huddle.v1";
let mockHuddle: PersistedMockHuddle | null = null;

function persistMockHuddle() {
  if (mockHuddle) {
    window.sessionStorage.setItem(
      MOCK_HUDDLE_STORAGE_KEY,
      JSON.stringify(mockHuddle),
    );
  }
}

async function emitMockHuddleState() {
  if (!mockHuddle) return;
  await emit("huddle-state-changed", structuredClone(mockHuddle.state));
}

const MOCK_POCKET_VOICE_KEYS = [
  "pocket:anna",
  "pocket:vera",
  "pocket:fantine",
  "pocket:charles",
  "pocket:paul",
  "pocket:eponine",
  "pocket:azelma",
  "pocket:george",
  "pocket:mary",
  "pocket:jane",
  "pocket:michael",
  "pocket:eve",
];

function refreshMockHuddleMembership(config?: E2eConfig | null) {
  if (!mockHuddle) return;
  mockHuddle.state.agent_pubkeys = mockHuddle.members
    .filter((member) => member.role === "bot")
    .map((member) => member.pubkey);
  mockHuddle.state.participants = mockHuddle.members.map(
    (member) => member.pubkey,
  );
  mockHuddle.state.agent_voice_settings ??= {};
  const agentSet = new Set(mockHuddle.state.agent_pubkeys);
  for (const pubkey of Object.keys(mockHuddle.state.agent_voice_settings)) {
    if (!agentSet.has(pubkey)) {
      delete mockHuddle.state.agent_voice_settings[pubkey];
    }
  }
  const defaultVoice =
    config?.mock?.ttsSettings?.voicePreferences.find((key) =>
      key.startsWith("pocket:"),
    ) ?? "pocket:mary";
  const used = new Set(
    Object.values(mockHuddle.state.agent_voice_settings).map(
      (settings) => settings.voice_key,
    ),
  );
  mockHuddle.state.agent_pubkeys.forEach((pubkey, index) => {
    if (mockHuddle?.state.agent_voice_settings[pubkey]) return;
    const voiceKey =
      index === 0 && !used.has(defaultVoice)
        ? defaultVoice
        : (MOCK_POCKET_VOICE_KEYS.find(
            (key) => key !== defaultVoice && !used.has(key),
          ) ?? defaultVoice);
    used.add(voiceKey);
    if (mockHuddle) {
      mockHuddle.state.agent_voice_settings[pubkey] = {
        enabled: true,
        voice_key: voiceKey,
      };
    }
  });
}

function initializeMockHuddle(
  seed: MockHuddleSeed | undefined,
  config?: E2eConfig,
) {
  mockHuddle = null;
  if (!seed) {
    window.sessionStorage.removeItem(MOCK_HUDDLE_STORAGE_KEY);
    return;
  }

  const persisted = window.sessionStorage.getItem(MOCK_HUDDLE_STORAGE_KEY);
  if (persisted) {
    try {
      mockHuddle = JSON.parse(persisted) as PersistedMockHuddle;
    } catch {
      window.sessionStorage.removeItem(MOCK_HUDDLE_STORAGE_KEY);
    }
  }

  if (!mockHuddle) {
    mockHuddle = {
      members: structuredClone(seed.members),
      state: {
        phase: seed.phase ?? "active",
        parent_channel_id: seed.parentChannelId,
        ephemeral_channel_id: seed.ephemeralChannelId,
        huddle_thread_event_id: seed.huddleThreadEventId ?? null,
        participants: [],
        agent_pubkeys: [],
        agent_voice_settings: {},
        tts_enabled: seed.ttsEnabled ?? false,
        transcription_enabled: seed.transcriptionEnabled ?? false,
        is_creator: seed.isCreator ?? true,
        voice_input_mode: "push_to_talk",
      },
    };
  }
  if (!mockChannels.some((channel) => channel.id === seed.ephemeralChannelId)) {
    const owner = createCurrentMember(config, "owner");
    mockChannels.push(
      createMockChannel({
        id: seed.ephemeralChannelId,
        name: "huddle",
        channel_type: "stream",
        visibility: "private",
        description: "",
        topic: null,
        purpose: null,
        last_message_at: null,
        archived_at: null,
        created_by: owner.pubkey,
        topic_set_by: null,
        topic_set_at: null,
        purpose_set_by: null,
        purpose_set_at: null,
        topic_required: false,
        max_members: null,
        nip29_group_id: null,
        ttl_seconds: 3_600,
        created_minutes_ago: 0,
        members: [owner],
      }),
    );
  }
  refreshMockHuddleMembership(config);
  persistMockHuddle();
}
const openedExternalUrls: string[] = [];
const defaultMockRelayAgents: RawRelayAgent[] = [
  {
    pubkey: ALICE_PUBKEY,
    name: "alice",
    agent_type: "goose",
    channels: ["general", "agents"],
    channel_ids: [
      "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50",
      "94a444a4-c0a3-5966-ab05-530c6ddc2301",
    ],
    capabilities: ["search", "summaries", "workflows"],
    status: "online",
    respond_to: "anyone",
    respond_to_allowlist: [],
  },
  {
    pubkey: CHARLIE_PUBKEY,
    name: "charlie",
    agent_type: "codex",
    channels: ["general"],
    channel_ids: ["9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50"],
    capabilities: ["code", "reviews"],
    status: "away",
    respond_to: "anyone",
    respond_to_allowlist: [],
  },
];
let mockRelayAgents: RawRelayAgent[] = defaultMockRelayAgents.map((agent) => ({
  ...agent,
  channels: [...agent.channels],
  channel_ids: [...agent.channel_ids],
  capabilities: [...agent.capabilities],
  respond_to_allowlist: [...(agent.respond_to_allowlist ?? [])],
}));

// ── Workflow mocks ─────────────────────────────────────────────────────────

type MockWorkflow = {
  id: string;
  name: string;
  owner_pubkey: string;
  channel_id: string | null;
  definition: Record<string, unknown>;
  status: "active" | "disabled" | "archived";
  created_at: number;
  updated_at: number;
};

type RawWorkflowTraceEntry = {
  step_id: string;
  status: string;
  output?: Record<string, unknown>;
  started_at?: number | null;
  completed_at?: number | null;
  error?: string | null;
};

type RawWorkflowRun = {
  id: string;
  workflow_id: string;
  status:
    | "pending"
    | "running"
    | "completed"
    | "failed"
    | "cancelled"
    | "waiting_approval";
  current_step: number | null;
  execution_trace: RawWorkflowTraceEntry[];
  started_at: number | null;
  completed_at: number | null;
  error_code: string | null;
  error_message: string | null;
  created_at: number;
};

const mockWorkflows: MockWorkflow[] = [];
let mockWorkflowRuns: RawWorkflowRun[] = [];
let mockWorkflowIdCounter = 0;

function resetMockWorkflows() {
  mockWorkflows.length = 0;
  mockWorkflowRuns = [];
  mockWorkflowIdCounter = 0;
}

function parseWorkflowDefinition(
  yamlDefinition: string,
): Record<string, unknown> {
  const parsed = yamlParse(yamlDefinition);
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
    throw new Error("Workflow definition must be a YAML object");
  }
  return parsed as Record<string, unknown>;
}

function handleGetChannelWorkflows(args: { channelId: string }) {
  return mockWorkflows.filter((w) => w.channel_id === args.channelId);
}

function handleGetChannelsWorkflows(args: { channelIds: string[] }) {
  const ids = new Set(args.channelIds);
  return mockWorkflows.filter(
    (w) => w.channel_id != null && ids.has(w.channel_id),
  );
}

function handleGetWorkflow(args: { workflowId: string }) {
  const workflow = mockWorkflows.find((w) => w.id === args.workflowId);
  if (!workflow) throw new Error(`Workflow ${args.workflowId} not found`);
  return workflow;
}

function handleCreateWorkflow(args: {
  channelId: string;
  yamlDefinition: string;
}) {
  mockWorkflowIdCounter += 1;
  const now = Math.floor(Date.now() / 1000);
  const definition = parseWorkflowDefinition(args.yamlDefinition);
  const name =
    typeof definition.name === "string"
      ? definition.name
      : `workflow_${mockWorkflowIdCounter}`;
  const workflow: MockWorkflow = {
    id: `mock-wf-${mockWorkflowIdCounter}`,
    name,
    owner_pubkey: MOCK_IDENTITY_PUBKEY,
    channel_id: args.channelId,
    definition,
    status: "active",
    created_at: now,
    updated_at: now,
  };
  mockWorkflows.push(workflow);

  const trigger = definition.trigger as Record<string, unknown> | undefined;
  return {
    ...workflow,
    webhook_secret:
      trigger?.on === "webhook"
        ? `mock-webhook-secret-${mockWorkflowIdCounter}`
        : undefined,
  };
}

function handleUpdateWorkflow(args: {
  workflowId: string;
  yamlDefinition: string;
}) {
  const workflow = mockWorkflows.find((w) => w.id === args.workflowId);
  if (!workflow) throw new Error(`Workflow ${args.workflowId} not found`);
  const definition = parseWorkflowDefinition(args.yamlDefinition);
  if (typeof definition.name === "string") workflow.name = definition.name;
  workflow.definition = definition;
  workflow.updated_at = Math.floor(Date.now() / 1000);

  const trigger = definition.trigger as Record<string, unknown> | undefined;
  return {
    ...workflow,
    webhook_secret:
      trigger?.on === "webhook"
        ? `mock-webhook-secret-${workflow.id}`
        : undefined,
  };
}

function handleDeleteWorkflow(args: { workflowId: string }) {
  const index = mockWorkflows.findIndex((w) => w.id === args.workflowId);
  if (index === -1) throw new Error(`Workflow ${args.workflowId} not found`);
  mockWorkflows.splice(index, 1);
  mockWorkflowRuns = mockWorkflowRuns.filter(
    (run) => run.workflow_id !== args.workflowId,
  );
}

function buildMockWorkflowRun(workflow: MockWorkflow): RawWorkflowRun {
  const createdAt = Math.floor(Date.now() / 1000);
  const rawSteps = Array.isArray(workflow.definition.steps)
    ? workflow.definition.steps
    : [];
  const executionTrace = rawSteps.map((candidate, index) => {
    const step =
      candidate && typeof candidate === "object"
        ? (candidate as Record<string, unknown>)
        : {};
    const startedAt = createdAt + index;
    const completedAt = startedAt + 1;
    const output: Record<string, unknown> = {};

    if (typeof step.action === "string") {
      output.action = step.action;
    }
    if (typeof step.name === "string" && step.name.trim().length > 0) {
      output.name = step.name;
    }
    if (typeof step.text === "string" && step.text.trim().length > 0) {
      output.preview = step.text;
    }

    return {
      step_id:
        typeof step.id === "string" && step.id.trim().length > 0
          ? step.id
          : `step_${index + 1}`,
      status: "completed",
      output,
      started_at: startedAt,
      completed_at: completedAt,
      error: null,
    };
  });

  const startedAt =
    executionTrace.length > 0
      ? (executionTrace[0].started_at ?? createdAt)
      : createdAt;
  const lastTraceEntry = executionTrace[executionTrace.length - 1];
  const completedAt =
    executionTrace.length > 0
      ? (lastTraceEntry?.completed_at ?? createdAt)
      : createdAt;

  return {
    id: `mock-run-${Date.now()}`,
    workflow_id: workflow.id,
    status: "completed",
    current_step: null,
    execution_trace: executionTrace,
    started_at: startedAt,
    completed_at: completedAt,
    error_code: null,
    error_message: null,
    created_at: createdAt,
  };
}

function handleTriggerWorkflow(args: { workflowId: string }) {
  const workflow = mockWorkflows.find((w) => w.id === args.workflowId);
  if (!workflow) throw new Error(`Workflow ${args.workflowId} not found`);
  const run = buildMockWorkflowRun(workflow);
  mockWorkflowRuns = [run, ...mockWorkflowRuns];
  return {
    run_id: run.id,
    workflow_id: workflow.id,
    status: run.status,
  };
}

function handleGetWorkflowRuns(args: {
  limit?: number | null;
  workflowId: string;
}) {
  const runs = mockWorkflowRuns.filter(
    (run) => run.workflow_id === args.workflowId,
  );
  return {
    runs: args.limit ? runs.slice(0, args.limit) : runs,
    next: null,
  };
}

function handleGetRunApprovals(_args: { workflowId: string; runId: string }) {
  return { approvals: [] };
}

const mockProfiles = new Map<string, RawProfile>([
  [
    MOCK_IDENTITY_PUBKEY,
    {
      pubkey: MOCK_IDENTITY_PUBKEY,
      display_name: DEFAULT_MOCK_IDENTITY.display_name,
      avatar_url: null,
      about: null,
      nip05_handle: null,
      owner_pubkey: null,
      is_agent: false,
      has_profile_event: true,
    },
  ],
  // alice, bob, and charlie are intentionally NOT seeded here — they are
  // covered by mockDisplayNames + mockAgentPubkeys and synthesised on demand
  // by getMockProfileByPubkey. Static seeds would cause ensureMockProfile to
  // return has_profile_event:true when alice/bob/charlie are used as the
  // active first-run identity, incorrectly skipping onboarding page 1.
  [
    PROFILE_ONLY_AGENT_PUBKEY,
    {
      pubkey: PROFILE_ONLY_AGENT_PUBKEY,
      display_name: "mira",
      avatar_url: null,
      about: null,
      nip05_handle: null,
      owner_pubkey: MOCK_IDENTITY_PUBKEY,
      is_agent: true,
      has_profile_event: true,
    },
  ],
  [
    OWNED_RELAY_AGENT_PUBKEY,
    {
      pubkey: OWNED_RELAY_AGENT_PUBKEY,
      display_name: "nadia",
      avatar_url: null,
      about: null,
      nip05_handle: null,
      owner_pubkey: MOCK_IDENTITY_PUBKEY,
      is_agent: true,
      has_profile_event: true,
    },
  ],
]);
const mockPresence = new Map<string, PresenceStatus>([
  [MOCK_IDENTITY_PUBKEY, "offline"],
  [DEFAULT_REAL_IDENTITY.pubkey, "offline"],
  [ALICE_PUBKEY, "online"],
  [BOB_PUBKEY, "away"],
  [CHARLIE_PUBKEY, "online"],
  [PROFILE_ONLY_AGENT_PUBKEY, "online"],
  [OWNED_RELAY_AGENT_PUBKEY, "online"],
  [OUTSIDER_PUBKEY, "offline"],
]);
const mockFeedOverrides: RawHomeFeedResponse["feed"] = {
  mentions: [],
  needs_action: [],
  activity: [],
  agent_activity: [],
};

let installed = false;
let nextSocketId = 1;

function syncMockRelayAgentsFromManagedAgents() {
  const baseAgents = mockRelayAgents.filter(
    (agent) =>
      !mockManagedAgents.some((managed) => managed.pubkey === agent.pubkey),
  );
  const managedAgentsAsRelay: RawRelayAgent[] = mockManagedAgents.map(
    (agent) => {
      const memberships = getManagedAgentRelayMembership(agent.pubkey);

      return {
        pubkey: agent.pubkey,
        name: agent.name,
        agent_type: agent.agent_command,
        channels: memberships.channels,
        channel_ids: memberships.channelIds,
        capabilities: ["messages", "channels", "mcp"],
        status:
          agent.status === "running" || agent.status === "deployed"
            ? "online"
            : "offline",
        respond_to: agent.respond_to,
        respond_to_allowlist: [...agent.respond_to_allowlist],
      };
    },
  );

  mockRelayAgents = [...baseAgents, ...managedAgentsAsRelay];
}

function getManagedAgentRelayMembership(pubkey: string) {
  const memberships = mockChannels.filter((channel) =>
    channel.members.some((member) => member.pubkey === pubkey),
  );

  return {
    channelIds: memberships.map((channel) => channel.id),
    channels: memberships.map((channel) => channel.name),
  };
}

function getConfig(): E2eConfig | undefined {
  return window.__BUZZ_E2E__;
}

function readStoredIdentityOverride(): TestIdentity | undefined {
  try {
    const rawValue = window.localStorage.getItem(
      E2E_IDENTITY_OVERRIDE_STORAGE_KEY,
    );
    if (!rawValue) {
      return undefined;
    }

    const parsed = JSON.parse(rawValue);
    if (
      !parsed ||
      typeof parsed !== "object" ||
      typeof parsed.privateKey !== "string" ||
      typeof parsed.pubkey !== "string" ||
      typeof parsed.username !== "string"
    ) {
      return undefined;
    }

    return {
      privateKey: parsed.privateKey,
      pubkey: parsed.pubkey,
      username: parsed.username,
    };
  } catch {
    return undefined;
  }
}

function writeStoredIdentityOverride(identity: TestIdentity) {
  window.localStorage.setItem(
    E2E_IDENTITY_OVERRIDE_STORAGE_KEY,
    JSON.stringify(identity),
  );
}

function importMockIdentity(nsec: string) {
  const decoded = decode(nsec.trim());
  if (decoded.type !== "nsec") {
    throw new Error("Invalid Nostr private key.");
  }

  const privateKey = bytesToHex(decoded.data);
  const pubkey = getPublicKey(decoded.data);
  const username = mockDisplayNames.get(pubkey) ?? "";
  const identity = {
    privateKey,
    pubkey,
    username,
  };
  writeStoredIdentityOverride(identity);
  if (!mockProfiles.has(pubkey)) {
    mockProfiles.set(pubkey, {
      pubkey,
      display_name: username || null,
      avatar_url: null,
      about: null,
      nip05_handle: null,
      owner_pubkey: null,
      // A non-empty username means this identity is registered in
      // mockDisplayNames — it has a real mock relay profile (kind:0).
      // A truly new identity (no username) has no event yet.
      has_profile_event: username.length > 0,
    });
  }

  return {
    pubkey,
    display_name: username,
  };
}

function isRelayMode(config: E2eConfig | undefined): boolean {
  return config?.mode === "relay";
}

function getRelayHttpUrl(config: E2eConfig | undefined): string {
  return config?.relayHttpUrl ?? DEFAULT_RELAY_HTTP_URL;
}

function getRelayWsUrl(config: E2eConfig | undefined): string {
  return config?.relayWsUrl ?? DEFAULT_RELAY_WS_URL;
}

function getIdentity(config: E2eConfig | undefined): TestIdentity | undefined {
  if (!isRelayMode(config)) {
    return undefined;
  }

  return config?.identity ?? DEFAULT_REAL_IDENTITY;
}

function getActiveIdentity(config: E2eConfig | undefined) {
  return readStoredIdentityOverride() ?? getIdentity(config);
}

function ensureMockProfile(config: E2eConfig | undefined): RawProfile {
  const pubkey = getMockMemberPubkey(config);
  const existing = mockProfiles.get(pubkey);
  if (existing) {
    return existing;
  }

  const displayName = getMockMemberDisplayName(config);
  const profile = {
    pubkey,
    display_name: displayName,
    avatar_url: null,
    about: null,
    nip05_handle: null,
    owner_pubkey: null,
    // Synthesised fallback: no kind:0 event exists on the relay for this
    // identity. Always false regardless of display name so the onboarding
    // gate cannot mistake a blank first-run identity for a returning user.
    has_profile_event: false,
  };
  mockProfiles.set(pubkey, profile);
  return profile;
}

function applyMockDisplayName(pubkey: string, displayName: string | null) {
  if (displayName) {
    mockDisplayNames.set(pubkey, displayName);
  } else {
    mockDisplayNames.delete(pubkey);
  }

  for (const channel of mockChannels) {
    for (const member of channel.members) {
      if (member.pubkey === pubkey) {
        member.display_name = displayName;
      }
    }
    syncMockChannel(channel);
  }
}

function getMockPresenceStatus(pubkey: string): PresenceStatus {
  return mockPresence.get(pubkey.toLowerCase()) ?? "offline";
}

function setMockPresenceStatus(pubkey: string, status: PresenceStatus) {
  mockPresence.set(pubkey.toLowerCase(), status);
}

function resolveHandler(handler: unknown): WsHandler {
  if (typeof handler === "function") {
    return handler as WsHandler;
  }

  if (
    typeof handler === "object" &&
    handler !== null &&
    "onmessage" in handler &&
    typeof handler.onmessage === "function"
  ) {
    return handler.onmessage as WsHandler;
  }

  throw new Error("Invalid websocket message handler.");
}

function sendWsText(handler: WsHandler, payload: unknown[]) {
  handler({
    type: "Text",
    data: JSON.stringify(payload),
  });
}

function sendWsClose(handler: WsHandler, code?: number, reason?: string) {
  handler({
    type: "Close",
    data: code === undefined ? undefined : { code, reason: reason ?? "" },
  });
}

function getChannelIdFromTags(tags: string[][]): string | undefined {
  return tags.find((tag) => tag[0] === "h")?.[1];
}

function getThreadReferenceFromTags(tags: string[][]) {
  const eventTags = tags.filter(
    (tag) => tag[0] === "e" && typeof tag[1] === "string",
  );

  if (eventTags.length === 0) {
    return {
      parentEventId: null,
      rootEventId: null,
    };
  }

  const rootTag = eventTags.find((tag) => tag[3] === "root");
  const replyTag =
    [...eventTags].reverse().find((tag) => tag[3] === "reply") ?? null;

  if (!replyTag) {
    return {
      parentEventId: null,
      rootEventId: null,
    };
  }

  return {
    parentEventId: replyTag[1] ?? null,
    rootEventId: rootTag?.[1] ?? replyTag[1] ?? null,
  };
}

/**
 * A reply broadcast to the channel timeline carries the exact tag
 * `["broadcast", "1"]` (NIP-CW §Top-level Classification).
 */
function isMockBroadcastReply(tags: string[][]): boolean {
  return tags.some((tag) => tag[0] === "broadcast" && tag[1] === "1");
}

/**
 * Mirror the relay's channel-window row set (buzz-db `thread.rs`, NIP-CW
 * §Top-level Classification): an event is a timeline row iff its depth is 0
 * (no reply marker → `rootEventId === null`) OR its depth is 1 (its parent is
 * the thread root) AND it is broadcast. Depth ≥ 2 replies never surface on the
 * timeline. A bare-`rootEventId === null` predicate silently dropped broadcast
 * depth-1 replies the real relay serves.
 */
function isMockTopLevelRow(event: RelayEvent): boolean {
  const { parentEventId, rootEventId } = getThreadReferenceFromTags(event.tags);
  if (rootEventId === null) {
    return true;
  }
  const isDepthOne = parentEventId !== null && parentEventId === rootEventId;
  return isDepthOne && isMockBroadcastReply(event.tags);
}

function appendMentionTags(
  tags: string[][],
  mentionPubkeys: string[] | undefined,
  selfPubkey: string,
) {
  const selfLower = selfPubkey.toLowerCase();
  const seen = new Set<string>([selfLower]);
  for (const pk of mentionPubkeys ?? []) {
    const lower = pk.toLowerCase();
    if (seen.has(lower)) {
      continue;
    }
    seen.add(lower);
    tags.push(["p", lower]);
  }
}

function buildTopLevelMessageTags(
  channelId: string,
  mentionPubkeys: string[] | undefined,
  selfPubkey: string,
) {
  const tags: string[][] = [["h", channelId]];
  appendMentionTags(tags, mentionPubkeys, selfPubkey);
  return tags;
}

function buildReplyMessageTags(
  channelId: string,
  authorPubkey: string,
  parentEventId: string,
  rootEventId: string,
  mentionPubkeys: string[] | undefined,
) {
  // Preserve the reply tag ordering that the desktop message hooks already
  // expect locally: author p, h, mention ps, then thread e-tags.
  const tags: string[][] = [
    ["p", authorPubkey],
    ["h", channelId],
  ];
  appendMentionTags(tags, mentionPubkeys, authorPubkey);

  if (parentEventId === rootEventId) {
    tags.push(["e", rootEventId, "", "reply"]);
    return tags;
  }

  tags.push(["e", rootEventId, "", "root"]);
  tags.push(["e", parentEventId, "", "reply"]);
  return tags;
}

function getMockMessageStore(channelId: string): RelayEvent[] {
  const existing = mockMessages.get(channelId);
  if (existing) {
    return existing;
  }

  const seeded: RelayEvent[] =
    channelId === "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50"
      ? [
          {
            id: "mock-general-welcome",
            pubkey: DEFAULT_MOCK_IDENTITY.pubkey,
            created_at: Math.floor(Date.now() / 1000) - 120,
            kind: 9,
            tags: [["h", channelId]],
            content: "Welcome to #general",
            sig: "mocksig".repeat(20).slice(0, 128),
          },
          // Alice authored — gives e2e specs a non-self profile pane to open
          // by clicking the second message-row's author button. Used by
          // tests/e2e/identity-archive.spec.ts to exercise the admin / OA /
          // none-of-the-above branches of the NIP-IA gate. Both seeds are
          // backdated (welcome at -120s, Alice at -60s) so user-sent messages
          // in other specs always land after both — preserving
          // `message-row.first()` = welcome and `.last()` = sent.
          {
            id: "mock-general-alice",
            pubkey: ALICE_PUBKEY,
            created_at: Math.floor(Date.now() / 1000) - 60,
            kind: 9,
            tags: [["h", channelId]],
            content: "Hey team — checking in.",
            sig: "mocksig".repeat(20).slice(0, 128),
          },
          // Reaction-target seed for the custom-emoji reaction guard. Real
          // 64-hex id so getReactionTargetId() accepts it as a reaction target
          // (the short-id seeds above can't be reacted to). Backdated after the
          // other seeds, so it stays at row index >= 2 and never displaces
          // first()=welcome / nth(1)=alice that other specs rely on.
          {
            id: REACTION_TARGET_EVENT_ID,
            pubkey: ALICE_PUBKEY,
            created_at: Math.floor(Date.now() / 1000) - 45,
            kind: 9,
            tags: [["h", channelId]],
            content: REACTION_TARGET_CONTENT,
            sig: "mocksig".repeat(20).slice(0, 128),
          },
          // System-message reaction target. A kind:40099 join event renders via
          // SystemMessageRow (testid `system-message-row`, NOT `message-row`),
          // so it never displaces the `message-row` index assertions other
          // specs rely on. Real 64-hex id so getReactionTargetId() accepts it
          // as a reaction target — this is the surface the original "react to a
          // system message" bug lived on. Backdated like the other seeds.
          {
            id: SYSTEM_REACTION_TARGET_EVENT_ID,
            pubkey: ALICE_PUBKEY,
            created_at: Math.floor(Date.now() / 1000) - 30,
            kind: KIND_SYSTEM_MESSAGE,
            tags: [["h", channelId]],
            content: JSON.stringify({
              type: "member_joined",
              actor: ALICE_PUBKEY,
              target: ALICE_PUBKEY,
            }),
            sig: "mocksig".repeat(20).slice(0, 128),
          },
        ]
      : channelId === "a27e1ee9-76a6-5bdf-a5d5-1d85610dad11"
        ? [
            {
              id: "mock-forum-release-thread",
              pubkey:
                "953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f",
              created_at: Math.floor(Date.now() / 1000) - 90 * 60,
              kind: 45001,
              tags: [["h", channelId]],
              content: "Release checklist: async feedback thread.",
              sig: "mocksig".repeat(20).slice(0, 128),
            },
            {
              id: "mock-forum-release-reply",
              pubkey: ALICE_PUBKEY,
              created_at: Math.floor(Date.now() / 1000) - 80 * 60,
              kind: 45003,
              tags: buildReplyMessageTags(
                channelId,
                ALICE_PUBKEY,
                "mock-forum-release-thread",
                "mock-forum-release-thread",
                undefined,
              ),
              content: "Looks good to me. We should ship it.",
              sig: "mocksig".repeat(20).slice(0, 128),
            },
            // Filler replies so the thread overflows the panel viewport — the
            // deep-link target (mock-forum-release-deeplink) sits below the fold
            // at open, proving scrollIntoView lands an offscreen content-
            // visibility row. Named IDs above are untouched.
            ...Array.from({ length: 24 }, (_, index) => ({
              id:
                index === 23
                  ? "mock-forum-release-deeplink"
                  : `mock-forum-release-filler-${index}`,
              pubkey: ALICE_PUBKEY,
              created_at: Math.floor(Date.now() / 1000) - (79 - index) * 60,
              kind: 45003,
              tags: buildReplyMessageTags(
                channelId,
                ALICE_PUBKEY,
                "mock-forum-release-thread",
                "mock-forum-release-thread",
                undefined,
              ),
              content:
                index === 23
                  ? "Deep-link target: confirmed the rollout plan end to end."
                  : `Follow-up note #${index + 1} on the release checklist.`,
              sig: "mocksig".repeat(20).slice(0, 128),
            })),
          ]
        : channelId === "94a444a4-c0a3-5966-ab05-530c6ddc2301"
          ? [
              // Charlie is a `bot` member of #agents (see channel seed), so this
              // message renders with role="bot" — the surface whose avatar opens
              // a managed-agent profile panel / hover popover with active-turn
              // badges. #agents has no message-row index assertions, so seeding
              // here is safe for existing specs.
              {
                id: "mock-agents-charlie",
                pubkey: CHARLIE_PUBKEY,
                created_at: Math.floor(Date.now() / 1000) - 90,
                kind: 9,
                tags: [["h", channelId]],
                content: "Indexing the channel catalog now.",
                sig: "mocksig".repeat(20).slice(0, 128),
              },
              // Owned remote relay agent: declared-owned by the mock viewer,
              // present in the relay registry, but NOT locally managed. This
              // keeps the profile Runtime-tab owner gate honest.
              {
                id: "mock-agents-owned-relay-nadia",
                pubkey: OWNED_RELAY_AGENT_PUBKEY,
                created_at: Math.floor(Date.now() / 1000) - 85,
                kind: 9,
                tags: [["h", channelId]],
                content: "Indexing remotely for my owner.",
                sig: "mocksig".repeat(20).slice(0, 128),
              },
              // Seed one message per managed agent that is a member of #agents.
              // This lets e2e specs open the profile panel by clicking the
              // agent's avatar in a message-row (the same pattern as charlie).
              ...mockManagedAgents
                .filter((agent) =>
                  mockChannels
                    .find((ch) => ch.id === channelId)
                    ?.members.some((m) => m.pubkey === agent.pubkey),
                )
                .map((agent, index) => ({
                  id: `mock-agents-managed-${agent.pubkey.slice(0, 8)}`,
                  pubkey: agent.pubkey,
                  created_at: Math.floor(Date.now() / 1000) - 80 + index,
                  kind: 9 as const,
                  tags: [["h", channelId]],
                  content: `${agent.name} reporting in.`,
                  sig: "mocksig".repeat(20).slice(0, 128),
                })),
            ]
          : channelId === "feedf00d-0000-4000-8000-000000000007"
            ? (() => {
                const count = getConfig()?.mock?.deepHistoryMessageCount ?? 600;
                return Array.from({ length: count }, (_, index) => ({
                  id: `mock-deep-history-${index}`,
                  pubkey: index % 2 === 0 ? ALICE_PUBKEY : MOCK_IDENTITY_PUBKEY,
                  created_at:
                    Math.floor(Date.now() / 1000) - (count - index) * 60,
                  kind: 9,
                  tags: [["h", channelId]],
                  content:
                    count > 600
                      ? `Deep history message #${index}\n${"variable wrapped history ".repeat((index % 12) + 1)}`
                      : `Deep history message #${index}`,
                  sig: "mocksig".repeat(20).slice(0, 128),
                }));
              })()
            : [];

  mockMessages.set(channelId, seeded);
  return seeded;
}

function prependMockHistory(input: {
  channelName: string;
  count: number;
  startIndex?: number;
  lineCount?: number;
  createdAtStart?: number;
  emit?: boolean;
}) {
  const channel = mockChannels.find(
    (candidate) => candidate.name === input.channelName,
  );
  if (!channel) {
    throw new Error(`Unknown mock channel: ${input.channelName}`);
  }

  const store = getMockMessageStore(channel.id);
  const earliestCreatedAt = store.reduce(
    (earliest, event) => Math.min(earliest, event.created_at),
    Math.floor(Date.now() / 1000),
  );
  const createdAtStart =
    input.createdAtStart ?? earliestCreatedAt - input.count - 1;
  const startIndex = input.startIndex ?? 0;
  const lineCount = input.lineCount ?? 1;

  const events = Array.from({ length: input.count }, (_, offset) => {
    const index = startIndex + offset;
    const body = Array.from(
      { length: lineCount },
      (_unused, lineIndex) => `mock older ${index} line ${lineIndex + 1}`,
    ).join("\n");

    return createMockEvent(
      9,
      body,
      [["h", channel.id]],
      ALICE_PUBKEY,
      createdAtStart + offset,
      `mock-older-${channel.name}-${index}`.replace(/[^a-zA-Z0-9]/g, ""),
    );
  });

  store.unshift(...events);
  store.sort((left, right) => left.created_at - right.created_at);

  if (input.emit) {
    for (const event of events) {
      emitMockLiveEvent(channel.id, event);
    }
  }

  return events;
}

function emitMockHistory(
  socket: MockSocket,
  subId: string,
  channelId: string,
  filter: MockFilter,
) {
  const events = getMockMessageStore(channelId)
    .filter((event) => {
      if (filter.kinds && !filter.kinds.includes(event.kind)) {
        return false;
      }
      if (filter.since !== undefined && event.created_at < filter.since) {
        return false;
      }
      if (filter.until !== undefined && event.created_at > filter.until) {
        return false;
      }
      return true;
    })
    // Relay order is `created_at DESC, id ASC` — match it (both the WS history
    // page and the `get_channel_messages_before` keyset are backed by that one
    // order in production, so the mock must be self-consistent too, else a
    // same-second slice returned here won't line up with the keyset's tiebreak
    // and the dense-second escape hatch can't prove completeness). Bare `until`
    // still can't advance past a second denser than one page; the composite
    // keyset is the escape hatch.
    .sort(
      (left, right) =>
        right.created_at - left.created_at || left.id.localeCompare(right.id),
    )
    .slice(0, filter.limit ?? 50)
    .sort(
      (left, right) =>
        left.created_at - right.created_at || left.id.localeCompare(right.id),
    );

  const emit = () => {
    for (const event of events) {
      sendWsText(socket.handler, ["EVENT", subId, event]);
    }
    sendWsText(socket.handler, ["EOSE", subId]);
  };

  emit();
}

function emitMockLiveEvent(channelId: string, event: RelayEvent) {
  for (const socket of mockSockets.values()) {
    for (const [subId, subscription] of socket.subscriptions) {
      if (
        (subscription.channelId === channelId ||
          subscription.channelId === GLOBAL_MOCK_SUBSCRIPTION) &&
        (!subscription.kinds || subscription.kinds.includes(event.kind))
      ) {
        sendWsText(socket.handler, ["EVENT", subId, event]);
      }
    }
  }
}

function emitOrDeferMockSendMessageLiveEcho(
  channelId: string,
  event: RelayEvent,
  config: E2eConfig | undefined,
) {
  if (config?.mock?.deferSendMessageLiveEcho) {
    deferredSendMessageLiveEchoes.push({ channelId, event });
    return;
  }
  emitMockLiveEvent(channelId, event);
}

function emitMockGlobalEvent(event: RelayEvent) {
  if (
    event.kind === KIND_PERSONA &&
    event.pubkey.toLowerCase() !== MOCK_IDENTITY_PUBKEY.toLowerCase() &&
    !personaHasExactSharedTag(event)
  ) {
    return;
  }
  for (const socket of mockSockets.values()) {
    for (const [subId, subscription] of socket.subscriptions) {
      if (subscription.kinds && !subscription.kinds.includes(event.kind)) {
        continue;
      }
      sendWsText(socket.handler, ["EVENT", subId, event]);
    }
  }
}

function hasMockLiveSubscription(channelId: string, kind?: number) {
  for (const socket of mockSockets.values()) {
    for (const subscription of socket.subscriptions.values()) {
      if (
        (subscription.channelId === channelId ||
          subscription.channelId === GLOBAL_MOCK_SUBSCRIPTION) &&
        (kind === undefined ||
          !subscription.kinds ||
          subscription.kinds.includes(kind))
      ) {
        return true;
      }
    }
  }

  return false;
}

/**
 * True iff a live REQ subscription is open with an `#p` filter containing
 * `ownerPubkey` and a `kinds` filter containing `kind`. Used to assert the
 * observer-archive reconciliation gate actually opens an owner-scoped live
 * filter (not just that `list_save_subscriptions` was called) once the
 * gate resolves.
 */
function hasMockOwnerKindSubscription(ownerPubkey: string, kind: number) {
  for (const socket of mockSockets.values()) {
    for (const subscription of socket.subscriptions.values()) {
      if (
        subscription.ownerPubkeys.includes(ownerPubkey) &&
        (subscription.kinds?.includes(kind) ?? false)
      ) {
        return true;
      }
    }
  }

  return false;
}

function recordMockMessage(channelId: string, event: RelayEvent) {
  const history = getMockMessageStore(channelId);
  history.push(event);

  const channel = mockChannels.find((candidate) => candidate.id === channelId);
  if (!channel) {
    return;
  }

  channel.last_message_at = new Date(event.created_at * 1_000).toISOString();
  touchMockChannel(channel);
}

function resetMockUserStatuses() {
  mockUserStatuses.length = 0;
}

// Mocked Rust-side pending deep-link queue (see desktop/src-tauri/src/deep_link.rs).
let mockPendingCommunityDeepLinks: Array<{
  id: string;
  kind: string;
  relayUrl: string;
  code: string | null;
  name: string | null;
}> = [];

function resetMockPendingCommunityDeepLinks(config: E2eConfig | null) {
  mockPendingCommunityDeepLinks = (
    config?.mock?.pendingCommunityDeepLinks ?? []
  ).map((pending) => ({
    ...pending,
    code: pending.code ?? null,
    name: pending.name ?? null,
  }));
}

let mockPendingNavigationDeepLinks: Array<{
  id: string;
  kind: "channel" | "message";
  channelId: string;
  messageId: string | null;
  threadRootId: string | null;
}> = [];

function resetMockPendingNavigationDeepLinks(config: E2eConfig | null) {
  mockPendingNavigationDeepLinks = (
    config?.mock?.pendingNavigationDeepLinks ?? []
  ).map((pending) => ({
    ...pending,
    messageId: pending.messageId ?? null,
    threadRootId: pending.threadRootId ?? null,
  }));
}

let mockPendingEntityDeepLinks: Array<{ id: string; href: string }> = [];

function resetMockPendingEntityDeepLinks(config: E2eConfig | null) {
  mockPendingEntityDeepLinks = (config?.mock?.pendingEntityDeepLinks ?? []).map(
    (pending) => ({ ...pending }),
  );
}

function recordMockUserStatus(event: RelayEvent) {
  const dTag = event.tags.find((tag) => tag[0] === "d")?.[1];
  if (dTag) {
    const index = mockUserStatuses.findIndex(
      (stored) =>
        stored.pubkey.toLowerCase() === event.pubkey.toLowerCase() &&
        stored.tags.some((tag) => tag[0] === "d" && tag[1] === dTag),
    );
    if (index >= 0) {
      mockUserStatuses.splice(index, 1);
    }
  }

  mockUserStatuses.push(event);
}

function filterMockUserStatuses(filter: MockFilter) {
  const authors = filter.authors?.map((author) => author.toLowerCase());
  const dTags = filter["#d"];

  return mockUserStatuses
    .filter((event) => {
      if (authors && !authors.includes(event.pubkey.toLowerCase())) {
        return false;
      }
      if (
        dTags &&
        !event.tags.some((tag) => tag[0] === "d" && dTags.includes(tag[1]))
      ) {
        return false;
      }
      return true;
    })
    .sort((a, b) => b.created_at - a.created_at);
}

function emitMockChannelMessage(
  channelId: string,
  content: string,
  parentEventId?: string | null,
  pubkey?: string,
  kind?: number,
  mentionPubkeys?: string[],
  extraTags?: string[][],
  createdAt?: number,
  pending?: boolean,
  id?: string,
) {
  const eventKind = kind ?? 9;
  if (!parentEventId) {
    const tags = buildTopLevelMessageTags(
      channelId,
      mentionPubkeys,
      pubkey ?? DEFAULT_MOCK_IDENTITY.pubkey,
    );
    if (extraTags) tags.push(...extraTags);
    const event = createMockEvent(
      eventKind,
      content,
      tags,
      pubkey,
      createdAt,
      id,
    );
    if (pending) event.pending = true;
    recordMockMessage(channelId, event);
    emitMockLiveEvent(channelId, event);
    return event;
  }

  const history = getMockMessageStore(channelId);
  const parentEvent =
    history.find((event) => event.id === parentEventId) ?? null;
  const parentThread = parentEvent
    ? getThreadReferenceFromTags(parentEvent.tags)
    : {
        parentEventId: null,
        rootEventId: null,
      };
  const rootEventId = parentThread.rootEventId ?? parentEventId;
  const authorPubkey = pubkey ?? DEFAULT_MOCK_IDENTITY.pubkey;
  const tags = buildReplyMessageTags(
    channelId,
    authorPubkey,
    parentEventId,
    rootEventId,
    mentionPubkeys,
  );
  if (extraTags) tags.push(...extraTags);
  const event = createMockEvent(
    eventKind,
    content,
    tags,
    authorPubkey,
    createdAt,
    id,
  );
  if (pending) event.pending = true;
  recordMockMessage(channelId, event);
  emitMockLiveEvent(channelId, event);
  const rootEvent = history.find((candidate) => candidate.id === rootEventId);
  if (rootEvent && mockHuddle?.state.ephemeral_channel_id === channelId) {
    const summary = buildMockChannelThreadSummary(
      channelId,
      rootEvent,
      getMockMessageStore(channelId),
    );
    if (summary) emitMockLiveEvent(channelId, summary);
  }
  return event;
}

function emitMockTypingIndicator(
  channelId: string,
  pubkey: string,
  threadHeadId?: string,
  createdAt?: number,
) {
  const event: RelayEvent = {
    id: crypto.randomUUID().replace(/-/g, ""),
    pubkey,
    created_at: createdAt ?? Math.floor(Date.now() / 1000),
    kind: 20002,
    tags: [
      ["h", channelId],
      ...(threadHeadId ? [["e", threadHeadId, "", "reply"]] : []),
    ],
    content: "",
    sig: "mocksig".repeat(20).slice(0, 128),
  };

  emitMockLiveEvent(channelId, event);
  return event;
}

function toRawForumPost(
  event: RelayEvent,
  channelId: string,
  threadSummary: RawThreadSummary | null,
): RawForumPost {
  return {
    event_id: event.id,
    pubkey: event.pubkey,
    content: event.content,
    kind: event.kind,
    created_at: event.created_at,
    channel_id: channelId,
    tags: event.tags,
    thread_summary: threadSummary,
    reactions: null,
  };
}

function toRawForumReply(event: RelayEvent, channelId: string): RawForumReply {
  const thread = getThreadReferenceFromTags(event.tags);

  return {
    event_id: event.id,
    pubkey: event.pubkey,
    content: event.content,
    kind: event.kind,
    created_at: event.created_at,
    channel_id: channelId,
    tags: event.tags,
    parent_event_id: thread.parentEventId,
    root_event_id: thread.rootEventId,
    depth:
      thread.rootEventId && thread.parentEventId !== thread.rootEventId ? 2 : 1,
    broadcast: false,
    reactions: null,
  };
}

async function handleGetForumPosts(args: {
  channelId: string;
  limit?: number | null;
  before?: number | null;
}): Promise<RawForumPostsResponse> {
  const events = getMockMessageStore(args.channelId);
  const posts = events
    .filter((event) => event.kind === 45001)
    .filter((event) => (args.before ? event.created_at < args.before : true))
    .sort((left, right) => right.created_at - left.created_at)
    .slice(0, args.limit ?? 50)
    .map((event) => {
      const replies = events.filter((candidate) => {
        if (candidate.kind !== 45003) {
          return false;
        }

        const thread = getThreadReferenceFromTags(candidate.tags);
        return (thread.rootEventId ?? thread.parentEventId) === event.id;
      });

      return toRawForumPost(event, args.channelId, {
        reply_count: replies.length,
        descendant_count: replies.length,
        last_reply_at:
          replies.length > 0 ? replies[replies.length - 1].created_at : null,
        participants: [...new Set(replies.map((reply) => reply.pubkey))],
      });
    });

  return {
    messages: posts,
    next_cursor: null,
  };
}

async function handleGetForumThread(args: {
  channelId: string;
  eventId: string;
}): Promise<RawForumThreadResponse> {
  const events = getMockMessageStore(args.channelId);
  const root = events.find(
    (event) => event.id === args.eventId && event.kind === 45001,
  );
  if (!root) {
    throw new Error(`Mock forum thread not found: ${args.eventId}`);
  }

  const replies = events
    .filter((event) => event.kind === 45003)
    .filter((event) => {
      const thread = getThreadReferenceFromTags(event.tags);
      return (thread.rootEventId ?? thread.parentEventId) === root.id;
    })
    .sort((left, right) => left.created_at - right.created_at)
    .map((event) => toRawForumReply(event, args.channelId));

  return {
    root: toRawForumPost(root, args.channelId, {
      reply_count: replies.length,
      descendant_count: replies.length,
      last_reply_at:
        replies.length > 0 ? replies[replies.length - 1].created_at : null,
      participants: [...new Set(replies.map((reply) => reply.pubkey))],
    }),
    replies,
    total_replies: replies.length,
    next_cursor: null,
  };
}

type RawThreadCursor = {
  created_at: number;
  event_id: string;
};

type RawThreadRepliesResponse = {
  events: RelayEvent[];
  next_cursor: RawThreadCursor | null;
};

/**
 * Mirror of the desktop `get_thread_replies` command: return the full reply
 * subtree under a root, chronological (oldest first), excluding the root itself,
 * with gap-free `(created_at, event_id)` keyset paging.
 *
 * The event-id tiebreak is load-bearing — same-second replies must all page
 * through even when they cross a page boundary. This lets a Playwright spec
 * assert the paged union equals the whole subtree, matching the relay contract.
 */
async function handleGetThreadReplies(
  args: {
    rootEventId: string;
    channelId?: string | null;
    limit?: number | null;
    depthLimit?: number | null;
    cursor?: RawThreadCursor | null;
  },
  config: E2eConfig | undefined,
): Promise<RawThreadRepliesResponse> {
  const cap = Math.min(args.limit ?? 200, 500);
  const filter: MockFilter & Record<string, unknown> = {
    "#e": [args.rootEventId],
    depth_limit: args.depthLimit ?? 64,
    kinds: [...TIMELINE_KINDS],
    limit: cap,
  };
  if (args.channelId) {
    filter["#h"] = [args.channelId];
  }
  if (args.cursor) {
    filter.thread_cursor = args.cursor.created_at;
    filter.thread_cursor_id = args.cursor.event_id;
  }
  const identity = getIdentity(config);
  if (
    !isPGatedFilterAuthorized(
      filter,
      identity?.pubkey ?? getMockMemberPubkey(config),
    )
  ) {
    throw new Error(P_GATED_REJECTION_MESSAGE);
  }

  let subtree: RelayEvent[];
  if (!identity) {
    // Mock store: walk the reply forest transitively from the root so nested
    // replies (reply-to-a-reply) are included, matching thread_metadata depth.
    const events = args.channelId
      ? getMockMessageStore(args.channelId)
      : Array.from(mockMessages.values()).flat();
    const byId = new Map(events.map((event) => [event.id, event]));
    const root = byId.get(args.rootEventId);
    const collected: RelayEvent[] = [];
    const included = new Set<string>();
    if (!root) {
      subtree = collected;
    } else {
      const frontier = new Set<string>([root.id]);
      for (;;) {
        let added = false;
        for (const event of events) {
          if (included.has(event.id)) {
            continue;
          }
          const ref = getThreadReferenceFromTags(event.tags);
          if (!ref.parentEventId || !frontier.has(ref.parentEventId)) {
            continue;
          }
          included.add(event.id);
          collected.push(event);
          frontier.add(event.id);
          added = true;
        }
        if (!added) {
          break;
        }
      }
      subtree = collected;
    }
  } else {
    // Config mode: exercise the real bridge thread path over /query.
    const events = await relayQuery(config, [filter]);
    const nextCursor =
      events.length >= cap
        ? {
            created_at: events[events.length - 1].created_at,
            event_id: events[events.length - 1].id,
          }
        : null;
    return { events, next_cursor: nextCursor };
  }

  // Mock mode paging: sort by the composite key, then slice strictly after the
  // cursor so same-second ties can never be skipped across a page boundary.
  subtree.sort(
    (left, right) =>
      left.created_at - right.created_at || left.id.localeCompare(right.id),
  );
  let start = 0;
  if (args.cursor) {
    const cursor = args.cursor;
    start = subtree.findIndex(
      (event) =>
        event.created_at > cursor.created_at ||
        (event.created_at === cursor.created_at &&
          event.id.localeCompare(cursor.event_id) > 0),
    );
    if (start < 0) {
      start = subtree.length;
    }
  }
  const page = subtree.slice(start, start + cap);
  const nextCursor =
    page.length >= cap && start + cap < subtree.length
      ? {
          created_at: page[page.length - 1].created_at,
          event_id: page[page.length - 1].id,
        }
      : null;
  const delayMs = config?.mock?.threadRepliesDelayMs ?? 0;
  if (delayMs > 0) {
    await new Promise((resolve) => window.setTimeout(resolve, delayMs));
  }

  return { events: page, next_cursor: nextCursor };
}

const TIMELINE_KINDS = new Set([
  9,
  40002,
  40008,
  40099,
  43001,
  43002,
  43003,
  43004,
  43005,
  43006,
  KIND_HUDDLE_STARTED,
]);

const KIND_GIFT_WRAP = 1059;
const P_GATED_KINDS = new Set([
  KIND_AGENT_OBSERVER_FRAME,
  KIND_MEMBER_ADDED_NOTIFICATION,
  KIND_MEMBER_REMOVED_NOTIFICATION,
  KIND_GIFT_WRAP,
  KIND_DM_VISIBILITY,
]);
const P_GATED_REJECTION_MESSAGE =
  "restricted: p-gated kinds require #p tag matching your pubkey";

function filterKinds(filter: { kinds?: unknown }): number[] | undefined {
  return Array.isArray(filter.kinds)
    ? filter.kinds.filter((kind): kind is number => typeof kind === "number")
    : undefined;
}

function filterCanMatchPGated(filter: { kinds?: unknown }) {
  const kinds = filterKinds(filter);
  return !kinds || kinds.some((kind) => P_GATED_KINDS.has(kind));
}

function filterHasOwnPTag(filter: { "#p"?: unknown }, pubkey: string) {
  const values = filter["#p"];
  return (
    Array.isArray(values) &&
    values.length > 0 &&
    values.every((value) => value === pubkey)
  );
}

function isPGatedFilterAuthorized(
  filter: { "#p"?: unknown; ids?: unknown; kinds?: unknown },
  pubkey: string,
) {
  if (!filterCanMatchPGated(filter)) {
    return true;
  }

  const kinds = filterKinds(filter);
  const ids = filter.ids;
  const explicitlyDmVisibility = kinds?.includes(KIND_DM_VISIBILITY);
  if (!explicitlyDmVisibility && Array.isArray(ids) && ids.length > 0) {
    return true;
  }

  return filterHasOwnPTag(filter, pubkey);
}

type RawChannelMessagesPageResponse = {
  events: RelayEvent[];
  next_cursor: RawThreadCursor | null;
};

/**
 * Mirror of the desktop `get_channel_messages_before` command: return one
 * keyset page of *top-level* channel history strictly older than a composite
 * `(before, before_id)` cursor, newest first (relay order `created_at DESC,
 * id ASC`).
 *
 * This is the dense-second escape hatch — the id tiebreak is load-bearing so a
 * single `created_at` second denser than one WS page can still be paged
 * through: within a tied second the relay advances via `id > before_id`. Lets a
 * Playwright spec assert the keyset union reaches every top-level message even
 * when a second holds more than one page.
 */
async function handleGetChannelMessagesBefore(
  args: {
    channelId: string;
    before: number;
    beforeId?: string | null;
    limit?: number | null;
  },
  config: E2eConfig | undefined,
): Promise<RawChannelMessagesPageResponse> {
  const cap = Math.min(args.limit ?? 200, 500);
  const identity = getIdentity(config);

  let events: RelayEvent[];
  if (!identity) {
    // Mock store: top-level timeline events for this channel.
    events = getMockMessageStore(args.channelId).filter((event) => {
      if (!TIMELINE_KINDS.has(event.kind)) {
        return false;
      }
      return isMockTopLevelRow(event);
    });
  } else {
    // Config mode: exercise the real bridge keyset over /query.
    const filter: Record<string, unknown> = {
      "#h": [args.channelId],
      kinds: [...TIMELINE_KINDS],
      until: args.before,
      limit: cap,
    };
    if (args.beforeId) {
      filter.before_id = args.beforeId;
    }
    const page = await relayQuery(config, [filter]);
    const nextCursor =
      page.length >= cap
        ? {
            created_at: page[page.length - 1].created_at,
            event_id: page[page.length - 1].id,
          }
        : null;
    return { events: page, next_cursor: nextCursor };
  }

  // Mock mode paging: relay order (created_at DESC, id ASC), then take the
  // slice strictly older than the composite cursor. Strictly-older means
  // `created_at < before OR (created_at === before AND id > before_id)` — the
  // id tiebreak walks *forward* through a tied second under ASC id order.
  events.sort(
    (left, right) =>
      right.created_at - left.created_at || left.id.localeCompare(right.id),
  );
  const before = args.before;
  const beforeId = args.beforeId ?? null;
  const older = events.filter((event) => {
    if (event.created_at < before) {
      return true;
    }
    if (event.created_at === before && beforeId !== null) {
      return event.id.localeCompare(beforeId) > 0;
    }
    return false;
  });
  const page = older.slice(0, cap);
  const nextCursor =
    page.length >= cap
      ? {
          created_at: page[page.length - 1].created_at,
          event_id: page[page.length - 1].id,
        }
      : null;

  return { events: page, next_cursor: nextCursor };
}

function getEventTargets(event: RelayEvent) {
  return event.tags.flatMap((tag) =>
    tag[0] === "e" && typeof tag[1] === "string" ? [tag[1]] : [],
  );
}

function buildMockChannelWindowAux(
  events: RelayEvent[],
  rows: RelayEvent[],
): RelayEvent[] {
  const collectHop = (kinds: Set<number>, targetIds: Set<string>) =>
    events.filter(
      (event) =>
        kinds.has(event.kind) &&
        getEventTargets(event).some((target) => targetIds.has(target)),
    );

  const firstHop = collectHop(
    CHANNEL_WINDOW_AUX_KINDS,
    new Set(rows.map((row) => row.id)),
  );
  const secondHop = collectHop(
    CHANNEL_WINDOW_AUX_DELETION_KINDS,
    new Set(firstHop.map((event) => event.id)),
  );
  const byId = new Map(firstHop.map((event) => [event.id, event]));
  for (const event of secondHop) byId.set(event.id, event);
  return [...byId.values()];
}

function buildMockChannelThreadSummary(
  channelId: string,
  root: RelayEvent,
  events: RelayEvent[],
): RelayEvent | null {
  const replies = events.filter((event) => {
    const thread = getThreadReferenceFromTags(event.tags);
    return thread.rootEventId === root.id;
  });
  if (replies.length === 0) return null;

  const directReplies = replies.filter(
    (event) => getThreadReferenceFromTags(event.tags).parentEventId === root.id,
  );
  const participants = [
    ...new Set(
      replies
        .sort(
          (left, right) =>
            right.created_at - left.created_at ||
            left.id.localeCompare(right.id),
        )
        .map((event) => event.pubkey),
    ),
  ].slice(0, 10);
  const lastReplyAt = Math.max(...replies.map((event) => event.created_at));
  return {
    id: `mock-window-summary-${root.id}`,
    pubkey: DEFAULT_MOCK_IDENTITY.pubkey,
    created_at: Math.floor(Date.now() / 1000),
    kind: KIND_CHANNEL_THREAD_SUMMARY,
    tags: [
      ["e", root.id],
      ["d", root.id],
      ["h", channelId],
    ],
    content: JSON.stringify({
      reply_count: directReplies.length,
      descendant_count: replies.length,
      last_reply_at: lastReplyAt,
      participants,
    }),
    sig: "mocksig".repeat(20).slice(0, 128),
  };
}

/**
 * Build the single kind-39006 bounds event a channel window response must carry.
 * The `d` tag key must match `expectedBoundsKey` in channelWindowResponse.ts:
 * `<channel>:head` at the frontier, else `<channel>:<created_at>:<event_id>` of
 * the request cursor (lower-cased). `has_more`/`next_cursor` must agree — the
 * parser rejects a bounds event where they disagree.
 */
function buildMockChannelWindowBounds(
  args: {
    channelId: string;
    cursor?: { created_at: number; event_id: string } | null;
  },
  hasMore: boolean,
  nextCursor: { created_at: number; id: string } | null,
): RelayEvent {
  const suffix = args.cursor
    ? `${args.cursor.created_at}:${args.cursor.event_id.toLowerCase()}`
    : "head";
  const boundsKey = `${args.channelId.toLowerCase()}:${suffix}`;
  return {
    id: `mock-window-bounds-${boundsKey}`,
    pubkey: DEFAULT_MOCK_IDENTITY.pubkey,
    created_at: Math.floor(Date.now() / 1000),
    kind: KIND_CHANNEL_WINDOW_BOUNDS,
    tags: [["d", boundsKey]],
    content: JSON.stringify({ has_more: hasMore, next_cursor: nextCursor }),
    sig: "mocksig".repeat(20).slice(0, 128),
  };
}

/**
 * one server-assembled channel window over the `/query` bridge. Emits the flat
 * event array the relay assembles — top-level rows (newest first), then the aux
 * closure, then relay-signed `39005` summaries and exactly one `39006` bounds
 * event carrying `has_more` + `next_cursor`. The client derives its cursor and
 * exhaustion solely from `39006`, never from the rows, so this handler returns
 * the raw array unchanged.
 *
 * This is the window read-model surface the overhaul introduced; without it the
 * relay-mode bridge has no handler and the timeline renders empty.
 */
async function handleGetChannelWindow(
  args: {
    channelId: string;
    limitRows?: number | null;
    cursor?: { created_at: number; event_id: string } | null;
  },
  config: E2eConfig | undefined,
): Promise<RelayEvent[]> {
  const execute = async () => {
    const cap = Math.min(args.limitRows ?? 50, 200);
    const identity = getIdentity(config);

    if (!identity) {
      // Mock store: server-assembled channel window over the mock event store,
      // mirroring the relay path's shape so callers (parseChannelWindowResponse)
      // parse both modes identically. Top-level timeline rows in relay order,
      // then exactly one kind-39006 bounds event.
      const events = getMockMessageStore(args.channelId);
      const candidates = events
        .filter(
          (event) => TIMELINE_KINDS.has(event.kind) && isMockTopLevelRow(event),
        )
        .sort(
          (left, right) =>
            right.created_at - left.created_at ||
            left.id.localeCompare(right.id),
        );
      // Honor the composite (until, before_id) cursor exactly like the relay's
      // keyset: keep only rows strictly older than the cursor under the
      // (created_at DESC, id ASC) order — older created_at, or the same second
      // with a strictly greater id.
      const cursor = args.cursor;
      const afterCursor = cursor
        ? candidates.filter(
            (event) =>
              event.created_at < cursor.created_at ||
              (event.created_at === cursor.created_at &&
                event.id > cursor.event_id),
          )
        : candidates;
      const rows = afterCursor.slice(0, cap);
      // Exhaustion probe mirrors the relay's limit+1: more rows past the cursor
      // than the page cap means another page exists. next_cursor is the last
      // retained row.
      const hasMore = afterCursor.length > cap;
      const lastRow = rows[rows.length - 1];
      const nextCursor =
        hasMore && lastRow
          ? { created_at: lastRow.created_at, id: lastRow.id }
          : null;
      const aux = buildMockChannelWindowAux(events, rows);
      const summaries = rows.flatMap((row) => {
        const summary = buildMockChannelThreadSummary(
          args.channelId,
          row,
          events,
        );
        return summary ? [summary] : [];
      });
      return [
        ...rows,
        ...aux,
        ...summaries,
        buildMockChannelWindowBounds(args, hasMore, nextCursor),
      ];
    }

    // Relay mode: mirror build_channel_window_filter exactly — top-level dispatch
    // with summaries + aux, composite (until, before_id) cursor (both or neither).
    const filter: Record<string, unknown> = {
      "#h": [args.channelId],
      kinds: [...TIMELINE_KINDS],
      limit: cap,
      top_level: true,
      include_summaries: true,
      include_aux: true,
    };
    if (args.cursor) {
      filter.until = args.cursor.created_at;
      filter.before_id = args.cursor.event_id;
    }
    return relayQuery(config, [filter]);
  };

  if (!args.cursor) {
    return execute();
  }

  const probe = window as unknown as {
    __CHANNEL_WINDOW_FETCH_COUNT__?: number;
    __CHANNEL_WINDOW_INFLIGHT__?: number;
    __CHANNEL_WINDOW_INFLIGHT_PEAK__?: number;
  };
  probe.__CHANNEL_WINDOW_FETCH_COUNT__ =
    (probe.__CHANNEL_WINDOW_FETCH_COUNT__ ?? 0) + 1;

  const delayMs = getConfig()?.mock?.channelWindowDelayMs ?? 0;
  if (delayMs <= 0) {
    return execute();
  }

  probe.__CHANNEL_WINDOW_INFLIGHT__ =
    (probe.__CHANNEL_WINDOW_INFLIGHT__ ?? 0) + 1;
  probe.__CHANNEL_WINDOW_INFLIGHT_PEAK__ = Math.max(
    probe.__CHANNEL_WINDOW_INFLIGHT_PEAK__ ?? 0,
    probe.__CHANNEL_WINDOW_INFLIGHT__,
  );
  await new Promise((resolve) => window.setTimeout(resolve, delayMs));
  try {
    return await execute();
  } finally {
    probe.__CHANNEL_WINDOW_INFLIGHT__ =
      (probe.__CHANNEL_WINDOW_INFLIGHT__ ?? 1) - 1;
  }
}

function getMockUserNotes(pubkey: string): RawUserNote[] {
  const now = Math.floor(Date.now() / 1000);

  if (pubkey === DEFAULT_MOCK_IDENTITY.pubkey) {
    // Two named notes plus generated filler so the Pulse feed overflows the
    // viewport — required to exercise windowed scroll + sticky-composer offset.
    const named: RawUserNote[] = [
      {
        id: "mock-note-launch",
        pubkey,
        created_at: now - 20 * 60,
        content: "Shipped the new desktop sidebar polish today.",
        tags: [],
      },
      {
        id: "mock-note-forum",
        pubkey,
        created_at: now - 3 * 60 * 60,
        content: "Forum threads feel like the right home for slower decisions.",
        tags: [],
      },
    ];
    const filler: RawUserNote[] = Array.from({ length: 28 }, (_, index) => ({
      id: `mock-note-filler-${index}`,
      pubkey,
      created_at: now - (4 + index) * 60 * 60,
      content: `Pulse update #${index + 1}: tracking virtualization rollout across desktop surfaces.`,
      tags: [],
    }));
    return [...named, ...filler];
  }

  if (pubkey === ALICE_PUBKEY) {
    return [
      {
        id: "mock-alice-note-release",
        pubkey,
        created_at: now - 45 * 60,
        content: "Release checklist is ready for async feedback.",
        tags: [],
      },
      {
        id: "mock-alice-note-design",
        pubkey,
        created_at: now - 5 * 60 * 60,
        content: "Trying a lighter forum layout for longer-form notes.",
        tags: [],
      },
    ];
  }

  return [];
}

async function handleGetUserNotes(
  args: {
    pubkey: string;
    limit?: number | null;
    before?: number | null;
    beforeId?: string | null;
  },
  config: E2eConfig | undefined,
): Promise<RawUserNotesResponse> {
  const identity = getIdentity(config);
  if (!identity) {
    const notes = getMockUserNotes(args.pubkey)
      .filter((note) => (args.before ? note.created_at < args.before : true))
      .sort((left, right) => right.created_at - left.created_at)
      .slice(0, args.limit ?? 50);

    return {
      notes,
      next_cursor: null,
    };
  }

  // Query kind:1 notes for the user
  const limit = args.limit ?? 50;
  const filter: Record<string, unknown> = {
    kinds: [1],
    authors: [args.pubkey],
    limit,
  };
  if (args.before !== undefined && args.before !== null) {
    filter.until = args.before;
  }
  const events = await relayQuery(config, [filter]);
  const notes = events.map((ev) => ({
    id: ev.id,
    pubkey: ev.pubkey,
    content: ev.content,
    created_at: ev.created_at,
    tags: ev.tags,
  }));
  return { notes, next_cursor: null };
}

async function handleGetGlobalNotes(
  args: { limit?: number | null; before?: number | null } | null,
  config: E2eConfig | undefined,
): Promise<RawUserNotesResponse> {
  const notes = [
    ...getMockUserNotes(DEFAULT_MOCK_IDENTITY.pubkey),
    ...getMockUserNotes(ALICE_PUBKEY),
  ]
    .filter((note) => (args?.before ? note.created_at < args.before : true))
    .sort((left, right) => right.created_at - left.created_at)
    .slice(0, args?.limit ?? 50);

  if (!getIdentity(config)) {
    return { notes, next_cursor: null };
  }

  const events = await relayQuery(config, [
    { kinds: [1], limit: args?.limit ?? 50, until: args?.before ?? undefined },
  ]);
  return {
    notes: events.map((ev) => ({
      id: ev.id,
      pubkey: ev.pubkey,
      content: ev.content,
      created_at: ev.created_at,
      tags: ev.tags,
    })),
    next_cursor: null,
  };
}

function handleGetNotesTimeline(args: {
  pubkeys?: string[];
  limitPerUser?: number | null;
}) {
  const pubkeys = args.pubkeys ?? [];
  const limitPerUser = args.limitPerUser ?? 10;
  const notes = pubkeys
    .flatMap((pubkey) => getMockUserNotes(pubkey).slice(0, limitPerUser))
    .sort((left, right) => right.created_at - left.created_at);
  return { notes, next_cursor: null };
}

function handleGetNote(args: { noteId?: string }) {
  const noteId = args.noteId;
  return (
    [
      ...getMockUserNotes(DEFAULT_MOCK_IDENTITY.pubkey),
      ...getMockUserNotes(ALICE_PUBKEY),
    ].find((note) => note.id === noteId) ?? null
  );
}

function handleGetNoteReactions() {
  return [];
}

function handleGetLikedNotes(): RawUserNotesResponse {
  return { notes: [], next_cursor: null };
}

// A random 64-hex event id, matching the shape of real Nostr event ids
// (sha256 → 64 hex). Most mock events use the 32-hex `createMockEvent` default,
// but kind:7 reactions need a real 64-hex id: the timeline's deletion path only
// accepts 64-hex `e` tags (getDeletionTargets in formatTimelineMessages.ts), so
// a kind:5 targeting a 32-hex reaction id would be silently ignored and the
// reaction pill would never clear on toggle-off.
// --- Mock projects (NIP-34 repo announcements + git activity) ---
// Deterministic fixtures so the Projects view (cards, stat pills, and the
// contribution heatmap) renders with data in screenshots and e2e specs.

const MOCK_PROJECT_SEEDS = [
  {
    dtag: "buzz",
    name: "buzz",
    description:
      "Relay, desktop, and mobile clients for the Buzz community platform.",
    cloneUrl: `${DEFAULT_RELAY_HTTP_URL}/git/${MOCK_IDENTITY_PUBKEY}/buzz`,
    owner: MOCK_IDENTITY_PUBKEY,
    contributors: [ALICE_PUBKEY, BOB_PUBKEY, CHARLIE_PUBKEY],
    activityLevel: 4,
  },
  {
    dtag: "relay-tools",
    name: "relay-tools",
    description: "Operator tooling and admin CLI for relay deployments.",
    cloneUrl: "https://github.com/block/relay-tools.git",
    owner: ALICE_PUBKEY,
    contributors: [MOCK_IDENTITY_PUBKEY, BOB_PUBKEY],
    activityLevel: 2,
  },
  {
    dtag: "design-system",
    name: "design-system",
    description: "Shared UI tokens, typography ramps, and component library.",
    cloneUrl: `${DEFAULT_RELAY_HTTP_URL}/git/${BOB_PUBKEY}/design-system`,
    owner: BOB_PUBKEY,
    contributors: [ALICE_PUBKEY],
    activityLevel: 1,
  },
] as const;

const MOCK_PROJECT_SUBJECTS = [
  "Fix reconnect backoff jitter",
  "Polish overview cards",
  "Add contribution heatmap",
  "Refactor filter matching",
  "Speed up event dedup",
  "Handle empty clone URLs",
  "Update onboarding copy",
  "Tighten p-gate checks",
];

const MOCK_PROJECT_KINDS = new Set<number>([
  KIND_PROJECT_ANNOUNCEMENT,
  KIND_REPO_ANNOUNCEMENT,
  KIND_REPO_STATE,
  KIND_GIT_PATCH,
  KIND_GIT_PULL_REQUEST,
  KIND_GIT_PR_UPDATE,
  KIND_GIT_ISSUE,
  KIND_GIT_STATUS_OPEN,
  KIND_GIT_STATUS_MERGED,
  KIND_GIT_STATUS_CLOSED,
  KIND_GIT_STATUS_DRAFT,
]);

function mulberry32(seed: number) {
  let state = seed;
  return () => {
    state |= 0;
    state = (state + 0x6d2b79f5) | 0;
    let t = Math.imul(state ^ (state >>> 15), 1 | state);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

let mockProjectEventStore: RelayEvent[] | null = null;
const MOCK_PROJECT_BRANCHES_KEY = "buzz-e2e-project-branches";

function readMockProjectBranches(): Record<string, Record<string, string>> {
  try {
    return JSON.parse(
      window.sessionStorage.getItem(MOCK_PROJECT_BRANCHES_KEY) ?? "{}",
    );
  } catch {
    return {};
  }
}

function writeMockProjectBranch(
  dtag: string,
  branch: string,
  commit: string | null,
) {
  const projects = readMockProjectBranches();
  const branches = { ...(projects[dtag] ?? {}) };
  if (commit) {
    branches[branch] = commit;
  } else {
    delete branches[branch];
  }
  projects[dtag] = branches;
  window.sessionStorage.setItem(
    MOCK_PROJECT_BRANCHES_KEY,
    JSON.stringify(projects),
  );
}

function buildMockProjectEvents(): RelayEvent[] {
  const events: RelayEvent[] = [];
  const daySeconds = 86_400;
  const now = Math.floor(Date.now() / 1000);
  const historyDays = 26 * 7;

  for (const [projectIndex, seed] of MOCK_PROJECT_SEEDS.entries()) {
    const owner =
      projectIndex === 0
        ? (window.__BUZZ_E2E_PROJECT_OWNER_OVERRIDE__ ?? seed.owner)
        : seed.owner;
    const repoAddress = `${KIND_REPO_ANNOUNCEMENT}:${owner}:${seed.dtag}`;
    const authors = [seed.owner, ...seed.contributors];
    const random = mulberry32(projectIndex + 1);

    events.push(
      createMockEvent(
        KIND_REPO_ANNOUNCEMENT,
        seed.description,
        [
          ["d", seed.dtag],
          ["name", seed.name],
          ["description", seed.description],
          ["buzz-channel", "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50"],
          ["clone", seed.cloneUrl],
          ...seed.contributors.map((pubkey) => ["p", pubkey]),
        ],
        owner,
        now - (historyDays + 30 + projectIndex) * daySeconds,
        `mock-project-${seed.dtag}`.replace(/[^a-zA-Z0-9]/g, ""),
      ),
    );
    events.push(
      createMockEvent(
        KIND_REPO_STATE,
        "",
        [
          ["d", seed.dtag],
          [
            "HEAD",
            `ref: refs/heads/${
              projectIndex === 0
                ? (getConfig()?.mock?.projectHeadBranch ?? "main")
                : "main"
            }`,
          ],
          ["refs/heads/main", "0123456789abcdef0123456789abcdef01234567"],
          ["refs/tags/v1.0.0", "0123456789abcdef0123456789abcdef01234567"],
          ...Object.entries(readMockProjectBranches()[seed.dtag] ?? {}).map(
            ([branch, commit]) => [`refs/heads/${branch}`, commit],
          ),
        ],
        getConfig()?.mock?.relaySelf ?? owner,
        now - projectIndex,
        `mock-repo-state-${seed.dtag}`.replace(/[^a-zA-Z0-9]/g, ""),
      ),
    );

    for (let dayOffset = historyDays; dayOffset >= 0; dayOffset -= 1) {
      // Roughly half the days are quiet; busy days scale with activityLevel.
      if (random() < 0.5) continue;
      const dayEventCount = 1 + Math.floor(random() * seed.activityLevel);

      for (let index = 0; index < dayEventCount; index += 1) {
        const createdAt =
          now - dayOffset * daySeconds - Math.floor(random() * 10) * 3_600;
        const author = authors[Math.floor(random() * authors.length)];
        const subject =
          MOCK_PROJECT_SUBJECTS[
            Math.floor(random() * MOCK_PROJECT_SUBJECTS.length)
          ];
        const commitHash = `${seed.dtag}${dayOffset}x${index}`
          .padEnd(40, "0")
          .slice(0, 40);
        const roll = random();
        const kind =
          roll < 0.7
            ? KIND_GIT_PATCH
            : roll < 0.85
              ? KIND_GIT_PULL_REQUEST
              : KIND_GIT_ISSUE;
        const tags = [
          ["a", repoAddress],
          ["subject", subject],
          ...(kind === KIND_GIT_ISSUE ? [] : [["c", commitHash]]),
          ...(kind === KIND_GIT_PULL_REQUEST
            ? [
                ["h", "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50"],
                ["branch-name", `feature/mock-${dayOffset}-${index}`],
                ["clone", seed.cloneUrl],
              ]
            : []),
        ];

        events.push(createMockEvent(kind, subject, tags, author, createdAt));
      }
    }
  }

  const projectOwner =
    window.__BUZZ_E2E_PROJECT_OWNER_OVERRIDE__ ?? MOCK_PROJECT_SEEDS[0].owner;
  events.push(
    createMockEvent(
      KIND_PROJECT_ANNOUNCEMENT,
      "",
      [
        ["d", "buzz"],
        ["name", "buzz"],
        ["description", "The complete Buzz community platform."],
        ["a", `${KIND_REPO_ANNOUNCEMENT}:${projectOwner}:buzz`],
        ["a", `${KIND_REPO_ANNOUNCEMENT}:${ALICE_PUBKEY}:relay-tools`],
      ],
      projectOwner,
      now,
      "project-buzz".padEnd(64, "0"),
    ),
  );

  return events;
}

function getMockProjectEventStore(): RelayEvent[] {
  if (!mockProjectEventStore) {
    mockProjectEventStore = buildMockProjectEvents();
    // Append any extra events injected by the test via addInitScript before
    // the app boots. This lets a test seed standalone repositories or other
    // project-scoped events without modifying the fixed seed data.
    const extras = window.__BUZZ_E2E_EXTRA_PROJECT_EVENTS__;
    if (extras && extras.length > 0) {
      mockProjectEventStore.push(...(extras as RelayEvent[]));
    }
  }
  return mockProjectEventStore;
}

/** Project-scoped publishes (PR/issue comments, NIP-34 status events) carry
 * a repo-address `a` tag instead of a channel `h` tag — store them with the
 * seeded project events so refetches see them. */
function isMockProjectScopedEvent(event: RelayEvent): boolean {
  const hasRepoAddressTag = event.tags.some(
    (tag) => tag[0] === "a" && (tag[1] ?? "").startsWith("30617:"),
  );
  return (
    (event.kind === KIND_REPO_ANNOUNCEMENT || hasRepoAddressTag) &&
    (event.kind === 1 || MOCK_PROJECT_KINDS.has(event.kind))
  );
}

function filterMockProjectEvents(filter: MockFilter): RelayEvent[] {
  const authors = filter.authors?.map((author) => author.toLowerCase());
  const idsSet =
    filter.ids && filter.ids.length > 0 ? new Set(filter.ids) : null;
  return getMockProjectEventStore()
    .filter((event) => {
      if (filter.kinds && !filter.kinds.includes(event.kind)) return false;
      if (authors && !authors.includes(event.pubkey.toLowerCase())) {
        return false;
      }
      // Filter by event ids when present — required for lost-ACK recovery queries.
      if (idsSet && !idsSet.has(event.id)) return false;
      if (
        filter["#d"] &&
        !event.tags.some(
          (tag) => tag[0] === "d" && filter["#d"]?.includes(tag[1]),
        )
      ) {
        return false;
      }
      if (
        filter["#a"] &&
        !event.tags.some(
          (tag) => tag[0] === "a" && filter["#a"]?.includes(tag[1]),
        )
      ) {
        return false;
      }
      if (
        filter["#e"] &&
        !event.tags.some(
          (tag) => tag[0] === "e" && filter["#e"]?.includes(tag[1]),
        )
      ) {
        return false;
      }
      // Inclusive `until`, like the relay's SQL `created_at <= $until`.
      if (filter.until !== undefined && event.created_at > filter.until) {
        return false;
      }
      return true;
    })
    .sort((left, right) => right.created_at - left.created_at)
    .slice(0, filter.limit ?? 500);
}

function mockEventId(): string {
  const bytes = new Uint8Array(32);
  crypto.getRandomValues(bytes);
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
}

function createMockEvent(
  kind: number,
  content: string,
  tags: string[][],
  pubkey = DEFAULT_MOCK_IDENTITY.pubkey,
  createdAt = Math.floor(Date.now() / 1000),
  // 64 hex chars like a real event id — share-link builders reject shorter
  // ids, so copy-link buttons only render with full-length ids.
  id = (crypto.randomUUID() + crypto.randomUUID()).replace(/-/g, ""),
): RelayEvent {
  return {
    id,
    pubkey,
    created_at: createdAt,
    kind,
    tags,
    content,
    sig: "mocksig".repeat(20).slice(0, 128),
  };
}

async function signWithIdentity(
  identity: TestIdentity,
  template: {
    kind: number;
    content: string;
    createdAt?: number;
    tags: string[][];
  },
) {
  const secretKey = hexToBytes(identity.privateKey);

  return finalizeEvent(
    {
      kind: template.kind,
      content: template.content,
      tags: template.tags,
      created_at: template.createdAt ?? Math.floor(Date.now() / 1000),
    },
    secretKey,
  );
}

async function assertOk(response: Response) {
  if (response.ok) {
    return;
  }

  const body = await response.text();
  throw new Error(body || `Request failed with ${response.status}`);
}

function getRelayIdentity(config: E2eConfig | undefined): TestIdentity {
  const identity = getIdentity(config);
  if (!identity) {
    throw new Error("Relay identity required.");
  }

  return identity;
}

async function relayJsonRequest<T>(
  config: E2eConfig | undefined,
  path: string,
  init: RequestInit = {},
): Promise<T> {
  const identity = getRelayIdentity(config);
  const headers = new Headers(init.headers);

  headers.set("X-Pubkey", identity.pubkey);
  if (init.body && !headers.has("Content-Type")) {
    headers.set("Content-Type", "application/json");
  }

  const response = await fetch(`${getRelayHttpUrl(config)}${path}`, {
    ...init,
    headers,
  });
  await assertOk(response);
  return response.json() as Promise<T>;
}

/**
 * Query the relay via POST /query (pure Nostr HTTP bridge).
 * Returns an array of raw Nostr events matching the filters.
 */
async function relayQuery(
  config: E2eConfig | undefined,
  filters: Array<Record<string, unknown>>,
): Promise<RelayEvent[]> {
  const identity = getRelayIdentity(config);
  if (
    !filters.every((filter) =>
      isPGatedFilterAuthorized(filter, identity.pubkey),
    )
  ) {
    throw new Error(P_GATED_REJECTION_MESSAGE);
  }

  const response = await fetch(`${getRelayHttpUrl(config)}/query`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Pubkey": identity.pubkey,
    },
    body: JSON.stringify(filters),
  });
  await assertOk(response);
  return response.json() as Promise<RelayEvent[]>;
}

async function submitSignedEvent(
  config: E2eConfig | undefined,
  template: { kind: number; content: string; tags: string[][] },
): Promise<{ event_id: string; accepted: boolean; message: string }> {
  const identity = getRelayIdentity(config);
  const signed = await signWithIdentity(identity, template);
  return relayJsonRequest(config, "/events", {
    method: "POST",
    body: JSON.stringify(signed),
  });
}

/** Build the channel-id → last-message-at map from the returned channel list. */
function buildLastMessages(
  channels: Array<{ id: string; last_message_at: string | null }>,
): Record<string, string> {
  const map: Record<string, string> = {};
  for (const ch of channels) {
    if (ch.last_message_at !== null) {
      map[ch.id] = ch.last_message_at;
    }
  }
  return map;
}

async function handleGetChannels(
  payload: unknown,
  config: E2eConfig | undefined,
) {
  const channelsReadDelayMs = config?.mock?.channelsReadDelayMs ?? 0;
  if (channelsReadDelayMs > 0) {
    await new Promise((resolve) =>
      window.setTimeout(resolve, channelsReadDelayMs),
    );
  }

  const channelsReadError =
    config?.mock?.channelsReadErrors?.shift() ??
    config?.mock?.channelsReadError;
  if (channelsReadError) {
    throw new Error(channelsReadError);
  }

  const identity = getIdentity(config);
  if (!identity) {
    // The hash is constant ("mock-hash") and full lists remain the default:
    // mock channel data mutates during tests while the hash does not. Focused
    // snapshot specs can opt into the not-modified branch explicitly.
    const channels = listMockChannels(config);
    const hash = "mock-hash";
    const knownHash = (payload as { knownHash?: unknown } | null)?.knownHash;
    const forcedNotModified =
      (config?.mock?.channelsNotModifiedResponses ?? 0) > 0;
    if (forcedNotModified && config?.mock) {
      config.mock.channelsNotModifiedResponses =
        (config.mock.channelsNotModifiedResponses ?? 1) - 1;
    }
    return {
      hash,
      channels:
        forcedNotModified ||
        (config?.mock?.honorChannelsKnownHash && knownHash === hash)
          ? null
          : channels,
      last_messages: buildLastMessages(channels),
    };
  }

  // Pure Nostr: query kind:39002 (membership) for our pubkey, extract channel
  // UUIDs from d-tags, then query kind:39000 (metadata) for those channels.
  const memberEvents = await relayQuery(config, [
    { kinds: [39002], "#p": [identity.pubkey], limit: 1000 },
  ]);

  const channelIds = [
    ...new Set(
      memberEvents.flatMap((ev) =>
        (ev.tags ?? [])
          .filter((t: string[]) => t[0] === "d")
          .map((t: string[]) => t[1]),
      ),
    ),
  ];

  // Also fetch ALL open channel metadata (for channel browser — shows joinable channels)
  const allMetaEvents = await relayQuery(config, [
    { kinds: [39000], limit: 200 },
  ]);

  // Merge: use all metadata events, mark membership
  const memberSet = new Set(channelIds);
  const metaEvents = allMetaEvents;

  // NIP-DV: query the viewer's latest DM visibility snapshot (kind:30622).
  // The snapshot is `#p`-gated to its owner, so we query by `#p`=my pubkey.
  // Its `h` tags are the DM channel ids to hide from the sidebar.
  const visibilityEvents = await relayQuery(config, [
    { kinds: [KIND_DM_VISIBILITY], "#p": [identity.pubkey], limit: 1 },
  ]);
  const latestVisibility = visibilityEvents.reduce<RelayEvent | null>(
    (latest, ev) =>
      !latest || ev.created_at > latest.created_at ? ev : latest,
    null,
  );
  const hiddenDms = new Set(
    ((latestVisibility?.tags ?? []) as string[][])
      .filter((t) => t[0] === "h")
      .map((t) => t[1]),
  );

  // Convert kind:39000 events to the RawChannel shape the frontend expects.
  const channels = metaEvents
    .map((ev) => {
      const tags = (ev.tags ?? []) as string[][];
      const getTag = (name: string) =>
        tags.find((t) => t[0] === name)?.[1] ?? null;
      const channelId = getTag("d") ?? "";
      const channelType = getTag("t") ?? "stream";
      const isPrivate = tags.some((t) => t[0] === "private");
      const isArchived = tags.some(
        (t) => t[0] === "archived" && t[1] === "true",
      );

      // Get participant pubkeys from the membership event for this channel
      const memberEvent = memberEvents.find((me) =>
        (me.tags ?? []).some(
          (t: string[]) => t[0] === "d" && t[1] === channelId,
        ),
      );
      const pTags = memberEvent
        ? ((memberEvent.tags ?? []) as string[][])
            .filter((t) => t[0] === "p")
            .map((t) => t[1])
        : [];

      return {
        id: channelId,
        name: getTag("name") ?? "",
        description: getTag("about") ?? "",
        channel_type: channelType as "stream" | "forum" | "dm",
        visibility: (isPrivate ? "private" : "open") as "open" | "private",
        topic: getTag("topic") ?? null,
        purpose: getTag("purpose") ?? null,
        member_count: pTags.length,
        last_message_at: null,
        archived_at: isArchived ? new Date().toISOString() : null,
        participants: pTags,
        participant_pubkeys: pTags,
        ttl_seconds: getTag("ttl") ? Number(getTag("ttl")) : null,
        ttl_deadline: getTag("ttl_deadline") ?? null,
        is_member: memberSet.has(channelId),
      };
    })
    .filter((c) => c.channel_type !== "dm" || !hiddenDms.has(c.id));

  return {
    hash: "mock-hash",
    channels,
    last_messages: buildLastMessages(channels),
  };
}

async function handleGetProfile(config: E2eConfig | undefined) {
  const identity = getIdentity(config);
  const forcedHasProfileEvent = config?.mock?.profileHasEvent;
  if (forcedHasProfileEvent !== undefined) {
    return {
      ...cloneProfile(ensureMockProfile(config)),
      has_profile_event: forcedHasProfileEvent,
    };
  }
  if (!identity) {
    const profileReadDelayMs = config?.mock?.profileReadDelayMs ?? 0;
    if (profileReadDelayMs > 0) {
      await new Promise<void>((resolve) => {
        window.setTimeout(resolve, profileReadDelayMs);
      });
    }

    const profileReadError = config?.mock?.profileReadError;
    if (profileReadError) {
      throw new Error(profileReadError);
    }

    return cloneProfile(ensureMockProfile(config));
  }

  // Pure Nostr: query kind:0 (profile metadata) for our pubkey.
  const events = await relayQuery(config, [
    { kinds: [0], authors: [identity.pubkey], limit: 1 },
  ]);
  if (events.length === 0) {
    return {
      pubkey: identity.pubkey,
      display_name: null,
      about: null,
      avatar_url: null,
      nip05_handle: null,
      owner_pubkey: null,
      has_profile_event: false,
    };
  }
  const content = JSON.parse(events[0].content ?? "{}");
  return {
    pubkey: identity.pubkey,
    display_name: content.display_name ?? content.name ?? null,
    about: content.about ?? null,
    avatar_url: content.picture ?? null,
    nip05_handle: content.nip05 ?? null,
    owner_pubkey: null,
    has_profile_event: true,
  };
}

async function handleUpdateProfile(
  args: {
    displayName?: string;
    avatarUrl?: string;
    about?: string;
    nip05Handle?: string;
  },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  if (!identity) {
    const profileUpdateError = config?.mock?.profileUpdateError;
    const profileUpdateErrors = config?.mock?.profileUpdateErrors;
    const nextProfileUpdateError = profileUpdateErrors?.shift();
    if (nextProfileUpdateError) {
      throw new Error(nextProfileUpdateError);
    }

    if (profileUpdateError) {
      if (config?.mock) {
        config.mock.profileUpdateError = undefined;
      }
      throw new Error(profileUpdateError);
    }

    const profile = ensureMockProfile(config);
    const hasDisplayNameUpdate = typeof args.displayName === "string";
    const hasAvatarUrlUpdate = typeof args.avatarUrl === "string";
    const hasAboutUpdate = typeof args.about === "string";
    const hasNip05HandleUpdate = typeof args.nip05Handle === "string";
    const nextDisplayName = args.displayName?.trim() ?? "";
    const nextAvatarUrl = args.avatarUrl?.trim() ?? "";
    const nextAbout = args.about?.trim() ?? "";
    const nextNip05Handle = args.nip05Handle?.trim() ?? "";

    if (hasDisplayNameUpdate && nextDisplayName !== profile.display_name) {
      profile.display_name = nextDisplayName || null;
      applyMockDisplayName(profile.pubkey, profile.display_name);
    }
    if (hasAvatarUrlUpdate && nextAvatarUrl !== profile.avatar_url) {
      profile.avatar_url = nextAvatarUrl || null;
    }
    if (hasAboutUpdate && nextAbout !== profile.about) {
      profile.about = nextAbout || null;
    }
    if (hasNip05HandleUpdate && nextNip05Handle !== profile.nip05_handle) {
      profile.nip05_handle = nextNip05Handle || null;
    }

    return cloneProfile(profile);
  }

  // Read-merge-write: fetch current profile, merge, sign kind:0.
  const currentEvents = await relayQuery(config, [
    { kinds: [0], authors: [identity.pubkey], limit: 1 },
  ]);
  const currentContent = currentEvents[0]
    ? JSON.parse(currentEvents[0].content ?? "{}")
    : {};
  const profileContent = JSON.stringify({
    display_name: args.displayName ?? currentContent.display_name ?? undefined,
    name: currentContent.display_name ?? undefined,
    picture: args.avatarUrl ?? currentContent.picture ?? undefined,
    about: args.about ?? currentContent.about ?? undefined,
    nip05: args.nip05Handle ?? currentContent.nip05 ?? undefined,
  });
  await submitSignedEvent(config, {
    kind: 0,
    content: profileContent,
    tags: [],
  });

  // Return the updated profile in RawProfile shape
  const updated = JSON.parse(profileContent);
  return {
    pubkey: identity.pubkey,
    display_name: updated.display_name ?? null,
    about: updated.about ?? null,
    avatar_url: updated.picture ?? null,
    nip05_handle: updated.nip05 ?? null,
    owner_pubkey: null,
    has_profile_event: true,
  };
}

async function handleGetUserProfile(
  args: {
    pubkey?: string;
  },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  if (!identity) {
    const pubkey = (args.pubkey ?? getMockMemberPubkey(config)).toLowerCase();
    const profile = getMockProfileByPubkey(pubkey);
    if (!profile) {
      throw new Error(`User ${pubkey} not found.`);
    }

    return cloneProfile(profile);
  }

  const targetPubkey = args.pubkey ?? identity.pubkey;
  const events = await relayQuery(config, [
    { kinds: [0], authors: [targetPubkey], limit: 1 },
  ]);
  if (events.length === 0) {
    return {
      pubkey: targetPubkey,
      display_name: null,
      about: null,
      avatar_url: null,
      nip05_handle: null,
      owner_pubkey: null,
      has_profile_event: false,
    };
  }
  const content = JSON.parse(events[0].content ?? "{}");
  return {
    pubkey: targetPubkey,
    display_name: content.display_name ?? content.name ?? null,
    about: content.about ?? null,
    avatar_url: content.picture ?? null,
    nip05_handle: content.nip05 ?? null,
    owner_pubkey: null,
    has_profile_event: true,
  };
}

async function handleGetUsersBatch(
  args: {
    pubkeys: string[];
  },
  config: E2eConfig | undefined,
) {
  const usersBatchDelayMs = config?.mock?.usersBatchDelayMs ?? 0;
  if (usersBatchDelayMs > 0) {
    await new Promise<void>((resolve) => {
      window.setTimeout(resolve, usersBatchDelayMs);
    });
  }

  const identity = getIdentity(config);
  if (!identity) {
    const profiles: RawUsersBatchResponse["profiles"] = {};
    const missing: string[] = [];

    for (const pubkey of args.pubkeys) {
      const normalizedPubkey = pubkey.toLowerCase();
      const profile = getMockProfileByPubkey(normalizedPubkey);

      if (!profile) {
        missing.push(pubkey);
        continue;
      }

      profiles[normalizedPubkey] = {
        display_name: profile.display_name,
        name: profile.name ?? null,
        avatar_url: profile.avatar_url,
        nip05_handle: profile.nip05_handle,
        owner_pubkey: profile.owner_pubkey,
        is_agent: profile.is_agent ?? false,
      };
    }

    return {
      profiles,
      missing,
    };
  }

  const events = await relayQuery(config, [
    { kinds: [0], authors: args.pubkeys, limit: args.pubkeys.length },
  ]);
  const profiles: RawUsersBatchResponse["profiles"] = {};
  const found = new Set<string>();
  for (const ev of events) {
    const pk = ev.pubkey?.toLowerCase() ?? "";
    found.add(pk);
    const content = JSON.parse(ev.content ?? "{}");
    profiles[pk] = {
      display_name: content.display_name ?? content.name ?? null,
      name: content.name ?? null,
      avatar_url: content.picture ?? null,
      nip05_handle: content.nip05 ?? null,
      owner_pubkey:
        ((ev.tags ?? []) as string[][]).find(
          (tag) => Array.isArray(tag) && tag[0] === "auth" && tag.length === 4,
        )?.[1] ?? null,
      is_agent: Array.isArray(ev.tags)
        ? ev.tags.some(
            (tag) =>
              Array.isArray(tag) && tag[0] === "auth" && tag.length === 4,
          )
        : false,
    };
  }
  for (const pubkey of args.pubkeys) {
    const normalizedPubkey = pubkey.toLowerCase();
    if (found.has(normalizedPubkey)) {
      continue;
    }

    const profile = getMockProfileByPubkey(normalizedPubkey);
    if (!profile) {
      continue;
    }

    found.add(normalizedPubkey);
    profiles[normalizedPubkey] = {
      display_name: profile.display_name,
      name: profile.name ?? null,
      avatar_url: profile.avatar_url,
      nip05_handle: profile.nip05_handle,
      owner_pubkey: profile.owner_pubkey,
      is_agent: profile.is_agent ?? false,
    };
  }
  const missing = args.pubkeys.filter((p) => !found.has(p.toLowerCase()));
  return { profiles, missing };
}

async function handleSearchUsers(
  args: {
    query: string;
    limit?: number;
    cursor?: string | null;
  },
  config: E2eConfig | undefined,
) {
  const userSearchDelayMs = config?.mock?.userSearchDelayMs ?? 0;
  if (userSearchDelayMs > 0) {
    await new Promise<void>((resolve) => {
      window.setTimeout(resolve, userSearchDelayMs);
    });
  }

  const identity = getIdentity(config);
  if (!identity) {
    const normalizedQuery = args.query.trim().toLowerCase();

    const limit = args.limit ?? 8;
    const page = Math.max(Number(args.cursor ?? 1) || 1, 1);
    const allResults = listMockProfiles()
      .filter((profile) => {
        if (normalizedQuery.length === 0) {
          return true;
        }

        const displayName = profile.display_name?.toLowerCase() ?? "";
        const nip05Handle = profile.nip05_handle?.toLowerCase() ?? "";
        const pubkey = profile.pubkey.toLowerCase();
        return (
          displayName.includes(normalizedQuery) ||
          nip05Handle.includes(normalizedQuery) ||
          pubkey.includes(normalizedQuery)
        );
      })
      .sort((left, right) => {
        const leftName = left.display_name ?? left.nip05_handle ?? left.pubkey;
        const rightName =
          right.display_name ?? right.nip05_handle ?? right.pubkey;
        return leftName.localeCompare(rightName);
      });
    const results = allResults
      .slice((page - 1) * limit, page * limit)
      .map((profile) => ({
        pubkey: profile.pubkey,
        display_name: profile.display_name,
        avatar_url: profile.avatar_url,
        nip05_handle: profile.nip05_handle,
        owner_pubkey: profile.owner_pubkey,
        is_agent: profile.is_agent ?? false,
      }));

    return {
      users: results,
      next_cursor: page * limit < allResults.length ? String(page + 1) : null,
    } satisfies RawSearchUsersResponse;
  }

  // NIP-50 search on kind:0 profiles
  const limit = args.limit ?? 8;
  const normalizedQuery = args.query.trim();
  const page = Math.max(Number(args.cursor ?? 1) || 1, 1);
  const filter =
    normalizedQuery.length === 0
      ? { kinds: [0], limit, page }
      : { kinds: [0], search: args.query, limit, page };
  const events = await relayQuery(config, [filter]);
  const users = events.map((ev) => {
    const content = JSON.parse(ev.content ?? "{}");
    return {
      pubkey: ev.pubkey ?? "",
      display_name: content.display_name ?? content.name ?? null,
      avatar_url: content.picture ?? null,
      nip05_handle: content.nip05 ?? null,
      owner_pubkey:
        ((ev.tags ?? []) as string[][]).find(
          (tag) => Array.isArray(tag) && tag[0] === "auth" && tag.length === 4,
        )?.[1] ?? null,
      is_agent: Array.isArray(ev.tags)
        ? ev.tags.some(
            (tag) =>
              Array.isArray(tag) && tag[0] === "auth" && tag.length === 4,
          )
        : false,
    };
  });
  return {
    users,
    next_cursor: users.length >= limit ? String(page + 1) : null,
  };
}

async function handleGetPresence(
  args: {
    pubkeys: string[];
  },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  if (!identity) {
    return Object.fromEntries(
      args.pubkeys.map((pubkey) => [
        pubkey.toLowerCase(),
        getMockPresenceStatus(pubkey),
      ]),
    ) satisfies RawPresenceLookup;
  }

  if (args.pubkeys.length === 0) {
    return {} satisfies RawPresenceLookup;
  }

  // Presence is ephemeral (kind:20001) — mock returns from in-memory map.
  const events = await relayQuery(config, [
    { kinds: [20001], authors: args.pubkeys, limit: args.pubkeys.length },
  ]);
  const result: RawPresenceLookup = {};
  for (const ev of events) {
    // Synthesized presence events have ["p", subject_pubkey] tag
    const pTag = ((ev.tags ?? []) as string[][]).find((t) => t[0] === "p");
    const pk = pTag?.[1] ?? ev.pubkey ?? "";
    result[pk.toLowerCase()] = (ev.content ?? "offline") as PresenceStatus;
  }
  // Fill missing pubkeys with "offline"
  for (const pk of args.pubkeys) {
    if (!result[pk.toLowerCase()]) {
      result[pk.toLowerCase()] = "offline";
    }
  }
  return result;
}

async function handleEnsureStarterChannels(
  config: E2eConfig | undefined,
): Promise<RawChannelWithMembership[]> {
  const starterChannelError =
    config?.mock?.ensureStarterChannelsErrors?.shift();
  if (starterChannelError) {
    throw new Error(starterChannelError);
  }

  const currentPubkey = getMockMemberPubkey(config);
  const ensureMember = (channel: MockChannel) => {
    if (
      channel.members.some(
        (member) =>
          normalizePubkey(member.pubkey) === normalizePubkey(currentPubkey),
      )
    ) {
      return;
    }

    channel.members.push(createCurrentMember(config, "member"));
    syncMockChannel(channel);
    touchMockChannel(channel);
  };

  for (const channelName of [
    STARTER_GENERAL_CHANNEL_NAME,
    STARTER_WELCOME_CHANNEL_NAME,
  ]) {
    const channel = mockChannels.find(
      (candidate) =>
        candidate.name === channelName &&
        candidate.channel_type === "stream" &&
        candidate.visibility === "open" &&
        candidate.archived_at === null,
    );
    if (!channel) {
      throw new Error(`Starter channel ${channelName} not found.`);
    }
    ensureMember(channel);
  }

  return listMockChannels(config);
}

async function handleCreateChannel(
  args: {
    name: string;
    channelType: "stream" | "forum";
    visibility: "open" | "private";
    description?: string;
    ttlSeconds?: number;
  },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  const ttlDeadline =
    typeof args.ttlSeconds === "number"
      ? new Date(Date.now() + args.ttlSeconds * 1_000).toISOString()
      : null;
  if (!identity) {
    const createChannelError = config?.mock?.createChannelErrors?.shift();
    if (createChannelError) {
      throw new Error(createChannelError);
    }

    const owner = createCurrentMember(config, "owner");
    const channel = createMockChannel({
      id: crypto.randomUUID(),
      name: args.name,
      channel_type: args.channelType,
      visibility: args.visibility,
      description: args.description ?? "",
      topic: null,
      purpose: null,
      last_message_at: null,
      archived_at: null,
      created_by: owner.pubkey,
      topic_set_by: null,
      topic_set_at: null,
      purpose_set_by: null,
      purpose_set_at: null,
      ttl_seconds: args.ttlSeconds ?? null,
      ttl_deadline: ttlDeadline,
      topic_required: false,
      max_members: null,
      nip29_group_id: null,
      created_minutes_ago: 0,
      updated_minutes_ago: 0,
      members: [owner],
    });
    mockChannels.push(channel);
    return toRawChannel(channel, config);
  }

  const channelId = crypto.randomUUID();
  const tags: string[][] = [
    ["h", channelId],
    ["name", args.name],
    ["channel_type", args.channelType],
    ["visibility", args.visibility],
  ];
  if (args.description) {
    tags.push(["about", args.description]);
  }
  if (typeof args.ttlSeconds === "number") {
    tags.push(["ttl", String(args.ttlSeconds)]);
  }
  await submitSignedEvent(config, { kind: 9007, content: "", tags });

  // Fetch the created channel via pure Nostr query.
  // The relay emits kind:39000 as a side effect of kind:9007.
  const metaEvents = await relayQuery(config, [
    { kinds: [39000], "#d": [channelId], limit: 1 },
  ]);
  const ev = metaEvents[0];
  if (!ev) {
    throw new Error(`Channel "${args.name}" not found after creation`);
  }
  const evTags = (ev.tags ?? []) as string[][];
  const getTag = (name: string) =>
    evTags.find((t) => t[0] === name)?.[1] ?? null;
  return {
    id: channelId,
    name: getTag("name") ?? args.name,
    description: getTag("about") ?? args.description ?? null,
    channel_type: args.channelType,
    visibility: args.visibility,
    topic: null,
    purpose: null,
    role: "owner",
    archived_at: null,
    ttl_seconds: args.ttlSeconds ?? null,
    ttl_deadline: ttlDeadline,
    created_at: ev.created_at
      ? new Date(ev.created_at * 1000).toISOString()
      : new Date().toISOString(),
  };
}

async function handleOpenDm(
  args: {
    pubkeys: string[];
  },
  config: E2eConfig | undefined,
) {
  const delayMs = config?.mock?.openDmDelayMs ?? 0;
  if (delayMs > 0) {
    await new Promise((resolve) => window.setTimeout(resolve, delayMs));
  }

  const normalizedPubkeys = normalizeParticipantPubkeys(args.pubkeys);
  if (normalizedPubkeys.length === 0) {
    throw new Error("Select at least one person to start a DM.");
  }

  const currentPubkey = getMockMemberPubkey(config).toLowerCase();
  const participantPubkeys = normalizeParticipantPubkeys([
    currentPubkey,
    ...normalizedPubkeys.filter((pubkey) => pubkey !== currentPubkey),
  ]);
  const existingChannel = findMockDmByParticipantPubkeys(participantPubkeys);
  if (existingChannel) {
    return toRawChannel(existingChannel, config);
  }

  const identity = getIdentity(config);
  if (!identity) {
    const members = participantPubkeys.map((pubkey) =>
      createMockMember(pubkey, "member", 0),
    );
    const channel = createMockChannel({
      id: crypto.randomUUID(),
      name:
        participantPubkeys.length === 2
          ? "DM"
          : `Group DM (${participantPubkeys.length})`,
      channel_type: "dm",
      visibility: "private",
      description: "Direct message conversation",
      topic: null,
      purpose: null,
      last_message_at: null,
      archived_at: null,
      created_by: getMockMemberPubkey(config),
      topic_set_by: null,
      topic_set_at: null,
      purpose_set_by: null,
      purpose_set_at: null,
      topic_required: false,
      max_members: participantPubkeys.length,
      nip29_group_id: null,
      created_minutes_ago: 0,
      updated_minutes_ago: 0,
      members,
    });
    syncMockChannel(channel);
    mockChannels.push(channel);
    return toRawChannel(channel, config);
  }

  // Submit kind:41010 (DM open) with p-tags for participants
  const tags = normalizedPubkeys.map((pk) => ["p", pk]);
  const result = await submitSignedEvent(config, {
    kind: 41010,
    content: "",
    tags,
  });
  // Parse channel_id from response message
  const respJson = JSON.parse(result.message.replace("response:", "") || "{}");
  const channelId = respJson.channel_id ?? "";

  // Fetch channel metadata
  const metaEvents = await relayQuery(config, [
    { kinds: [39000], "#d": [channelId], limit: 1 },
  ]);
  const ev = metaEvents[0];
  const evTags = (ev?.tags ?? []) as string[][];
  const getTag = (name: string) =>
    evTags.find((t) => t[0] === name)?.[1] ?? null;
  return {
    id: channelId,
    name: getTag("name") ?? "DM",
    description: null,
    channel_type: "dm",
    visibility: "private",
    topic: null,
    purpose: null,
    role: "member",
    archived_at: null,
    ttl_seconds: null,
    ttl_deadline: null,
    created_at: ev?.created_at
      ? new Date(ev.created_at * 1000).toISOString()
      : new Date().toISOString(),
  };
}

async function handleHideDm(
  args: { channelId: string },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  if (!identity) {
    const index = mockChannels.findIndex(
      (channel) => channel.id === args.channelId,
    );
    if (index === -1) {
      throw new Error(`DM ${args.channelId} not found.`);
    }
    // Remove from mock list (simulates hiding from sidebar).
    mockChannels.splice(index, 1);
    return;
  }

  // Submit kind:41012 (DM hide) with h-tag
  await submitSignedEvent(config, {
    kind: 41012,
    content: "",
    tags: [["h", args.channelId]],
  });
}

async function handleGetChannelDetails(
  args: { channelId: string },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  if (!identity) {
    return toRawChannelDetail(getMockChannel(args.channelId), config);
  }

  const metaEvents = await relayQuery(config, [
    { kinds: [39000], "#d": [args.channelId], limit: 1 },
  ]);
  const ev = metaEvents[0];
  const evTags = (ev?.tags ?? []) as string[][];
  const getTag = (name: string) =>
    evTags.find((t) => t[0] === name)?.[1] ?? null;

  // Get members for member_count
  const memberEvents = await relayQuery(config, [
    { kinds: [39002], "#d": [args.channelId], limit: 1 },
  ]);
  const memberTags = ((memberEvents[0]?.tags ?? []) as string[][]).filter(
    (t) => t[0] === "p",
  );

  return {
    id: args.channelId,
    name: getTag("name") ?? "",
    description: getTag("about") ?? null,
    channel_type: getTag("t") ?? "stream",
    visibility: evTags.some((t) => t[0] === "private") ? "private" : "open",
    topic: getTag("topic") ?? null,
    purpose: getTag("purpose") ?? null,
    member_count: memberTags.length,
    role: "member",
    archived_at: evTags.some((t) => t[0] === "archived" && t[1] === "true")
      ? new Date().toISOString()
      : null,
    ttl_seconds: getTag("ttl") ? Number(getTag("ttl")) : null,
    ttl_deadline: getTag("ttl_deadline") ?? null,
    created_at: ev?.created_at
      ? new Date(ev.created_at * 1000).toISOString()
      : new Date().toISOString(),
  };
}

async function handleGetChannelMembers(
  args: { channelId: string },
  config: E2eConfig | undefined,
): Promise<RawChannelMembersResponse> {
  const delayMs = config?.mock?.channelMembersReadDelayMs ?? 0;
  if (delayMs > 0) {
    await new Promise((resolve) => window.setTimeout(resolve, delayMs));
  }

  const identity = getIdentity(config);
  if (!identity) {
    const channel = getMockChannel(args.channelId);
    return {
      members: cloneMembers(channel.members),
      next_cursor: null,
    };
  }

  const memberEvents = await relayQuery(config, [
    { kinds: [39002], "#d": [args.channelId], limit: 1 },
  ]);
  const memberTags = ((memberEvents[0]?.tags ?? []) as string[][]).filter(
    (t) => t[0] === "p",
  );
  const members = memberTags.map((t) => ({
    pubkey: t[1],
    role: (t[3] ?? t[2] ?? "member") as
      | "owner"
      | "admin"
      | "member"
      | "guest"
      | "bot",
    is_agent: (t[3] ?? t[2]) === "bot",
    display_name: null,
    avatar_url: null,
    joined_at: new Date().toISOString(),
  }));
  return { members, next_cursor: null };
}

async function handleUpdateChannel(
  args: {
    channelId: string;
    name?: string;
    description?: string;
    visibility?: "open" | "private";
    ttlSeconds?: number | null;
  },
  config: E2eConfig | undefined,
) {
  const delayMs = config?.mock?.updateChannelDelayMs ?? 0;
  if (delayMs > 0) {
    await new Promise((resolve) => window.setTimeout(resolve, delayMs));
  }

  const identity = getIdentity(config);
  if (!identity) {
    const channel = getMockChannel(args.channelId);
    if (args.name !== undefined) {
      channel.name = args.name;
    }
    if (args.description !== undefined) {
      channel.description = args.description;
    }
    if (args.visibility !== undefined) {
      channel.visibility = args.visibility;
    }
    if (args.ttlSeconds !== undefined) {
      channel.ttl_seconds = args.ttlSeconds;
      channel.ttl_deadline =
        args.ttlSeconds === null
          ? null
          : new Date(Date.now() + args.ttlSeconds * 1000).toISOString();
    }
    touchMockChannel(channel);
    return toRawChannelDetail(channel, config);
  }

  const tags: string[][] = [["h", args.channelId]];
  if (args.name !== undefined) {
    tags.push(["name", args.name]);
  }
  if (args.description !== undefined) {
    tags.push(["about", args.description]);
  }
  if (args.visibility !== undefined) {
    tags.push(["visibility", args.visibility]);
  }
  if (args.ttlSeconds !== undefined) {
    tags.push(["ttl", args.ttlSeconds === null ? "" : String(args.ttlSeconds)]);
  }
  await submitSignedEvent(config, { kind: 9002, content: "", tags });

  // Re-fetch updated metadata
  const metaEvents = await relayQuery(config, [
    { kinds: [39000], "#d": [args.channelId], limit: 1 },
  ]);
  const ev = metaEvents[0];
  const evTags = (ev?.tags ?? []) as string[][];
  const getTag = (name: string) =>
    evTags.find((t) => t[0] === name)?.[1] ?? null;
  const ttlTag = getTag("ttl");
  const ttlSeconds = ttlTag === null || ttlTag === "" ? null : Number(ttlTag);
  return {
    id: args.channelId,
    name: getTag("name") ?? "",
    description: getTag("about") ?? null,
    channel_type: getTag("t") ?? "stream",
    visibility: getTag("visibility") ?? "open",
    topic: getTag("topic") ?? null,
    purpose: getTag("purpose") ?? null,
    member_count: 0,
    role: "owner",
    archived_at: null,
    ttl_seconds: ttlSeconds,
    ttl_deadline:
      ttlSeconds === null
        ? null
        : new Date(Date.now() + ttlSeconds * 1000).toISOString(),
    created_at: ev?.created_at
      ? new Date(ev.created_at * 1000).toISOString()
      : new Date().toISOString(),
  };
}

async function handleSetChannelTopic(
  args: {
    channelId: string;
    topic: string;
  },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  if (!identity) {
    const channel = getMockChannel(args.channelId);
    const nextTopic = args.topic.trim();

    channel.topic = nextTopic.length > 0 ? nextTopic : null;
    channel.topic_set_by = getMockMemberPubkey(config);
    channel.topic_set_at = new Date().toISOString();
    touchMockChannel(channel);
    return;
  }

  await submitSignedEvent(config, {
    kind: 9002,
    content: "",
    tags: [
      ["h", args.channelId],
      ["topic", args.topic],
    ],
  });
}

async function handleSetChannelPurpose(
  args: {
    channelId: string;
    purpose: string;
  },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  if (!identity) {
    const channel = getMockChannel(args.channelId);
    const nextPurpose = args.purpose.trim();

    channel.purpose = nextPurpose.length > 0 ? nextPurpose : null;
    channel.purpose_set_by = getMockMemberPubkey(config);
    channel.purpose_set_at = new Date().toISOString();
    touchMockChannel(channel);
    return;
  }

  await submitSignedEvent(config, {
    kind: 9002,
    content: "",
    tags: [
      ["h", args.channelId],
      ["purpose", args.purpose],
    ],
  });
}

type MockUpdaterChannel = {
  onmessage?: (event: { event: "Finished" }) => void;
};

function notifyUpdaterFinished(payload: unknown) {
  const channel = (payload as { onEvent?: MockUpdaterChannel } | null)?.onEvent;
  channel?.onmessage?.({ event: "Finished" });
}

function handleUpdaterCheck(config: E2eConfig | undefined) {
  if (!config?.mock?.updateAvailable) {
    return null;
  }

  const version = config.mock.updateVersion ?? "0.3.18";

  return {
    rid: 42,
    currentVersion: "0.3.17",
    version,
    date: "2026-06-12T00:00:00Z",
    body: `Mock update ${version}`,
    rawJson: null,
  };
}

async function handleUpdaterDownload(
  payload: unknown,
  config: E2eConfig | undefined,
) {
  const delayMs = config?.mock?.updateDownloadDelayMs ?? 0;

  if (delayMs > 0) {
    await new Promise((resolve) => window.setTimeout(resolve, delayMs));
  }

  notifyUpdaterFinished(payload);
  return 43;
}

function handleUpdaterInstall() {
  return null;
}

async function handleRestart(config: E2eConfig | undefined) {
  const delayMs = config?.mock?.restartDelayMs ?? 0;

  if (delayMs > 0) {
    await new Promise((resolve) => window.setTimeout(resolve, delayMs));
  }

  return null;
}

async function handleArchiveChannel(
  args: { channelId: string },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  if (!identity) {
    const channel = getMockChannel(args.channelId);
    channel.archived_at = new Date().toISOString();
    touchMockChannel(channel);
    return;
  }

  await submitSignedEvent(config, {
    kind: 9002,
    content: "",
    tags: [
      ["h", args.channelId],
      ["archived", "true"],
    ],
  });
}

async function handleUnarchiveChannel(
  args: { channelId: string },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  if (!identity) {
    const channel = getMockChannel(args.channelId);
    channel.archived_at = null;
    touchMockChannel(channel);
    return;
  }

  await submitSignedEvent(config, {
    kind: 9002,
    content: "",
    tags: [
      ["h", args.channelId],
      ["archived", "false"],
    ],
  });
}

async function handleDeleteChannel(
  args: { channelId: string },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  if (!identity) {
    const index = mockChannels.findIndex(
      (channel) => channel.id === args.channelId,
    );
    if (index === -1) {
      throw new Error(`Channel ${args.channelId} not found.`);
    }

    mockChannels.splice(index, 1);
    mockMessages.delete(args.channelId);
    return;
  }

  await submitSignedEvent(config, {
    kind: 9008,
    content: "",
    tags: [["h", args.channelId]],
  });
}

async function handleAddChannelMembers(
  args: {
    channelId: string;
    pubkeys: string[];
    role?: RawChannelMember["role"];
  },
  config: E2eConfig | undefined,
): Promise<RawAddChannelMembersResponse> {
  const addChannelMembersDelayMs = config?.mock?.addChannelMembersDelayMs ?? 0;
  if (addChannelMembersDelayMs > 0) {
    await new Promise((resolve) =>
      window.setTimeout(resolve, addChannelMembersDelayMs),
    );
  }
  const configuredErrors = config?.mock?.addChannelMembersErrors;
  if (configuredErrors && configuredErrors.length > 0) {
    const index = Math.min(
      addChannelMembersCallCount,
      configuredErrors.length - 1,
    );
    addChannelMembersCallCount += 1;
    const error = configuredErrors[index];
    if (error) {
      return {
        added: [],
        errors: args.pubkeys.map((pubkey) => ({ pubkey, error })),
      };
    }
  }
  const identity = getIdentity(config);
  if (!identity) {
    const channel = getMockChannel(args.channelId);
    const added: string[] = [];
    const errors: RawAddChannelMembersResponse["errors"] = [];
    const existingPubkeys = new Set(
      channel.members.map((member) => normalizePubkey(member.pubkey)),
    );

    for (const pubkey of args.pubkeys) {
      const normalizedPubkey = normalizePubkey(pubkey);
      if (existingPubkeys.has(normalizedPubkey)) {
        errors.push({
          pubkey,
          error: "Already a member.",
        });
        continue;
      }

      existingPubkeys.add(normalizedPubkey);
      added.push(pubkey);
    }

    // DM participant sets are immutable. Adding a member creates or reuses a
    // separate DM for the expanded set instead of mutating the source channel.
    const targetChannel =
      channel.channel_type === "dm" && added.length > 0
        ? getMockChannel(
            (
              await handleOpenDm(
                {
                  pubkeys: [
                    ...channel.members.map((member) => member.pubkey),
                    ...added,
                  ],
                },
                config,
              )
            ).id,
          )
        : channel;

    for (const pubkey of added) {
      const existingMember = targetChannel.members.find(
        (member) => normalizePubkey(member.pubkey) === normalizePubkey(pubkey),
      );
      if (existingMember) {
        existingMember.role = args.role ?? "member";
        existingMember.is_agent =
          args.role === "bot" ||
          mockAgentPubkeys.has(pubkey) ||
          mockManagedAgents.some((agent) => agent.pubkey === pubkey);
        existingMember.display_name = mockDisplayNames.get(pubkey) ?? null;
        continue;
      }
      targetChannel.members.push({
        pubkey,
        role: args.role ?? "member",
        is_agent:
          args.role === "bot" ||
          mockAgentPubkeys.has(pubkey) ||
          mockManagedAgents.some((agent) => agent.pubkey === pubkey),
        joined_at: new Date().toISOString(),
        display_name: mockDisplayNames.get(pubkey) ?? null,
      });
    }

    syncMockChannel(targetChannel);
    touchMockChannel(targetChannel);
    syncMockRelayAgentsFromManagedAgents();
    return {
      added,
      errors,
    };
  }

  const added: string[] = [];
  const errors: RawAddChannelMembersResponse["errors"] = [];
  for (const pubkey of args.pubkeys) {
    try {
      const tags: string[][] = [
        ["h", args.channelId],
        ["p", pubkey],
      ];
      if (args.role) {
        tags.push(["role", args.role]);
      }
      await submitSignedEvent(config, { kind: 9000, content: "", tags });
      added.push(pubkey);
    } catch (e) {
      errors.push({ pubkey, error: String(e) });
    }
  }
  return { added, errors };
}

async function handleRemoveChannelMember(
  args: {
    channelId: string;
    pubkey: string;
  },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  if (!identity) {
    const channel = getMockChannel(args.channelId);
    channel.members = channel.members.filter(
      (member) => member.pubkey !== args.pubkey,
    );
    syncMockChannel(channel);
    touchMockChannel(channel);
    syncMockRelayAgentsFromManagedAgents();
    return;
  }

  await submitSignedEvent(config, {
    kind: 9001,
    content: "",
    tags: [
      ["h", args.channelId],
      ["p", args.pubkey],
    ],
  });
}

async function handleJoinChannel(
  args: {
    channelId: string;
  },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  if (!identity) {
    const joinChannelError = config?.mock?.joinChannelErrors?.shift();
    if (joinChannelError) {
      throw new Error(joinChannelError);
    }

    const channel = getMockChannel(args.channelId);
    const currentPubkey = getMockMemberPubkey(config);

    if (channel.members.some((member) => member.pubkey === currentPubkey)) {
      return;
    }

    channel.members.push(createCurrentMember(config, "member"));
    syncMockChannel(channel);
    touchMockChannel(channel);
    return;
  }

  await submitSignedEvent(config, {
    kind: 9021,
    content: "",
    tags: [["h", args.channelId]],
  });
}

async function handleLeaveChannel(
  args: {
    channelId: string;
  },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  if (!identity) {
    const channel = getMockChannel(args.channelId);
    const currentPubkey = getMockMemberPubkey(config);

    channel.members = channel.members.filter(
      (member) => member.pubkey !== currentPubkey,
    );
    syncMockChannel(channel);
    touchMockChannel(channel);
    return;
  }

  await submitSignedEvent(config, {
    kind: 9022,
    content: "",
    tags: [["h", args.channelId]],
  });
}

async function handleGetFeed(
  args: {
    since?: number;
    limit?: number;
    types?: string;
  },
  config: E2eConfig | undefined,
): Promise<RawHomeFeedResponse> {
  const feedReadError = config?.mock?.feedReadError;
  if (feedReadError) {
    throw new Error(feedReadError);
  }

  const identity = getIdentity(config);
  if (!identity) {
    const now = Math.floor(Date.now() / 1000);
    const limit = args.limit ?? 50;
    const wantedTypes =
      args.types
        ?.split(",")
        .map((value) => value.trim())
        .filter((value) => value.length > 0) ?? [];
    const includeType = (type: string) =>
      wantedTypes.length === 0 || wantedTypes.includes(type);

    const currentPubkey = getMockMemberPubkey(config).toLowerCase();
    const defaultFeed: RawHomeFeedResponse["feed"] =
      currentPubkey === ALICE_PUBKEY
        ? {
            mentions: [
              {
                id: "mock-feed-alice-mention",
                kind: 9,
                pubkey: BOB_PUBKEY,
                content: "Alice, can you sanity-check the new design mocks?",
                created_at: now - 90,
                channel_id: "b5e2f8a1-3c44-5912-9e67-4a8d1f2b3c4e",
                channel_name: "design",
                tags: [
                  ["e", "b5e2f8a1-3c44-5912-9e67-4a8d1f2b3c4e"],
                  ["p", ALICE_PUBKEY],
                ],
                category: "mention" as const,
              },
            ],
            needs_action: [
              {
                id: "mock-feed-alice-reminder",
                kind: 40007,
                pubkey:
                  "0000000000000000000000000000000000000000000000000000000000000000",
                content: "Reminder: post the engineering launch note.",
                created_at: now - 15 * 60,
                channel_id: "1c7e1c02-87bb-5e88-b2da-5a7a9432d0c9",
                channel_name: "engineering",
                tags: [
                  ["e", "1c7e1c02-87bb-5e88-b2da-5a7a9432d0c9"],
                  ["p", ALICE_PUBKEY],
                ],
                category: "needs_action" as const,
              },
            ],
            activity: [
              {
                id: "mock-feed-alice-self-activity",
                kind: 9,
                pubkey: ALICE_PUBKEY,
                content: "I posted the latest design review summary.",
                created_at: now - 25 * 60,
                channel_id: "b5e2f8a1-3c44-5912-9e67-4a8d1f2b3c4e",
                channel_name: "design",
                tags: [["e", "b5e2f8a1-3c44-5912-9e67-4a8d1f2b3c4e"]],
                category: "activity" as const,
              },
              {
                id: "mock-feed-alice-activity",
                kind: 9,
                pubkey: BOB_PUBKEY,
                content: "Engineering signed off on the desktop build.",
                created_at: now - 42 * 60,
                channel_id: "1c7e1c02-87bb-5e88-b2da-5a7a9432d0c9",
                channel_name: "engineering",
                tags: [["e", "1c7e1c02-87bb-5e88-b2da-5a7a9432d0c9"]],
                category: "activity" as const,
              },
            ],
            agent_activity: [
              {
                id: "mock-feed-alice-agent",
                kind: 43003,
                pubkey:
                  "db0b028cd36f4d3e36c8300cce87252c1f7fc9495ffecc53f393fcac341ffd36",
                content: "Agent progress: design review summary complete.",
                created_at: now - 2 * 60 * 60,
                channel_id: "1c7e1c02-87bb-5e88-b2da-5a7a9432d0c9",
                channel_name: "engineering",
                tags: [["e", "1c7e1c02-87bb-5e88-b2da-5a7a9432d0c9"]],
                category: "agent_activity" as const,
              },
            ],
          }
        : currentPubkey === DEFAULT_REAL_IDENTITY.pubkey.toLowerCase()
          ? {
              mentions: [
                {
                  id: "mock-feed-tyler-mention",
                  kind: 9,
                  pubkey: ALICE_PUBKEY,
                  content: "Tyler, can you review the DM onboarding copy?",
                  created_at: now - 90,
                  channel_id: "f48efb06-0c93-5025-aac9-2e646bb6bfa8",
                  channel_name: "alice-tyler",
                  tags: [
                    ["e", "f48efb06-0c93-5025-aac9-2e646bb6bfa8"],
                    ["p", DEFAULT_REAL_IDENTITY.pubkey],
                  ],
                  category: "mention" as const,
                },
              ],
              needs_action: [
                {
                  id: "mock-feed-tyler-reminder",
                  kind: 40007,
                  pubkey:
                    "0000000000000000000000000000000000000000000000000000000000000000",
                  content: "Reminder: answer Bob in the launch DM thread.",
                  created_at: now - 15 * 60,
                  channel_id: "7eb9f239-9393-50b0-bd76-d85eef0511c7",
                  channel_name: "bob-tyler",
                  tags: [
                    ["e", "7eb9f239-9393-50b0-bd76-d85eef0511c7"],
                    ["p", DEFAULT_REAL_IDENTITY.pubkey],
                  ],
                  category: "needs_action" as const,
                },
              ],
              activity: [
                {
                  id: "mock-feed-tyler-self-activity",
                  kind: 9,
                  pubkey: DEFAULT_REAL_IDENTITY.pubkey,
                  content: "I sent the follow-up in the Alice DM.",
                  created_at: now - 25 * 60,
                  channel_id: "f48efb06-0c93-5025-aac9-2e646bb6bfa8",
                  channel_name: "alice-tyler",
                  tags: [["e", "f48efb06-0c93-5025-aac9-2e646bb6bfa8"]],
                  category: "activity" as const,
                },
              ],
              agent_activity: [
                {
                  id: "mock-feed-tyler-agent",
                  kind: 43003,
                  pubkey:
                    "db0b028cd36f4d3e36c8300cce87252c1f7fc9495ffecc53f393fcac341ffd36",
                  content: "Agent progress: DM summary complete.",
                  created_at: now - 2 * 60 * 60,
                  channel_id: "f48efb06-0c93-5025-aac9-2e646bb6bfa8",
                  channel_name: "alice-tyler",
                  tags: [["e", "f48efb06-0c93-5025-aac9-2e646bb6bfa8"]],
                  category: "agent_activity" as const,
                },
              ],
            }
          : {
              mentions: [
                {
                  id: "mock-feed-mention",
                  kind: 9,
                  pubkey: ALICE_PUBKEY,
                  content: "Please review the release checklist.",
                  created_at: now - 90,
                  channel_id: "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50",
                  channel_name: "general",
                  tags: [
                    ["e", "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50"],
                    ["p", currentPubkey],
                  ],
                  category: "mention" as const,
                },
              ],
              needs_action: [
                {
                  id: "mock-feed-reminder",
                  kind: 40007,
                  pubkey:
                    "0000000000000000000000000000000000000000000000000000000000000000",
                  content: "Reminder: update the launch plan before lunch.",
                  created_at: now - 15 * 60,
                  channel_id: "94a444a4-c0a3-5966-ab05-530c6ddc2301",
                  channel_name: "agents",
                  tags: [
                    ["e", "94a444a4-c0a3-5966-ab05-530c6ddc2301"],
                    ["p", currentPubkey],
                  ],
                  category: "needs_action" as const,
                },
              ],
              activity: [
                {
                  id: "mock-feed-self-activity",
                  kind: 9,
                  pubkey: currentPubkey,
                  content: "I posted a note about the launch checklist.",
                  created_at: now - 25 * 60,
                  channel_id: "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50",
                  channel_name: "general",
                  tags: [["e", "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50"]],
                  category: "activity" as const,
                },
                {
                  id: "mock-feed-activity",
                  kind: 9,
                  pubkey: BOB_PUBKEY,
                  content: "Engineering shipped the desktop build.",
                  created_at: now - 42 * 60,
                  channel_id: "1c7e1c02-87bb-5e88-b2da-5a7a9432d0c9",
                  channel_name: "engineering",
                  tags: [["e", "1c7e1c02-87bb-5e88-b2da-5a7a9432d0c9"]],
                  category: "activity" as const,
                },
              ],
              agent_activity: [
                {
                  id: "mock-feed-agent",
                  kind: 43003,
                  pubkey:
                    "db0b028cd36f4d3e36c8300cce87252c1f7fc9495ffecc53f393fcac341ffd36",
                  content: "Agent progress: channel index complete.",
                  created_at: now - 2 * 60 * 60,
                  channel_id: "94a444a4-c0a3-5966-ab05-530c6ddc2301",
                  channel_name: "agents",
                  tags: [["e", "94a444a4-c0a3-5966-ab05-530c6ddc2301"]],
                  category: "agent_activity" as const,
                },
              ],
            };

    const mergeFeedCategory = (
      category: keyof RawHomeFeedResponse["feed"],
    ): RawFeedItem[] =>
      includeType(category)
        ? [...mockFeedOverrides[category], ...defaultFeed[category]]
            .sort((left, right) => right.created_at - left.created_at)
            .slice(0, limit)
        : [];

    const mentions = mergeFeedCategory("mentions");
    const needsAction = mergeFeedCategory("needs_action");
    const activity = mergeFeedCategory("activity");
    const agentActivity = mergeFeedCategory("agent_activity");

    return {
      feed: {
        mentions,
        needs_action: needsAction,
        activity,
        agent_activity: agentActivity,
      },
      meta: {
        since: args.since ?? now - 7 * 24 * 60 * 60,
        total:
          mentions.length +
          needsAction.length +
          activity.length +
          agentActivity.length,
        generated_at: now,
      },
    };
  }

  // Feed is composed of multiple queries: mentions (#p), activity, approvals.
  // For e2e, return a minimal feed structure with mentions.
  const limit = args.limit ?? 50;
  const mentionEvents = await relayQuery(config, [
    {
      kinds: [
        9,
        40002,
        1,
        45001,
        45003,
        KIND_GIT_PULL_REQUEST,
        KIND_GIT_PR_UPDATE,
        KIND_GIT_ISSUE,
        KIND_GIT_STATUS_OPEN,
        KIND_GIT_STATUS_MERGED,
        KIND_GIT_STATUS_CLOSED,
        KIND_GIT_STATUS_DRAFT,
      ],
      "#p": [identity.pubkey],
      limit,
    },
  ]);

  // Look up channel names for feed items
  const channelIdsInFeed = [
    ...new Set(
      mentionEvents
        .map(
          (ev) =>
            ((ev.tags ?? []) as string[][]).find((t) => t[0] === "h")?.[1],
        )
        .filter(Boolean) as string[],
    ),
  ];
  const channelNameMap = new Map<string, string>();
  if (channelIdsInFeed.length > 0) {
    const metaEvents = await relayQuery(config, [
      {
        kinds: [39000],
        "#d": channelIdsInFeed,
        limit: channelIdsInFeed.length,
      },
    ]);
    for (const me of metaEvents) {
      const d = ((me.tags ?? []) as string[][]).find((t) => t[0] === "d")?.[1];
      const name = ((me.tags ?? []) as string[][]).find(
        (t) => t[0] === "name",
      )?.[1];
      if (d && name) channelNameMap.set(d, name);
    }
  }

  const items = mentionEvents.map((ev) => {
    const chId =
      ((ev.tags ?? []) as string[][]).find((t) => t[0] === "h")?.[1] ?? null;
    return {
      id: ev.id ?? "",
      pubkey: ev.pubkey ?? "",
      content: ev.content ?? "",
      created_at: ev.created_at ?? 0,
      kind: ev.kind ?? 9,
      tags: (ev.tags ?? []) as string[][],
      channel_id: chId,
      channel_name: chId ? (channelNameMap.get(chId) ?? "") : "",
      // Native-shaped: get_feed emits channel_type: null (Option<String>),
      // never omits the key. Keeping the bridge faithful here is what lets
      // the DM dedupe e2e catch null-vs-undefined regressions at the API
      // conversion seam.
      channel_type: null,
      category: "mention" as const,
    };
  });
  return {
    feed: {
      mentions: items,
      needs_action: [],
      activity: [],
      agent_activity: [],
    },
    meta: {
      since: Math.floor(Date.now() / 1000) - 7 * 86400,
      total: items.length,
      generated_at: Math.floor(Date.now() / 1000),
    },
  };
}

async function delayAgentList(config: E2eConfig | undefined) {
  const agentListDelayMs = config?.mock?.agentListDelayMs ?? 0;
  if (agentListDelayMs > 0) {
    await new Promise<void>((resolve) => {
      window.setTimeout(resolve, agentListDelayMs);
    });
  }
}

async function handleListRelayAgents(
  config: E2eConfig | undefined,
): Promise<RawRelayAgent[]> {
  await delayAgentList(config);
  const error = config?.mock?.relayAgentListErrors?.shift();
  if (error) throw new Error(error);
  syncMockRelayAgentsFromManagedAgents();
  return mockRelayAgents.map(cloneRelayAgent);
}

function withMockRuntimeConfigMetadata(
  runtime: RawAcpRuntimeCatalogEntry,
): RawAcpRuntimeCatalogEntry {
  return {
    ...runtime,
    node_required: runtime.node_required ?? false,
    auth_status: runtime.auth_status ?? { status: "unknown" },
    model_env_var:
      "model_env_var" in runtime
        ? runtime.model_env_var
        : runtime.id === "buzz-agent"
          ? "BUZZ_AGENT_MODEL"
          : runtime.id === "goose"
            ? "GOOSE_MODEL"
            : null,
    provider_env_var:
      "provider_env_var" in runtime
        ? runtime.provider_env_var
        : runtime.id === "buzz-agent"
          ? "BUZZ_AGENT_PROVIDER"
          : runtime.id === "goose"
            ? "GOOSE_PROVIDER"
            : null,
    thinking_env_var:
      "thinking_env_var" in runtime
        ? runtime.thinking_env_var
        : runtime.id === "buzz-agent"
          ? "BUZZ_AGENT_THINKING_EFFORT"
          : runtime.id === "goose"
            ? "GOOSE_THINKING_EFFORT"
            : null,
    max_tokens_env_var:
      "max_tokens_env_var" in runtime
        ? runtime.max_tokens_env_var
        : runtime.id === "buzz-agent"
          ? "BUZZ_AGENT_MAX_OUTPUT_TOKENS"
          : runtime.id === "goose"
            ? "GOOSE_MAX_TOKENS"
            : null,
    context_limit_env_var:
      "context_limit_env_var" in runtime
        ? runtime.context_limit_env_var
        : runtime.id === "buzz-agent"
          ? "BUZZ_AGENT_MAX_CONTEXT_TOKENS"
          : runtime.id === "goose"
            ? "GOOSE_CONTEXT_LIMIT"
            : null,
    max_rounds_env_var:
      "max_rounds_env_var" in runtime
        ? runtime.max_rounds_env_var
        : runtime.id === "buzz-agent"
          ? "BUZZ_AGENT_MAX_ROUNDS"
          : null,
  };
}

let runtimeCatalogDiscoveryCount = 0;
let mockInstallCompleted = false;
let mockConnectCompleted = false;

async function handleDiscoverAcpRuntimes(
  config: E2eConfig | undefined,
): Promise<RawAcpRuntimeCatalogEntry[]> {
  const delayMs = config?.mock?.acpRuntimesDelayMs ?? 0;
  if (delayMs > 0) {
    await new Promise<void>((resolve) => {
      window.setTimeout(resolve, delayMs);
    });
  }

  if (config?.mock?.acpRuntimesError) {
    throw new Error("Mocked catalog discovery failure");
  }

  const afterInstallSequence =
    config?.mock?.acpRuntimesCatalogAfterInstallSequence;
  if (mockInstallCompleted && afterInstallSequence?.length) {
    const index = Math.min(
      runtimeCatalogDiscoveryCount,
      afterInstallSequence.length - 1,
    );
    runtimeCatalogDiscoveryCount += 1;
    return afterInstallSequence[index].map(withMockRuntimeConfigMetadata);
  }
  const afterInstall = config?.mock?.acpRuntimesCatalogAfterInstall;
  if (mockInstallCompleted && afterInstall) {
    return afterInstall.map(withMockRuntimeConfigMetadata);
  }
  const afterConnect = config?.mock?.acpRuntimesCatalogAfterConnect;
  if (mockConnectCompleted && afterConnect) {
    return afterConnect.map(withMockRuntimeConfigMetadata);
  }
  const sequence = config?.mock?.acpRuntimesCatalogSequence;
  if (sequence && sequence.length > 0) {
    const index = Math.min(runtimeCatalogDiscoveryCount, sequence.length - 1);
    runtimeCatalogDiscoveryCount += 1;
    return sequence[index].map(withMockRuntimeConfigMetadata);
  }
  const configured = config?.mock?.acpRuntimesCatalog;
  if (configured) {
    return mergeMockCustomHarnesses(
      configured.map(withMockRuntimeConfigMetadata),
    );
  }
  const defaultCatalog: RawAcpRuntimeCatalogEntry[] = [
    {
      id: "goose",
      label: "Goose",
      avatar_url: "",
      availability: "available",
      command: "goose",
      binary_path: "/usr/local/bin/goose",
      default_args: ["acp"],
      mcp_command: null,
      install_hint: "Install Goose via the official install script.",
      install_instructions_url: "https://block.github.io/goose/",
      can_auto_install: true,
      requires_external_cli: true,
      underlying_cli_path: null,
      node_required: false,
      auth_status: { status: "not_applicable" },
      source: "builtin",
      login_hint: undefined,
    },
    {
      id: "claude",
      label: "Claude Code",
      avatar_url: "",
      availability: "adapter_missing",
      command: null,
      binary_path: null,
      default_args: [],
      mcp_command: null,
      install_hint: "Install the Claude Code ACP adapter via npm.",
      install_instructions_url:
        "https://www.npmjs.com/package/@anthropic-ai/claude-agent-acp",
      can_auto_install: true,
      requires_external_cli: true,
      underlying_cli_path: "/usr/local/bin/claude",
      node_required: false,
      auth_status: { status: "unknown" },
      source: "builtin",
      login_hint: undefined,
    },
    {
      id: "codex",
      label: "Codex",
      avatar_url: "",
      availability: "not_installed",
      command: null,
      binary_path: null,
      default_args: [],
      mcp_command: null,
      install_hint:
        "The codex-acp adapter must be built from source. See the GitHub repo.",
      install_instructions_url: "https://github.com/openai/codex",
      can_auto_install: false,
      requires_external_cli: true,
      underlying_cli_path: null,
      node_required: false,
      auth_status: { status: "unknown" },
      source: "builtin",
      login_hint: undefined,
    },
    {
      id: "buzz-agent",
      label: "Buzz Agent",
      avatar_url: "",
      availability: "available",
      command: "buzz-agent",
      binary_path: "/usr/local/bin/buzz-agent",
      default_args: [],
      mcp_command: "buzz-dev-mcp",
      install_hint: "Ships with the Buzz desktop app.",
      install_instructions_url: "https://github.com/block/buzz",
      can_auto_install: false,
      requires_external_cli: false,
      underlying_cli_path: null,
      node_required: false,
      auth_status: { status: "not_applicable" },
      source: "builtin",
      login_hint: undefined,
    },
  ];
  return mergeMockCustomHarnesses(
    defaultCatalog.map(withMockRuntimeConfigMetadata),
  );
}

async function handleDiscoverAcpAuthMethods(
  args: { runtimeId?: string },
  config: E2eConfig | undefined,
): Promise<RawAcpAuthMethodsResult> {
  const globalError = config?.mock?.acpAuthMethodsError;
  if (globalError) {
    throw new Error(globalError);
  }
  const runtimeId = args.runtimeId ?? "";
  const perRuntimeError = config?.mock?.acpAuthMethodsErrors?.[runtimeId];
  if (perRuntimeError) {
    throw new Error(perRuntimeError);
  }
  const configured = config?.mock?.acpAuthMethods?.[runtimeId];
  if (configured) {
    return configured;
  }
  return { methods: [] };
}

async function handleConnectAcpRuntime(
  _args: { request?: { runtimeId?: string; methodId?: string } },
  config: E2eConfig | undefined,
): Promise<RawConnectAcpRuntimeResult> {
  const error = config?.mock?.connectAcpRuntimeError;
  if (error) {
    throw new Error(error);
  }
  const delayMs = config?.mock?.connectAcpRuntimeDelayMs ?? 0;
  if (delayMs > 0) {
    await new Promise((resolve) => window.setTimeout(resolve, delayMs));
  }
  mockConnectCompleted = true;
  return config?.mock?.connectAcpRuntimeResult ?? { launched: true };
}

// Per-page install call counter. Reset each test run because this module is
// re-evaluated via addInitScript, so the counter starts at 0 for every test.
let installCallCount = 0;
/** Per-runtime call counters for `installAcpRuntimeByRuntime` sequences. */
const installCallCountByRuntime: Record<string, number> = {};
let addChannelMembersCallCount = 0;
let setGlobalAgentConfigCallCount = 0;
let mockGlobalAgentConfig: {
  env_vars: Record<string, string>;
  provider: string | null;
  model: string | null;
  preferred_runtime?: string | null;
} | null = null;

// Per-page get_nsec call counter for sequenced error testing.
let nsecCallCount = 0;
let backupVerificationCallCount = 0;
let backupSaveCallCount = 0;

const MOCK_NCRYPTSEC =
  "ncryptsec1qgg9947rlpvqu76pj5ecreduf9jxhselq2nae2kghhvd5g7dgjtcxfqtd67p9m0w57lspw8gsq6yphnm8623nsl8xn9j4jdzz84zm3frztj3z7s35vpzmqf6ksu8r89qk5z2zxfmu5gv8th8wclt0h4p";
const MOCK_BACKUP_PASSPHRASE = "mock horse battery staple lake orbit";
const MOCK_PASSPHRASE_WORDS = [
  "mock",
  "horse",
  "battery",
  "staple",
  "lake",
  "orbit",
  "cedar",
  "plume",
  "raven",
  "tundra",
];

// Per-page explicit catalog publication outcomes.
let personaSharePublicationCallCount = 0;

// Per-page confirm_team_snapshot_import call counter for sequenced error testing.
let teamSnapshotConfirmCallCount = 0;

// Live-output sequence for the install currently being replayed. The backend
// counter is per run (`InstallReporter::for_run` starts a fresh one), so this
// restarts too — a bridge that stayed monotonic across installs would hide a UI
// that carried a stale sequence number into the next run and rejected all of it.
let installOutputSeq = 0;

/**
 * Replay the live output the Rust reporter emits while an install runs: a clear
 * signal, then one line per entry. `seq` is install-wide and monotonic, matching
 * the backend contract the UI's ordering depends on.
 *
 * The clear and the first line emit synchronously with the install invocation,
 * exactly as the backend does — the command is invoked from the click handler,
 * so those events land before React has committed the pending install state.
 * Delaying them would let a listener that mounts on that state still catch them,
 * hiding the very race the UI has to survive.
 *
 * Later lines are spaced so each is observable rather than collapsing into one
 * frame with the next.
 */
const INSTALL_OUTPUT_REPLAY_GAP_MS = 1000;

async function replayInstallOutput(
  runtimeId: string,
  lines: string[],
): Promise<void> {
  installOutputSeq = 0;
  // The leading null is the clear signal the backend sends when an attempt
  // starts, so this replays a whole attempt rather than only its output.
  const events: (string | null)[] = [null, ...lines];
  for (const [index, line] of events.entries()) {
    // Index 0 and 1 are the clear and the first line: no gap before either.
    if (index > 1) {
      await new Promise((resolve) =>
        window.setTimeout(resolve, INSTALL_OUTPUT_REPLAY_GAP_MS),
      );
    }
    // `emit` reaches listeners registered through the real `listen` API, which
    // is what the UI hook uses; mockIPC's shouldMockEvents wires the two.
    await emit("acp-install-output", {
      runtime_id: runtimeId,
      seq: installOutputSeq++,
      line,
    });
  }
}

async function handleInstallAcpRuntime(
  args: {
    runtimeId?: string;
  },
  config: E2eConfig | undefined,
): Promise<RawInstallRuntimeResult> {
  const runtimeId = args.runtimeId ?? "";
  const outputLines = config?.mock?.installAcpRuntimeOutputLines;
  if (outputLines && outputLines.length > 0) {
    await replayInstallOutput(runtimeId, outputLines);
  }
  const perRuntime = config?.mock?.installAcpRuntimeByRuntime?.[runtimeId];

  if (perRuntime) {
    const delayMs = perRuntime.delayMs ?? 0;
    if (delayMs > 0) {
      await new Promise((resolve) => window.setTimeout(resolve, delayMs));
    }
    const seq = perRuntime.results;
    if (seq && seq.length > 0) {
      const idx = Math.min(
        installCallCountByRuntime[runtimeId] ?? 0,
        seq.length - 1,
      );
      installCallCountByRuntime[runtimeId] = idx + 1;
      const result = seq[idx];
      if (result.success) mockInstallCompleted = true;
      return result;
    }
    if (perRuntime.result) {
      if (perRuntime.result.success) mockInstallCompleted = true;
      return perRuntime.result;
    }
  }

  const delayMs = config?.mock?.installAcpRuntimeDelayMs ?? 0;
  if (delayMs > 0) {
    await new Promise((resolve) => window.setTimeout(resolve, delayMs));
  }
  const sequence = config?.mock?.installAcpRuntimeResults;
  if (sequence && sequence.length > 0) {
    const idx = Math.min(installCallCount, sequence.length - 1);
    installCallCount++;
    const result = sequence[idx];
    if (result.success) mockInstallCompleted = true;
    return result;
  }
  const configured = config?.mock?.installAcpRuntimeResult;
  if (configured) {
    if (configured.success) mockInstallCompleted = true;
    return configured;
  }
  mockInstallCompleted = true;
  return {
    success: true,
    steps: [
      {
        step: "adapter",
        command: `mock install ${args.runtimeId ?? "unknown"}`,
        success: true,
        stdout: "mock: installed successfully",
        stderr: "",
        exit_code: 0,
      },
    ],
    restarted_count: 0,
    failed_restart_count: 0,
    log_path: null,
  };
}

async function handleDiscoverManagedAgentPrereqs(
  args: {
    input?: {
      acpCommand?: string;
      mcpCommand?: string;
    };
  },
  config: E2eConfig | undefined,
): Promise<RawManagedAgentPrereqs> {
  const configuredPrereqs = config?.mock?.managedAgentPrereqs;

  return {
    acp: {
      command:
        configuredPrereqs?.acp?.command ?? args.input?.acpCommand ?? "buzz-acp",
      resolved_path:
        configuredPrereqs?.acp?.resolvedPath ??
        "/Users/wesb/dev/buzz/target/debug/buzz-acp",
      available: configuredPrereqs?.acp?.available ?? true,
    },
    mcp: {
      command: configuredPrereqs?.mcp?.command ?? args.input?.mcpCommand ?? "",
      resolved_path: configuredPrereqs?.mcp?.resolvedPath ?? "",
      available: configuredPrereqs?.mcp?.available ?? true,
    },
  };
}

async function handleListManagedAgents(
  config: E2eConfig | undefined,
): Promise<RawManagedAgent[]> {
  await delayAgentList(config);
  return mockManagedAgents.map(cloneManagedAgent);
}

function isAgentMemoryListing(
  value: RawAgentMemoryListing | Record<string, RawAgentMemoryListing>,
): value is RawAgentMemoryListing {
  return (
    "memories" in value &&
    Array.isArray(value.memories) &&
    "truncated" in value &&
    "fetchedAt" in value
  );
}

async function handleGetAgentMemory(
  args: { agentPubkey?: string },
  config: E2eConfig | undefined,
): Promise<RawAgentMemoryListing> {
  const pubkey = args.agentPubkey?.toLowerCase();
  if (!pubkey) {
    throw new Error("mock get_agent_memory: missing agent pubkey");
  }

  const isManagedAgent = mockManagedAgents.some(
    (agent) => agent.pubkey.toLowerCase() === pubkey,
  );
  if (!isManagedAgent) {
    throw new Error(`mock get_agent_memory: unmanaged agent ${pubkey}`);
  }

  const configuredMemory = config?.mock?.agentMemory;
  if (!configuredMemory) {
    return {
      core: null,
      memories: [],
      truncated: false,
      fetchedAt: Math.floor(Date.now() / 1000),
    };
  }

  if (isAgentMemoryListing(configuredMemory)) {
    return cloneAgentMemoryListing(configuredMemory);
  }

  const listing =
    configuredMemory[pubkey] ?? configuredMemory[args.agentPubkey ?? ""];
  return listing
    ? cloneAgentMemoryListing(listing)
    : {
        core: null,
        memories: [],
        truncated: false,
        fetchedAt: Math.floor(Date.now() / 1000),
      };
}

async function handleListPersonas(): Promise<RawPersona[]> {
  return mockPersonas.map((persona) => ({ ...persona }));
}

type PersonaBehaviorInput = {
  respondTo?: "owner-only" | "allowlist" | "anyone";
  respondToAllowlist?: string[];
  parallelism?: number;
};

/** Mirrors `apply_persona_behavior`: replace all four as a unit. */
function applyMockPersonaBehavior(
  persona: RawPersona,
  behavior: PersonaBehaviorInput | undefined,
) {
  if (behavior === undefined) {
    return;
  }
  persona.respond_to = behavior.respondTo ?? null;
  persona.respond_to_allowlist =
    behavior.respondTo === "allowlist"
      ? [...(behavior.respondToAllowlist ?? [])]
      : [];
  persona.parallelism = behavior.parallelism ?? null;
}

async function handleCreatePersona(args: {
  input: {
    displayName: string;
    avatarUrl?: string;
    systemPrompt: string;
    runtime?: string;
    model?: string;
    provider?: string;
    envVars?: Record<string, string>;
    behavior?: PersonaBehaviorInput;
    catalogSource?: { ownerPubkey: string; personaId: string };
  };
}): Promise<RawPersona> {
  const now = new Date().toISOString();
  const persona: RawPersona = {
    id: crypto.randomUUID(),
    display_name: args.input.displayName.trim(),
    avatar_url: args.input.avatarUrl?.trim() || null,
    system_prompt: args.input.systemPrompt.trim(),
    runtime: args.input.runtime?.trim() || null,
    model: args.input.model?.trim() || null,
    provider: args.input.provider?.trim() || null,
    is_builtin: false,
    is_active: true,
    shared: false,
    source_team: null,
    // Mirrors `CatalogSource::normalized`: the coordinate a catalog copy keeps
    // so the catalog can tell an already-added foreign entry from a new one.
    catalog_source: args.input.catalogSource
      ? {
          owner_pubkey: args.input.catalogSource.ownerPubkey
            .trim()
            .toLowerCase(),
          persona_id: args.input.catalogSource.personaId.trim(),
        }
      : null,
    env_vars: { ...(args.input.envVars ?? {}) },
    created_at: now,
    updated_at: now,
  };
  applyMockPersonaBehavior(persona, args.input.behavior);
  mockPersonas.push(persona);
  upsertMockPersonaEvent(persona);
  return { ...persona };
}

type MockUpdatePersonaInput = {
  id: string;
  displayName: string;
  avatarUrl?: string;
  systemPrompt: string;
  runtime?: string;
  model?: string;
  provider?: string;
  envVars?: Record<string, string>;
  behavior?: PersonaBehaviorInput;
};

async function handleUpdatePersona(args: {
  input: MockUpdatePersonaInput;
}): Promise<RawPersona> {
  return { ...(await applyMockPersonaUpdate(args.input)) };
}

/**
 * Save an edit to the mock persona store, exactly like `update_persona_with`,
 * and return the live record so a caller can publish it.
 *
 * Deliberately does NOT publish a catalog event: the real `update_persona`
 * only enqueues a pending head for the out-of-band flush loop, so nothing has
 * reached the relay by the time the command returns. Publishing here would
 * make a UI that never calls `update_persona_and_publish` look like it kept
 * the "Save and publish" promise.
 */
async function applyMockPersonaUpdate(
  input: MockUpdatePersonaInput,
): Promise<RawPersona> {
  const persona = mockPersonas.find((candidate) => candidate.id === input.id);
  if (!persona) {
    throw new Error(`agent ${input.id} not found`);
  }
  persona.display_name = input.displayName.trim();
  persona.avatar_url = input.avatarUrl?.trim() || null;
  persona.system_prompt = input.systemPrompt.trim();
  persona.runtime = input.runtime?.trim() || null;
  persona.model = input.model?.trim() || null;
  persona.provider = input.provider?.trim() || null;
  if (input.envVars !== undefined) {
    // Absent = preserve; present = replace entirely (matches Rust handler).
    persona.env_vars = { ...input.envVars };
  }
  applyMockPersonaBehavior(persona, input.behavior);
  persona.updated_at = new Date().toISOString();

  await emit("agents-data-changed");
  return persona;
}

async function handleDeletePersona(args: { id: string }): Promise<void> {
  const persona = mockPersonas.find((candidate) => candidate.id === args.id);
  if (!persona) {
    throw new Error(`agent ${args.id} not found`);
  }
  if (persona.is_builtin) {
    throw new Error("Built-in agents cannot be deleted.");
  }
  if (mockTeams.some((team) => team.persona_ids.includes(args.id))) {
    throw new Error(
      `${persona.display_name} is still referenced by a team. Remove it from those teams first.`,
    );
  }

  mockPersonas = mockPersonas.filter((candidate) => candidate.id !== args.id);
  const now = new Date().toISOString();
  for (const agent of mockManagedAgents) {
    if (agent.persona_id === args.id) {
      agent.persona_id = null;
      agent.updated_at = now;
    }
  }
}

async function handleSetPersonaActive(args: {
  id: string;
  active: boolean;
}): Promise<RawPersona> {
  const persona = mockPersonas.find((candidate) => candidate.id === args.id);
  if (!persona) {
    throw new Error(`agent ${args.id} not found`);
  }
  if (!persona.is_builtin) {
    throw new Error(
      "Only built-in agents can be added to or removed from My Agents.",
    );
  }
  if (
    !args.active &&
    mockManagedAgents.some((agent) => agent.persona_id === args.id)
  ) {
    throw new Error(
      `${persona.display_name} is still assigned to a managed agent. Remove or reassign those agents first.`,
    );
  }
  if (
    !args.active &&
    mockTeams.some((team) => team.persona_ids.includes(args.id))
  ) {
    throw new Error(
      `${persona.display_name} is still referenced by a team. Remove it from those teams first.`,
    );
  }

  persona.is_active = args.active;
  persona.updated_at = new Date().toISOString();
  return { ...persona };
}

function personaHasExactSharedTag(event: RelayEvent): boolean {
  const tags = event.tags.filter((tag) => tag[0] === "shared");
  return tags.length === 1 && tags[0]?.length === 2 && tags[0]?.[1] === "true";
}

function upsertMockPersonaRelayEvent(event: RelayEvent): void {
  const sourceId = event.tags.find((tag) => tag[0] === "d")?.[1];
  if (!sourceId) return;
  const existingIndex = mockPersonaEvents.findIndex(
    (candidate) =>
      candidate.pubkey.toLowerCase() === event.pubkey.toLowerCase() &&
      candidate.tags.some((tag) => tag[0] === "d" && tag[1] === sourceId),
  );
  if (existingIndex >= 0) {
    mockPersonaEvents.splice(existingIndex, 1);
  }
  mockPersonaEvents.push(event);
}

function upsertMockPersonaEvent(
  persona: RawPersona,
  identity?: TestIdentity,
): void {
  const template = {
    created_at: Math.floor(Date.now() / 1_000),
    kind: KIND_PERSONA,
    tags: [["d", persona.id], ...(persona.shared ? [["shared", "true"]] : [])],
    content: JSON.stringify({
      display_name: persona.display_name,
      system_prompt: persona.system_prompt,
      avatar_url: persona.avatar_url,
      runtime: persona.runtime ?? null,
      model: persona.model ?? null,
      provider: persona.provider ?? null,
      name_pool: persona.name_pool ?? [],
      respond_to: persona.respond_to ?? null,
      respond_to_allowlist: persona.respond_to_allowlist ?? [],
      parallelism: persona.parallelism ?? null,
    }),
  };
  const event: RelayEvent = identity
    ? finalizeEvent(template, hexToBytes(identity.privateKey))
    : {
        ...template,
        id: mockEventId(),
        pubkey: MOCK_IDENTITY_PUBKEY,
        sig: "0".repeat(128),
      };
  upsertMockPersonaRelayEvent(event);
  emitMockGlobalEvent(event);
}

type MockPersonaPublicationResult = {
  persona: RawPersona;
  publicationStatus: "published" | "queued";
  relayMessage?: string;
};

/**
 * Publish a persona's catalog head and report the relay outcome, like
 * `publish_prepared_persona`. A `queued` outcome must NOT make the event
 * visible to catalog readers — that is the whole distinction the UI reports.
 */
function publishMockPersonaHead(
  persona: RawPersona,
  config: E2eConfig | undefined,
): MockPersonaPublicationResult {
  const publicationStatus =
    config?.mock?.personaSharePublicationStatuses?.[
      personaSharePublicationCallCount++
    ] ?? "published";
  if (publicationStatus === "published") {
    upsertMockPersonaEvent(persona, getActiveIdentity(config));
  }
  return {
    persona: { ...persona },
    publicationStatus,
    ...(publicationStatus === "queued"
      ? { relayMessage: "relay unreachable: could not connect to relay" }
      : {}),
  };
}

async function handleSetPersonaShared(
  args: {
    id: string;
    shared: boolean;
  },
  config?: E2eConfig,
): Promise<MockPersonaPublicationResult> {
  const persona = mockPersonas.find((candidate) => candidate.id === args.id);
  if (!persona) {
    throw new Error(`agent ${args.id} not found`);
  }
  if (persona.is_builtin) {
    throw new Error("Built-in agents cannot be shared to the catalog.");
  }
  persona.shared = args.shared;
  persona.updated_at = new Date().toISOString();
  return publishMockPersonaHead(persona, config);
}

/** Mirrors `update_persona_and_publish`: save the edit, then await the relay. */
async function handleUpdatePersonaAndPublish(
  args: { input: MockUpdatePersonaInput },
  config?: E2eConfig,
): Promise<MockPersonaPublicationResult> {
  return publishMockPersonaHead(
    await applyMockPersonaUpdate(args.input),
    config,
  );
}

function ensureMockPersonaIsActive(personaId: string) {
  const persona = mockPersonas.find((candidate) => candidate.id === personaId);
  if (!persona) {
    throw new Error(`agent ${personaId} not found`);
  }
  if (!persona.is_active) {
    throw new Error(`${persona.display_name} is not in My Agents.`);
  }
}

function ensureMockPersonaIdsAreActive(personaIds: string[]) {
  for (const personaId of personaIds) {
    ensureMockPersonaIsActive(personaId);
  }
}

async function handleListTeams(): Promise<RawTeam[]> {
  return mockTeams.map((team) => ({
    ...team,
    persona_ids: [...team.persona_ids],
  }));
}

async function handleCreateTeam(args: {
  input: {
    name: string;
    description?: string;
    personaIds: string[];
  };
}): Promise<RawTeam> {
  ensureMockPersonaIdsAreActive(args.input.personaIds);
  const now = new Date().toISOString();
  const team: RawTeam = {
    id: crypto.randomUUID(),
    name: args.input.name.trim(),
    description: args.input.description?.trim() || null,
    persona_ids: [...args.input.personaIds],
    is_builtin: false,
    source_dir: null,
    is_symlink: false,
    symlink_target: null,
    version: null,
    created_at: now,
    updated_at: now,
  };
  mockTeams.push(team);
  return { ...team, persona_ids: [...team.persona_ids] };
}

async function handleUpdateTeam(args: {
  input: {
    id: string;
    name: string;
    description?: string;
    personaIds: string[];
  };
}): Promise<RawTeam> {
  const team = mockTeams.find((candidate) => candidate.id === args.input.id);
  if (!team) {
    throw new Error(`Team ${args.input.id} not found.`);
  }

  ensureMockPersonaIdsAreActive(args.input.personaIds);
  team.name = args.input.name.trim();
  team.description = args.input.description?.trim() || null;
  team.persona_ids = [...args.input.personaIds];
  team.updated_at = new Date().toISOString();

  return { ...team, persona_ids: [...team.persona_ids] };
}

async function handleDeleteTeam(args: { id: string }): Promise<void> {
  const team = mockTeams.find((candidate) => candidate.id === args.id);
  if (team?.is_builtin) {
    throw new Error("Built-in teams cannot be deleted.");
  }
  mockTeams = mockTeams.filter((candidate) => candidate.id !== args.id);
}

async function handleExportTeamToJson(args: { id: string }): Promise<boolean> {
  const team = mockTeams.find((candidate) => candidate.id === args.id);
  if (!team) {
    throw new Error(`Team ${args.id} not found.`);
  }

  const missingPersonaIds = team.persona_ids.filter(
    (personaId) =>
      !mockPersonas.some((candidate) => candidate.id === personaId),
  );
  if (missingPersonaIds.length > 0) {
    throw new Error(
      `Team ${team.name} references missing personas: ${missingPersonaIds.join(", ")}. Repair the team before exporting.`,
    );
  }

  return true;
}

async function handlePickTeamDirectory(): Promise<string | null> {
  return "/Users/dev/agents/new-team";
}

async function handleInstallTeamFromDirectory(args: {
  path: string;
  symlink: boolean;
}): Promise<RawTeam> {
  const now = new Date().toISOString();
  const team: RawTeam = {
    id: crypto.randomUUID(),
    name: "Installed Team",
    description: null,
    persona_ids: [],
    is_builtin: false,
    source_dir: args.path,
    is_symlink: args.symlink,
    symlink_target: args.symlink ? args.path : null,
    version: null,
    created_at: now,
    updated_at: now,
  };
  mockTeams.push(team);
  return { ...team, persona_ids: [...team.persona_ids] };
}

async function handleSyncTeamDirectory(args: { teamId: string }): Promise<{
  personas_added: string[];
  personas_removed: string[];
  personas_updated: string[];
  metadata_changed: boolean;
}> {
  const team = mockTeams.find((candidate) => candidate.id === args.teamId);
  if (!team) {
    throw new Error(`Team ${args.teamId} not found.`);
  }
  return {
    personas_added: [],
    personas_removed: [],
    personas_updated: [],
    metadata_changed: false,
  };
}

async function handleParseTeamFile(): Promise<{
  name: string;
  description: string | null;
  personas: Array<{
    display_name: string;
    system_prompt: string;
    avatar_url: string | null;
  }>;
}> {
  return {
    name: "Imported Team",
    description: null,
    personas: [],
  };
}

async function handleCreateManagedAgent(
  args: {
    input: {
      name: string;
      personaId?: string;
      relayUrl?: string;
      acpCommand?: string;
      agentCommand?: string;
      agentArgs?: string[];
      mcpCommand?: string;
      turnTimeoutSeconds?: number;
      idleTimeoutSeconds?: number;
      maxTurnDurationSeconds?: number;
      parallelism?: number;
      systemPrompt?: string;
      avatarUrl?: string;
      model?: string;
      provider?: string;
      envVars?: Record<string, string>;
      spawnAfterCreate?: boolean;
      startOnAppLaunch?: boolean;
      backend?:
        | { type: "local" }
        | { type: "provider"; id: string; config: Record<string, unknown> };
      respondTo?: "owner-only" | "allowlist" | "anyone";
      respondToAllowlist?: string[];
    };
  },
  config: E2eConfig | undefined,
): Promise<RawCreateManagedAgentResponse> {
  const delayMs = config?.mock?.createManagedAgentDelayMs ?? 0;
  if (delayMs > 0) {
    await new Promise((resolve) => window.setTimeout(resolve, delayMs));
  }

  if (args.input.personaId) {
    ensureMockPersonaIsActive(args.input.personaId);
  }
  // Mint-parity with resolve_mint_behavioral_defaults: an explicit input
  // wins; otherwise the linked definition's stored quad applies (mode+list
  // travel together); otherwise the schema default.
  const linkedPersona = args.input.personaId
    ? (mockPersonas.find((persona) => persona.id === args.input.personaId) ??
      null)
    : null;
  const mintRespondTo =
    args.input.respondTo ??
    (linkedPersona?.respond_to as RawManagedAgent["respond_to"] | null) ??
    "owner-only";
  const mintRespondToAllowlist =
    args.input.respondTo !== undefined
      ? (args.input.respondToAllowlist ?? [])
      : (linkedPersona?.respond_to_allowlist ?? []);
  const mintParallelism = resolveAgentParallelism(
    args.input.parallelism,
    linkedPersona?.parallelism,
  );
  const personaAvatarUrl =
    args.input.personaId === undefined
      ? null
      : (mockPersonas.find((persona) => persona.id === args.input.personaId)
          ?.avatar_url ?? null);
  const avatarUrl = args.input.avatarUrl?.trim() || personaAvatarUrl;
  const name = args.input.name.trim();
  const now = new Date().toISOString();
  const pubkey = crypto
    .randomUUID()
    .replace(/-/g, "")
    .padEnd(64, "0")
    .slice(0, 64);
  const agentCommand = args.input.agentCommand ?? "buzz-agent";
  const agentArgs =
    args.input.agentArgs && args.input.agentArgs.length > 0
      ? [...args.input.agentArgs]
      : agentCommand === "goose"
        ? ["acp"]
        : [];
  const managedAgent: MockManagedAgent = {
    pubkey,
    name,
    persona_id: args.input.personaId ?? null,
    // Create never pins a harness id — the record inherits from the persona.
    runtime: null,
    relay_url: args.input.relayUrl ?? DEFAULT_RELAY_WS_URL,
    acp_command: args.input.acpCommand ?? "buzz-acp",
    agent_command: agentCommand,
    agent_args: agentArgs,
    mcp_command: args.input.mcpCommand ?? "",
    turn_timeout_seconds: args.input.turnTimeoutSeconds ?? 320,
    idle_timeout_seconds: args.input.idleTimeoutSeconds ?? null,
    max_turn_duration_seconds: args.input.maxTurnDurationSeconds ?? null,
    parallelism: mintParallelism,
    system_prompt: args.input.systemPrompt?.trim() || null,
    avatar_url: avatarUrl,
    model: args.input.model?.trim() || linkedPersona?.model || null,
    provider: args.input.provider?.trim() || linkedPersona?.provider || null,
    env_vars: { ...(args.input.envVars ?? {}) },
    status: args.input.spawnAfterCreate ? "running" : "stopped",
    pid: args.input.spawnAfterCreate ? 42000 + mockManagedAgents.length : null,
    created_at: now,
    updated_at: now,
    last_started_at: args.input.spawnAfterCreate ? now : null,
    last_stopped_at: null,
    last_exit_code: null,
    last_error: null,
    last_error_code: null,
    log_path: `/tmp/mock-agent-${pubkey}.log`,
    start_on_app_launch: args.input.startOnAppLaunch ?? true,
    auto_restart_on_config_change: true,
    backend: args.input.backend ?? { type: "local" as const },
    backend_agent_id: null,
    respond_to: mintRespondTo,
    respond_to_allowlist: [...mintRespondToAllowlist],
    private_key_nsec: `nsec1mock${pubkey.slice(0, 20)}`,
    log_lines: [
      `buzz-acp starting: relay=${args.input.relayUrl ?? DEFAULT_RELAY_WS_URL} agent_pubkey=${pubkey} parallelism=${mintParallelism}`,
      args.input.systemPrompt?.trim()
        ? `system prompt override configured (${args.input.systemPrompt.trim().length} chars)`
        : "system prompt override not set",
      args.input.spawnAfterCreate
        ? "connected to relay at ws://localhost:3000"
        : "profile created; harness not started",
    ],
  };

  mockManagedAgents.unshift(managedAgent);
  if (args.input.spawnAfterCreate && managedAgent.backend.type === "local") {
    // The real create command spawns a pair runtime on the agent's effective
    // relay (`start_local_agent_with_preflight`), so mirror that row here.
    upsertMockManagedAgentRuntime(pubkey, managedAgent.relay_url, "ready");
  }
  applyMockDisplayName(pubkey, name);
  mockAgentPubkeys.add(pubkey);
  mockProfiles.set(pubkey, {
    pubkey,
    display_name: name,
    avatar_url: avatarUrl,
    about: args.input.systemPrompt?.trim() || null,
    nip05_handle: null,
    owner_pubkey: MOCK_IDENTITY_PUBKEY,
    is_agent: true,
    has_profile_event: true,
  });
  syncMockRelayAgentsFromManagedAgents();

  return {
    agent: cloneManagedAgent(managedAgent),
    private_key_nsec: managedAgent.private_key_nsec,
    profile_sync_error: null,
    spawn_error: null,
  };
}

function getMockManagedAgent(pubkey: string): MockManagedAgent {
  const agent = mockManagedAgents.find(
    (candidate) => candidate.pubkey === pubkey,
  );
  if (!agent) {
    throw new Error(`Managed agent ${pubkey} not found.`);
  }

  return agent;
}

function upsertMockManagedAgentRuntime(
  pubkey: string,
  relayUrl: string,
  lifecycle: MockManagedAgentRuntimeRow["lifecycle"],
): MockManagedAgentRuntimeRow {
  let row = mockManagedAgentRuntimes.find(
    (candidate) =>
      candidate.pubkey === pubkey && candidate.relayUrl === relayUrl,
  );
  if (!row) {
    row = {
      pubkey,
      relayUrl,
      localSetup: true,
      lifecycle,
      pid: null,
      error: null,
      logPath: null,
    };
    mockManagedAgentRuntimes.push(row);
  }
  row.lifecycle = lifecycle;
  row.pid =
    lifecycle === "stopped"
      ? null
      : (row.pid ?? 43000 + mockManagedAgentRuntimes.length);
  row.error = null;
  return row;
}

// Pair-scoped lifecycle, mirroring `runtime_commands.rs`: the command touches
// exactly one agent+relay runtime and rejects non-local agents.
function handleManagedAgentRuntimeAction(
  action: "start" | "stop" | "restart",
  args: { pubkey: string; relayUrl: string },
): MockManagedAgentRuntimeRow {
  const agent = getMockManagedAgent(args.pubkey);
  if (agent.backend.type !== "local") {
    throw new Error("managed runtime pairs require a local agent");
  }
  return {
    ...upsertMockManagedAgentRuntime(
      args.pubkey,
      args.relayUrl,
      action === "stop" ? "stopped" : "ready",
    ),
  };
}

function isRelayMeshManagedAgent(agent: MockManagedAgent): boolean {
  return agent.backend.type === "local" && agent.provider === "relay-mesh";
}

async function handleStartManagedAgent(
  args: {
    pubkey: string;
  },
  config?: E2eConfig,
): Promise<RawManagedAgent> {
  const startError = config?.mock?.startManagedAgentErrors?.shift();
  if (startError) {
    throw new Error(startError);
  }

  const agent = getMockManagedAgent(args.pubkey);
  if (isRelayMeshManagedAgent(agent)) {
    // Model the backend start preflight (ensure_relay_mesh_for_record): a
    // saved relay-mesh agent re-resolves a live serve target for its model
    // and only fails when no peer currently serves it.
    const modelId = agent.model ?? "auto";
    const hasLiveTarget =
      mockMeshState.admitted &&
      (modelId === "auto" ||
        mockMeshState.models.some((model) => model.id === modelId));
    if (!hasLiveTarget) {
      throw new Error(
        "Buzz shared compute cannot start because no live member is serving this model.",
      );
    }
  }

  const now = new Date().toISOString();
  if (agent.backend.type === "provider") {
    agent.status = "deployed";
    agent.pid = null;
    agent.backend_agent_id =
      agent.backend_agent_id ?? `mock-provider-${agent.pubkey.slice(0, 12)}`;
  } else {
    agent.status = "running";
    agent.pid = agent.pid ?? 42000 + mockManagedAgents.indexOf(agent);
    // The real command spawns a pair runtime keyed by the agent's effective
    // relay (`start_managed_agent_process`), so mirror that row here.
    upsertMockManagedAgentRuntime(agent.pubkey, agent.relay_url, "ready");
  }
  agent.updated_at = now;
  agent.last_started_at = now;
  agent.last_error = null;
  setMockPresenceStatus(agent.pubkey, "online");
  agent.log_lines.push(
    agent.backend.type === "provider"
      ? `deployed mock provider harness at ${now}`
      : `started mock harness at ${now}`,
  );
  syncMockRelayAgentsFromManagedAgents();
  return cloneManagedAgent(agent);
}

async function handleStopManagedAgent(args: {
  pubkey: string;
}): Promise<RawManagedAgent> {
  const agent = getMockManagedAgent(args.pubkey);
  const now = new Date().toISOString();
  agent.status = "stopped";
  agent.pid = null;
  agent.updated_at = now;
  agent.last_stopped_at = now;
  // Legacy agent-wide stop deliberately drains EVERY pair runtime for the
  // agent (`runtime/stop.rs`) — mirror that so specs can prove the sidebar
  // no longer routes through it.
  for (const row of mockManagedAgentRuntimes) {
    if (row.pubkey === args.pubkey) {
      row.lifecycle = "stopped";
      row.pid = null;
    }
  }
  setMockPresenceStatus(agent.pubkey, "offline");
  agent.log_lines.push(`stopped mock harness at ${now}`);
  syncMockRelayAgentsFromManagedAgents();
  return cloneManagedAgent(agent);
}

async function handleDeleteManagedAgent(args: {
  pubkey: string;
  forceRemoteDelete?: boolean | null;
}): Promise<void> {
  // Model the backend invariant: reject deletion of deployed remote agents
  // unless force_remote_delete is true.
  const agent = mockManagedAgents.find((a) => a.pubkey === args.pubkey);
  if (
    agent &&
    agent.backend.type === "provider" &&
    agent.backend_agent_id != null &&
    !args.forceRemoteDelete
  ) {
    throw new Error(
      "cannot delete a deployed remote agent without force_remote_delete: true",
    );
  }
  mockManagedAgents = mockManagedAgents.filter(
    (candidate) => candidate.pubkey !== args.pubkey,
  );
  syncMockRelayAgentsFromManagedAgents();
}

async function handleSetManagedAgentStartOnAppLaunch(args: {
  pubkey: string;
  startOnAppLaunch: boolean;
}): Promise<RawManagedAgent> {
  const agent = getMockManagedAgent(args.pubkey);
  agent.start_on_app_launch = args.startOnAppLaunch;
  agent.updated_at = new Date().toISOString();
  return cloneManagedAgent(agent);
}

async function handleSetManagedAgentAutoRestart(args: {
  pubkey: string;
  autoRestartOnConfigChange: boolean;
}): Promise<RawManagedAgent> {
  const agent = getMockManagedAgent(args.pubkey);
  agent.auto_restart_on_config_change = args.autoRestartOnConfigChange;
  agent.updated_at = new Date().toISOString();
  return cloneManagedAgent(agent);
}

async function handleGetManagedAgentLog(args: {
  pubkey: string;
  lineCount?: number;
}): Promise<RawManagedAgentLog> {
  const agent = getMockManagedAgent(args.pubkey);
  const count = args.lineCount ?? 120;
  return {
    content: agent.log_lines.slice(-count).join("\n"),
    log_path: agent.log_path,
  };
}

async function handleUpdateManagedAgent(args: {
  input: {
    pubkey: string;
    name?: string;
    model?: string | null;
    systemPrompt?: string | null;
    envVars?: Record<string, string>;
    respondTo?: "owner-only" | "allowlist" | "anyone";
    respondToAllowlist?: string[];
  };
}): Promise<{ agent: RawManagedAgent; profile_sync_error: string | null }> {
  const agent = getMockManagedAgent(args.input.pubkey);
  if (args.input.name !== undefined) {
    agent.name = args.input.name;
  }
  if (args.input.model !== undefined) {
    agent.model = args.input.model;
  }
  if (args.input.systemPrompt !== undefined) {
    agent.system_prompt = args.input.systemPrompt;
  }
  if (args.input.envVars !== undefined) {
    agent.env_vars = { ...args.input.envVars };
  }
  if (args.input.respondTo !== undefined) {
    agent.respond_to = args.input.respondTo;
  }
  if (args.input.respondToAllowlist !== undefined) {
    agent.respond_to_allowlist = args.input.respondToAllowlist;
  }
  agent.updated_at = new Date().toISOString();
  return { agent: cloneManagedAgent(agent), profile_sync_error: null };
}

/**
 * Mock-mode `search_messages` predicate, mirroring the relay's filter contract.
 *
 * `since`/`until` are NIP-01 bounds and both inclusive — the relay keeps events
 * where `since <= created_at <= until` (`crates/buzz-core/src/filter.rs`). The
 * `before:` operator's exclusivity is encoded upstream in
 * `parseSearchOperators`, which subtracts a second; the mock must not subtract
 * it a second time.
 *
 * Exported so the boundary behavior is unit-testable without a browser.
 */
export function mockSearchHitMatches(
  hit: Pick<
    RawSearchHit,
    "channel_id" | "pubkey" | "created_at" | "content" | "channel_name"
  >,
  filters: {
    /** Lowercased FTS query; empty matches everything. */
    query: string;
    channelId?: string;
    authorSet: Set<string> | null;
    since?: number;
    until?: number;
  },
): boolean {
  if (filters.channelId && hit.channel_id !== filters.channelId) {
    return false;
  }
  if (filters.authorSet && !filters.authorSet.has(hit.pubkey.toLowerCase())) {
    return false;
  }
  if (filters.since != null && hit.created_at < filters.since) {
    return false;
  }
  if (filters.until != null && hit.created_at > filters.until) {
    return false;
  }
  if (!filters.query) {
    return true;
  }
  return (
    hit.content.toLowerCase().includes(filters.query) ||
    (hit.channel_name?.toLowerCase().includes(filters.query) ?? false)
  );
}

async function handleSearchMessages(
  args: {
    q: string;
    limit?: number;
    channelId?: string;
    authors?: string[];
    since?: number;
    until?: number;
  },
  config: E2eConfig | undefined,
): Promise<RawSearchResponse> {
  const identity = getIdentity(config);
  if (!identity) {
    const query = args.q.trim().toLowerCase();
    const limit = args.limit ?? 20;
    const now = Math.floor(Date.now() / 1000);

    const mockHits: RawSearchHit[] = [
      {
        event_id: "mock-general-welcome",
        content: "Welcome to #general",
        kind: 9,
        pubkey: DEFAULT_MOCK_IDENTITY.pubkey,
        channel_id: "9a1657ac-f7aa-5db0-b632-d8bbeb6dfb50",
        channel_name: "general",
        created_at: now - 60,
        score: 8.5,
      },
      {
        event_id: "mock-engineering-shipped",
        content: "Engineering shipped the desktop build.",
        kind: 9,
        pubkey:
          "bb22a5299220cad76ffd46190ccbeede8ab5dc260faa28b6e5a2cb31b9aff260",
        channel_id: "1c7e1c02-87bb-5e88-b2da-5a7a9432d0c9",
        channel_name: "engineering",
        created_at: now - 42 * 60,
        score: 7.2,
      },
      {
        event_id: "mock-design-critique",
        content: "Design critique notes for the browse flow.",
        kind: 9,
        pubkey:
          "953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f",
        channel_id: "b5e2f8a1-3c44-5912-9e67-4a8d1f2b3c4e",
        channel_name: "design",
        created_at: now - 75 * 60,
        score: 6.6,
      },
      {
        event_id: "mock-forum-release-thread",
        content: "Release checklist: async feedback thread.",
        kind: 45001,
        pubkey:
          "953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f",
        channel_id: "a27e1ee9-76a6-5bdf-a5d5-1d85610dad11",
        channel_name: "watercooler",
        created_at: now - 90 * 60,
        score: 5.8,
      },
      {
        event_id: "mock-forum-release-reply",
        content: "Looks good to me. We should ship it.",
        kind: 45003,
        pubkey: ALICE_PUBKEY,
        channel_id: "a27e1ee9-76a6-5bdf-a5d5-1d85610dad11",
        channel_name: "watercooler",
        created_at: now - 80 * 60,
        score: 5.2,
      },
    ];
    for (const [channelId, events] of mockMessages) {
      const channel = mockChannels.find(
        (candidate) => candidate.id === channelId,
      );
      for (const event of events) {
        mockHits.push({
          event_id: event.id,
          content: event.content,
          kind: event.kind,
          pubkey: event.pubkey,
          channel_id: channelId,
          channel_name: channel?.name ?? null,
          created_at: event.created_at,
          score: 1,
        });
      }
    }

    const authorSet = args.authors?.length
      ? new Set(args.authors.map((author) => author.toLowerCase()))
      : null;

    const hits = mockHits
      .filter((hit) =>
        mockSearchHitMatches(hit, {
          query,
          channelId: args.channelId,
          authorSet,
          since: args.since,
          until: args.until,
        }),
      )
      .slice(0, limit);

    return {
      hits,
      found: hits.length,
    };
  }

  // NIP-50 search via POST /query — forward operator pushdown fields.
  const limit = args.limit ?? 20;
  const filter: Record<string, unknown> = {
    kinds: [9, 40002, 45001, 45003],
    search: args.q,
    limit,
  };
  if (args.channelId) {
    filter["#h"] = [args.channelId];
  }
  if (args.authors?.length) {
    filter.authors = args.authors;
  }
  if (args.since != null) {
    filter.since = args.since;
  }
  if (args.until != null) {
    filter.until = args.until;
  }
  const events = await relayQuery(config, [filter]);
  const hits = events.map((ev) => ({
    event_id: ev.id ?? "",
    pubkey: ev.pubkey ?? "",
    content: ev.content ?? "",
    created_at: ev.created_at ?? 0,
    kind: ev.kind ?? 9,
    tags: ev.tags ?? [],
    sig: ev.sig ?? "",
    channel_id:
      ((ev.tags ?? []) as string[][]).find((t) => t[0] === "h")?.[1] ?? null,
    channel_name: null,
    score: 1.0,
  }));
  return { hits, found: hits.length };
}

/**
 * Descriptors returned by the mocked upload commands. A spec can override via
 * `MockBridgeOptions.uploadDescriptors`; otherwise we return a single generic
 * PDF so the file-attachment flow (chip → send → FileCard) can be exercised
 * out of the box.
 */
async function resolveMockUploadDescriptors(
  config: E2eConfig | undefined,
): Promise<RawBlobDescriptor[]> {
  const delayMs = config?.mock?.uploadDelayMs ?? 0;
  if (delayMs > 0) {
    await new Promise((resolve) => window.setTimeout(resolve, delayMs));
  }

  const configured = config?.mock?.uploadDescriptors;
  // `undefined` means "not configured" → default PDF. An explicit `[]` is a
  // valid override (e.g. modelling a picker cancel / no-files-selected), so it
  // must pass through rather than fall back to the default.
  if (configured !== undefined) return configured;
  return [
    {
      url: `https://mock.relay/media/${"a".repeat(64)}.pdf`,
      sha256: "a".repeat(64),
      size: 12345,
      type: "application/pdf",
      uploaded: Math.floor(Date.now() / 1000),
      filename: "quarterly-report.pdf",
    },
  ];
}

async function resolveMockUploadDescriptorForBytes(
  args: { data: number[] | Uint8Array; filename?: string | null },
  config: E2eConfig | undefined,
): Promise<RawBlobDescriptor> {
  const uploadDelayMs = config?.mock?.linkPreviewUploadDelayMs ?? 0;
  if (args.filename?.startsWith("link-preview-")) {
    if (uploadDelayMs > 0) {
      await new Promise((resolve) => setTimeout(resolve, uploadDelayMs));
    }
    const errorFilenames = config?.mock?.linkPreviewUploadErrorFilenames;
    if (errorFilenames?.some((needle) => args.filename?.includes(needle))) {
      throw new Error(`mock upload failed for ${args.filename}`);
    }
  }
  const configured = config?.mock?.uploadDescriptors;
  if (configured !== undefined) {
    const descriptors = await resolveMockUploadDescriptors(config);
    const descriptor = descriptors[0];
    if (!descriptor) throw new Error("mock upload returned no descriptor");
    return descriptor;
  }

  const bytes = Uint8Array.from(args.data);
  const digest = await crypto.subtle.digest("SHA-256", bytes);
  const sha256 = Array.from(new Uint8Array(digest), (value) =>
    value.toString(16).padStart(2, "0"),
  ).join("");
  const filename = args.filename ?? "upload.bin";
  const normalizedFilename = filename.toLowerCase();
  const isAgentJson = normalizedFilename.endsWith(".agent.json");
  const extension = isAgentJson
    ? "json"
    : normalizedFilename.endsWith(".png")
      ? "png"
      : normalizedFilename.endsWith(".jpg") ||
          normalizedFilename.endsWith(".jpeg")
        ? "jpg"
        : normalizedFilename.endsWith(".gif")
          ? "gif"
          : normalizedFilename.endsWith(".webp")
            ? "webp"
            : "bin";
  const type = isAgentJson
    ? "application/json"
    : extension === "bin"
      ? "application/octet-stream"
      : `image/${extension === "jpg" ? "jpeg" : extension}`;
  return {
    url: `${getRelayHttpUrl(config)}/media/${sha256}.${extension}`,
    sha256,
    size: bytes.length,
    type,
    uploaded: Math.floor(Date.now() / 1000),
    filename,
  };
}

async function handleSendChannelMessage(
  args: {
    channelId: string;
    content: string;
    parentEventId?: string | null;
    kind?: number | null;
    mentionPubkeys?: string[];
    mediaTags?: string[][] | null;
    emojiTags?: string[][] | null;
    mentionTags?: string[][] | null;
    linkPreviewTags?: string[][] | null;
    sentFromThreadTag?: string[] | null;
    suppressLinkPreviews?: boolean;
  },
  config: E2eConfig | undefined,
): Promise<RawSendChannelMessageResponse> {
  const kind = args.kind ?? 9;
  const sendMessageDelayMs = config?.mock?.sendMessageDelayMs ?? 0;
  if (sendMessageDelayMs > 0) {
    await new Promise((resolve) =>
      window.setTimeout(resolve, sendMessageDelayMs),
    );
  }

  // Mirror the WebSocket send path's failure injection so specs that route
  // the first message through the acknowledged HTTP transport still exercise
  // `sendMessageErrors`. The real command rejects on a relay `OK false`, which
  // surfaces to callers as a thrown error carrying the relay reason.
  const sendMessageError =
    kind === 9 ? config?.mock?.sendMessageErrors?.shift() : null;
  if (sendMessageError) {
    throw new Error(sendMessageError);
  }

  // NIP-92 imeta attachments. The real relay echoes these back on the stored
  // event; mirror that here so attachment renderers (FileCard, images, video)
  // have the imeta tags they key on. `null`/empty → no extra tags.
  const mediaTags = args.mediaTags ?? [];
  // NIP-30 custom-emoji tags ride their own validated arg server-side; the
  // relay echoes them back on the stored event too, so mirror that here so the
  // emoji renderer keeps resolving `:shortcode:` after the round-trip.
  const emojiTags = args.emojiTags ?? [];
  // Reference-only mentions are already part of the outbound event. Preserve
  // them in the mock event too so local echoes match the complete sent tag set.
  const mentionTags = args.mentionTags ?? [];
  // Sender-authored link preview snapshots are independently validated by the
  // real command and echoed on the stored event. Preserve them in the mock so
  // E2E recipient rendering exercises the same authored-snapshot path.
  const linkPreviewTags = args.linkPreviewTags ?? [];
  if (linkPreviewTags.length > 0) {
    const suppression = linkPreviewTags.some(
      (tag) =>
        tag.length === 2 && tag[0] === "link-preview" && tag[1] === "none",
    );
    const snapshots = linkPreviewTags.filter(
      (tag) => tag[0] === "link-preview" && tag[1] === "snapshot",
    );
    const validSnapshots = parseLinkPreviewSnapshots(
      snapshots,
      args.content,
      new URL(getRelayHttpUrl(config)).origin,
    );
    if (
      (suppression && linkPreviewTags.length !== 1) ||
      (!suppression &&
        (snapshots.length !== linkPreviewTags.length ||
          validSnapshots.length !== snapshots.length ||
          snapshots.some(
            (tag) => !isValidLinkPreviewSnapshotCanonicalUrl(tag[3] ?? ""),
          )))
    ) {
      throw new Error("invalid link-preview snapshot tag");
    }
  }
  // All independently validated kinds end up on the stored event's tag set,
  // just like the real relay.
  const extraTags = [
    ...mediaTags,
    ...emojiTags,
    ...mentionTags,
    ...linkPreviewTags,
    ...(args.sentFromThreadTag ? [args.sentFromThreadTag] : []),
    ...(args.suppressLinkPreviews ? [["link-preview", "none"]] : []),
  ];
  const identity = getIdentity(config);
  if (!identity) {
    const createdAt = Math.floor(Date.now() / 1000);
    const mockPubkey = getMockMemberPubkey(config);

    if (!args.parentEventId) {
      const event = createMockEvent(kind, args.content, [
        ...buildTopLevelMessageTags(
          args.channelId,
          args.mentionPubkeys,
          mockPubkey,
        ),
        ...extraTags,
      ]);
      recordMockMessage(args.channelId, event);
      emitOrDeferMockSendMessageLiveEcho(args.channelId, event, config);

      return {
        event_id: event.id,
        parent_event_id: null,
        root_event_id: null,
        depth: 0,
        created_at: createdAt,
      };
    }

    const history = getMockMessageStore(args.channelId);
    const parentEvent = history.find(
      (event) => event.id === args.parentEventId,
    );
    const parentThread = parentEvent
      ? getThreadReferenceFromTags(parentEvent.tags)
      : {
          parentEventId: null,
          rootEventId: null,
        };
    const rootEventId = parentThread.rootEventId ?? args.parentEventId;
    const depth = parentEvent
      ? (() => {
          let currentEvent: RelayEvent | undefined = parentEvent;
          let nextDepth = 1;

          while (currentEvent) {
            const reference = getThreadReferenceFromTags(currentEvent.tags);
            if (!reference.parentEventId) {
              return nextDepth;
            }

            nextDepth += 1;
            currentEvent = history.find(
              (event) => event.id === reference.parentEventId,
            );
          }

          return nextDepth;
        })()
      : 1;

    const event: RelayEvent = {
      id: mockEventId(),
      pubkey: mockPubkey,
      created_at: createdAt,
      kind,
      tags: [
        ...buildReplyMessageTags(
          args.channelId,
          mockPubkey,
          args.parentEventId,
          rootEventId,
          args.mentionPubkeys,
        ),
        ...extraTags,
      ],
      content: args.content.trim(),
      sig: "mocksig".repeat(20).slice(0, 128),
    };

    recordMockMessage(args.channelId, event);
    emitOrDeferMockSendMessageLiveEcho(args.channelId, event, config);

    return {
      event_id: event.id,
      parent_event_id: args.parentEventId,
      root_event_id: rootEventId,
      depth,
      created_at: createdAt,
    };
  }

  const relayIdentity = getRelayIdentity(config);
  const tags = args.parentEventId
    ? buildReplyMessageTags(
        args.channelId,
        relayIdentity.pubkey,
        args.parentEventId,
        args.parentEventId,
        args.mentionPubkeys,
      )
    : buildTopLevelMessageTags(
        args.channelId,
        args.mentionPubkeys,
        relayIdentity.pubkey,
      );

  const result = await submitSignedEvent(config, {
    kind,
    content: args.content.trim(),
    tags: [...tags, ...extraTags],
  });

  return {
    event_id: result.event_id,
    parent_event_id: args.parentEventId ?? null,
    root_event_id: args.parentEventId ?? null,
    depth: args.parentEventId ? 1 : 0,
    created_at: Math.floor(Date.now() / 1000),
  };
}

async function handleSendManagedAgentChannelMessage(
  args: {
    agentPubkey: string;
    channelId: string;
    content: string;
    marker?: string | null;
    markerScope?: "agent" | "channel" | null;
    mentionPubkeys?: string[] | null;
    parentEventId?: string | null;
    additionalMarkers?: string[] | null;
  },
  _config: E2eConfig | undefined,
): Promise<RawSendChannelMessageResponse> {
  const agent = getMockManagedAgent(args.agentPubkey);
  const marker = args.marker?.trim();
  if (marker) {
    const existing = getMockMessageStore(args.channelId).find(
      (event) =>
        (args.markerScope === "channel" || event.pubkey === agent.pubkey) &&
        event.tags.some((tag) => tag[0] === "client" && tag[1] === marker),
    );
    if (existing) {
      return {
        event_id: existing.id,
        parent_event_id: args.parentEventId ?? null,
        root_event_id: args.parentEventId ?? null,
        depth: args.parentEventId ? 1 : 0,
        created_at: existing.created_at,
      };
    }
  }

  const createdAt = Math.floor(Date.now() / 1000);
  const tags = args.parentEventId
    ? buildReplyMessageTags(
        args.channelId,
        agent.pubkey,
        args.parentEventId,
        args.parentEventId,
        args.mentionPubkeys ?? undefined,
      )
    : buildTopLevelMessageTags(
        args.channelId,
        args.mentionPubkeys ?? undefined,
        agent.pubkey,
      );
  for (const clientMarker of [marker, ...(args.additionalMarkers ?? [])]) {
    if (clientMarker?.trim()) tags.push(["client", clientMarker.trim()]);
  }
  const event = createMockEvent(
    9,
    args.content.trim(),
    tags,
    agent.pubkey,
    createdAt,
  );
  recordMockMessage(args.channelId, event);
  emitMockLiveEvent(args.channelId, event);

  return {
    event_id: event.id,
    parent_event_id: args.parentEventId ?? null,
    root_event_id: args.parentEventId ?? null,
    depth: args.parentEventId ? 1 : 0,
    created_at: createdAt,
  };
}

/**
 * Mock the `delete_message` Tauri command. Removes the event from the
 * in-memory mock store and records the kind:5 structural event used by Inbox
 * refreshes. Do not emit it live: mock target IDs may fail the production
 * 64-hex deletion filter, letting a live merge restore the flattened row.
 */
function handleDeleteMessage(
  args: {
    channelId: string;
    eventId: string;
  },
  config: E2eConfig | undefined,
): void {
  const history = mockMessages.get(args.channelId);
  if (history) {
    const index = history.findIndex((ev) => ev.id === args.eventId);
    if (index !== -1) history.splice(index, 1);
  }

  const deletion = createMockEvent(
    KIND_DELETION,
    "",
    [
      ["e", args.eventId],
      ["h", args.channelId],
    ],
    getMockMemberPubkey(config),
  );
  recordMockMessage(args.channelId, deletion);
}

/**
 * Mock the `edit_message` Tauri command. Mirrors the real Rust command
 * (`build_message_edit`): emit a kind:40003 edit event carrying `["e", target]`
 * plus the new content, media (imeta) tags, and NIP-30 emoji tags. The timeline
 * (`formatTimelineMessages`) scans for these edit events and overlays the new
 * content + media/emoji tags onto the original via `applyEditTagOverlay`, so
 * recording + emitting the edit event is all the bridge needs to do — the same
 * path the real relay drives. `null`/empty tag args → no extra tags.
 */
async function handleEditMessage(
  args: {
    channelId: string;
    eventId: string;
    content: string;
    mediaTags?: string[][] | null;
    emojiTags?: string[][] | null;
    mentionPubkeys?: string[] | null;
    mentionTags?: string[][] | null;
    suppressLinkPreviews?: boolean;
  },
  config: E2eConfig | undefined,
): Promise<void> {
  const mediaTags = args.mediaTags ?? [];
  const emojiTags = args.emojiTags ?? [];
  const mentionPubkeys = args.mentionPubkeys ?? [];
  const mentionTags = args.mentionTags;
  const extraTags = [
    ...mediaTags,
    ...emojiTags,
    ...mentionPubkeys.map((pubkey) => ["p", pubkey]),
    ...(mentionTags ?? []),
    ...(mentionTags ? [["buzz:mention-snapshot"]] : []),
    ...(args.suppressLinkPreviews ? [["link-preview", "none"]] : []),
  ];
  const tags = [["h", args.channelId], ["e", args.eventId], ...extraTags];
  const content = args.content.trim();
  const identity = getIdentity(config);

  if (!identity) {
    const editEvent = createMockEvent(
      KIND_STREAM_MESSAGE_EDIT,
      content,
      tags,
      getMockMemberPubkey(config),
    );
    recordMockMessage(args.channelId, editEvent);
    emitMockLiveEvent(args.channelId, editEvent);
    return;
  }

  await submitSignedEvent(config, {
    kind: KIND_STREAM_MESSAGE_EDIT,
    content,
    tags,
  });
}

/** Locate the channel a stored mock event lives in (reactions carry no channel arg). */
function findMockEventChannel(eventId: string): string | undefined {
  for (const [channelId, events] of mockMessages) {
    if (events.some((event) => event.id === eventId)) {
      return channelId;
    }
  }
  return undefined;
}

/**
 * Mock the `add_reaction` Tauri command. Mirrors the real Rust command: a
 * kind:7 whose content is the emoji, plus — for a custom emoji — the NIP-30
 * `["emoji", shortcode, url]` tag (shortcode normalized to match the relay).
 * Recorded into the target's channel store and emitted live so the timeline's
 * reaction aggregation renders the pill (the channel subscription includes
 * kind:7). Unicode reactions carry no emoji tag, like the real command.
 */
async function handleAddReaction(
  args: { eventId: string; emoji: string; emojiUrl?: string | null },
  config: E2eConfig | undefined,
): Promise<void> {
  const channelId = findMockEventChannel(args.eventId);
  if (!channelId) {
    throw new Error(`mock add_reaction: unknown target event ${args.eventId}`);
  }

  const emoji = args.emoji.trim();
  // Real add_reaction events carry only the target `e` tag. Channel live
  // subscriptions already know which channel matched and restore that context
  // before merging the event into the timeline cache.
  const tags: string[][] = [["e", args.eventId]];
  if (args.emojiUrl) {
    const shortcode = emoji.replace(/^:+/, "").replace(/:+$/, "").toLowerCase();
    tags.push(["emoji", shortcode, args.emojiUrl]);
  }

  const event = createMockEvent(
    KIND_REACTION,
    emoji,
    tags,
    getMockMemberPubkey(config),
    Math.floor(Date.now() / 1000),
    // 64-hex id so the kind:5 deletion emitted by remove_reaction is accepted
    // by the timeline (getDeletionTargets requires a 64-hex `e` tag).
    mockEventId(),
  );
  recordMockMessage(channelId, event);
  emitMockLiveEvent(channelId, event);
}

/**
 * Mock the `remove_reaction` Tauri command. Finds the active member's own
 * kind:7 for this target+emoji, removes it from the store, and emits a kind:5
 * deletion so the timeline drops the reaction (the real command deletes via a
 * kind:5 too).
 */
async function handleRemoveReaction(
  args: { eventId: string; emoji: string },
  config: E2eConfig | undefined,
): Promise<void> {
  const channelId = findMockEventChannel(args.eventId);
  if (!channelId) {
    return;
  }

  const myPubkey = getMockMemberPubkey(config).toLowerCase();
  const emoji = args.emoji.trim();
  const store = getMockMessageStore(channelId);
  const reaction = store.find(
    (event) =>
      event.kind === KIND_REACTION &&
      event.pubkey.toLowerCase() === myPubkey &&
      event.content.trim() === emoji &&
      event.tags.some((t) => t[0] === "e" && t[1] === args.eventId),
  );
  if (!reaction) {
    return;
  }

  const index = store.indexOf(reaction);
  store.splice(index, 1);

  const deletion = createMockEvent(
    KIND_DELETION,
    "",
    [["e", reaction.id]],
    getMockMemberPubkey(config),
  );
  recordMockMessage(channelId, deletion);
  emitMockLiveEvent(channelId, deletion);
}

async function handleGetEvent(
  args: {
    eventId: string;
  },
  config: E2eConfig | undefined,
) {
  // Defer/release seam: when __BUZZ_E2E_DEFER_GET_EVENT__ is set to this
  // event's ID, hold this call in the queue until __BUZZ_E2E_RELEASE_GET_EVENT__()
  // is called.  Only the target ID is deferred; all other IDs resolve normally.
  // This keeps ancestor-lookup and context loads from being stalled or counted.
  if (
    window.__BUZZ_E2E_DEFER_GET_EVENT__ &&
    window.__BUZZ_E2E_DEFER_GET_EVENT__ === args.eventId
  ) {
    // Increment the count only for calls that are actually deferred.
    window.__BUZZ_E2E_GET_EVENT_CALL_COUNT__ =
      (window.__BUZZ_E2E_GET_EVENT_CALL_COUNT__ ?? 0) + 1;
    return new Promise<string>((resolve, reject) => {
      deferredGetEventQueue.push({
        resolve,
        reject,
        run: () => resolveGetEvent(args, config),
      });
    });
  }

  return resolveGetEvent(args, config);
}

async function resolveGetEvent(
  args: {
    eventId: string;
  },
  config: E2eConfig | undefined,
) {
  const identity = getIdentity(config);
  if (!identity) {
    // Allow test specs to mark specific event IDs as definitively deleted.
    if (config?.mock?.deletedEventIds?.includes(args.eventId)) {
      throw new Error("event not found");
    }
    const knownEvents: RelayEvent[] = [
      ...Array.from(mockMessages.values()).flat(),
      {
        id: "mock-engineering-shipped",
        pubkey:
          "bb22a5299220cad76ffd46190ccbeede8ab5dc260faa28b6e5a2cb31b9aff260",
        created_at: Math.floor(Date.now() / 1000) - 42 * 60,
        kind: 9,
        tags: [["h", "1c7e1c02-87bb-5e88-b2da-5a7a9432d0c9"]],
        content: "Engineering shipped the desktop build.",
        sig: "mocksig".repeat(20).slice(0, 128),
      },
      {
        id: "mock-design-critique",
        pubkey:
          "953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f",
        created_at: Math.floor(Date.now() / 1000) - 75 * 60,
        kind: 9,
        tags: [["h", "b5e2f8a1-3c44-5912-9e67-4a8d1f2b3c4e"]],
        content: "Design critique notes for the browse flow.",
        sig: "mocksig".repeat(20).slice(0, 128),
      },
      {
        id: "mock-forum-release-thread",
        pubkey:
          "953d3363262e86b770419834c53d2446409db6d918a57f8f339d495d54ab001f",
        created_at: Math.floor(Date.now() / 1000) - 90 * 60,
        kind: 45001,
        tags: [["e", "a27e1ee9-76a6-5bdf-a5d5-1d85610dad11"]],
        content: "Release checklist: async feedback thread.",
        sig: "mocksig".repeat(20).slice(0, 128),
      },
      {
        id: "mock-forum-release-reply",
        pubkey: ALICE_PUBKEY,
        created_at: Math.floor(Date.now() / 1000) - 80 * 60,
        kind: 45003,
        tags: buildReplyMessageTags(
          "a27e1ee9-76a6-5bdf-a5d5-1d85610dad11",
          ALICE_PUBKEY,
          "mock-forum-release-thread",
          "mock-forum-release-thread",
          undefined,
        ),
        content: "Looks good to me. We should ship it.",
        sig: "mocksig".repeat(20).slice(0, 128),
      },
    ];
    const event = knownEvents.find((item) => item.id === args.eventId);
    if (!event) {
      throw new Error(`Event not found: ${args.eventId}`);
    }

    return JSON.stringify(event);
  }

  // Query single event by ID via POST /query
  const events = await relayQuery(config, [{ ids: [args.eventId], limit: 1 }]);
  if (events.length === 0) {
    throw new Error(`Event not found: ${args.eventId}`);
  }
  return JSON.stringify(events[0]);
}

async function connectRealSocket(args: { url?: string; onMessage: unknown }) {
  relayWebsocketConnectAttemptStarts.push(Date.now());
  const wsId = nextSocketId++;
  const ws = new WebSocket(args.url ?? DEFAULT_RELAY_WS_URL);
  const handler = resolveHandler(args.onMessage);

  realSockets.set(wsId, ws);
  ws.addEventListener("message", (event) => {
    handler({
      type: "Text",
      data: event.data,
    });
  });
  ws.addEventListener("close", () => {
    sendWsClose(handler);
    realSockets.delete(wsId);
  });
  ws.addEventListener("error", () => {
    handler({
      type: "Error",
    });
  });

  return await new Promise<number>((resolve) => {
    ws.addEventListener("open", () => resolve(wsId), { once: true });
    ws.addEventListener("error", () => resolve(wsId), { once: true });
  });
}

async function connectMockSocket(args: { onMessage: unknown }) {
  relayWebsocketConnectAttemptStarts.push(Date.now());
  if (mockWebsocketUnavailable) {
    throw new Error("mock relay unavailable");
  }
  const connectError = getConfig()?.mock?.websocketConnectErrors?.shift();
  if (connectError) {
    throw new Error(connectError);
  }

  if (mockWebsocketSendMutexWedged) {
    return new Promise<number>(() => {});
  }

  const wsId = nextSocketId++;
  const handler = resolveHandler(args.onMessage);

  mockSockets.set(wsId, {
    handler,
    subscriptions: new Map(),
  });

  window.setTimeout(() => {
    sendWsText(handler, ["AUTH", `mock-challenge-${wsId}`]);
  }, 0);

  return wsId;
}

async function sendToRealSocket(args: {
  id: number;
  message?: {
    type: "Text" | "Close";
    data?: string;
  };
}) {
  const socket = realSockets.get(args.id);
  if (!socket) {
    return;
  }

  if (args.message?.type === "Close") {
    socket.close();
    return;
  }

  if (args.message?.type === "Text") {
    socket.send(args.message.data ?? "");
  }
}

function sendToMockSocket(args: {
  id: number;
  message?: {
    type: "Text" | "Close";
    data?: string;
  };
}) {
  const socket = mockSockets.get(args.id);
  if (
    getConfig()?.mock?.stallWebsocketSends &&
    args.message?.type !== "Close"
  ) {
    mockWebsocketSendMutexWedged = true;
    return new Promise<void>(() => {});
  }

  if (!socket || !args.message) {
    return;
  }

  if (args.message.type === "Close") {
    mockSockets.delete(args.id);
    sendWsClose(socket.handler);
    return;
  }

  if (args.message.type !== "Text" || !args.message.data) {
    return;
  }

  const [type, ...rest] = JSON.parse(args.message.data) as [
    string,
    ...unknown[],
  ];

  if (type === "AUTH") {
    const event = rest[0] as RelayEvent;
    const response = mockAuthResponses.shift() ?? {
      success: true,
      message: "",
    };
    sendWsText(socket.handler, [
      "OK",
      event.id,
      response.success,
      response.message,
    ]);
    return;
  }

  if (type === "REQ") {
    const subId = rest[0] as string;
    const filters = rest.slice(1) as MockFilter[];
    if (
      !filters.every((filter) =>
        isPGatedFilterAuthorized(filter, MOCK_IDENTITY_PUBKEY),
      )
    ) {
      sendWsText(socket.handler, ["CLOSED", subId, P_GATED_REJECTION_MESSAGE]);
      return;
    }

    if (subId.startsWith("live-")) {
      // Collect channel IDs from all filters in the REQ
      const channelIds = new Set<string>();
      const kinds = new Set<number>();
      const ownerPubkeys = new Set<string>();
      for (const f of filters) {
        const cid = f["#h"]?.[0];
        if (cid) channelIds.add(cid);
        for (const kind of f.kinds ?? []) {
          kinds.add(kind);
        }
        for (const p of f["#p"] ?? []) {
          ownerPubkeys.add(p);
        }
      }
      const onlyChannelId =
        channelIds.size === 1
          ? (channelIds.values().next().value as string)
          : undefined;
      if (
        getConfig()?.mock?.closeChannelLiveSubscriptionOnce &&
        !mockClosedChannelLiveSubscription &&
        onlyChannelId &&
        kinds.has(KIND_CHANNEL_THREAD_SUMMARY)
      ) {
        mockClosedChannelLiveSubscription = true;
        sendWsText(socket.handler, ["CLOSED", subId, "rate-limited"]);
        return;
      }
      socket.subscriptions.set(subId, {
        channelId: onlyChannelId ?? GLOBAL_MOCK_SUBSCRIPTION,
        kinds: kinds.size > 0 ? [...kinds] : null,
        ownerPubkeys: [...ownerPubkeys],
      });
      sendWsText(socket.handler, ["EOSE", subId]);
      return;
    }

    const filter = rest[1] as MockFilter;
    if (filter.kinds?.includes(13534)) {
      sendWsText(socket.handler, [
        "EVENT",
        subId,
        createMockRelayMembershipEvent(),
      ]);
      const eoseDelayMs = getConfig()?.mock?.relayMembershipEoseDelayMs ?? 0;
      if (eoseDelayMs > 0) {
        window.setTimeout(
          () => sendWsText(socket.handler, ["EOSE", subId]),
          eoseDelayMs,
        );
      } else {
        sendWsText(socket.handler, ["EOSE", subId]);
      }
      return;
    }

    if (filter.kinds?.includes(KIND_EMOJI_SET)) {
      // Honor `authors` so `fetchOwnEmoji` (authors:[me]) sees only the
      // caller's set, while the union fetch (no authors) sees every member's —
      // matching the real relay and the own-vs-community split in the UI.
      const authors = filter.authors?.map((a) => a.toLowerCase());
      for (const emojiEvent of createMockCustomEmojiSetEvents()) {
        if (authors && !authors.includes(emojiEvent.pubkey.toLowerCase())) {
          continue;
        }
        sendWsText(socket.handler, ["EVENT", subId, emojiEvent]);
      }
      sendWsText(socket.handler, ["EOSE", subId]);
      return;
    }

    if (filter.kinds?.includes(KIND_USER_STATUS)) {
      for (const statusEvent of filterMockUserStatuses(filter)) {
        sendWsText(socket.handler, ["EVENT", subId, statusEvent]);
      }
      sendWsText(socket.handler, ["EOSE", subId]);
      return;
    }

    if (filter.kinds?.includes(KIND_EVENT_REMINDER)) {
      const authors = filter.authors?.map((a) => a.toLowerCase());
      for (const event of mockReminderEvents) {
        if (authors && !authors.includes(event.pubkey.toLowerCase())) continue;
        sendWsText(socket.handler, ["EVENT", subId, event]);
      }
      sendWsText(socket.handler, ["EOSE", subId]);
      return;
    }

    if (filter.kinds?.includes(KIND_PERSONA)) {
      const authors = filter.authors?.map((author) => author.toLowerCase());
      const sourceIds = filter["#d"];
      for (const event of mockPersonaEvents) {
        if (authors && !authors.includes(event.pubkey.toLowerCase())) continue;
        if (
          event.pubkey.toLowerCase() !== MOCK_IDENTITY_PUBKEY.toLowerCase() &&
          !personaHasExactSharedTag(event)
        ) {
          continue;
        }
        const sourceId = event.tags.find((tag) => tag[0] === "d")?.[1];
        if (sourceIds && (!sourceId || !sourceIds.includes(sourceId))) continue;
        sendWsText(socket.handler, ["EVENT", subId, event]);
      }
      sendWsText(socket.handler, ["EOSE", subId]);
      return;
    }

    // Project queries: NIP-34 kinds, or kind:1 comments scoped by repo `a`
    // tag or by issue/PR root `e` tag (discussions, approvals, review
    // requests, assignment operations). Channel messages are kind 9, so a
    // kind:1 `#e` query can only target project discussion events.
    if (
      filter.kinds?.some((kind) => MOCK_PROJECT_KINDS.has(kind)) ||
      (filter.kinds?.includes(1) && (filter["#a"] || filter["#e"]))
    ) {
      window.__BUZZ_E2E_PROJECT_QUERY_FILTERS__ ??= [];
      window.__BUZZ_E2E_PROJECT_QUERY_FILTERS__.push(filter);
      const rejectedKinds =
        window.__BUZZ_E2E_REJECT_PROJECT_QUERY_KINDS__ ?? [];
      if (filter.kinds?.some((kind) => rejectedKinds.includes(kind))) {
        sendWsText(socket.handler, [
          "CLOSED",
          subId,
          "mock project query failure",
        ]);
        return;
      }
      for (const event of filterMockProjectEvents(filter)) {
        sendWsText(socket.handler, ["EVENT", subId, event]);
      }
      sendWsText(socket.handler, ["EOSE", subId]);
      return;
    }

    const channelId = filter["#h"]?.[0];
    if (channelId && subId.startsWith("history-")) {
      const closeReason = mockChannelHistoryCloses.shift();
      if (closeReason) {
        sendWsText(socket.handler, ["CLOSED", subId, closeReason]);
        return;
      }
    }
    if (!channelId) {
      // Aux-backfill filters (reactions/deletions) are `#e`-keyed with no
      // channel tag — serve them across all channel stores like the relay.
      const referencedIds = filter["#e"];
      if (referencedIds && referencedIds.length > 0) {
        const targets = new Set(referencedIds);
        for (const events of mockMessages.values()) {
          for (const event of events) {
            if (filter.kinds && !filter.kinds.includes(event.kind)) {
              continue;
            }
            if (
              event.tags.some(
                (tag) => tag[0] === "e" && tag[1] && targets.has(tag[1]),
              )
            ) {
              sendWsText(socket.handler, ["EVENT", subId, event]);
            }
          }
        }
      }
      sendWsText(socket.handler, ["EOSE", subId]);
      return;
    }

    emitMockHistory(socket, subId, channelId, filter);
    return;
  }

  if (type === "CLOSE") {
    const subId = rest[0] as string;
    socket.subscriptions.delete(subId);
    return;
  }

  if (type === "EVENT") {
    const event = rest[0] as RelayEvent;

    if (event.kind === 28936) {
      sendWsText(socket.handler, ["OK", event.id, true, ""]);
      return;
    }

    if ([9030, 9031, 9032].includes(event.kind)) {
      const accepted = updateMockRelayMembershipFromAdminEvent(event);
      sendWsText(socket.handler, [
        "OK",
        event.id,
        accepted,
        accepted ? "" : "Invalid relay admin event.",
      ]);
      return;
    }

    if (event.kind === 9033) {
      sendWsText(socket.handler, ["OK", event.id, true, ""]);
      return;
    }

    if (event.kind === 30078) {
      sendWsText(socket.handler, ["OK", event.id, true, ""]);
      return;
    }

    if (event.kind === KIND_EVENT_REMINDER) {
      // Upsert by d-tag (replaceable event)
      const dTag = event.tags.find((t) => t[0] === "d")?.[1];
      if (dTag) {
        const idx = mockReminderEvents.findIndex(
          (e) =>
            e.pubkey.toLowerCase() === event.pubkey.toLowerCase() &&
            e.tags.some((t) => t[0] === "d" && t[1] === dTag),
        );
        if (idx >= 0) mockReminderEvents.splice(idx, 1);
      }
      mockReminderEvents.push(event);
      sendWsText(socket.handler, ["OK", event.id, true, ""]);
      return;
    }

    if (event.kind === KIND_PERSONA) {
      const sourceId = event.tags.find((tag) => tag[0] === "d")?.[1];
      if (!sourceId) {
        sendWsText(socket.handler, [
          "OK",
          event.id,
          false,
          "invalid: persona event missing d tag.",
        ]);
        return;
      }
      const sharedTags = event.tags.filter((tag) => tag[0] === "shared");
      if (
        sharedTags.length > 1 ||
        (sharedTags.length === 1 && !personaHasExactSharedTag(event))
      ) {
        sendWsText(socket.handler, [
          "OK",
          event.id,
          false,
          'invalid: shared tag must be exactly ["shared","true"].',
        ]);
        return;
      }
      upsertMockPersonaRelayEvent(event);
      emitMockGlobalEvent(event);
      sendWsText(socket.handler, ["OK", event.id, true, ""]);
      return;
    }

    if (event.kind === 20001) {
      const status = event.content;
      if (status === "online" || status === "away" || status === "offline") {
        setMockPresenceStatus(event.pubkey, status);
      }
      emitMockGlobalEvent(event);
      sendWsText(socket.handler, ["OK", event.id, true, ""]);
      return;
    }

    if (event.kind === KIND_USER_STATUS) {
      const hasGeneralDTag = event.tags.some(
        (tag) => tag[0] === "d" && tag[1] === "general",
      );
      if (!hasGeneralDTag) {
        sendWsText(socket.handler, [
          "OK",
          event.id,
          false,
          "invalid: user status missing d tag.",
        ]);
        return;
      }

      recordMockUserStatus(event);
      emitMockGlobalEvent(event);
      sendWsText(socket.handler, ["OK", event.id, true, ""]);
      return;
    }

    if (isMockProjectScopedEvent(event)) {
      if (event.pubkey !== DEFAULT_MOCK_IDENTITY.pubkey) {
        sendWsText(socket.handler, [
          "OK",
          event.id,
          false,
          "invalid: event pubkey does not match authenticated identity",
        ]);
        return;
      }
      if (
        event.kind === KIND_PROJECT_ANNOUNCEMENT &&
        window.__BUZZ_E2E_UNSUPPORTED_PROJECT_ANNOUNCEMENTS__
      ) {
        sendWsText(socket.handler, [
          "OK",
          event.id,
          false,
          "restricted: unknown event kind",
        ]);
        return;
      }
      const rejectionIndex =
        window.__BUZZ_E2E_REJECT_PROJECT_EVENT_KINDS__?.indexOf(event.kind) ??
        -1;
      if (rejectionIndex >= 0) {
        window.__BUZZ_E2E_REJECT_PROJECT_EVENT_KINDS__?.splice(
          rejectionIndex,
          1,
        );
        sendWsText(socket.handler, [
          "OK",
          event.id,
          false,
          "mock project event rejection",
        ]);
        return;
      }
      getMockProjectEventStore().push(event);
      const acceptedProjectEvents =
        window.__BUZZ_E2E_ACCEPTED_PROJECT_EVENTS__ ?? [];
      acceptedProjectEvents.push({
        content: event.content,
        kind: event.kind,
        tags: event.tags,
      });
      window.__BUZZ_E2E_ACCEPTED_PROJECT_EVENTS__ = acceptedProjectEvents;
      const failedAckIndex =
        window.__BUZZ_E2E_FAIL_PROJECT_EVENT_ACK_KINDS__?.indexOf(event.kind) ??
        -1;
      if (failedAckIndex >= 0) {
        window.__BUZZ_E2E_FAIL_PROJECT_EVENT_ACK_KINDS__?.splice(
          failedAckIndex,
          1,
        );
        sendWsText(socket.handler, [
          "OK",
          event.id,
          false,
          "mock lost project acknowledgement",
        ]);
        return;
      }
      sendWsText(socket.handler, ["OK", event.id, true, ""]);
      return;
    }

    const channelId = getChannelIdFromTags(event.tags);
    if (!channelId) {
      sendWsText(socket.handler, [
        "OK",
        event.id,
        false,
        "Missing channel tag.",
      ]);
      return;
    }

    const sendMessageError =
      event.kind === 9 ? getConfig()?.mock?.sendMessageErrors?.shift() : null;
    if (sendMessageError) {
      sendWsText(socket.handler, ["OK", event.id, false, sendMessageError]);
      return;
    }

    recordMockMessage(channelId, event);
    emitMockLiveEvent(channelId, event);
    sendWsText(socket.handler, ["OK", event.id, true, ""]);
  }
}

function disconnectMockSocket(id: number) {
  const socket = mockSockets.get(id);
  if (!socket) {
    return;
  }

  mockSockets.delete(id);
  sendWsClose(socket.handler);
}

export function maybeInstallE2eTauriMocks() {
  if (installed) {
    return;
  }

  const config = getConfig();
  if (!config) {
    return;
  }

  mockClosedChannelLiveSubscription = false;
  mockWebsocketUnavailable = false;
  mockAuthResponses.length = 0;
  mockChannelHistoryCloses.length = 0;
  relayWebsocketConnectAttemptStarts.length = 0;
  deferredSendMessageLiveEchoes.length = 0;
  deferredLinkPreviewMetadataQueue = [];
  deferredLinkPreviewUploadQueue = [];
  cancelledMediaUploadIds = new Set<string>();
  window.__BUZZ_E2E_LINK_PREVIEW_UPLOAD_STARTS__ = 0;
  window.__BUZZ_E2E_RELEASE_LINK_PREVIEW_METADATA__ = () => {
    const queued = deferredLinkPreviewMetadataQueue.splice(0);
    for (const release of queued) release();
    return queued.length;
  };
  window.__BUZZ_E2E_RELEASE_LINK_PREVIEW_UPLOADS__ = () => {
    const queued = deferredLinkPreviewUploadQueue.splice(0);
    for (const release of queued) release();
    return queued.length;
  };
  mockGlobalAgentConfig = config.mock?.globalAgentConfig
    ? { ...config.mock.globalAgentConfig }
    : null;
  resetMockRelayMembers(config);
  resetMockRelayAgents(config);
  resetMockManagedAgents(config);
  resetMockPersonas(config);
  resetMockTeams(config);
  seedMockSearchProfiles(config);
  resetMockWorkflows();
  resetMockMesh();
  resetMockUserStatuses();
  resetMockPersonaCatalogEvents(config);
  resetMockSaveSubscriptions(config);
  resetMockPendingCommunityDeepLinks(config);
  resetMockPendingNavigationDeepLinks(config);
  resetMockPendingEntityDeepLinks(config);
  initializeMockHuddle(config.mock?.huddle, config);
  mockWebsocketSendMutexWedged = false;
  if (config.mock?.windowLabel) {
    (window as Window & { isTauri?: boolean }).isTauri = true;
  }
  mockWindows(config.mock?.windowLabel ?? "main");
  window.__BUZZ_E2E_COMMANDS__ = [];
  window.__BUZZ_E2E_COMMAND_PAYLOADS__ = [];
  window.__BUZZ_E2E_COMMAND_LOG__ = [];
  mockMediaProxyPort = config.mock?.mediaProxyInitiallyUnavailable
    ? 0
    : MOCK_MEDIA_PROXY_PORT;
  window.__BUZZ_E2E_RELEASE_MEDIA_PROXY__ = () => {
    mockMediaProxyPort = MOCK_MEDIA_PROXY_PORT;
    resetMediaCaches();
    ensureRelayOriginFetch();
    return mockMediaProxyPort;
  };
  window.__BUZZ_E2E_EMIT_MOCK_HUDDLE_TTS_SPEAKER__ = (payload) =>
    emit("huddle-tts-speaker-level", payload);
  window.__BUZZ_E2E_SIGNED_EVENTS__ = [];
  window.__BUZZ_E2E_WEBVIEW_ZOOM__ = 1;
  window.__BUZZ_E2E_EMIT_MEDIA_UPLOAD_PHASE__ = async (input) => {
    await emit("media-upload-phase", input);
  };
  window.__BUZZ_E2E_EMIT_MEDIA_UPLOAD_PROGRESS__ = async (input) => {
    await emit("media-upload-progress", input);
  };
  window.__BUZZ_E2E_SET_MOCK_HUDDLE_SNAPSHOT__ = async ({
    members,
    transcriptionEnabled,
  }) => {
    if (!mockHuddle) {
      throw new Error("No mock huddle is configured.");
    }
    mockHuddle.members = structuredClone(members);
    mockHuddle.state.transcription_enabled = transcriptionEnabled;
    refreshMockHuddleMembership(config);
    persistMockHuddle();
    await emitMockHuddleState();
  };
  window.__BUZZ_E2E_EMIT_MOCK_MESSAGE__ = ({
    channelName,
    content,
    parentEventId,
    pubkey,
    kind,
    mentionPubkeys,
    extraTags,
    createdAt,
    pending,
    id,
  }) => {
    const channel = mockChannels.find(
      (candidate) => candidate.name === channelName,
    );
    if (!channel) {
      throw new Error(`Mock channel ${channelName} not found.`);
    }

    return emitMockChannelMessage(
      channel.id,
      content,
      parentEventId,
      pubkey,
      kind,
      mentionPubkeys,
      extraTags,
      createdAt,
      pending,
      id,
    );
  };
  window.__BUZZ_E2E_PREPEND_MOCK_HISTORY__ = prependMockHistory;
  window.__BUZZ_E2E_EMIT_MOCK_TYPING__ = ({
    channelName,
    createdAt,
    pubkey,
    threadHeadId,
  }) => {
    const channel = mockChannels.find(
      (candidate) => candidate.name === channelName,
    );
    if (!channel) {
      throw new Error(`Mock channel ${channelName} not found.`);
    }

    return emitMockTypingIndicator(
      channel.id,
      pubkey ?? CHARLIE_PUBKEY,
      threadHeadId,
      createdAt,
    );
  };
  window.__BUZZ_E2E_HAS_MOCK_LIVE_SUBSCRIPTION__ = ({ channelName, kind }) => {
    const channel = mockChannels.find(
      (candidate) => candidate.name === channelName,
    );
    if (!channel) {
      throw new Error(`Mock channel ${channelName} not found.`);
    }

    return hasMockLiveSubscription(channel.id, kind);
  };
  window.__BUZZ_E2E_HAS_MOCK_OWNER_KIND_SUBSCRIPTION__ = ({
    ownerPubkey,
    kind,
  }) => hasMockOwnerKindSubscription(ownerPubkey, kind);
  window.__BUZZ_E2E_PUSH_MOCK_FEED_ITEM__ = (item) => {
    const category = item.category === "mention" ? "mentions" : item.category;
    mockFeedOverrides[category].unshift(item);
    window.dispatchEvent(new CustomEvent("buzz:e2e-home-feed-updated"));
    return item;
  };
  window.__BUZZ_E2E_REPLACE_MOCK_FEED_ITEM__ = (oldId, item) => {
    const category = item.category === "mention" ? "mentions" : item.category;
    // Remove the old item from every category bucket (it may have been in a
    // different bucket or the same one).
    for (const bucket of Object.values(mockFeedOverrides)) {
      const idx = (bucket as RawFeedItem[]).findIndex((r) => r.id === oldId);
      if (idx !== -1) {
        (bucket as RawFeedItem[]).splice(idx, 1);
        break;
      }
    }
    // Insert the replacement at the front of the correct bucket.
    mockFeedOverrides[category].unshift(item);
    window.dispatchEvent(new CustomEvent("buzz:e2e-home-feed-updated"));
    return item;
  };
  window.__BUZZ_E2E_MD_PARSE_COUNT__ = getMarkdownParseCount;
  window.__BUZZ_E2E_ACTIVATE_TIMEOUT__ = (expiresAtMs: number) => {
    const expiresAtSec = expiresAtMs > 0 ? Math.floor(expiresAtMs / 1000) : 0;
    const msg =
      expiresAtSec > 0
        ? `restricted: you are timed out until ${expiresAtSec}`
        : "restricted: you are timed out until 0";
    recordTimeoutFromRejection(msg);
  };
  window.__BUZZ_E2E_INVALIDATE_CHANNELS__ = async () => {
    await window.__BUZZ_E2E_QUERY_CLIENT__?.invalidateQueries({
      queryKey: ["channels"],
    });
  };
  window.__BUZZ_E2E_MUTATE_CHANNEL__ = ({
    channelId,
    channelType,
    description,
    removeMemberPubkey,
  }) => {
    const channel = mockChannels.find((ch) => ch.id === channelId);
    if (!channel) return;
    if (channelType !== undefined) {
      channel.channel_type = channelType;
    }
    if (description !== undefined) {
      channel.description = description;
    }
    if (removeMemberPubkey !== undefined) {
      channel.members = channel.members.filter(
        (m) => m.pubkey !== removeMemberPubkey,
      );
      syncMockChannel(channel);
    }
    touchMockChannel(channel);
  };
  // get_event defer/release seam — reset counter and queue on each install.
  window.__BUZZ_E2E_GET_EVENT_CALL_COUNT__ = 0;
  window.__BUZZ_E2E_DEFER_GET_EVENT__ = null;
  deferredGetEventQueue = [];
  deferNextChannelsRead = false;
  deferredChannelsReadResolve = null;
  window.__BUZZ_E2E_CHANNELS_READ_PENDING__ = 0;
  window.__BUZZ_E2E_DEFER_NEXT_CHANNELS_READ__ = () => {
    if (deferredChannelsReadResolve) {
      throw new Error("a channel read is already deferred");
    }
    deferNextChannelsRead = true;
  };
  window.__BUZZ_E2E_RELEASE_CHANNELS_READ__ = () => {
    deferNextChannelsRead = false;
    const resolve = deferredChannelsReadResolve;
    deferredChannelsReadResolve = null;
    window.__BUZZ_E2E_CHANNELS_READ_PENDING__ = 0;
    resolve?.();
    return resolve ? 1 : 0;
  };
  window.__BUZZ_E2E_RELEASE_GET_EVENT__ = () => {
    const queued = deferredGetEventQueue.splice(0);
    for (const entry of queued) {
      entry.run().then(entry.resolve, entry.reject);
    }
    // Disable deferral and reset counter after release so the seam is inert
    // for the remainder of the test (no stray defers from context loads).
    window.__BUZZ_E2E_DEFER_GET_EVENT__ = null;
    window.__BUZZ_E2E_GET_EVENT_CALL_COUNT__ = 0;
    return queued.length;
  };
  window.__BUZZ_E2E_EMIT_MOCK_READ_STATE__ = ({
    clientId,
    contexts,
    createdAt,
    slotId,
  }) => {
    const blob = JSON.stringify({
      v: 1,
      client_id: clientId,
      contexts,
    });
    const event = createMockEvent(
      30078,
      blob,
      [
        ["d", `read-state:${slotId}`],
        ["t", "read-state"],
      ],
      getMockMemberPubkey(config),
      createdAt,
    );
    emitMockLiveEvent(GLOBAL_MOCK_SUBSCRIPTION, event);
    return event;
  };
  window.__BUZZ_E2E_SET_RELAY_CONNECTION_STATE__ = (state) => {
    // Directly emit a connection state change on the relay client singleton,
    // for tests that need to drive degraded relay UI without waiting for the
    // real auth-timeout + reconnect-debounce cycle (~10 s). Reaches the
    // TS-private emitter via a cast so the production class carries no
    // test-only seam.
    (
      relayClient as unknown as {
        connectionStateEmitter: { set: (s: ConnectionState) => void };
      }
    ).connectionStateEmitter.set(state);
  };
  window.__BUZZ_E2E_GET_RELAY_CONNECTION_STATE__ = () =>
    relayClient.getConnectionState();
  window.__BUZZ_E2E_QUEUE_AUTH_RESPONSES__ = (responses) => {
    mockAuthResponses.push(...responses);
  };
  window.__BUZZ_E2E_QUEUE_CHANNEL_HISTORY_CLOSES__ = (reasons) => {
    mockChannelHistoryCloses.push(...reasons);
  };
  window.__BUZZ_E2E_CLOSE_LIVE_SUBSCRIPTIONS__ = (reason) => {
    let closed = 0;
    for (const socket of mockSockets.values()) {
      for (const subId of [...socket.subscriptions.keys()]) {
        sendWsText(socket.handler, ["CLOSED", subId, reason]);
        socket.subscriptions.delete(subId);
        closed += 1;
      }
    }
    return closed;
  };

  window.__BUZZ_E2E_SEED_MOCK_REMINDERS__ = (reminders) => {
    mockReminderEvents.length = 0;
    for (const r of reminders) {
      mockReminderEvents.push(r);
    }
  };

  window.__BUZZ_E2E_SET_STALL_WEBSOCKET_SENDS__ = (stall) => {
    const config = getConfig();
    if (!config?.mock) return;
    config.mock.stallWebsocketSends = stall;
    if (!stall) mockWebsocketSendMutexWedged = false;
  };
  window.__BUZZ_E2E_DISCONNECT_MOCK_WEBSOCKETS__ = () => {
    const socketIds = [...mockSockets.keys()];
    for (const socketId of socketIds) disconnectMockSocket(socketId);
    return socketIds.length;
  };
  window.__BUZZ_E2E_RESTART_MOCK_WEBSOCKETS__ = () => {
    const sockets = [...mockSockets.values()];
    mockSockets.clear();
    for (const socket of sockets) {
      sendWsClose(socket.handler, 1012, "relay restarting");
    }
    return sockets.length;
  };
  window.__BUZZ_E2E_SET_MOCK_WEBSOCKET_UNAVAILABLE__ = (unavailable) => {
    mockWebsocketUnavailable = unavailable;
    if (unavailable) relayWebsocketConnectAttemptStarts.length = 0;
  };
  window.__BUZZ_E2E_GET_WEBSOCKET_CONNECT_ATTEMPTS__ = () => [
    ...relayWebsocketConnectAttemptStarts,
  ];
  window.__BUZZ_E2E_ACTIVATE_RELAY_RATE_LIMIT__ = (seconds) => {
    activateRateLimit(seconds);
  };
  window.__BUZZ_E2E_RESET_WEBSOCKET_CONNECT_ATTEMPTS__ = () => {
    relayWebsocketConnectAttemptStarts.length = 0;
  };
  window.__BUZZ_E2E_RELEASE_SEND_MESSAGE_LIVE_ECHO__ = () => {
    const queued = deferredSendMessageLiveEchoes.splice(0);
    for (const { channelId, event } of queued) {
      emitMockLiveEvent(channelId, event);
    }
    return queued.length;
  };
  // Tests vary mesh admission and models to exercise provider discovery and
  // the managed-agent start preflight.
  window.__BUZZ_E2E_SET_MESH__ = (mesh) => {
    if (mesh.admitted !== undefined) mockMeshState.admitted = mesh.admitted;
    if (mesh.models !== undefined) mockMeshState.models = mesh.models;
    if (mesh.denyReason !== undefined)
      mockMeshState.denyReason = mesh.denyReason;
    if (mesh.nodeState !== undefined) mockMeshState.nodeState = mesh.nodeState;
    if (mesh.nodeMode !== undefined) mockMeshState.nodeMode = mesh.nodeMode;
    if (mesh.servingUsage !== undefined)
      mockMeshState.servingUsage = {
        ...mockMeshState.servingUsage,
        ...mesh.servingUsage,
      };
  };
  let seedTurnSeq = Date.now();
  window.__BUZZ_E2E_SEED_ACTIVE_TURNS__ = ({
    agentPubkey,
    channelId,
    turnId,
    kind = "turn_started",
  }) => {
    seedTurnSeq += 1;
    const event = {
      seq: seedTurnSeq,
      timestamp: new Date().toISOString(),
      kind,
      agentIndex: 0,
      channelId,
      sessionId: null,
      turnId,
      payload: null,
    };
    syncAgentTurnsFromEvents(agentPubkey, [event]);
    syncAgentObserverEvents(agentPubkey, [event]);
  };
  window.__BUZZ_E2E_SEED_OBSERVER_EVENTS__ = ({ agentPubkey, events }) => {
    injectObserverEventsForE2E(agentPubkey, events);
  };
  const meshModelName = (modelId: string) => {
    const basename = modelId.split("/").at(-1) ?? modelId;
    return basename
      .replace(/-(?:Instruct|GGUF)(?:-|:|$).*/, "")
      .replace(/:.*/, "")
      .replaceAll("-", " ");
  };
  const meshNodeStatus = (
    state: "off" | "running",
    mode: "serve" | "client" | null,
  ) => {
    const model = mockMeshState.activeModel ?? mockMeshState.models[0] ?? null;
    return {
      state,
      mode,
      health: { status: "ok" as const, reason: null },
      apiBaseUrl: state === "running" ? "http://127.0.0.1:9337/v1" : null,
      consoleUrl: null,
      modelId: model?.id ?? null,
      modelName: model?.name ?? null,
      inviteToken: state === "running" ? "mock-endpoint-addr" : null,
      endpointId: state === "running" ? "mock-endpoint-id" : null,
      deviceId: state === "running" ? "mock-endpoint-id" : null,
      deviceName: state === "running" ? "Mock desktop" : null,
    };
  };
  let mockImportedVoices: Array<{
    key: string;
    displayName: string;
    backend: string;
    backendName: string;
    availability: "installed";
    fallbackKey: string;
    referenceFile: string;
    provenance: {
      source: string;
      contentHash: string;
      license: null;
      sourceUrl: null;
    };
  }> = [];
  const handleMockCommand = async (
    command: string,
    payload: unknown,
  ): Promise<unknown> => {
    const activeConfig = getConfig();
    const identity = getActiveIdentity(activeConfig);
    window.__BUZZ_E2E_COMMANDS__?.push(command);
    const loggedPayload = (() => {
      if (payload instanceof Uint8Array) {
        return { rawByteLength: payload.byteLength };
      }
      try {
        return JSON.parse(JSON.stringify(payload ?? null));
      } catch {
        return null;
      }
    })();
    window.__BUZZ_E2E_COMMAND_PAYLOADS__?.push({
      command,
      payload: loggedPayload,
    });
    window.__BUZZ_E2E_COMMAND_LOG__?.push({ command, payload });

    switch (command) {
      case "get_huddle_state": {
        const snapshot = mockHuddle ? structuredClone(mockHuddle.state) : null;
        const delayMs = activeConfig?.mock?.huddleStateReadDelayMs ?? 0;
        if (delayMs > 0) {
          await new Promise((resolve) => window.setTimeout(resolve, delayMs));
        }
        return snapshot;
      }
      case "get_voice_input_mode":
        return mockHuddle?.state.voice_input_mode ?? "push_to_talk";
      case "set_voice_input_mode": {
        if (!mockHuddle) throw new Error("No active mock huddle.");
        const mode = (payload as { mode?: unknown }).mode;
        if (mode !== "push_to_talk" && mode !== "voice_activity") {
          throw new Error("Missing voice input mode.");
        }
        mockHuddle.state.voice_input_mode = mode;
        persistMockHuddle();
        await emitMockHuddleState();
        return null;
      }
      case "set_huddle_manual_mic_unmuted":
        return null;
      case "get_huddle_agent_pubkeys":
        if (!mockHuddle) return [];
        return [...mockHuddle.state.agent_pubkeys];
      case "start_huddle": {
        const request = payload as {
          parentChannelId: string;
          memberPubkeys?: string[];
          channelName?: string;
        };
        const ephemeralChannelId = crypto.randomUUID();
        const selfPubkey = identity?.pubkey ?? DEFAULT_MOCK_IDENTITY.pubkey;
        const owner = createCurrentMember(activeConfig, "owner");
        mockHuddle = {
          members: [
            { pubkey: selfPubkey, role: "owner" },
            ...(request.memberPubkeys ?? []).map((pubkey) => ({
              pubkey,
              role: "bot" as const,
            })),
          ],
          state: {
            phase: "creating",
            parent_channel_id: request.parentChannelId,
            ephemeral_channel_id: ephemeralChannelId,
            huddle_thread_event_id: null,
            participants: [],
            agent_pubkeys: [],
            agent_voice_settings: {},
            tts_enabled: false,
            transcription_enabled: false,
            is_creator: true,
            voice_input_mode: "push_to_talk",
          },
        };
        refreshMockHuddleMembership(activeConfig);
        persistMockHuddle();
        await emitMockHuddleState();
        mockChannels.push(
          createMockChannel({
            id: ephemeralChannelId,
            name: request.channelName ?? "huddle",
            channel_type: "stream",
            visibility: "private",
            description: "",
            topic: null,
            purpose: null,
            last_message_at: null,
            archived_at: null,
            created_by: owner.pubkey,
            topic_set_by: null,
            topic_set_at: null,
            purpose_set_by: null,
            purpose_set_at: null,
            topic_required: false,
            max_members: null,
            nip29_group_id: null,
            ttl_seconds: 3_600,
            created_minutes_ago: 0,
            members: [owner],
          }),
        );
        emitMockGlobalEvent(
          createMockEvent(
            KIND_MEMBER_ADDED_NOTIFICATION,
            "",
            [
              ["h", ephemeralChannelId],
              ["p", selfPubkey],
            ],
            selfPubkey,
          ),
        );
        if ((activeConfig?.mock?.startHuddleReturnDelayMs ?? 0) > 0) {
          await new Promise((resolve) =>
            window.setTimeout(
              resolve,
              activeConfig?.mock?.startHuddleReturnDelayMs ?? 0,
            ),
          );
        }
        if (mockHuddle) {
          mockHuddle.state.phase = "connected";
          persistMockHuddle();
          await emitMockHuddleState();
        }
        return { ephemeral_channel_id: ephemeralChannelId };
      }
      case "confirm_huddle_active":
        if (mockHuddle) {
          mockHuddle.state.phase = "active";
          persistMockHuddle();
          await emitMockHuddleState();
        }
        return null;
      case "open_huddle_window":
        if ((activeConfig?.mock?.openHuddleWindowDelayMs ?? 0) > 0) {
          await new Promise((resolve) =>
            window.setTimeout(
              resolve,
              activeConfig?.mock?.openHuddleWindowDelayMs ?? 0,
            ),
          );
        }
        return null;
      case "close_huddle_companion":
        await emit("huddle-companion-returned", null);
        return null;
      case "leave_huddle":
      case "end_huddle":
        mockHuddle = null;
        window.sessionStorage.removeItem(MOCK_HUDDLE_STORAGE_KEY);
        // Deliberately leave the unarchived backing channel in the mock list.
        // This models relay/cache propagation lag and proves the UI removes it
        // at the local huddle lifecycle boundary rather than waiting for data.
        await emit("huddle-state-changed", {
          phase: "idle",
          parent_channel_id: null,
          ephemeral_channel_id: null,
          huddle_thread_event_id: null,
          participants: [],
          agent_pubkeys: [],
          agent_voice_settings: {},
          tts_enabled: false,
          transcription_enabled: false,
          is_creator: false,
          voice_input_mode: "push_to_talk",
        });
        return null;
      case "set_huddle_transcription_enabled":
        if (!mockHuddle) {
          throw new Error("No active mock huddle.");
        }
        mockHuddle.state.transcription_enabled = Boolean(
          (payload as { enabled?: boolean }).enabled,
        );
        persistMockHuddle();
        await emitMockHuddleState();
        return null;
      case "ensure_huddle_agent_voice_settings":
        refreshMockHuddleMembership(activeConfig);
        persistMockHuddle();
        return structuredClone(mockHuddle?.state.agent_voice_settings ?? {});
      case "set_huddle_agent_tts_enabled": {
        if (!mockHuddle) throw new Error("No active mock huddle.");
        const request = payload as { agentPubkey?: string; enabled?: boolean };
        if (!request.agentPubkey || typeof request.enabled !== "boolean") {
          throw new Error("Missing agent text-to-speech setting.");
        }
        refreshMockHuddleMembership(activeConfig);
        const settings =
          mockHuddle.state.agent_voice_settings[request.agentPubkey];
        if (!settings) throw new Error("Agent is not in the active huddle.");
        settings.enabled = request.enabled;
        persistMockHuddle();
        await emitMockHuddleState();
        return structuredClone(settings);
      }
      case "set_huddle_agent_voice": {
        if (!mockHuddle) throw new Error("No active mock huddle.");
        const request = payload as { agentPubkey?: string; voiceKey?: string };
        if (!request.agentPubkey || !request.voiceKey) {
          throw new Error("Missing agent voice setting.");
        }
        refreshMockHuddleMembership(activeConfig);
        const settings =
          mockHuddle.state.agent_voice_settings[request.agentPubkey];
        if (!settings) throw new Error("Agent is not in the active huddle.");
        settings.voice_key = request.voiceKey;
        persistMockHuddle();
        await emitMockHuddleState();
        return structuredClone(settings);
      }
      case "sync_agents_to_active_huddle": {
        const request = payload as {
          channelId?: string;
          agentPubkeys?: string[];
        };
        if (
          !mockHuddle ||
          !request.channelId ||
          (request.channelId !== mockHuddle.state.parent_channel_id &&
            request.channelId !== mockHuddle.state.ephemeral_channel_id)
        ) {
          return { matched_active_huddle: false, added: [] };
        }

        const existingAgents = new Set(
          mockHuddle.members
            .filter((member) => member.role === "bot")
            .map((member) => member.pubkey),
        );
        const added: string[] = [];
        for (const pubkey of request.agentPubkeys ?? []) {
          if (!pubkey || existingAgents.has(pubkey)) continue;
          existingAgents.add(pubkey);
          added.push(pubkey);
          mockHuddle.members.push({ pubkey, role: "bot" });
        }
        refreshMockHuddleMembership(activeConfig);
        persistMockHuddle();
        if (added.length > 0) {
          await emitMockHuddleState();
        }
        return { matched_active_huddle: true, added };
      }
      case "add_agent_to_huddle": {
        const error = activeConfig?.mock?.addAgentToHuddleError;
        if (error) {
          throw new Error(error);
        }
        const result = activeConfig?.mock?.addAgentToHuddleResult;
        if (!result) {
          throw new Error("No mock add-agent result configured.");
        }
        return structuredClone(result);
      }
      case "remove_agent_from_huddle": {
        const agentPubkey = (payload as { agentPubkey?: string })?.agentPubkey;
        if (!mockHuddle || !agentPubkey) {
          throw new Error("No active huddle agent to remove.");
        }
        const initialCount = mockHuddle.members.length;
        mockHuddle.members = mockHuddle.members.filter(
          (member) => member.role !== "bot" || member.pubkey !== agentPubkey,
        );
        if (mockHuddle.members.length === initialCount) {
          throw new Error("Agent is not in this huddle.");
        }
        refreshMockHuddleMembership(activeConfig);
        persistMockHuddle();
        await emitMockHuddleState();
        return;
      }
      case "get_model_status":
        return { stt: "ready", tts: "ready" };
      case "get_tts_settings":
        return (
          activeConfig?.mock?.ttsSettings ?? {
            version: 1,
            agentTextToSpeech: true,
            voicePreferences: ["pocket:mary"],
          }
        );
      case "list_voice_registry":
        return [
          ...[
            [
              "anna",
              "Anna",
              "anna.wav",
              "p228_023_enhanced.wav",
              "0a6de25cf12bf1540beb85979f306a92be81fecc051c547c5395e7e5237a3856",
            ],
            [
              "vera",
              "Vera",
              "vera.wav",
              "p229_023_enhanced.wav",
              "309cf91a895830f15842b398f69a4962cb1f7e0bfab10e25dd27838e826c204b",
            ],
            [
              "fantine",
              "Fantine",
              "fantine.wav",
              "p244_023_enhanced.wav",
              "5f07d4e2a3f20a15572aae885156b43ef3fc12ef3812996fd135680d9956448b",
            ],
            [
              "charles",
              "Charles",
              "charles.wav",
              "p254_023_enhanced.wav",
              "6b681a429198f16e378d53bccb08d06939da7b00144a7696111d4f8f76be7756",
            ],
            [
              "paul",
              "Paul",
              "paul.wav",
              "p259_023_enhanced.wav",
              "7aba504fe0b3b16478b69ed27ce6007e3cb42b0c1915b5f1c6a6024ae37d679b",
            ],
            [
              "eponine",
              "Eponine",
              "eponine.wav",
              "p262_023_enhanced.wav",
              "a13c27fb47627b05223691a0ef2974358a18c886e6c2f9d2762ff1d02c20926b",
            ],
            [
              "azelma",
              "Azelma",
              "azelma.wav",
              "p303_023_enhanced.wav",
              "60e3d26cdf2efdec5df712152c839928f4d5522821e6554ae11fd96c57ab1026",
            ],
            [
              "george",
              "George",
              "george.wav",
              "p315_023_enhanced.wav",
              "29a41f93bf5236e5b21501091d7774c255d5f3d4e62fa4f9fdf0a92a793c84ae",
            ],
            [
              "mary",
              "Mary",
              "reference_sample.wav",
              "p333_023_enhanced.wav",
              "a35b0468382218e9f37a9a7494d1e4b74deaf18d7ced22265b4e325bb55c183f",
            ],
            [
              "jane",
              "Jane",
              "jane.wav",
              "p339_023_enhanced.wav",
              "2f12e7f155eb3118f55425394f1b049e5b1b67bdc9b3932c8ba4521420aeb84a",
            ],
            [
              "michael",
              "Michael",
              "michael.wav",
              "p360_023_enhanced.wav",
              "b6743e9195e5e3fd34fe9d1633ae93f7ffab787b249e45f6467d7d6f7a6ee6ad",
            ],
            [
              "eve",
              "Eve",
              "eve.wav",
              "p361_023_enhanced.wav",
              "396e7cbd066b0f3fb6d67fa26e7904076958239d736d4390f15b5fe88feb14cd",
            ],
          ].map(
            ([id, displayName, referenceFile, upstreamFile, contentHash]) => ({
              key: `pocket:${id}`,
              displayName,
              backend: "pocket",
              backendName: "Pocket TTS",
              availability: "bundled",
              fallbackKey: id === "mary" ? null : "pocket:mary",
              referenceFile,
              provenance: {
                source: "bundled",
                contentHash,
                license: "CC-BY-4.0",
                sourceUrl: `https://huggingface.co/kyutai/tts-voices/blob/323332d33f997de8394f24a193e1a76df720e01a/vctk/${upstreamFile}`,
              },
            }),
          ),
          ...mockImportedVoices,
        ];
      case "set_tts_enabled": {
        const enabled = (payload as { enabled?: boolean })?.enabled;
        if (typeof enabled !== "boolean")
          throw new Error("Missing text-to-speech enabled state");
        const settings = {
          version: 1,
          agentTextToSpeech: enabled,
          voicePreferences: activeConfig?.mock?.ttsSettings
            ?.voicePreferences ?? ["pocket:mary"],
        };
        if (activeConfig) {
          activeConfig.mock ??= {};
          activeConfig.mock.ttsSettings = settings;
        }
        return settings;
      }
      case "interrupt_huddle_speech":
        return;
      case "set_pocket_voice": {
        const voiceKey = (payload as { voiceKey?: string })?.voiceKey;
        if (!voiceKey) throw new Error("Missing Pocket voice key");
        const current = activeConfig?.mock?.ttsSettings ?? {
          version: 1,
          agentTextToSpeech: true,
          voicePreferences: ["pocket:mary"],
        };
        const firstPocketIndex = current.voicePreferences.findIndex((key) =>
          key.startsWith("pocket:"),
        );
        const preferences = current.voicePreferences.filter(
          (key) => !key.startsWith("pocket:"),
        );
        preferences.splice(
          firstPocketIndex < 0 ? preferences.length : firstPocketIndex,
          0,
          voiceKey,
        );
        const settings = { ...current, voicePreferences: preferences };
        if (activeConfig) {
          activeConfig.mock ??= {};
          activeConfig.mock.ttsSettings = settings;
        }
        return settings;
      }
      case "preview_pocket_voice":
        return null;
      case "import_pocket_voice": {
        const importResult =
          activeConfig?.mock?.pocketVoiceImportResult ?? "success";
        if (importResult === "cancel") return null;
        if (importResult === "invalid") {
          throw new Error("Voice WAV must contain PCM or 32-bit float audio");
        }
        const contentHash = "1".repeat(64);
        const imported = {
          key: `pocket:imported:${contentHash}`,
          displayName: "My voice",
          backend: "pocket",
          backendName: "Pocket TTS",
          availability: "installed" as const,
          fallbackKey: "pocket:mary",
          referenceFile: `${contentHash}.wav`,
          provenance: {
            source: "local import",
            contentHash,
            license: null,
            sourceUrl: null,
          },
        };
        mockImportedVoices = [imported];
        const current = activeConfig?.mock?.ttsSettings ?? {
          version: 1,
          agentTextToSpeech: true,
          voicePreferences: ["pocket:mary"],
        };
        const settings = {
          ...current,
          voicePreferences: [imported.key],
        };
        if (activeConfig) {
          activeConfig.mock ??= {};
          activeConfig.mock.ttsSettings = settings;
        }
        return {
          settings,
          registry: await handleMockCommand("list_voice_registry", null),
        };
      }
      case "delete_pocket_voice": {
        const voiceKey = (payload as { voiceKey?: string })?.voiceKey;
        if (!voiceKey?.startsWith("pocket:imported:"))
          throw new Error("Missing imported Pocket voice key");
        mockImportedVoices = mockImportedVoices.filter(
          (voice) => voice.key !== voiceKey,
        );
        const current = activeConfig?.mock?.ttsSettings ?? {
          version: 1,
          agentTextToSpeech: true,
          voicePreferences: ["pocket:mary"],
        };
        const settings = {
          ...current,
          voicePreferences: current.voicePreferences.includes(voiceKey)
            ? ["pocket:mary"]
            : current.voicePreferences,
        };
        if (activeConfig) {
          activeConfig.mock ??= {};
          activeConfig.mock.ttsSettings = settings;
        }
        return {
          settings,
          registry: await handleMockCommand("list_voice_registry", null),
        };
      }
      case "get_builderlab_auth":
        return activeConfig?.mock?.builderlabAuth ?? null;
      case "start_builderlab_login": {
        const delayMs = activeConfig?.mock?.builderlabLoginDelayMs ?? 0;
        if (delayMs > 0)
          await new Promise((resolve) => window.setTimeout(resolve, delayMs));
        const nextAuth = activeConfig?.mock?.builderlabAuth ?? {
          email: "owner@example.com",
          expiresAt: "2099-01-01T00:00:00Z",
        };
        if (activeConfig?.mock) activeConfig.mock.builderlabAuth = nextAuth;
        return nextAuth;
      }
      case "cancel_builderlab_login":
        return null;
      case "clear_builderlab_auth":
        if (activeConfig?.mock) activeConfig.mock.builderlabAuth = null;
        return null;
      case "get_builderlab_nostr_identity":
        return activeConfig?.mock?.builderlabIdentity
          ? { identity: activeConfig.mock.builderlabIdentity }
          : { error: { code: "missing_mapping", setup_needed: true } };
      case "bind_builderlab_nostr_identity": {
        if (activeConfig?.mock?.builderlabBindError)
          return { error: activeConfig.mock.builderlabBindError };
        const activeIdentity = identity ?? DEFAULT_MOCK_IDENTITY;
        const nextIdentity = {
          pubkey_hex: activeIdentity.pubkey,
          npub: `npub1${activeIdentity.pubkey}`,
        };
        if (activeConfig?.mock)
          activeConfig.mock.builderlabIdentity = nextIdentity;
        return { identity: nextIdentity };
      }
      case "delete_builderlab_nostr_identity":
        if (activeConfig?.mock) activeConfig.mock.builderlabIdentity = null;
        return {};
      case "list_builderlab_communities":
        return {
          communities: activeConfig?.mock?.builderlabCommunities ?? [],
        };
      case "check_builderlab_community_name":
        return {
          available: true,
          normalized_host: `${(payload as { name?: string })?.name ?? "community"}.communities.buzz.xyz`,
        };
      case "create_builderlab_community": {
        const name = (payload as { name?: string })?.name ?? "community";
        return {
          community: activeConfig?.mock?.builderlabCreatedCommunity ?? {
            id: `hosted-${name}`,
            name,
            normalized_host: `${name}.communities.buzz.xyz`,
          },
        };
      }
      case "mesh_installed_models":
        return mockMeshState.models;
      case "mesh_model_catalog":
        return {
          gpuName: "Mock Apple GPU",
          vramDisplay: "32 GB",
          vramGb: 32,
          recommended: "Gemma-4-E4B-it-Q4_K_M",
          entries: [
            {
              name: "Gemma-4-E4B-it-Q4_K_M",
              size: "3.5GB",
              sizeGb: 3.5,
              description: "Buzz-curated local agent model",
              fit: "comfortable",
              installed: true,
              recommended: true,
              curated: true,
            },
          ],
        };
      case "mesh_node_status":
        return meshNodeStatus(mockMeshState.nodeState, mockMeshState.nodeMode);
      case "mesh_serving_usage":
        return mockMeshState.servingUsage;
      case "mesh_start_node": {
        const req = (
          payload as {
            request?: { mode?: "serve" | "client"; modelId?: string };
          } | null
        )?.request;
        mockMeshState.nodeState = "running";
        mockMeshState.nodeMode = req?.mode ?? "serve";
        mockMeshState.activeModel = req?.modelId
          ? { id: req.modelId, name: meshModelName(req.modelId) }
          : (mockMeshState.models[0] ?? null);
        return meshNodeStatus(mockMeshState.nodeState, mockMeshState.nodeMode);
      }
      case "mesh_stop_node":
        // Mirror the backend contract: "stop sharing" must never tear down a
        // client (consume) node occupying the single slot. Leave it running.
        if (mockMeshState.nodeMode === "client") {
          return meshNodeStatus(
            mockMeshState.nodeState,
            mockMeshState.nodeMode,
          );
        }
        mockMeshState.nodeState = "off";
        mockMeshState.nodeMode = null;
        mockMeshState.activeModel = null;
        return meshNodeStatus("off", null);
      case "get_identity": {
        const isLost =
          !mockIdentityLostCleared && activeConfig?.mock?.identityLost === true;
        const isLocked =
          !mockIdentityLockedCleared &&
          activeConfig?.mock?.identityLocked === true;
        if (identity) {
          return {
            pubkey: identity.pubkey,
            display_name: identity.username,
            lost: false,
            locked: false,
          };
        }

        return { ...DEFAULT_MOCK_IDENTITY, lost: isLost, locked: isLocked };
      }
      case "sign_nostr_identity_binding": {
        const request = payload as {
          challengeId: string;
          expiresAt: string;
          nonce: string;
          origin: string;
          verificationCode: string;
        };
        const signDelayMs = activeConfig?.mock?.nostrBindSignDelayMs ?? 0;
        if (signDelayMs > 0) {
          await new Promise((resolve) => setTimeout(resolve, signDelayMs));
        }
        const activeIdentity = identity ?? DEFAULT_MOCK_IDENTITY;
        return JSON.stringify({
          id: "e2e-signed-nostr-binding",
          pubkey: activeIdentity.pubkey,
          created_at: 0,
          kind: 24243,
          tags: [
            ["challenge_id", request.challengeId],
            ["nonce", request.nonce],
            ["verification_code", request.verificationCode],
            ["origin", request.origin],
            ["expires_at", request.expiresAt],
          ],
          content: "",
          sig: "e2e-signed-nostr-binding",
        });
      }
      case "sign_out":
        // Production wipes local state and restarts the app. In the browser
        // harness there is nothing to wipe; resolving is enough — specs
        // assert invocation via __BUZZ_E2E_COMMANDS__ and the pending UI.
        return;
      case "generate_backup_passphrase": {
        const request = payload as {
          words?: number;
          separator?: string;
        } | null;
        const wordCount = Math.min(Math.max(request?.words ?? 3, 3), 10);
        return MOCK_PASSPHRASE_WORDS.slice(0, wordCount).join(
          request?.separator ?? " ",
        );
      }
      case "create_ncryptsec_backup": {
        const delayMs = activeConfig?.mock?.backupEncryptionDelayMs ?? 0;
        if (delayMs > 0) {
          await new Promise((resolve) => setTimeout(resolve, delayMs));
        }
        return MOCK_NCRYPTSEC;
      }
      case "save_ncryptsec_copy": {
        const paths = activeConfig?.mock?.backupSavePaths ?? [
          "/tmp/buzz-identity.ncryptsec",
        ];
        const index = Math.min(backupSaveCallCount, paths.length - 1);
        backupSaveCallCount += 1;
        return paths[index];
      }
      case "verify_ncryptsec_backup": {
        const request = payload as { password?: string } | null;
        const configuredErrors = activeConfig?.mock?.backupVerificationErrors;
        const hasConfiguredResult = Boolean(
          activeConfig?.mock?.backupVerificationPubkeys,
        );
        const errors = configuredErrors ?? [
          hasConfiguredResult || request?.password === MOCK_BACKUP_PASSPHRASE
            ? null
            : "wrong backup password or damaged key backup",
        ];
        const index = Math.min(backupVerificationCallCount, errors.length - 1);
        const error = errors[index];
        if (error) {
          backupVerificationCallCount += 1;
          throw new Error(error);
        }
        const pubkeys = activeConfig?.mock?.backupVerificationPubkeys ?? [
          identity?.pubkey ?? DEFAULT_MOCK_IDENTITY.pubkey,
        ];
        const pubkey = pubkeys[Math.min(index, pubkeys.length - 1)];
        backupVerificationCallCount += 1;
        return {
          pubkey,
          npub: npubEncode(pubkey),
          matchesCurrentIdentity:
            pubkey === (identity?.pubkey ?? DEFAULT_MOCK_IDENTITY.pubkey),
        };
      }
      case "get_nsec": {
        const nsecSequence = activeConfig?.mock?.nsecErrors;
        if (nsecSequence && nsecSequence.length > 0) {
          const idx = Math.min(nsecCallCount, nsecSequence.length - 1);
          nsecCallCount++;
          const entry = nsecSequence[idx];
          if (entry !== null) {
            throw new Error(entry);
          }
          return "nsec1mock000000000000000000000000000000000000000000000000000000";
        }
        const nsecError = activeConfig?.mock?.nsecError;
        if (nsecError) {
          throw new Error(nsecError);
        }
        return "nsec1mock000000000000000000000000000000000000000000000000000000";
      }
      case "persist_current_identity": {
        // Persist the ephemeral key: clears only the lost flag. The locked flag
        // is cleared only by import_identity; production rejects
        // persist_current_identity when the identity is in the locked state.
        mockIdentityLostCleared = true;
        const currentPubkey = identity?.pubkey ?? DEFAULT_MOCK_IDENTITY.pubkey;
        const currentDisplayName =
          identity?.username ?? DEFAULT_MOCK_IDENTITY.display_name;
        return {
          pubkey: currentPubkey,
          display_name: currentDisplayName,
          lost: false,
          locked: false,
        };
      }
      case "import_identity": {
        const importDelayMs = activeConfig?.mock?.identityImportDelayMs ?? 0;
        if (importDelayMs > 0) {
          await new Promise((resolve) =>
            window.setTimeout(resolve, importDelayMs),
          );
        }
        const request = payload as { nsec?: string; password?: string } | null;
        const input = request?.nsec ?? "";
        if (input.trim().startsWith("ncryptsec1")) {
          if (
            input.trim() !== MOCK_NCRYPTSEC ||
            request?.password !== MOCK_BACKUP_PASSPHRASE
          ) {
            throw new Error("Wrong backup password or damaged key backup.");
          }
          mockIdentityLostCleared = true;
          mockIdentityLockedCleared = true;
          return importMockIdentity(
            nsecEncode(hexToBytes(DEFAULT_REAL_IDENTITY.privateKey)),
          );
        }
        mockIdentityLostCleared = true;
        mockIdentityLockedCleared = true;
        return importMockIdentity(input);
      }
      case "validate_repos_dir":
        // The browser harness has no host filesystem to validate. Treat the
        // seeded empty/default path as valid so Add Community can continue to
        // relay-policy discovery.
        return;
      case "fetch_join_policy":
        return activeConfig?.mock?.joinPolicy ?? null;
      case "fetch_link_preview_metadata": {
        if (activeConfig?.mock?.deferLinkPreviewMetadata) {
          await new Promise<void>((resolve) => {
            deferredLinkPreviewMetadataQueue.push(resolve);
          });
        }
        const startBlockMs =
          activeConfig?.mock?.linkPreviewMetadataStartBlockMs ?? 0;
        if (startBlockMs > 0) {
          const stopAt = performance.now() + startBlockMs;
          while (performance.now() < stopAt) {
            // Deliberately block to model uncached native command startup.
          }
        }
        const delayMs = activeConfig?.mock?.linkPreviewMetadataDelayMs ?? 0;
        if (delayMs > 0) {
          await new Promise((resolve) => window.setTimeout(resolve, delayMs));
        }
        const href = (payload as { href?: string }).href;
        const metadataByHref = activeConfig?.mock?.linkPreviewMetadataByHref;
        if (href && metadataByHref && Object.hasOwn(metadataByHref, href)) {
          return metadataByHref[href];
        }
        return activeConfig?.mock?.linkPreviewMetadata ?? null;
      }
      case "apply_workspace": {
        const applyDelayMs = activeConfig?.mock?.applyCommunityDelayMs ?? 0;
        if (applyDelayMs > 0) {
          return new Promise((resolve) =>
            window.setTimeout(resolve, applyDelayMs),
          );
        }
        return;
      }
      case "update_tray_agent_activity":
      case "clear_tray_agent_activity":
      case "requeue_tray_actions":
        return null;
      case "take_tray_actions":
        return [];
      case "get_profile":
        return handleGetProfile(activeConfig);
      case "update_profile":
        return handleUpdateProfile(
          payload as Parameters<typeof handleUpdateProfile>[0],
          activeConfig,
        );
      case "update_profile_at_relay":
        return handleUpdateProfile(
          {
            avatarUrl: (payload as { avatarUrl: string }).avatarUrl,
          },
          activeConfig,
        );
      case "get_user_profile":
        return handleGetUserProfile(
          (payload as Parameters<typeof handleGetUserProfile>[0]) ?? {},
          activeConfig,
        );
      case "get_users_batch":
        return handleGetUsersBatch(
          payload as Parameters<typeof handleGetUsersBatch>[0],
          activeConfig,
        );
      case "get_user_notes":
        return handleGetUserNotes(
          payload as Parameters<typeof handleGetUserNotes>[0],
          activeConfig,
        );
      case "get_global_notes":
        return handleGetGlobalNotes(
          payload as Parameters<typeof handleGetGlobalNotes>[0],
          activeConfig,
        );
      case "get_notes_timeline":
        return handleGetNotesTimeline(
          payload as Parameters<typeof handleGetNotesTimeline>[0],
        );
      case "get_note":
        return handleGetNote(payload as Parameters<typeof handleGetNote>[0]);
      case "get_note_reactions":
        return handleGetNoteReactions();
      case "get_liked_notes":
        return handleGetLikedNotes();
      case "search_users":
        return handleSearchUsers(
          payload as Parameters<typeof handleSearchUsers>[0],
          activeConfig,
        );
      case "get_presence":
        return handleGetPresence(
          (payload as Parameters<typeof handleGetPresence>[0]) ?? {
            pubkeys: [],
          },
          activeConfig,
        );
      case "get_os_idle_seconds":
        // e2e runs headless with no OS idle API; the presence hook falls back
        // to in-app activity tracking.
        return null;
      case "get_git_identity":
        // Matches the "Thomas P" author on a mock snapshot commit so the
        // viewer-identity avatar attribution is exercised in e2e.
        return { name: "Thomas P", email: "thomasp@example.com" };
      case "get_project_repo_snapshot":
        return {
          latest_commit: {
            hash: "0123456789abcdef0123456789abcdef01234567",
            short_hash: "0123456",
            author_name: "Brain",
            author_email: "brain@example.com",
            timestamp: Math.floor(Date.now() / 1000) - 600,
            subject: "Add Trello board workflow details",
          },
          commits: [
            {
              hash: "0123456789abcdef0123456789abcdef01234567",
              short_hash: "0123456",
              author_name: "Brain",
              author_email: "brain@example.com",
              timestamp: Math.floor(Date.now() / 1000) - 600,
              subject: "Add Trello board workflow details",
            },
            {
              hash: "123456789abcdef0123456789abcdef012345678",
              short_hash: "1234567",
              author_name: "Thomas P",
              author_email: "thomasp@example.com",
              timestamp: Math.floor(Date.now() / 1000) - 1_800,
              subject: "Point project repository details at active branch",
            },
            {
              hash: "23456789abcdef0123456789abcdef0123456789",
              short_hash: "2345678",
              author_name: "Brain",
              author_email: "brain@example.com",
              timestamp: Math.floor(Date.now() / 1000) - 3_600,
              subject: "Make project repository-first",
            },
            {
              hash: "3456789abcdef0123456789abcdef0123456789a",
              short_hash: "3456789",
              author_name: "Git Importer",
              author_email: "git-importer@example.com",
              timestamp: Math.floor(Date.now() / 1000) - 7_200,
              subject: "Merge remote project history into local community",
            },
          ],
          contributors: [
            {
              name: "Brain",
              email: "brain@example.com",
              commit_count: 8,
              last_commit_at: Math.floor(Date.now() / 1000) - 600,
            },
            {
              name: "Thomas P",
              email: "thomasp@example.com",
              commit_count: 3,
              last_commit_at: Math.floor(Date.now() / 1000) - 1_800,
            },
            {
              name: "Git Importer",
              email: "git-importer@example.com",
              commit_count: 1,
              last_commit_at: Math.floor(Date.now() / 1000) - 7_200,
            },
          ],
          files: [
            {
              path: "desktop/src/features/projects/ui/ProjectDetailScreen.tsx",
              kind: "blob",
              size: 18420,
              preview_content:
                'export function ProjectDetailScreen() {\n  return <CommunityTabs defaultValue="files" />;\n}\n',
            },
            {
              path: "desktop/src/features/projects/ui/ProjectsView.tsx",
              kind: "blob",
              size: 16412,
              preview_content:
                "export function ProjectsView() {\n  return <ProjectsToolbar />;\n}\n",
            },
            {
              path: "desktop/src/features/projects/hooks.ts",
              kind: "blob",
              size: 9520,
              preview_content:
                "export function useProjectRepoSnapshotQuery(project) {\n  return useQuery({ queryKey: [project.id, 'repo-snapshot'] });\n}\n",
            },
            {
              path: "crates/buzz-relay/src/api/git/transport.rs",
              kind: "blob",
              size: 33120,
              preview_content:
                "// Smart HTTP git transport\n// Handles upload-pack and receive-pack for Buzz git repos.\n",
            },
          ],
        };
      case "get_project_local_repo_snapshot":
        return null;
      case "get_project_repo_diff":
        return {
          additions: 27,
          deletions: 4,
          commit_body: [
            "See the [project guide](https://example.com/project-guide).",
            "",
            "![Architecture](/buzz.svg)",
            "",
            "![Demo](https://example.com/project-demo.mp4)",
          ].join("\n"),
          files: [
            {
              path: "desktop/src/features/projects/ui/ProjectDetailScreen.tsx",
              additions: 18,
              deletions: 3,
              patch: [
                "@@ -1,6 +1,8 @@",
                ' import { Tabs } from "@/shared/ui/tabs";',
                "",
                "-function CommunityTabs() {",
                "+function CommunityTabs({ selectedCommitHash }) {",
                '+  const [selectedTab, setSelectedTab] = useState("overview");',
                "+",
                "   return (",
                '     <Tabs value="overview">',
                "       <ProjectTabsList />",
              ].join("\n"),
              truncated: false,
            },
            {
              path: "desktop/src/features/projects/hooks.ts",
              additions: 9,
              deletions: 1,
              patch: [
                "@@ -10,4 +10,12 @@",
                " export function useProjectQuery(projectId) {",
                "   return useQuery({ queryKey: [projectId] });",
                " }",
                "+",
                "+export function useProjectCommitDiffQuery(project, hash) {",
                '+  return useQuery({ queryKey: [project?.id, "commit-diff", hash] });',
                "+}",
              ].join("\n"),
              truncated: false,
            },
          ],
        };
      case "get_project_local_repo_diff":
        return null;
      case "get_project_repo_sync_status":
        return (
          window.__BUZZ_E2E_PROJECT_REPO_SYNC_STATUS__ ?? {
            local_path: null,
            local_branch: null,
            local_branches: [],
            local_head: null,
            local_short_head: null,
            remote_branch: "main",
            remote_head: "0123456789abcdef0123456789abcdef01234567",
            remote_short_head: "0123456",
            merge_base: "0123456789abcdef0123456789abcdef01234567",
            ahead_count: 0,
            behind_count: 0,
            has_uncommitted_changes: false,
            has_untracked_files: false,
            can_push: false,
            push_block_reason: "No local checkout found.",
            can_pull: false,
            pull_block_reason: "No local checkout found.",
          }
        );
      case "list_project_local_repositories":
        return [];
      case "push_project_local_repository": {
        const input = payload as { branchName?: string | null };
        const status = window.__BUZZ_E2E_PROJECT_REPO_SYNC_STATUS__;
        const branch = input.branchName ?? status?.remote_branch ?? "main";
        const commit =
          status?.local_head ?? "0123456789abcdef0123456789abcdef01234567";
        if (status) {
          status.remote_branch = branch;
          status.remote_head = commit;
          status.remote_short_head = commit.slice(0, 7);
          status.ahead_count = 0;
          status.can_push = false;
          status.push_block_reason = "Local branch is already pushed.";
        }
        return {
          pushed: true,
          message: `Pushed ${branch} to remote.`,
          branch,
          commit,
          merge_base:
            status?.merge_base ?? "0123456789abcdef0123456789abcdef01234567",
        };
      }
      case "pull_project_local_repository":
        return {
          pulled: true,
          message: "Pulled main from remote.",
        };
      case "clone_project_repository": {
        const path = "/tmp/buzz/REPOS/mock-project";
        const commit = "0123456789abcdef0123456789abcdef01234567";
        window.__BUZZ_E2E_PROJECT_REPO_SYNC_STATUS__ = {
          local_path: path,
          local_branch: "main",
          local_branches: ["main"],
          local_head: commit,
          local_short_head: commit.slice(0, 7),
          remote_branch: "main",
          remote_head: commit,
          remote_short_head: commit.slice(0, 7),
          merge_base: commit,
          ahead_count: 0,
          behind_count: 0,
          has_uncommitted_changes: false,
          has_untracked_files: false,
          can_push: false,
          push_block_reason: "Local branch is already pushed.",
          can_pull: false,
          pull_block_reason: "Local branch is up to date.",
        };
        return {
          path,
          cloned: true,
          message: "Cloned repository.",
        };
      }
      case "create_project_remote_branch": {
        const input = payload as {
          cloneUrl: string;
          expectedCommit: string;
          newBranch: string;
          sourceBranch: string;
        };
        const dtag = new URL(input.cloneUrl).pathname
          .split("/")
          .filter(Boolean)
          .at(-1)
          ?.replace(/\.git$/, "");
        const repoState = getMockProjectEventStore().find(
          (event) =>
            event.kind === KIND_REPO_STATE &&
            event.tags.some((tag) => tag[0] === "d" && tag[1] === dtag),
        );
        if (repoState) {
          repoState.tags = repoState.tags.filter(
            (tag) => tag[0] !== `refs/heads/${input.newBranch}`,
          );
          repoState.tags.push([
            `refs/heads/${input.newBranch}`,
            input.expectedCommit,
          ]);
          repoState.created_at = Math.floor(Date.now() / 1000);
        }
        if (dtag) {
          writeMockProjectBranch(dtag, input.newBranch, input.expectedCommit);
        }
        return {
          branch: input.newBranch,
          commit: input.expectedCommit,
          message: `Created branch ${input.newBranch} from ${input.sourceBranch}.`,
        };
      }
      case "delete_project_remote_branch": {
        const input = payload as {
          branch: string;
          cloneUrl: string;
          expectedCommit: string;
        };
        const dtag = new URL(input.cloneUrl).pathname
          .split("/")
          .filter(Boolean)
          .at(-1)
          ?.replace(/\.git$/, "");
        const repoState = getMockProjectEventStore().find(
          (event) =>
            event.kind === KIND_REPO_STATE &&
            event.tags.some((tag) => tag[0] === "d" && tag[1] === dtag),
        );
        if (repoState) {
          repoState.tags = repoState.tags.filter(
            (tag) => tag[0] !== `refs/heads/${input.branch}`,
          );
          repoState.created_at = Math.floor(Date.now() / 1000);
        }
        if (dtag) {
          writeMockProjectBranch(dtag, input.branch, null);
        }
        return {
          branch: input.branch,
          commit: input.expectedCommit,
          message: `Deleted branch ${input.branch}.`,
        };
      }
      case "publish_project_owner_announcement": {
        const { input } = payload as {
          input: {
            content: string;
            createdAt?: number;
            kind: number;
            tags: string[][];
            targetOwner: string;
          };
        };
        const normalizedTargetOwner = input.targetOwner.toLowerCase();
        const canSignAsOwner =
          (identity?.pubkey ?? MOCK_IDENTITY_PUBKEY).toLowerCase() ===
            normalizedTargetOwner ||
          mockManagedAgents.some(
            (agent) => agent.pubkey.toLowerCase() === normalizedTargetOwner,
          );
        if (!canSignAsOwner) {
          throw new Error(
            "Only the owner identity or the owner of its managed agent can perform this action.",
          );
        }
        const event = createMockEvent(
          input.kind,
          input.content,
          input.tags,
          normalizedTargetOwner,
          input.createdAt,
        );
        window.__BUZZ_E2E_SIGNED_EVENTS__?.push({
          content: event.content,
          createdAt: event.created_at,
          kind: event.kind,
          tags: event.tags,
        });

        let publicationError: string | null = null;
        if (
          event.kind === KIND_PROJECT_ANNOUNCEMENT &&
          window.__BUZZ_E2E_UNSUPPORTED_PROJECT_ANNOUNCEMENTS__
        ) {
          publicationError = "restricted: unknown event kind";
        } else {
          const rejectionIndex =
            window.__BUZZ_E2E_REJECT_PROJECT_EVENT_KINDS__?.indexOf(
              event.kind,
            ) ?? -1;
          if (rejectionIndex >= 0) {
            window.__BUZZ_E2E_REJECT_PROJECT_EVENT_KINDS__?.splice(
              rejectionIndex,
              1,
            );
            publicationError = "mock project event rejection";
          } else {
            getMockProjectEventStore().push(event);
            const acceptedProjectEvents =
              window.__BUZZ_E2E_ACCEPTED_PROJECT_EVENTS__ ?? [];
            acceptedProjectEvents.push({
              content: event.content,
              kind: event.kind,
              tags: event.tags,
            });
            window.__BUZZ_E2E_ACCEPTED_PROJECT_EVENTS__ = acceptedProjectEvents;
            const failedAckIndex =
              window.__BUZZ_E2E_FAIL_PROJECT_EVENT_ACK_KINDS__?.indexOf(
                event.kind,
              ) ?? -1;
            if (failedAckIndex >= 0) {
              window.__BUZZ_E2E_FAIL_PROJECT_EVENT_ACK_KINDS__?.splice(
                failedAckIndex,
                1,
              );
              publicationError = "mock lost project acknowledgement";
            }
          }
        }
        return {
          event: JSON.stringify(event),
          publication_error: publicationError,
        };
      }
      case "sign_project_pull_request_status": {
        const { input } = payload as {
          input: {
            createdAt: number;
            pullRequestAuthor: string;
            pullRequestId: string;
            repoAddress: string;
            status: "open" | "draft" | "closed";
            targetOwner: string;
          };
        };
        const kind = {
          open: KIND_GIT_STATUS_OPEN,
          draft: KIND_GIT_STATUS_DRAFT,
          closed: KIND_GIT_STATUS_CLOSED,
        }[input.status];
        const recipientPubkeys = Array.from(
          new Set(
            [input.targetOwner, input.pullRequestAuthor].map((pubkey) =>
              pubkey.trim().toLowerCase(),
            ),
          ),
        );
        const event = createMockEvent(
          kind,
          "",
          [
            ["e", input.pullRequestId, "", "root"],
            ["a", input.repoAddress],
            ...recipientPubkeys.map((pubkey) => ["p", pubkey]),
          ],
          input.targetOwner,
          input.createdAt,
        );
        window.__BUZZ_E2E_SIGNED_EVENTS__?.push(event);
        getMockProjectEventStore().push(event);
        return null;
      }
      case "sign_project_pull_request_review_request": {
        const { input } = payload as {
          input: {
            pullRequestId: string;
            repoAddress: string;
            reviewerLabel: string;
            reviewers: string[];
            targetOwner: string;
          };
        };
        const event = createMockEvent(
          KIND_TEXT_NOTE,
          `Requested a review from ${input.reviewerLabel}`,
          [
            ["e", input.pullRequestId, "", "root"],
            ["a", input.repoAddress],
            ...input.reviewers.map((reviewer) => ["p", reviewer]),
            ["t", "review-request"],
          ],
          input.targetOwner,
        );
        window.__BUZZ_E2E_SIGNED_EVENTS__?.push({
          content: event.content,
          kind: event.kind,
          tags: event.tags,
        });
        getMockProjectEventStore().push(event);
        return null;
      }
      case "publish_project_pull_request_merged_status": {
        const { input } = payload as {
          input: { statusEvent: string; targetOwner: string };
        };
        const event = JSON.parse(input.statusEvent) as RelayEvent;
        if (event.pubkey !== input.targetOwner) {
          throw new Error("mock merged status owner mismatch");
        }
        getMockProjectEventStore().push(event);
        return null;
      }
      case "merge_project_pull_request": {
        const { input } = payload as {
          input: {
            expectedCommit: string;
            pullRequestAuthor: string;
            pullRequestId: string;
            repoAddress: string;
            sourceBranch: string;
            statusCreatedAt: number;
            targetBranch: string;
            targetOwner: string;
          };
        };
        const normalizedTargetOwner = input.targetOwner.toLowerCase();
        const canSignAsOwner =
          (identity?.pubkey ?? MOCK_IDENTITY_PUBKEY).toLowerCase() ===
            normalizedTargetOwner ||
          mockManagedAgents.some(
            (agent) => agent.pubkey.toLowerCase() === normalizedTargetOwner,
          );
        if (!canSignAsOwner) {
          throw new Error(
            "Only the repository owner or the owner of its managed agent can merge pull requests.",
          );
        }
        if (window.__BUZZ_E2E_PROJECT_MERGE_ERROR__) {
          throw window.__BUZZ_E2E_PROJECT_MERGE_ERROR__;
        }
        const mergeCommit = "abcdef0123456789abcdef0123456789abcdef01";
        const statusEvent = createMockEvent(
          KIND_GIT_STATUS_MERGED,
          "",
          [
            ["e", input.pullRequestId, "", "root"],
            ["a", input.repoAddress],
            ["p", input.targetOwner],
            ["p", input.pullRequestAuthor],
            ["merge-commit", mergeCommit],
            ["r", mergeCommit],
          ],
          input.targetOwner,
          input.statusCreatedAt,
        );
        window.__BUZZ_E2E_SIGNED_EVENTS__?.push({
          content: statusEvent.content,
          kind: statusEvent.kind,
          tags: statusEvent.tags,
        });
        const rejectionIndex =
          window.__BUZZ_E2E_REJECT_PROJECT_EVENT_KINDS__?.indexOf(
            statusEvent.kind,
          ) ?? -1;
        let statusPublicationError: string | null = null;
        if (rejectionIndex >= 0) {
          window.__BUZZ_E2E_REJECT_PROJECT_EVENT_KINDS__?.splice(
            rejectionIndex,
            1,
          );
          statusPublicationError = "mock project event rejection";
        } else {
          getMockProjectEventStore().push(statusEvent);
        }
        return {
          message: "Merged feature into main.",
          merge_commit: mergeCommit,
          status_event: JSON.stringify(statusEvent),
          status_publication_error: statusPublicationError,
        };
      }
      case "open_project_merge_recovery_terminal": {
        const { input } = payload as {
          input: { expectedCommit: string };
        };
        return {
          path: "/tmp/buzz/REPOS/buzz",
          cloned: false,
          recoveryRef: `refs/buzz/merge-recovery/${input.expectedCommit}`,
          targetRef: `refs/buzz/merge-recovery-target/${"f".repeat(40)}`,
        };
      }
      case "open_project_terminal":
        return {
          path: "/tmp/buzz/REPOS/buzz",
          cloned: false,
        };
      case "get_relay_ws_url":
        return getRelayWsUrl(activeConfig);
      case "get_default_relay_url":
        return getRelayWsUrl(activeConfig);
      case "auto_connect_default_relay_enabled":
        return activeConfig?.autoConnectDefaultRelay ?? false;
      case "get_legacy_workspace_storage":
        return {
          workspaces: null,
          activeWorkspaceId: null,
          onboardingCompletions: [],
        };
      case "take_pending_community_deep_link":
        // Mirrors the Rust queue: peek the head; acknowledge removes it.
        return mockPendingCommunityDeepLinks[0] ?? null;
      case "acknowledge_pending_community_deep_link": {
        const { id } = payload as { id: string };
        const index = mockPendingCommunityDeepLinks.findIndex(
          (pending) => pending.id === id,
        );
        if (index === -1) {
          return false;
        }
        mockPendingCommunityDeepLinks.splice(index, 1);
        return true;
      }
      case "clear_pending_navigation_deep_links":
        if (activeConfig?.mock?.clearPendingNavigationDeepLinksError) {
          throw new Error(
            activeConfig.mock.clearPendingNavigationDeepLinksError,
          );
        }
        mockPendingNavigationDeepLinks.length = 0;
        return;
      case "take_pending_navigation_deep_link":
        return mockPendingNavigationDeepLinks[0] ?? null;
      case "acknowledge_pending_navigation_deep_link": {
        const { id } = payload as { id: string };
        if (mockPendingNavigationDeepLinks[0]?.id !== id) return false;
        mockPendingNavigationDeepLinks.shift();
        return true;
      }
      case "take_pending_entity_deep_link":
        return mockPendingEntityDeepLinks[0] ?? null;
      case "acknowledge_pending_entity_deep_link": {
        const { id } = payload as { id: string };
        if (mockPendingEntityDeepLinks[0]?.id !== id) {
          return false;
        }
        mockPendingEntityDeepLinks.shift();
        return true;
      }
      case "get_relay_http_url":
        return getRelayHttpUrl(activeConfig);
      case "relay_requires_membership":
        return activeConfig?.mock?.relayRequiresMembership ?? false;
      case "discover_acp_providers":
        return handleDiscoverAcpRuntimes(activeConfig);
      case "save_custom_harness":
        return handleSaveCustomHarness(
          payload as Parameters<typeof handleSaveCustomHarness>[0],
        );
      case "delete_custom_harness":
        return handleDeleteCustomHarness(
          payload as Parameters<typeof handleDeleteCustomHarness>[0],
          activeConfig,
        );
      case "discover_acp_auth_methods":
        return handleDiscoverAcpAuthMethods(
          payload as { runtimeId?: string },
          activeConfig,
        );
      case "connect_acp_runtime":
        return handleConnectAcpRuntime(
          payload as { request?: { runtimeId?: string; methodId?: string } },
          activeConfig,
        );
      case "install_acp_runtime":
        return handleInstallAcpRuntime(
          payload as { runtimeId?: string },
          activeConfig,
        );
      case "discover_backend_providers":
        return activeConfig?.mock?.backendProviders ?? [];
      case "probe_backend_provider": {
        const probeDelayMs =
          activeConfig?.mock?.backendProviderProbeDelayMs ?? 0;
        if (probeDelayMs > 0) {
          await new Promise((resolve) =>
            window.setTimeout(resolve, probeDelayMs),
          );
        }
        return (
          activeConfig?.mock?.backendProviderProbeResult ?? {
            ok: false,
            error: "mock: no providers available",
          }
        );
      }
      case "discover_managed_agent_prereqs":
        return handleDiscoverManagedAgentPrereqs(
          payload as Parameters<typeof handleDiscoverManagedAgentPrereqs>[0],
          activeConfig,
        );
      case "get_channels": {
        // Claim the one-shot before starting the read, then hold only that
        // invocation's completion. Later reads pass through and cannot join it.
        const deferred = deferNextChannelsRead;
        deferNextChannelsRead = false;
        const channels = handleGetChannels(payload, activeConfig);
        if (deferred) {
          await new Promise<void>((resolve) => {
            deferredChannelsReadResolve = resolve;
            window.__BUZZ_E2E_CHANNELS_READ_PENDING__ = 1;
          });
        }
        return channels;
      }
      case "get_feed":
        return handleGetFeed(
          (payload as Parameters<typeof handleGetFeed>[0]) ?? {},
          activeConfig,
        );
      case "list_relay_agents":
        return handleListRelayAgents(activeConfig);
      case "list_personas":
        return handleListPersonas();
      case "create_persona":
        return handleCreatePersona(
          payload as Parameters<typeof handleCreatePersona>[0],
        );
      case "update_persona":
        return handleUpdatePersona(
          payload as Parameters<typeof handleUpdatePersona>[0],
        );
      case "update_persona_and_publish":
        return handleUpdatePersonaAndPublish(
          payload as Parameters<typeof handleUpdatePersonaAndPublish>[0],
          activeConfig,
        );
      case "delete_persona":
        return handleDeletePersona(
          payload as Parameters<typeof handleDeletePersona>[0],
        );
      case "reconcile_inbound_persona_event": {
        const nostrEvent = JSON.parse(
          (payload as { eventJson: string }).eventJson,
        ) as {
          kind: number;
          tags: string[][];
          content: string;
          created_at: number;
        };
        if (nostrEvent.kind === 30175) {
          // Persona upsert — parse content and upsert into mockPersonas by d-tag
          const dTag = nostrEvent.tags.find((t) => t[0] === "d")?.[1];
          if (dTag) {
            const content = JSON.parse(nostrEvent.content) as {
              display_name?: string;
              system_prompt?: string;
            };
            const now = new Date().toISOString();
            const existing = mockPersonas.find((p) => p.id === dTag);
            const shared = nostrEvent.tags.some(
              (tag) =>
                tag.length === 2 && tag[0] === "shared" && tag[1] === "true",
            );
            if (existing) {
              existing.display_name =
                content.display_name ?? existing.display_name;
              existing.system_prompt =
                content.system_prompt ?? existing.system_prompt;
              existing.shared = shared;
              existing.updated_at = now;
            } else {
              mockPersonas.push({
                id: dTag,
                display_name: content.display_name ?? dTag,
                avatar_url: null,
                system_prompt: content.system_prompt ?? "",
                is_builtin: false,
                is_active: true,
                shared,
                env_vars: {},
                created_at: now,
                updated_at: now,
              });
            }
          }
        } else if (nostrEvent.kind === 5) {
          // Tombstone — extract d-tag from a-tag "30175:<pubkey>:<d_tag>" and remove
          const aTagValue = nostrEvent.tags.find((t) => t[0] === "a")?.[1];
          if (aTagValue) {
            const dTag = aTagValue.split(":")[2];
            if (dTag) {
              mockPersonas = mockPersonas.filter((p) => p.id !== dTag);
            }
          }
        }
        // Mirror the real Rust backend: emit "agents-data-changed" after reconcile.
        await emit("agents-data-changed");
        return undefined;
      }
      case "set_persona_active":
        return handleSetPersonaActive(
          payload as Parameters<typeof handleSetPersonaActive>[0],
        );
      case "set_persona_shared":
        return handleSetPersonaShared(
          payload as Parameters<typeof handleSetPersonaShared>[0],
          activeConfig,
        );
      case "list_teams":
        return handleListTeams();
      case "list_channel_templates":
        return (activeConfig?.mock?.channelTemplates ?? []).map((template) => ({
          id: template.id,
          name: template.name,
          description: template.description,
          channel_type: template.channelType,
          visibility: template.visibility,
          canvas_template: template.canvasTemplate,
          agents: template.agents,
          is_builtin: template.isBuiltin,
          created_at: template.createdAt,
          updated_at: template.updatedAt,
        }));
      case "create_channel_template": {
        const { input } = payload as {
          input: {
            name: string;
            description?: string;
            channelType?: "stream" | "forum";
            visibility?: "open" | "private";
            canvasTemplate?: string;
            agents?: ChannelTemplate["agents"];
          };
        };
        const timestamp = new Date().toISOString();
        const created: ChannelTemplate = {
          id: `template-${Date.now()}`,
          name: input.name,
          description: input.description ?? null,
          channelType: input.channelType ?? "stream",
          visibility: input.visibility ?? "open",
          canvasTemplate: input.canvasTemplate ?? null,
          agents: input.agents ?? { personas: [], teams: [] },
          isBuiltin: false,
          createdAt: timestamp,
          updatedAt: timestamp,
        };
        if (activeConfig) {
          activeConfig.mock ??= {};
          activeConfig.mock.channelTemplates = [
            ...(activeConfig.mock.channelTemplates ?? []),
            created,
          ];
        }
        return {
          id: created.id,
          name: created.name,
          description: created.description,
          channel_type: created.channelType,
          visibility: created.visibility,
          canvas_template: created.canvasTemplate,
          agents: created.agents,
          is_builtin: created.isBuiltin,
          created_at: created.createdAt,
          updated_at: created.updatedAt,
        };
      }
      case "create_team":
        return handleCreateTeam(
          payload as Parameters<typeof handleCreateTeam>[0],
        );
      case "update_team":
        return handleUpdateTeam(
          payload as Parameters<typeof handleUpdateTeam>[0],
        );
      case "delete_team":
        return handleDeleteTeam(
          payload as Parameters<typeof handleDeleteTeam>[0],
        );
      case "export_team_to_json":
        return handleExportTeamToJson(payload as { id: string });
      case "pick_team_directory":
        return handlePickTeamDirectory();
      case "install_team_from_directory":
        return handleInstallTeamFromDirectory(
          payload as Parameters<typeof handleInstallTeamFromDirectory>[0],
        );
      case "sync_team_directory":
        return handleSyncTeamDirectory(
          payload as Parameters<typeof handleSyncTeamDirectory>[0],
        );
      case "parse_team_file":
        return handleParseTeamFile();
      case "export_agent_snapshot":
        // Mimics the save-to-disk path: report success without a real dialog.
        // Specs assert invocation via __BUZZ_E2E_COMMANDS__.
        return true;
      case "encode_agent_snapshot_for_send": {
        // Return the requested wire format so both message sharing (PNG) and
        // community catalog publication (JSON) exercise their real branches.
        // Optional encodeDelayMs lets specs observe the "preparing" phase before
        // the upload begins.
        const encodeDelayMs = activeConfig?.mock?.encodeDelayMs ?? 0;
        if (encodeDelayMs > 0) {
          await new Promise((resolve) =>
            window.setTimeout(resolve, encodeDelayMs),
          );
        }
        const input = payload as {
          id: string;
          memoryLevel: "none" | "core" | "everything";
          format: "json" | "png";
        };
        if (input.format === "json") {
          const persona = mockPersonas.find(
            (candidate) => candidate.id === input.id,
          );
          const snapshot = {
            format: "buzz-agent-snapshot",
            version: 1,
            definition: {
              name: persona?.display_name ?? "E2E Agent",
              sourceIsBuiltIn: persona?.is_builtin ?? false,
              systemPrompt: persona?.system_prompt ?? "",
              runtime: persona?.runtime ?? null,
              model: persona?.model ?? null,
              provider: persona?.provider ?? null,
              respondTo: persona?.respond_to ?? null,
              respondToAllowlist: persona?.respond_to_allowlist ?? [],
              namePool: persona?.name_pool ?? [],
            },
            profile: {
              displayName: persona?.display_name ?? "E2E Agent",
              avatarUrl: persona?.avatar_url ?? null,
            },
            memory: {
              level: input.memoryLevel,
              entries: [],
            },
          };
          const fileBytes = Array.from(
            new TextEncoder().encode(JSON.stringify(snapshot)),
          );
          return {
            fileBytes,
            fileName: "e2e-agent.agent.json",
          };
        }
        return {
          fileBytes: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
          fileName: "e2e-agent.agent.png",
        };
      }
      case "preview_agent_snapshot_import": {
        // Return a minimal preview — no writes performed.
        return {
          displayName: "Imported Agent",
          isBuiltIn: true,
          model: "claude-opus-4-5",
          runtime: "goose",
          systemPrompt: null,
          avatarUrl: null,
          memoryLevel: "none",
          memoryEntryCount: 0,
          hasSourceAllowlist: false,
          sourceAllowlistCount: 0,
          sourceAllowlist: [],
          manifestJson: "{}",
          locked: false,
        };
      }
      case "confirm_agent_snapshot_import": {
        // Return a successful import result with fresh synthetic keys.
        const importResult = {
          displayName: "Imported Agent",
          newPubkey:
            "e2e000000000000000000000000000000000000000000000000000000000000ff",
          personaId: `e2e-persona-${Date.now()}`,
          memoryWritten: 0,
          memoryTotal: 0,
          memoryErrors: [],
          profileSyncError: null,
        };
        return importResult;
      }
      case "export_team_snapshot":
        // Mimics the save-to-disk path: report success without a real dialog.
        return true;
      case "encode_team_snapshot_for_send": {
        // Return a minimal PNG-shaped payload so the send flow can proceed
        // through upload_media_bytes without a real Rust encode step.
        const encodeDelayMs = activeConfig?.mock?.encodeDelayMs ?? 0;
        if (encodeDelayMs > 0) {
          await new Promise((resolve) =>
            window.setTimeout(resolve, encodeDelayMs),
          );
        }
        return {
          fileBytes: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a],
          fileName: "e2e-team.team.png",
        };
      }
      case "preview_team_snapshot_import": {
        // Return a minimal preview — no writes performed.
        const previewHasAllowlist =
          activeConfig?.mock?.teamSnapshotPreviewHasSourceAllowlist ?? false;
        return {
          name: "Imported Team",
          description: null,
          instructions: null,
          members: [
            {
              displayName: "Team Member",
              systemPrompt: null,
              avatarUrl: null,
              hasSourceAllowlist: previewHasAllowlist,
              sourceAllowlistCount: previewHasAllowlist ? 3 : 0,
            },
          ],
          hasSourceAllowlist: previewHasAllowlist,
        };
      }
      case "confirm_team_snapshot_import": {
        // Sequenced failure injection: fail once, then succeed (retry test).
        const confirmSequence = activeConfig?.mock?.teamSnapshotConfirmErrors;
        if (confirmSequence && confirmSequence.length > 0) {
          const idx = Math.min(
            teamSnapshotConfirmCallCount,
            confirmSequence.length - 1,
          );
          teamSnapshotConfirmCallCount++;
          const entry = confirmSequence[idx];
          if (entry !== null) {
            throw new Error(entry);
          }
        }
        // Return a successful import result with fresh synthetic keys.
        // The nested `team` uses snake_case (Rust TeamRecord has no rename_all);
        // the outer struct and members use camelCase (their Rust types do).
        const importTs = Date.now();
        return {
          team: {
            id: `e2e-team-${importTs}`,
            name: "Imported Team",
            description: null,
            persona_ids: [`e2e-persona-${importTs}`],
            instructions: null,
            is_builtin: false,
            source_dir: null,
            is_symlink: false,
            symlink_target: null,
            version: null,
            created_at: new Date().toISOString(),
            updated_at: new Date().toISOString(),
          },
          personaIds: [`e2e-persona-${importTs}`],
          members: [
            {
              displayName: "Team Member",
              pubkey:
                "e2e000000000000000000000000000000000000000000000000000000000000ee",
              personaId: `e2e-persona-${importTs}`,
              memoryWritten: 0,
              memoryTotal: 0,
              memoryErrors: [],
              profileSyncError: null,
            },
          ],
        };
      }
      case "list_managed_agents":
        return handleListManagedAgents(activeConfig);
      case "get_agent_memory":
        return handleGetAgentMemory(
          (payload as Parameters<typeof handleGetAgentMemory>[0]) ?? {},
          activeConfig,
        );
      case "create_managed_agent":
        return handleCreateManagedAgent(
          payload as Parameters<typeof handleCreateManagedAgent>[0],
          activeConfig,
        );
      case "start_managed_agent":
        return handleStartManagedAgent(
          payload as Parameters<typeof handleStartManagedAgent>[0],
          activeConfig,
        );
      case "stop_managed_agent":
        return handleStopManagedAgent(
          payload as Parameters<typeof handleStopManagedAgent>[0],
        );
      case "list_managed_agent_runtimes":
        return mockManagedAgentRuntimes.map((row) => ({ ...row }));
      case "start_managed_agent_runtime":
        return handleManagedAgentRuntimeAction(
          "start",
          payload as { pubkey: string; relayUrl: string },
        );
      case "stop_managed_agent_runtime":
        return handleManagedAgentRuntimeAction(
          "stop",
          payload as { pubkey: string; relayUrl: string },
        );
      case "restart_managed_agent_runtime":
        return handleManagedAgentRuntimeAction(
          "restart",
          payload as { pubkey: string; relayUrl: string },
        );
      case "reconcile_managed_agent_runtimes":
        // Post-create bootstrap reconcile: no new pairs in the mock world.
        return [];
      case "set_agent_managed_profiles":
        return undefined;
      case "set_managed_agent_auto_restart":
        return handleSetManagedAgentAutoRestart(
          payload as Parameters<typeof handleSetManagedAgentAutoRestart>[0],
        );
      case "set_managed_agent_start_on_app_launch":
        return handleSetManagedAgentStartOnAppLaunch(
          payload as Parameters<
            typeof handleSetManagedAgentStartOnAppLaunch
          >[0],
        );
      case "delete_managed_agent":
        return handleDeleteManagedAgent(
          payload as Parameters<typeof handleDeleteManagedAgent>[0],
        );
      case "get_managed_agent_log":
        return handleGetManagedAgentLog(
          payload as Parameters<typeof handleGetManagedAgentLog>[0],
        );
      case "get_agent_models":
        return {
          agentName: "mock-agent",
          agentVersion: "0.0.0",
          models: [],
          agentDefaultModel: null,
          selectedModel: null,
          supportsSwitching: false,
        };
      case "discover_agent_models": {
        const discoverError = activeConfig?.mock?.discoverAgentModelsError;
        if (discoverError) {
          throw new Error(discoverError);
        }
        const discoverOverride = activeConfig?.mock?.discoverAgentModels;
        if (discoverOverride) {
          return {
            agentName: "mock-agent",
            agentVersion: "0.0.0",
            models: discoverOverride.models.map((model) => ({
              id: model.id,
              name: model.name,
              description: model.description ?? null,
            })),
            agentDefaultModel: discoverOverride.agentDefaultModel ?? null,
            selectedModel: discoverOverride.selectedModel ?? null,
            supportsSwitching: discoverOverride.supportsSwitching,
          };
        }
        const input = (
          payload as {
            input?: { agentCommand?: string; provider?: string };
          } | null
        )?.input;
        const agentCommand = input?.agentCommand?.trim() ?? "";
        const provider = input?.provider?.trim() ?? "";
        const openAiModels = [
          { id: "gpt-5.5", name: "GPT-5.5", description: null },
          { id: "gpt-5.4", name: "GPT-5.4", description: null },
          { id: "gpt-5.4-mini", name: "GPT-5.4 mini", description: null },
          { id: "gpt-5.4-nano", name: "GPT-5.4 nano", description: null },
        ];
        const anthropicModels = [
          {
            id: "goose-claude-4-6-opus",
            name: "Claude Opus 4.6",
            description: null,
          },
          {
            id: "goose-claude-4-6-sonnet",
            name: "Claude Sonnet 4.6",
            description: null,
          },
        ];
        const claudeRuntimeModels = [
          {
            id: "claude-sonnet-4-20250514",
            name: "Claude Sonnet 4",
            description: null,
          },
          {
            id: "claude-opus-4-20250514",
            name: "Claude Opus 4",
            description: null,
          },
        ];
        const codexRuntimeModels = [
          { id: "gpt-5.5", name: "GPT-5.5", description: null },
          { id: "gpt-5.5[low]", name: "GPT-5.5 (low)", description: null },
          {
            id: "gpt-5.5[medium]",
            name: "GPT-5.5 (medium)",
            description: null,
          },
          { id: "gpt-5.5[high]", name: "GPT-5.5 (high)", description: null },
        ];
        if (provider === "relay-mesh") {
          if (!mockMeshState.admitted) {
            throw new Error(mockMeshState.denyReason);
          }
          if (mockMeshState.models.length === 0) {
            throw new Error(
              "no Buzz shared compute serving members are available",
            );
          }
        }
        const models =
          provider === "relay-mesh"
            ? mockMeshState.models.map((model) => ({
                id: model.id,
                name: model.name,
                description: null,
              }))
            : provider === "openai"
              ? openAiModels
              : provider === "anthropic"
                ? anthropicModels
                : agentCommand.includes("claude")
                  ? claudeRuntimeModels
                  : agentCommand.includes("codex")
                    ? codexRuntimeModels
                    : [...anthropicModels, ...openAiModels];
        return {
          agentName: "mock-agent",
          agentVersion: "0.0.0",
          models,
          agentDefaultModel: agentCommand.includes("codex")
            ? "gpt-5.5[high]"
            : null,
          selectedModel: null,
          supportsSwitching: true,
        };
      }
      case "get_agent_config_surface": {
        const configArgs = payload as { pubkey: string };
        return buildMockConfigSurface(configArgs.pubkey);
      }
      case "get_runtime_file_config": {
        const runtimeId = (payload as { runtimeId?: string } | null | undefined)
          ?.runtimeId;
        if (!runtimeId) return null;
        return config.mock?.runtimeFileConfigs?.[runtimeId] ?? null;
      }
      case "get_global_agent_config": {
        // Return the mutable persisted mock value, seeded from the test config.
        return (
          mockGlobalAgentConfig ?? {
            env_vars: {},
            provider: null,
            model: null,
            preferred_runtime: null,
          }
        );
      }
      case "get_global_agent_config_set_call_count":
        return setGlobalAgentConfigCallCount;
      case "set_global_agent_config": {
        setGlobalAgentConfigCallCount += 1;
        const saveErrors = activeConfig?.mock?.setGlobalAgentConfigErrors;
        const saveError =
          saveErrors?.[
            Math.min(setGlobalAgentConfigCallCount - 1, saveErrors.length - 1)
          ];
        if (saveError) throw new Error(saveError);
        // Echo back the submitted config as the saved value (mirrors the
        // backend's strip-on-write pass in tests where all values are already
        // non-empty). The invoke payload wraps it as { config }.
        const savedConfig = (
          payload as {
            config: {
              env_vars: Record<string, string>;
              provider: string | null;
              model: string | null;
              preferred_runtime: string | null;
            };
          }
        ).config;
        // Optional configurable delay so specs can hold a save in flight and
        // interleave edits (mid-save race + autosave-coalescing coverage).
        // Two aliases: onboarding specs use setGlobalAgentConfigDelayMs,
        // settings-card specs use globalConfigSaveDelayMs.
        const saveDelayMs =
          config?.mock?.globalConfigSaveDelayMs ??
          activeConfig?.mock?.setGlobalAgentConfigDelayMs ??
          0;
        if (saveDelayMs > 0) {
          await new Promise((resolve) => setTimeout(resolve, saveDelayMs));
        }
        mockGlobalAgentConfig = savedConfig;
        // In the E2E environment there are no running agents to restart, so
        // the counts default to 0 unless a spec drives them explicitly.
        return {
          config: savedConfig,
          restarted_count: config?.mock?.globalConfigRestartedCount ?? 0,
          failed_restart_count:
            config?.mock?.globalConfigFailedRestartCount ?? 0,
        };
      }
      case "get_baked_build_env": {
        const bakedEnvDelayMs = config?.mock?.bakedBuildEnvDelayMs ?? 0;
        if (bakedEnvDelayMs > 0) {
          await new Promise((resolve) =>
            window.setTimeout(resolve, bakedEnvDelayMs),
          );
        }
        return config?.mock?.bakedBuildEnv ?? [];
      }
      case "get_baked_build_env_keys":
        return (config?.mock?.bakedBuildEnv ?? []).map((entry) => entry.key);
      case "agent_access_owner_only":
        return config?.mock?.ownerOnlyAccessBuild ?? false;
      case "update_managed_agent":
        return handleUpdateManagedAgent(
          payload as Parameters<typeof handleUpdateManagedAgent>[0],
        );
      case "create_channel":
        return handleCreateChannel(
          payload as Parameters<typeof handleCreateChannel>[0],
          activeConfig,
        );
      case "ensure_starter_channels":
        return handleEnsureStarterChannels(activeConfig);
      case "open_dm":
        return handleOpenDm(
          payload as Parameters<typeof handleOpenDm>[0],
          activeConfig,
        );
      case "hide_dm":
        return handleHideDm(
          payload as Parameters<typeof handleHideDm>[0],
          activeConfig,
        );
      case "get_channel_details":
        return handleGetChannelDetails(
          payload as Parameters<typeof handleGetChannelDetails>[0],
          activeConfig,
        );
      case "get_channel_members":
        return handleGetChannelMembers(
          payload as Parameters<typeof handleGetChannelMembers>[0],
          activeConfig,
        );
      case "update_channel":
        return handleUpdateChannel(
          (payload as { input: Parameters<typeof handleUpdateChannel>[0] })
            .input,
          activeConfig,
        );
      case "set_channel_topic":
        return handleSetChannelTopic(
          payload as Parameters<typeof handleSetChannelTopic>[0],
          activeConfig,
        );
      case "set_channel_purpose":
        return handleSetChannelPurpose(
          payload as Parameters<typeof handleSetChannelPurpose>[0],
          activeConfig,
        );
      case "archive_channel":
        return handleArchiveChannel(
          payload as Parameters<typeof handleArchiveChannel>[0],
          activeConfig,
        );
      case "unarchive_channel":
        return handleUnarchiveChannel(
          payload as Parameters<typeof handleUnarchiveChannel>[0],
          activeConfig,
        );
      case "delete_channel":
        return handleDeleteChannel(
          payload as Parameters<typeof handleDeleteChannel>[0],
          activeConfig,
        );
      case "add_channel_members":
        return handleAddChannelMembers(
          payload as Parameters<typeof handleAddChannelMembers>[0],
          activeConfig,
        );
      case "remove_channel_member":
        return handleRemoveChannelMember(
          payload as Parameters<typeof handleRemoveChannelMember>[0],
          activeConfig,
        );
      case "join_channel":
        return handleJoinChannel(
          payload as Parameters<typeof handleJoinChannel>[0],
          activeConfig,
        );
      case "leave_channel":
        return handleLeaveChannel(
          payload as Parameters<typeof handleLeaveChannel>[0],
          activeConfig,
        );
      case "search_messages":
        return handleSearchMessages(
          payload as Parameters<typeof handleSearchMessages>[0],
          activeConfig,
        );
      case "get_forum_posts":
        return handleGetForumPosts(
          payload as Parameters<typeof handleGetForumPosts>[0],
        );
      case "get_forum_thread":
        return handleGetForumThread(
          payload as Parameters<typeof handleGetForumThread>[0],
        );
      case "get_thread_replies":
        return handleGetThreadReplies(
          payload as Parameters<typeof handleGetThreadReplies>[0],
          activeConfig,
        );
      case "get_channel_messages_before":
        return handleGetChannelMessagesBefore(
          payload as Parameters<typeof handleGetChannelMessagesBefore>[0],
          activeConfig,
        );
      case "get_channel_window":
        return handleGetChannelWindow(
          payload as Parameters<typeof handleGetChannelWindow>[0],
          activeConfig,
        );
      case "send_channel_message":
        return handleSendChannelMessage(
          payload as Parameters<typeof handleSendChannelMessage>[0],
          activeConfig,
        );
      case "has_managed_agent_channel_message_marker": {
        const args = payload as {
          channelId: string;
          marker: string;
          agentPubkey?: string | null;
          markerScope?: "agent" | "channel" | null;
        };
        return getMockMessageStore(args.channelId).some(
          (event) =>
            (args.markerScope === "channel" ||
              event.pubkey === args.agentPubkey) &&
            event.tags.some(
              (tag) => tag[0] === "client" && tag[1] === args.marker,
            ),
        );
      }
      case "send_managed_agent_channel_message":
        return handleSendManagedAgentChannelMessage(
          payload as Parameters<typeof handleSendManagedAgentChannelMessage>[0],
          activeConfig,
        );
      case "delete_message":
        handleDeleteMessage(
          payload as Parameters<typeof handleDeleteMessage>[0],
          activeConfig,
        );
        return null;
      case "edit_message":
        return handleEditMessage(
          (payload as { input: Parameters<typeof handleEditMessage>[0] }).input,
          activeConfig,
        );
      case "add_reaction":
        return handleAddReaction(
          payload as Parameters<typeof handleAddReaction>[0],
          activeConfig,
        );
      case "remove_reaction":
        return handleRemoveReaction(
          payload as Parameters<typeof handleRemoveReaction>[0],
          activeConfig,
        );
      case "get_media_proxy_port":
        return mockMediaProxyPort;
      case "pick_and_upload_media":
        return await resolveMockUploadDescriptors(activeConfig);
      case "pick_and_upload_image":
        return (await resolveMockUploadDescriptors(activeConfig))[0] ?? null;
      case "upload_media_bytes": {
        const input = payload as {
          data: number[];
          filename?: string | null;
          progressId?: string | null;
        };
        if (
          activeConfig?.mock?.deferLinkPreviewUploadRegistration &&
          input.filename?.startsWith("link-preview-")
        ) {
          await new Promise<void>((resolve) => {
            deferredLinkPreviewUploadQueue.push(resolve);
          });
        }
        if (input.progressId && cancelledMediaUploadIds.has(input.progressId)) {
          throw new Error("upload cancelled");
        }
        if (input.filename?.startsWith("link-preview-")) {
          window.__BUZZ_E2E_LINK_PREVIEW_UPLOAD_STARTS__ =
            (window.__BUZZ_E2E_LINK_PREVIEW_UPLOAD_STARTS__ ?? 0) + 1;
        }
        return resolveMockUploadDescriptorForBytes(input, activeConfig);
      }
      case "cancel_media_upload": {
        const progressId = (payload as { progressId?: string }).progressId;
        if (progressId) cancelledMediaUploadIds.add(progressId);
        return null;
      }
      case "release_media_upload": {
        const progressId = (payload as { progressId?: string }).progressId;
        if (progressId) cancelledMediaUploadIds.delete(progressId);
        return null;
      }
      case "upload_media_bytes_raw":
        return resolveMockUploadDescriptorForBytes(
          {
            data: payload as Uint8Array,
          },
          activeConfig,
        );
      case "fetch_media_bytes": {
        // The real command fetches relay media through Rust reqwest and
        // replies with raw bytes (`tauri::ipc::Response` → ArrayBuffer). In
        // E2E the browser fetch suffices — specs serve the URL via page.route.
        const response = await fetch((payload as { url: string }).url);
        if (!response.ok) throw new Error(`fetch failed: ${response.status}`);
        return await response.arrayBuffer();
      }
      case "fetch_snapshot_bytes": {
        // The real command fetches + validates a snapshot attachment in memory
        // (size cap, SHA-256, decode). In E2E the bridge returns a minimal
        // valid .agent.json payload so the import flow can proceed without a
        // real relay. A non-null snapshotFetchError config forces a rejection.
        const err = activeConfig?.mock?.snapshotFetchError;
        if (err) throw new Error(err);
        const jsonBytes = Array.from(
          new TextEncoder().encode(
            JSON.stringify({
              format: "buzz-agent-snapshot",
              version: 1,
              definition: { system_prompt: "E2E imported agent prompt." },
              profile: { display_name: "Imported Agent" },
              memory: { level: "none", entries: [] },
            }),
          ),
        );
        // Return as ArrayBuffer to mirror the real Tauri ipc::Response.
        const buf = new ArrayBuffer(jsonBytes.length);
        new Uint8Array(buf).set(jsonBytes);
        return buf;
      }
      case "download_image":
      case "save_png_data_url":
      case "download_file":
      case "save_agent_card":
        // The save dialog can't run headlessly; report a successful save so the
        // FileCard / image-menu click handlers resolve. Specs assert the
        // command was invoked via `__BUZZ_E2E_COMMANDS__`, not the dialog.
        return true;
      case "card_mint_key_status":
        // Cards: pretend a key is configured in global defaults so the mint
        // form renders and the key-status row is shown.
        return "global";
      case "list_agent_cards":
        // Cards archive starts empty in E2E; specs exercising the gallery
        // can extend this with a seeded config knob when needed.
        return [];
      case "copy_image_to_clipboard":
        return;
      case "copy_text_to_clipboard":
        await navigator.clipboard.writeText((payload as { text: string }).text);
        return;
      case "read_clipboard_text":
        return navigator.clipboard.readText();
      case "get_event":
        return handleGetEvent(
          payload as Parameters<typeof handleGetEvent>[0],
          activeConfig,
        );
      case "sign_event":
        window.__BUZZ_E2E_SIGNED_EVENTS__?.push({
          content: (payload as { content: string }).content,
          createdAt: (payload as { createdAt?: number }).createdAt,
          kind: (payload as { kind: number }).kind,
          tags: (payload as { tags: string[][] }).tags,
        });
        if (identity) {
          return JSON.stringify(
            await signWithIdentity(identity, {
              kind: (payload as { kind: number }).kind,
              content: (payload as { content: string }).content,
              createdAt: (payload as { createdAt?: number }).createdAt,
              tags: (payload as { tags: string[][] }).tags,
            }),
          );
        }

        return JSON.stringify(
          createMockEvent(
            (payload as { kind: number }).kind,
            (payload as { content: string }).content,
            (payload as { tags: string[][] }).tags,
            DEFAULT_MOCK_IDENTITY.pubkey,
            (payload as { createdAt?: number }).createdAt,
          ),
        );
      case "nip44_encrypt_to_self":
        return (payload as { plaintext: string }).plaintext;
      case "nip44_decrypt_from_self":
        return (payload as { ciphertext: string }).ciphertext;
      case "create_auth_event":
        if (identity) {
          return JSON.stringify(
            await signWithIdentity(identity, {
              kind: 22242,
              content: "",
              tags: [
                ["relay", (payload as { relayUrl: string }).relayUrl],
                ["challenge", (payload as { challenge: string }).challenge],
              ],
            }),
          );
        }

        return JSON.stringify(
          createMockEvent(22242, "", [
            ["relay", (payload as { relayUrl: string }).relayUrl],
            ["challenge", (payload as { challenge: string }).challenge],
          ]),
        );
      case "plugin:websocket|connect":
        if (isRelayMode(activeConfig)) {
          return connectRealSocket(
            payload as Parameters<typeof connectRealSocket>[0],
          );
        }

        return connectMockSocket(
          payload as Parameters<typeof connectMockSocket>[0],
        );
      case "plugin:websocket|disconnect": {
        const { id } = payload as { id: number };
        if (isRelayMode(activeConfig)) {
          realSockets.get(id)?.close();
          realSockets.delete(id);
        } else {
          const socket = mockSockets.get(id);
          mockSockets.delete(id);
          if (socket) sendWsClose(socket.handler);
        }
        return null;
      }
      case "plugin:websocket|disconnect_all":
        for (const socket of realSockets.values()) socket.close();
        realSockets.clear();
        for (const socket of mockSockets.values()) sendWsClose(socket.handler);
        mockSockets.clear();
        mockWebsocketSendMutexWedged = false;
        return null;
      case "plugin:websocket|send":
        if (isRelayMode(activeConfig)) {
          return sendToRealSocket(
            payload as Parameters<typeof sendToRealSocket>[0],
          );
        }

        return sendToMockSocket(
          payload as Parameters<typeof sendToMockSocket>[0],
        );
      case "plugin:opener|open_url":
        if (activeConfig?.mock?.openerError) {
          throw new Error(activeConfig.mock.openerError);
        }
        openedExternalUrls.push(String((payload as { url: string | URL }).url));
        return null;
      case "get_e2e_opened_external_urls":
        return [...openedExternalUrls];
      case "clear_e2e_opened_external_urls":
        openedExternalUrls.length = 0;
        return null;
      case "plugin:window|show":
      case "plugin:window|unminimize":
      case "plugin:window|set_focus":
      case "plugin:window|set_badge_count":
      case "plugin:window|set_badge_label":
        return null;
      case "plugin:updater|check":
        return handleUpdaterCheck(activeConfig);
      case "plugin:updater|download":
        return handleUpdaterDownload(payload, activeConfig);
      case "plugin:updater|install":
        return handleUpdaterInstall();
      case "is_auto_update_supported":
        // Default true so all existing tests continue to use the auto-update
        // path. Set mock.autoUpdateSupported: false to simulate a .deb install.
        return activeConfig?.mock?.autoUpdateSupported !== false;
      case "relay_reconnect_hook":
        return null;
      case "relay_reconnect_hook_configured":
        return false;
      case "plugin:resources|close":
        return null;
      case "plugin:process|restart":
        return handleRestart(activeConfig);
      case "get_channel_workflows":
        return handleGetChannelWorkflows(
          payload as Parameters<typeof handleGetChannelWorkflows>[0],
        );
      case "get_channels_workflows":
        return handleGetChannelsWorkflows(
          payload as Parameters<typeof handleGetChannelsWorkflows>[0],
        );
      case "get_workflow":
        return handleGetWorkflow(
          payload as Parameters<typeof handleGetWorkflow>[0],
        );
      case "create_workflow":
        return handleCreateWorkflow(
          payload as Parameters<typeof handleCreateWorkflow>[0],
        );
      case "update_workflow":
        return handleUpdateWorkflow(
          payload as Parameters<typeof handleUpdateWorkflow>[0],
        );
      case "delete_workflow":
        return handleDeleteWorkflow(
          payload as Parameters<typeof handleDeleteWorkflow>[0],
        );
      case "trigger_workflow":
        return handleTriggerWorkflow(
          payload as Parameters<typeof handleTriggerWorkflow>[0],
        );
      case "get_workflow_runs":
        return handleGetWorkflowRuns(
          payload as Parameters<typeof handleGetWorkflowRuns>[0],
        );
      case "get_run_approvals":
        return handleGetRunApprovals(
          payload as Parameters<typeof handleGetRunApprovals>[0],
        );
      case "plugin:webview|set_webview_zoom":
        window.__BUZZ_E2E_WEBVIEW_ZOOM__ = (payload as { value: number }).value;
        return;
      case "start_pairing": {
        const delayMs = activeConfig?.mock?.pairingStartDelayMs ?? 0;
        if (delayMs > 0) {
          await new Promise((resolve) => window.setTimeout(resolve, delayMs));
        }
        return "nostrpair://8f4b8db31967ce14fef970a1ff1e8eecf19a430aa1c83875e2f5be68dcac0f1a?relay=wss%3A%2F%2Frelay.example.com&secret=87d5a8cfd5807a0cb44f728b67d88d6dcb8daf99be137c158f21a50c1e913c0a&v=1";
      }
      case "start_identity_recovery_pairing": {
        const delayMs = activeConfig?.mock?.pairingStartDelayMs ?? 0;
        if (delayMs > 0) {
          await new Promise((resolve) => window.setTimeout(resolve, delayMs));
        }
        return `nostrpair://8f4b8db31967ce14fef970a1ff1e8eecf19a430aa1c83875e2f5be68dcac0f1a?relay=wss%3A%2F%2Frelay.example.com&secret=87d5a8cfd5807a0cb44f728b67d88d6dcb8daf99be137c158f21a50c1e913c0a&v=1&mode=recover`;
      }
      case "cancel_pairing":
      case "confirm_pairing_sas":
        return null;
      case "complete_identity_recovery_pairing":
        mockIdentityLostCleared = true;
        await emit("pairing-complete", {});
        return null;
      // ── NIP-IA identity archival ────────────────────────────────────────
      // These mocks drive the archive-button gate matrix in
      // tests/e2e/identity-archive.spec.ts. Defaults keep the button hidden
      // for non-self viewees so the negative case is the unsurprising one.
      case "resolve_oa_owner": {
        const isMe = activeConfig?.mock?.oaOwnerIsMe ?? false;
        const owner = isMe
          ? (identity?.pubkey ?? DEFAULT_MOCK_IDENTITY.pubkey)
          : "ff".repeat(32);
        return { owner, is_me: isMe };
      }
      case "list_archived_identities": {
        const archived = activeConfig?.mock?.archivedIdentities ?? [];
        return { archived };
      }
      case "get_relay_self":
        if ((activeConfig?.mock?.relaySelfDelayMs ?? 0) > 0) {
          await new Promise((resolve) =>
            window.setTimeout(
              resolve,
              activeConfig?.mock?.relaySelfDelayMs ?? 0,
            ),
          );
        }
        return activeConfig?.mock?.relaySelf ?? null;
      case "archive_identity":
      case "unarchive_identity":
        // The spec only verifies UI state, not the submitted request shape;
        // returning null mirrors the Rust submit_event success path.
        return null;
      case "set_canvas":
        return { ok: true, event_id: mockEventId() };
      case "get_canvas": {
        const canvasReadError = activeConfig?.mock?.canvasReadError;
        if (canvasReadError) {
          throw new Error(canvasReadError);
        }
        // Return the no-canvas success shape — content null means no canvas set.
        return { content: null, updated_at: null, author: null };
      }
      // ── Local-save archive ──────────────────────────────────────────────
      // These stubs drive the LocalArchiveSettingsCard in screenshot / UI tests
      // without requiring a real SQLite backend. `mockSaveSubscriptions` is a
      // mutable clone of `activeConfig.mock.saveSubscriptions` (reset on
      // install); create/merge/delete/remove mutate it with the same
      // union / delete-row-when-empty semantics as the real Rust commands
      // (see `archive/store.rs::merge_owner_p_kinds` / `remove_owner_p_kind`)
      // so specs can drive default-on seeding and toggle ON/OFF flows.
      case "list_save_subscriptions": {
        const win = window as unknown as Record<string, unknown>;
        if (!win.__BUZZ_E2E_IPC_COUNTERS__) {
          win.__BUZZ_E2E_IPC_COUNTERS__ = {};
        }
        const ipcCounters = win.__BUZZ_E2E_IPC_COUNTERS__ as Record<
          string,
          number
        >;
        ipcCounters.list_save_subscriptions =
          (ipcCounters.list_save_subscriptions ?? 0) + 1;
        const ident = activeConfig?.identity ?? DEFAULT_MOCK_IDENTITY;
        return mockSaveSubscriptions.map((s) => ({
          identity_pubkey: ident.pubkey,
          relay_url: DEFAULT_RELAY_WS_URL,
          scope_type: s.scope_type,
          scope_value: s.scope_value,
          kinds: s.kinds,
          created_at: Math.floor(Date.now() / 1000),
        }));
      }
      case "create_save_subscription": {
        const req = payload as {
          scopeType: string;
          scopeValue: string;
          kinds: number[];
        };
        const kindsJson = JSON.stringify(req.kinds);
        const existing = mockSaveSubscriptions.find(
          (s) =>
            s.scope_type === req.scopeType && s.scope_value === req.scopeValue,
        );
        if (existing) {
          existing.kinds = kindsJson;
        } else {
          mockSaveSubscriptions.push({
            scope_type: req.scopeType,
            scope_value: req.scopeValue,
            kinds: kindsJson,
          });
        }
        return null;
      }
      case "delete_save_subscription": {
        const req = payload as { scopeType: string; scopeValue: string };
        const before = mockSaveSubscriptions.length;
        mockSaveSubscriptions = mockSaveSubscriptions.filter(
          (s) =>
            !(
              s.scope_type === req.scopeType && s.scope_value === req.scopeValue
            ),
        );
        return mockSaveSubscriptions.length < before;
      }
      case "archive_events":
        // Returns the ArchiveBatchResult shape the UI expects.
        return { persisted: 0, dropped: 0 };
      case "agent_metric_archive_default_enabled":
        return activeConfig?.mock?.agentMetricArchiveDefaultEnabled ?? true;
      case "set_prevent_sleep_active":
        return null;
      case "set_window_vibrancy":
        return null;
      case "plugin:window|is_fullscreen":
        return false;
      // Settings reads the app version through the app plugin. Without this the
      // bridge throws an unhandled page error on every Settings render, which
      // shows up as noise in unrelated specs.
      case "plugin:app|version":
        return MOCK_APP_VERSION;
      case "merge_save_subscription_kinds": {
        // Mirrors `merge_owner_p_kinds`: union `kind` into the owner_p row's
        // kinds, creating the row if it doesn't exist yet.
        const { kind } = payload as { kind: number };
        const ident = activeConfig?.identity ?? DEFAULT_MOCK_IDENTITY;
        const row = mockSaveSubscriptions.find(
          (s) => s.scope_type === "owner_p" && s.scope_value === ident.pubkey,
        );
        if (row) {
          const kinds: number[] = JSON.parse(row.kinds);
          if (!kinds.includes(kind)) {
            row.kinds = JSON.stringify([...kinds, kind]);
          }
        } else {
          mockSaveSubscriptions.push({
            scope_type: "owner_p",
            scope_value: ident.pubkey,
            kinds: JSON.stringify([kind]),
          });
        }
        return null;
      }
      case "remove_save_subscription_kind": {
        // Mirrors `remove_owner_p_kind`: remove `kind` from the owner_p row's
        // kinds, deleting the row entirely once its kinds list is empty.
        const { kind } = payload as { kind: number };
        const ident = activeConfig?.identity ?? DEFAULT_MOCK_IDENTITY;
        const row = mockSaveSubscriptions.find(
          (s) => s.scope_type === "owner_p" && s.scope_value === ident.pubkey,
        );
        if (row) {
          const kinds: number[] = JSON.parse(row.kinds).filter(
            (k: number) => k !== kind,
          );
          if (kinds.length === 0) {
            mockSaveSubscriptions = mockSaveSubscriptions.filter(
              (s) => s !== row,
            );
          } else {
            row.kinds = JSON.stringify(kinds);
          }
        }
        return null;
      }
      default:
        throw new Error(`Unsupported mocked Tauri command: ${command}`);
    }
  };
  window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__ = (command, payload) =>
    handleMockCommand(command, payload ?? null);
  window.__BUZZ_E2E_EMIT_TAURI_EVENT__ = (event, payload) =>
    emit(event, payload);
  mockIPC(handleMockCommand, { shouldMockEvents: true });
  const tauriInternals = (
    window as typeof window & {
      __TAURI_INTERNALS__: {
        listen?: (
          event: string,
          callback: () => void,
        ) => Promise<() => Promise<void>>;
      };
    }
  ).__TAURI_INTERNALS__;
  // Page-evaluated E2E specs use this surface; delegate to Tauri's mocked channel
  // so their listeners observe the same events emitted by application test seams.
  tauriInternals.listen = async (event, callback) => {
    const unlisten = await listen(event, () => callback());
    return async () => unlisten();
  };

  installed = true;
}
