import { useEffect, useRef, useState } from 'react';
import { SidebarChildVO } from '@/app/home/components/home-sidebar/HomeSidebarChild';
import { useNavigate, useLocation, useSearchParams } from 'react-router-dom';
import { sidebarConfigList } from '@/app/home/components/home-sidebar/sidbarConfigList';
import langbotIcon from '@/app/assets/langbot-logo.webp';
import { systemInfo, httpClient } from '@/app/infra/http/HttpClient';
import {
  clearUserInfo,
  getCloudServiceClientSync,
  useCurrentWorkspace,
  useWorkspaceBootstrap,
} from '@/app/infra/http';
import { useTranslation } from 'react-i18next';
import {
  Moon,
  Sun,
  Monitor,
  ChevronsUpDown,
  CircleHelp,
  Lightbulb,
  LogOut,
  KeyRound,
  Settings,
  Star,
  Ellipsis,
  ArrowUp,
  ExternalLink,
  Trash,
  Bug,
  Upload,
  Store,
  Github,
  Zap,
  FilePlus2,
  Sparkles,
  Server,
  Puzzle,
  RefreshCcw,
  UsersRound,
} from 'lucide-react';
import { useTheme } from '@/components/providers/theme-provider';

import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import { Checkbox } from '@/components/ui/checkbox';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { LanguageSelector } from '@/components/ui/language-selector';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import WorkspaceSwitcher, {
  OPEN_WORKSPACE_SETTINGS_EVENT,
} from '@/app/home/components/workspace-settings/WorkspaceSwitcher';
import NewVersionDialog from '@/app/home/components/new-version-dialog/NewVersionDialog';
import SettingsDialog, {
  SettingsSection,
  SETTINGS_ACTION_BY_SECTION,
  SETTINGS_SECTION_BY_ACTION,
} from '@/app/home/components/settings-dialog/SettingsDialog';
import { GitHubRelease } from '@/app/infra/http/CloudServiceClient';
import { useAsyncTask, AsyncTaskStatus } from '@/hooks/useAsyncTask';
import { toast } from 'sonner';
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarMenuSub,
  SidebarMenuSubButton,
  SidebarMenuSubItem,
  useSidebar,
} from '@/components/ui/sidebar';
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from '@/components/ui/collapsible';
import { ChevronDown, ChevronRight, Plus } from 'lucide-react';
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from '@/components/ui/tooltip';
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { useSidebarData, SidebarEntityItem } from './SidebarDataContext';
import { FeedbackPopoverContent } from './FeedbackPopover';
import {
  type WorkspaceQuotaItem,
  useWorkspaceQuotaStatus,
} from '@/app/home/components/workspace-quota/useWorkspaceQuotaStatus';
import { WorkspaceQuotaTooltip } from '@/app/home/components/workspace-quota/WorkspaceQuotaTooltip';

// Compare two version strings, returns true if v1 > v2
function compareVersions(v1: string, v2: string): boolean {
  const clean1 = v1.replace(/^v/, '');
  const clean2 = v2.replace(/^v/, '');

  const parts1 = clean1.split('.').map((p) => parseInt(p, 10) || 0);
  const parts2 = clean2.split('.').map((p) => parseInt(p, 10) || 0);

  const maxLen = Math.max(parts1.length, parts2.length);

  for (let i = 0; i < maxLen; i++) {
    const p1 = parts1[i] || 0;
    const p2 = parts2[i] || 0;
    if (p1 > p2) return true;
    if (p1 < p2) return false;
  }
  return false;
}

// Discord brand glyph (lucide-react has no Discord icon).
function DiscordIcon({ className }: { className?: string }) {
  return (
    <svg
      role="img"
      viewBox="0 0 24 24"
      fill="currentColor"
      xmlns="http://www.w3.org/2000/svg"
      className={className}
      aria-hidden="true"
    >
      <path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
    </svg>
  );
}

// IDs of sidebar entries that have collapsible entity sub-items
const ENTITY_CATEGORY_IDS = [
  'bots',
  'pipelines',
  'knowledge',
  'plugins',
  'mcp',
  'skills',
] as const;
type EntityCategoryId = (typeof ENTITY_CATEGORY_IDS)[number];

// Categories that support detail pages via ?id= query param
const DETAIL_PAGE_CATEGORIES: EntityCategoryId[] = [
  'bots',
  'pipelines',
  'knowledge',
  'plugins',
  'mcp',
  'skills',
];

// Categories that support creating new entities from the sidebar
const CREATABLE_CATEGORIES: EntityCategoryId[] = [
  'bots',
  'pipelines',
  'knowledge',
  'mcp',
  'skills',
];

// Categories where clicking the parent only toggles collapse (no list page)
const COLLAPSIBLE_ONLY_CATEGORIES: EntityCategoryId[] = [
  'bots',
  'pipelines',
  'knowledge',
  'mcp',
  'skills',
];

function isEntityCategory(id: string): id is EntityCategoryId {
  return (ENTITY_CATEGORY_IDS as readonly string[]).includes(id);
}

// Map sidebar config IDs to SidebarDataContext keys
const ENTITY_KEY_MAP: Record<
  EntityCategoryId,
  'bots' | 'pipelines' | 'knowledgeBases' | 'plugins' | 'mcpServers' | 'skills'
> = {
  bots: 'bots',
  pipelines: 'pipelines',
  knowledge: 'knowledgeBases',
  plugins: 'plugins',
  mcp: 'mcpServers',
  skills: 'skills',
};

// Route prefix map for entity detail pages
const ENTITY_ROUTE_MAP: Record<EntityCategoryId, string> = {
  bots: '/home/bots',
  pipelines: '/home/pipelines',
  knowledge: '/home/knowledge',
  plugins: '/home/extensions',
  mcp: '/home/mcp',
  skills: '/home/skills',
};

// localStorage key for collapsible section open/closed state
const SIDEBAR_SECTIONS_KEY = 'sidebar_sections';
const SIDEBAR_LIST_EXPANSION_KEY = 'sidebar_entity_list_expansion';
const SCROLL_HINT_BOTTOM_THRESHOLD = 40;

type SidebarNavSection = 'home' | 'extensions';
type SidebarListExpansionState = Record<
  SidebarNavSection,
  Partial<Record<EntityCategoryId, boolean>>
>;

function createEmptyListExpansionState(): SidebarListExpansionState {
  return {
    home: {},
    extensions: {},
  };
}

function loadSectionState(): Record<string, boolean> {
  if (typeof window === 'undefined') return {};
  try {
    const stored = localStorage.getItem(SIDEBAR_SECTIONS_KEY);
    return stored ? JSON.parse(stored) : {};
  } catch {
    return {};
  }
}

function saveSectionState(state: Record<string, boolean>) {
  try {
    localStorage.setItem(SIDEBAR_SECTIONS_KEY, JSON.stringify(state));
  } catch {
    // Ignore storage errors
  }
}

function loadListExpansionState(): SidebarListExpansionState {
  if (typeof window === 'undefined') return createEmptyListExpansionState();
  try {
    const stored = localStorage.getItem(SIDEBAR_LIST_EXPANSION_KEY);
    if (!stored) return createEmptyListExpansionState();
    const parsed = JSON.parse(stored) as Partial<SidebarListExpansionState>;
    return {
      home: parsed.home ?? {},
      extensions: parsed.extensions ?? {},
    };
  } catch {
    return createEmptyListExpansionState();
  }
}

function saveListExpansionState(state: SidebarListExpansionState) {
  try {
    localStorage.setItem(SIDEBAR_LIST_EXPANSION_KEY, JSON.stringify(state));
  } catch {
    // Ignore storage errors
  }
}

// Maximum number of entity sub-items visible before "More" toggle
const MAX_VISIBLE_ITEMS = 5;
const MCP_REFRESH_POLL_INTERVAL_MS = 1000;
const MCP_REFRESH_TIMEOUT_MS = 60000;

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

const UNLIMITED_QUOTA: WorkspaceQuotaItem = {
  count: 0,
  max: -1,
  reached: false,
  loading: false,
  disabled: false,
};

async function waitForMCPRefreshTask(taskId: number) {
  const deadline = Date.now() + MCP_REFRESH_TIMEOUT_MS;

  while (Date.now() < deadline) {
    const task = await httpClient.getAsyncTask(taskId);
    if (task.runtime.done) return task;
    await sleep(MCP_REFRESH_POLL_INTERVAL_MS);
  }

  throw new Error(`Timed out waiting for MCP refresh task ${taskId}`);
}

async function refreshEnabledMCPConnections() {
  const resp = await httpClient.getMCPServers();
  const enabledServers = resp.servers.filter((server) => server.enable);
  if (enabledServers.length === 0) return;

  const taskResults = await Promise.allSettled(
    enabledServers.map((server) => httpClient.testMCPServer(server.name, {})),
  );
  const taskIds: number[] = [];

  for (const result of taskResults) {
    if (
      result.status === 'fulfilled' &&
      typeof result.value.task_id === 'number'
    ) {
      taskIds.push(result.value.task_id);
    } else if (result.status === 'rejected') {
      console.error('Failed to start MCP refresh task:', result.reason);
    }
  }

  await Promise.allSettled(taskIds.map(waitForMCPRefreshTask));
}

// Sort entity items by updatedAt descending (most recent first), items without updatedAt go last
function sortByRecent(items: SidebarEntityItem[]): SidebarEntityItem[] {
  return [...items].sort((a, b) => {
    if (!a.updatedAt && !b.updatedAt) return 0;
    if (!a.updatedAt) return 1;
    if (!b.updatedAt) return -1;
    return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
  });
}

// MCP status dot color: disabled → gray, error → red, connecting → yellow, connected → green
function mcpStatusColor(item: SidebarEntityItem): string {
  if (item.enabled === false) return 'bg-muted-foreground/40';
  switch (item.runtimeStatus) {
    case 'connected':
      return 'bg-green-500';
    case 'connecting':
      return 'bg-yellow-500';
    case 'error':
      return 'bg-red-500';
    default:
      return 'bg-muted-foreground/40';
  }
}

function MCPStatusIcon({
  item,
  borderClass,
}: {
  item: SidebarEntityItem;
  borderClass: string;
}) {
  return (
    <span className="relative shrink-0">
      <Server className="size-4 !text-blue-500" />
      <span
        className={cn(
          'absolute -bottom-1 -right-1 size-3 rounded-full border-2',
          borderClass,
          mcpStatusColor(item),
        )}
      />
    </span>
  );
}

// Plugin operation type enum
enum PluginOperationType {
  DELETE = 'DELETE',
  UPDATE = 'UPDATE',
}

// Renders sidebar navigation items with collapsible sub-items for entity categories
function NavItems({
  selectedChild,
  onChildClick,
  section,
  sectionOpenState,
  onSectionToggle,
}: {
  selectedChild: SidebarChildVO | undefined;
  onChildClick: (child: SidebarChildVO) => void;
  section: SidebarNavSection;
  sectionOpenState: Record<string, boolean>;
  onSectionToggle: (id: string, open: boolean) => void;
}) {
  const navigate = useNavigate();
  const location = useLocation();
  const pathname = location.pathname;
  const [searchParams] = useSearchParams();
  const sidebarData = useSidebarData();
  const quotaStatus = useWorkspaceQuotaStatus();
  const { state: sidebarState, isMobile } = useSidebar();
  const { t } = useTranslation();
  const currentWorkspace = useCurrentWorkspace();
  const canManageResources =
    currentWorkspace?.permissions.includes('resource.manage') ?? false;
  const canOperateRuntime =
    currentWorkspace?.permissions.includes('runtime.operate') ?? false;
  // Track which entity categories have their full list expanded
  const [expandedLists, setExpandedLists] = useState<SidebarListExpansionState>(
    loadListExpansionState,
  );
  // Track popover open state for collapsed sidebar entity categories
  const [popoverOpen, setPopoverOpen] = useState<Record<string, boolean>>({});
  // Spin state for the installed-extensions refresh button
  const [extRefreshing, setExtRefreshing] = useState(false);

  const handleRefreshExtensions = async (e: React.MouseEvent) => {
    e.stopPropagation();
    if (extRefreshing) return;
    setExtRefreshing(true);
    try {
      const results = await Promise.allSettled([
        sidebarData.refreshPlugins(),
        sidebarData.refreshSkills(),
        refreshEnabledMCPConnections(),
      ]);
      const mcpRefreshResult = results[2];
      if (mcpRefreshResult.status === 'rejected') {
        console.error(
          'Failed to refresh MCP connections:',
          mcpRefreshResult.reason,
        );
      }
      await sidebarData.refreshMCPServers();
    } finally {
      setExtRefreshing(false);
    }
  };

  // Plugin operation state
  const [showPluginOpModal, setShowPluginOpModal] = useState(false);
  const [pluginOpType, setPluginOpType] = useState<PluginOperationType>(
    PluginOperationType.DELETE,
  );
  const [targetPluginItem, setTargetPluginItem] =
    useState<SidebarEntityItem | null>(null);
  const [deleteData, setDeleteData] = useState(false);

  const asyncTask = useAsyncTask({
    onSuccess: () => {
      const msg =
        pluginOpType === PluginOperationType.DELETE
          ? t('plugins.deleteSuccess')
          : t('plugins.updateSuccess');
      toast.success(msg);
      setShowPluginOpModal(false);
      sidebarData.refreshPlugins();
    },
  });

  function handlePluginDelete(item: SidebarEntityItem) {
    setTargetPluginItem(item);
    setPluginOpType(PluginOperationType.DELETE);
    setDeleteData(false);
    asyncTask.reset();
    setShowPluginOpModal(true);
  }

  function handlePluginUpdate(item: SidebarEntityItem) {
    setTargetPluginItem(item);
    setPluginOpType(PluginOperationType.UPDATE);
    asyncTask.reset();
    setShowPluginOpModal(true);
  }

  function executePluginOperation() {
    if (!targetPluginItem) return;
    const slashIdx = targetPluginItem.id.indexOf('/');
    const author =
      slashIdx >= 0 ? targetPluginItem.id.substring(0, slashIdx) : '';
    const name =
      slashIdx >= 0
        ? targetPluginItem.id.substring(slashIdx + 1)
        : targetPluginItem.id;

    const apiCall =
      pluginOpType === PluginOperationType.DELETE
        ? httpClient.removePlugin(author, name, deleteData)
        : httpClient.upgradePlugin(author, name);

    apiCall
      .then((res) => {
        asyncTask.startTask(res.task_id);
      })
      .catch((error) => {
        const errorMessage =
          pluginOpType === PluginOperationType.DELETE
            ? t('plugins.deleteError') + error.message
            : t('plugins.updateError') + error.message;
        toast.error(errorMessage);
      });
  }

  const sectionItems = sidebarConfigList.filter((c) => c.section === section);

  function handleListExpansionToggle(id: EntityCategoryId, expanded: boolean) {
    setExpandedLists(() => {
      const latest = loadListExpansionState();
      const next = {
        ...latest,
        [section]: {
          ...latest[section],
          [id]: expanded,
        },
      };
      saveListExpansionState(next);
      return next;
    });
  }

  // Persist open state for sections that become active through navigation,
  // so they remain expanded when the user switches to a different section.
  const sectionOpenRef = useRef(sectionOpenState);
  sectionOpenRef.current = sectionOpenState;
  useEffect(() => {
    sectionItems.forEach((config) => {
      if (!isEntityCategory(config.id)) return;
      const routePrefix = ENTITY_ROUTE_MAP[config.id];
      const active =
        pathname === routePrefix || pathname.startsWith(routePrefix + '/');
      if (active && sectionOpenRef.current[config.id] === undefined) {
        onSectionToggle(config.id, true);
      }
    });
  }, [pathname, sectionItems, onSectionToggle]);

  return (
    <>
      {sectionItems.map((config) => {
        if (!isEntityCategory(config.id)) {
          if (config.id === 'add-extension' && !canManageResources) {
            return null;
          }
          // Non-entity entries (e.g. monitoring and the extension market) render as plain links.
          return (
            <SidebarMenuItem key={config.id}>
              <SidebarMenuButton
                isActive={selectedChild?.id === config.id}
                onClick={() => onChildClick(config)}
                tooltip={config.name}
              >
                {config.icon}
                <span className="cursor-pointer select-none">
                  {config.name}
                </span>
              </SidebarMenuButton>
            </SidebarMenuItem>
          );
        }

        // Entity categories: collapsible with sub-items
        const categoryId = config.id;
        const entityKey = ENTITY_KEY_MAP[categoryId];
        const isExtensionsCategory = categoryId === 'plugins';
        const items: SidebarEntityItem[] = isExtensionsCategory
          ? [
              ...sidebarData.plugins.map((p) => ({
                ...p,
                extensionType: 'plugin' as const,
              })),
              ...sidebarData.mcpServers.map((m) => ({
                ...m,
                extensionType: 'mcp' as const,
              })),
              ...sidebarData.skills.map((s) => ({
                ...s,
                extensionType: 'skill' as const,
              })),
            ]
          : sidebarData[entityKey];
        const routePrefix = ENTITY_ROUTE_MAP[categoryId];
        const hasDetailPages = DETAIL_PAGE_CATEGORIES.includes(categoryId);
        const canCreate =
          canManageResources && CREATABLE_CATEGORIES.includes(categoryId);
        const isCollapseOnly = COLLAPSIBLE_ONLY_CATEGORIES.includes(categoryId);
        const isPlugin = categoryId === 'plugins';
        const isSkill = categoryId === 'skills';
        const isBot = categoryId === 'bots';
        const isMCP = categoryId === 'mcp';
        const quota =
          categoryId === 'bots'
            ? quotaStatus.bots
            : categoryId === 'pipelines'
              ? quotaStatus.pipelines
              : categoryId === 'knowledge'
                ? quotaStatus.knowledgeBases
                : categoryId === 'plugins' ||
                    categoryId === 'mcp' ||
                    categoryId === 'skills'
                  ? quotaStatus.extensions
                  : UNLIMITED_QUOTA;

        const resolveItemRoute = (item: SidebarEntityItem): string => {
          if (item.extensionType === 'mcp') {
            return `/home/mcp?id=${encodeURIComponent(item.id)}`;
          }
          if (item.extensionType === 'skill') {
            return `/home/skills?id=${encodeURIComponent(item.id)}`;
          }
          return hasDetailPages
            ? `${routePrefix}?id=${encodeURIComponent(item.id)}`
            : routePrefix;
        };
        const isActive =
          selectedChild?.id === categoryId ||
          pathname === routePrefix ||
          pathname.startsWith(routePrefix + '/');

        // Use stored open state if available, otherwise default to active state
        const isOpen = sectionOpenState[categoryId] ?? isActive;

        // When sidebar is collapsed on desktop and category is collapse-only,
        // show a popover flyout instead of the hidden collapsible sub-items
        const isCollapsed = sidebarState === 'collapsed' && !isMobile;
        const showPopover = isCollapsed && isCollapseOnly;

        // Shared entity list renderer used by both popover and collapsible
        const renderEntityList = (inPopover: boolean) => {
          const sortedItems = isExtensionsCategory
            ? [...items].sort((a, b) =>
                a.name.localeCompare(b.name, undefined, {
                  sensitivity: 'base',
                }),
              )
            : sortByRecent(items);
          const isExpanded = expandedLists[section]?.[categoryId] ?? false;
          const maxItems = inPopover ? 10 : MAX_VISIBLE_ITEMS;
          const visibleItems =
            sortedItems.length > maxItems && !isExpanded
              ? sortedItems.slice(0, maxItems)
              : sortedItems;
          const hiddenCount = sortedItems.length - maxItems;

          if (sortedItems.length === 0) {
            return (
              <div
                className={cn(
                  'text-muted-foreground text-xs',
                  inPopover ? 'px-2 py-3 text-center' : 'px-2 py-1.5',
                )}
              >
                {t('common.noItems')}
              </div>
            );
          }

          const itemActiveCheck = (item: SidebarEntityItem): boolean => {
            if (item.extensionType === 'mcp') {
              return (
                pathname === '/home/mcp' && searchParams.get('id') === item.id
              );
            }
            if (item.extensionType === 'skill') {
              return (
                pathname === '/home/skills' &&
                searchParams.get('id') === item.id
              );
            }
            return (
              hasDetailPages &&
              pathname === routePrefix &&
              searchParams.get('id') === item.id
            );
          };

          const itemIsPlugin = (item: SidebarEntityItem): boolean =>
            isExtensionsCategory ? item.extensionType === 'plugin' : isPlugin;

          const showGroupHeaders =
            isExtensionsCategory &&
            !inPopover &&
            sidebarData.extensionsGroupByType;

          const groupOrder: Array<'plugin' | 'mcp' | 'skill'> = [
            'plugin',
            'mcp',
            'skill',
          ];
          const groupLabelKey: Record<'plugin' | 'mcp' | 'skill', string> = {
            plugin: 'market.typePlugin',
            mcp: 'market.typeMCP',
            skill: 'market.typeSkill',
          };

          const renderItem = (item: SidebarEntityItem) => {
            const itemRoute = resolveItemRoute(item);
            const isItemActive = itemActiveCheck(item);
            const itemIsPluginType = itemIsPlugin(item);
            if (inPopover) {
              return (
                <button
                  key={item.id}
                  type="button"
                  className={cn(
                    'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-left',
                    'hover:bg-accent hover:text-accent-foreground transition-colors',
                    isItemActive &&
                      'bg-accent text-accent-foreground font-medium',
                  )}
                  onClick={() => {
                    navigate(itemRoute);
                    setPopoverOpen((prev) => ({
                      ...prev,
                      [categoryId]: false,
                    }));
                  }}
                >
                  {item.extensionType === 'mcp' ? (
                    <MCPStatusIcon item={item} borderClass="border-popover" />
                  ) : item.extensionType === 'skill' ? (
                    <Sparkles className="size-4 shrink-0 !text-blue-500" />
                  ) : item.emoji ? (
                    <span className="text-sm shrink-0">{item.emoji}</span>
                  ) : item.iconURL ? (
                    <span className="relative shrink-0">
                      <img
                        src={item.iconURL}
                        alt=""
                        className="size-4 rounded"
                      />
                      {(isBot || isMCP) && (
                        <span
                          className={cn(
                            'absolute -bottom-0.5 -right-0.5 size-2 rounded-full border-2 border-popover',
                            isMCP
                              ? mcpStatusColor(item)
                              : item.enabled === false
                                ? 'bg-muted-foreground/40'
                                : 'bg-green-500',
                          )}
                        />
                      )}
                    </span>
                  ) : item.extensionType === 'plugin' ? (
                    <Puzzle className="size-4 shrink-0 !text-blue-500" />
                  ) : isMCP ? (
                    <span
                      className={cn(
                        'size-2 shrink-0 rounded-full',
                        mcpStatusColor(item),
                      )}
                    />
                  ) : null}
                  <span className="truncate">{item.name}</span>
                </button>
              );
            }

            // Normal sidebar sub-item rendering
            return (
              <SidebarMenuSubItem
                key={item.id}
                className={itemIsPluginType ? 'group/plugin-item relative' : ''}
              >
                <Tooltip delayDuration={500}>
                  <TooltipTrigger asChild>
                    <SidebarMenuSubButton asChild isActive={isItemActive}>
                      <a
                        href={itemRoute}
                        className={cn(
                          itemIsPluginType && !item.debug ? 'pr-6' : '',
                        )}
                        onClick={(e) => {
                          e.preventDefault();
                          navigate(itemRoute);
                        }}
                      >
                        {item.extensionType === 'mcp' ? (
                          <MCPStatusIcon
                            item={item}
                            borderClass="border-sidebar"
                          />
                        ) : item.extensionType === 'skill' ? (
                          <Sparkles className="size-4 shrink-0 !text-blue-500" />
                        ) : item.emoji ? (
                          <span className="text-sm shrink-0">{item.emoji}</span>
                        ) : item.iconURL ? (
                          <span className="relative shrink-0">
                            <img
                              src={item.iconURL}
                              alt=""
                              className="size-4 rounded"
                            />
                            {(isBot || isMCP) && (
                              <span
                                className={cn(
                                  'absolute -bottom-0.5 -right-0.5 size-2 rounded-full border-2 border-sidebar',
                                  isMCP
                                    ? mcpStatusColor(item)
                                    : item.enabled === false
                                      ? 'bg-muted-foreground/40'
                                      : 'bg-green-500',
                                )}
                              />
                            )}
                          </span>
                        ) : item.extensionType === 'plugin' ? (
                          <Puzzle className="size-4 shrink-0 !text-blue-500" />
                        ) : isMCP ? (
                          <span
                            className={cn(
                              'size-2 shrink-0 rounded-full',
                              mcpStatusColor(item),
                            )}
                          />
                        ) : null}
                        <span className="truncate">{item.name}</span>
                        {item.debug && (
                          <Bug className="size-3.5 shrink-0 text-orange-400" />
                        )}
                      </a>
                    </SidebarMenuSubButton>
                  </TooltipTrigger>
                  <TooltipContent
                    side="right"
                    align="center"
                    className="max-w-64"
                  >
                    {/* Full name — so truncated sidebar items are readable on hover */}
                    <div className="break-words font-medium">{item.name}</div>
                    {item.description && (
                      <div className="mt-0.5 break-words text-xs text-muted-foreground">
                        {item.description.length > 80
                          ? item.description.slice(0, 80) + '…'
                          : item.description}
                      </div>
                    )}
                  </TooltipContent>
                </Tooltip>
                {/* Plugin context menu - shown on hover (not for debug plugins) */}
                {itemIsPluginType && !item.debug && (
                  <PluginItemMenu
                    item={item}
                    canManage={canManageResources}
                    onUpdate={() => handlePluginUpdate(item)}
                    onDelete={() => handlePluginDelete(item)}
                  />
                )}
              </SidebarMenuSubItem>
            );
          };

          return (
            <>
              {showGroupHeaders
                ? groupOrder.map((type) => {
                    const groupItems = visibleItems.filter(
                      (it) => it.extensionType === type,
                    );
                    if (groupItems.length === 0) return null;
                    return (
                      <div key={type} className="flex flex-col gap-0.5 mt-0.5">
                        <div className="px-2 pt-1 pb-0.5 text-[0.65rem] font-semibold uppercase tracking-wide text-muted-foreground">
                          {t(groupLabelKey[type])}
                        </div>
                        {groupItems.map((item) => renderItem(item))}
                      </div>
                    );
                  })
                : visibleItems.map((item) => renderItem(item))}
              {/* Show more / less toggle when items exceed limit */}
              {sortedItems.length > maxItems && !inPopover && (
                <SidebarMenuSubItem>
                  <SidebarMenuSubButton
                    asChild
                    className="text-muted-foreground"
                  >
                    <button
                      type="button"
                      onClick={() =>
                        handleListExpansionToggle(categoryId, !isExpanded)
                      }
                    >
                      <span className="text-xs">
                        {isExpanded
                          ? t('common.less')
                          : t('common.more', { count: hiddenCount })}
                      </span>
                    </button>
                  </SidebarMenuSubButton>
                </SidebarMenuSubItem>
              )}
              {hiddenCount > 0 && inPopover && !isExpanded && (
                <button
                  type="button"
                  className="flex w-full items-center justify-center rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent transition-colors"
                  onClick={() => handleListExpansionToggle(categoryId, true)}
                >
                  {t('common.more', { count: hiddenCount })}
                </button>
              )}
            </>
          );
        };

        // Popover flyout for collapsed sidebar
        if (showPopover) {
          return (
            <SidebarMenuItem key={config.id}>
              <Popover
                open={popoverOpen[config.id] ?? false}
                onOpenChange={(open) =>
                  setPopoverOpen((prev) => ({ ...prev, [config.id]: open }))
                }
              >
                <PopoverTrigger asChild>
                  <SidebarMenuButton
                    isActive={isActive}
                    tooltip={config.name}
                    className="group/category-header"
                  >
                    {config.icon}
                    <span>{config.name}</span>
                  </SidebarMenuButton>
                </PopoverTrigger>
                <PopoverContent
                  side="right"
                  align="start"
                  sideOffset={8}
                  className="w-56 p-2"
                >
                  <div className="flex items-center justify-between mb-1 px-2">
                    <span className="text-sm font-medium">{config.name}</span>
                    {canCreate && (
                      <WorkspaceQuotaTooltip
                        quota={quota}
                        resource={config.name}
                        side="right"
                      >
                        {isPlugin ? (
                          <DropdownMenu>
                            <DropdownMenuTrigger asChild>
                              <button
                                type="button"
                                disabled={quota.disabled}
                                aria-disabled={quota.disabled}
                                aria-label={`${t('common.create')} ${config.name}`}
                                className="p-1 rounded-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors disabled:pointer-events-none disabled:opacity-40"
                              >
                                <Plus className="size-3.5" />
                              </button>
                            </DropdownMenuTrigger>
                            <DropdownMenuContent align="end">
                              {systemInfo.enable_marketplace && (
                                <DropdownMenuItem
                                  onClick={(e) => {
                                    e.stopPropagation();
                                    navigate('/home/add-extension');
                                    setPopoverOpen((prev) => ({
                                      ...prev,
                                      [config.id]: false,
                                    }));
                                  }}
                                >
                                  <Store className="size-4" />
                                  {t('plugins.goToMarketplace')}
                                </DropdownMenuItem>
                              )}
                              <DropdownMenuItem
                                onClick={(e) => {
                                  e.stopPropagation();
                                  navigate('/home/add-extension?manual=1');
                                  setPopoverOpen((prev) => ({
                                    ...prev,
                                    [config.id]: false,
                                  }));
                                }}
                              >
                                <Upload className="size-4" />
                                {t('plugins.uploadLocal')}
                              </DropdownMenuItem>
                              <DropdownMenuItem
                                onClick={(e) => {
                                  e.stopPropagation();
                                  navigate('/home/add-extension?manual=1');
                                  setPopoverOpen((prev) => ({
                                    ...prev,
                                    [config.id]: false,
                                  }));
                                }}
                              >
                                <Github className="size-4" />
                                {t('plugins.installFromGithub')}
                              </DropdownMenuItem>
                            </DropdownMenuContent>
                          </DropdownMenu>
                        ) : isSkill ? (
                          <DropdownMenu>
                            <DropdownMenuTrigger asChild>
                              <button
                                type="button"
                                disabled={quota.disabled}
                                aria-disabled={quota.disabled}
                                aria-label={`${t('common.create')} ${config.name}`}
                                className="p-1 rounded-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors disabled:pointer-events-none disabled:opacity-40"
                              >
                                <Plus className="size-3.5" />
                              </button>
                            </DropdownMenuTrigger>
                            <DropdownMenuContent align="end">
                              <DropdownMenuItem
                                onClick={(e) => {
                                  e.stopPropagation();
                                  navigate('/home/skills?action=create');
                                  setPopoverOpen((prev) => ({
                                    ...prev,
                                    [config.id]: false,
                                  }));
                                }}
                              >
                                <FilePlus2 className="size-4" />
                                {t('skills.createManually')}
                              </DropdownMenuItem>
                              <DropdownMenuItem
                                onClick={(e) => {
                                  e.stopPropagation();
                                  navigate('/home/add-extension?manual=1');
                                  setPopoverOpen((prev) => ({
                                    ...prev,
                                    [config.id]: false,
                                  }));
                                }}
                              >
                                <Upload className="size-4" />
                                {t('skills.uploadZip')}
                              </DropdownMenuItem>
                              <DropdownMenuItem
                                onClick={(e) => {
                                  e.stopPropagation();
                                  navigate('/home/add-extension?manual=1');
                                  setPopoverOpen((prev) => ({
                                    ...prev,
                                    [config.id]: false,
                                  }));
                                }}
                              >
                                <Github className="size-4" />
                                {t('skills.importFromGithub')}
                              </DropdownMenuItem>
                            </DropdownMenuContent>
                          </DropdownMenu>
                        ) : (
                          <button
                            type="button"
                            disabled={quota.disabled}
                            aria-disabled={quota.disabled}
                            aria-label={`${t('common.create')} ${config.name}`}
                            className="p-1 rounded-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors disabled:pointer-events-none disabled:opacity-40"
                            onClick={() => {
                              navigate(`${routePrefix}?id=new`);
                              setPopoverOpen((prev) => ({
                                ...prev,
                                [config.id]: false,
                              }));
                            }}
                          >
                            <Plus className="size-3.5" />
                          </button>
                        )}
                      </WorkspaceQuotaTooltip>
                    )}
                  </div>
                  <div className="flex flex-col gap-0.5 max-h-80 overflow-y-auto">
                    {renderEntityList(true)}
                  </div>
                </PopoverContent>
              </Popover>
            </SidebarMenuItem>
          );
        }

        // Normal expanded sidebar with collapsible sub-items
        return (
          <Collapsible
            key={config.id}
            asChild
            open={isOpen}
            onOpenChange={(open) => onSectionToggle(config.id, open)}
            className="group/collapsible"
          >
            <SidebarMenuItem>
              <SidebarMenuButton
                asChild
                isActive={false}
                tooltip={config.name}
                className="group/category-header"
              >
                <div
                  role="button"
                  tabIndex={0}
                  onClick={() => {
                    if (isCollapseOnly) {
                      onSectionToggle(config.id, !isOpen);
                    } else {
                      onChildClick(config);
                    }
                  }}
                  onKeyDown={(e) => {
                    if (e.key === 'Enter' || e.key === ' ') {
                      e.preventDefault();
                      if (isCollapseOnly) {
                        onSectionToggle(config.id, !isOpen);
                      } else {
                        onChildClick(config);
                      }
                    }
                  }}
                >
                  {config.icon}
                  <span className="cursor-pointer select-none">
                    {config.name}
                  </span>
                  <div className="ml-auto flex items-center gap-0.5 -mr-1">
                    {isExtensionsCategory && canOperateRuntime && (
                      <button
                        type="button"
                        title={t('common.refresh', '刷新')}
                        className="p-1 rounded-sm text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [@media(hover:hover)]:opacity-0 group-hover/category-header:opacity-100 transition-all"
                        onClick={handleRefreshExtensions}
                      >
                        <RefreshCcw
                          className={cn(
                            'size-3.5',
                            extRefreshing && 'animate-spin',
                          )}
                        />
                      </button>
                    )}
                    {canCreate && (
                      <WorkspaceQuotaTooltip
                        quota={quota}
                        resource={config.name}
                        side="right"
                      >
                        {isPlugin ? (
                          <DropdownMenu>
                            <DropdownMenuTrigger asChild>
                              <button
                                type="button"
                                disabled={quota.disabled}
                                aria-disabled={quota.disabled}
                                aria-label={`${t('common.create')} ${config.name}`}
                                className="p-1 rounded-sm text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [@media(hover:hover)]:opacity-0 group-hover/category-header:opacity-100 transition-all disabled:pointer-events-none disabled:opacity-40"
                                onClick={(e) => e.stopPropagation()}
                              >
                                <Plus className="size-3.5" />
                              </button>
                            </DropdownMenuTrigger>
                            <DropdownMenuContent align="end">
                              {systemInfo.enable_marketplace && (
                                <DropdownMenuItem
                                  onClick={(e) => {
                                    e.stopPropagation();
                                    navigate('/home/add-extension');
                                  }}
                                >
                                  <Store className="size-4" />
                                  {t('plugins.goToMarketplace')}
                                </DropdownMenuItem>
                              )}
                              <DropdownMenuItem
                                onClick={(e) => {
                                  e.stopPropagation();
                                  navigate('/home/add-extension?manual=1');
                                }}
                              >
                                <Upload className="size-4" />
                                {t('plugins.uploadLocal')}
                              </DropdownMenuItem>
                              <DropdownMenuItem
                                onClick={(e) => {
                                  e.stopPropagation();
                                  navigate('/home/add-extension?manual=1');
                                }}
                              >
                                <Github className="size-4" />
                                {t('plugins.installFromGithub')}
                              </DropdownMenuItem>
                            </DropdownMenuContent>
                          </DropdownMenu>
                        ) : isSkill ? (
                          <DropdownMenu>
                            <DropdownMenuTrigger asChild>
                              <button
                                type="button"
                                disabled={quota.disabled}
                                aria-disabled={quota.disabled}
                                aria-label={`${t('common.create')} ${config.name}`}
                                className="p-1 rounded-sm text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [@media(hover:hover)]:opacity-0 group-hover/category-header:opacity-100 transition-all disabled:pointer-events-none disabled:opacity-40"
                                onClick={(e) => e.stopPropagation()}
                              >
                                <Plus className="size-3.5" />
                              </button>
                            </DropdownMenuTrigger>
                            <DropdownMenuContent align="end">
                              <DropdownMenuItem
                                onClick={(e) => {
                                  e.stopPropagation();
                                  navigate('/home/skills?action=create');
                                }}
                              >
                                <FilePlus2 className="size-4" />
                                {t('skills.createManually')}
                              </DropdownMenuItem>
                              <DropdownMenuItem
                                onClick={(e) => {
                                  e.stopPropagation();
                                  navigate('/home/add-extension?manual=1');
                                }}
                              >
                                <Upload className="size-4" />
                                {t('skills.uploadZip')}
                              </DropdownMenuItem>
                              <DropdownMenuItem
                                onClick={(e) => {
                                  e.stopPropagation();
                                  navigate('/home/add-extension?manual=1');
                                }}
                              >
                                <Github className="size-4" />
                                {t('skills.importFromGithub')}
                              </DropdownMenuItem>
                            </DropdownMenuContent>
                          </DropdownMenu>
                        ) : (
                          <button
                            type="button"
                            disabled={quota.disabled}
                            aria-disabled={quota.disabled}
                            aria-label={`${t('common.create')} ${config.name}`}
                            className="p-1 rounded-sm text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [@media(hover:hover)]:opacity-0 group-hover/category-header:opacity-100 transition-all disabled:pointer-events-none disabled:opacity-40"
                            onClick={(e) => {
                              e.stopPropagation();
                              navigate(`${routePrefix}?id=new`);
                            }}
                          >
                            <Plus className="size-3.5" />
                          </button>
                        )}
                      </WorkspaceQuotaTooltip>
                    )}
                    <CollapsibleTrigger asChild>
                      <button
                        type="button"
                        className="p-1 rounded-sm hover:bg-sidebar-accent"
                        onClick={(e) => e.stopPropagation()}
                      >
                        <ChevronRight className="size-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
                      </button>
                    </CollapsibleTrigger>
                  </div>
                </div>
              </SidebarMenuButton>
              <CollapsibleContent>
                <SidebarMenuSub>{renderEntityList(false)}</SidebarMenuSub>
              </CollapsibleContent>
            </SidebarMenuItem>
          </Collapsible>
        );
      })}

      {/* Plugin operation confirmation dialog */}
      <Dialog
        open={showPluginOpModal}
        onOpenChange={(open) => {
          if (!open) {
            setShowPluginOpModal(false);
            setTargetPluginItem(null);
            asyncTask.reset();
          }
        }}
      >
        <DialogContent>
          <DialogHeader>
            <DialogTitle>
              {pluginOpType === PluginOperationType.DELETE
                ? t('plugins.deleteConfirm')
                : t('plugins.updateConfirm')}
            </DialogTitle>
          </DialogHeader>
          <DialogDescription>
            {asyncTask.status === AsyncTaskStatus.WAIT_INPUT && (
              <div className="flex flex-col gap-4">
                <div>
                  {(() => {
                    const slashIdx = targetPluginItem?.id.indexOf('/') ?? -1;
                    const author =
                      slashIdx >= 0
                        ? targetPluginItem!.id.substring(0, slashIdx)
                        : '';
                    const name =
                      slashIdx >= 0
                        ? targetPluginItem!.id.substring(slashIdx + 1)
                        : (targetPluginItem?.id ?? '');
                    return pluginOpType === PluginOperationType.DELETE
                      ? t('plugins.confirmDeletePlugin', { author, name })
                      : t('plugins.confirmUpdatePlugin', { author, name });
                  })()}
                </div>
                {pluginOpType === PluginOperationType.DELETE && (
                  <div className="flex items-center space-x-2">
                    <Checkbox
                      id="sidebar-delete-data"
                      checked={deleteData}
                      onCheckedChange={(checked) =>
                        setDeleteData(checked === true)
                      }
                    />
                    <label
                      htmlFor="sidebar-delete-data"
                      className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
                    >
                      {t('plugins.deleteDataCheckbox')}
                    </label>
                  </div>
                )}
              </div>
            )}
            {asyncTask.status === AsyncTaskStatus.RUNNING && (
              <div>
                {pluginOpType === PluginOperationType.DELETE
                  ? t('plugins.deleting')
                  : t('plugins.updating')}
              </div>
            )}
            {asyncTask.status === AsyncTaskStatus.ERROR && (
              <div>
                {pluginOpType === PluginOperationType.DELETE
                  ? t('plugins.deleteError')
                  : t('plugins.updateError')}
                <div className="text-red-500">{asyncTask.error}</div>
              </div>
            )}
          </DialogDescription>
          <DialogFooter>
            {asyncTask.status === AsyncTaskStatus.WAIT_INPUT && (
              <Button
                variant="outline"
                onClick={() => {
                  setShowPluginOpModal(false);
                  setTargetPluginItem(null);
                  asyncTask.reset();
                }}
              >
                {t('common.cancel')}
              </Button>
            )}
            {asyncTask.status === AsyncTaskStatus.WAIT_INPUT && (
              <Button
                variant={
                  pluginOpType === PluginOperationType.DELETE
                    ? 'destructive'
                    : 'default'
                }
                onClick={executePluginOperation}
              >
                {pluginOpType === PluginOperationType.DELETE
                  ? t('plugins.confirmDelete')
                  : t('plugins.confirmUpdate')}
              </Button>
            )}
            {asyncTask.status === AsyncTaskStatus.RUNNING && (
              <Button
                variant={
                  pluginOpType === PluginOperationType.DELETE
                    ? 'destructive'
                    : 'default'
                }
                disabled
              >
                {pluginOpType === PluginOperationType.DELETE
                  ? t('plugins.deleting')
                  : t('plugins.updating')}
              </Button>
            )}
            {asyncTask.status === AsyncTaskStatus.ERROR && (
              <Button
                variant="default"
                onClick={() => {
                  setShowPluginOpModal(false);
                  asyncTask.reset();
                }}
              >
                {t('plugins.close')}
              </Button>
            )}
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </>
  );
}

// Dropdown menu for plugin sidebar sub-items (shown on hover)
function PluginItemMenu({
  item,
  canManage,
  onUpdate,
  onDelete,
}: {
  item: SidebarEntityItem;
  canManage: boolean;
  onUpdate: () => void;
  onDelete: () => void;
}) {
  const { t } = useTranslation();
  const [open, setOpen] = useState(false);

  const isMarketplace = item.installSource === 'marketplace';
  const isGithub = item.installSource === 'github';
  const hasSourceLink = isMarketplace || isGithub;

  if (!canManage && !hasSourceLink) return null;

  function handleViewSource() {
    const slashIdx = item.id.indexOf('/');
    const author = slashIdx >= 0 ? item.id.substring(0, slashIdx) : '';
    const name = slashIdx >= 0 ? item.id.substring(slashIdx + 1) : item.id;

    if (isGithub && item.installInfo?.github_url) {
      window.open(item.installInfo.github_url as string, '_blank');
    } else if (isMarketplace) {
      window.open(
        getCloudServiceClientSync().getPluginMarketplaceURL(
          systemInfo.cloud_service_url,
          author,
          name,
        ),
        '_blank',
      );
    }
  }

  return (
    <DropdownMenu open={open} onOpenChange={setOpen}>
      <DropdownMenuTrigger asChild>
        <button
          type="button"
          className={`absolute right-1 top-1/2 -translate-y-1/2 rounded-sm p-0.5 text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground ${
            open
              ? 'opacity-100'
              : item.hasUpdate
                ? 'opacity-100'
                : 'opacity-0 group-hover/plugin-item:opacity-100'
          } transition-opacity`}
          onClick={(e) => e.stopPropagation()}
        >
          <Ellipsis className="size-4" />
          {item.hasUpdate && !open && (
            <span className="absolute -top-0.5 -right-0.5 size-2 rounded-full bg-red-500" />
          )}
        </button>
      </DropdownMenuTrigger>
      <DropdownMenuContent side="right" align="start">
        {canManage && isMarketplace && (
          <DropdownMenuItem
            className="cursor-pointer"
            onClick={() => {
              onUpdate();
              setOpen(false);
            }}
          >
            <ArrowUp className="size-4" />
            <span>{t('plugins.update')}</span>
            {item.hasUpdate && (
              <Badge className="ml-auto bg-red-500 hover:bg-red-500 text-white text-[0.6rem] px-1.5 py-0 h-4">
                {t('plugins.new')}
              </Badge>
            )}
          </DropdownMenuItem>
        )}
        {hasSourceLink && (
          <DropdownMenuItem
            className="cursor-pointer"
            onClick={() => {
              handleViewSource();
              setOpen(false);
            }}
          >
            <ExternalLink className="size-4" />
            <span>{t('plugins.viewSource')}</span>
          </DropdownMenuItem>
        )}
        {canManage && (
          <DropdownMenuItem
            className="cursor-pointer text-red-600 focus:text-red-600"
            onClick={() => {
              onDelete();
              setOpen(false);
            }}
          >
            <Trash className="size-4" />
            <span>{t('plugins.delete')}</span>
          </DropdownMenuItem>
        )}
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

// Plugin pages navigation section — grouped by plugin
function PluginPagesNav() {
  const { pluginPages } = useSidebarData();
  const navigate = useNavigate();
  const location = useLocation();
  const [searchParams] = useSearchParams();
  const { t } = useTranslation();

  if (pluginPages.length === 0) return null;

  const pathname = location.pathname;
  const currentId =
    pathname === '/home/plugin-pages' ? searchParams.get('id') : null;

  // Group pages by plugin (author/name)
  const grouped = new Map<
    string,
    { label: string; iconURL: string; pages: typeof pluginPages }
  >();
  for (const page of pluginPages) {
    const key = `${page.pluginAuthor}/${page.pluginName}`;
    if (!grouped.has(key)) {
      grouped.set(key, {
        label: page.pluginLabel,
        iconURL: page.pluginIconURL,
        pages: [],
      });
    }
    grouped.get(key)!.pages.push(page);
  }

  return (
    <SidebarGroup>
      <SidebarGroupLabel title={t('sidebar.pluginPagesTooltip')}>
        {t('sidebar.pluginPages')}
      </SidebarGroupLabel>
      <SidebarGroupContent>
        <SidebarMenu>
          {Array.from(grouped.entries()).map(
            ([pluginKey, { label, iconURL, pages }]) => {
              const hasActivePage = pages.some((p) => p.id === currentId);

              const pluginIcon = (
                <img
                  src={iconURL}
                  alt=""
                  className="size-4 rounded-sm object-cover shrink-0"
                  onError={(e) => {
                    (e.target as HTMLImageElement).style.display = 'none';
                  }}
                />
              );

              // Single page — render directly without nesting
              if (pages.length === 1) {
                const page = pages[0];
                const isActive = currentId === page.id;
                const route = `/home/plugin-pages?id=${encodeURIComponent(page.id)}`;
                return (
                  <SidebarMenuItem key={page.id}>
                    <SidebarMenuButton
                      isActive={isActive}
                      tooltip={page.name}
                      onClick={() => navigate(route)}
                      className="select-none"
                    >
                      {pluginIcon}
                      <span className="cursor-pointer">{page.name}</span>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                );
              }

              // Multiple pages — collapsible group
              return (
                <Collapsible
                  key={pluginKey}
                  defaultOpen={hasActivePage}
                  className="group/collapsible"
                >
                  <SidebarMenuItem>
                    <CollapsibleTrigger asChild>
                      <SidebarMenuButton
                        tooltip={label}
                        className="select-none"
                      >
                        {pluginIcon}
                        <span className="cursor-pointer">{label}</span>
                        <ChevronRight className="ml-auto size-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
                      </SidebarMenuButton>
                    </CollapsibleTrigger>
                    <CollapsibleContent>
                      <SidebarMenuSub>
                        {pages.map((page) => {
                          const isActive = currentId === page.id;
                          const route = `/home/plugin-pages?id=${encodeURIComponent(page.id)}`;
                          return (
                            <SidebarMenuSubItem key={page.id}>
                              <SidebarMenuSubButton
                                isActive={isActive}
                                onClick={() => navigate(route)}
                                className="select-none"
                              >
                                <span className="cursor-pointer">
                                  {page.name}
                                </span>
                              </SidebarMenuSubButton>
                            </SidebarMenuSubItem>
                          );
                        })}
                      </SidebarMenuSub>
                    </CollapsibleContent>
                  </SidebarMenuItem>
                </Collapsible>
              );
            },
          )}
        </SidebarMenu>
      </SidebarGroupContent>
    </SidebarGroup>
  );
}

function findSidebarChildForPath(pathname: string): SidebarChildVO | undefined {
  const matchedChild =
    sidebarConfigList.find((childConfig) => childConfig.route === pathname) ||
    sidebarConfigList.find((childConfig) =>
      pathname.startsWith(childConfig.route + '/'),
    );
  if (matchedChild) return matchedChild;

  if (
    pathname === '/home/mcp' ||
    pathname === '/home/skills' ||
    pathname === '/home/plugin-pages' ||
    pathname.startsWith('/home/mcp/') ||
    pathname.startsWith('/home/skills/') ||
    pathname.startsWith('/home/plugin-pages/')
  ) {
    return sidebarConfigList.find(
      (childConfig) => childConfig.id === 'plugins',
    );
  }

  if (
    pathname === '/home/add-extension' ||
    pathname.startsWith('/home/add-extension/')
  ) {
    return sidebarConfigList.find(
      (childConfig) => childConfig.id === 'add-extension',
    );
  }

  return undefined;
}

export default function HomeSidebar({
  onSelectedChangeAction,
}: {
  onSelectedChangeAction: (sidebarChild: SidebarChildVO) => void;
}) {
  const navigate = useNavigate();
  const location = useLocation();
  const pathname = location.pathname;
  const [searchParams] = useSearchParams();
  const { isMobile } = useSidebar();

  useEffect(() => {
    handleRouteChange(pathname);
  }, [pathname]);

  useEffect(() => {
    const action = searchParams.get('action');
    if (action && SETTINGS_SECTION_BY_ACTION[action]) {
      setSettingsSection(SETTINGS_SECTION_BY_ACTION[action]);
      setSettingsOpen(true);
    }
  }, [searchParams]);

  const [selectedChild, setSelectedChild] = useState<SidebarChildVO>();
  const [sectionOpenState, setSectionOpenState] =
    useState<Record<string, boolean>>(loadSectionState);
  const { theme, setTheme } = useTheme();
  const { t } = useTranslation();
  const currentWorkspace = useCurrentWorkspace();
  const workspaces = useWorkspaceBootstrap();
  const showWorkspaceSwitcher =
    workspaces.length > 1 ||
    currentWorkspace?.workspace.source === 'cloud_projection';
  const canViewStorageAnalysis =
    currentWorkspace?.workspace.source !== 'cloud_projection' &&
    currentWorkspace?.permissions.includes('audit.view');
  const [settingsOpen, setSettingsOpen] = useState(false);
  const [settingsSection, setSettingsSection] =
    useState<SettingsSection>('models');
  const [latestRelease, setLatestRelease] = useState<GitHubRelease | null>(
    null,
  );
  const [hasNewVersion, setHasNewVersion] = useState(false);
  const [versionDialogOpen, setVersionDialogOpen] = useState(false);
  const [feedbackOpen, setFeedbackOpen] = useState(false);
  const [userEmail, setUserEmail] = useState<string>('');
  const [starCount, setStarCount] = useState<number | null>(null);
  const [userMenuOpen, setUserMenuOpen] = useState(false);
  const navigationContentRef = useRef<HTMLDivElement | null>(null);
  const [showScrollHint, setShowScrollHint] = useState(false);

  function scrollNavigationToBottom() {
    const contentEl = navigationContentRef.current;
    if (!contentEl) return;

    const maxScrollTop = contentEl.scrollHeight - contentEl.clientHeight;
    contentEl.scrollTo({
      top: maxScrollTop,
      behavior: 'smooth',
    });
    setShowScrollHint(false);

    window.setTimeout(() => {
      if (contentEl.scrollTop < maxScrollTop - 2) {
        contentEl.scrollTop = maxScrollTop;
      }
      setShowScrollHint(false);
    }, 250);
  }
  function openSettings(section: SettingsSection) {
    setSettingsSection(section);
    setSettingsOpen(true);
    const params = new URLSearchParams(searchParams.toString());
    params.set('action', SETTINGS_ACTION_BY_SECTION[section]);
    navigate(`${pathname}?${params.toString()}`, {
      preventScrollReset: true,
    });
  }

  useEffect(() => {
    const openWorkspaceSettings = () => openSettings('workspace');
    window.addEventListener(
      OPEN_WORKSPACE_SETTINGS_EVENT,
      openWorkspaceSettings,
    );
    return () =>
      window.removeEventListener(
        OPEN_WORKSPACE_SETTINGS_EVENT,
        openWorkspaceSettings,
      );
  });

  function handleSettingsSectionChange(section: SettingsSection) {
    setSettingsSection(section);
    const params = new URLSearchParams(searchParams.toString());
    params.set('action', SETTINGS_ACTION_BY_SECTION[section]);
    navigate(`${pathname}?${params.toString()}`, {
      preventScrollReset: true,
    });
  }

  function handleSettingsOpenChange(open: boolean) {
    setSettingsOpen(open);
    if (!open) {
      const params = new URLSearchParams(searchParams.toString());
      params.delete('action');
      const newUrl = params.toString()
        ? `${pathname}?${params.toString()}`
        : pathname;
      navigate(newUrl, { preventScrollReset: true });
    }
  }

  useEffect(() => {
    initSelect();

    const storedEmail = localStorage.getItem('userEmail');
    if (storedEmail) {
      setUserEmail(storedEmail);
    } else {
      httpClient
        .getUserInfo()
        .then((info) => {
          setUserEmail(info.user);
          localStorage.setItem('userEmail', info.user);
        })
        .catch(() => {});
    }

    // Cloud edition is updated centrally by the operator, so end users should
    // not see a "new version available" prompt in the sidebar. Skip the GitHub
    // release check entirely for edition=cloud.
    if (systemInfo?.edition !== 'cloud') {
      getCloudServiceClientSync()
        .getLangBotReleases()
        .then((releases) => {
          if (releases && releases.length > 0) {
            const latestStable = releases.find(
              (r) => !r.prerelease && !r.draft,
            );
            const latest = latestStable || releases[0];
            setLatestRelease(latest);

            const currentVersion = systemInfo?.version;
            if (currentVersion && latest.tag_name) {
              const isNewer = compareVersions(latest.tag_name, currentVersion);
              setHasNewVersion(isNewer);
            }
          }
        })
        .catch((error) => {
          console.error('Failed to fetch releases:', error);
        });
    }

    getCloudServiceClientSync()
      .getGitHubRepoInfo()
      .then((info) => {
        if (info?.repo?.stargazers_count != null) {
          setStarCount(info.repo.stargazers_count);
        }
      })
      .catch(() => {});
  }, []);

  useEffect(() => {
    const contentEl = navigationContentRef.current;
    if (!contentEl) return;

    let animationFrame = 0;
    const updateScrollHint = () => {
      cancelAnimationFrame(animationFrame);
      animationFrame = requestAnimationFrame(() => {
        const hasHiddenContent =
          contentEl.scrollTop + contentEl.clientHeight <
          contentEl.scrollHeight - SCROLL_HINT_BOTTOM_THRESHOLD;
        setShowScrollHint(hasHiddenContent);
      });
    };

    updateScrollHint();
    contentEl.addEventListener('scroll', updateScrollHint, { passive: true });

    const resizeObserver = new ResizeObserver(updateScrollHint);
    resizeObserver.observe(contentEl);
    if (contentEl.firstElementChild) {
      resizeObserver.observe(contentEl.firstElementChild);
    }

    const mutationObserver = new MutationObserver(updateScrollHint);
    mutationObserver.observe(contentEl, {
      childList: true,
      subtree: true,
      attributes: true,
    });

    window.addEventListener('resize', updateScrollHint);

    return () => {
      cancelAnimationFrame(animationFrame);
      contentEl.removeEventListener('scroll', updateScrollHint);
      resizeObserver.disconnect();
      mutationObserver.disconnect();
      window.removeEventListener('resize', updateScrollHint);
    };
  }, []);

  // Update selected state + notify parent without navigating
  function selectChild(child: SidebarChildVO) {
    setSelectedChild(child);
    onSelectedChangeAction(child);
  }

  // Toggle collapsible section open/closed with localStorage persistence
  function handleSectionToggle(id: string, open: boolean) {
    setSectionOpenState((prev) => {
      const next = { ...prev, [id]: open };
      saveSectionState(next);
      return next;
    });
  }

  // User click: update state AND navigate
  function handleChildClick(child: SidebarChildVO) {
    selectChild(child);
    navigate(child.route);
  }

  function initSelect() {
    const currentPath = pathname;
    const matchedChild = findSidebarChildForPath(currentPath);
    if (matchedChild) {
      // Route already matches — just select without navigating (preserves ?id= query params)
      selectChild(matchedChild);
    } else {
      // No match — redirect to the first route under /home
      const defaultChild =
        sidebarConfigList.find((c) => c.route.startsWith('/home')) ??
        sidebarConfigList[0];
      handleChildClick(defaultChild);
    }
  }

  function handleRouteChange(pathname: string) {
    if (!pathname.startsWith('/home')) return;
    const routeSelectChild = findSidebarChildForPath(pathname);
    if (routeSelectChild) {
      setSelectedChild(routeSelectChild);
      onSelectedChangeAction(routeSelectChild);
    }
  }

  function handleLogout() {
    clearUserInfo();
    localStorage.removeItem('token');
    localStorage.removeItem('userEmail');
    window.location.href = '/login';
  }

  // Get the initial letter for user avatar
  const userInitial = userEmail ? userEmail.charAt(0).toUpperCase() : 'U';

  return (
    <>
      <Sidebar variant="inset" collapsible="icon">
        {/* Header: Logo using sidebar-07 team-switcher pattern */}
        <SidebarHeader>
          <SidebarMenu>
            <SidebarMenuItem>
              <SidebarMenuButton
                size="lg"
                className="cursor-default hover:bg-transparent active:bg-transparent"
                tooltip="LangBot"
              >
                <img
                  src={langbotIcon}
                  alt="LangBot"
                  className="size-8 rounded-lg"
                />
                <div className="grid flex-1 text-left text-sm leading-tight">
                  <div className="flex items-center gap-1.5">
                    <span className="truncate font-semibold">LangBot</span>
                    <Badge
                      variant="secondary"
                      className={`shrink-0 px-1 py-0 h-3.5 text-[0.55rem] font-medium ${
                        systemInfo?.edition === 'cloud'
                          ? 'border-transparent bg-blue-500 text-white'
                          : ''
                      }`}
                    >
                      {systemInfo?.edition === 'cloud'
                        ? t('sidebar.editionCloud')
                        : t('sidebar.editionCommunity')}
                    </Badge>
                  </div>
                  <div className="flex items-center gap-1.5">
                    <span className="truncate text-xs text-muted-foreground">
                      {systemInfo?.version}
                    </span>
                    {hasNewVersion && (
                      <Badge
                        onClick={() => setVersionDialogOpen(true)}
                        className="bg-red-500 hover:bg-red-600 text-white text-[0.55rem] px-1 py-0 h-3.5 cursor-pointer"
                      >
                        {t('plugins.new')}
                      </Badge>
                    )}
                  </div>
                </div>
              </SidebarMenuButton>
            </SidebarMenuItem>
          </SidebarMenu>
        </SidebarHeader>

        {showWorkspaceSwitcher && (
          <div className="px-2 group-data-[collapsible=icon]:px-0">
            <WorkspaceSwitcher className="w-full group-data-[collapsible=icon]:min-w-0 group-data-[collapsible=icon]:px-2" />
          </div>
        )}

        {/* Navigation items grouped by section */}
        <div className="relative flex min-h-0 flex-1 flex-col overflow-hidden">
          <SidebarContent ref={navigationContentRef} className="min-h-0 pb-8">
            <SidebarGroup>
              <SidebarGroupLabel>{t('sidebar.home')}</SidebarGroupLabel>
              <SidebarGroupContent>
                <SidebarMenu>
                  <NavItems
                    selectedChild={selectedChild}
                    onChildClick={handleChildClick}
                    section="home"
                    sectionOpenState={sectionOpenState}
                    onSectionToggle={handleSectionToggle}
                  />
                </SidebarMenu>
              </SidebarGroupContent>
            </SidebarGroup>
            <SidebarGroup>
              <SidebarGroupLabel>{t('sidebar.extensions')}</SidebarGroupLabel>
              <SidebarGroupContent>
                <SidebarMenu>
                  <NavItems
                    selectedChild={selectedChild}
                    onChildClick={handleChildClick}
                    section="extensions"
                    sectionOpenState={sectionOpenState}
                    onSectionToggle={handleSectionToggle}
                  />
                </SidebarMenu>
              </SidebarGroupContent>
            </SidebarGroup>
            <PluginPagesNav />
          </SidebarContent>
          <button
            type="button"
            onClick={scrollNavigationToBottom}
            disabled={!showScrollHint}
            aria-label={t('sidebar.scrollToBottom')}
            aria-hidden={!showScrollHint}
            tabIndex={showScrollHint ? 0 : -1}
            className={cn(
              'absolute inset-x-0 bottom-2 z-10 mx-auto flex w-fit justify-center rounded-full transition-opacity duration-200 group-data-[collapsible=icon]:hidden',
              showScrollHint
                ? 'pointer-events-auto opacity-100'
                : 'pointer-events-none opacity-0',
            )}
          >
            <span className="flex size-7 items-center justify-center rounded-full border border-sidebar-border bg-sidebar/95 text-sidebar-foreground/70 shadow-sm backdrop-blur transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground">
              <ChevronDown className="size-4" />
            </span>
          </button>
        </div>

        {/* Footer */}
        <SidebarFooter>
          {/* Models entry */}
          <SidebarMenu>
            <SidebarMenuItem>
              <SidebarMenuButton
                onClick={() => openSettings('models')}
                tooltip={t('models.title')}
              >
                <Sparkles className="text-blue-500" />
                <span>{t('models.title')}</span>
              </SidebarMenuButton>
            </SidebarMenuItem>
          </SidebarMenu>

          {/* API-key management is available only to authorized Workspace roles. */}
          {currentWorkspace?.permissions.includes('api_key.manage') && (
            <SidebarMenu>
              <SidebarMenuItem>
                <SidebarMenuButton
                  onClick={() => openSettings('apiIntegration')}
                  tooltip={t('common.apiIntegration')}
                >
                  <KeyRound className="size-4 text-blue-500" />
                  <span>{t('common.apiIntegration')}</span>
                </SidebarMenuButton>
              </SidebarMenuItem>
            </SidebarMenu>
          )}

          {/* User menu using sidebar-07 nav-user DropdownMenu pattern */}
          <SidebarMenu>
            <SidebarMenuItem>
              <DropdownMenu open={userMenuOpen} onOpenChange={setUserMenuOpen}>
                <DropdownMenuTrigger asChild>
                  <SidebarMenuButton
                    size="lg"
                    className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
                    tooltip={t('common.accountOptions')}
                  >
                    <Avatar className="h-8 w-8 rounded-lg">
                      <AvatarFallback className="rounded-lg bg-primary text-primary-foreground text-xs">
                        {userInitial}
                      </AvatarFallback>
                    </Avatar>
                    <div className="grid flex-1 text-left text-sm leading-tight">
                      <span className="truncate font-medium">
                        {userEmail || t('common.accountOptions')}
                      </span>
                    </div>
                    <ChevronsUpDown className="ml-auto size-4" />
                  </SidebarMenuButton>
                </DropdownMenuTrigger>
                <DropdownMenuContent
                  className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
                  side={isMobile ? 'bottom' : 'right'}
                  align="end"
                  sideOffset={4}
                >
                  {/* User info header */}
                  <DropdownMenuLabel className="p-0 font-normal">
                    <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
                      <Avatar className="h-8 w-8 rounded-lg">
                        <AvatarFallback className="rounded-lg bg-primary text-primary-foreground text-xs">
                          {userInitial}
                        </AvatarFallback>
                      </Avatar>
                      <div className="grid flex-1 text-left text-sm leading-tight">
                        <span className="truncate font-medium">
                          {userEmail || t('common.accountOptions')}
                        </span>
                      </div>
                    </div>
                  </DropdownMenuLabel>
                  <DropdownMenuSeparator />

                  {/* Language & Theme row */}
                  <div className="flex items-center gap-2 px-1 py-1">
                    <LanguageSelector triggerClassName="flex-1" />
                    <Button
                      variant="outline"
                      size="icon"
                      onClick={() =>
                        setTheme(
                          theme === 'light'
                            ? 'dark'
                            : theme === 'dark'
                              ? 'system'
                              : 'light',
                        )
                      }
                      className="h-9 w-9 shrink-0"
                    >
                      {theme === 'light' && (
                        <Sun className="h-[1.2rem] w-[1.2rem]" />
                      )}
                      {theme === 'dark' && (
                        <Moon className="h-[1.2rem] w-[1.2rem]" />
                      )}
                      {theme === 'system' && (
                        <Monitor className="h-[1.2rem] w-[1.2rem]" />
                      )}
                    </Button>
                  </div>
                  <DropdownMenuSeparator />

                  {/* Account actions */}
                  <DropdownMenuGroup>
                    <DropdownMenuItem
                      onClick={() => {
                        setUserMenuOpen(false);
                        openSettings('account');
                      }}
                    >
                      <Settings />
                      {t('account.settings')}
                    </DropdownMenuItem>
                    <DropdownMenuItem
                      onClick={() => {
                        setUserMenuOpen(false);
                        openSettings('workspace');
                      }}
                    >
                      <UsersRound />
                      {t('workspace.settings')}
                    </DropdownMenuItem>
                    {canViewStorageAnalysis && (
                      <DropdownMenuItem
                        onClick={() => {
                          setUserMenuOpen(false);
                          openSettings('storageAnalysis');
                        }}
                      >
                        {t('storageAnalysis.title')}
                      </DropdownMenuItem>
                    )}
                    <DropdownMenuItem
                      onClick={() => {
                        setUserMenuOpen(false);
                        navigate('/wizard');
                      }}
                    >
                      <Zap className="text-blue-500" />
                      {t('sidebar.quickStart')}
                    </DropdownMenuItem>
                  </DropdownMenuGroup>
                  <DropdownMenuSeparator />

                  {/* External links */}
                  <DropdownMenuGroup>
                    <DropdownMenuItem
                      onClick={() => {
                        const language =
                          localStorage.getItem('langbot_language');
                        if (language === 'zh-Hans' || language === 'zh-Hant') {
                          window.open(
                            'https://link.langbot.app/zh/docs/guide',
                            '_blank',
                          );
                        } else {
                          window.open(
                            'https://link.langbot.app/en/docs/guide',
                            '_blank',
                          );
                        }
                      }}
                    >
                      <CircleHelp />
                      {t('common.helpDocs')}
                    </DropdownMenuItem>
                    <DropdownMenuItem
                      onClick={() => {
                        setUserMenuOpen(false);
                        setFeedbackOpen(true);
                      }}
                    >
                      <Lightbulb />
                      {t('common.featureRequest')}
                    </DropdownMenuItem>
                    <DropdownMenuItem
                      onClick={() => {
                        window.open(
                          'https://github.com/langbot-app/LangBot',
                          '_blank',
                        );
                      }}
                    >
                      <Star
                        className={cn(
                          'text-yellow-500',
                          userMenuOpen && 'animate-twinkle',
                        )}
                      />
                      <span className="flex-1">{t('common.starOnGitHub')}</span>
                      {starCount != null && (
                        <Badge variant="secondary" className="ml-auto text-xs">
                          {starCount >= 1000
                            ? `${(starCount / 1000).toFixed(1)}k`
                            : starCount}
                        </Badge>
                      )}
                    </DropdownMenuItem>
                    <DropdownMenuItem
                      onClick={() => {
                        window.open('https://discord.gg/wdNEHETs87', '_blank');
                      }}
                    >
                      <DiscordIcon className="text-[#5865F2]" />
                      {t('common.joinDiscord')}
                    </DropdownMenuItem>
                  </DropdownMenuGroup>
                  <DropdownMenuSeparator />

                  {/* Logout */}
                  <DropdownMenuItem onClick={() => handleLogout()}>
                    <LogOut />
                    {t('common.logout')}
                  </DropdownMenuItem>
                </DropdownMenuContent>
              </DropdownMenu>
            </SidebarMenuItem>
          </SidebarMenu>
        </SidebarFooter>
      </Sidebar>

      <Dialog open={feedbackOpen} onOpenChange={setFeedbackOpen}>
        <DialogContent className="w-[calc(100vw-2rem)] sm:max-w-[380px]">
          <DialogHeader className="sr-only">
            <DialogTitle>{t('monitoring.feedback.title')}</DialogTitle>
            <DialogDescription>
              {t('monitoring.feedback.description')}
            </DialogDescription>
          </DialogHeader>
          <FeedbackPopoverContent onSubmitted={() => setFeedbackOpen(false)} />
        </DialogContent>
      </Dialog>

      <SettingsDialog
        open={settingsOpen}
        onOpenChange={handleSettingsOpenChange}
        section={settingsSection}
        onSectionChange={handleSettingsSectionChange}
      />
      <NewVersionDialog
        open={versionDialogOpen}
        onOpenChange={setVersionDialogOpen}
        release={latestRelease}
      />
    </>
  );
}
