import { useCallback, useEffect, useMemo, useState, useRef, memo } from 'react';
import { Command } from 'cmdk';
import * as Popover from '@radix-ui/react-popover';
import * as Dialog from '@radix-ui/react-dialog';
import { RefreshCw, Search, X, ChevronRight, ChevronDown, Plus, Trash2 } from 'lucide-react';
import type {
  ModelDownloadStatus,
  ModelEntry,
  ProviderCapabilityEntry,
  RecentModelEntry,
  RemoteNodeInfo,
  RoutingMode,
  UiAgentInfo,
} from '../types';
import { useUiStore } from '../store/uiStore';

/* ------------------------------------------------------------------ */
/*  Types                                                              */
/* ------------------------------------------------------------------ */

interface ModelPickerPopoverProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  isInMobileMenu?: boolean;
  connected: boolean;
  routingMode: RoutingMode;
  activeAgentId: string;
  sessionId: string | null;
  sessionsByAgent: Record<string, string>;
  agents: UiAgentInfo[];
  allModels: ModelEntry[];
  modelsRefreshing?: boolean;
  modelsLoadedOnce?: boolean;
  currentProvider?: string;
  currentModel?: string;
  /** Mesh node running the current provider, if any. Absent for local providers. */
  currentNode?: string;
  /** Known remote nodes — used to resolve a human-readable label for currentNode. */
  remoteNodes?: RemoteNodeInfo[];
  currentWorkspace: string | null;
  recentModelsByWorkspace: Record<string, RecentModelEntry[]>;
  agentMode: string;
  reasoningEffort: string | null;
  onRefresh: () => void;
  onSetSessionModel: (sessionId: string, modelId: string, node?: string) => void;
  onSetReasoningEffort: (effort: string | null) => void;
  onCycleReasoningEffort: () => void;
  providerCapabilities: Record<string, ProviderCapabilityEntry>;
  modelDownloads: Record<string, ModelDownloadStatus>;
  onAddCustomModelFromHf: (provider: string, repo: string, filename: string, displayName?: string) => void;
  onAddCustomModelFromFile: (provider: string, filePath: string, displayName?: string) => void;
  onDeleteCustomModel: (provider: string, modelId: string) => void;
}

const TARGET_ACTIVE = 'active';
const TARGET_ALL = 'all';

/* ------------------------------------------------------------------ */
/*  Helpers                                                            */
/* ------------------------------------------------------------------ */

const RECENT_PREFIX = 'recent:';
const normalizeValue = (value: string) =>
  value.startsWith(RECENT_PREFIX) ? value.slice(RECENT_PREFIX.length) : value;

interface GroupedModels {
  /** Display heading — "provider" for local, "provider (node)" for remote */
  heading: string;
  provider: string;
  /** Stable node id (PeerId) — used as identity for backend requests. */
  node?: string;
  /** Human-readable node label — used for display badges. */
  nodeLabel?: string;
  models: { value: string; display: string; id?: string; source?: string }[];
}

const localeCompare = new Intl.Collator(undefined, { sensitivity: 'base' }).compare;

/** Build a unique group key that separates local vs. remote providers. */
function groupKey(entry: ModelEntry): string {
  return entry.node_id ? `${entry.provider}@${entry.node_id}` : entry.provider;
}

function groupByProvider(models: ModelEntry[]): GroupedModels[] {
  const hasRemote = models.some((m) => m.node_id);
  const map = new Map<string, GroupedModels>();
  for (const entry of models) {
    const key = groupKey(entry);
    if (!map.has(key)) {
      const nodeDisplay = entry.node_label ?? entry.node_id;
      const heading = nodeDisplay
        ? `${entry.provider} (${nodeDisplay})`
        : hasRemote
          ? `${entry.provider} (local)`
          : entry.provider;
      map.set(key, { heading, provider: entry.provider, node: entry.node_id, nodeLabel: nodeDisplay, models: [] });
    }
    map.get(key)!.models.push({
      value: entry.model,
      display: entry.label ?? entry.model,
      id: entry.id,
      source: entry.source,
    });
  }
  return Array.from(map.values())
    .sort((a, b) => localeCompare(a.heading, b.heading))
    .map((g) => ({
      ...g,
      models: [...g.models].sort((a, b) => localeCompare(a.display, b.display)),
    }));
}

/** Build the cmdk item value for a model entry: "provider/model" or "provider@node/model". */
function itemValue(provider: string, model: string, node?: string): string {
  return node ? `${provider}@${node}/${model}` : `${provider}/${model}`;
}

function sessionModelId(provider: string, model: string, id?: string): string {
  const canonical = id ?? model;
  // If id already starts with "provider/", pass it through as-is to avoid double-prefixing.
  if (canonical.startsWith(`${provider}/`)) return canonical;
  return `${provider}/${canonical}`;
}

/* ------------------------------------------------------------------ */
/*  Trigger label helpers — no allModels scan needed                   */
/* ------------------------------------------------------------------ */

/** Stable platform detection — computed once, not per-render. */
const isMacPlatform = navigator.platform.includes('Mac');
const shortcutHint = isMacPlatform ? '⌘⇧M' : 'Ctrl+Shift+M';

function makeTriggerLabel(currentProvider?: string, currentModel?: string): string {
  return currentProvider && currentModel
    ? `${currentProvider} / ${currentModel}`
    : 'Select model';
}

function makeTriggerTitle(currentProvider?: string, currentModel?: string, currentNodeLabel?: string): string {
  return currentProvider && currentModel
    ? `${currentProvider} / ${currentModel}${currentNodeLabel ? ` on ${currentNodeLabel}` : ''} (${shortcutHint} to open)`
    : `Select model (${shortcutHint})`;
}

/** Short label for the reasoning effort badge on the trigger button. */
function effortBadgeLabel(effort: string | null): string {
  switch (effort) {
    case 'low': return 'Lo';
    case 'medium': return 'Med';
    case 'high': return 'Hi';
    case 'max': return 'Max';
    default: return 'Auto';
  }
}

/* ------------------------------------------------------------------ */
/*  ModelPickerPanel — heavy panel, only mounted when open            */
/* ------------------------------------------------------------------ */

interface ModelPickerPanelProps {
  connected: boolean;
  routingMode: RoutingMode;
  sessionId: string | null;
  sessionsByAgent: Record<string, string>;
  agents: UiAgentInfo[];
  allModels: ModelEntry[];
  modelsRefreshing?: boolean;
  modelsLoadedOnce?: boolean;
  currentProvider?: string;
  currentModel?: string;
  currentNode?: string;
  currentWorkspace: string | null;
  recentModelsByWorkspace: Record<string, RecentModelEntry[]>;
  agentMode: string;
  reasoningEffort: string | null;
  onRefresh: () => void;
  onSetSessionModel: (sessionId: string, modelId: string, node?: string) => void;
  onSetReasoningEffort: (effort: string | null) => void;
  providerCapabilities: Record<string, ProviderCapabilityEntry>;
  modelDownloads: Record<string, ModelDownloadStatus>;
  onAddCustomModelFromHf: (provider: string, repo: string, filename: string, displayName?: string) => void;
  onAddCustomModelFromFile: (provider: string, filePath: string, displayName?: string) => void;
  onDeleteCustomModel: (provider: string, modelId: string) => void;
  onOpenChange: (open: boolean) => void;
  isInMobileMenu: boolean;
  inputRef: React.RefObject<HTMLInputElement | null>;
  commandListRef: React.RefObject<HTMLDivElement | null>;
}

const ModelPickerPanel = memo(function ModelPickerPanel({
  connected,
  routingMode,
  sessionId,
  sessionsByAgent,
  agents,
  allModels,
  modelsRefreshing = false,
  modelsLoadedOnce = false,
  currentProvider,
  currentModel,
  currentNode,
  currentWorkspace,
  recentModelsByWorkspace,
  agentMode,
  reasoningEffort,
  onRefresh,
  onSetSessionModel,
  onSetReasoningEffort,
  providerCapabilities,
  modelDownloads,
  onAddCustomModelFromHf,
  onAddCustomModelFromFile,
  onDeleteCustomModel,
  onOpenChange,
  isInMobileMenu,
  inputRef,
  commandListRef,
}: ModelPickerPanelProps) {
  const [target, setTarget] = useState(TARGET_ACTIVE);
  const [isRefreshing, setIsRefreshing] = useState(false);
  const [selectedValue, setSelectedValue] = useState('');
  const [hfRepo, setHfRepo] = useState('');
  const [hfFilename, setHfFilename] = useState('');
  const [filePath, setFilePath] = useState('');
  const { setModeModelPreference } = useUiStore();

  // When the popover opens, auto-select the current model and scroll to top.
  useEffect(() => {
    if (currentProvider && currentModel) {
      const val = itemValue(currentProvider, currentModel, currentNode);
      setSelectedValue(currentNode ? val : `${RECENT_PREFIX}${val}`);
      setTimeout(() => {
        if (commandListRef.current) {
          commandListRef.current.scrollTop = 0;
        }
      }, 0);
    }
    // Reset on unmount (panel is unmounted when closed)
    return () => setSelectedValue('');
  }, [currentProvider, currentModel, currentNode, commandListRef]);

  // ---- Heavy memos — only run while panel is mounted (i.e. open) ----

  const targetAgents = useMemo(
    () => agents.filter((agent) => sessionsByAgent[agent.id]),
    [agents, sessionsByAgent],
  );

  const grouped = useMemo(() => groupByProvider(allModels), [allModels]);

  const selectedProvider = currentProvider ?? '';
  const selectedProviderCapabilities = providerCapabilities[selectedProvider];
  const canManageCustomModels = Boolean(
    selectedProvider && !currentNode && selectedProviderCapabilities?.supports_custom_models,
  );

  const selectedProviderEntry = useMemo(
    () => allModels.find((m) => m.provider === selectedProvider && m.model === currentModel && !m.node_id),
    [allModels, selectedProvider, currentModel],
  );

  const activeDownload = useMemo(() => {
    if (!selectedProvider) return undefined;
    const values = Object.values(modelDownloads).filter((d) => d.provider === selectedProvider);
    return values.sort((a, b) => (b.percent ?? 0) - (a.percent ?? 0))[0];
  }, [modelDownloads, selectedProvider]);

  // Build a lookup map so we can resolve provider/model/node from the cmdk value
  const modelMap = useMemo(() => {
    const m = new Map<string, { provider: string; model: string; node?: string; nodeLabel?: string }>();
    for (const entry of allModels) {
      m.set(itemValue(entry.provider, entry.model, entry.node_id), {
        provider: entry.provider,
        model: entry.model,
        node: entry.node_id,
        nodeLabel: entry.node_label ?? entry.node_id,
      });
    }
    return m;
  }, [allModels]);

  const modelIdByValue = useMemo(() => {
    const m = new Map<string, string>();
    for (const entry of allModels) {
      m.set(
        itemValue(entry.provider, entry.model, entry.node_id),
        sessionModelId(entry.provider, entry.model, entry.id),
      );
    }
    return m;
  }, [allModels]);

  // O(1) lookup set for available local models — avoids O(n*m) nested scan in recent filter
  const localModelKeySet = useMemo(() => {
    const s = new Set<string>();
    for (const m of allModels) {
      if (!m.node_id) s.add(`${m.provider}/${m.model}`);
    }
    return s;
  }, [allModels]);

  // Get recent models for current workspace from backend data
  const recentModels = useMemo(() => {
    const key = currentWorkspace ?? '';
    const recent = recentModelsByWorkspace[key] || [];
    return recent
      .filter(entry => localModelKeySet.has(`${entry.provider}/${entry.model}`))
      .slice(0, 5);
  }, [currentWorkspace, recentModelsByWorkspace, localModelKeySet]);

  const hasTargetSession =
    target === TARGET_ALL
      ? Object.values(sessionsByAgent).length > 0
      : target === TARGET_ACTIVE
        ? Boolean(sessionId)
        : Boolean(sessionsByAgent[target]);

  const selectedEntry = modelMap.get(normalizeValue(selectedValue));
  const canSwitch = connected && selectedEntry !== undefined && hasTargetSession;

  /* ---- actions ---- */

  const handleRefresh = useCallback(() => {
    setIsRefreshing(true);
    onRefresh();
    setTimeout(() => setIsRefreshing(false), 1000);
  }, [onRefresh]);

  const switchModel = useCallback(
    (value: string) => {
      const entry = modelMap.get(normalizeValue(value));
      if (!entry) return;

      const normalized = normalizeValue(value);
      const modelId = modelIdByValue.get(normalized) ?? `${entry.provider}/${entry.model}`;
      const sessionIds = new Set<string>();

      if (target === TARGET_ALL) {
        Object.values(sessionsByAgent).forEach((id) => sessionIds.add(id));
      } else if (target === TARGET_ACTIVE) {
        if (sessionId) sessionIds.add(sessionId);
      } else if (sessionsByAgent[target]) {
        sessionIds.add(sessionsByAgent[target]);
      }

      if (sessionIds.size === 0) return;
      sessionIds.forEach((id) => onSetSessionModel(id, modelId, entry.node));

      if (!entry.node) {
        setModeModelPreference(agentMode, entry.provider, entry.model);
      }

      onOpenChange(false);
    },
    [
      modelMap,
      modelIdByValue,
      target,
      sessionsByAgent,
      sessionId,
      onSetSessionModel,
      onOpenChange,
      agentMode,
      setModeModelPreference,
    ],
  );

  const handleSwitchClick = useCallback(() => {
    if (canSwitch) switchModel(selectedValue);
  }, [canSwitch, selectedValue, switchModel]);

  const handleAddHfModel = useCallback(() => {
    const repo = hfRepo.trim();
    const filename = hfFilename.trim();
    if (!repo || !filename || !selectedProvider) return;
    onAddCustomModelFromHf(selectedProvider, repo, filename, filename);
    setHfRepo('');
    setHfFilename('');
  }, [hfRepo, hfFilename, onAddCustomModelFromHf, selectedProvider]);

  const handleAddFileModel = useCallback(() => {
    const path = filePath.trim();
    if (!path || !selectedProvider) return;
    const label = path.split('/').pop() || path;
    onAddCustomModelFromFile(selectedProvider, path, label);
    setFilePath('');
  }, [filePath, onAddCustomModelFromFile, selectedProvider]);

  const handleDeleteCurrentCustom = useCallback(() => {
    const modelId = selectedProviderEntry?.id;
    if (!modelId || selectedProviderEntry?.source !== 'custom' || !selectedProvider) return;
    onDeleteCustomModel(selectedProvider, modelId);
  }, [onDeleteCustomModel, selectedProviderEntry, selectedProvider]);

  const CloseComponent = isInMobileMenu ? Dialog.Close : Popover.Close;

  return (
    <>
      {/* Header */}
      <div className="flex items-center justify-between px-3 py-2 border-b border-surface-border/60">
        <span className="text-xs font-semibold text-ui-secondary uppercase tracking-wider">
          Switch Model
        </span>
        <div className="flex items-center gap-2">
          <button
            type="button"
            onClick={handleRefresh}
            disabled={!connected || isRefreshing}
            className="p-1 rounded text-ui-secondary hover:text-accent-primary hover:bg-accent-primary/10 transition-colors disabled:opacity-50"
            title="Refresh model list"
          >
            <RefreshCw className={`h-3.5 w-3.5 ${isRefreshing ? 'animate-spin' : ''}`} />
          </button>
          <CloseComponent className="p-1 rounded text-ui-secondary hover:text-ui-primary hover:bg-surface-elevated/60 transition-colors">
            <X className="h-3.5 w-3.5" />
          </CloseComponent>
        </div>
      </div>

      {/* Target selector */}
      <div className="px-3 py-2 border-b border-surface-border/40">
        <div className="flex items-center gap-2 text-xs">
          <span className="text-[10px] uppercase tracking-widest text-ui-muted">Target</span>
          <select
            value={target}
            onChange={(e) => setTarget(e.target.value)}
            className="flex-1 rounded-lg border border-surface-border bg-surface-elevated/70 px-2 py-1 text-xs text-ui-primary focus:border-accent-primary focus:outline-none"
            disabled={!connected}
          >
            <option value={TARGET_ACTIVE}>Active agent</option>
            <option value={TARGET_ALL}>All agents</option>
            {targetAgents.map((agent) => (
              <option key={agent.id} value={agent.id}>
                {agent.name}
              </option>
            ))}
          </select>
        </div>
        {canManageCustomModels && (
          <div className="mt-2 pt-2 border-t border-surface-border/40 space-y-2">
            <div className="text-[10px] uppercase tracking-widest text-ui-muted">Add custom model ({selectedProvider})</div>
            <div className="flex items-center gap-2">
              <input
                value={hfRepo}
                onChange={(e) => setHfRepo(e.target.value)}
                placeholder="HF repo (owner/name-GGUF)"
                className="flex-1 rounded-lg border border-surface-border bg-surface-elevated/70 px-2 py-1 text-xs text-ui-primary"
              />
              <input
                value={hfFilename}
                onChange={(e) => setHfFilename(e.target.value)}
                placeholder="filename.gguf"
                className="flex-1 rounded-lg border border-surface-border bg-surface-elevated/70 px-2 py-1 text-xs text-ui-primary"
              />
              <button
                type="button"
                onClick={handleAddHfModel}
                className="px-2 py-1 rounded border border-surface-border text-ui-secondary hover:text-accent-primary"
                title="Add from Hugging Face"
              >
                <Plus className="h-3.5 w-3.5" />
              </button>
            </div>
            <div className="flex items-center gap-2">
              <input
                value={filePath}
                onChange={(e) => setFilePath(e.target.value)}
                placeholder="/absolute/path/to/model.gguf"
                className="flex-1 rounded-lg border border-surface-border bg-surface-elevated/70 px-2 py-1 text-xs text-ui-primary"
              />
              <button
                type="button"
                onClick={handleAddFileModel}
                className="px-2 py-1 rounded border border-surface-border text-ui-secondary hover:text-accent-primary"
                title="Add local GGUF file"
              >
                <Plus className="h-3.5 w-3.5" />
              </button>
              <button
                type="button"
                onClick={handleDeleteCurrentCustom}
                disabled={selectedProviderEntry?.source !== 'custom'}
                className="px-2 py-1 rounded border border-surface-border text-ui-secondary hover:text-red-400 disabled:opacity-40"
                title="Delete selected custom model"
              >
                <Trash2 className="h-3.5 w-3.5" />
              </button>
            </div>
            {activeDownload && (
              <div className="text-[10px] text-ui-muted">
                {activeDownload.status}: {activeDownload.model_id}
                {activeDownload.percent != null ? ` (${Math.round(activeDownload.percent)}%)` : ''}
                {activeDownload.message ? ` - ${activeDownload.message}` : ''}
              </div>
            )}
          </div>
        )}
      </div>

      {/* Reasoning effort selector */}
      <div className="px-3 py-2 border-b border-surface-border/40">
        <div className="flex items-center gap-2 text-xs">
          <span className="text-[10px] uppercase tracking-widest text-ui-muted">Thinking</span>
          <div className="flex-1 flex items-center gap-1">
            {([
              { value: null, label: 'Auto' },
              { value: 'low', label: 'Low' },
              { value: 'medium', label: 'Med' },
              { value: 'high', label: 'High' },
              { value: 'max', label: 'Max' },
            ] as const).map((opt) => {
              const isActive = reasoningEffort === opt.value;
              return (
                <button
                  key={opt.label}
                  type="button"
                  onClick={() => onSetReasoningEffort(opt.value)}
                  className={`flex-1 px-1.5 py-1 rounded text-[10px] font-medium transition-colors ${
                    isActive
                      ? 'bg-accent-primary/20 text-accent-primary border border-accent-primary/40'
                      : 'text-ui-muted hover:text-ui-secondary hover:bg-surface-elevated/60 border border-transparent'
                  }`}
                >
                  {opt.label}
                </button>
              );
            })}
          </div>
        </div>
      </div>

      {/* Command palette (filter + list) */}
      <Command
        label="Model picker"
        value={selectedValue}
        onValueChange={setSelectedValue}
        className="flex flex-col flex-1 min-h-0"
        filter={(value, search, keywords) => {
          if (!search) return 1;
          const normalized = normalizeValue(value);
          const withoutNode = normalized.replace(/@[^/]+\//, '/');
          const searchLower = search.toLowerCase();
          if (withoutNode.toLowerCase().includes(searchLower)) return 1;
          if (keywords?.some((k) => k.toLowerCase().includes(searchLower))) return 1;
          return 0;
        }}
      >
        <div className="px-3 py-2 border-b border-surface-border/40">
          <div className="relative">
            <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-ui-muted pointer-events-none" />
            <Command.Input
              ref={inputRef}
              placeholder="Filter models..."
              className="w-full rounded-lg border border-surface-border bg-surface-elevated/70 pl-8 pr-3 py-1.5 text-xs text-ui-primary placeholder:text-ui-muted focus:border-accent-primary focus:outline-none"
            />
          </div>
        </div>

        <Command.List ref={commandListRef} className="flex-1 overflow-y-auto px-1 py-1 max-h-[240px]">
          {allModels.length === 0 ? (
            <Command.Loading className="px-3 py-6 text-center text-xs text-ui-muted">
              {modelsRefreshing ? 'Refreshing models...' : modelsLoadedOnce ? 'No models available' : 'Loading models...'}
            </Command.Loading>
          ) : (
            <>
              {/* Show refresh indicator when models are being refreshed */}
              {modelsRefreshing && (
                <div className="px-3 py-1.5 text-center text-[10px] text-ui-muted animate-pulse">
                  Refreshing model list...
                </div>
              )}
              <Command.Empty className="px-3 py-6 text-center text-xs text-ui-muted">
                No models match your search
              </Command.Empty>

              {recentModels.length > 0 && (
                <Command.Group
                  heading="Recent"
                  className="mb-2 [&_[cmdk-group-heading]]:sticky [&_[cmdk-group-heading]]:top-0 [&_[cmdk-group-heading]]:z-10 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1 [&_[cmdk-group-heading]]:text-[10px] [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-widest [&_[cmdk-group-heading]]:text-accent-primary [&_[cmdk-group-heading]]:bg-surface-canvas/95"
                >
                  {recentModels.map((entry) => {
                    const val = itemValue(entry.provider, entry.model);
                    const isCurrent = currentProvider === entry.provider && currentModel === entry.model;

                    return (
                      <Command.Item
                        key={`recent-${val}`}
                        value={`${RECENT_PREFIX}${val}`}
                        keywords={[entry.provider, entry.model]}
                        onSelect={(v) => switchModel(v)}
                        className="w-full flex items-center gap-2 px-2 py-1.5 rounded-lg text-left text-xs transition-colors text-ui-secondary data-[selected=true]:bg-accent-primary/20 data-[selected=true]:text-accent-primary data-[selected=true]:border data-[selected=true]:border-accent-primary/40 hover:bg-surface-elevated/60 cursor-pointer"
                      >
                        <ChevronRight className="cmdk-chevron h-3 w-3 flex-shrink-0 opacity-0 text-accent-primary transition-opacity" />
                        <span className="flex-1 truncate">
                          {entry.provider} / {entry.model}
                        </span>
                        {isCurrent && (
                          <span className="flex-shrink-0 px-1.5 py-0.5 rounded text-[9px] uppercase tracking-wider bg-accent-tertiary/20 text-accent-tertiary border border-accent-tertiary/30">
                            current
                          </span>
                        )}
                      </Command.Item>
                    );
                  })}
                </Command.Group>
              )}

              {recentModels.length > 0 && (
                <div className="h-px bg-surface-border/40 my-2" />
              )}

              {grouped.map((group) => (
                <Command.Group
                  key={group.heading}
                  heading={group.heading}
                  className="mb-1 [&_[cmdk-group-heading]]:sticky [&_[cmdk-group-heading]]:top-0 [&_[cmdk-group-heading]]:z-10 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1 [&_[cmdk-group-heading]]:text-[10px] [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-widest [&_[cmdk-group-heading]]:text-ui-muted [&_[cmdk-group-heading]]:bg-surface-canvas/95"
                >
                  {group.models.map((model) => {
                    const val = itemValue(group.provider, model.value, group.node);
                    const isCurrent =
                      currentProvider === group.provider &&
                      currentModel === model.value &&
                      (group.node ?? undefined) === (currentNode ?? undefined);

                    return (
                      <Command.Item
                        key={val}
                        value={val}
                        keywords={[group.provider, model.display, model.value, ...(group.nodeLabel ? [group.nodeLabel] : [])]}
                        onSelect={(v) => switchModel(v)}
                        className="w-full flex items-center gap-2 px-2 py-1.5 rounded-lg text-left text-xs transition-colors text-ui-secondary data-[selected=true]:bg-accent-primary/20 data-[selected=true]:text-accent-primary data-[selected=true]:border data-[selected=true]:border-accent-primary/40 hover:bg-surface-elevated/60 cursor-pointer"
                      >
                        <ChevronRight className="cmdk-chevron h-3 w-3 flex-shrink-0 opacity-0 text-accent-primary transition-opacity" />
                        <span className="flex-1 truncate">{model.display}</span>
                        {group.nodeLabel && (
                          <span className="flex-shrink-0 px-1.5 py-0.5 rounded text-[9px] uppercase tracking-wider bg-blue-500/10 text-blue-400 border border-blue-500/20">
                            {group.nodeLabel}
                          </span>
                        )}
                        {isCurrent && (
                          <span className="flex-shrink-0 px-1.5 py-0.5 rounded text-[9px] uppercase tracking-wider bg-accent-tertiary/20 text-accent-tertiary border border-accent-tertiary/30">
                            current
                          </span>
                        )}
                      </Command.Item>
                    );
                  })}
                </Command.Group>
              ))}
            </>
          )}
        </Command.List>
      </Command>

      <div className="px-3 py-2 border-t border-surface-border/60 bg-surface-elevated/30">
        <div className="flex items-center justify-between gap-3">
          <div className="text-[10px] text-ui-muted truncate flex-1 flex items-center gap-1.5">
            {selectedEntry ? (
              <>
                <span className="text-ui-secondary">
                  {selectedEntry.provider} / {selectedEntry.model}
                </span>
                {selectedEntry.nodeLabel && (
                  <span className="px-1 py-0.5 rounded text-[9px] uppercase tracking-wider bg-blue-500/10 text-blue-400 border border-blue-500/20">
                    {selectedEntry.nodeLabel}
                  </span>
                )}
              </>
            ) : (
              'Select a model above'
            )}
          </div>
          <button
            type="button"
            onClick={handleSwitchClick}
            disabled={!canSwitch}
            className={`px-4 py-1.5 rounded-lg text-xs font-medium transition-all ${
              canSwitch
                ? 'bg-accent-primary/20 border border-accent-primary text-accent-primary hover:bg-accent-primary/30 hover:shadow-glow-primary'
                : 'bg-surface-elevated/50 border border-surface-border text-ui-muted cursor-not-allowed'
            }`}
          >
            {routingMode === 'broadcast' && target === TARGET_ALL ? 'Switch all' : 'Switch'}
          </button>
        </div>
      </div>
    </>
  );
});

/* ------------------------------------------------------------------ */
/*  ModelPickerPopover — cheap trigger always rendered                 */
/*  Panel is only mounted when open                                    */
/* ------------------------------------------------------------------ */

export const ModelPickerPopover = memo(function ModelPickerPopover({
  open,
  onOpenChange,
  isInMobileMenu = false,
  connected,
  routingMode,
  activeAgentId: _activeAgentId,
  sessionId,
  sessionsByAgent,
  agents,
  allModels,
  modelsRefreshing = false,
  modelsLoadedOnce = false,
  currentProvider,
  currentModel,
  currentNode,
  remoteNodes,
  currentWorkspace,
  recentModelsByWorkspace,
  agentMode,
  reasoningEffort,
  onRefresh,
  onSetSessionModel,
  onSetReasoningEffort,
  onCycleReasoningEffort,
  providerCapabilities,
  modelDownloads,
  onAddCustomModelFromHf,
  onAddCustomModelFromFile,
  onDeleteCustomModel,
}: ModelPickerPopoverProps) {
  const inputRef = useRef<HTMLInputElement>(null);
  const commandListRef = useRef<HTMLDivElement>(null);
  const { focusMainInput } = useUiStore();

  // Resolve node badge label from the known remote-nodes list.
  // If currentNode is not found in remoteNodes it is the local peer — show no badge.
  const currentNodeLabel = useMemo(() => {
    if (!currentNode) return undefined;
    return remoteNodes?.find((n) => n.id === currentNode)?.label;
  }, [currentNode, remoteNodes]);

  // Cheap trigger-only derivations — no allModels scan
  const triggerLabel = makeTriggerLabel(currentProvider, currentModel);
  const triggerTitle = makeTriggerTitle(currentProvider, currentModel, currentNodeLabel);

  // Shared panel props — passed through only when panel is mounted
  const panelProps: ModelPickerPanelProps = {
    connected,
    routingMode,
    sessionId,
    sessionsByAgent,
    agents,
    allModels,
    modelsRefreshing,
    modelsLoadedOnce,
    currentProvider,
    currentModel,
    currentNode,
    currentWorkspace,
    recentModelsByWorkspace,
    agentMode,
    reasoningEffort,
    onRefresh,
    onSetSessionModel,
    onSetReasoningEffort,
    providerCapabilities,
    modelDownloads,
    onAddCustomModelFromHf,
    onAddCustomModelFromFile,
    onDeleteCustomModel,
    onOpenChange,
    isInMobileMenu,
    inputRef,
    commandListRef,
  };

  if (isInMobileMenu) {
    return (
      <Dialog.Root open={open} onOpenChange={onOpenChange}>
        <Dialog.Trigger asChild>
          <button
            type="button"
            className="flex items-center gap-2 px-2.5 py-1 rounded-lg border border-surface-border/40 bg-surface-elevated/50 text-xs text-ui-secondary hover:border-accent-primary/60 hover:text-accent-primary transition-colors w-full"
            title={triggerTitle}
          >
            <span className="truncate">{triggerLabel}</span>
            {currentNodeLabel && (
              <span className="flex-shrink-0 px-1.5 py-0.5 rounded text-[9px] uppercase tracking-wider bg-blue-500/10 text-blue-400 border border-blue-500/20">
                {currentNodeLabel}
              </span>
            )}
            <span className="flex-shrink-0 px-1.5 py-0.5 rounded text-[9px] uppercase tracking-wider bg-purple-500/10 text-purple-400 border border-purple-500/20">
              {effortBadgeLabel(reasoningEffort)}
            </span>
            <ChevronDown className={`w-3.5 h-3.5 flex-shrink-0 transition-transform ${open ? 'rotate-180' : ''}`} />
          </button>
        </Dialog.Trigger>

        <Dialog.Portal>
          <Dialog.Overlay className="fixed inset-0 z-40 bg-black/40" />
          <Dialog.Content
            className="fixed left-1/2 top-4 z-50 w-[calc(100vw-1rem)] max-w-[480px] max-h-[calc(100vh-2rem)] -translate-x-1/2 flex flex-col rounded-xl border border-accent-primary/30 bg-surface-canvas shadow-lg shadow-accent-primary/25 animate-fade-in"
            onOpenAutoFocus={(e) => {
              e.preventDefault();
            }}
            onCloseAutoFocus={(e) => {
              e.preventDefault();
            }}
          >
            {/* Panel only mounted when dialog is open */}
            {open && <ModelPickerPanel {...panelProps} />}
          </Dialog.Content>
        </Dialog.Portal>
      </Dialog.Root>
    );
  }

  return (
    <Popover.Root open={open} onOpenChange={onOpenChange}>
      <Popover.Trigger asChild>
        <button
          type="button"
          className="flex items-center gap-2 px-2.5 py-1 rounded-lg border border-surface-border/40 bg-surface-elevated/50 text-xs text-ui-secondary hover:border-accent-primary/60 hover:text-accent-primary transition-colors max-w-[20rem] flex-shrink min-w-0"
          title={triggerTitle}
        >
          <span className="truncate">{triggerLabel}</span>
          {currentNodeLabel && (
            <span className="flex-shrink-0 px-1.5 py-0.5 rounded text-[9px] uppercase tracking-wider bg-blue-500/10 text-blue-400 border border-blue-500/20">
              {currentNodeLabel}
            </span>
          )}
          <button
            type="button"
            onClick={(e) => { e.stopPropagation(); onCycleReasoningEffort(); }}
            className="flex-shrink-0 px-1.5 py-0.5 rounded text-[9px] uppercase tracking-wider bg-purple-500/10 text-purple-400 border border-purple-500/20 hover:bg-purple-500/20 transition-colors"
            title={`Reasoning: ${reasoningEffort ?? 'auto'} (${isMacPlatform ? '⌘;' : 'Ctrl+;'} to cycle)`}
          >
            {effortBadgeLabel(reasoningEffort)}
          </button>
          <ChevronDown className={`w-3.5 h-3.5 flex-shrink-0 transition-transform ${open ? 'rotate-180' : ''}`} />
        </button>
      </Popover.Trigger>

      <Popover.Portal>
        <Popover.Content
          align="end"
          sideOffset={8}
          className="z-50 w-[480px] max-h-[420px] flex flex-col rounded-xl border border-accent-primary/30 bg-surface-canvas shadow-lg shadow-accent-primary/25 animate-fade-in"
          onOpenAutoFocus={(e) => {
            e.preventDefault();
            inputRef.current?.focus();
          }}
          onCloseAutoFocus={(e) => {
            e.preventDefault();
            focusMainInput();
          }}
        >
          {/* Panel only mounted when popover is open */}
          {open && <ModelPickerPanel {...panelProps} />}
        </Popover.Content>
      </Popover.Portal>
    </Popover.Root>
  );
});
