"use client";

import {
  type ColumnDef,
  flexRender,
  getCoreRowModel,
  getSortedRowModel,
  type RowData,
  type RowSelectionState,
  type SortingState,
  useReactTable,
  type VisibilityState,
} from "@tanstack/react-table";
import { useVirtualizer } from "@tanstack/react-virtual";
import { invoke } from "@tauri-apps/api/core";
import { emit, listen } from "@tauri-apps/api/event";
import { AnimatePresence, motion } from "motion/react";
import type { Dispatch, SetStateAction } from "react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { FaApple, FaLinux, FaWindows } from "react-icons/fa";
import { FiWifi } from "react-icons/fi";
import {
  LuCheck,
  LuChevronDown,
  LuChevronUp,
  LuCookie,
  LuInfo,
  LuLock,
  LuMoon,
  LuPlay,
  LuPuzzle,
  LuSquare,
  LuTrash2,
  LuTriangleAlert,
  LuUserSearch,
  LuUsers,
} from "react-icons/lu";
import { CookieBotEnrolDialog } from "@/components/cookie-bot-enrol-dialog";
import { CookieBotRunsDialog } from "@/components/cookie-bot-runs-dialog";
import {
  describeCadence,
  enableProfileSync,
  minutesToClock,
  outcomeLabel,
  type PreflightFix,
  preflight,
  preflightFixLabel,
  preflightReason,
  runStatusLabel,
  StatusDot,
  sessionPhaseLabel,
  sessionTone,
} from "@/components/cookie-bot-shared";
import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog";
import {
  ProfileBypassRulesDialog,
  ProfileDnsBlocklistDialog,
  ProfileInfoDialog,
  ProfileLaunchHookDialog,
} from "@/components/profile-info-dialog";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from "@/components/ui/command";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { ProBadge } from "@/components/ui/pro-badge";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip";
import { useBrowserState } from "@/hooks/use-browser-state";
import { useCloudAuth } from "@/hooks/use-cloud-auth";
import { cookieBotScopeFor, useCookieBot } from "@/hooks/use-cookie-bot";
import { useProxyEvents } from "@/hooks/use-proxy-events";
import { useRemoteHandoff } from "@/hooks/use-remote-handoff";
import { useScrollFade } from "@/hooks/use-scroll-fade";
import { useTableSorting } from "@/hooks/use-table-sorting";
import { useTeamLocks } from "@/hooks/use-team-locks";
import { useVpnEvents } from "@/hooks/use-vpn-events";
import { parseBackendError, translateBackendError } from "@/lib/backend-errors";
import {
  getBrowserDisplayName,
  getOSDisplayName,
  getProfileIcon,
  isCrossOsProfile,
} from "@/lib/browser-utils";
import {
  type CookieBotSchedule,
  cancelCookieBotRun,
  deleteCookieBotSchedule,
  runCookieBotNow,
} from "@/lib/cookie-bot";
import { DNS_BLOCKLIST_LEVELS } from "@/lib/dns-blocklist-levels";
import { canUseCookieBot } from "@/lib/entitlements";
import { formatRelativeTime } from "@/lib/flag-utils";
import type { RemoteHandoffState } from "@/lib/remote-sessions";
import { showErrorToast, showSuccessToast } from "@/lib/toast-utils";
import { cn } from "@/lib/utils";
import type {
  BrowserProfile,
  ExtensionGroup,
  LocationItem,
  ProfileBotState,
  ProxyCheckResult,
  StoredProxy,
  SyncSessionInfo,
  TrafficSnapshot,
  VpnConfig,
} from "@/types";
import { BandwidthMiniChart } from "./bandwidth-mini-chart";
import {
  DataTableActionBar,
  DataTableActionBarAction,
  DataTableActionBarSelection,
} from "./data-table-action-bar";
import { Logo } from "./icons/logo";
import MultipleSelector, { type Option } from "./multiple-selector";
import { ProxyCheckButton } from "./proxy-check-button";
import { TrafficDetailsDialog } from "./traffic-details-dialog";
import { Input } from "./ui/input";
import { RippleButton } from "./ui/ripple";
import { Skeleton } from "./ui/skeleton";

declare module "@tanstack/react-table" {
  interface ColumnMeta<TData extends RowData, TValue> {
    // Emit no width for this column so table-fixed hands it all remaining
    // space. Checking columnDef.size alone can't express this: TanStack
    // resolves an unspecified size to its 150px default.
    flexWidth?: boolean;
  }
}

// Stable table meta type to pass volatile state/handlers into TanStack Table without
// causing column definitions to be recreated on every render.
interface TableMeta {
  t: (key: string, options?: Record<string, unknown>) => string;
  selectedProfiles: string[];
  selectableCount: number;
  showCheckboxes: boolean;
  isClient: boolean;
  runningProfiles: Set<string>;
  launchingProfiles: Set<string>;
  stoppingProfiles: Set<string>;
  isUpdating: (browser: string) => boolean;
  browserState: ReturnType<typeof useBrowserState>;

  // Tags editor state
  tagsOverrides: Record<string, string[]>;
  allTags: string[];
  openTagsEditorFor: string | null;
  setAllTags: React.Dispatch<React.SetStateAction<string[]>>;
  setOpenTagsEditorFor: React.Dispatch<React.SetStateAction<string | null>>;
  setTagsOverrides: React.Dispatch<
    React.SetStateAction<Record<string, string[]>>
  >;

  // Note editor state
  noteOverrides: Record<string, string | null>;
  openNoteEditorFor: string | null;
  setOpenNoteEditorFor: React.Dispatch<React.SetStateAction<string | null>>;
  setNoteOverrides: React.Dispatch<
    React.SetStateAction<Record<string, string | null>>
  >;

  // Proxy selector state
  openProxySelectorFor: string | null;
  setOpenProxySelectorFor: React.Dispatch<React.SetStateAction<string | null>>;
  proxyOverrides: Record<string, string | null>;
  storedProxies: StoredProxy[];
  handleProxySelection: (
    profileId: string,
    proxyId: string | null,
  ) => void | Promise<void>;
  checkingProfileId: string | null;
  proxyCheckResults: Record<string, ProxyCheckResult>;

  // VPN selector state
  vpnConfigs: VpnConfig[];
  vpnOverrides: Record<string, string | null>;
  handleVpnSelection: (
    profileId: string,
    vpnId: string | null,
  ) => void | Promise<void>;

  // Extension groups (for Ext column lookup)
  extensionGroups: ExtensionGroup[];

  // Click handlers for inline Ext / DNS cell editing
  onAssignExtensionGroup?: (profileIds: string[]) => void;
  setDnsBlocklistProfile: React.Dispatch<
    React.SetStateAction<BrowserProfile | null>
  >;

  // Selection helpers
  isProfileSelected: (id: string) => boolean;
  handleToggleAll: (checked: boolean) => void;
  handleCheckboxChange: (id: string, checked: boolean) => void;
  handleIconClick: (id: string) => void;

  // Rename helpers
  handleRename: () => void | Promise<void>;
  setProfileToRename: React.Dispatch<
    React.SetStateAction<BrowserProfile | null>
  >;
  setNewProfileName: React.Dispatch<React.SetStateAction<string>>;
  setRenameError: React.Dispatch<React.SetStateAction<string | null>>;
  profileToRename: BrowserProfile | null;
  newProfileName: string;
  isRenamingSaving: boolean;
  renameError: string | null;

  // Launch/stop helpers
  setLaunchingProfiles: React.Dispatch<React.SetStateAction<Set<string>>>;
  setStoppingProfiles: React.Dispatch<React.SetStateAction<Set<string>>>;
  onKillProfile: (profile: BrowserProfile) => void | Promise<void>;
  onLaunchProfile: (profile: BrowserProfile) => void | Promise<unknown>;

  // Overflow actions
  onAssignProfilesToGroup?: (profileIds: string[]) => void;
  onConfigureWayfern?: (profile: BrowserProfile) => void;
  onCloneProfile?: (profile: BrowserProfile) => void;
  onCopyCookiesToProfile?: (profile: BrowserProfile) => void;
  onOpenCookieManagement?: (profile: BrowserProfile) => void;

  // Traffic snapshots (lightweight real-time data)
  trafficSnapshots: Record<string, TrafficSnapshot>;
  onOpenTrafficDialog?: (profileId: string) => void;

  // Sync
  syncStatuses: Record<string, { status: string; error?: string }>;
  onOpenProfileSyncDialog?: (profile: BrowserProfile) => void;
  onToggleProfileSync?: (profile: BrowserProfile) => void;
  crossOsUnlocked?: boolean;
  syncUnlocked?: boolean;

  // Country proxy creation (inline in proxy dropdown)
  countries: LocationItem[];
  canCreateLocationProxy: boolean;
  loadCountries: () => Promise<void>;
  handleCreateCountryProxy: (
    profileId: string,
    country: LocationItem,
  ) => Promise<void>;

  // Team locks
  isProfileLockedByAnother: (profileId: string) => boolean;
  getProfileLockEmail: (profileId: string) => string | undefined;

  // Remote execution.
  //
  // `getRemoteHandoff` is the authoritative answer to "can this be opened
  // here", read from the same store the backend gate reads. The team-lock cache
  // above cannot serve it: it refreshes on a 30-second poll and says nothing at
  // all about a session that has finished but whose work has not been pulled
  // back yet.
  getRemoteHandoff: (profileId: string) => RemoteHandoffState | null;

  // Synchronizer
  getProfileSyncInfo: (profileId: string) =>
    | {
        session: SyncSessionInfo;
        isLeader: boolean;
        failedAtUrl: string | null;
      }
    | undefined;
  onLaunchWithSync: (profile: BrowserProfile) => void;

  // Cookie Bot
  cookieBotUnlocked: boolean;
  /** Narrow container: the bot column shows its state mark without the label. */
  cookieBotCompact: boolean;
  getProfileBotState: (profileId: string) => ProfileBotState;
  /** A run this desktop has just asked for, before the stream confirms it. */
  botPendingProfiles: Set<string>;
  onBotEnrol: (profile: BrowserProfile) => void;
  onBotEdit: (profile: BrowserProfile, schedule: CookieBotSchedule) => void;
  onBotRunNow: (profile: BrowserProfile) => void;
  onBotStopRun: (runId: string) => void;
  onBotViewActivity: (profile: BrowserProfile) => void;
  onBotUnenrol: (profile: BrowserProfile) => void;
  /**
   * Perform the repair a failed preflight names, or null when this surface has
   * no way to reach it. A reason with no affordance is what the menu showed
   * before: "No proxy or VPN · Attach a proxy" as inert label text that reads
   * like a button and answers no click.
   */
  onBotFix: ((profile: BrowserProfile, fix: PreflightFix) => void) | null;
}

/**
 * Below this container width the bot column keeps its state mark but drops the
 * "next run" label: an operator still sees at a glance which rows are enrolled
 * and which are running, and the row menu stays reachable, without taking the
 * width the name needs.
 */
const BOT_LABEL_WIDTH = 880;

/** Below this the bot column leaves entirely, like the other low-priority ones. */
const BOT_COLUMN_MIN_WIDTH = 400;

/** Bulk enrolments of this size or larger are confirmed, as run and stop are. */
const BULK_ENROL_CONFIRM_THRESHOLD = 10;

/**
 * Run statuses that mean the browser never came up.
 *
 * `POST /cookie-bot/runs` answers 202 with the run row it recorded, so a
 * refusal — no capacity, no sites, a profile someone else has open — arrives as
 * a successful response carrying a terminal status.
 */
const RUN_DID_NOT_START = new Set(["skipped", "failed", "cancelled"]);

interface SyncStatusDot {
  color: string;
  tooltip: string;
  animate: boolean;
  encrypted: boolean;
}

function getProfileSyncStatusDot(
  profile: BrowserProfile,
  liveStatus:
    | "syncing"
    | "waiting"
    | "synced"
    | "error"
    | "disabled"
    | undefined,
  t: (key: string, options?: Record<string, unknown>) => string,
  errorMessage?: string,
): SyncStatusDot | null {
  const encrypted = profile.sync_mode === "Encrypted";
  const status =
    liveStatus ??
    (profile.sync_mode && profile.sync_mode !== "Disabled"
      ? "synced"
      : "disabled");

  switch (status) {
    case "syncing":
      return {
        color: "bg-warning",
        tooltip: t("profileTable.syncTooltipSyncing"),
        animate: true,
        encrypted,
      };
    case "waiting":
      return {
        color: "bg-warning",
        tooltip: t("profileTable.syncTooltipCloseToSync"),
        animate: false,
        encrypted,
      };
    case "synced":
      return {
        color: "bg-success",
        tooltip: profile.last_sync
          ? t("profileTable.syncTooltipSyncedAt", {
              time: new Date(profile.last_sync * 1000).toLocaleString(),
            })
          : t("profileTable.syncTooltipSynced"),
        animate: false,
        encrypted,
      };
    case "error":
      return {
        color: "bg-destructive",
        tooltip: errorMessage
          ? t("profileTable.syncTooltipErrorWith", { error: errorMessage })
          : t("profileTable.syncTooltipError"),
        animate: false,
        encrypted,
      };
    case "disabled":
      if (profile.last_sync) {
        return {
          color: "bg-muted-foreground",
          tooltip: t("profileTable.syncTooltipDisabledWithLast", {
            time: formatRelativeTime(profile.last_sync),
          }),
          animate: false,
          encrypted: false,
        };
      }
      return null;
    default:
      return null;
  }
}

// Inline extension-group dropdown for the Ext column. Matches the
// proxy column's Popover-style picker — no nested dialog.
function ExtCell({
  profile,
  meta,
}: {
  profile: BrowserProfile;
  meta: TableMeta;
}) {
  const [open, setOpen] = React.useState(false);
  const [isSaving, setIsSaving] = React.useState(false);
  const groupId = profile.extension_group_id ?? null;
  const group = groupId
    ? meta.extensionGroups.find((g) => g.id === groupId)
    : undefined;
  const label = group?.name ?? meta.t("profiles.table.extDefault");

  const onPick = async (nextId: string | null) => {
    setIsSaving(true);
    try {
      await invoke("assign_extension_group_to_profile", {
        profileId: profile.id,
        extensionGroupId: nextId,
      });
    } catch (err) {
      console.error("Failed to assign extension group:", err);
    } finally {
      setIsSaving(false);
      setOpen(false);
    }
  };

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <button
          type="button"
          disabled={isSaving}
          className="flex h-7 w-full items-center gap-1.5 rounded px-1.5 text-left text-xs text-muted-foreground transition-colors duration-100 hover:bg-accent hover:text-accent-foreground disabled:opacity-50"
        >
          <LuPuzzle className="size-3 shrink-0" />
          <span className="flex-1 truncate" title={label}>
            {label}
          </span>
          <LuChevronDown className="size-3 shrink-0 text-muted-foreground" />
        </button>
      </PopoverTrigger>
      <PopoverContent className="w-56 p-0" align="start">
        <Command>
          <CommandInput placeholder={meta.t("profiles.table.extSearch")} />
          <CommandList>
            <CommandEmpty>{meta.t("profiles.table.extEmpty")}</CommandEmpty>
            <CommandGroup>
              <CommandItem
                value="__default__"
                onSelect={() => {
                  void onPick(null);
                }}
              >
                {groupId === null && <LuCheck className="mr-2 size-3.5" />}
                <span className={groupId === null ? "" : "ml-5"}>
                  {meta.t("profiles.table.extDefault")}
                </span>
              </CommandItem>
              {meta.extensionGroups.map((g) => (
                <CommandItem
                  key={g.id}
                  value={g.name}
                  onSelect={() => {
                    void onPick(g.id);
                  }}
                >
                  {groupId === g.id && <LuCheck className="mr-2 size-3.5" />}
                  <span className={groupId === g.id ? "" : "ml-5"}>
                    {g.name}
                  </span>
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
}

// Inline DNS blocklist dropdown — same Popover/Command pattern as Ext.
function DnsCell({
  profile,
  meta,
}: {
  profile: BrowserProfile;
  meta: TableMeta;
}) {
  const [open, setOpen] = React.useState(false);
  const [isSaving, setIsSaving] = React.useState(false);
  const level = profile.dns_blocklist ?? null;
  const LEVELS = DNS_BLOCKLIST_LEVELS;
  const currentLabel =
    level === null
      ? null
      : (LEVELS.find((l) => l.value === level)?.labelKey ?? null);

  const onPick = async (nextLevel: string | null) => {
    setIsSaving(true);
    try {
      await invoke("update_profile_dns_blocklist", {
        profileId: profile.id,
        dnsBlocklist: nextLevel,
      });
    } catch (err) {
      console.error("Failed to update DNS blocklist:", err);
    } finally {
      setIsSaving(false);
      setOpen(false);
    }
  };

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <button
          type="button"
          data-onborda="dns-blocklist"
          disabled={isSaving}
          className="flex h-7 w-full items-center gap-1.5 rounded px-1.5 text-left text-xs text-muted-foreground transition-colors duration-100 hover:bg-accent hover:text-accent-foreground disabled:opacity-50"
          title={
            level
              ? meta.t("profiles.table.dnsLevel", { level })
              : meta.t("dnsBlocklist.none")
          }
        >
          <FiWifi className="size-3 shrink-0" />
          <span className="flex-1 truncate text-[11px] tracking-wide">
            {currentLabel ? meta.t(currentLabel) : "—"}
          </span>
          <LuChevronDown className="size-3 shrink-0 text-muted-foreground" />
        </button>
      </PopoverTrigger>
      <PopoverContent className="w-48 p-0" align="start">
        <Command>
          <CommandList>
            <CommandGroup>
              <CommandItem
                value="__none__"
                onSelect={() => {
                  void onPick(null);
                }}
              >
                {level === null && <LuCheck className="mr-2 size-3.5" />}
                <span className={level === null ? "" : "ml-5"}>
                  {meta.t("dnsBlocklist.none")}
                </span>
              </CommandItem>
              {LEVELS.map((l) => (
                <CommandItem
                  key={l.value}
                  value={l.value}
                  onSelect={() => {
                    void onPick(l.value);
                  }}
                >
                  {level === l.value && <LuCheck className="mr-2 size-3.5" />}
                  <span className={level === l.value ? "" : "ml-5"}>
                    {meta.t(l.labelKey)}
                  </span>
                </CommandItem>
              ))}
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
}

const TagsCell = React.memo<{
  profile: BrowserProfile;
  isDisabled: boolean;
  tagsOverrides: Record<string, string[]>;
  allTags: string[];
  setAllTags: React.Dispatch<React.SetStateAction<string[]>>;
  openTagsEditorFor: string | null;
  setOpenTagsEditorFor: React.Dispatch<React.SetStateAction<string | null>>;
  setTagsOverrides: React.Dispatch<
    React.SetStateAction<Record<string, string[]>>
  >;
}>(
  ({
    profile,
    isDisabled,
    tagsOverrides,
    allTags,
    setAllTags,
    openTagsEditorFor,
    setOpenTagsEditorFor,
    setTagsOverrides,
  }) => {
    const { t: translate } = useTranslation();
    const effectiveTags: string[] = Object.hasOwn(tagsOverrides, profile.id)
      ? tagsOverrides[profile.id]
      : (profile.tags ?? []);

    const valueOptions: Option[] = React.useMemo(
      () => effectiveTags.map((t) => ({ value: t, label: t })),
      [effectiveTags],
    );
    const allOptions: Option[] = React.useMemo(
      () => allTags.map((t) => ({ value: t, label: t })),
      [allTags],
    );

    const onTagsChange = React.useCallback(
      async (newTagsRaw: string[]) => {
        // Dedupe tags
        const seen = new Set<string>();
        const newTags: string[] = [];
        for (const t of newTagsRaw) {
          if (!seen.has(t)) {
            seen.add(t);
            newTags.push(t);
          }
        }
        setTagsOverrides((prev) => ({ ...prev, [profile.id]: newTags }));
        try {
          await invoke<BrowserProfile>("update_profile_tags", {
            profileId: profile.id,
            tags: newTags,
          });
          setAllTags((prev) => {
            const next = new Set(prev);
            for (const t of newTags) next.add(t);
            return Array.from(next).sort();
          });
        } catch (error) {
          console.error("Failed to update tags:", error);
        }
      },
      [profile.id, setTagsOverrides, setAllTags],
    );

    const handleChange = React.useCallback(
      async (opts: Option[]) => {
        const newTagsRaw = opts.map((o) => o.value);
        await onTagsChange(newTagsRaw);
      },
      [onTagsChange],
    );

    const containerRef = React.useRef<HTMLDivElement | null>(null);
    const editorRef = React.useRef<HTMLDivElement | null>(null);
    const [visibleCount, setVisibleCount] = React.useState<number>(
      effectiveTags.length,
    );
    const [isFocused, setIsFocused] = React.useState(false);

    React.useLayoutEffect(() => {
      // Only measure when not editing this profile's tags
      if (openTagsEditorFor === profile.id) return;
      const container = containerRef.current;
      if (!container) return;

      let timeoutId: number | undefined;
      const compute = () => {
        if (timeoutId) clearTimeout(timeoutId);
        timeoutId = window.setTimeout(() => {
          const available = container.clientWidth;
          if (available <= 0) return;
          const canvas = document.createElement("canvas");
          const ctx = canvas.getContext("2d");
          if (!ctx) return;
          const style = window.getComputedStyle(container);
          const font = `${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
          ctx.font = font;
          const padding = 16;
          const gap = 4;
          let used = 0;
          let count = 0;
          for (let i = 0; i < effectiveTags.length; i++) {
            const text = effectiveTags[i];
            const width = Math.ceil(ctx.measureText(text).width) + padding;
            const remaining = effectiveTags.length - (i + 1);
            let extra = 0;
            if (remaining > 0) {
              const plusText = `+${remaining}`;
              extra = Math.ceil(ctx.measureText(plusText).width) + padding;
            }
            const nextUsed =
              used +
              (used > 0 ? gap : 0) +
              width +
              (remaining > 0 ? gap + extra : 0);
            if (nextUsed <= available) {
              used += (used > 0 ? gap : 0) + width;
              count = i + 1;
            } else {
              break;
            }
          }
          setVisibleCount(count);
        }, 16); // Debounce with RAF timing
      };
      compute();
      const ro = new ResizeObserver(compute);
      ro.observe(container);
      return () => {
        ro.disconnect();
        if (timeoutId) clearTimeout(timeoutId);
      };
    }, [effectiveTags, openTagsEditorFor, profile.id]);

    React.useEffect(() => {
      if (openTagsEditorFor !== profile.id) return;
      const handleClick = (e: MouseEvent) => {
        const target = e.target as Node | null;
        if (
          editorRef.current &&
          target &&
          !editorRef.current.contains(target)
        ) {
          setOpenTagsEditorFor(null);
        }
      };
      document.addEventListener("mousedown", handleClick);
      return () => {
        document.removeEventListener("mousedown", handleClick);
      };
    }, [openTagsEditorFor, profile.id, setOpenTagsEditorFor]);

    React.useEffect(() => {
      if (openTagsEditorFor === profile.id && editorRef.current) {
        // Focus the inner input of MultipleSelector on open
        const inputEl = editorRef.current.querySelector("input");
        if (inputEl) {
          inputEl.focus();
        }
      }
    }, [openTagsEditorFor, profile.id]);

    if (openTagsEditorFor !== profile.id) {
      const hiddenCount = Math.max(0, effectiveTags.length - visibleCount);
      const ButtonContent = (
        <button
          type="button"
          ref={containerRef as unknown as React.RefObject<HTMLButtonElement>}
          className={cn(
            "flex h-6 w-full cursor-pointer items-center gap-1 overflow-hidden rounded border-none bg-transparent px-2 py-1",
            isDisabled
              ? "cursor-not-allowed opacity-60"
              : "cursor-pointer hover:bg-muted",
          )}
          onClick={() => {
            if (!isDisabled) setOpenTagsEditorFor(profile.id);
          }}
        >
          {effectiveTags.slice(0, visibleCount).map((t) => (
            <Badge key={t} variant="secondary" className="px-2 py-0 text-xs">
              {t}
            </Badge>
          ))}
          {effectiveTags.length === 0 && (
            <span className="text-muted-foreground">
              {translate("profileTable.noTags")}
            </span>
          )}
          {hiddenCount > 0 && (
            <Badge variant="outline" className="px-2 py-0 text-xs">
              +{hiddenCount}
            </Badge>
          )}
        </button>
      );

      return (
        <div className="h-6 w-full cursor-pointer">
          <Tooltip>
            <TooltipTrigger asChild>{ButtonContent}</TooltipTrigger>
            {hiddenCount > 0 && (
              <TooltipContent className="max-w-[320px]">
                <div className="flex flex-wrap gap-1">
                  {effectiveTags.map((t) => (
                    <Badge
                      key={t}
                      variant="secondary"
                      className="px-2 py-0 text-xs"
                    >
                      {t}
                    </Badge>
                  ))}
                </div>
              </TooltipContent>
            )}
          </Tooltip>
        </div>
      );
    }

    return (
      <div
        className={cn(
          "relative h-6 w-full",
          isDisabled && "pointer-events-none opacity-60",
        )}
      >
        <div
          ref={editorRef}
          className="absolute top-0 left-0 z-50 min-h-6 w-40 rounded-md bg-popover shadow-md"
        >
          <MultipleSelector
            value={valueOptions}
            options={allOptions}
            onChange={(opts) => void handleChange(opts)}
            creatable
            selectFirstItem={false}
            placeholder={
              effectiveTags.length === 0
                ? translate("profileTable.addTagsPlaceholder")
                : ""
            }
            className={cn(
              "border-0! bg-transparent focus-within:ring-0!",
              "[&_div:first-child]:border-0! [&_div:first-child]:ring-0! [&_div:first-child]:focus-within:ring-0!",
              "[&_div:first-child]:min-h-6! [&_div:first-child]:px-2! [&_div:first-child]:py-1!",
              "[&_div:first-child>div]:h-6! [&_div:first-child>div]:items-center",
              "[&_input]:mt-0! [&_input]:ml-0! [&_input]:px-0!",
              !isFocused && "[&_div:first-child>div]:justify-center",
            )}
            badgeClassName="shrink-0"
            inputProps={{
              className: "!py-0 text-sm caret-current !ml-0 !mt-0 !px-0",
              onKeyDown: (e) => {
                if (e.key === "Escape") setOpenTagsEditorFor(null);
              },
              onFocus: () => {
                setIsFocused(true);
              },
              onBlur: () => {
                setIsFocused(false);
              },
            }}
          />
        </div>
      </div>
    );
  },
);

TagsCell.displayName = "TagsCell";

const NonHoverableTooltip = React.memo<{
  children: React.ReactNode;
  content: React.ReactNode;
  sideOffset?: number;
  alignOffset?: number;
  horizontalOffset?: number;
}>(
  ({
    children,
    content,
    sideOffset = 4,
    alignOffset = 0,
    horizontalOffset = 0,
  }) => {
    const [isOpen, setIsOpen] = React.useState(false);

    return (
      <Tooltip open={isOpen} onOpenChange={setIsOpen}>
        <TooltipTrigger
          asChild
          onMouseEnter={() => {
            setIsOpen(true);
          }}
          onMouseLeave={() => {
            setIsOpen(false);
          }}
        >
          {children}
        </TooltipTrigger>
        <TooltipContent
          sideOffset={sideOffset}
          alignOffset={alignOffset}
          arrowOffset={horizontalOffset}
          onPointerEnter={(e) => {
            e.preventDefault();
          }}
          onPointerLeave={() => {
            setIsOpen(false);
          }}
          className="pointer-events-none"
          style={
            horizontalOffset !== 0
              ? { transform: `translateX(${horizontalOffset}px)` }
              : undefined
          }
        >
          {content}
        </TooltipContent>
      </Tooltip>
    );
  },
);

NonHoverableTooltip.displayName = "NonHoverableTooltip";

// CSS-truncated text whose tooltip only appears when the text actually
// overflows its column (measured on hover, so it tracks live resizes).
const OverflowTooltipText = React.memo<{
  text: string;
  className?: string;
}>(({ text, className }) => {
  const textRef = React.useRef<HTMLSpanElement | null>(null);
  const [isOverflowing, setIsOverflowing] = React.useState(false);

  return (
    <Tooltip
      onOpenChange={(open) => {
        if (!open) return;
        const el = textRef.current;
        if (el) setIsOverflowing(el.scrollWidth > el.clientWidth);
      }}
    >
      <TooltipTrigger asChild>
        <span
          ref={textRef}
          className={cn("block max-w-full min-w-0 truncate", className)}
        >
          {text}
        </span>
      </TooltipTrigger>
      {isOverflowing && <TooltipContent>{text}</TooltipContent>}
    </Tooltip>
  );
});

OverflowTooltipText.displayName = "OverflowTooltipText";

// Must be rendered inside a <Popover>; the tooltip shows the full assignment
// name only when it is truncated in the cell.
const ProxyCellTrigger = React.memo<{
  displayName: string;
  hasAssignment: boolean;
  vpnBadge: string | null;
  isDisabled: boolean;
}>(({ displayName, hasAssignment, vpnBadge, isDisabled }) => {
  const textRef = React.useRef<HTMLSpanElement | null>(null);
  const [isOverflowing, setIsOverflowing] = React.useState(false);

  return (
    <Tooltip
      onOpenChange={(open) => {
        if (!open) return;
        const el = textRef.current;
        if (el) setIsOverflowing(el.scrollWidth > el.clientWidth);
      }}
    >
      <TooltipTrigger asChild>
        <PopoverTrigger asChild>
          <span
            className={cn(
              "flex max-w-full min-w-0 items-center gap-2 rounded px-2 py-1",
              isDisabled
                ? "pointer-events-none cursor-not-allowed opacity-60"
                : "cursor-pointer hover:bg-muted",
            )}
          >
            {vpnBadge && (
              <Badge
                variant="outline"
                className="shrink-0 px-1 py-0 text-[10px] leading-tight"
              >
                {vpnBadge}
              </Badge>
            )}
            <span
              ref={textRef}
              className={cn(
                "min-w-0 truncate text-sm",
                !hasAssignment && "text-muted-foreground",
              )}
            >
              {displayName}
            </span>
          </span>
        </PopoverTrigger>
      </TooltipTrigger>
      {hasAssignment && isOverflowing && (
        <TooltipContent>{displayName}</TooltipContent>
      )}
    </Tooltip>
  );
});

ProxyCellTrigger.displayName = "ProxyCellTrigger";

const NoteCell = React.memo<{
  profile: BrowserProfile;
  isDisabled: boolean;
  noteOverrides: Record<string, string | null>;
  openNoteEditorFor: string | null;
  setOpenNoteEditorFor: React.Dispatch<React.SetStateAction<string | null>>;
  setNoteOverrides: React.Dispatch<
    React.SetStateAction<Record<string, string | null>>
  >;
}>(
  ({
    profile,
    isDisabled,
    noteOverrides,
    openNoteEditorFor,
    setOpenNoteEditorFor,
    setNoteOverrides,
  }) => {
    const { t } = useTranslation();
    const effectiveNote: string | null = Object.hasOwn(
      noteOverrides,
      profile.id,
    )
      ? noteOverrides[profile.id]
      : (profile.note ?? null);

    const onNoteChange = React.useCallback(
      async (newNote: string | null) => {
        const trimmedNote = newNote?.trim() ?? null;
        setNoteOverrides((prev) => ({ ...prev, [profile.id]: trimmedNote }));
        try {
          await invoke<BrowserProfile>("update_profile_note", {
            profileId: profile.id,
            note: trimmedNote,
          });
        } catch (error) {
          console.error("Failed to update note:", error);
        }
      },
      [profile.id, setNoteOverrides],
    );

    const editorRef = React.useRef<HTMLDivElement | null>(null);
    const textareaRef = React.useRef<HTMLTextAreaElement | null>(null);
    const [noteValue, setNoteValue] = React.useState(effectiveNote ?? "");

    // Update local state when effective note changes (from outside)
    React.useEffect(() => {
      if (openNoteEditorFor !== profile.id) {
        setNoteValue(effectiveNote ?? "");
      }
    }, [effectiveNote, openNoteEditorFor, profile.id]);

    // Auto-resize textarea on open
    React.useEffect(() => {
      if (openNoteEditorFor === profile.id && textareaRef.current) {
        const textarea = textareaRef.current;
        textarea.style.height = "auto";
        textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`;
      }
    }, [openNoteEditorFor, profile.id]);

    const handleTextareaChange = React.useCallback(
      (e: React.ChangeEvent<HTMLTextAreaElement>) => {
        const newValue = e.target.value;
        setNoteValue(newValue);
        // Auto-resize
        const textarea = e.target;
        textarea.style.height = "auto";
        textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`;
      },
      [],
    );

    React.useEffect(() => {
      if (openNoteEditorFor !== profile.id) return;
      const handleClick = (e: MouseEvent) => {
        const target = e.target as Node | null;
        if (
          editorRef.current &&
          target &&
          !editorRef.current.contains(target)
        ) {
          const currentValue = textareaRef.current?.value ?? "";
          void onNoteChange(currentValue);
          setOpenNoteEditorFor(null);
        }
      };
      document.addEventListener("mousedown", handleClick);
      return () => {
        document.removeEventListener("mousedown", handleClick);
      };
    }, [openNoteEditorFor, profile.id, setOpenNoteEditorFor, onNoteChange]);

    React.useEffect(() => {
      if (openNoteEditorFor === profile.id && textareaRef.current) {
        textareaRef.current.focus();
        // Move cursor to end
        const len = textareaRef.current.value.length;
        textareaRef.current.setSelectionRange(len, len);
      }
    }, [openNoteEditorFor, profile.id]);

    const displayNote = effectiveNote ?? "";
    const showTooltip = displayNote.length > 0;

    if (openNoteEditorFor !== profile.id) {
      return (
        <div className="min-h-6 w-full">
          <Tooltip>
            <TooltipTrigger asChild>
              <button
                type="button"
                className={cn(
                  "flex min-h-6 w-full min-w-0 items-center rounded border-none bg-transparent px-2 py-1 text-left",
                  isDisabled
                    ? "cursor-not-allowed opacity-60"
                    : "cursor-pointer hover:bg-muted",
                )}
                onClick={() => {
                  if (!isDisabled) {
                    setNoteValue(effectiveNote ?? "");
                    setOpenNoteEditorFor(profile.id);
                  }
                }}
              >
                <span
                  className={cn(
                    "block w-full truncate text-sm",
                    !effectiveNote && "text-muted-foreground",
                  )}
                >
                  {effectiveNote ? displayNote : t("profiles.note.empty")}
                </span>
              </button>
            </TooltipTrigger>
            {showTooltip && (
              <TooltipContent className="max-w-[320px]">
                <p className="wrap-break-word whitespace-pre-wrap">
                  {effectiveNote ?? t("profiles.note.empty")}
                </p>
              </TooltipContent>
            )}
          </Tooltip>
        </div>
      );
    }

    return (
      <div
        className={cn(
          "relative w-full",
          isDisabled && "pointer-events-none opacity-60",
        )}
      >
        <div
          ref={editorRef}
          className="absolute top-[-15px] -left-px z-50 min-h-6 w-60 rounded-md border bg-popover shadow-md"
        >
          <textarea
            ref={textareaRef}
            value={noteValue}
            onChange={handleTextareaChange}
            onKeyDown={(e) => {
              if (e.key === "Escape") {
                setNoteValue(effectiveNote ?? "");
                setOpenNoteEditorFor(null);
              } else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
                void onNoteChange(noteValue);
                setOpenNoteEditorFor(null);
              }
            }}
            onBlur={() => {
              void onNoteChange(noteValue);
              setOpenNoteEditorFor(null);
            }}
            placeholder={t("profiles.note.placeholder")}
            className="max-h-[200px] min-h-6 w-full resize-none border-0 bg-transparent px-2 py-1 text-sm focus:ring-0 focus:outline-none"
            style={{
              overflow: "auto",
            }}
            rows={1}
          />
        </div>
      </div>
    );
  },
);

NoteCell.displayName = "NoteCell";

/** `HH:MM` of the server's own next-run instant, in this machine's locale. */
function formatNextRun(schedule: CookieBotSchedule): string | null {
  if (!schedule.next_run_at) return null;
  const date = new Date(schedule.next_run_at);
  if (Number.isNaN(date.getTime())) return null;
  return date.toLocaleTimeString(undefined, {
    hour: "2-digit",
    minute: "2-digit",
  });
}

/**
 * One row's Cookie Bot state, and the row's bot actions.
 *
 * The whole cell is the menu trigger. A dedicated kebab would cost another
 * column of width in a table that is already dense, and the state mark is
 * exactly the thing an operator reaches for when they want to change it. The
 * dot, the tone and the phase wording all come from the shared status
 * vocabulary, so a run cannot read one way here and another on the Cookie Bot
 * page.
 */
const BotCell = React.memo<{
  profile: BrowserProfile;
  meta: TableMeta;
}>(({ profile, meta }) => {
  // Own `t` rather than `meta.t`: the shared status helpers take a real
  // `TFunction`, and every other cell in this file resolves it the same way.
  const { t } = useTranslation();
  const { schedule, liveSession } = meta.getProfileBotState(profile.id);
  const check = preflight(profile);
  const isPending = meta.botPendingProfiles.has(profile.id);
  const isLive = liveSession !== null;
  const nextRun = schedule ? formatNextRun(schedule) : null;

  // The server computes "why tonight would be refused" on every read, precisely
  // so a detached proxy is visible in the afternoon rather than announcing
  // itself as a skipped run at 02:00. Dropping it left a broken enrolment
  // showing a healthy dot and a next-run time it could never keep.
  const blockedReason =
    schedule?.enabled && schedule.blocked_by
      ? outcomeLabel(t, schedule.blocked_by)
      : null;

  // `provisioning` is a transfer in progress — the one meaning the app already
  // reserves a pulsing dot for. Nothing else pulses.
  const isPreparing = liveSession?.state === "provisioning" || isPending;
  const tone = liveSession
    ? sessionTone(liveSession)
    : isPending
      ? "warning"
      : schedule
        ? schedule.enabled && !blockedReason
          ? "muted"
          : "warning"
        : null;

  const label = isPending
    ? t("cookieBot.status.provisioning")
    : liveSession
      ? sessionPhaseLabel(t, liveSession)
      : schedule
        ? !schedule.enabled
          ? t("cookieBot.state.paused")
          : (blockedReason ?? nextRun ?? t("cookieBot.state.enrolled"))
        : "—";

  const summary = schedule
    ? blockedReason
      ? t("cookieBot.state.blocked", { reason: blockedReason })
      : t("cookieBot.state.summary", {
          cadence: describeCadence(t, schedule.days_mask),
          time: minutesToClock(schedule.run_at_minute),
        })
    : check.eligible
      ? t("cookieBot.state.notEnrolled")
      : // The repair is its own menu item when this surface can reach it, so
        // the label stays a statement instead of looking like a second button.
        [
          preflightReason(t, check),
          meta.onBotFix ? null : preflightFixLabel(t, check.fix),
        ]
          .filter(Boolean)
          .join(" · ");

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <button
          type="button"
          aria-label={t("cookieBot.state.rowMenu", { name: profile.name })}
          className="flex h-9 w-full min-w-0 cursor-pointer items-center gap-1.5 rounded border-none bg-transparent px-1.5 text-left transition-colors duration-100 hover:bg-muted"
        >
          {tone ? (
            <StatusDot tone={tone} pulse={isPreparing} className="size-1.5" />
          ) : (
            <span aria-hidden="true" className="size-1.5 shrink-0" />
          )}
          {!meta.cookieBotCompact && (
            <span
              className={cn(
                "min-w-0 truncate text-xs tabular-nums",
                isLive || isPending
                  ? "text-foreground"
                  : "text-muted-foreground",
              )}
            >
              {label}
            </span>
          )}
        </button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end" className="min-w-56">
        <DropdownMenuLabel className="font-normal text-muted-foreground">
          {summary}
        </DropdownMenuLabel>
        <DropdownMenuSeparator />
        {schedule ? (
          <>
            <DropdownMenuItem
              onClick={() => {
                meta.onBotEdit(profile, schedule);
              }}
            >
              {t("cookieBot.actions.editSchedule")}
            </DropdownMenuItem>
            {liveSession?.run_id ? (
              <DropdownMenuItem
                onClick={() => {
                  if (liveSession.run_id) {
                    meta.onBotStopRun(liveSession.run_id);
                  }
                }}
              >
                {t("cookieBot.running.stop")}
              </DropdownMenuItem>
            ) : (
              <DropdownMenuItem
                disabled={isLive || isPending}
                onClick={() => {
                  meta.onBotRunNow(profile);
                }}
              >
                {t("cookieBot.actions.runNow")}
              </DropdownMenuItem>
            )}
            <DropdownMenuItem
              onClick={() => {
                meta.onBotViewActivity(profile);
              }}
            >
              {t("cookieBot.actions.viewActivity")}
            </DropdownMenuItem>
            <DropdownMenuSeparator />
            <DropdownMenuItem
              variant="destructive"
              onClick={() => {
                meta.onBotUnenrol(profile);
              }}
            >
              {t("cookieBot.schedule.unenrol")}
            </DropdownMenuItem>
          </>
        ) : (
          <>
            {!check.eligible && check.fix && meta.onBotFix && (
              <DropdownMenuItem
                onClick={() => {
                  meta.onBotFix?.(profile, check.fix as PreflightFix);
                }}
              >
                {preflightFixLabel(t, check.fix)}
              </DropdownMenuItem>
            )}
            <DropdownMenuItem
              disabled={!check.eligible}
              onClick={() => {
                meta.onBotEnrol(profile);
              }}
            >
              {t("cookieBot.actions.enrol")}
            </DropdownMenuItem>
          </>
        )}
      </DropdownMenuContent>
    </DropdownMenu>
  );
});

BotCell.displayName = "BotCell";

interface ProfilesDataTableProps {
  profiles: BrowserProfile[];
  onLaunchProfile: (profile: BrowserProfile) => void | Promise<unknown>;
  onKillProfile: (profile: BrowserProfile) => void | Promise<void>;
  onCloneProfile: (profile: BrowserProfile) => void | Promise<void>;
  onDeleteProfile: (profile: BrowserProfile) => void | Promise<void>;
  onRenameProfile: (profileId: string, newName: string) => Promise<void>;
  onConfigureWayfern: (profile: BrowserProfile) => void;
  onCopyCookiesToProfile?: (profile: BrowserProfile) => void;
  onOpenCookieManagement?: (profile: BrowserProfile) => void;
  runningProfiles: Set<string>;
  isUpdating: (browser: string) => boolean;
  onDeleteSelectedProfiles: (profileIds: string[]) => Promise<void>;
  onAssignProfilesToGroup: (profileIds: string[]) => void;
  /** Opens proxy assignment for a specific set of profiles. */
  onAssignProfilesToProxy?: (profileIds: string[]) => void;
  selectedGroupId: string | null;
  selectedProfiles: string[];
  onSelectedProfilesChange: Dispatch<SetStateAction<string[]>>;
  onBulkDelete?: () => void;
  onBulkGroupAssignment?: () => void;
  onBulkProxyAssignment?: () => void;
  onBulkCopyCookies?: () => void;
  onBulkRun?: () => void;
  onBulkStop?: () => void;
  bulkActionsUnlocked?: boolean;
  onBulkExtensionGroupAssignment?: () => void;
  onAssignExtensionGroup?: (profileIds: string[]) => void;
  onOpenProfileSyncDialog?: (profile: BrowserProfile) => void;
  onToggleProfileSync?: (profile: BrowserProfile) => void;
  crossOsUnlocked?: boolean;
  syncUnlocked?: boolean;
  getProfileSyncInfo?: (profileId: string) =>
    | {
        session: SyncSessionInfo;
        isLeader: boolean;
        failedAtUrl: string | null;
      }
    | undefined;
  onLaunchWithSync?: (profile: BrowserProfile) => void;
  onSetPassword?: (profile: BrowserProfile) => void;
  onChangePassword?: (profile: BrowserProfile) => void;
  onRemovePassword?: (profile: BrowserProfile) => void;
  /**
   * When provided, the info dialog is controlled by the parent. Allows the
   * command palette in page.tsx to open the dialog directly without lifting
   * every other piece of internal table state.
   */
  infoDialogProfile?: BrowserProfile | null;
  onInfoDialogProfileChange?: (profile: BrowserProfile | null) => void;
  /** Initial data load in flight — renders skeleton rows instead of "empty". */
  isLoading?: boolean;
  /** True when the app has zero profiles overall (not just a filtered view). */
  showOnboardingEmptyState?: boolean;
  onCreateProfile?: () => void;
  onImportProfiles?: () => void;
}

export function ProfilesDataTable({
  profiles,
  onLaunchProfile,
  onKillProfile,
  onCloneProfile,
  onDeleteProfile,
  onRenameProfile,
  onConfigureWayfern,
  onCopyCookiesToProfile,
  onOpenCookieManagement,
  runningProfiles,
  isUpdating,
  onAssignProfilesToGroup,
  onAssignProfilesToProxy,
  selectedProfiles,
  onSelectedProfilesChange,
  onBulkDelete,
  onBulkGroupAssignment,
  onBulkProxyAssignment,
  onBulkCopyCookies,
  onBulkRun,
  onBulkStop,
  bulkActionsUnlocked = false,
  onBulkExtensionGroupAssignment,
  onAssignExtensionGroup,
  onOpenProfileSyncDialog,
  onToggleProfileSync,
  crossOsUnlocked = false,
  syncUnlocked = false,
  getProfileSyncInfo,
  onLaunchWithSync,
  onSetPassword,
  onChangePassword,
  onRemovePassword,
  infoDialogProfile,
  onInfoDialogProfileChange,
  isLoading = false,
  showOnboardingEmptyState = false,
  onCreateProfile,
  onImportProfiles,
}: ProfilesDataTableProps) {
  const { t } = useTranslation();
  const { getTableSorting, updateSorting, isLoaded } = useTableSorting();
  const [sorting, setSorting] = React.useState<SortingState>([]);

  // Sync external selectedProfiles with table's row selection state
  const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});
  const prevSelectedProfilesRef = React.useRef<string[]>(selectedProfiles);

  // Update row selection when external selectedProfiles changes
  React.useEffect(() => {
    // Only update if selectedProfiles actually changed
    if (
      prevSelectedProfilesRef.current.length !== selectedProfiles.length ||
      !prevSelectedProfilesRef.current.every((id) =>
        selectedProfiles.includes(id),
      )
    ) {
      const newSelection: RowSelectionState = {};
      for (const profileId of selectedProfiles) {
        newSelection[profileId] = true;
      }
      setRowSelection(newSelection);
      prevSelectedProfilesRef.current = selectedProfiles;
      // When the parent clears the selection (e.g. after a bulk action like
      // delete / move-to-group), collapse the checkbox column back to icons.
      // Otherwise the row checkboxes stay visible and only revert after the
      // user clicks one — which the per-checkbox handler resets.
      if (selectedProfiles.length === 0) {
        setShowCheckboxes(false);
      }
    }
  }, [selectedProfiles]);

  // Update external selectedProfiles when table selection changes
  const handleRowSelectionChange = React.useCallback(
    (updater: React.SetStateAction<RowSelectionState>) => {
      setRowSelection((prevSelection) => {
        const newSelection =
          typeof updater === "function" ? updater(prevSelection) : updater;

        const selectedIds = Object.keys(newSelection).filter(
          (id) => newSelection[id],
        );

        // Only update external state if selection actually changed.
        // A Set gives O(1) membership; Array.includes() inside .every() would
        // be O(n*m) over large selections.
        const prevIdSet = new Set(
          Object.keys(prevSelection).filter((id) => prevSelection[id]),
        );

        if (
          selectedIds.length !== prevIdSet.size ||
          !selectedIds.every((id) => prevIdSet.has(id))
        ) {
          onSelectedProfilesChange(selectedIds);
        }

        return newSelection;
      });
    },
    [onSelectedProfilesChange],
  );
  const [profileToRename, setProfileToRename] =
    React.useState<BrowserProfile | null>(null);
  const [newProfileName, setNewProfileName] = React.useState("");
  const [renameError, setRenameError] = React.useState<string | null>(null);
  const [isRenamingSaving, setIsRenamingSaving] = React.useState(false);
  const renameContainerRef = React.useRef<HTMLDivElement | null>(null);
  const [profileToDelete, setProfileToDelete] =
    React.useState<BrowserProfile | null>(null);
  const [isDeleting, setIsDeleting] = React.useState(false);
  const [internalInfoDialogProfile, setInternalInfoDialogProfile] =
    React.useState<BrowserProfile | null>(null);
  const isInfoDialogControlled = onInfoDialogProfileChange !== undefined;
  const profileForInfoDialog = isInfoDialogControlled
    ? (infoDialogProfile ?? null)
    : internalInfoDialogProfile;
  const setProfileForInfoDialog = React.useCallback(
    (p: BrowserProfile | null) => {
      if (isInfoDialogControlled) {
        onInfoDialogProfileChange?.(p);
      } else {
        setInternalInfoDialogProfile(p);
      }
    },
    [isInfoDialogControlled, onInfoDialogProfileChange],
  );
  const [bypassRulesProfile, setBypassRulesProfile] =
    React.useState<BrowserProfile | null>(null);
  const [dnsBlocklistProfile, setDnsBlocklistProfile] =
    React.useState<BrowserProfile | null>(null);
  const [launchHookProfile, setLaunchHookProfile] =
    React.useState<BrowserProfile | null>(null);
  const [launchingProfiles, setLaunchingProfiles] = React.useState<Set<string>>(
    new Set(),
  );
  const [stoppingProfiles, setStoppingProfiles] = React.useState<Set<string>>(
    new Set(),
  );

  const { storedProxies } = useProxyEvents();
  const { vpnConfigs } = useVpnEvents();
  const { user } = useCloudAuth();
  const { isProfileLocked, getLockInfo } = useTeamLocks(user?.id);
  // Which profiles cannot be opened on this computer, and why. Event-driven and
  // read from the backend's own gate, so the button state and the refusal the
  // backend would give can never disagree.
  const { handoffFor } = useRemoteHandoff();

  // Cookie Bot. Enrolments and live runs both live server-side, so the table
  // reads them from the shared store rather than from BrowserProfile.
  const cookieBotUnlocked = canUseCookieBot(user);
  const {
    scheduleFor,
    liveSessionFor,
    refresh: refreshCookieBotState,
  } = useCookieBot(cookieBotUnlocked, cookieBotScopeFor(user));
  const [botPendingProfiles, setBotPendingProfiles] = React.useState<
    Set<string>
  >(new Set());
  const [botScheduleDialog, setBotScheduleDialog] = React.useState<{
    profiles: BrowserProfile[];
    existing: CookieBotSchedule | null;
  } | null>(null);
  const [botRunsProfile, setBotRunsProfile] =
    React.useState<BrowserProfile | null>(null);
  const [botUnenrolProfile, setBotUnenrolProfile] =
    React.useState<BrowserProfile | null>(null);
  const [isUnenrolling, setIsUnenrolling] = React.useState(false);
  const [pendingBulkEnrol, setPendingBulkEnrol] = React.useState<
    BrowserProfile[] | null
  >(null);

  // Content columns grow proportionally with the container but never drop
  // below the compact-layout floor; the name column takes the remainder.
  // Computed in px from the observed container width because fixed table
  // layout ignores max()/calc() column widths.
  const [containerWidth, setContainerWidth] = React.useState(0);

  const [proxyOverrides, setProxyOverrides] = React.useState<
    Record<string, string | null>
  >({});
  const [vpnOverrides, setVpnOverrides] = React.useState<
    Record<string, string | null>
  >({});
  const [showCheckboxes, setShowCheckboxes] = React.useState(false);
  const [tagsOverrides, setTagsOverrides] = React.useState<
    Record<string, string[]>
  >({});
  const [allTags, setAllTags] = React.useState<string[]>([]);
  const [openTagsEditorFor, setOpenTagsEditorFor] = React.useState<
    string | null
  >(null);
  const [openProxySelectorFor, setOpenProxySelectorFor] = React.useState<
    string | null
  >(null);
  const [checkingProfileId, setCheckingProfileId] = React.useState<
    string | null
  >(null);
  const [proxyCheckResults, setProxyCheckResults] = React.useState<
    Record<string, ProxyCheckResult>
  >({});
  const [noteOverrides, setNoteOverrides] = React.useState<
    Record<string, string | null>
  >({});
  const [openNoteEditorFor, setOpenNoteEditorFor] = React.useState<
    string | null
  >(null);
  const [trafficSnapshots, setTrafficSnapshots] = React.useState<
    Record<string, TrafficSnapshot>
  >({});
  const [trafficDialogProfile, setTrafficDialogProfile] = React.useState<{
    id: string;
    name?: string;
  } | null>(null);
  const [syncStatuses, setSyncStatuses] = React.useState<
    Record<string, { status: string; error?: string }>
  >({});

  // Country proxy creation state (for inline proxy creation in dropdown)
  const [countries, setCountries] = React.useState<LocationItem[]>([]);
  const [countriesLoaded, setCountriesLoaded] = React.useState(false);

  // Extension groups for the Ext column lookup. Refreshed when the
  // backend emits 'extensions-changed' (group rename/create/delete).
  const [extensionGroups, setExtensionGroups] = React.useState<
    ExtensionGroup[]
  >([]);

  React.useEffect(() => {
    let mounted = true;
    let unlisten: (() => void) | undefined;
    const load = async () => {
      try {
        const data = await invoke<ExtensionGroup[]>("list_extension_groups");
        if (mounted) setExtensionGroups(data);
      } catch (e) {
        console.error("Failed to load extension groups:", e);
      }
    };
    void load();
    void listen("extensions-changed", () => {
      void load();
    }).then((u) => {
      if (mounted) unlisten = u;
      else u();
    });
    return () => {
      mounted = false;
      unlisten?.();
    };
  }, []);
  const canCreateLocationProxy = false;

  const loadCountries = React.useCallback(async () => {
    if (countriesLoaded || !canCreateLocationProxy) return;
    try {
      const data = await invoke<LocationItem[]>("cloud_get_countries");
      setCountries(data);
      setCountriesLoaded(true);
    } catch (e) {
      console.error("Failed to load countries:", e);
    }
  }, [countriesLoaded]);

  // Load cached check results for proxies
  React.useEffect(() => {
    const loadCachedResults = async () => {
      const results: Record<string, ProxyCheckResult> = {};
      const proxyIds = new Set<string>();
      for (const profile of profiles) {
        if (profile.proxy_id) {
          proxyIds.add(profile.proxy_id);
        }
      }
      for (const proxyId of proxyIds) {
        try {
          const cached = await invoke<ProxyCheckResult | null>(
            "get_cached_proxy_check",
            { proxyId },
          );
          if (cached) {
            results[proxyId] = cached;
          }
        } catch (_error) {
          // Ignore errors
        }
      }
      setProxyCheckResults(results);
    };
    if (profiles.length > 0) {
      void loadCachedResults();
    }
  }, [profiles]);

  const loadAllTags = React.useCallback(async () => {
    try {
      const tags = await invoke<string[]>("get_all_tags");
      setAllTags(tags);
    } catch (error) {
      console.error("Failed to load tags:", error);
    }
  }, []);

  const handleProxySelection = React.useCallback(
    async (profileId: string, proxyId: string | null) => {
      try {
        await invoke("update_profile_proxy", {
          profileId,
          proxyId,
        });
        setProxyOverrides((prev) => ({ ...prev, [profileId]: proxyId }));
        setVpnOverrides((prev) => ({ ...prev, [profileId]: null }));
        await emit("profile-updated");
      } catch (error) {
        console.error("Failed to update proxy settings:", error);
      } finally {
        setOpenProxySelectorFor(null);
      }
    },
    [],
  );

  const handleVpnSelection = React.useCallback(
    async (profileId: string, vpnId: string | null) => {
      try {
        await invoke("update_profile_vpn", {
          profileId,
          vpnId,
        });
        setVpnOverrides((prev) => ({ ...prev, [profileId]: vpnId }));
        setProxyOverrides((prev) => ({ ...prev, [profileId]: null }));
        await emit("profile-updated");
      } catch (error) {
        console.error("Failed to update VPN settings:", error);
      } finally {
        setOpenProxySelectorFor(null);
      }
    },
    [],
  );

  const handleCreateCountryProxy = React.useCallback(
    async (profileId: string, country: LocationItem) => {
      try {
        await invoke("create_cloud_location_proxy", {
          name: country.name,
          country: country.code,
          region: null,
          city: null,
          isp: null,
        });
        await emit("stored-proxies-changed");
        // Wait briefly for proxy list to update, then find and assign the new proxy
        await new Promise((r) => setTimeout(r, 200));
        const updatedProxies =
          await invoke<StoredProxy[]>("get_stored_proxies");
        const newProxy = updatedProxies.find(
          (p: StoredProxy) =>
            p.is_cloud_derived && p.geo_country === country.code,
        );
        if (newProxy) {
          await handleProxySelection(profileId, newProxy.id);
        }
        setOpenProxySelectorFor(null);
      } catch (error) {
        console.error("Failed to create country proxy:", error);
      }
    },
    [handleProxySelection],
  );

  const getProfileBotState = React.useCallback(
    (profileId: string): ProfileBotState => ({
      schedule: scheduleFor(profileId),
      liveSession: liveSessionFor(profileId),
    }),
    [scheduleFor, liveSessionFor],
  );

  const handleBotEnrol = React.useCallback((profile: BrowserProfile) => {
    setBotScheduleDialog({ profiles: [profile], existing: null });
  }, []);

  const handleBotEdit = React.useCallback(
    (profile: BrowserProfile, schedule: CookieBotSchedule) => {
      setBotScheduleDialog({ profiles: [profile], existing: schedule });
    },
    [],
  );

  const handleBotViewActivity = React.useCallback((profile: BrowserProfile) => {
    setBotRunsProfile(profile);
  }, []);

  const handleBotFix = React.useCallback(
    (profile: BrowserProfile, fix: PreflightFix) => {
      if (fix === "proxy") {
        onAssignProfilesToProxy?.([profile.id]);
        return;
      }
      if (fix === "syncSettings") {
        onOpenProfileSyncDialog?.(profile);
        return;
      }
      void enableProfileSync(profile.id).catch((error: unknown) => {
        showErrorToast(
          parseBackendError(error)
            ? translateBackendError(t as never, error)
            : t("cookieBot.preflight.fixFailed"),
        );
      });
    },
    [onAssignProfilesToProxy, onOpenProfileSyncDialog, t],
  );

  // Null rather than a no-op when nothing is wired: the menu then states the
  // reason without offering a repair it cannot perform.
  const botFixHandler =
    onAssignProfilesToProxy || onOpenProfileSyncDialog ? handleBotFix : null;

  const handleBotRunNow = React.useCallback(
    async (profile: BrowserProfile) => {
      // Held locally until the stream reports the session: the run is real the
      // moment the command returns, and a row that still looks idle invites a
      // second click that would spend a second hour.
      setBotPendingProfiles((prev) => new Set(prev).add(profile.id));
      try {
        const started = await runCookieBotNow(profile.id);
        // 202, not 200: the route answers with a RECORDED run, and a run that
        // could not get a host comes back already terminal, carrying an
        // `outcome_code`, rather than as an HTTP error. Treating every 2xx as
        // "started" told a user their run had begun on a night when every
        // Windows host in a four-slot fleet was busy, and the only trace was a
        // row in a history panel they had to go and open.
        if (RUN_DID_NOT_START.has(started.run.status)) {
          showErrorToast(
            t("cookieBot.actions.runNotStarted", {
              reason:
                outcomeLabel(t as never, started.run.outcome_code) ??
                runStatusLabel(t as never, started.run.status),
            }),
          );
        } else {
          showSuccessToast(t("cookieBot.actions.runStarted"));
        }
        await refreshCookieBotState();
      } catch (error) {
        showErrorToast(translateBackendError(t as never, error));
      } finally {
        setBotPendingProfiles((prev) => {
          const next = new Set(prev);
          next.delete(profile.id);
          return next;
        });
      }
    },
    [refreshCookieBotState, t],
  );

  const handleBotStopRun = React.useCallback(
    async (runId: string) => {
      try {
        await cancelCookieBotRun(runId);
        showSuccessToast(t("cookieBot.running.stopped"));
        await refreshCookieBotState();
      } catch (error) {
        showErrorToast(translateBackendError(t as never, error));
      }
    },
    [refreshCookieBotState, t],
  );

  const handleBotUnenrol = React.useCallback(async () => {
    if (!botUnenrolProfile) return;
    setIsUnenrolling(true);
    try {
      await deleteCookieBotSchedule(botUnenrolProfile.id);
      showSuccessToast(t("cookieBot.schedule.unenrolled"));
      setBotUnenrolProfile(null);
      await refreshCookieBotState();
    } catch (error) {
      showErrorToast(translateBackendError(t as never, error));
    } finally {
      setIsUnenrolling(false);
    }
  }, [botUnenrolProfile, refreshCookieBotState, t]);

  const handleBulkCookieBotEnrol = React.useCallback(() => {
    const targets = profiles.filter((p) => selectedProfiles.includes(p.id));
    if (targets.length === 0) return;
    const eligible = targets.filter((p) => preflight(p).eligible);
    // Same guard as bulk run: an action that can touch nothing says so instead
    // of opening a dialog whose only outcome is a refusal.
    if (eligible.length === 0) {
      showErrorToast(t("cookieBot.actionBar.noneEligible"));
      return;
    }
    // Ten or more is the threshold bulk run and stop already use, and enrolling
    // is the heavier commitment of the three: each row books a nightly job
    // against a shared budget.
    if (eligible.length >= BULK_ENROL_CONFIRM_THRESHOLD) {
      setPendingBulkEnrol(targets);
      return;
    }
    setBotScheduleDialog({ profiles: targets, existing: null });
  }, [profiles, selectedProfiles, t]);

  // Use shared browser state hook
  const browserState = useBrowserState(
    profiles,
    runningProfiles,
    isUpdating,
    launchingProfiles,
    stoppingProfiles,
  );

  // Listen for sync status events
  React.useEffect(() => {
    if (!browserState.isClient) return;
    let unlisten: (() => void) | undefined;
    void (async () => {
      try {
        unlisten = await listen<{
          profile_id: string;
          status: string;
          error?: string;
        }>("profile-sync-status", (event) => {
          const { profile_id, status, error } = event.payload;
          setSyncStatuses((prev) => ({
            ...prev,
            [profile_id]: { status, error },
          }));
        });
      } catch (error) {
        console.error("Failed to listen for sync status events:", error);
      }
    })();
    return () => {
      if (unlisten) unlisten();
    };
  }, [browserState.isClient]);

  // Fetch traffic snapshots for running profiles (lightweight, real-time data)
  // Convert Set to sorted array to avoid Set reference comparison issues in dependencies
  const runningProfileIds = React.useMemo(
    () => Array.from(runningProfiles).sort(),
    [runningProfiles],
  );
  const runningCount = runningProfileIds.length;
  React.useEffect(() => {
    if (!browserState.isClient) return;

    if (runningCount === 0) {
      setTrafficSnapshots({});
      return;
    }

    const fetchTrafficSnapshots = async () => {
      try {
        const allSnapshots = await invoke<TrafficSnapshot[]>(
          "get_all_traffic_snapshots",
        );
        const newSnapshots: Record<string, TrafficSnapshot> = {};
        // O(1) membership; runningProfileIds.includes() in this loop would be
        // O(snapshots * runningProfiles).
        const runningSet = new Set(runningProfileIds);
        for (const snapshot of allSnapshots) {
          if (snapshot.profile_id) {
            // Only keep snapshots for profiles that are currently running
            if (runningSet.has(snapshot.profile_id)) {
              const existing = newSnapshots[snapshot.profile_id];
              if (!existing || snapshot.last_update > existing.last_update) {
                newSnapshots[snapshot.profile_id] = snapshot;
              }
            }
          }
        }
        setTrafficSnapshots(newSnapshots);
      } catch (error) {
        console.error("Failed to fetch traffic snapshots:", error);
      }
    };

    void fetchTrafficSnapshots();
    const interval = setInterval(() => {
      void fetchTrafficSnapshots();
    }, 1000);
    return () => {
      clearInterval(interval);
    };
  }, [browserState.isClient, runningCount, runningProfileIds]);

  // Clean up snapshots for profiles that are no longer running
  React.useEffect(() => {
    if (!browserState.isClient) return;

    setTrafficSnapshots((prev) => {
      const cleaned: Record<string, TrafficSnapshot> = {};
      const runningSet = new Set(runningProfileIds);
      for (const [profileId, snapshot] of Object.entries(prev)) {
        // Only keep snapshots for profiles that are currently running
        if (runningSet.has(profileId)) {
          cleaned[profileId] = snapshot;
        }
      }
      // Only update if something was removed
      if (Object.keys(cleaned).length !== Object.keys(prev).length) {
        return cleaned;
      }
      return prev;
    });
  }, [browserState.isClient, runningProfileIds]);

  // Clear launching/stopping spinners when backend reports running status changes
  React.useEffect(() => {
    if (!browserState.isClient) return;
    let unlisten: (() => void) | undefined;
    void (async () => {
      try {
        unlisten = await listen<{ id: string; is_running: boolean }>(
          "profile-running-changed",
          (event) => {
            const { id } = event.payload;
            // Clear launching state for this profile if present
            setLaunchingProfiles((prev) => {
              if (!prev.has(id)) return prev;
              const next = new Set(prev);
              next.delete(id);
              return next;
            });
            // Clear stopping state for this profile if present
            setStoppingProfiles((prev) => {
              if (!prev.has(id)) return prev;
              const next = new Set(prev);
              next.delete(id);
              return next;
            });
          },
        );
      } catch (error) {
        console.error("Failed to listen for profile running changes:", error);
      }
    })();
    return () => {
      if (unlisten) unlisten();
    };
  }, [browserState.isClient]);

  // Keep stored proxies up-to-date by listening for changes emitted elsewhere in the app
  React.useEffect(() => {
    if (!browserState.isClient) return;
    let unlisten: (() => void) | undefined;
    void (async () => {
      try {
        unlisten = await listen("stored-proxies-changed", () => {
          // Also refresh tags on profile updates
          void loadAllTags();
        });
      } catch (_err) {
        // Best-effort only
      }
    })();
    return () => {
      if (unlisten) unlisten();
    };
  }, [browserState.isClient, loadAllTags]);

  // A running browser keeps the name it launched with, so close any inline
  // rename that was open when the profile entered a runtime transition.
  React.useEffect(() => {
    if (!profileToRename) return;

    const profileId = profileToRename.id;
    const isRuntimeLocked =
      (browserState.isClient && runningProfiles.has(profileId)) ||
      launchingProfiles.has(profileId) ||
      stoppingProfiles.has(profileId);

    if (isRuntimeLocked) {
      setProfileToRename(null);
      setNewProfileName("");
      setRenameError(null);
    }
  }, [
    profileToRename,
    runningProfiles,
    launchingProfiles,
    stoppingProfiles,
    browserState.isClient,
  ]);

  // Automatically deselect profiles that become running, updating, launching, or stopping
  React.useEffect(() => {
    const newSet = new Set(selectedProfiles);
    let hasChanges = false;

    for (const profileId of selectedProfiles) {
      const profile = profiles.find((p) => p.id === profileId);
      if (profile) {
        const isRunning =
          browserState.isClient && runningProfiles.has(profile.id);
        const isLaunching = launchingProfiles.has(profile.id);
        const isStopping = stoppingProfiles.has(profile.id);

        if (isRunning || isLaunching || isStopping) {
          newSet.delete(profileId);
          hasChanges = true;
        }
      }
    }

    if (hasChanges) {
      onSelectedProfilesChange(Array.from(newSet));
    }
  }, [
    profiles,
    runningProfiles,
    launchingProfiles,
    stoppingProfiles,
    browserState.isClient,
    onSelectedProfilesChange,
    selectedProfiles,
  ]);

  // Update local sorting state when settings are loaded
  React.useEffect(() => {
    if (isLoaded && browserState.isClient) {
      setSorting(getTableSorting());
    }
  }, [isLoaded, getTableSorting, browserState.isClient]);

  // Handle sorting changes
  const handleSortingChange = React.useCallback(
    (updater: React.SetStateAction<SortingState>) => {
      if (!browserState.isClient) return;
      const newSorting =
        typeof updater === "function" ? updater(sorting) : updater;
      setSorting(newSorting);
      updateSorting(newSorting);
    },
    [browserState.isClient, sorting, updateSorting],
  );

  const handleRename = React.useCallback(async () => {
    if (!profileToRename || !newProfileName.trim()) return;

    try {
      setIsRenamingSaving(true);
      await onRenameProfile(profileToRename.id, newProfileName.trim());
      setProfileToRename(null);
      setNewProfileName("");
      setRenameError(null);
    } catch (error) {
      setRenameError(
        error instanceof Error
          ? error.message
          : t("errors.renameProfileFailed", { error: String(error) }),
      );
    } finally {
      setIsRenamingSaving(false);
    }
  }, [profileToRename, newProfileName, onRenameProfile, t]);

  // Cancel inline rename on outside click
  React.useEffect(() => {
    if (!profileToRename) return;
    const handleClickOutside = (event: MouseEvent) => {
      const target = event.target as Node | null;
      if (
        target &&
        renameContainerRef.current &&
        !renameContainerRef.current.contains(target)
      ) {
        setProfileToRename(null);
        setNewProfileName("");
        setRenameError(null);
      }
    };
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [profileToRename]);

  const handleDelete = async () => {
    if (!profileToDelete) return;

    setIsDeleting(true);
    // Minimum loading time for visual feedback
    const minLoadingTime = new Promise((r) => setTimeout(r, 300));
    try {
      await Promise.all([onDeleteProfile(profileToDelete), minLoadingTime]);
      setProfileToDelete(null);
    } catch (error) {
      console.error("Failed to delete profile:", error);
    } finally {
      setIsDeleting(false);
    }
  };

  // Handle icon/checkbox click
  const handleIconClick = React.useCallback(
    (profileId: string) => {
      const profile = profiles.find((p) => p.id === profileId);
      if (!profile) return;

      // Prevent selection of profiles whose browsers are updating
      if (!browserState.canSelectProfile(profile)) {
        return;
      }

      setShowCheckboxes(true);
      const newSet = new Set(selectedProfiles);
      if (newSet.has(profileId)) {
        newSet.delete(profileId);
      } else {
        newSet.add(profileId);
      }

      // Hide checkboxes if no profiles are selected
      if (newSet.size === 0) {
        setShowCheckboxes(false);
      }

      onSelectedProfilesChange(Array.from(newSet));
    },
    [profiles, browserState, onSelectedProfilesChange, selectedProfiles],
  );

  React.useEffect(() => {
    if (browserState.isClient) {
      void loadAllTags();
    }
  }, [browserState.isClient, loadAllTags]);

  // Handle checkbox change
  const handleCheckboxChange = React.useCallback(
    (profileId: string, checked: boolean) => {
      const newSet = new Set(selectedProfiles);
      if (checked) {
        newSet.add(profileId);
      } else {
        newSet.delete(profileId);
      }

      // Hide checkboxes if no profiles are selected
      if (newSet.size === 0) {
        setShowCheckboxes(false);
      }

      onSelectedProfilesChange(Array.from(newSet));
    },
    [onSelectedProfilesChange, selectedProfiles],
  );

  // Handle select all checkbox
  const handleToggleAll = React.useCallback(
    (checked: boolean) => {
      const newSet = checked
        ? new Set(
            profiles
              .filter((profile) => {
                const isRunning =
                  browserState.isClient && runningProfiles.has(profile.id);
                const isLaunching = launchingProfiles.has(profile.id);
                const isStopping = stoppingProfiles.has(profile.id);
                return !isRunning && !isLaunching && !isStopping;
              })
              .map((profile) => profile.id),
          )
        : new Set<string>();

      setShowCheckboxes(checked);
      onSelectedProfilesChange(Array.from(newSet));
    },
    [
      profiles,
      onSelectedProfilesChange,
      browserState.isClient,
      runningProfiles,
      launchingProfiles,
      stoppingProfiles,
    ],
  );

  // Memoize selectableProfiles calculation
  const selectableProfiles = React.useMemo(() => {
    return profiles.filter((profile) => {
      const isRunning =
        browserState.isClient && runningProfiles.has(profile.id);
      const isLaunching = launchingProfiles.has(profile.id);
      const isStopping = stoppingProfiles.has(profile.id);
      return !isRunning && !isLaunching && !isStopping;
    });
  }, [
    profiles,
    browserState.isClient,
    runningProfiles,
    launchingProfiles,
    stoppingProfiles,
  ]);

  // Build table meta from volatile state so columns can stay stable
  const tableMeta = React.useMemo<TableMeta>(
    () => ({
      t,
      selectedProfiles,
      selectableCount: selectableProfiles.length,
      showCheckboxes,
      isClient: browserState.isClient,
      runningProfiles,
      launchingProfiles,
      stoppingProfiles,
      isUpdating,
      browserState,

      // Tags editor state
      tagsOverrides,
      allTags,
      openTagsEditorFor,
      setAllTags,
      setOpenTagsEditorFor,
      setTagsOverrides,

      // Note editor state
      noteOverrides,
      openNoteEditorFor,
      setOpenNoteEditorFor,
      setNoteOverrides,

      // Proxy selector state
      openProxySelectorFor,
      setOpenProxySelectorFor,
      proxyOverrides,
      storedProxies,
      handleProxySelection,
      checkingProfileId,
      proxyCheckResults,

      // VPN selector state
      vpnConfigs,
      vpnOverrides,
      handleVpnSelection,

      // Extension groups
      extensionGroups,
      onAssignExtensionGroup,
      setDnsBlocklistProfile,

      // Selection helpers
      isProfileSelected: (id: string) => selectedProfiles.includes(id),
      handleToggleAll,
      handleCheckboxChange,
      handleIconClick,

      // Rename helpers
      handleRename,
      setProfileToRename,
      setNewProfileName,
      setRenameError,
      profileToRename,
      newProfileName,
      isRenamingSaving,
      renameError,

      // Launch/stop helpers
      setLaunchingProfiles,
      setStoppingProfiles,
      onKillProfile,
      onLaunchProfile,

      // Overflow actions
      onAssignProfilesToGroup,
      onCloneProfile: onCloneProfile
        ? (profile: BrowserProfile) => {
            void onCloneProfile(profile);
          }
        : undefined,
      onConfigureWayfern,
      onCopyCookiesToProfile,
      onOpenCookieManagement,

      // Traffic snapshots (lightweight real-time data)
      trafficSnapshots,
      onOpenTrafficDialog: (profileId: string) => {
        const profile = profiles.find((p) => p.id === profileId);
        setTrafficDialogProfile({ id: profileId, name: profile?.name });
      },

      // Sync
      syncStatuses,
      onOpenProfileSyncDialog,
      onToggleProfileSync,
      crossOsUnlocked,
      syncUnlocked,

      // Country proxy creation
      countries,
      canCreateLocationProxy,
      loadCountries,
      handleCreateCountryProxy,

      // Team locks
      isProfileLockedByAnother: isProfileLocked,
      getProfileLockEmail: (profileId: string) =>
        getLockInfo(profileId)?.lockedByEmail,

      // Remote execution
      getRemoteHandoff: handoffFor,

      // Synchronizer
      getProfileSyncInfo: getProfileSyncInfo ?? (() => undefined),
      onLaunchWithSync:
        onLaunchWithSync ??
        (() => {
          /* empty */
        }),

      // Cookie Bot
      cookieBotUnlocked,
      cookieBotCompact: containerWidth > 0 && containerWidth < BOT_LABEL_WIDTH,
      getProfileBotState,
      botPendingProfiles,
      onBotEnrol: handleBotEnrol,
      onBotEdit: handleBotEdit,
      onBotRunNow: (profile: BrowserProfile) => {
        void handleBotRunNow(profile);
      },
      onBotStopRun: (runId: string) => {
        void handleBotStopRun(runId);
      },
      onBotViewActivity: handleBotViewActivity,
      onBotUnenrol: setBotUnenrolProfile,
      onBotFix: botFixHandler,
    }),
    [
      t,
      selectedProfiles,
      selectableProfiles.length,
      showCheckboxes,
      browserState.isClient,
      runningProfiles,
      launchingProfiles,
      stoppingProfiles,
      isUpdating,
      browserState,
      tagsOverrides,
      allTags,
      openTagsEditorFor,
      noteOverrides,
      openNoteEditorFor,
      openProxySelectorFor,
      proxyOverrides,
      storedProxies,
      handleProxySelection,
      checkingProfileId,
      proxyCheckResults,
      vpnConfigs,
      vpnOverrides,
      handleVpnSelection,
      extensionGroups,
      onAssignExtensionGroup,
      handleToggleAll,
      handleCheckboxChange,
      handleIconClick,
      handleRename,
      profileToRename,
      newProfileName,
      isRenamingSaving,
      trafficSnapshots,
      profiles,
      renameError,
      onKillProfile,
      onLaunchProfile,
      onAssignProfilesToGroup,
      onCloneProfile,
      onConfigureWayfern,
      onCopyCookiesToProfile,
      onOpenCookieManagement,
      syncStatuses,
      onOpenProfileSyncDialog,
      onToggleProfileSync,
      crossOsUnlocked,
      syncUnlocked,
      countries,
      loadCountries,
      handleCreateCountryProxy,
      isProfileLocked,
      getLockInfo,
      handoffFor,
      getProfileSyncInfo,
      onLaunchWithSync,
      cookieBotUnlocked,
      containerWidth,
      getProfileBotState,
      botPendingProfiles,
      handleBotEnrol,
      handleBotEdit,
      handleBotRunNow,
      handleBotStopRun,
      handleBotViewActivity,
      botFixHandler,
    ],
  );

  const columns: ColumnDef<BrowserProfile>[] = React.useMemo(
    () => [
      {
        id: "select",
        header: ({ table }) => {
          const meta = table.options.meta as TableMeta;
          return (
            <span>
              <Checkbox
                checked={
                  meta.selectedProfiles.length === meta.selectableCount &&
                  meta.selectableCount !== 0
                }
                onCheckedChange={(value) => {
                  meta.handleToggleAll(!!value);
                }}
                aria-label={t("common.aria.selectAll")}
                className="cursor-pointer"
              />
            </span>
          );
        },
        cell: ({ row, table }) => {
          const meta = table.options.meta as TableMeta;
          const profile = row.original;
          const browser = profile.browser;
          const IconComponent = getProfileIcon(profile);
          const isCrossOs = isCrossOsProfile(profile);

          const isSelected = meta.isProfileSelected(profile.id);
          const isRunning =
            meta.isClient && meta.runningProfiles.has(profile.id);
          const isLaunching = meta.launchingProfiles.has(profile.id);
          const isStopping = meta.stoppingProfiles.has(profile.id);
          const isDisabled = isRunning || isLaunching || isStopping;

          // Cross-OS profiles: show OS icon when checkboxes aren't visible, show checkbox when they are
          if (isCrossOs && !meta.showCheckboxes && !isSelected) {
            const resolvedOs = profile.host_os || profile.wayfern_config?.os;
            const osName = resolvedOs
              ? getOSDisplayName(resolvedOs)
              : "another OS";
            const crossOsTooltip = t("crossOs.viewOnly", { os: osName });
            const OsIcon =
              resolvedOs === "macos"
                ? FaApple
                : resolvedOs === "windows"
                  ? FaWindows
                  : FaLinux;
            return (
              <Tooltip>
                <TooltipTrigger asChild>
                  <span className="flex size-4 items-center justify-center">
                    <button
                      type="button"
                      className="flex cursor-pointer items-center justify-center border-none p-0"
                      onClick={() => {
                        meta.handleIconClick(profile.id);
                      }}
                      aria-label={t("common.aria.selectProfile")}
                    >
                      <span className="group size-4">
                        <OsIcon className="size-4 text-muted-foreground group-hover:hidden" />
                        <span className="peer pointer-events-none hidden size-4 shrink-0 items-center justify-center rounded-[4px] border border-input shadow-xs transition-shadow duration-150 outline-none group-hover:block dark:bg-input/30 dark:data-[state=checked]:bg-primary" />
                      </span>
                    </button>
                  </span>
                </TooltipTrigger>
                <TooltipContent>
                  <p>{crossOsTooltip}</p>
                </TooltipContent>
              </Tooltip>
            );
          }

          // Cross-OS profiles with checkboxes visible: show checkbox (selectable for bulk delete)
          if (isCrossOs && (meta.showCheckboxes || isSelected)) {
            const resolvedOs = profile.host_os || profile.wayfern_config?.os;
            const osName = resolvedOs
              ? getOSDisplayName(resolvedOs)
              : "another OS";
            const crossOsTooltip = t("crossOs.viewOnly", { os: osName });
            return (
              <NonHoverableTooltip
                content={<p>{crossOsTooltip}</p>}
                sideOffset={4}
                horizontalOffset={8}
              >
                <span className="flex size-4 items-center justify-center">
                  <Checkbox
                    checked={isSelected}
                    onCheckedChange={(value) => {
                      meta.handleCheckboxChange(profile.id, !!value);
                    }}
                    aria-label={t("common.aria.selectRow")}
                    className="size-4"
                  />
                </span>
              </NonHoverableTooltip>
            );
          }

          if (isDisabled) {
            const tooltipMessage = isRunning
              ? t("profiles.table.cantModifyRunning")
              : isLaunching
                ? t("profiles.table.cantModifyLaunching")
                : isStopping
                  ? t("profiles.table.cantModifyStopping")
                  : t("profiles.table.cantModifyUpdating");

            return (
              <Tooltip>
                <TooltipTrigger asChild>
                  <span className="flex size-4 cursor-not-allowed items-center justify-center">
                    {IconComponent && (
                      <IconComponent className="size-4 opacity-50" />
                    )}
                  </span>
                </TooltipTrigger>
                <TooltipContent>
                  <p>{tooltipMessage}</p>
                </TooltipContent>
              </Tooltip>
            );
          }

          const browserName = getBrowserDisplayName(browser);

          if (meta.showCheckboxes || isSelected) {
            return (
              <NonHoverableTooltip
                content={<p>{browserName}</p>}
                sideOffset={4}
                horizontalOffset={8}
              >
                <span className="flex size-4 items-center justify-center">
                  <Checkbox
                    checked={isSelected}
                    onCheckedChange={(value) => {
                      meta.handleCheckboxChange(profile.id, !!value);
                    }}
                    aria-label={t("common.aria.selectRow")}
                    className="size-4"
                  />
                </span>
              </NonHoverableTooltip>
            );
          }

          return (
            <NonHoverableTooltip
              content={<p>{browserName}</p>}
              sideOffset={4}
              horizontalOffset={8}
            >
              <span className="relative flex size-4 items-center justify-center">
                <button
                  type="button"
                  className="flex cursor-pointer items-center justify-center border-none p-0"
                  onClick={() => {
                    meta.handleIconClick(profile.id);
                  }}
                  aria-label={t("common.aria.selectProfile")}
                >
                  <span className="group size-4">
                    {IconComponent && (
                      <IconComponent className="size-4 group-hover:hidden" />
                    )}
                    <span className="peer pointer-events-none hidden size-4 shrink-0 items-center justify-center rounded-[4px] border border-input shadow-xs transition-shadow duration-150 outline-none group-hover:block dark:bg-input/30 dark:data-[state=checked]:bg-primary" />
                  </span>
                </button>
              </span>
            </NonHoverableTooltip>
          );
        },
        enableSorting: false,
        enableHiding: false,
        size: 28,
      },
      {
        id: "actions",
        size: 48,
        cell: ({ row, table }) => {
          const meta = table.options.meta as TableMeta;
          const profile = row.original;
          const handoff = meta.getRemoteHandoff(profile.id);
          // A profile open on the fleet IS running, and the button has to say
          // so: it is the control that stops it, and stopping now reaches the
          // remote browser rather than looking for a local process that was
          // never there.
          const isRunningRemotely = handoff === "running";
          const isPendingRemotePull = handoff === "pending_sync";
          const isRunning =
            (meta.isClient && meta.runningProfiles.has(profile.id)) ||
            isRunningRemotely;
          const isLaunching = meta.launchingProfiles.has(profile.id);
          const isStopping = meta.stoppingProfiles.has(profile.id);
          const isLockedByAnother = meta.isProfileLockedByAnother(profile.id);
          const isSyncing = meta.syncStatuses[profile.id]?.status === "syncing";
          // A remote session holds the profile lock under its own holder id, so
          // `isLockedByAnother` is true for the user's OWN fleet session. That
          // must not disable the control that stops it.
          const canLaunch = isRunningRemotely
            ? true
            : meta.browserState.canLaunchProfile(profile) &&
              !isPendingRemotePull &&
              !isLockedByAnother &&
              !isSyncing;
          const lockEmail = meta.getProfileLockEmail(profile.id);
          const tooltipContent = isRunningRemotely
            ? meta.t("profiles.remote.runningTooltip")
            : isPendingRemotePull
              ? meta.t("profiles.remote.pendingSyncTooltip")
              : isLockedByAnother
                ? meta.t("sync.team.cannotLaunchLocked", { email: lockEmail })
                : meta.browserState.getLaunchTooltipContent(profile);

          const handleProfileStop = async (profile: BrowserProfile) => {
            meta.setStoppingProfiles((prev: Set<string>) =>
              new Set(prev).add(profile.id),
            );
            try {
              await meta.onKillProfile(profile);
            } catch (error) {
              meta.setStoppingProfiles((prev: Set<string>) => {
                const next = new Set(prev);
                next.delete(profile.id);
                return next;
              });
              throw error;
            }
          };

          const handleProfileLaunch = async (profile: BrowserProfile) => {
            meta.setLaunchingProfiles((prev: Set<string>) =>
              new Set(prev).add(profile.id),
            );
            try {
              await meta.onLaunchProfile(profile);
            } finally {
              // Always clear launching state — the running state is tracked
              // separately via profile-running-changed events
              meta.setLaunchingProfiles((prev: Set<string>) => {
                const next = new Set(prev);
                next.delete(profile.id);
                return next;
              });
            }
          };

          const syncInfo = meta.getProfileSyncInfo(profile.id);
          const isLeader = syncInfo?.isLeader === true;
          const isFollower = syncInfo?.isLeader === false;
          const isDesynced = isFollower && syncInfo.failedAtUrl != null;
          const stopTooltip = isLeader
            ? meta.t("profiles.synchronizer.stopLeader")
            : isFollower
              ? meta.t("profiles.synchronizer.stopFollower", {
                  leaderName: syncInfo.session.leader_profile_name ?? "",
                })
              : tooltipContent;

          const handleStop = async () => {
            if (isLeader && syncInfo) {
              // Stop leader: invoke stop_sync_session which kills leader + all followers
              try {
                await invoke("stop_sync_session", {
                  sessionId: syncInfo.session.id,
                });
              } catch (error) {
                console.error("Failed to stop sync session:", error);
              }
            } else if (isFollower && syncInfo) {
              // Stop follower: remove from session
              try {
                await invoke("remove_sync_follower", {
                  sessionId: syncInfo.session.id,
                  followerProfileId: profile.id,
                });
              } catch (error) {
                console.error("Failed to remove sync follower:", error);
              }
            } else {
              await handleProfileStop(profile);
            }
          };

          const buttonVariant = isRunning
            ? isFollower
              ? "secondary"
              : "destructive"
            : "default";

          return (
            <div className="flex items-center gap-2">
              {isDesynced && (
                <Tooltip>
                  <TooltipTrigger asChild>
                    <span>
                      <LuTriangleAlert className="size-4 text-warning-text" />
                    </span>
                  </TooltipTrigger>
                  <TooltipContent>
                    {meta.t("profiles.synchronizer.desyncedTooltip", {
                      url: syncInfo?.failedAtUrl ?? "",
                    })}
                  </TooltipContent>
                </Tooltip>
              )}
              <Tooltip>
                <TooltipTrigger asChild>
                  <span className="inline-flex">
                    <RippleButton
                      variant={buttonVariant}
                      size="sm"
                      disabled={!canLaunch || isLaunching || isStopping}
                      aria-label={
                        isRunning
                          ? meta.t("profiles.actions.stop")
                          : meta.t("profiles.actions.launch")
                      }
                      className={cn(
                        "grid size-7 place-items-center p-0",
                        !canLaunch && "cursor-not-allowed opacity-50",
                        canLaunch && "cursor-pointer",
                        isFollower && "border-accent",
                        isRunning &&
                          "bg-destructive/10 text-destructive-text hover:bg-destructive/20",
                      )}
                      onClick={() =>
                        isRunning
                          ? void handleStop()
                          : void handleProfileLaunch(profile)
                      }
                    >
                      <AnimatePresence mode="wait" initial={false}>
                        {isLaunching || isStopping ? (
                          <motion.span
                            key="spinner"
                            initial={{ opacity: 0 }}
                            animate={{ opacity: 1 }}
                            exit={{ opacity: 0 }}
                            transition={{ duration: 0.12 }}
                            className="grid place-items-center"
                          >
                            <div className="size-3 animate-spin rounded-full border border-current border-t-transparent" />
                          </motion.span>
                        ) : isRunning ? (
                          <motion.span
                            key="stop"
                            initial={{ opacity: 0 }}
                            animate={{ opacity: 1 }}
                            exit={{ opacity: 0 }}
                            transition={{ duration: 0.12 }}
                            className="grid place-items-center"
                          >
                            <LuSquare className="size-3.5 fill-current" />
                          </motion.span>
                        ) : (
                          <motion.span
                            key="play"
                            initial={{ opacity: 0 }}
                            animate={{ opacity: 1 }}
                            exit={{ opacity: 0 }}
                            transition={{ duration: 0.12 }}
                            className="grid place-items-center"
                          >
                            <LuPlay className="size-3.5 fill-current" />
                          </motion.span>
                        )}
                      </AnimatePresence>
                    </RippleButton>
                  </span>
                </TooltipTrigger>
                {(stopTooltip || tooltipContent) && (
                  <TooltipContent>
                    {isRunning ? stopTooltip : tooltipContent}
                  </TooltipContent>
                )}
              </Tooltip>
            </div>
          );
        },
      },
      {
        // Hidden, sort-only column so profiles can be sorted by creation date
        // without showing a Created column in the table. Kept
        // hidden via columnVisibility; sorting still works on hidden columns.
        id: "created_at",
        accessorFn: (row) => row.created_at ?? 0,
        enableSorting: true,
        enableHiding: true,
        sortingFn: "basic",
        header: () => null,
        cell: () => null,
      },
      {
        accessorKey: "name",
        // The only column without a fixed width: table-fixed hands it all
        // remaining space as the window grows or shrinks.
        meta: { flexWidth: true },
        // The Name header doubles as the sort control: clicking opens a menu to
        // sort by name (A–Z / Z–A) or by creation date (newest / oldest), so
        // creation-date sorting needs no visible column.
        header: ({ table }) => {
          const meta = table.options.meta as TableMeta;
          const sort = table.getState().sorting[0];
          const isActive = (id: string, desc: boolean) =>
            sort?.id === id && !!sort.desc === desc;
          return (
            <DropdownMenu>
              <DropdownMenuTrigger asChild>
                <Button
                  variant="ghost"
                  className="h-auto cursor-pointer justify-start p-0 text-left font-semibold"
                >
                  {meta.t("common.labels.name")}
                  {isActive("name", false) ? (
                    <LuChevronUp className="ml-2 size-4" />
                  ) : isActive("name", true) ? (
                    <LuChevronDown className="ml-2 size-4" />
                  ) : (
                    <LuChevronDown className="ml-2 size-4 opacity-50" />
                  )}
                </Button>
              </DropdownMenuTrigger>
              <DropdownMenuContent align="start">
                <DropdownMenuItem
                  onClick={() =>
                    table.setSorting([{ id: "name", desc: false }])
                  }
                >
                  {isActive("name", false) && (
                    <LuCheck className="mr-2 size-3.5" />
                  )}
                  {meta.t("profiles.sort.nameAsc")}
                </DropdownMenuItem>
                <DropdownMenuItem
                  onClick={() => table.setSorting([{ id: "name", desc: true }])}
                >
                  {isActive("name", true) && (
                    <LuCheck className="mr-2 size-3.5" />
                  )}
                  {meta.t("profiles.sort.nameDesc")}
                </DropdownMenuItem>
                <DropdownMenuItem
                  onClick={() =>
                    table.setSorting([{ id: "created_at", desc: true }])
                  }
                >
                  {isActive("created_at", true) && (
                    <LuCheck className="mr-2 size-3.5" />
                  )}
                  {meta.t("profiles.sort.newest")}
                </DropdownMenuItem>
                <DropdownMenuItem
                  onClick={() =>
                    table.setSorting([{ id: "created_at", desc: false }])
                  }
                >
                  {isActive("created_at", false) && (
                    <LuCheck className="mr-2 size-3.5" />
                  )}
                  {meta.t("profiles.sort.oldest")}
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
          );
        },
        enableSorting: true,
        sortingFn: "alphanumeric",
        cell: ({ row, table }) => {
          const meta = table.options.meta as TableMeta;
          const profile = row.original as BrowserProfile;
          const rawName: string = row.getValue("name");
          const name = getBrowserDisplayName(rawName);
          const isRuntimeLocked =
            (meta.isClient && meta.runningProfiles.has(profile.id)) ||
            meta.launchingProfiles.has(profile.id) ||
            meta.stoppingProfiles.has(profile.id);
          const isCrossOsBlocked = isCrossOsProfile(profile);
          const isEditing =
            meta.profileToRename?.id === profile.id &&
            !isRuntimeLocked &&
            !isCrossOsBlocked;

          if (isEditing) {
            return (
              <div
                ref={renameContainerRef}
                className="relative overflow-visible"
              >
                <Input
                  autoFocus
                  value={meta.newProfileName}
                  onChange={(e) => {
                    meta.setNewProfileName(e.target.value);
                    if (meta.renameError) meta.setRenameError(null);
                  }}
                  onKeyDown={(e) => {
                    if (e.key === "Enter" && !(e.metaKey || e.ctrlKey)) {
                      void meta.handleRename();
                    } else if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
                      void meta.handleRename();
                    } else if (e.key === "Escape") {
                      meta.setProfileToRename(null);
                      meta.setNewProfileName("");
                      meta.setRenameError(null);
                    }
                  }}
                  onBlur={() => {
                    if (
                      meta.newProfileName.trim().length > 0 &&
                      meta.newProfileName.trim() !== profile.name
                    ) {
                      void meta.handleRename();
                    } else {
                      meta.setProfileToRename(null);
                      meta.setNewProfileName("");
                      meta.setRenameError(null);
                    }
                  }}
                  className="h-6 w-full max-w-full min-w-0 border-0 px-2 py-1 text-sm leading-none font-medium shadow-none focus-visible:ring-0"
                />
              </div>
            );
          }

          const display = (
            <OverflowTooltipText
              text={name}
              className="text-left leading-none font-medium"
            />
          );

          const lockedEmail = meta.getProfileLockEmail(profile.id);
          const isLocked = meta.isProfileLockedByAnother(profile.id);
          const nameControl = isRuntimeLocked ? (
            <div className="mr-auto h-6 max-w-full min-w-0 cursor-text overflow-hidden rounded px-2 py-1 text-left select-text">
              {display}
            </div>
          ) : (
            <button
              type="button"
              className={cn(
                "mr-auto h-6 max-w-full min-w-0 overflow-hidden rounded border-none bg-transparent px-2 py-1 text-left",
                isCrossOsBlocked
                  ? "cursor-not-allowed opacity-60"
                  : "cursor-pointer hover:bg-muted",
              )}
              onClick={() => {
                if (isCrossOsBlocked) return;
                meta.setProfileToRename(profile);
                meta.setNewProfileName(profile.name);
                meta.setRenameError(null);
              }}
              onKeyDown={(e) => {
                if (isCrossOsBlocked) return;
                if (e.key === "Enter" || e.key === " ") {
                  e.preventDefault();
                  meta.setProfileToRename(profile);
                  meta.setNewProfileName(profile.name);
                  meta.setRenameError(null);
                }
              }}
            >
              {display}
            </button>
          );

          return (
            <div className="flex max-w-full min-w-0 items-center gap-1.5 overflow-hidden">
              {nameControl}
              {isLocked && (
                <Tooltip>
                  <TooltipTrigger asChild>
                    <span>
                      <LuLock className="size-3 text-muted-foreground" />
                    </span>
                  </TooltipTrigger>
                  <TooltipContent>
                    {meta.t("sync.team.profileLocked", { email: lockedEmail })}
                  </TooltipContent>
                </Tooltip>
              )}
            </div>
          );
        },
      },
      {
        id: "tags",
        size: 100,
        header: ({ table }) => {
          const meta = table.options.meta as TableMeta;
          return meta.t("profileTable.tagsHeader");
        },
        cell: ({ row, table }) => {
          const meta = table.options.meta as TableMeta;
          const profile = row.original;
          const isDisabled = isCrossOsProfile(profile);

          return (
            <TagsCell
              profile={profile}
              isDisabled={isDisabled}
              tagsOverrides={meta.tagsOverrides ?? {}}
              allTags={meta.allTags ?? []}
              setAllTags={meta.setAllTags}
              openTagsEditorFor={meta.openTagsEditorFor ?? null}
              setOpenTagsEditorFor={meta.setOpenTagsEditorFor}
              setTagsOverrides={meta.setTagsOverrides}
            />
          );
        },
      },
      {
        id: "note",
        size: 80,
        header: ({ table }) => {
          const meta = table.options.meta as TableMeta;
          return meta.t("profileTable.noteHeader");
        },
        cell: ({ row, table }) => {
          const meta = table.options.meta as TableMeta;
          const profile = row.original;
          const isDisabled = isCrossOsProfile(profile);

          return (
            <NoteCell
              profile={profile}
              isDisabled={isDisabled}
              noteOverrides={meta.noteOverrides ?? {}}
              openNoteEditorFor={meta.openNoteEditorFor ?? null}
              setOpenNoteEditorFor={meta.setOpenNoteEditorFor}
              setNoteOverrides={meta.setNoteOverrides}
            />
          );
        },
      },
      {
        id: "proxy",
        size: 110,
        header: ({ table }) => {
          const meta = table.options.meta as TableMeta;
          return meta.t("profiles.table.proxy");
        },
        cell: ({ row, table }) => {
          const meta = table.options.meta as TableMeta;
          const profile = row.original;
          const isCrossOs = isCrossOsProfile(profile);
          const isCrossOsBlocked = isCrossOs;
          const isRunning =
            meta.isClient && meta.runningProfiles.has(profile.id);
          const isLaunching = meta.launchingProfiles.has(profile.id);
          const isStopping = meta.stoppingProfiles.has(profile.id);
          const isDisabled =
            isRunning || isLaunching || isStopping || isCrossOsBlocked;

          const hasProxyOverride = Object.hasOwn(
            meta.proxyOverrides,
            profile.id,
          );
          const effectiveProxyId = hasProxyOverride
            ? meta.proxyOverrides[profile.id]
            : (profile.proxy_id ?? null);
          const effectiveProxy = effectiveProxyId
            ? (meta.storedProxies.find((p) => p.id === effectiveProxyId) ??
              null)
            : null;

          const hasVpnOverride = Object.hasOwn(meta.vpnOverrides, profile.id);
          const effectiveVpnId = hasVpnOverride
            ? meta.vpnOverrides[profile.id]
            : (profile.vpn_id ?? null);
          const effectiveVpn = effectiveVpnId
            ? (meta.vpnConfigs.find((v) => v.id === effectiveVpnId) ?? null)
            : null;

          const hasAssignment = Boolean(effectiveProxy || effectiveVpn);
          const displayName = effectiveVpn
            ? effectiveVpn.name
            : effectiveProxy
              ? effectiveProxy.name
              : meta.t("profiles.table.notSelected");
          const vpnBadge = effectiveVpn ? "WG" : null;
          const isSelectorOpen = meta.openProxySelectorFor === profile.id;
          const selectedId = effectiveVpnId ?? effectiveProxyId ?? null;

          // When profile is running, show bandwidth chart instead of proxy selector
          if (isRunning && meta.trafficSnapshots) {
            const snapshot = meta.trafficSnapshots[profile.id];
            const bandwidthData = snapshot?.recent_bandwidth
              ? [...snapshot.recent_bandwidth]
              : [];
            const currentBandwidth =
              (snapshot?.current_bytes_sent ?? 0) +
              (snapshot?.current_bytes_received ?? 0);

            return (
              <div className="min-w-0 overflow-hidden">
                <BandwidthMiniChart
                  key={`${profile.id}-${snapshot?.last_update ?? 0}-${bandwidthData.length}`}
                  data={bandwidthData}
                  currentBandwidth={currentBandwidth}
                  onClick={() => meta.onOpenTrafficDialog?.(profile.id)}
                />
              </div>
            );
          }

          return (
            <div className="flex min-w-0 items-center gap-2 overflow-hidden">
              <Popover
                open={isSelectorOpen}
                onOpenChange={(open) => {
                  meta.setOpenProxySelectorFor(open ? profile.id : null);
                }}
              >
                <ProxyCellTrigger
                  displayName={displayName}
                  hasAssignment={hasAssignment}
                  vpnBadge={vpnBadge}
                  isDisabled={isDisabled}
                />

                {!isDisabled && (
                  <PopoverContent
                    className="w-[240px] p-0"
                    align="end"
                    sideOffset={8}
                  >
                    <Command>
                      <CommandInput
                        placeholder={
                          meta.canCreateLocationProxy
                            ? t("createProfile.proxy.searchWithCountries")
                            : t("createProfile.proxy.search")
                        }
                        onFocus={() => {
                          if (meta.canCreateLocationProxy)
                            void meta.loadCountries();
                        }}
                      />
                      <CommandList>
                        <CommandEmpty>
                          {t("createProfile.proxy.notFound")}
                        </CommandEmpty>
                        <CommandGroup>
                          <CommandItem
                            value="__none__"
                            onSelect={() =>
                              void meta.handleProxySelection(profile.id, null)
                            }
                          >
                            <LuCheck
                              className={cn(
                                "mr-2 size-4",
                                selectedId === null
                                  ? "opacity-100"
                                  : "opacity-0",
                              )}
                            />
                            {t("common.labels.none")}
                          </CommandItem>
                          {meta.storedProxies
                            .filter(
                              (proxy: StoredProxy) =>
                                !proxy.is_cloud_managed &&
                                !proxy.is_cloud_derived,
                            )
                            .map((proxy: StoredProxy) => (
                              <CommandItem
                                key={proxy.id}
                                value={proxy.name}
                                onSelect={() =>
                                  void meta.handleProxySelection(
                                    profile.id,
                                    proxy.id,
                                  )
                                }
                              >
                                <LuCheck
                                  className={cn(
                                    "mr-2 size-4",
                                    effectiveProxyId === proxy.id &&
                                      !effectiveVpn
                                      ? "opacity-100"
                                      : "opacity-0",
                                  )}
                                />
                                {proxy.name}
                              </CommandItem>
                            ))}
                        </CommandGroup>
                        {meta.vpnConfigs.length > 0 && (
                          <CommandGroup heading={t("profileTable.vpnsHeading")}>
                            {meta.vpnConfigs.map((vpn) => (
                              <CommandItem
                                key={vpn.id}
                                value={`vpn-${vpn.name}`}
                                onSelect={() =>
                                  void meta.handleVpnSelection(
                                    profile.id,
                                    vpn.id,
                                  )
                                }
                              >
                                <LuCheck
                                  className={cn(
                                    "mr-2 size-4",
                                    effectiveVpnId === vpn.id
                                      ? "opacity-100"
                                      : "opacity-0",
                                  )}
                                />
                                <Badge
                                  variant="outline"
                                  className="mr-1 px-1 py-0 text-[10px] leading-tight"
                                >
                                  WG
                                </Badge>
                                {vpn.name}
                              </CommandItem>
                            ))}
                          </CommandGroup>
                        )}
                        {meta.canCreateLocationProxy &&
                          meta.countries.length > 0 && (
                            <CommandGroup
                              heading={t("profileTable.createByCountryHeading")}
                            >
                              {meta.countries
                                .filter(
                                  (c) =>
                                    !meta.storedProxies.some(
                                      (p) =>
                                        p.is_cloud_derived &&
                                        p.geo_country === c.code,
                                    ),
                                )
                                .map((country) => (
                                  <CommandItem
                                    key={`country-${country.code}`}
                                    value={`create-${country.name}`}
                                    onSelect={() =>
                                      void meta.handleCreateCountryProxy(
                                        profile.id,
                                        country,
                                      )
                                    }
                                  >
                                    <span className="mr-2 size-4" />+{" "}
                                    {country.name}
                                  </CommandItem>
                                ))}
                            </CommandGroup>
                          )}
                      </CommandList>
                    </Command>
                  </PopoverContent>
                )}
              </Popover>
              {effectiveProxy && !effectiveVpn && !isDisabled && (
                <ProxyCheckButton
                  proxy={effectiveProxy}
                  profileId={profile.id}
                  checkingProfileId={meta.checkingProfileId}
                  cachedResult={meta.proxyCheckResults[effectiveProxy.id]}
                  setCheckingProfileId={setCheckingProfileId}
                  onCheckComplete={(result) => {
                    setProxyCheckResults((prev) => ({
                      ...prev,
                      [effectiveProxy.id]: result,
                    }));
                  }}
                  onCheckFailed={(result) => {
                    setProxyCheckResults((prev) => ({
                      ...prev,
                      [effectiveProxy.id]: result,
                    }));
                  }}
                />
              )}
            </div>
          );
        },
      },
      {
        id: "ext",
        size: 95,
        header: ({ table }) => {
          const meta = table.options.meta as TableMeta;
          return meta.t("profiles.table.ext");
        },
        cell: ({ row, table }) => {
          const meta = table.options.meta as TableMeta;
          const profile = row.original;
          return <ExtCell profile={profile} meta={meta} />;
        },
      },
      {
        id: "dns",
        size: 95,
        header: ({ table }) => {
          const meta = table.options.meta as TableMeta;
          return meta.t("profiles.table.dns");
        },
        cell: ({ row, table }) => {
          const meta = table.options.meta as TableMeta;
          const profile = row.original;
          return <DnsCell profile={profile} meta={meta} />;
        },
      },
      {
        id: "bot",
        size: 84,
        header: ({ table }) => {
          const meta = table.options.meta as TableMeta;
          if (meta.cookieBotCompact) return null;
          return meta.t("profiles.table.bot");
        },
        cell: ({ row, table }) => {
          const meta = table.options.meta as TableMeta;
          return <BotCell profile={row.original} meta={meta} />;
        },
      },
      {
        id: "sync",
        header: "",
        size: 28,
        cell: ({ row, table }) => {
          const profile = row.original;
          const meta = table.options.meta as TableMeta;
          const syncEntry = meta.syncStatuses[profile.id];
          const liveStatus = syncEntry?.status as
            | "syncing"
            | "waiting"
            | "synced"
            | "error"
            | "disabled"
            | undefined;

          const dot = getProfileSyncStatusDot(
            profile,
            liveStatus,
            meta.t,
            syncEntry?.error,
          );
          if (!dot) return null;

          return (
            <Tooltip>
              <TooltipTrigger asChild>
                <span className="flex h-9 w-full items-center justify-center">
                  {dot.encrypted ? (
                    <LuLock
                      className={`size-3 ${dot.color.replace("bg-", "text-")}${dot.animate ? " animate-pulse" : ""}`}
                    />
                  ) : (
                    <span
                      className={`size-2 rounded-full ${dot.color}${dot.animate ? " animate-pulse" : ""}`}
                    />
                  )}
                </span>
              </TooltipTrigger>
              <TooltipContent>{dot.tooltip}</TooltipContent>
            </Tooltip>
          );
        },
      },
      {
        id: "settings",
        size: 32,
        cell: ({ row, table }) => {
          const meta = table.options.meta as TableMeta;
          const profile = row.original;

          return (
            <div className="flex h-9 w-full items-center justify-end">
              <Button
                variant="ghost"
                className="size-7 p-0"
                disabled={!meta.isClient}
                onClick={() => {
                  setProfileForInfoDialog(profile);
                }}
              >
                <span className="sr-only">
                  {t("profiles.aria.profileInfo")}
                </span>
                <LuInfo className="size-4" />
              </Button>
            </div>
          );
        },
      },
    ],
    [t, setProfileForInfoDialog],
  );

  // Low-priority columns leave the table as the container narrows (most
  // expendable first); their data stays reachable via the profile info
  // dialog. Visibility (not CSS hiding) so table-fixed reclaims the width.
  // `bot` starts hidden and is switched on by the resize effect below. An
  // unentitled account must never see a paid column, not even for the frame
  // before the observer's first measurement lands.
  const [columnVisibility, setColumnVisibility] =
    React.useState<VisibilityState>({ created_at: false, bot: false });

  const table = useReactTable({
    data: profiles,
    columns,
    state: {
      sorting,
      rowSelection,
      columnVisibility,
    },
    onSortingChange: handleSortingChange,
    onRowSelectionChange: handleRowSelectionChange,
    onColumnVisibilityChange: setColumnVisibility,
    enableRowSelection: (row) => {
      const profile = row.original;
      const isRunning =
        browserState.isClient && runningProfiles.has(profile.id);
      const isLaunching = launchingProfiles.has(profile.id);
      const isStopping = stoppingProfiles.has(profile.id);
      return !isRunning && !isLaunching && !isStopping;
    },
    getSortedRowModel: getSortedRowModel(),
    getCoreRowModel: getCoreRowModel(),
    getRowId: (row) => row.id,
    meta: tableMeta,
  });

  const scrollParentRef = React.useRef<HTMLDivElement | null>(null);
  const columnWidth = React.useCallback(
    (id: string, sizePx: number) => {
      // The bot column is the one column with two shapes: a labelled state at
      // full width, a bare mark when the table is narrow. Taking a proportion
      // in the compact shape would waste the space the name column needs.
      if (id === "bot") {
        return containerWidth > 0 && containerWidth < BOT_LABEL_WIDTH
          ? "28px"
          : `${Math.max(84, Math.round(containerWidth * 0.09))}px`;
      }
      const proportions: Record<string, { pct: number; floor: number }> = {
        tags: { pct: 0.12, floor: 100 },
        note: { pct: 0.1, floor: 80 },
        proxy: { pct: 0.13, floor: 110 },
        ext: { pct: 0.11, floor: 95 },
        dns: { pct: 0.11, floor: 95 },
      };
      const p = proportions[id];
      if (!p) return `${sizePx}px`;
      return `${Math.max(p.floor, Math.round(containerWidth * p.pct))}px`;
    },
    [containerWidth],
  );
  const sortedRows = table.getRowModel().rows;
  useScrollFade(scrollParentRef);

  React.useEffect(() => {
    const el = scrollParentRef.current;
    if (!el) return;
    const update = () => {
      const w = el.clientWidth;
      setContainerWidth(Math.round(w / 8) * 8);
      setColumnVisibility((prev) => {
        const next: VisibilityState = {
          // Always hidden — sort-only column.
          created_at: false,
          dns: w >= 768,
          ext: w >= 672,
          note: w >= 576,
          tags: w >= 512,
          // Bot state survives further down than the other content columns:
          // by then it is a 28px mark, and it is the only place a row's
          // enrolment and its actions can be reached.
          bot: cookieBotUnlocked && w >= BOT_COLUMN_MIN_WIDTH,
        };
        return Object.keys(next).every((k) => prev[k] === next[k])
          ? prev
          : next;
      });
    };
    update();
    const ro = new ResizeObserver(update);
    ro.observe(el);
    return () => {
      ro.disconnect();
    };
  }, [cookieBotUnlocked]);

  // Compact 36px row from the redesign spec; estimateSize must match the
  // actual rendered row height or virtualizer placement drifts under scroll.
  const ROW_HEIGHT = 36;

  const rowVirtualizer = useVirtualizer({
    count: sortedRows.length,
    getScrollElement: () => scrollParentRef.current,
    estimateSize: () => ROW_HEIGHT,
    overscan: 8,
  });

  const virtualRows = rowVirtualizer.getVirtualItems();
  const totalSize = rowVirtualizer.getTotalSize();
  const paddingTop = virtualRows.length > 0 ? virtualRows[0].start : 0;
  const paddingBottom =
    virtualRows.length > 0
      ? totalSize - virtualRows[virtualRows.length - 1].end
      : 0;

  return (
    <>
      <div className="relative flex min-h-0 flex-1 flex-col">
        <div
          ref={scrollParentRef}
          className={cn(
            "scroll-fade relative min-h-0 flex-1 overflow-auto",
            // Clearance for the floating selection action bar (bottom-6 +
            // ~46px tall) so the last rows can scroll out from behind it.
            // Same predicate DataTableActionBar uses for its visibility.
            table.getFilteredSelectedRowModel().rows.length > 0 && "pb-20",
          )}
          style={
            {
              // Sticky table header is 32px tall (h-8); shift the top
              // fade band below it so the header stays fully opaque and
              // only body rows fade as they scroll past.
              "--scroll-fade-top-offset": "32px",
            } as React.CSSProperties
          }
        >
          <Table className="table-fixed" containerClassName="overflow-visible">
            <TableHeader className="sticky top-0 z-10 overflow-visible bg-background [&_tr]:border-0">
              {table.getHeaderGroups().map((headerGroup) => (
                <TableRow
                  key={headerGroup.id}
                  className="overflow-visible border-0!"
                >
                  {headerGroup.headers.map((header) => {
                    return (
                      <TableHead
                        key={header.id}
                        style={{
                          width: header.column.columnDef.meta?.flexWidth
                            ? undefined
                            : columnWidth(
                                header.column.id,
                                header.column.getSize(),
                              ),
                        }}
                      >
                        {header.isPlaceholder
                          ? null
                          : flexRender(
                              header.column.columnDef.header,
                              header.getContext(),
                            )}
                      </TableHead>
                    );
                  })}
                </TableRow>
              ))}
            </TableHeader>
            <TableBody className="overflow-visible">
              {sortedRows.length === 0 ? (
                isLoading ? (
                  Array.from({ length: 8 }, (_, i) => (
                    <TableRow
                      key={`skeleton-${i}`}
                      className="border-0!"
                      style={{ height: `${ROW_HEIGHT}px` }}
                    >
                      <TableCell
                        colSpan={table.getVisibleLeafColumns().length}
                        className="py-0"
                      >
                        <div className="flex items-center gap-3">
                          <Skeleton className="size-7 shrink-0 rounded-md" />
                          <Skeleton
                            className="h-3"
                            style={{ width: `${30 + ((i * 17) % 40)}%` }}
                          />
                          <div className="flex-1" />
                          <Skeleton className="h-3 w-16" />
                          <Skeleton className="h-3 w-10" />
                        </div>
                      </TableCell>
                    </TableRow>
                  ))
                ) : showOnboardingEmptyState ? (
                  <TableRow className="border-0! hover:bg-transparent">
                    <TableCell
                      colSpan={table.getVisibleLeafColumns().length}
                      className="py-16"
                    >
                      <div className="flex flex-col items-center gap-3 text-center">
                        <Logo className="size-12 text-muted-foreground" />
                        <div>
                          <p className="text-sm font-medium text-foreground">
                            {t("profiles.table.emptyTitle")}
                          </p>
                          <p className="mt-1 text-xs text-muted-foreground">
                            {t("profiles.table.emptyHint")}
                          </p>
                        </div>
                        <div className="mt-1 flex gap-2">
                          {onCreateProfile && (
                            <RippleButton size="sm" onClick={onCreateProfile}>
                              {t("profiles.table.emptyCreate")}
                            </RippleButton>
                          )}
                          {onImportProfiles && (
                            <RippleButton
                              size="sm"
                              variant="outline"
                              onClick={onImportProfiles}
                            >
                              {t("profiles.table.emptyImport")}
                            </RippleButton>
                          )}
                        </div>
                      </div>
                    </TableCell>
                  </TableRow>
                ) : (
                  <TableRow className="border-0! hover:bg-transparent">
                    <TableCell
                      colSpan={table.getVisibleLeafColumns().length}
                      className="py-16"
                    >
                      <div className="flex flex-col items-center gap-3 text-center">
                        <div className="grid size-12 place-items-center rounded-full bg-muted/60">
                          <LuUserSearch className="size-6 text-muted-foreground" />
                        </div>
                        <div>
                          <p className="text-sm font-medium text-foreground">
                            {t("profiles.table.emptyFilteredTitle")}
                          </p>
                          <p className="mt-1 text-xs text-muted-foreground">
                            {t("profiles.table.emptyFilteredHint")}
                          </p>
                        </div>
                        {onCreateProfile && (
                          <RippleButton
                            size="sm"
                            variant="outline"
                            className="mt-1"
                            onClick={onCreateProfile}
                          >
                            {t("profiles.table.emptyCreate")}
                          </RippleButton>
                        )}
                      </div>
                    </TableCell>
                  </TableRow>
                )
              ) : (
                <>
                  {paddingTop > 0 && (
                    <tr style={{ height: `${paddingTop}px` }}>
                      <td colSpan={table.getVisibleLeafColumns().length} />
                    </tr>
                  )}
                  {virtualRows.map((virtualRow) => {
                    const row = sortedRows[virtualRow.index];
                    const rowIsCrossOs = isCrossOsProfile(row.original);
                    const crossOsTitle = rowIsCrossOs
                      ? t("crossOs.viewOnly", {
                          os: getOSDisplayName(
                            row.original.host_os ||
                              row.original.wayfern_config?.os ||
                              "",
                          ),
                        })
                      : undefined;
                    return (
                      <TableRow
                        key={row.id}
                        data-state={row.getIsSelected() && "selected"}
                        title={crossOsTitle}
                        style={{ height: `${ROW_HEIGHT}px` }}
                        className={cn(
                          "overflow-visible border-0! hover:bg-muted",
                          rowIsCrossOs && "opacity-60",
                        )}
                      >
                        {row.getVisibleCells().map((cell) => (
                          <TableCell
                            key={cell.id}
                            className="overflow-visible py-0"
                            style={{
                              width: cell.column.columnDef.meta?.flexWidth
                                ? undefined
                                : columnWidth(
                                    cell.column.id,
                                    cell.column.getSize(),
                                  ),
                            }}
                          >
                            {flexRender(
                              cell.column.columnDef.cell,
                              cell.getContext(),
                            )}
                          </TableCell>
                        ))}
                      </TableRow>
                    );
                  })}
                  {paddingBottom > 0 && (
                    <tr style={{ height: `${paddingBottom}px` }}>
                      <td colSpan={table.getVisibleLeafColumns().length} />
                    </tr>
                  )}
                </>
              )}
            </TableBody>
          </Table>
        </div>
      </div>
      <DeleteConfirmationDialog
        isOpen={profileToDelete !== null}
        onClose={() => {
          setProfileToDelete(null);
        }}
        onConfirm={handleDelete}
        title={t("profiles.delete.title")}
        description={t("profiles.delete.description", {
          profileName: profileToDelete?.name ?? "",
        })}
        confirmButtonText={t("profiles.delete.confirmButton")}
        isLoading={isDeleting}
      />
      {profileForInfoDialog &&
        (() => {
          const infoProfile =
            profiles.find((p) => p.id === profileForInfoDialog.id) ??
            profileForInfoDialog;
          const infoIsRunning =
            browserState.isClient && runningProfiles.has(infoProfile.id);
          const infoIsLaunching = launchingProfiles.has(infoProfile.id);
          const infoIsStopping = stoppingProfiles.has(infoProfile.id);
          const infoIsCrossOs = isCrossOsProfile(infoProfile);
          const infoIsDisabled =
            infoIsRunning || infoIsLaunching || infoIsStopping || infoIsCrossOs;
          return (
            <ProfileInfoDialog
              isOpen={profileForInfoDialog !== null}
              onClose={() => {
                setProfileForInfoDialog(null);
              }}
              profile={infoProfile}
              storedProxies={storedProxies}
              vpnConfigs={vpnConfigs}
              onOpenTrafficDialog={(profileId) => {
                const profile = profiles.find((p) => p.id === profileId);
                setTrafficDialogProfile({ id: profileId, name: profile?.name });
              }}
              onOpenProfileSyncDialog={onOpenProfileSyncDialog}
              onAssignProfilesToGroup={onAssignProfilesToGroup}
              onConfigureWayfern={onConfigureWayfern}
              onCopyCookiesToProfile={onCopyCookiesToProfile}
              onOpenCookieManagement={onOpenCookieManagement}
              onAssignExtensionGroup={onAssignExtensionGroup}
              onOpenBypassRules={(profile) => {
                setBypassRulesProfile(profile);
              }}
              onOpenDnsBlocklist={(profile) => {
                setDnsBlocklistProfile(profile);
              }}
              onOpenLaunchHook={(profile) => {
                setLaunchHookProfile(profile);
              }}
              onCloneProfile={onCloneProfile}
              onLaunchWithSync={onLaunchWithSync}
              onSetPassword={onSetPassword}
              onChangePassword={onChangePassword}
              onRemovePassword={onRemovePassword}
              onDeleteProfile={(profile) => {
                setProfileForInfoDialog(null);
                setProfileToDelete(profile);
              }}
              crossOsUnlocked={crossOsUnlocked}
              isRunning={infoIsRunning}
              isDisabled={infoIsDisabled}
              isCrossOs={infoIsCrossOs}
              syncStatuses={syncStatuses}
            />
          );
        })()}
      <DataTableActionBar table={table}>
        <DataTableActionBarSelection table={table} />
        {onBulkRun && (
          <span className="relative inline-flex">
            <DataTableActionBarAction
              tooltip={
                bulkActionsUnlocked
                  ? t("profiles.actionBar.runSelected")
                  : t("profiles.actionBar.proRequired")
              }
              onClick={bulkActionsUnlocked ? onBulkRun : undefined}
              disabled={!bulkActionsUnlocked}
              size="icon"
            >
              <LuPlay className="fill-current" />
            </DataTableActionBarAction>
            {!bulkActionsUnlocked && (
              <ProBadge className="pointer-events-none absolute -top-2 -right-2" />
            )}
          </span>
        )}
        {onBulkStop && (
          <span className="relative inline-flex">
            <DataTableActionBarAction
              tooltip={
                bulkActionsUnlocked
                  ? t("profiles.actionBar.stopSelected")
                  : t("profiles.actionBar.proRequired")
              }
              onClick={bulkActionsUnlocked ? onBulkStop : undefined}
              disabled={!bulkActionsUnlocked}
              size="icon"
            >
              <LuSquare className="fill-current" />
            </DataTableActionBarAction>
            {!bulkActionsUnlocked && (
              <ProBadge className="pointer-events-none absolute -top-2 -right-2" />
            )}
          </span>
        )}
        {onBulkGroupAssignment && (
          <DataTableActionBarAction
            tooltip={t("profiles.actionBar.assignToGroup")}
            onClick={onBulkGroupAssignment}
            size="icon"
          >
            <LuUsers />
          </DataTableActionBarAction>
        )}
        {onBulkProxyAssignment && (
          <DataTableActionBarAction
            tooltip={t("profiles.actionBar.assignProxy")}
            onClick={onBulkProxyAssignment}
            size="icon"
          >
            <FiWifi />
          </DataTableActionBarAction>
        )}
        {onBulkExtensionGroupAssignment && (
          <DataTableActionBarAction
            tooltip={t("profiles.actionBar.assignExtensionGroup")}
            onClick={onBulkExtensionGroupAssignment}
            size="icon"
          >
            <LuPuzzle />
          </DataTableActionBarAction>
        )}
        {onBulkCopyCookies && (
          <DataTableActionBarAction
            tooltip={t("profiles.actionBar.copyCookies")}
            onClick={onBulkCopyCookies}
            size="icon"
          >
            <LuCookie />
          </DataTableActionBarAction>
        )}
        <span className="relative inline-flex">
          <DataTableActionBarAction
            tooltip={
              cookieBotUnlocked
                ? t("cookieBot.actionBar.enrol")
                : t("cookieBot.actionBar.proRequired")
            }
            onClick={cookieBotUnlocked ? handleBulkCookieBotEnrol : undefined}
            disabled={!cookieBotUnlocked}
            size="icon"
          >
            <LuMoon />
          </DataTableActionBarAction>
          {!cookieBotUnlocked && (
            <ProBadge className="pointer-events-none absolute -top-2 -right-2" />
          )}
        </span>
        {onBulkDelete && (
          <DataTableActionBarAction
            tooltip={t("common.buttons.delete")}
            onClick={onBulkDelete}
            size="icon"
            variant="destructive"
            className="border-destructive bg-destructive hover:bg-destructive"
          >
            <LuTrash2 />
          </DataTableActionBarAction>
        )}
      </DataTableActionBar>
      {trafficDialogProfile && (
        <TrafficDetailsDialog
          isOpen={trafficDialogProfile !== null}
          onClose={() => {
            setTrafficDialogProfile(null);
          }}
          profileId={trafficDialogProfile.id}
          profileName={trafficDialogProfile.name}
        />
      )}
      <ProfileBypassRulesDialog
        isOpen={bypassRulesProfile !== null}
        onClose={() => {
          setBypassRulesProfile(null);
        }}
        profileId={bypassRulesProfile?.id ?? null}
        initialRules={bypassRulesProfile?.proxy_bypass_rules ?? []}
      />
      <ProfileDnsBlocklistDialog
        isOpen={dnsBlocklistProfile !== null}
        onClose={() => {
          setDnsBlocklistProfile(null);
        }}
        profileId={dnsBlocklistProfile?.id ?? null}
        currentLevel={dnsBlocklistProfile?.dns_blocklist ?? null}
      />
      <ProfileLaunchHookDialog
        isOpen={launchHookProfile !== null}
        onClose={() => {
          setLaunchHookProfile(null);
        }}
        profileId={launchHookProfile?.id ?? null}
        currentLaunchHook={launchHookProfile?.launch_hook ?? null}
      />
      {botScheduleDialog && (
        <CookieBotEnrolDialog
          isOpen
          onClose={() => {
            setBotScheduleDialog(null);
          }}
          profiles={botScheduleDialog.profiles}
          existing={botScheduleDialog.existing}
          onOpenProfileSync={onOpenProfileSyncDialog}
          // "A proxy or VPN is required" is the precondition most profiles
          // fail, and without this the dialog showed the reason with no way to
          // act on it — one-click fixable from the Cookie Bot page and a dead
          // end from the row menu that is the primary entry point.
          onAssignProxy={onAssignProfilesToProxy}
          onSaved={() => {
            // Clearing after a bulk write mirrors the other bulk actions: the
            // selection has been acted on, and leaving it live invites a second
            // pass over profiles that are already enrolled.
            if (botScheduleDialog.profiles.length > 1) {
              onSelectedProfilesChange([]);
            }
          }}
        />
      )}
      <DeleteConfirmationDialog
        isOpen={pendingBulkEnrol !== null}
        onClose={() => {
          setPendingBulkEnrol(null);
        }}
        onConfirm={() => {
          if (!pendingBulkEnrol) return;
          setBotScheduleDialog({
            profiles: pendingBulkEnrol,
            existing: null,
          });
          setPendingBulkEnrol(null);
        }}
        title={t("cookieBot.enrol.confirmBulkTitle", {
          count:
            pendingBulkEnrol?.filter((p) => preflight(p).eligible).length ?? 0,
        })}
        description={t("cookieBot.enrol.confirmBulkDescription", {
          count:
            pendingBulkEnrol?.filter((p) => preflight(p).eligible).length ?? 0,
        })}
        confirmButtonText={t("cookieBot.enrol.confirmBulkButton", {
          count:
            pendingBulkEnrol?.filter((p) => preflight(p).eligible).length ?? 0,
        })}
        confirmButtonVariant="default"
        profileIds={pendingBulkEnrol
          ?.filter((p) => preflight(p).eligible)
          .map((p) => p.id)}
        profiles={pendingBulkEnrol?.map((p) => ({ id: p.id, name: p.name }))}
      />
      <CookieBotRunsDialog
        isOpen={botRunsProfile !== null}
        onClose={() => {
          setBotRunsProfile(null);
        }}
        profileId={botRunsProfile?.id ?? null}
        profileName={botRunsProfile?.name}
        onRunCancelled={() => {
          void refreshCookieBotState();
        }}
      />
      <DeleteConfirmationDialog
        isOpen={botUnenrolProfile !== null}
        onClose={() => {
          setBotUnenrolProfile(null);
        }}
        onConfirm={handleBotUnenrol}
        title={t("cookieBot.schedule.unenrolTitle", {
          name: botUnenrolProfile?.name ?? "",
        })}
        description={t("cookieBot.schedule.unenrolDescription")}
        confirmButtonText={t("cookieBot.schedule.unenrol")}
        isLoading={isUnenrolling}
      />
    </>
  );
}
