import { useState, useRef, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { copyToClipboard } from "@/lib/clipboard";
import {
  getUserInfo,
  getRemoteSyncUserInfo,
  getApiKeys,
  createApiKey,
  deleteApiKey,
  changePassword,
  deleteAccount,
  logoutUser,
  setupTOTP,
  enableTOTP,
  disableTOTP,
  getVersionInfo,
  releaseUrlFrom,
  getUserRoles,
  saveUserPreferences,
  getUserPreferences,
} from "@/main-axios";
import { getDatabaseTransferUrl } from "@/lib/database-transfer-url";
import { readRailPreference, setRailPreference } from "./rail-preferences";
import { useHostSidebarPreferences } from "./tree/hooks/useHostSidebarPreferences";
import {
  deleteWebAuthnCredential,
  listWebAuthnCredentials,
  registerWebAuthnCredential,
  type WebAuthnCredentialSummary,
  type WebAuthnUserVerification,
} from "@/api/webauthn-api";
import type { UserRole } from "@/main-axios";
import type React from "react";
import { isElectron } from "@/lib/electron";
import { RemoteSyncPanel } from "@/settings/RemoteSyncPanel.tsx";
import { shouldForceLocalPreferenceStorage } from "@/settings/remote-sync-state";
import { C2STunnelPresetManager } from "@/user/C2STunnelPresetManager";
import { Button } from "@/components/button";
import { Input } from "@/components/input";
import { VersionBadge } from "@/components/version-badge";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
  DialogFooter,
} from "@/components/dialog";
import {
  Activity,
  AlertCircle,
  CheckCircle2,
  ChevronDown,
  Copy,
  Database,
  Eye,
  EyeOff,
  KeyRound,
  LayoutTemplate,
  Network,
  Palette,
  Plus,
  RotateCcw,
  Shield,
  ShieldCheck,
  Trash2,
  Type,
  User,
  X,
} from "lucide-react";
import { SettingRow, FakeSwitch } from "@/components/section-card";
import { visibleRailItems } from "./rail-items";
import { InterfacePresetSettings } from "./InterfacePresetSettings";
import { useUiPreferencesContext } from "@/contexts/UiPreferencesContext";
import { KeybindingsDialog } from "./KeybindingsDialog";
import {
  ACCENT_PRESET_COLORS,
  applyAccentColor,
  applyFontSize,
  applyUiFont,
  FONT_SIZES,
  UI_FONTS,
} from "@/lib/theme";
import type { ApiKey } from "@/main-axios";
import { useTheme } from "@/components/theme-provider";
import type { FontSizeId, ThemeId, UiFontId } from "@/types/ui-types";
import { toast } from "sonner";
import { changeAppLanguage, normalizeLanguageCode } from "@/i18n/i18n";
import { clearLocalAdaptivePreferences } from "@/lib/local-adaptive-preferences";
import { ConnectionDefaultsSettings } from "./ConnectionDefaultsSettings";

type UserProfileSection =
  | "account"
  | "interface"
  | "appearance"
  | "security"
  | "api-keys"
  | "data"
  | "c2s-tunnels";

const THEMES: { id: ThemeId; preview: string }[] = [
  { id: "system", preview: "auto" },
  { id: "light", preview: "#ffffff" },
  { id: "dark", preview: "#1a1c22" },
  { id: "dracula", preview: "#282a36" },
  { id: "catppuccin", preview: "#1e1e2e" },
  { id: "nord", preview: "#2e3440" },
  { id: "solarized", preview: "#002b36" },
  { id: "tokyo-night", preview: "#1a1b26" },
  { id: "one-dark", preview: "#282c34" },
  { id: "gruvbox", preview: "#282828" },
];

const LANGUAGES = [
  { code: "en", label: "English" },
  { code: "af", label: "Afrikaans" },
  { code: "ar", label: "العربية" },
  { code: "bn", label: "বাংলা" },
  { code: "bg", label: "Български" },
  { code: "ca", label: "Català" },
  { code: "zh-CN", label: "中文 (简体)" },
  { code: "zh-TW", label: "中文 (繁體)" },
  { code: "cs", label: "Čeština" },
  { code: "da", label: "Dansk" },
  { code: "nl", label: "Nederlands" },
  { code: "fi", label: "Suomi" },
  { code: "fr", label: "Français" },
  { code: "de", label: "Deutsch" },
  { code: "el", label: "Ελληνικά" },
  { code: "he", label: "עברית" },
  { code: "hi", label: "हिन्दी" },
  { code: "hu", label: "Magyar" },
  { code: "id", label: "Indonesia" },
  { code: "it", label: "Italiano" },
  { code: "ja", label: "日本語" },
  { code: "ko", label: "한국어" },
  { code: "no", label: "Norsk" },
  { code: "pl", label: "Polski" },
  { code: "pt-PT", label: "Português (PT)" },
  { code: "pt-BR", label: "Português (BR)" },
  { code: "ro", label: "Română" },
  { code: "ru", label: "Русский" },
  { code: "sr", label: "Српски" },
  { code: "es-ES", label: "Español" },
  { code: "sv-SE", label: "Svenska" },
  { code: "th", label: "ไทย" },
  { code: "tr", label: "Türkçe" },
  { code: "uk", label: "Українська" },
  { code: "vi", label: "Tiếng Việt" },
];

export function AccordionSection({
  id,
  label,
  icon,
  open,
  onToggle,
  hidden = false,
  children,
}: {
  id: string;
  label: string;
  icon: React.ReactNode;
  open: boolean;
  onToggle: () => void;
  hidden?: boolean;
  children: React.ReactNode;
}) {
  if (hidden) return null;

  return (
    <div className="border border-border bg-card overflow-hidden">
      <button
        onClick={onToggle}
        aria-expanded={open}
        aria-controls={`${id}-content`}
        className="flex items-center gap-2 w-full px-3 py-2.5 text-left hover:bg-muted/40 transition-colors"
      >
        <span className="text-muted-foreground shrink-0">{icon}</span>
        <span className="text-xs font-bold uppercase tracking-widest text-foreground flex-1">
          {label}
        </span>
        <ChevronDown
          className={`size-3.5 text-muted-foreground shrink-0 transition-transform duration-150 ${open ? "rotate-180" : ""}`}
        />
      </button>
      {open && (
        <div id={`${id}-content`} className="border-t border-border px-3 pb-3">
          {children}
        </div>
      )}
    </div>
  );
}

type ApiErrorLike = {
  response?: {
    data?: {
      error?: string;
    };
  };
};

function apiErrorMessage(error: unknown, fallback: string) {
  return (error as ApiErrorLike).response?.data?.error || fallback;
}

type CreatedProfileApiKey = {
  id: string;
  name: string;
  token?: string;
  tokenPrefix?: string;
  createdAt?: string;
  expiresAt?: string | null;
};

export function NewApiKeyDialog({
  open,
  onOpenChange,
  onAdd,
  userId,
}: {
  open: boolean;
  onOpenChange: (v: boolean) => void;
  onAdd: (key: CreatedProfileApiKey) => void;
  userId: string;
}) {
  const { t } = useTranslation();
  const [name, setName] = useState("");
  const [expiry, setExpiry] = useState("");
  const [createdToken, setCreatedToken] = useState<string | null>(null);
  const [creating, setCreating] = useState(false);

  const close = () => {
    setName("");
    setExpiry("");
    setCreatedToken(null);
    onOpenChange(false);
  };

  const handleCreate = async () => {
    if (!name.trim()) {
      toast.error(t("newUi.sidebar.userProfile.apiKeyNameRequired"));
      return;
    }
    if (creating) return;
    setCreating(true);
    try {
      const created = await createApiKey(
        name.trim(),
        userId,
        expiry ? new Date(expiry).toISOString() : undefined,
      );
      onAdd(created);
      setCreatedToken(created.token);
      toast.success(t("newUi.sidebar.userProfile.apiKeyCreated", { name }));
    } catch {
      toast.error(t("newUi.sidebar.userProfile.apiKeyCreateFailed"));
    } finally {
      setCreating(false);
    }
  };

  return (
    <Dialog
      open={open}
      onOpenChange={(nextOpen) => {
        if (nextOpen || !createdToken) onOpenChange(nextOpen);
      }}
    >
      <DialogContent className="sm:max-w-md rounded-none border-border bg-card p-0 gap-0 overflow-hidden">
        <DialogHeader className="px-5 pt-5 pb-4 border-b border-border">
          <div className="flex items-center gap-2.5">
            <div className="size-8 border border-border bg-muted flex items-center justify-center shrink-0">
              <Network className="size-3.5 text-accent-brand" />
            </div>
            <div>
              <DialogTitle className="text-base font-bold leading-none">
                {t("newUi.sidebar.userProfile.createApiKeyTitle")}
              </DialogTitle>
              <DialogDescription className="text-xs text-muted-foreground mt-0.5">
                {t("newUi.sidebar.userProfile.createApiKeyDescription")}
              </DialogDescription>
            </div>
          </div>
        </DialogHeader>

        {createdToken ? (
          <div className="flex flex-col gap-3 px-5 py-4">
            <span className="text-xs font-semibold text-accent-brand">
              {t("newUi.sidebar.userProfile.apiKeyCreatedWarning")}
            </span>
            <div className="flex items-center gap-2 border border-border bg-muted/30 px-2 py-2">
              <code className="min-w-0 flex-1 break-all text-xs text-accent-brand">
                {createdToken}
              </code>
              <Button
                variant="ghost"
                size="icon"
                className="size-7 shrink-0"
                aria-label={t("newUi.sidebar.userProfile.copyApiKey")}
                onClick={() => {
                  copyToClipboard(createdToken);
                  toast.info(t("newUi.sidebar.userProfile.copiedToClipboard"));
                }}
              >
                <Copy className="size-3.5" />
              </Button>
            </div>
          </div>
        ) : (
          <div className="flex flex-col gap-4 px-5 py-4">
            <div className="flex flex-col gap-1.5">
              <label className="text-xs font-bold uppercase tracking-widest text-muted-foreground">
                {t("newUi.sidebar.userProfile.apiKeyNameLabel")}
              </label>
              <Input
                autoFocus
                placeholder={t(
                  "newUi.sidebar.userProfile.apiKeyNamePlaceholder",
                )}
                value={name}
                onChange={(e) => setName(e.target.value)}
                onKeyDown={(e) => e.key === "Enter" && handleCreate()}
                className="rounded-none bg-muted/50 border-border text-sm h-9"
              />
            </div>

            <div className="flex flex-col gap-1.5">
              <label className="text-xs font-bold uppercase tracking-widest text-muted-foreground">
                {t("newUi.sidebar.userProfile.expiryDateLabel")}{" "}
                <span className="text-muted-foreground/50 normal-case font-medium">
                  ({t("newUi.sidebar.userProfile.optional")})
                </span>
              </label>
              <Input
                type="date"
                value={expiry}
                onChange={(e) => setExpiry(e.target.value)}
                className="rounded-none bg-muted/50 border-border text-sm h-9"
              />
            </div>
          </div>
        )}

        <DialogFooter className="px-5 py-3 border-t border-border bg-muted/20">
          {createdToken ? (
            <Button
              variant="outline"
              className="rounded-none border-accent-brand/40 text-[10px] font-bold uppercase tracking-widest text-accent-brand"
              onClick={close}
            >
              {t("newUi.sidebar.userProfile.done")}
            </Button>
          ) : (
            <>
              <Button
                variant="ghost"
                onClick={close}
                className="rounded-none text-[10px] font-bold uppercase tracking-widest"
              >
                {t("newUi.sidebar.userProfile.cancel")}
              </Button>
              <Button
                variant="outline"
                className="border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 rounded-none text-[10px] font-bold uppercase tracking-widest gap-1.5"
                onClick={handleCreate}
                disabled={creating}
              >
                <KeyRound className="size-3" />{" "}
                {t("newUi.sidebar.userProfile.createKey")}
              </Button>
            </>
          )}
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

function PasswordChangeSection({
  showPassword,
  setShowPassword,
  onLogout,
}: {
  showPassword: boolean;
  setShowPassword: (v: boolean | ((prev: boolean) => boolean)) => void;
  onLogout?: () => void;
}) {
  const { t } = useTranslation();
  const [currentPw, setCurrentPw] = useState("");
  const [newPw, setNewPw] = useState("");
  const [confirmPw, setConfirmPw] = useState("");

  async function handleUpdate() {
    if (!currentPw || !newPw) {
      toast.error(t("newUi.sidebar.userProfile.passwordFieldsRequired"));
      return;
    }
    if (newPw !== confirmPw) {
      toast.error(t("newUi.sidebar.userProfile.passwordMismatch"));
      return;
    }
    if (newPw.length < 6) {
      toast.error(t("newUi.sidebar.userProfile.passwordTooShort"));
      return;
    }
    try {
      await changePassword(currentPw, newPw);
      toast.success(t("newUi.sidebar.userProfile.passwordUpdated"));
      setCurrentPw("");
      setNewPw("");
      setConfirmPw("");
      onLogout?.();
    } catch (e: unknown) {
      toast.error(
        apiErrorMessage(e, t("newUi.sidebar.userProfile.passwordUpdateFailed")),
      );
    }
  }

  return (
    <div className="flex flex-col gap-3 border-t border-border pt-3">
      <span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
        {t("newUi.sidebar.userProfile.changePassword")}
      </span>
      <div className="flex flex-col gap-1.5">
        <label className="text-[10px] font-semibold text-muted-foreground uppercase tracking-widest">
          {t("newUi.sidebar.userProfile.currentPasswordLabel")}
        </label>
        <div className="relative">
          <Input
            type={showPassword ? "text" : "password"}
            placeholder={t(
              "newUi.sidebar.userProfile.currentPasswordPlaceholder",
            )}
            value={currentPw}
            onChange={(e) => setCurrentPw(e.target.value)}
            className="pr-9 text-sm"
          />
          <button
            onClick={() => setShowPassword((o) => !o)}
            className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
          >
            {showPassword ? (
              <EyeOff className="size-4" />
            ) : (
              <Eye className="size-4" />
            )}
          </button>
        </div>
      </div>
      <div className="flex flex-col gap-1.5">
        <label className="text-[10px] font-semibold text-muted-foreground uppercase tracking-widest">
          {t("newUi.sidebar.userProfile.newPasswordLabel")}
        </label>
        <Input
          type="password"
          placeholder={t("newUi.sidebar.userProfile.newPasswordPlaceholder")}
          value={newPw}
          onChange={(e) => setNewPw(e.target.value)}
          className="text-sm"
        />
      </div>
      <div className="flex flex-col gap-1.5">
        <label className="text-[10px] font-semibold text-muted-foreground uppercase tracking-widest">
          {t("newUi.sidebar.userProfile.confirmPasswordLabel")}
        </label>
        <Input
          type="password"
          placeholder={t(
            "newUi.sidebar.userProfile.confirmPasswordPlaceholder",
          )}
          value={confirmPw}
          onChange={(e) => setConfirmPw(e.target.value)}
          className="text-sm"
        />
      </div>
      <Button
        variant="outline"
        size="sm"
        className="border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand self-end"
        onClick={handleUpdate}
      >
        <KeyRound className="size-3.5" />
        {t("newUi.sidebar.userProfile.updatePassword")}
      </Button>
    </div>
  );
}

export function UserProfilePanel({
  username,
  onLogout,
  userPrefs,
  onPrefsChange,
  remoteSyncInitialServerUrl,
}: {
  username?: string;
  onLogout?: () => void;
  remoteSyncInitialServerUrl?: string;
  userPrefs?: {
    reopenTabsOnLogin: boolean;
    storageMode?: string | null;
    commandAutocomplete?: boolean | null;
    commandPaletteEnabled?: boolean | null;
    aiAssistantEnabled?: boolean | null;
    aiReadOnlyCommands?: boolean | null;
    showHostTags?: boolean | null;
    hostTrayOnClick?: boolean | null;
    compactHostView?: boolean | null;
    pinAppRail?: boolean | null;
    expandAppRailOnHover?: boolean | null;
    foldersCollapsed?: boolean | null;
    confirmSnippetExecution?: boolean | null;
    disableUpdateCheck?: boolean | null;
    confirmTabClose?: boolean | null;
    hiddenRailTabs?: string | null;
    statusColorScheme?: string | null;
  };
  onPrefsChange?: (prefs: {
    reopenTabsOnLogin?: boolean;
    storageMode?: "local" | "cloud";
  }) => void;
}) {
  const { t } = useTranslation();
  const themeLabel: Record<ThemeId, string> = {
    system: t("newUi.sidebar.userProfile.themeSystem"),
    light: t("newUi.sidebar.userProfile.themeLight"),
    dark: t("newUi.sidebar.userProfile.themeDark"),
    dracula: t("newUi.sidebar.userProfile.themeDracula"),
    catppuccin: t("newUi.sidebar.userProfile.themeCatppuccin"),
    nord: t("newUi.sidebar.userProfile.themeNord"),
    solarized: t("newUi.sidebar.userProfile.themeSolarized"),
    "tokyo-night": t("newUi.sidebar.userProfile.themeTokyoNight"),
    "one-dark": t("newUi.sidebar.userProfile.themeOneDark"),
    gruvbox: t("newUi.sidebar.userProfile.themeGruvbox"),
  };
  const [openSections, setOpenSections] = useState<Set<UserProfileSection>>(
    () => new Set(["account"]),
  );

  // User info
  const [userId, setUserId] = useState("");
  const [accountUsername, setAccountUsername] = useState(username ?? "");
  const [accountTotpEnabled, setAccountTotpEnabled] = useState(false);
  const [userRole, setUserRole] = useState("");
  const [authMethod, setAuthMethod] = useState("");
  const [version, setVersion] = useState("");
  const [versionStatus, setVersionStatus] = useState<
    "up_to_date" | "requires_update" | "beta"
  >("up_to_date");
  const [releaseUrl, setReleaseUrl] = useState("");
  const [isOidc, setIsOidc] = useState(false);
  const [isDualAuth, setIsDualAuth] = useState(false);

  // TOTP
  const [totpEnabled, setTotpEnabled] = useState(false);
  const [totpStep, setTotpStep] = useState<
    "idle" | "setup" | "verify" | "backup"
  >("idle");
  const [totpQrCode, setTotpQrCode] = useState("");
  const [totpSecret, setTotpSecret] = useState("");
  const [totpCode, setTotpCode] = useState("");
  const [totpBackupCodes, setTotpBackupCodes] = useState<string[]>([]);
  const [totpLoading, setTotpLoading] = useState(false);
  const [showDisableTotp, setShowDisableTotp] = useState(false);
  const [disableTotpInput, setDisableTotpInput] = useState("");
  const [showAddTotp, setShowAddTotp] = useState(false);
  const [addTotpInput, setAddTotpInput] = useState("");
  const [addingTotpAuthenticator, setAddingTotpAuthenticator] = useState(false);
  const [passkeys, setPasskeys] = useState<WebAuthnCredentialSummary[]>([]);
  const [passkeyLoading, setPasskeyLoading] = useState(false);
  const [passkeyName, setPasskeyName] = useState("");
  const [passkeyUserVerification, setPasskeyUserVerification] =
    useState<WebAuthnUserVerification>("preferred");

  // Delete account
  const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
  const [deletePassword, setDeletePassword] = useState("");
  const [deleteLoading, setDeleteLoading] = useState(false);

  // Data export/import
  const [importFile, setImportFile] = useState<File | null>(null);
  const [exportLoading, setExportLoading] = useState(false);
  const [importLoading, setImportLoading] = useState(false);

  // UI state
  const [showPassword, setShowPassword] = useState(false);
  const [newKeyOpen, setNewKeyOpen] = useState(false);
  const colorInputRef = useRef<HTMLInputElement>(null);
  const { theme, setTheme } = useTheme();
  const localSnapshot = useRef<Record<string, string | null>>({});

  // Appearance state — initialized from localStorage
  const [accentColor, setAccentColor] = useState(
    () => localStorage.getItem("termix-accent") ?? "#f59145",
  );
  const [customColorInput, setCustomColorInput] = useState(
    () => localStorage.getItem("termix-accent") ?? "#f59145",
  );
  const [fontSize, setFontSize] = useState<FontSizeId>(
    () => (localStorage.getItem("termix-font-size") as FontSizeId) ?? "md",
  );
  const [uiFont, setUiFont] = useState<UiFontId>(
    () =>
      (localStorage.getItem("termix-ui-font") as UiFontId) ?? "jetbrains-mono",
  );
  const [language, setLanguage] = useState(() =>
    normalizeLanguageCode(localStorage.getItem("i18nextLng")),
  );
  const [storageMode, setStorageMode] = useState<"local" | "cloud">(() =>
    userPrefs?.storageMode === "cloud" ? "cloud" : "local",
  );

  useEffect(() => {
    if (userPrefs?.storageMode) {
      setStorageMode(userPrefs.storageMode === "cloud" ? "cloud" : "local");
    }
  }, [userPrefs?.storageMode]);

  // Remote sync is not connected by default on the desktop app (it's an
  // opt-in feature configured from this same panel). "cloud" storage mode
  // and Termix ID both assume a real multi-device server account, so they
  // stay hidden/forced-off until the user actually connects one.
  const [isRemoteSyncConnected, setIsRemoteSyncConnected] = useState<
    boolean | null
  >(() => (isElectron() ? null : true));

  useEffect(() => {
    if (!isElectron()) return;
    let cancelled = false;
    const refreshSyncStatus = () => {
      window.electronAPI
        ?.invoke?.("get-remote-sync-config")
        .then((config) => {
          if (!cancelled) {
            setIsRemoteSyncConnected(
              !!(config as { serverUrl?: string } | null)?.serverUrl,
            );
          }
        })
        .catch(() => {});
    };
    refreshSyncStatus();
    const unsubscribe = window.electronAPI?.onRemoteSyncStatusChanged?.(() =>
      refreshSyncStatus(),
    );
    return () => {
      cancelled = true;
      unsubscribe?.();
    };
  }, []);

  useEffect(() => {
    if (
      shouldForceLocalPreferenceStorage(
        isElectron(),
        isRemoteSyncConnected,
        storageMode,
      )
    ) {
      setStorageMode("local");
      onPrefsChange?.({ storageMode: "local" });
    }
  }, [isRemoteSyncConnected, storageMode, onPrefsChange]);

  // Settings toggles — all backed by localStorage
  const [commandAutocomplete, setCommandAutocomplete] = useState(
    () => localStorage.getItem("commandAutocomplete") === "true",
  );
  const [terminalLinkClickBehavior, setTerminalLinkClickBehavior] = useState(
    () => localStorage.getItem("terminalLinkClickBehavior") ?? "confirm",
  );
  const [commandPaletteEnabled, setCommandPaletteEnabled] = useState(() => {
    const v = localStorage.getItem("commandPaletteShortcutEnabled");
    return v !== null ? v === "true" : true;
  });
  const [keybindingsDialogOpen, setKeybindingsDialogOpen] = useState(false);
  const [aiAssistantEnabled, setAiAssistantEnabled] = useState(false);
  const [aiReadOnlyCommands, setAiReadOnlyCommands] = useState(false);
  // Null until the status call answers; the whole section stays hidden while
  // unknown so a user who cannot have the feature never sees it mentioned.
  const [aiGloballyEnabled, setAiGloballyEnabled] = useState<boolean | null>(
    null,
  );
  // Sidebar display customization (density, tags, tray trigger, status
  // colors) now lives in the dedicated Customize Sidebar panel opened from
  // the Hosts toolbar, not here -- resetToDefaults still needs write access.
  const { update: updateSidebarPrefs } = useHostSidebarPreferences();
  const uiPrefs = useUiPreferencesContext();

  useEffect(() => {
    let cancelled = false;
    import("@/api/ai-api")
      .then(({ getAiStatus }) => getAiStatus())
      .then((status) => {
        if (cancelled) return;
        setAiGloballyEnabled(status.globallyEnabled);
        setAiAssistantEnabled(status.enabled);
        setAiReadOnlyCommands(status.allowReadOnlyCommands);
      })
      .catch(() => {
        if (!cancelled) setAiGloballyEnabled(false);
      });
    return () => {
      cancelled = true;
    };
  }, []);

  /**
   * Turning the assistant off also hides its rail tab, so declining removes
   * the feature from view entirely rather than leaving an inert entry behind.
   */
  const applyAiEnabled = (enabled: boolean) => {
    setAiAssistantEnabled(enabled);

    const hidden = new Set<string>(
      JSON.parse(localStorage.getItem("hiddenRailTabs") ?? "[]"),
    );
    if (enabled) hidden.delete("ai");
    else hidden.add("ai");

    const serialized = JSON.stringify([...hidden]);
    localStorage.setItem("hiddenRailTabs", serialized);
    setHiddenRailTabs(hidden);
    window.dispatchEvent(new Event("hiddenRailTabsChanged"));

    if (storageMode === "cloud") {
      saveToCloud({ aiAssistantEnabled: enabled, hiddenRailTabs: serialized });
    }
  };
  const [pinAppRail, setPinAppRail] = useState(() =>
    readRailPreference("pinAppRail"),
  );
  const [expandAppRailOnHover, setExpandAppRailOnHover] = useState(() =>
    readRailPreference("expandAppRailOnHover"),
  );
  // Read values are unused now that the Snippets settings UI lives in
  // SnippetsPanel.tsx; the setters still back the cloud-sync/reset/snapshot
  // machinery for these two localStorage-backed prefs below.
  const [_foldersCollapsed, setFoldersCollapsed] = useState(
    () => localStorage.getItem("defaultSnippetFoldersCollapsed") !== "false",
  );
  const [_confirmSnippetExecution, setConfirmSnippetExecution] = useState(
    () => localStorage.getItem("confirmSnippetExecution") === "true",
  );
  const [disableUpdateCheck, setDisableUpdateCheck] = useState(
    () => localStorage.getItem("disableUpdateCheck") === "true",
  );
  const [confirmTabClose, setConfirmTabClose] = useState(
    () => localStorage.getItem("confirmTabClose") === "true",
  );
  const [hiddenRailTabs, setHiddenRailTabs] = useState<Set<string>>(() => {
    try {
      const stored = localStorage.getItem("hiddenRailTabs");
      return stored ? new Set(JSON.parse(stored)) : new Set();
    } catch {
      return new Set();
    }
  });

  // API keys
  const [apiKeys, setApiKeys] = useState<ApiKey[]>([]);

  // RBAC roles
  const [userRoles, setUserRoles] = useState<UserRole[]>([]);

  useEffect(() => {
    getUserInfo()
      .then(async (localInfo) => {
        setUserId(localInfo.userId);
        setTotpEnabled(localInfo.totp_enabled ?? false);
        setIsOidc(localInfo.is_oidc ?? false);
        setIsDualAuth(localInfo.is_dual_auth ?? false);
        const remoteInfo = await getRemoteSyncUserInfo();
        const info = remoteInfo ?? localInfo;
        setAccountUsername(info.username);
        setAccountTotpEnabled(info.totp_enabled ?? false);
        setUserRole(
          info.is_admin
            ? t("newUi.sidebar.userProfile.roleAdministrator")
            : t("newUi.sidebar.userProfile.roleUser"),
        );
        if (info.is_dual_auth) {
          setAuthMethod(t("newUi.sidebar.userProfile.authMethodDual"));
        } else if (info.is_oidc) {
          setAuthMethod(t("newUi.sidebar.userProfile.authMethodOidc"));
        } else {
          setAuthMethod(t("newUi.sidebar.userProfile.authMethodLocal"));
        }
        if (remoteInfo) {
          setUserRoles(remoteInfo.roles ?? []);
        } else {
          getUserRoles(localInfo.userId)
            .then(({ roles }) => setUserRoles(roles ?? []))
            .catch(() => {});
        }
      })
      .catch(() => {});
    getApiKeys()
      .then(({ apiKeys: keys }) => setApiKeys(keys))
      .catch(() => {});
    listWebAuthnCredentials()
      .then(({ credentials }) => setPasskeys(credentials ?? []))
      .catch(() => {});
    getVersionInfo()
      .then((info) => {
        setVersion(info.localVersion);
        setVersionStatus(info.status ?? "up_to_date");
        setReleaseUrl(releaseUrlFrom(info));
      })
      .catch(() => {});
  }, [t]);

  // The rail can toggle these from its right-click menu while this panel is
  // mounted, so mirror the change back into the switches instead of letting
  // them drift from the stored value.
  useEffect(() => {
    const pinHandler = () => setPinAppRail(readRailPreference("pinAppRail"));
    const hoverHandler = () =>
      setExpandAppRailOnHover(readRailPreference("expandAppRailOnHover"));
    window.addEventListener("pinAppRailChanged", pinHandler);
    window.addEventListener("expandAppRailOnHoverChanged", hoverHandler);
    return () => {
      window.removeEventListener("pinAppRailChanged", pinHandler);
      window.removeEventListener("expandAppRailOnHoverChanged", hoverHandler);
    };
  }, []);

  function saveToCloud(prefs: Parameters<typeof saveUserPreferences>[0]) {
    void saveUserPreferences(prefs).catch(() => {});
  }

  async function handleStorageModeChange(mode: "local" | "cloud") {
    setStorageMode(mode);
    onPrefsChange?.({ storageMode: mode });
    if (mode === "cloud") {
      // Snapshot current browser localStorage values so any tab can restore them later
      const SNAPSHOT_KEYS = [
        "termix-accent",
        "termix-font-size",
        "termix-ui-font",
        "i18nextLng",
        "commandAutocomplete",
        "commandPaletteShortcutEnabled",
        "showHostTags",
        "hostTrayOnClick",
        "compactHostView",
        "pinAppRail",
        "expandAppRailOnHover",
        "defaultSnippetFoldersCollapsed",
        "snippetShowCommands",
        "confirmSnippetExecution",
        "disableUpdateCheck",
        "confirmTabClose",
        "hiddenRailTabs",
        "statusColorScheme",
        "uiPreferences",
        // Layout/view keys that were never snapshotted, so switching to cloud
        // and back silently reset them.
        "dashboardTab.slots",
        "dashboardTab.mainWidthPct",
        "termix-terminal-toolbar-density",
        "fileManagerViewMode",
      ];
      const snap: Record<string, string | null> = { __theme: theme };
      for (const key of SNAPSHOT_KEYS) snap[key] = localStorage.getItem(key);
      localSnapshot.current = snap;
      localStorage.setItem("termix-local-snapshot", JSON.stringify(snap));

      try {
        const prefs = await getUserPreferences();
        if (prefs.theme) setTheme(prefs.theme as ThemeId);
        if (prefs.fontSize) {
          setFontSize(prefs.fontSize as FontSizeId);
          applyFontSize(prefs.fontSize as FontSizeId);
        }
        if (prefs.accentColor) {
          setAccentColor(prefs.accentColor);
          setCustomColorInput(prefs.accentColor);
          localStorage.setItem("termix-accent", prefs.accentColor);
          applyAccentColor(prefs.accentColor);
        }
        if (prefs.language) {
          const language = await changeAppLanguage(prefs.language);
          setLanguage(language);
        }
        if (prefs.commandAutocomplete != null) {
          setCommandAutocomplete(prefs.commandAutocomplete);
          localStorage.setItem(
            "commandAutocomplete",
            String(prefs.commandAutocomplete),
          );
        }
        if (prefs.commandPaletteEnabled != null) {
          setCommandPaletteEnabled(prefs.commandPaletteEnabled);
          localStorage.setItem(
            "commandPaletteShortcutEnabled",
            String(prefs.commandPaletteEnabled),
          );
        }
        if (prefs.aiAssistantEnabled != null) {
          setAiAssistantEnabled(prefs.aiAssistantEnabled);
        }
        if (prefs.aiReadOnlyCommands != null) {
          setAiReadOnlyCommands(prefs.aiReadOnlyCommands);
        }
        // showHostTags/hostTrayOnClick/compactHostView/statusColorScheme are
        // no longer restored here -- useHostSidebarPreferences independently
        // fetches its own authoritative copy from /host-sidebar/preferences.
        if (prefs.pinAppRail != null) {
          setPinAppRail(prefs.pinAppRail);
          localStorage.setItem("pinAppRail", String(prefs.pinAppRail));
          window.dispatchEvent(new Event("pinAppRailChanged"));
        }
        if (prefs.expandAppRailOnHover != null) {
          setExpandAppRailOnHover(prefs.expandAppRailOnHover);
          localStorage.setItem(
            "expandAppRailOnHover",
            String(prefs.expandAppRailOnHover),
          );
          window.dispatchEvent(new Event("expandAppRailOnHoverChanged"));
        }
        if (prefs.foldersCollapsed != null) {
          setFoldersCollapsed(prefs.foldersCollapsed);
          localStorage.setItem(
            "defaultSnippetFoldersCollapsed",
            String(prefs.foldersCollapsed),
          );
        }
        if (prefs.confirmSnippetExecution != null) {
          setConfirmSnippetExecution(prefs.confirmSnippetExecution);
          localStorage.setItem(
            "confirmSnippetExecution",
            String(prefs.confirmSnippetExecution),
          );
        }
        if (prefs.disableUpdateCheck != null) {
          setDisableUpdateCheck(prefs.disableUpdateCheck);
          localStorage.setItem(
            "disableUpdateCheck",
            String(prefs.disableUpdateCheck),
          );
        }
        if (prefs.confirmTabClose != null) {
          setConfirmTabClose(prefs.confirmTabClose);
          localStorage.setItem(
            "confirmTabClose",
            String(prefs.confirmTabClose),
          );
        }
        if (prefs.hiddenRailTabs != null) {
          const s = new Set<string>(JSON.parse(prefs.hiddenRailTabs));
          setHiddenRailTabs(s);
          localStorage.setItem("hiddenRailTabs", prefs.hiddenRailTabs);
          window.dispatchEvent(new CustomEvent("hiddenRailTabsChanged"));
        }
      } catch {
        // leave UI as-is on error
      }
      saveToCloud({ storageMode: "cloud" });
    } else {
      restoreLocalSnapshot();
      saveToCloud({ storageMode: "local" });
    }
  }

  function resetToDefaults() {
    clearLocalAdaptivePreferences();
    const DEFAULT_ACCENT = "#f59145";
    setTheme("system");
    setFontSize("md");
    applyFontSize("md");
    setUiFont("jetbrains-mono");
    applyUiFont("jetbrains-mono");
    setAccentColor(DEFAULT_ACCENT);
    setCustomColorInput(DEFAULT_ACCENT);
    localStorage.setItem("termix-accent", DEFAULT_ACCENT);
    applyAccentColor(DEFAULT_ACCENT);
    setLanguage("en");
    void changeAppLanguage("en");
    setCommandAutocomplete(false);
    localStorage.setItem("commandAutocomplete", "false");
    setCommandPaletteEnabled(true);
    localStorage.setItem("commandPaletteShortcutEnabled", "true");
    updateSidebarPrefs((prev) => ({
      ...prev,
      display: {
        ...prev.display,
        showTags: true,
        trayTrigger: "hover",
        density: "comfortable",
        statusColorScheme: "accent",
      },
    }));
    setPinAppRail(false);
    localStorage.setItem("pinAppRail", "false");
    window.dispatchEvent(new Event("pinAppRailChanged"));
    setExpandAppRailOnHover(true);
    localStorage.setItem("expandAppRailOnHover", "true");
    window.dispatchEvent(new Event("expandAppRailOnHoverChanged"));
    setFoldersCollapsed(true);
    localStorage.removeItem("defaultSnippetFoldersCollapsed");
    setConfirmSnippetExecution(false);
    localStorage.setItem("confirmSnippetExecution", "false");
    setDisableUpdateCheck(false);
    localStorage.setItem("disableUpdateCheck", "false");
    setConfirmTabClose(false);
    localStorage.setItem("confirmTabClose", "false");
    setHiddenRailTabs(new Set());
    localStorage.removeItem("hiddenRailTabs");
    window.dispatchEvent(new CustomEvent("hiddenRailTabsChanged"));
    // Back to the preset that matches the pre-preset UI, with nothing pinned.
    uiPrefs?.setPreset("balanced");
    uiPrefs?.clearAllOverrides();
    if (storageMode === "cloud") {
      saveToCloud({
        theme: "system",
        fontSize: "md",
        accentColor: DEFAULT_ACCENT,
        language: "en",
        commandAutocomplete: false,
        commandPaletteEnabled: true,
        pinAppRail: false,
        expandAppRailOnHover: true,
        foldersCollapsed: true,
        confirmSnippetExecution: false,
        disableUpdateCheck: false,
        confirmTabClose: false,
        hiddenRailTabs: "[]",
      });
    }
    localSnapshot.current = {};
    localStorage.removeItem("termix-local-snapshot");
    toast.success(t("newUi.sidebar.userProfile.resetToDefaultsSuccess"));
  }

  function restoreLocalSnapshot() {
    // Prefer the persisted snapshot (survives new tabs/reloads), fall back to in-memory ref.
    // If neither exists there were never any browser values to restore, so use current localStorage.
    const persisted = localStorage.getItem("termix-local-snapshot");
    const snap: Record<string, string | null> = persisted
      ? (JSON.parse(persisted) as Record<string, string | null>)
      : localSnapshot.current;
    const hasSnap = Object.keys(snap).length > 0;
    const restore = (key: string, fallback: string | null = null) =>
      hasSnap
        ? snap[key] !== undefined
          ? snap[key]
          : fallback
        : (localStorage.getItem(key) ?? fallback);

    const restoredTheme =
      ((hasSnap ? snap["__theme"] : theme) as ThemeId) ?? "system";
    setTheme(restoredTheme);

    const restoredFontSize =
      (restore("termix-font-size", "md") as FontSizeId) ?? "md";
    setFontSize(restoredFontSize);
    applyFontSize(restoredFontSize);

    const restoredUiFont =
      (restore("termix-ui-font", "jetbrains-mono") as UiFontId) ??
      "jetbrains-mono";
    setUiFont(restoredUiFont);
    applyUiFont(restoredUiFont);

    const restoredAccent = restore("termix-accent", "#f59145") ?? "#f59145";
    setAccentColor(restoredAccent);
    setCustomColorInput(restoredAccent);
    localStorage.setItem("termix-accent", restoredAccent);
    applyAccentColor(restoredAccent);

    const restoredLang = normalizeLanguageCode(restore("i18nextLng", "en"));
    setLanguage(restoredLang);
    void changeAppLanguage(restoredLang);

    const restoredAutocomplete =
      restore("commandAutocomplete", "false") === "true";
    setCommandAutocomplete(restoredAutocomplete);
    localStorage.setItem("commandAutocomplete", String(restoredAutocomplete));

    const restoredPalette =
      restore("commandPaletteShortcutEnabled", "true") !== "false";
    setCommandPaletteEnabled(restoredPalette);
    localStorage.setItem(
      "commandPaletteShortcutEnabled",
      String(restoredPalette),
    );

    // showHostTags/hostTrayOnClick/compactHostView/statusColorScheme are no
    // longer part of this snapshot -- useHostSidebarPreferences keeps its own
    // localStorage cache independent of storageMode switches.

    const restoredPinRail = restore("pinAppRail", "false") === "true";
    setPinAppRail(restoredPinRail);
    localStorage.setItem("pinAppRail", String(restoredPinRail));
    window.dispatchEvent(new Event("pinAppRailChanged"));

    const restoredExpandRailOnHover =
      restore("expandAppRailOnHover", "true") !== "false";
    setExpandAppRailOnHover(restoredExpandRailOnHover);
    localStorage.setItem(
      "expandAppRailOnHover",
      String(restoredExpandRailOnHover),
    );
    window.dispatchEvent(new Event("expandAppRailOnHoverChanged"));

    const restoredFolders =
      restore("defaultSnippetFoldersCollapsed", null) !== "false";
    setFoldersCollapsed(restoredFolders);
    const snapFolders = hasSnap
      ? snap["defaultSnippetFoldersCollapsed"]
      : localStorage.getItem("defaultSnippetFoldersCollapsed");
    if (snapFolders == null) {
      localStorage.removeItem("defaultSnippetFoldersCollapsed");
    } else {
      localStorage.setItem("defaultSnippetFoldersCollapsed", snapFolders);
    }

    const restoredConfirmSnippet =
      restore("confirmSnippetExecution", "false") === "true";
    setConfirmSnippetExecution(restoredConfirmSnippet);
    localStorage.setItem(
      "confirmSnippetExecution",
      String(restoredConfirmSnippet),
    );

    const restoredUpdateCheck =
      restore("disableUpdateCheck", "false") === "true";
    setDisableUpdateCheck(restoredUpdateCheck);
    localStorage.setItem("disableUpdateCheck", String(restoredUpdateCheck));

    const restoredConfirmTab = restore("confirmTabClose", "false") === "true";
    setConfirmTabClose(restoredConfirmTab);
    localStorage.setItem("confirmTabClose", String(restoredConfirmTab));

    const restoredHiddenRaw = hasSnap
      ? snap["hiddenRailTabs"]
      : localStorage.getItem("hiddenRailTabs");
    if (restoredHiddenRaw == null) {
      setHiddenRailTabs(new Set());
      localStorage.removeItem("hiddenRailTabs");
    } else {
      try {
        setHiddenRailTabs(new Set(JSON.parse(restoredHiddenRaw)));
      } catch {
        setHiddenRailTabs(new Set());
      }
      localStorage.setItem("hiddenRailTabs", restoredHiddenRaw);
    }
    window.dispatchEvent(new CustomEvent("hiddenRailTabsChanged"));

    localStorage.removeItem("termix-local-snapshot");
    localSnapshot.current = {};
  }

  function handleThemeChange(id: ThemeId) {
    setTheme(id);
    if (storageMode === "cloud") saveToCloud({ theme: id });
  }

  function handleAccentChange(value: string) {
    setAccentColor(value);
    setCustomColorInput(value);
    localStorage.setItem("termix-accent", value);
    applyAccentColor(value);
    if (storageMode === "cloud") saveToCloud({ accentColor: value });
  }

  function handleFontSizeChange(id: FontSizeId) {
    setFontSize(id);
    applyFontSize(id);
    if (storageMode === "cloud") saveToCloud({ fontSize: id });
  }

  function handleUiFontChange(id: UiFontId) {
    setUiFont(id);
    applyUiFont(id);
  }

  function handleLanguageChange(code: string) {
    void changeAppLanguage(code)
      .then((language) => {
        setLanguage(language);
        if (storageMode === "cloud") saveToCloud({ language });
      })
      .catch(() => {});
  }

  function toggle(id: UserProfileSection) {
    setOpenSections((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }

  async function handleStartTotpSetup() {
    setTotpLoading(true);
    try {
      const result = await setupTOTP();
      setTotpQrCode(result.qr_code);
      setTotpSecret(result.secret);
      setTotpCode("");
      setTotpStep("setup");
    } catch {
      toast.error(t("newUi.sidebar.userProfile.totpSetupFailed"));
    } finally {
      setTotpLoading(false);
    }
  }

  async function handleAddTotpAuthenticator() {
    if (!addTotpInput) {
      toast.error(t("newUi.sidebar.userProfile.totpAddInputRequired"));
      return;
    }
    setTotpLoading(true);
    try {
      const result = await setupTOTP(addTotpInput);
      setTotpQrCode(result.qr_code);
      setTotpSecret(result.secret);
      setAddingTotpAuthenticator(true);
      setShowAddTotp(false);
      setAddTotpInput("");
      setTotpStep("setup");
    } catch (e: unknown) {
      toast.error(
        apiErrorMessage(e, t("newUi.sidebar.userProfile.totpAddFailed")),
      );
    } finally {
      setTotpLoading(false);
    }
  }

  async function handleVerifyTotp() {
    if (!totpCode || totpCode.length !== 6) {
      toast.error(t("newUi.sidebar.userProfile.totpEnter6Digits"));
      return;
    }
    setTotpLoading(true);
    try {
      const result = await enableTOTP(totpCode);
      setTotpBackupCodes(result.backup_codes ?? []);
      setTotpEnabled(true);
      if (!isRemoteSyncConnected) setAccountTotpEnabled(true);
      setTotpStep("backup");
      toast.success(t("newUi.sidebar.userProfile.totpEnabledSuccess"));
    } catch (e: unknown) {
      toast.error(
        apiErrorMessage(e, t("newUi.sidebar.userProfile.totpInvalidCode")),
      );
    } finally {
      setTotpLoading(false);
    }
  }

  async function handleDisableTotp() {
    if (!disableTotpInput) {
      toast.error(t("newUi.sidebar.userProfile.totpDisableInputRequired"));
      return;
    }
    setTotpLoading(true);
    try {
      await disableTOTP(disableTotpInput);
      setTotpEnabled(false);
      if (!isRemoteSyncConnected) setAccountTotpEnabled(false);
      setShowDisableTotp(false);
      setDisableTotpInput("");
      toast.success(t("newUi.sidebar.userProfile.totpDisabledSuccess"));
    } catch (e: unknown) {
      toast.error(
        apiErrorMessage(e, t("newUi.sidebar.userProfile.totpDisableFailed")),
      );
    } finally {
      setTotpLoading(false);
    }
  }

  async function handleRegisterPasskey() {
    setPasskeyLoading(true);
    try {
      await registerWebAuthnCredential(
        passkeyName || "Passkey",
        passkeyUserVerification,
      );
      const { credentials } = await listWebAuthnCredentials();
      setPasskeys(credentials ?? []);
      setPasskeyName("");
      toast.success(t("newUi.sidebar.userProfile.passkeyAdded"));
    } catch (e: unknown) {
      toast.error(
        apiErrorMessage(e, t("newUi.sidebar.userProfile.passkeyAddFailed")),
      );
    } finally {
      setPasskeyLoading(false);
    }
  }

  async function handleDeletePasskey(credentialId: string) {
    setPasskeyLoading(true);
    try {
      await deleteWebAuthnCredential(credentialId);
      setPasskeys((prev) => prev.filter((item) => item.id !== credentialId));
      toast.success(t("newUi.sidebar.userProfile.passkeyDeleted"));
    } catch (e: unknown) {
      toast.error(
        apiErrorMessage(e, t("newUi.sidebar.userProfile.passkeyDeleteFailed")),
      );
    } finally {
      setPasskeyLoading(false);
    }
  }

  function downloadBackupCodes() {
    const blob = new Blob([totpBackupCodes.join("\n")], { type: "text/plain" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "termix-backup-codes.txt";
    a.click();
    URL.revokeObjectURL(url);
  }

  async function handleDeleteAccount() {
    if (!deletePassword.trim()) {
      toast.error(t("newUi.sidebar.userProfile.deletePasswordRequired"));
      return;
    }
    setDeleteLoading(true);
    try {
      await deleteAccount(deletePassword);
      await logoutUser().catch(() => {});
      window.location.reload();
    } catch (e: unknown) {
      toast.error(
        apiErrorMessage(e, t("newUi.sidebar.userProfile.deleteFailed")),
      );
      setDeleteLoading(false);
    }
  }

  async function handleExportData() {
    setExportLoading(true);
    try {
      const apiUrl = getDatabaseTransferUrl("export", {
        electron: isElectron(),
        configuredServerUrl: null,
        location: window.location,
      });

      const response = await fetch(apiUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        credentials: "include",
        body: JSON.stringify({}),
      });

      if (response.ok) {
        const blob = await response.blob();
        const contentDisposition = response.headers.get("content-disposition");
        const filename =
          contentDisposition?.match(/filename="([^"]+)"/)?.[1] ||
          "termix-export.sqlite";
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        window.URL.revokeObjectURL(url);
        document.body.removeChild(a);
        toast.success(t("newUi.sidebar.userProfile.exportSuccess"));
      } else {
        const err = await response.json().catch(() => ({}));
        toast.error(err.error || t("newUi.sidebar.userProfile.exportFailed"));
      }
    } catch {
      toast.error(t("newUi.sidebar.userProfile.exportFailed"));
    } finally {
      setExportLoading(false);
    }
  }

  async function handleImportData() {
    if (!importFile) {
      toast.error(t("newUi.sidebar.userProfile.importSelectFile"));
      return;
    }
    setImportLoading(true);
    try {
      const apiUrl = getDatabaseTransferUrl("import", {
        electron: isElectron(),
        configuredServerUrl: null,
        location: window.location,
      });

      const formData = new FormData();
      formData.append("file", importFile);

      const response = await fetch(apiUrl, {
        method: "POST",
        credentials: "include",
        body: formData,
      });

      if (response.ok) {
        const result = await response.json();
        if (result.success) {
          const s = result.summary;
          const total =
            (s.sshHostsImported || 0) +
            (s.sshCredentialsImported || 0) +
            (s.fileManagerItemsImported || 0) +
            (s.dismissedAlertsImported || 0) +
            (s.settingsImported || 0);
          toast.success(
            t("newUi.sidebar.userProfile.importCompleted", {
              total,
              skipped: s.skippedItems || 0,
            }),
          );
          setImportFile(null);
          setTimeout(() => window.location.reload(), 1500);
        } else {
          toast.error(
            t("newUi.sidebar.userProfile.importFailed", {
              error: result.summary?.errors?.join(", ") || "Unknown error",
            }),
          );
        }
      } else {
        const err = await response.json().catch(() => ({}));
        toast.error(
          t("newUi.sidebar.userProfile.importFailed", {
            error: err.error || "Unknown error",
          }),
        );
      }
    } catch {
      toast.error(
        t("newUi.sidebar.userProfile.importFailed", {
          error: "Unknown error",
        }),
      );
    } finally {
      setImportLoading(false);
    }
  }

  const canChangePasword = !isOidc || isDualAuth;

  return (
    <div className="mx-auto flex w-full max-w-5xl flex-col gap-2 p-3">
      <NewApiKeyDialog
        open={newKeyOpen}
        onOpenChange={setNewKeyOpen}
        onAdd={(key) => setApiKeys((prev) => [key as ApiKey, ...prev])}
        userId={userId}
      />

      {/* Donate banner */}
      <div className="border border-accent-brand/40 bg-accent-brand/10 px-3 py-2.5 flex flex-col gap-1.5">
        <div className="flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-widest text-accent-brand">
          {t("newUi.sidebar.userProfile.donateTitle")}
        </div>
        <p className="text-[10px] text-muted-foreground leading-relaxed">
          {t("newUi.sidebar.userProfile.donateDescription")}
        </p>
        <p className="text-[10px] text-muted-foreground leading-relaxed">
          {t("newUi.sidebar.userProfile.donateMilestones")}
        </p>
        <a
          href="https://donate.termix.site/"
          target="_blank"
          rel="noopener noreferrer"
          className="self-start flex items-center gap-1 text-[10px] font-bold uppercase tracking-widest bg-accent-brand text-white px-2 py-1 hover:opacity-90 transition-opacity"
        >
          {t("newUi.sidebar.userProfile.donateButton")}
        </a>
      </div>

      {/* Storage mode toggle — only meaningful once a remote server is
          connected; with no sync there's nowhere for "cloud" to sync to,
          so this stays forced to local storage and hidden. */}
      {(!isElectron() || isRemoteSyncConnected === true) && (
        <div className="border border-border bg-card px-3 py-2.5 flex flex-col gap-2">
          <span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
            {t("newUi.sidebar.userProfile.storageModeSwitch")}
          </span>
          <div className="flex border border-border overflow-hidden w-full">
            <button
              onClick={() => handleStorageModeChange("local")}
              className={`flex-1 py-1 text-[10px] font-bold uppercase tracking-widest transition-colors ${
                storageMode === "local"
                  ? "bg-accent-brand text-white"
                  : "text-muted-foreground hover:text-foreground hover:bg-muted/40"
              }`}
            >
              {t("newUi.sidebar.userProfile.storageModeLocal")}
            </button>
            <button
              onClick={() => handleStorageModeChange("cloud")}
              className={`flex-1 py-1 text-[10px] font-bold uppercase tracking-widest transition-colors ${
                storageMode === "cloud"
                  ? "bg-accent-brand text-white"
                  : "text-muted-foreground hover:text-foreground hover:bg-muted/40"
              }`}
            >
              {t("newUi.sidebar.userProfile.storageModeCloud")}
            </button>
          </div>
          <p className="text-[10px] text-muted-foreground leading-relaxed">
            {t("newUi.sidebar.userProfile.storageModeDescription")}
          </p>
          <button
            onClick={resetToDefaults}
            className="self-start flex items-center gap-1 text-[10px] font-bold uppercase tracking-widest text-muted-foreground hover:text-foreground transition-colors"
          >
            <RotateCcw className="size-3" />
            {t("newUi.sidebar.userProfile.resetToDefaults")}
          </button>
        </div>
      )}

      {/* Account */}
      <AccordionSection
        id="account"
        label={t("newUi.sidebar.userProfile.sectionAccount")}
        icon={<User className="size-3.5" />}
        open={openSections.has("account")}
        onToggle={() => toggle("account")}
      >
        <div className="flex flex-col gap-0 pt-2">
          {isElectron() ? (
            <div className="border border-accent-brand/40 bg-accent-brand/10 px-3 py-2.5 mb-2">
              <div className="flex items-center gap-1.5 text-xs font-semibold text-accent-brand">
                <ShieldCheck className="size-3.5" />
                {t("newUi.sidebar.userProfile.desktopProfileTitle")}
              </div>
              <p className="text-[10px] text-muted-foreground leading-relaxed mt-1">
                {t("newUi.sidebar.userProfile.desktopProfileDescription")}
              </p>
            </div>
          ) : (
            <div className="grid grid-cols-2 gap-x-4 gap-y-0">
              <div className="flex flex-col py-2">
                <span className="text-[10px] text-muted-foreground uppercase tracking-widest font-semibold">
                  {t("newUi.sidebar.userProfile.usernameLabel")}
                </span>
                <span className="text-sm font-semibold mt-0.5">
                  {accountUsername || "—"}
                </span>
              </div>
              <div className="flex flex-col py-2">
                <span className="text-[10px] text-muted-foreground uppercase tracking-widest font-semibold">
                  {t("newUi.sidebar.userProfile.roleLabel")}
                </span>
                <div className="flex flex-wrap gap-1 mt-0.5">
                  <span className="inline-flex items-center px-1.5 py-0.5 text-[10px] font-semibold border border-accent-brand/40 bg-accent-brand/10 text-accent-brand w-fit">
                    {userRole || "—"}
                  </span>
                  {userRoles.map((r) => (
                    <span
                      key={r.roleId}
                      className="inline-flex items-center px-1.5 py-0.5 text-[10px] font-semibold border border-border bg-muted text-muted-foreground w-fit"
                    >
                      {r.roleDisplayName}
                    </span>
                  ))}
                </div>
              </div>
              <div className="flex flex-col py-2">
                <span className="text-[10px] text-muted-foreground uppercase tracking-widest font-semibold">
                  {t("newUi.sidebar.userProfile.authMethodLabel")}
                </span>
                <span className="text-sm font-semibold mt-0.5">
                  {authMethod || "—"}
                </span>
              </div>
              <div className="flex flex-col py-2">
                <span className="text-[10px] text-muted-foreground uppercase tracking-widest font-semibold">
                  {t("newUi.sidebar.userProfile.twoFaLabel")}
                </span>
                <span className="flex items-center gap-1 mt-0.5">
                  {accountTotpEnabled ? (
                    <>
                      <ShieldCheck className="size-3.5 text-accent-brand" />
                      <span className="text-sm font-semibold text-accent-brand">
                        {t("newUi.sidebar.userProfile.twoFaOn")}
                      </span>
                    </>
                  ) : (
                    <span className="text-sm font-semibold text-muted-foreground">
                      {t("newUi.sidebar.userProfile.twoFaOff")}
                    </span>
                  )}
                </span>
              </div>
            </div>
          )}

          <div className="border-t border-border pt-3 mt-1">
            <span className="text-[10px] text-muted-foreground uppercase tracking-widest font-semibold">
              {t("newUi.sidebar.userProfile.versionLabel")}
            </span>
            <div className="flex items-center justify-between mt-1.5">
              <span className="text-sm font-bold text-accent-brand">
                {version ? `v${version}` : "—"}
              </span>
              <VersionBadge status={versionStatus} releaseUrl={releaseUrl} />
            </div>
          </div>

          <div className="border-t border-border pt-3 mt-3">
            <div className="flex items-center justify-between">
              <div className="flex flex-col gap-0.5">
                <span className="text-xs font-medium">
                  {t("newUi.sidebar.userProfile.betaProgramTitle")}
                </span>
                <span className="text-[10px] text-muted-foreground">
                  {t("newUi.sidebar.userProfile.betaProgramDescription")}{" "}
                  <a
                    href="https://github.com/Termix-SSH/Support/issues/new?template=beta_feedback.yml"
                    target="_blank"
                    rel="noopener noreferrer"
                    className="text-accent-brand hover:underline"
                  >
                    {t("newUi.sidebar.userProfile.betaProgramFeedback")}
                  </a>
                </span>
              </div>
              <a
                href="https://docs.termix.site/install/server/docker#beta-builds"
                target="_blank"
                rel="noopener noreferrer"
                className="shrink-0 ml-3 flex items-center gap-1 text-[10px] font-bold uppercase tracking-widest border border-border px-2 py-1.5 hover:bg-muted/40 transition-colors"
              >
                {t("newUi.sidebar.userProfile.betaProgramLearnMore")}
              </a>
            </div>
          </div>

          <div className="border-t border-border pt-3 mt-3">
            <div className="flex items-center justify-between">
              <div className="flex flex-col gap-0.5">
                <span className="text-xs font-medium">
                  {t("newUi.sidebar.userProfile.cliTitle")}
                </span>
                <span className="text-[10px] text-muted-foreground">
                  {t("newUi.sidebar.userProfile.cliDescription")}
                </span>
              </div>
              <a
                href="https://docs.termix.site/cli"
                target="_blank"
                rel="noopener noreferrer"
                className="shrink-0 ml-3 flex items-center gap-1 text-[10px] font-bold uppercase tracking-widest border border-border px-2 py-1.5 hover:bg-muted/40 transition-colors"
              >
                {t("newUi.sidebar.userProfile.cliLearnMore")}
              </a>
            </div>
          </div>

          {isElectron() && (
            <div className="border-t border-border pt-3 mt-3">
              <RemoteSyncPanel initialServerUrl={remoteSyncInitialServerUrl} />
            </div>
          )}

          {!isElectron() && (
            <div className="border-t border-border pt-3 mt-3">
              <div className="flex items-center justify-between">
                <div className="flex flex-col gap-0.5">
                  <span className="text-xs font-medium text-destructive">
                    {t("newUi.sidebar.userProfile.deleteAccount")}
                  </span>
                  <span className="text-[10px] text-muted-foreground">
                    {t("newUi.sidebar.userProfile.deleteAccountDescription")}
                  </span>
                </div>
                <Button
                  variant="outline"
                  size="sm"
                  className="border-destructive/40 text-destructive hover:bg-destructive/10 hover:text-destructive shrink-0 ml-3 text-[10px] h-7"
                  onClick={() => setShowDeleteConfirm(true)}
                >
                  {t("newUi.sidebar.userProfile.deleteButton")}
                </Button>
              </div>
            </div>
          )}
        </div>
      </AccordionSection>

      {/* Interface */}
      <AccordionSection
        id="interface"
        label={t("newUi.sidebar.userProfile.sectionInterface")}
        icon={<LayoutTemplate size={13} />}
        open={openSections.has("interface")}
        onToggle={() => toggle("interface")}
      >
        <div className="flex flex-col gap-4 pt-3">
          <InterfacePresetSettings
            onRunSetupAgain={() =>
              window.dispatchEvent(new Event("termix:open-onboarding"))
            }
          />
        </div>
      </AccordionSection>

      {/* Appearance */}
      <AccordionSection
        id="appearance"
        label={t("newUi.sidebar.userProfile.sectionAppearance")}
        icon={<Palette className="size-3.5" />}
        open={openSections.has("appearance")}
        onToggle={() => toggle("appearance")}
      >
        <div className="flex flex-col gap-4 pt-3">
          <div className="flex flex-col gap-1.5">
            <span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
              {t("newUi.sidebar.userProfile.languageLabel")}
            </span>
            <select
              value={language}
              onChange={(e) => handleLanguageChange(e.target.value)}
              className="px-2.5 py-1.5 text-xs bg-background border border-border text-foreground outline-none focus:ring-1 focus:ring-ring w-full"
            >
              {LANGUAGES.map((lang) => (
                <option key={lang.code} value={lang.code}>
                  {lang.label}
                </option>
              ))}
            </select>
          </div>

          <div className="flex flex-col gap-1.5">
            <span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
              {t("newUi.sidebar.userProfile.themeLabel")}
            </span>
            <div className="relative">
              <select
                value={theme}
                onChange={(e) => handleThemeChange(e.target.value as ThemeId)}
                className="w-full px-2.5 py-1.5 text-xs bg-background border border-border text-foreground outline-none focus:ring-1 focus:ring-ring appearance-none pr-7"
              >
                {THEMES.map((th) => (
                  <option key={th.id} value={th.id}>
                    {themeLabel[th.id]}
                  </option>
                ))}
              </select>
              <ChevronDown className="absolute right-2 top-1/2 -translate-y-1/2 size-3 text-muted-foreground pointer-events-none" />
            </div>
            <div className="flex gap-1 mt-0.5">
              {THEMES.filter((th) => th.id !== "system").map((th) => (
                <button
                  key={th.id}
                  title={themeLabel[th.id]}
                  onClick={() => handleThemeChange(th.id)}
                  className={`h-4 flex-1 border transition-all ${theme === th.id ? "border-accent-brand ring-1 ring-accent-brand" : "border-border/50"}`}
                  style={{ background: th.preview }}
                />
              ))}
            </div>
          </div>

          <div className="flex flex-col gap-1.5">
            <span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-1.5">
              <Type className="size-3" />
              {t("newUi.sidebar.userProfile.interfaceFontLabel")}
            </span>
            <select
              value={uiFont}
              onChange={(event) =>
                handleUiFontChange(event.target.value as UiFontId)
              }
              className="px-2.5 py-1.5 text-xs bg-background border border-border text-foreground outline-none focus:ring-1 focus:ring-ring w-full"
            >
              {UI_FONTS.map((font) => (
                <option key={font.id} value={font.id}>
                  {font.label}
                </option>
              ))}
            </select>
            <span className="text-[10px] text-muted-foreground">
              {t("newUi.sidebar.userProfile.interfaceFontDescription")}
            </span>
          </div>

          <div className="flex flex-col gap-1.5">
            <span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground flex items-center gap-1.5">
              <Type className="size-3" />
              {t("newUi.sidebar.userProfile.fontSizeLabel")}
            </span>
            <div className="flex gap-1">
              {FONT_SIZES.map((fs) => (
                <button
                  key={fs.id}
                  onClick={() => handleFontSizeChange(fs.id)}
                  className={`flex-1 py-1.5 border text-[10px] font-bold transition-colors ${
                    fontSize === fs.id
                      ? "border-accent-brand/40 bg-accent-brand/10 text-accent-brand"
                      : "border-border text-muted-foreground hover:text-foreground"
                  }`}
                >
                  {fs.label}
                </button>
              ))}
            </div>
          </div>

          <div className="flex flex-col gap-2">
            <span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
              {t("newUi.sidebar.userProfile.accentColorLabel")}
            </span>
            <div className="grid grid-cols-6 gap-1">
              {ACCENT_PRESET_COLORS.map((ac) => (
                <button
                  key={ac.value}
                  title={ac.label}
                  onClick={() => handleAccentChange(ac.value)}
                  className={`h-6 border-2 transition-all ${
                    accentColor === ac.value
                      ? "border-foreground scale-110"
                      : "border-transparent hover:border-foreground/40"
                  }`}
                  style={{ background: ac.value }}
                />
              ))}
            </div>
            <div className="flex items-center gap-2 border border-border bg-muted/30 px-2 py-1.5">
              <button
                onClick={() => colorInputRef.current?.click()}
                className="size-5 shrink-0 border border-border/60 cursor-pointer"
                style={{ background: accentColor }}
                title={t("newUi.sidebar.userProfile.colorPickerTooltip")}
              />
              <input
                ref={colorInputRef}
                type="color"
                value={accentColor.startsWith("#") ? accentColor : "#f97316"}
                onChange={(e) => handleAccentChange(e.target.value)}
                className="sr-only"
              />
              <Input
                value={customColorInput}
                onChange={(e) => setCustomColorInput(e.target.value)}
                onBlur={() => {
                  const v = customColorInput.trim();
                  if (
                    /^#[0-9a-fA-F]{6}$/.test(v) ||
                    /^#[0-9a-fA-F]{3}$/.test(v)
                  ) {
                    handleAccentChange(v);
                  } else {
                    setCustomColorInput(accentColor);
                  }
                }}
                onKeyDown={(e) => {
                  if (e.key === "Enter") {
                    const v = customColorInput.trim();
                    if (
                      /^#[0-9a-fA-F]{6}$/.test(v) ||
                      /^#[0-9a-fA-F]{3}$/.test(v)
                    ) {
                      handleAccentChange(v);
                    }
                  }
                }}
                placeholder="#f97316"
                className="h-6 text-[11px] font-mono border-0 bg-transparent p-0 focus-visible:ring-0 flex-1 min-w-0"
              />
              <span className="text-[10px] text-muted-foreground shrink-0">
                hex
              </span>
            </div>
          </div>

          <div className="flex flex-col gap-1 border-t border-border pt-3">
            <span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1">
              {t("newUi.sidebar.userProfile.settingsTerminal")}
            </span>
            <ConnectionDefaultsSettings />
            <SettingRow
              label={t("newUi.sidebar.userProfile.commandAutocomplete")}
              description={t(
                "newUi.sidebar.userProfile.commandAutocompleteDesc",
              )}
            >
              <FakeSwitch
                checked={commandAutocomplete}
                onChange={(v) => {
                  setCommandAutocomplete(v);
                  localStorage.setItem("commandAutocomplete", v.toString());
                  if (storageMode === "cloud")
                    saveToCloud({ commandAutocomplete: v });
                }}
              />
            </SettingRow>
            <div className="flex flex-col gap-1.5 py-3 border-b border-border">
              <div className="flex flex-col gap-0.5">
                <span className="text-sm font-medium leading-snug">
                  {t("newUi.sidebar.userProfile.localEcho")}
                </span>
                <span className="text-xs text-muted-foreground leading-snug">
                  {t("newUi.sidebar.userProfile.localEchoDesc")}
                </span>
              </div>
              <select
                defaultValue={
                  localStorage.getItem("terminalLocalEchoMode") ?? "auto"
                }
                onChange={(e) =>
                  localStorage.setItem("terminalLocalEchoMode", e.target.value)
                }
                className="h-7 border border-border bg-background px-2 text-xs outline-none focus:ring-1 focus:ring-ring"
              >
                <option value="off">{t("hosts.localEchoOff")}</option>
                <option value="auto">{t("hosts.localEchoAuto")}</option>
                <option value="on">{t("hosts.localEchoOn")}</option>
              </select>
            </div>
            <SettingRow
              label={t("newUi.sidebar.userProfile.keyboardShortcuts")}
              description={t(
                "newUi.sidebar.userProfile.keyboardShortcutsDescription",
              )}
            >
              <Button
                variant="outline"
                size="sm"
                onClick={() => setKeybindingsDialogOpen(true)}
              >
                {t("newUi.sidebar.userProfile.manageShortcuts")}
              </Button>
            </SettingRow>
            <div className="flex flex-col gap-1.5 py-3 border-b border-border">
              <div className="flex flex-col gap-0.5">
                <span className="text-sm font-medium leading-snug">
                  {t("newUi.sidebar.userProfile.terminalLinkBehavior")}
                </span>
                <span className="text-xs text-muted-foreground leading-snug">
                  {t("newUi.sidebar.userProfile.terminalLinkBehaviorDesc")}
                </span>
              </div>
              <select
                value={terminalLinkClickBehavior}
                onChange={(e) => {
                  setTerminalLinkClickBehavior(e.target.value);
                  localStorage.setItem(
                    "terminalLinkClickBehavior",
                    e.target.value,
                  );
                }}
                className="h-7 border border-border bg-background px-2 text-xs outline-none focus:ring-1 focus:ring-ring"
              >
                <option value="confirm">
                  {t("hosts.linkClickBehaviorConfirm")}
                </option>
                <option value="direct">
                  {t("hosts.linkClickBehaviorDirect")}
                </option>
              </select>
            </div>
            <SettingRow
              label={t("newUi.sidebar.userProfile.commandPalette")}
              description={t("newUi.sidebar.userProfile.commandPaletteDesc")}
            >
              <FakeSwitch
                checked={commandPaletteEnabled}
                onChange={(v) => {
                  setCommandPaletteEnabled(v);
                  localStorage.setItem(
                    "commandPaletteShortcutEnabled",
                    v.toString(),
                  );
                  window.dispatchEvent(
                    new Event("commandPaletteShortcutEnabledChanged"),
                  );
                  if (storageMode === "cloud")
                    saveToCloud({ commandPaletteEnabled: v });
                }}
              />
            </SettingRow>
            {aiGloballyEnabled && (
              <>
                <SettingRow
                  label={t("ai.enableTitle")}
                  description={t("ai.enableDescription")}
                >
                  <FakeSwitch
                    checked={aiAssistantEnabled}
                    onChange={applyAiEnabled}
                  />
                </SettingRow>
                {aiAssistantEnabled && (
                  <SettingRow
                    label={t("ai.readOnlyCommands")}
                    description={t("ai.readOnlyCommandsDescription")}
                  >
                    <FakeSwitch
                      checked={aiReadOnlyCommands}
                      onChange={(v) => {
                        setAiReadOnlyCommands(v);
                        if (storageMode === "cloud")
                          saveToCloud({ aiReadOnlyCommands: v });
                      }}
                    />
                  </SettingRow>
                )}
              </>
            )}
            <SettingRow
              label={t("newUi.sidebar.userProfile.reopenTabsOnLogin")}
              description={t("newUi.sidebar.userProfile.reopenTabsOnLoginDesc")}
            >
              <FakeSwitch
                checked={userPrefs?.reopenTabsOnLogin ?? false}
                onChange={(v) => {
                  onPrefsChange?.({ reopenTabsOnLogin: v });
                  import("@/main-axios").then(({ saveUserPreferences }) => {
                    saveUserPreferences({ reopenTabsOnLogin: v }).catch(
                      () => {},
                    );
                  });
                }}
              />
            </SettingRow>
            <SettingRow
              label={t("newUi.sidebar.userProfile.confirmTabClose")}
              description={t("newUi.sidebar.userProfile.confirmTabCloseDesc")}
            >
              <FakeSwitch
                checked={confirmTabClose}
                onChange={(v) => {
                  setConfirmTabClose(v);
                  localStorage.setItem("confirmTabClose", v.toString());
                  if (storageMode === "cloud")
                    saveToCloud({ confirmTabClose: v });
                }}
              />
            </SettingRow>
          </div>

          <div className="flex flex-col gap-1 border-t border-border pt-3">
            <span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1">
              {t("newUi.sidebar.userProfile.settingsAppRail")}
            </span>
            <p className="text-[10px] text-muted-foreground mb-2">
              {t("newUi.sidebar.userProfile.sidebarSettingsMoved")}
            </p>
            <SettingRow
              label={t("newUi.sidebar.userProfile.pinAppRail")}
              description={t("newUi.sidebar.userProfile.pinAppRailDesc")}
            >
              <FakeSwitch
                checked={pinAppRail}
                onChange={(v) => {
                  setPinAppRail(v);
                  setRailPreference("pinAppRail", v);
                }}
              />
            </SettingRow>
            <SettingRow
              label={t("newUi.sidebar.userProfile.expandAppRailOnHover")}
              description={t(
                "newUi.sidebar.userProfile.expandAppRailOnHoverDesc",
              )}
            >
              <FakeSwitch
                checked={expandAppRailOnHover}
                onChange={(v) => {
                  setExpandAppRailOnHover(v);
                  setRailPreference("expandAppRailOnHover", v);
                }}
              />
            </SettingRow>
          </div>

          <div className="flex flex-col gap-1 border-t border-border pt-3">
            <span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1">
              {t("newUi.sidebar.userProfile.settingsNavigation")}
            </span>
            <p className="text-[10px] text-muted-foreground mb-2">
              {t("newUi.sidebar.userProfile.navigationTabsDesc")}
            </p>
            {visibleRailItems()
              .filter((tab) => tab.id !== "ai" || aiGloballyEnabled)
              .map((tab) => (
                <div
                  key={tab.id}
                  className="flex items-center justify-between py-1.5"
                >
                  <span className="flex items-center gap-1.5 text-xs font-medium text-foreground">
                    <span className="text-muted-foreground">
                      <tab.icon size={12} />
                    </span>
                    {t(tab.labelKey)}
                  </span>
                  <FakeSwitch
                    checked={!hiddenRailTabs.has(tab.id)}
                    onChange={(visible) => {
                      const next = new Set(hiddenRailTabs);
                      if (visible) next.delete(tab.id);
                      else next.add(tab.id);
                      setHiddenRailTabs(next);
                      const serialized = JSON.stringify([...next]);
                      localStorage.setItem("hiddenRailTabs", serialized);
                      window.dispatchEvent(new Event("hiddenRailTabsChanged"));
                      if (storageMode === "cloud")
                        saveToCloud({ hiddenRailTabs: serialized });
                    }}
                  />
                </div>
              ))}
          </div>

          <div className="flex flex-col gap-1 border-t border-border pt-3">
            <span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-1">
              {t("newUi.sidebar.userProfile.settingsUpdates")}
            </span>
            <SettingRow
              label={t("newUi.sidebar.userProfile.disableUpdateChecks")}
              description={t(
                "newUi.sidebar.userProfile.disableUpdateChecksDesc",
              )}
            >
              <FakeSwitch
                checked={disableUpdateCheck}
                onChange={(v) => {
                  setDisableUpdateCheck(v);
                  localStorage.setItem("disableUpdateCheck", v.toString());
                  if (storageMode === "cloud")
                    saveToCloud({ disableUpdateCheck: v });
                }}
              />
            </SettingRow>
          </div>
        </div>
      </AccordionSection>

      {/* The embedded desktop backend auto-authenticates its machine-local
          profile, so server login controls would imply protection they do
          not provide. Remote Sync owns its separate account UI above. */}
      <AccordionSection
        hidden={isElectron()}
        id="security"
        label={t("newUi.sidebar.userProfile.sectionSecurity")}
        icon={<Shield className="size-3.5" />}
        open={openSections.has("security")}
        onToggle={() => toggle("security")}
      >
        <div className="flex flex-col gap-4 pt-3">
          <div className="flex flex-col gap-2">
            <div className="flex items-center justify-between">
              <div className="flex flex-col gap-0.5">
                <div className="flex items-center gap-2">
                  <span className="text-xs font-medium">
                    {t("newUi.sidebar.userProfile.totpAuthenticator")}
                  </span>
                  <a
                    href="https://docs.termix.site/features/authentication/totp"
                    target="_blank"
                    rel="noreferrer"
                    className="text-[10px] text-accent-brand hover:underline"
                  >
                    {t("hosts.docsLink")}
                  </a>
                </div>
                <span className="text-[10px] text-muted-foreground">
                  {totpEnabled
                    ? t("newUi.sidebar.userProfile.totpEnabled")
                    : t("newUi.sidebar.userProfile.totpDisabled")}
                </span>
              </div>
              {totpEnabled ? (
                <div className="ml-3 flex shrink-0 gap-2">
                  <Button
                    variant="outline"
                    size="sm"
                    className="h-7 text-[10px] border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand"
                    onClick={() => setShowAddTotp((open) => !open)}
                    disabled={totpLoading || totpStep !== "idle"}
                  >
                    {t("newUi.sidebar.userProfile.totpAddAuthenticator")}
                  </Button>
                  <Button
                    variant="outline"
                    size="sm"
                    className="h-7 text-[10px] border-destructive/40 text-destructive hover:bg-destructive/10 hover:text-destructive"
                    onClick={() => setShowDisableTotp((o) => !o)}
                  >
                    {t("newUi.sidebar.userProfile.disable")}
                  </Button>
                </div>
              ) : (
                <Button
                  variant="outline"
                  size="sm"
                  className="shrink-0 ml-3 text-[10px] h-7 border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand"
                  onClick={handleStartTotpSetup}
                  disabled={totpLoading || totpStep !== "idle"}
                >
                  {t("newUi.sidebar.userProfile.enable")}
                </Button>
              )}
            </div>

            {totpEnabled && showAddTotp && (
              <div className="border border-border bg-muted/20 p-3 flex flex-col gap-3">
                <span className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
                  {t("newUi.sidebar.userProfile.totpAddTitle")}
                </span>
                <span className="text-[10px] text-muted-foreground">
                  {t("newUi.sidebar.userProfile.totpAddDescription")}
                </span>
                <Input
                  placeholder={t(
                    "newUi.sidebar.userProfile.totpDisablePlaceholder",
                  )}
                  value={addTotpInput}
                  onChange={(e) => setAddTotpInput(e.target.value)}
                  onKeyDown={(e) =>
                    e.key === "Enter" && handleAddTotpAuthenticator()
                  }
                  className="text-sm"
                />
                <div className="flex gap-2">
                  <Button
                    variant="ghost"
                    size="sm"
                    className="flex-1 text-xs"
                    onClick={() => {
                      setShowAddTotp(false);
                      setAddTotpInput("");
                    }}
                  >
                    {t("newUi.sidebar.userProfile.cancel")}
                  </Button>
                  <Button
                    variant="outline"
                    size="sm"
                    className="flex-1 text-xs border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand"
                    onClick={handleAddTotpAuthenticator}
                    disabled={totpLoading}
                  >
                    {t("common.continue")}
                  </Button>
                </div>
              </div>
            )}

            {/* Disable TOTP form */}
            {totpEnabled && showDisableTotp && (
              <div className="border border-border bg-muted/20 p-3 flex flex-col gap-3">
                <span className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
                  {t("newUi.sidebar.userProfile.totpDisableTitle")}
                </span>
                <Input
                  placeholder={t(
                    "newUi.sidebar.userProfile.totpDisablePlaceholder",
                  )}
                  value={disableTotpInput}
                  onChange={(e) => setDisableTotpInput(e.target.value)}
                  className="text-sm"
                />
                <div className="flex gap-2">
                  <Button
                    variant="ghost"
                    size="sm"
                    className="flex-1 text-xs"
                    onClick={() => {
                      setShowDisableTotp(false);
                      setDisableTotpInput("");
                    }}
                  >
                    {t("newUi.sidebar.userProfile.cancel")}
                  </Button>
                  <Button
                    variant="outline"
                    size="sm"
                    className="flex-1 text-xs border-destructive/40 text-destructive hover:bg-destructive/10 hover:text-destructive"
                    onClick={handleDisableTotp}
                    disabled={totpLoading}
                  >
                    {t("newUi.sidebar.userProfile.totpDisableConfirm")}
                  </Button>
                </div>
              </div>
            )}

            {/* TOTP setup: scan QR */}
            {totpStep === "setup" && (
              <div className="border border-border bg-muted/20 p-3 flex flex-col gap-3">
                <div className="flex items-center justify-between">
                  <span className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
                    {t("newUi.sidebar.userProfile.setupTotp")}
                  </span>
                  <button
                    onClick={() => {
                      setTotpStep("idle");
                      setAddingTotpAuthenticator(false);
                    }}
                    className="text-muted-foreground hover:text-foreground"
                  >
                    <X className="size-3.5" />
                  </button>
                </div>
                {totpQrCode ? (
                  <div className="flex items-center justify-center p-3 bg-background border border-border">
                    <img
                      src={totpQrCode}
                      alt={t("newUi.sidebar.userProfile.qrCode")}
                      className="size-32"
                    />
                  </div>
                ) : (
                  <div className="flex items-center justify-center p-3 bg-background border border-border">
                    <div className="size-24 bg-muted flex items-center justify-center text-[10px] text-muted-foreground">
                      {t("newUi.sidebar.userProfile.qrCode")}
                    </div>
                  </div>
                )}
                <div className="flex items-center gap-2 bg-muted/30 border border-border px-2 py-1.5">
                  <span className="text-[10px] font-mono flex-1 tracking-widest select-all truncate">
                    {totpSecret}
                  </span>
                  <button
                    onClick={() => {
                      copyToClipboard(totpSecret);
                      toast.info(t("newUi.sidebar.userProfile.secretCopied"));
                    }}
                    className="text-muted-foreground hover:text-accent-brand shrink-0"
                  >
                    <Copy className="size-3.5" />
                  </button>
                </div>
                <span className="text-[10px] text-muted-foreground text-center">
                  {t(
                    addingTotpAuthenticator
                      ? "newUi.sidebar.userProfile.totpAddScanInstructions"
                      : "newUi.sidebar.userProfile.totpInstructions",
                  )}
                </span>
                <Button
                  variant="outline"
                  size="sm"
                  className="text-xs border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand"
                  onClick={() => {
                    if (addingTotpAuthenticator) {
                      setAddingTotpAuthenticator(false);
                      setTotpStep("idle");
                      toast.success(
                        t("newUi.sidebar.userProfile.totpAddSuccess"),
                      );
                    } else {
                      setTotpStep("verify");
                    }
                  }}
                >
                  {t(
                    addingTotpAuthenticator
                      ? "newUi.sidebar.userProfile.done"
                      : "newUi.sidebar.userProfile.totpContinueVerify",
                  )}
                </Button>
              </div>
            )}

            {/* TOTP setup: verify code */}
            {!totpEnabled && totpStep === "verify" && (
              <div className="border border-border bg-muted/20 p-3 flex flex-col gap-3">
                <div className="flex items-center justify-between">
                  <span className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
                    {t("newUi.sidebar.userProfile.totpVerifyTitle")}
                  </span>
                  <button
                    onClick={() => setTotpStep("setup")}
                    className="text-muted-foreground hover:text-foreground"
                  >
                    <X className="size-3.5" />
                  </button>
                </div>
                <Input
                  placeholder={t(
                    "newUi.sidebar.userProfile.totpCodePlaceholder",
                  )}
                  value={totpCode}
                  onChange={(e) =>
                    setTotpCode(e.target.value.replace(/\D/g, "").slice(0, 6))
                  }
                  onKeyDown={(e) => e.key === "Enter" && handleVerifyTotp()}
                  className="text-center font-mono tracking-widest text-lg h-10"
                  maxLength={6}
                />
                <div className="flex gap-2">
                  <Button
                    variant="ghost"
                    size="sm"
                    className="flex-1 text-xs"
                    onClick={() => setTotpStep("setup")}
                  >
                    {t("newUi.sidebar.userProfile.cancel")}
                  </Button>
                  <Button
                    variant="outline"
                    size="sm"
                    className="flex-1 text-xs border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand"
                    onClick={handleVerifyTotp}
                    disabled={totpLoading || totpCode.length !== 6}
                  >
                    <CheckCircle2 className="size-3.5" />
                    {t("newUi.sidebar.userProfile.verify")}
                  </Button>
                </div>
              </div>
            )}

            {/* TOTP setup: backup codes */}
            {totpStep === "backup" && totpBackupCodes.length > 0 && (
              <div className="border border-border bg-muted/20 p-3 flex flex-col gap-3">
                <span className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
                  {t("newUi.sidebar.userProfile.totpBackupTitle")}
                </span>
                <div className="grid grid-cols-2 gap-1">
                  {totpBackupCodes.map((code) => (
                    <span
                      key={code}
                      className="text-[10px] font-mono bg-muted border border-border px-2 py-1 text-center"
                    >
                      {code}
                    </span>
                  ))}
                </div>
                <Button
                  variant="outline"
                  size="sm"
                  className="text-xs border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand"
                  onClick={downloadBackupCodes}
                >
                  {t("newUi.sidebar.userProfile.totpDownloadBackup")}
                </Button>
                <Button
                  variant="ghost"
                  size="sm"
                  className="text-xs"
                  onClick={() => setTotpStep("idle")}
                >
                  {t("newUi.sidebar.userProfile.done")}
                </Button>
              </div>
            )}
          </div>

          <div className="flex flex-col gap-3 border-t border-border pt-3">
            <div className="flex items-center justify-between gap-3">
              <div className="flex flex-col gap-0.5">
                <span className="text-xs font-medium">
                  {t("newUi.sidebar.userProfile.passkeys")}
                </span>
                <span className="text-[10px] text-muted-foreground">
                  {t("newUi.sidebar.userProfile.passkeysDesc")}
                </span>
              </div>
            </div>

            <div className="grid grid-cols-[1fr_auto] gap-2">
              <Input
                placeholder={t("newUi.sidebar.userProfile.passkeyName")}
                value={passkeyName}
                onChange={(e) => setPasskeyName(e.target.value)}
                className="h-8 text-xs"
                disabled={passkeyLoading}
              />
              <select
                value={passkeyUserVerification}
                onChange={(e) =>
                  setPasskeyUserVerification(
                    e.target.value as WebAuthnUserVerification,
                  )
                }
                className="h-8 w-28 text-xs border border-border bg-background px-2 outline-none focus:ring-1 focus:ring-ring"
                disabled={passkeyLoading}
              >
                <option value="preferred">
                  {t("newUi.sidebar.userProfile.passkeyUvPreferred")}
                </option>
                <option value="required">
                  {t("newUi.sidebar.userProfile.passkeyUvRequired")}
                </option>
                <option value="discouraged">
                  {t("newUi.sidebar.userProfile.passkeyUvDiscouraged")}
                </option>
              </select>
            </div>
            <Button
              variant="outline"
              size="sm"
              className="h-7 text-[10px] border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand"
              onClick={handleRegisterPasskey}
              disabled={passkeyLoading}
            >
              <KeyRound className="size-3" />
              {t("newUi.sidebar.userProfile.addPasskey")}
            </Button>

            <div className="flex flex-col divide-y divide-border border border-border">
              {passkeys.length === 0 ? (
                <div className="py-4 text-center text-[10px] text-muted-foreground">
                  {t("newUi.sidebar.userProfile.noPasskeys")}
                </div>
              ) : (
                passkeys.map((passkey) => (
                  <div
                    key={passkey.id}
                    className="flex items-center justify-between gap-2 px-2 py-2"
                  >
                    <div className="min-w-0 flex flex-col">
                      <span className="text-xs font-medium truncate">
                        {passkey.name}
                      </span>
                      <span className="text-[10px] text-muted-foreground truncate">
                        {passkey.deviceType || "unknown"}
                        {passkey.backedUp ? " / synced" : ""}
                      </span>
                    </div>
                    <Button
                      variant="ghost"
                      size="icon"
                      className="size-6 text-muted-foreground hover:text-destructive"
                      onClick={() => handleDeletePasskey(passkey.id)}
                      disabled={passkeyLoading}
                    >
                      <Trash2 className="size-3" />
                    </Button>
                  </div>
                ))
              )}
            </div>
          </div>

          {canChangePasword && (
            <PasswordChangeSection
              showPassword={showPassword}
              setShowPassword={setShowPassword}
              onLogout={onLogout}
            />
          )}
        </div>
      </AccordionSection>

      {/* API Keys */}
      <AccordionSection
        id="api-keys"
        label={t("newUi.sidebar.userProfile.sectionApiKeys")}
        icon={<Network className="size-3.5" />}
        open={openSections.has("api-keys")}
        onToggle={() => toggle("api-keys")}
      >
        <div className="flex flex-col gap-2 pt-3">
          <div className="flex items-center justify-between">
            <span className="text-[10px] text-muted-foreground">
              {t("newUi.sidebar.userProfile.apiKeyCount", {
                count: apiKeys.length,
              })}
            </span>
            <Button
              variant="outline"
              size="sm"
              className="h-6 text-[10px] font-bold uppercase tracking-widest border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 gap-1"
              onClick={() => setNewKeyOpen(true)}
            >
              <Plus className="size-3" />{" "}
              {t("newUi.sidebar.userProfile.newKey")}
            </Button>
          </div>

          <div className="flex flex-col divide-y divide-border">
            {apiKeys.length === 0 ? (
              <div className="py-6 text-center text-muted-foreground text-xs">
                {t("newUi.sidebar.userProfile.noApiKeys")}
              </div>
            ) : (
              apiKeys.map((key) => (
                <div
                  key={key.id}
                  className="flex items-start justify-between py-2.5 gap-2"
                >
                  <div className="flex flex-col gap-0.5 min-w-0 flex-1">
                    <div className="flex items-center gap-1.5 flex-wrap">
                      <span className="text-xs font-semibold truncate">
                        {key.name}
                      </span>
                      {key.isActive && (
                        <span className="text-[9px] font-bold px-1 py-px border border-accent-brand/40 bg-accent-brand/10 text-accent-brand uppercase shrink-0">
                          {t("newUi.sidebar.userProfile.apiKeyActive")}
                        </span>
                      )}
                    </div>
                    <span className="text-[10px] font-mono text-muted-foreground truncate">
                      {key.tokenPrefix}…
                    </span>
                    <span className="text-[10px] text-muted-foreground">
                      {t("newUi.sidebar.userProfile.apiKeyUser")}:{" "}
                      {key.username}
                    </span>
                    {key.expiresAt && (
                      <span className="text-[10px] text-muted-foreground">
                        {t("newUi.sidebar.userProfile.apiKeyExpires")}:{" "}
                        {new Date(key.expiresAt).toLocaleDateString()}
                      </span>
                    )}
                  </div>
                  <div className="flex items-center gap-0.5 shrink-0">
                    <Button
                      variant="ghost"
                      size="icon"
                      className="size-6 text-muted-foreground hover:text-destructive"
                      onClick={async () => {
                        try {
                          await deleteApiKey(key.id);
                          setApiKeys((prev) =>
                            prev.filter((k) => k.id !== key.id),
                          );
                          toast.success(
                            t("newUi.sidebar.userProfile.apiKeyRevoked", {
                              name: key.name,
                            }),
                          );
                        } catch {
                          toast.error(
                            t("newUi.sidebar.userProfile.apiKeyRevokeFailed"),
                          );
                        }
                      }}
                    >
                      <Trash2 className="size-3" />
                    </Button>
                  </div>
                </div>
              ))
            )}
          </div>

          <div className="border-t border-border pt-2 text-[10px] text-muted-foreground flex flex-col gap-1">
            <p>
              {t("newUi.sidebar.userProfile.apiKeyUsageHint")}{" "}
              <code className="font-mono text-accent-brand bg-accent-brand/10 px-1">
                Authorization: Bearer
              </code>{" "}
              {t("newUi.sidebar.userProfile.apiKeyUsageHintHeader")}
            </p>
            <p>{t("newUi.sidebar.userProfile.apiKeyPermissionsHint")}</p>
          </div>
        </div>
      </AccordionSection>

      <AccordionSection
        id="data"
        label={t("newUi.sidebar.userProfile.sectionData")}
        icon={<Database className="size-3.5" />}
        open={openSections.has("data")}
        onToggle={() => toggle("data")}
      >
        <div className="flex flex-col gap-3 pt-3">
          <div className="flex flex-col gap-1.5">
            <span className="text-xs font-medium">
              {t("newUi.sidebar.userProfile.exportData")}
            </span>
            <span className="text-[10px] text-muted-foreground">
              {t("newUi.sidebar.userProfile.exportDataDesc")}
            </span>
            <Button
              variant="outline"
              size="sm"
              className="self-start text-xs border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand mt-1"
              onClick={handleExportData}
              disabled={exportLoading}
            >
              {exportLoading
                ? t("newUi.sidebar.userProfile.exporting")
                : t("newUi.sidebar.userProfile.export")}
            </Button>
          </div>
          <div className="flex flex-col gap-1.5 border-t border-border pt-3">
            <span className="text-xs font-medium">
              {t("newUi.sidebar.userProfile.importData")}
            </span>
            <span className="text-[10px] text-muted-foreground">
              {importFile
                ? t("newUi.sidebar.userProfile.importDataSelected", {
                    name: importFile.name,
                  })
                : t("newUi.sidebar.userProfile.importDataDesc")}
            </span>
            <div className="flex items-center gap-2 mt-1">
              <div className="relative">
                <input
                  type="file"
                  accept=".sqlite,.db"
                  onChange={(e) => setImportFile(e.target.files?.[0] ?? null)}
                  className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
                />
                <Button
                  variant="outline"
                  size="sm"
                  className="pointer-events-none text-xs"
                >
                  {importFile
                    ? t("newUi.sidebar.userProfile.changeFile")
                    : t("newUi.sidebar.userProfile.selectFile")}
                </Button>
              </div>
              {importFile && (
                <Button
                  variant="outline"
                  size="sm"
                  className="text-xs border-accent-brand/40 text-accent-brand hover:bg-accent-brand/10 hover:text-accent-brand"
                  onClick={handleImportData}
                  disabled={importLoading}
                >
                  {importLoading
                    ? t("newUi.sidebar.userProfile.importing")
                    : t("newUi.sidebar.userProfile.import")}
                </Button>
              )}
            </div>
          </div>
        </div>
      </AccordionSection>

      {isElectron() && (
        <AccordionSection
          id="c2s-tunnels"
          label={t("newUi.sidebar.userProfile.sectionC2sTunnels")}
          icon={<Activity className="size-3.5" />}
          open={openSections.has("c2s-tunnels")}
          onToggle={() => toggle("c2s-tunnels")}
        >
          <C2STunnelPresetManager />
        </AccordionSection>
      )}

      {/* Delete account dialog */}
      <Dialog
        open={showDeleteConfirm}
        onOpenChange={(open) => {
          setShowDeleteConfirm(open);
          if (!open) setDeletePassword("");
        }}
      >
        <DialogContent className="sm:max-w-md">
          <DialogHeader>
            <DialogTitle className="text-lg font-bold text-destructive">
              {t("newUi.sidebar.userProfile.deleteAccount")}
            </DialogTitle>
            <DialogDescription className="text-xs text-muted-foreground">
              {t("newUi.sidebar.userProfile.deleteAccountPermanent")}
            </DialogDescription>
          </DialogHeader>
          <div className="flex flex-col gap-3 mt-1">
            <div className="flex items-start gap-2.5 border border-destructive/30 bg-destructive/5 px-3 py-2.5">
              <AlertCircle className="size-4 text-destructive shrink-0 mt-0.5" />
              <span className="text-xs text-destructive">
                {t("newUi.sidebar.userProfile.deleteAccountWarning")}
              </span>
            </div>
            <div className="flex flex-col gap-1.5">
              <label className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
                {t("newUi.sidebar.userProfile.confirmPasswordLabel")}
              </label>
              <Input
                type="password"
                placeholder={t(
                  "newUi.sidebar.userProfile.confirmPasswordDeletePlaceholder",
                )}
                value={deletePassword}
                onChange={(e) => setDeletePassword(e.target.value)}
                onKeyDown={(e) => e.key === "Enter" && handleDeleteAccount()}
              />
            </div>
          </div>
          <div className="flex items-center justify-end gap-2 mt-2">
            <Button
              variant="ghost"
              onClick={() => {
                setShowDeleteConfirm(false);
                setDeletePassword("");
              }}
            >
              {t("newUi.sidebar.userProfile.cancel")}
            </Button>
            <Button
              variant="outline"
              className="border-destructive/40 text-destructive hover:bg-destructive/10 hover:text-destructive"
              onClick={handleDeleteAccount}
              disabled={deleteLoading || !deletePassword.trim()}
            >
              <Trash2 className="size-3.5" />
              {deleteLoading
                ? t("newUi.sidebar.userProfile.deleting")
                : t("newUi.sidebar.userProfile.deleteAccount")}
            </Button>
          </div>
        </DialogContent>
      </Dialog>
      <KeybindingsDialog
        open={keybindingsDialogOpen}
        onOpenChange={setKeybindingsDialogOpen}
      />
    </div>
  );
}
