import { useEffect, useState, useMemo, useCallback, useRef } from 'react';
import { View, Text, TextInput, Switch, StyleSheet, ScrollView, Modal, TouchableOpacity, Platform, Pressable, ActivityIndicator, RefreshControl, Share, Alert, LayoutAnimation, UIManager, Image, useWindowDimensions } from 'react-native';
import {
  AppConfig,
  DEFAULT_HANDS_FREE_MESSAGE_DEBOUNCE_MS,
  saveConfig,
  useConfigContext,
} from '../store/config';
import { useSessionContext } from '../store/sessions';
import { useConnectionManager } from '../store/connectionManager';
import { useTheme, ThemeMode } from '../ui/ThemeProvider';
import { spacing, radius } from '../ui/theme';
import { AppShellSettingsLayout } from '../ui/AppShellSettingsLayout';
import {
  getAppShellDesktopSettingsNavItemIdForMobileSection,
  getAppShellMobileSettingsInitialExpandedState,
  getAppShellMobileSettingsSectionIdsForDesktopNavItem,
  getDesktopSettingsNavItems,
  isAppShellMobileSettingsSectionId,
  resolveAppShellLayout,
  type AppShellMobileSettingsSectionId,
  type AppShellSettingsNavItemId,
} from '../ui/appShell';
import { useProfile } from '../store/profile';
import { usePushNotifications } from '../lib/pushNotifications';
import {
  createButtonAccessibilityLabel,
  createMcpServerSwitchAccessibilityLabel,
  createMinimumTouchTargetStyle,
  createSwitchAccessibilityLabel,
} from '../lib/accessibility';
import {
  ExtendedSettingsApiClient,
  Profile,
  MCPServer,
  MCPConfig,
  MCPServerConfig,
  McpOAuthStatusResponse,
  MCPTransportType,
  ChatGptWebAuthStatus,
  Settings,
  ModelPresetSummary,
  ModelInfo,
  SettingsUpdate,
  OpenAiReasoningEffort,
  CodexTextVerbosity,
  Skill,
  KnowledgeNote,
  KnowledgeNoteContext,
  KnowledgeNoteDateFilter,
  KnowledgeNoteSort,
  AgentProfile,
  Loop,
  BundleComponentKey,
  BundleComponentSelection,
  BundleImportConflictStrategy,
  BundleImportPreview,
} from '../lib/settingsApi';
import { getAcpxMainAgentOptions } from '../lib/mainAgentOptions';
import { TTSSettings } from '../ui/TTSSettings';
import { MicrophoneSelector } from '../ui/MicrophoneSelector';
import Slider from '@react-native-community/slider';

// STT Provider Options
const STT_PROVIDERS = [
  { label: 'OpenAI', value: 'openai' },
  { label: 'Groq', value: 'groq' },
  { label: 'Parakeet (Local)', value: 'parakeet' },
] as const;

// Chat/Agent Provider Options (for Agent and Transcript Processing)
const CHAT_PROVIDERS = [
  { label: 'OpenAI', value: 'openai' },
  { label: 'Groq', value: 'groq' },
  { label: 'Gemini', value: 'gemini' },
  { label: 'OpenAI Codex', value: 'chatgpt-web' },
] as const;

// TTS Provider Options
const TTS_PROVIDERS = [
  { label: 'OpenAI', value: 'openai' },
  { label: 'Groq', value: 'groq' },
  { label: 'Gemini', value: 'gemini' },
  { label: 'Edge TTS (Free)', value: 'edge' },
  { label: 'Kitten (Local)', value: 'kitten' },
  { label: 'Supertonic (Local)', value: 'supertonic' },
] as const;

const SECRET_MASK = '••••••••';
const DEFAULT_STT_PROVIDER_ID = 'openai';
const DEFAULT_TTS_PROVIDER_ID = 'openai';
const DEFAULT_TRANSCRIPT_POST_PROCESSING_PROVIDER_ID = 'openai';
const DEFAULT_TRANSCRIPT_POST_PROCESSING_ENABLED = true;
const DEFAULT_TRANSCRIPTION_PREVIEW_ENABLED = true;
const DEFAULT_PARAKEET_NUM_THREADS = 2;
const PARAKEET_NUM_THREAD_OPTIONS = [1, 2, 4, 8] as const;
const KNOWN_STT_MODEL_IDS = {
  openai: ['gpt-4o-transcribe', 'gpt-4o-mini-transcribe', 'whisper-1'],
  groq: ['whisper-large-v3', 'whisper-large-v3-turbo', 'distil-whisper-large-v3-en'],
} as const;
const DEFAULT_TTS_ENABLED = true;
const DEFAULT_TTS_AUTO_PLAY = true;
const DEFAULT_TTS_PREPROCESSING_ENABLED = true;
const DEFAULT_TTS_REMOVE_CODE_BLOCKS = true;
const DEFAULT_TTS_REMOVE_URLS = true;
const DEFAULT_TTS_CONVERT_MARKDOWN = true;
const DEFAULT_TTS_USE_LLM_PREPROCESSING = false;
const DEFAULT_SUPERTONIC_TTS_LANGUAGE = 'en';
const DEFAULT_SUPERTONIC_TTS_STEPS = 5;
const MIN_SUPERTONIC_TTS_STEPS = 2;
const MAX_SUPERTONIC_TTS_STEPS = 10;
const DEFAULT_MCP_AUTO_PASTE_ENABLED = false;
const DEFAULT_MCP_AUTO_PASTE_DELAY = 1000;
const MIN_MCP_AUTO_PASTE_DELAY = 0;
const MAX_MCP_AUTO_PASTE_DELAY = 60000;
const DEFAULT_MAIN_AGENT_MODE = 'api';
const DEFAULT_STREAMER_MODE_ENABLED = false;
const DEFAULT_WHATSAPP_ENABLED = false;
const DEFAULT_WHATSAPP_AUTO_REPLY = false;
const DEFAULT_WHATSAPP_LOG_MESSAGES = false;
const DEFAULT_DISCORD_ENABLED = false;
const DEFAULT_DISCORD_DM_ENABLED = true;
const DEFAULT_DISCORD_REQUIRE_MENTION = true;
const DEFAULT_DISCORD_LOG_MESSAGES = false;
const DEFAULT_LOCAL_TRACE_LOGGING_ENABLED = false;
const DEFAULT_CODEX_REASONING_EFFORT: OpenAiReasoningEffort = 'low';
const DEFAULT_CODEX_TEXT_VERBOSITY: CodexTextVerbosity = 'medium';

const MAIN_AGENT_MODE_OPTIONS = [
  { value: 'api', compactLabel: 'API' },
  { value: 'acpx', compactLabel: 'acpx' },
] as const;

const LOCAL_TRACE_LOGGING_LABEL = 'Local trace logging';
const LOCAL_TRACE_LOGGING_HELPER = 'Write agent session traces to JSONL files on the desktop machine.';
const LOCAL_TRACE_LOG_PATH_LABEL = 'Trace Folder';
const LOCAL_TRACE_LOG_PATH_PLACEHOLDER = 'Use default traces folder';
const LOCAL_TRACE_LOG_PATH_HELPER = 'Optional desktop filesystem path for trace files.';

const OPENAI_REASONING_EFFORT_OPTIONS: readonly { value: OpenAiReasoningEffort; label: string }[] = [
  { value: 'none', label: 'None' },
  { value: 'minimal', label: 'Minimal' },
  { value: 'low', label: 'Low' },
  { value: 'medium', label: 'Medium' },
  { value: 'high', label: 'High' },
  { value: 'xhigh', label: 'Extra high' },
];

const CODEX_TEXT_VERBOSITY_OPTIONS: readonly { value: CodexTextVerbosity; label: string }[] = [
  { value: 'low', label: 'Low' },
  { value: 'medium', label: 'Medium' },
  { value: 'high', label: 'High' },
];

const SUPERTONIC_TTS_LANGUAGES = [
  { label: 'English', value: 'en' },
  { label: 'Korean', value: 'ko' },
  { label: 'Spanish', value: 'es' },
  { label: 'Portuguese', value: 'pt' },
  { label: 'French', value: 'fr' },
] as const;

const DEFAULT_CONVERSATIONS_ENABLED = true;
const DEFAULT_AUTO_SAVE_CONVERSATIONS = true;
const DEFAULT_MAX_CONVERSATIONS_TO_KEEP = 1000;
const MIN_CONVERSATIONS_TO_KEEP = 1;
const MAX_CONVERSATIONS_TO_KEEP = 10000;

const parseMaxConversationsToKeepDraft = (value: string): number | null => {
  const parsedValue = Number.parseInt(value.trim(), 10);
  if (Number.isNaN(parsedValue)) return null;
  if (parsedValue < MIN_CONVERSATIONS_TO_KEEP || parsedValue > MAX_CONVERSATIONS_TO_KEEP) return null;
  return parsedValue;
};

const formatMaxConversationsToKeepValidationMessage = () =>
  `Keep Recent Chats must be between ${MIN_CONVERSATIONS_TO_KEEP} and ${MAX_CONVERSATIONS_TO_KEEP} before saving.`;

const parseMcpAutoPasteDelayDraft = (value: string): number | null => {
  const parsedValue = Number.parseInt(value.trim(), 10);
  if (!Number.isFinite(parsedValue)) return null;
  if (parsedValue < MIN_MCP_AUTO_PASTE_DELAY || parsedValue > MAX_MCP_AUTO_PASTE_DELAY) return null;
  return parsedValue;
};

const formatMcpAutoPasteDelayValidationMessage = () =>
  `Auto-paste delay must be between ${MIN_MCP_AUTO_PASTE_DELAY} and ${MAX_MCP_AUTO_PASTE_DELAY}ms before saving.`;

type ProviderSecretSettingKey =
  | 'openaiApiKey'
  | 'groqApiKey'
  | 'geminiApiKey'
  | 'chatgptWebAccessToken'
  | 'chatgptWebSessionToken';
type ProviderBaseUrlSettingKey = 'openaiBaseUrl' | 'groqBaseUrl' | 'geminiBaseUrl' | 'chatgptWebBaseUrl';

const PROVIDER_CREDENTIAL_SECTIONS: Array<{
  id: string;
  label: string;
  secrets: Array<{ key: ProviderSecretSettingKey; label: string; placeholder: string }>;
  baseUrl: ProviderBaseUrlSettingKey;
  baseUrlPlaceholder: string;
}> = [
  {
    id: 'openai',
    label: 'OpenAI Compatible',
    secrets: [{ key: 'openaiApiKey', label: 'API Key', placeholder: 'sk-...' }],
    baseUrl: 'openaiBaseUrl',
    baseUrlPlaceholder: 'https://api.openai.com/v1',
  },
  {
    id: 'groq',
    label: 'Groq',
    secrets: [{ key: 'groqApiKey', label: 'API Key', placeholder: 'gsk_...' }],
    baseUrl: 'groqBaseUrl',
    baseUrlPlaceholder: 'https://api.groq.com/openai/v1',
  },
  {
    id: 'gemini',
    label: 'Gemini',
    secrets: [{ key: 'geminiApiKey', label: 'API Key', placeholder: 'AIza...' }],
    baseUrl: 'geminiBaseUrl',
    baseUrlPlaceholder: 'https://generativelanguage.googleapis.com',
  },
  {
    id: 'chatgpt-web',
    label: 'OpenAI Codex',
    secrets: [
      { key: 'chatgptWebAccessToken', label: 'Access Token', placeholder: 'access token' },
      { key: 'chatgptWebSessionToken', label: 'Session Token', placeholder: 'session token' },
    ],
    baseUrl: 'chatgptWebBaseUrl',
    baseUrlPlaceholder: 'https://chatgpt.com',
  },
];

type DiscordListSettingKey =
  | 'discordAllowUserIds'
  | 'discordAllowGuildIds'
  | 'discordAllowChannelIds'
  | 'discordAllowRoleIds'
  | 'discordDmAllowUserIds';

const DISCORD_LIST_SETTING_KEYS: DiscordListSettingKey[] = [
  'discordAllowUserIds',
  'discordAllowGuildIds',
  'discordAllowChannelIds',
  'discordAllowRoleIds',
  'discordDmAllowUserIds',
];

const DISCORD_LIST_SETTING_SECTIONS: Array<{
  key: DiscordListSettingKey;
  label: string;
  placeholder: string;
  helper: string;
}> = [
  {
    key: 'discordAllowUserIds',
    label: 'Allowed User IDs',
    placeholder: 'One Discord user ID per line',
    helper: 'Leave blank to allow any user in allowed channels or DMs.',
  },
  {
    key: 'discordAllowGuildIds',
    label: 'Allowed Server IDs',
    placeholder: 'One Discord server ID per line',
    helper: 'Restrict server mention handling to specific Discord servers.',
  },
  {
    key: 'discordAllowChannelIds',
    label: 'Allowed Channel IDs',
    placeholder: 'One Discord channel or thread ID per line',
    helper: 'Restrict server mention handling to specific channels or threads.',
  },
  {
    key: 'discordAllowRoleIds',
    label: 'Allowed Role IDs',
    placeholder: 'One Discord role ID per line',
    helper: 'Require at least one of these roles for server mention handling.',
  },
  {
    key: 'discordDmAllowUserIds',
    label: 'Allowed DM User IDs',
    placeholder: 'One Discord user ID per line',
    helper: 'Restrict direct-message access to specific Discord users.',
  },
];

function formatConfigListInput(values?: string[], options: { separator?: 'comma' | 'newline' } = {}): string {
  const separator = options.separator === 'newline' ? '\n' : ', ';
  return (values || []).join(separator);
}

function parseConfigListInput(value: string, options: { unique?: boolean } = {}): string[] {
  const entries = value
    .split(/[\n,]/)
    .map((entry) => entry.trim())
    .filter(Boolean);
  return options.unique ? [...new Set(entries)] : entries;
}

const LOOP_DAY_LABELS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

type BundleImportComponentsState = Required<BundleComponentSelection>;

const DEFAULT_BUNDLE_IMPORT_COMPONENTS: BundleImportComponentsState = {
  agentProfiles: true,
  mcpServers: true,
  skills: true,
  repeatTasks: true,
  knowledgeNotes: true,
};

const BUNDLE_IMPORT_COMPONENT_OPTIONS: Array<{ key: BundleComponentKey; label: string }> = [
  { key: 'agentProfiles', label: 'Agents' },
  { key: 'mcpServers', label: 'MCP servers' },
  { key: 'skills', label: 'Skills' },
  { key: 'repeatTasks', label: 'Tasks' },
  { key: 'knowledgeNotes', label: 'Knowledge' },
];

const BUNDLE_IMPORT_CONFLICT_STRATEGIES: Array<{ value: BundleImportConflictStrategy; label: string }> = [
  { value: 'skip', label: 'Skip' },
  { value: 'rename', label: 'Rename' },
  { value: 'overwrite', label: 'Overwrite' },
];

type ModelPresetEditorMode = 'create' | 'edit';

type ModelPresetDraft = {
  id?: string;
  name: string;
  baseUrl: string;
  apiKey: string;
  agentModel: string;
  transcriptProcessingModel: string;
  isBuiltIn: boolean;
  hasApiKey: boolean;
};

const EMPTY_MODEL_PRESET_DRAFT: ModelPresetDraft = {
  name: '',
  baseUrl: '',
  apiKey: '',
  agentModel: '',
  transcriptProcessingModel: '',
  isBuiltIn: false,
  hasApiKey: false,
};

function buildModelPresetDraftFromSummary(preset?: ModelPresetSummary | null): ModelPresetDraft {
  if (!preset) return EMPTY_MODEL_PRESET_DRAFT;
  return {
    id: preset.id,
    name: preset.name,
    baseUrl: preset.baseUrl,
    apiKey: '',
    agentModel: preset.agentModel || preset.mcpToolsModel || '',
    transcriptProcessingModel: preset.transcriptProcessingModel || '',
    isBuiltIn: preset.isBuiltIn ?? false,
    hasApiKey: !!preset.hasApiKey,
  };
}

function buildModelPresetPayloadFromDraft(draft: ModelPresetDraft):
  | { ok: true; payload: { name: string; baseUrl: string; apiKey?: string; agentModel?: string; transcriptProcessingModel?: string } }
  | { ok: false; error: string } {
  const name = draft.name.trim();
  const baseUrl = draft.baseUrl.trim();
  if (!name) return { ok: false, error: 'Endpoint name is required' };
  if (!baseUrl) return { ok: false, error: 'Endpoint base URL is required' };

  return {
    ok: true,
    payload: {
      name,
      baseUrl,
      apiKey: draft.apiKey.trim(),
      agentModel: draft.agentModel.trim(),
      transcriptProcessingModel: draft.transcriptProcessingModel.trim(),
    },
  };
}

function describeLoopCadence(loop: Loop): string {
  if (loop.runContinuously) return 'Continuous';
  if (!loop.schedule) return `Every ${loop.intervalMinutes}min`;
  const times = loop.schedule.times.join(', ');
  if (loop.schedule.type === 'daily') return `Daily at ${times}`;
  const days = loop.schedule.daysOfWeek.map(day => LOOP_DAY_LABELS[day] ?? String(day)).join(', ');
  return `${days} at ${times}`;
}

// OpenAI TTS Voice Options
const OPENAI_TTS_VOICES = [
  { label: 'Alloy', value: 'alloy' },
  { label: 'Echo', value: 'echo' },
  { label: 'Fable', value: 'fable' },
  { label: 'Onyx', value: 'onyx' },
  { label: 'Nova', value: 'nova' },
  { label: 'Shimmer', value: 'shimmer' },
] as const;

const OPENAI_TTS_MODELS = [
  { label: 'GPT-4o Mini TTS', value: 'gpt-4o-mini-tts' },
  { label: 'TTS-1 (Standard)', value: 'tts-1' },
  { label: 'TTS-1-HD (High Quality)', value: 'tts-1-hd' },
] as const;

// Groq TTS Voice Options (English) - Orpheus model voices
const GROQ_TTS_VOICES_ENGLISH = [
  { label: 'Autumn', value: 'autumn' },
  { label: 'Diana', value: 'diana' },
  { label: 'Hannah', value: 'hannah' },
  { label: 'Austin', value: 'austin' },
  { label: 'Daniel', value: 'daniel' },
  { label: 'Troy', value: 'troy' },
] as const;

// Groq TTS Voice Options (Arabic Saudi) - Orpheus model voices
const GROQ_TTS_VOICES_ARABIC = [
  { label: 'Fahad', value: 'fahad' },
  { label: 'Sultan', value: 'sultan' },
  { label: 'Lulwa', value: 'lulwa' },
  { label: 'Noura', value: 'noura' },
] as const;

const GROQ_TTS_MODELS = [
  { label: 'Orpheus TTS (English)', value: 'canopylabs/orpheus-v1-english' },
  { label: 'Orpheus TTS (Arabic Saudi)', value: 'canopylabs/orpheus-arabic-saudi' },
] as const;

// Gemini TTS Voice Options (30 voices)
const GEMINI_TTS_VOICES = [
  { label: 'Zephyr (Bright)', value: 'Zephyr' },
  { label: 'Puck (Upbeat)', value: 'Puck' },
  { label: 'Charon (Informative)', value: 'Charon' },
  { label: 'Kore (Firm)', value: 'Kore' },
  { label: 'Fenrir (Excitable)', value: 'Fenrir' },
  { label: 'Leda (Young)', value: 'Leda' },
  { label: 'Orus (Corporate)', value: 'Orus' },
  { label: 'Aoede (Breezy)', value: 'Aoede' },
  { label: 'Callirrhoe (Casual)', value: 'Callirrhoe' },
  { label: 'Autonoe (Bright)', value: 'Autonoe' },
  { label: 'Enceladus (Breathy)', value: 'Enceladus' },
  { label: 'Iapetus (Clear)', value: 'Iapetus' },
  { label: 'Umbriel (Calm)', value: 'Umbriel' },
  { label: 'Algieba (Smooth)', value: 'Algieba' },
  { label: 'Despina (Smooth)', value: 'Despina' },
  { label: 'Erinome (Serene)', value: 'Erinome' },
  { label: 'Algenib (Gravelly)', value: 'Algenib' },
  { label: 'Rasalgethi (Informative)', value: 'Rasalgethi' },
  { label: 'Laomedeia (Upbeat)', value: 'Laomedeia' },
  { label: 'Achernar (Soft)', value: 'Achernar' },
  { label: 'Alnilam (Firm)', value: 'Alnilam' },
  { label: 'Schedar (Even)', value: 'Schedar' },
  { label: 'Gacrux (Mature)', value: 'Gacrux' },
  { label: 'Pulcherrima (Forward)', value: 'Pulcherrima' },
  { label: 'Achird (Friendly)', value: 'Achird' },
  { label: 'Zubenelgenubi (Casual)', value: 'Zubenelgenubi' },
  { label: 'Vindemiatrix (Gentle)', value: 'Vindemiatrix' },
  { label: 'Sadachbia (Lively)', value: 'Sadachbia' },
  { label: 'Sadaltager (Knowledgeable)', value: 'Sadaltager' },
  { label: 'Sulafat (Warm)', value: 'Sulafat' },
] as const;

const GEMINI_TTS_MODELS = [
  { label: 'Gemini 2.5 Flash TTS', value: 'gemini-2.5-flash-preview-tts' },
  { label: 'Gemini 2.5 Pro TTS', value: 'gemini-2.5-pro-preview-tts' },
] as const;

const EDGE_TTS_VOICES = [
  { label: 'Aria (US, Female)', value: 'en-US-AriaNeural' },
  { label: 'Guy (US, Male)', value: 'en-US-GuyNeural' },
  { label: 'Jenny (US, Female)', value: 'en-US-JennyNeural' },
  { label: 'Brian (US, Male)', value: 'en-US-BrianNeural' },
  { label: 'Sonia (UK, Female)', value: 'en-GB-SoniaNeural' },
  { label: 'Ryan (UK, Male)', value: 'en-GB-RyanNeural' },
] as const;

const EDGE_TTS_MODELS = [
  { label: 'Edge Neural (Free)', value: 'edge-tts' },
] as const;

const KITTEN_TTS_VOICES = [
  { label: 'Voice 2 - Male (Default)', value: 0 },
  { label: 'Voice 2 - Female', value: 1 },
  { label: 'Voice 3 - Male', value: 2 },
  { label: 'Voice 3 - Female', value: 3 },
  { label: 'Voice 4 - Male', value: 4 },
  { label: 'Voice 4 - Female', value: 5 },
  { label: 'Voice 5 - Male', value: 6 },
  { label: 'Voice 5 - Female', value: 7 },
] as const;

const SUPERTONIC_TTS_VOICES = [
  { label: 'Male 1 (M1)', value: 'M1' },
  { label: 'Male 2 (M2)', value: 'M2' },
  { label: 'Male 3 (M3)', value: 'M3' },
  { label: 'Male 4 (M4)', value: 'M4' },
  { label: 'Male 5 (M5)', value: 'M5' },
  { label: 'Female 1 (F1)', value: 'F1' },
  { label: 'Female 2 (F2)', value: 'F2' },
  { label: 'Female 3 (F3)', value: 'F3' },
  { label: 'Female 4 (F4)', value: 'F4' },
  { label: 'Female 5 (F5)', value: 'F5' },
] as const;

type TtsVoiceOption = { label: string; value: string | number };

// Helper to get TTS voices for a provider
const getTtsVoicesForProvider = (providerId: string, ttsModel?: string): readonly TtsVoiceOption[] => {
  switch (providerId) {
    case 'openai':
      return OPENAI_TTS_VOICES;
    case 'groq':
      // Groq voices depend on the selected model (English vs Arabic)
      return ttsModel === 'canopylabs/orpheus-arabic-saudi' ? GROQ_TTS_VOICES_ARABIC : GROQ_TTS_VOICES_ENGLISH;
    case 'gemini':
      return GEMINI_TTS_VOICES;
    case 'edge':
      return EDGE_TTS_VOICES;
    case 'kitten':
      return KITTEN_TTS_VOICES;
    case 'supertonic':
      return SUPERTONIC_TTS_VOICES;
    default:
      return [];
  }
};

// Helper to get TTS models for a provider
const getTtsModelsForProvider = (providerId: string): readonly { label: string; value: string }[] => {
  switch (providerId) {
    case 'openai':
      return OPENAI_TTS_MODELS;
    case 'groq':
      return GROQ_TTS_MODELS;
    case 'gemini':
      return GEMINI_TTS_MODELS;
    case 'edge':
      return EDGE_TTS_MODELS;
    default:
      return [];
  }
};

const getTtsModelSettingKey = (providerId?: string): keyof SettingsUpdate => {
  switch (providerId) {
    case 'openai':
      return 'openaiTtsModel';
    case 'groq':
      return 'groqTtsModel';
    case 'gemini':
      return 'geminiTtsModel';
    case 'edge':
      return 'edgeTtsModel';
    default:
      return 'openaiTtsModel';
  }
};

const getTtsVoiceSettingKey = (providerId?: string): keyof SettingsUpdate => {
  switch (providerId) {
    case 'openai':
      return 'openaiTtsVoice';
    case 'groq':
      return 'groqTtsVoice';
    case 'gemini':
      return 'geminiTtsVoice';
    case 'edge':
      return 'edgeTtsVoice';
    case 'kitten':
      return 'kittenVoiceId';
    case 'supertonic':
      return 'supertonicVoice';
    default:
      return 'openaiTtsVoice';
  }
};

const getDefaultSttModel = (providerId?: string): string | undefined => {
  if (providerId === 'openai') return 'whisper-1';
  if (providerId === 'groq') return 'whisper-large-v3-turbo';
  return undefined;
};

// Enable LayoutAnimation on Android
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
  UIManager.setLayoutAnimationEnabledExperimental(true);
}

const THEME_PREFERENCE_OPTIONS: { label: string; value: ThemeMode }[] = [
  { label: 'Light', value: 'light' },
  { label: 'Dark', value: 'dark' },
  { label: 'System', value: 'system' },
];

const KNOWLEDGE_NOTE_CONTEXT_FILTER_OPTIONS: Array<{ label: string; value: 'all' | KnowledgeNoteContext }> = [
  { label: 'All', value: 'all' },
  { label: 'Search only', value: 'search-only' },
  { label: 'Auto', value: 'auto' },
];

const KNOWLEDGE_NOTE_DATE_FILTER_OPTIONS: Array<{ label: string; value: KnowledgeNoteDateFilter }> = [
  { label: 'Any time', value: 'all' },
  { label: '7d', value: '7d' },
  { label: '30d', value: '30d' },
  { label: '90d', value: '90d' },
  { label: 'Year', value: 'year' },
];

const KNOWLEDGE_NOTE_SORT_OPTIONS: Array<{ label: string; value: KnowledgeNoteSort }> = [
  { label: 'Best', value: 'relevance' },
  { label: 'Newest', value: 'updated-desc' },
  { label: 'A-Z', value: 'title-asc' },
];

function sortAgentProfilesForSettings(profiles: AgentProfile[]): AgentProfile[] {
  return [...profiles].sort((a, b) => {
    if (a.isDefault !== b.isDefault) return a.isDefault ? -1 : 1;
    if (a.isBuiltIn !== b.isBuiltIn) return a.isBuiltIn ? -1 : 1;
    return (a.displayName || a.name).localeCompare(b.displayName || b.name);
  });
}

function setAgentProfileEnabledInList(profiles: AgentProfile[], profileId: string, enabled: boolean): AgentProfile[] {
  return profiles.map(profile => profile.id === profileId ? { ...profile, enabled } : profile);
}

function removeAgentProfileFromList(profiles: AgentProfile[], profileId: string): AgentProfile[] {
  return profiles.filter(profile => profile.id !== profileId);
}

function getAgentListInitial(profile: AgentProfile): string {
  return (profile.displayName || profile.name || 'A').slice(0, 1).toUpperCase();
}

type McpServerEditorMode = 'create' | 'edit';

type McpServerDraft = {
  name: string;
  transport: MCPTransportType;
  command: string;
  args: string;
  env: string;
  url: string;
  headers: string;
  timeout: string;
  disabled: boolean;
  oauthEnabled: boolean;
  oauthScope: string;
  oauthClientId: string;
  oauthUseDiscovery: boolean;
  oauthUseDynamicRegistration: boolean;
};

const EMPTY_MCP_SERVER_DRAFT: McpServerDraft = {
  name: '',
  transport: 'stdio',
  command: '',
  args: '',
  env: '',
  url: '',
  headers: '',
  timeout: '',
  disabled: false,
  oauthEnabled: false,
  oauthScope: '',
  oauthClientId: '',
  oauthUseDiscovery: true,
  oauthUseDynamicRegistration: true,
};

const MCP_TRANSPORT_OPTIONS: Array<{ label: string; value: MCPTransportType }> = [
  { label: 'Command', value: 'stdio' },
  { label: 'HTTP', value: 'streamableHttp' },
  { label: 'WebSocket', value: 'websocket' },
];

function setMcpServerRuntimeEnabledInList(servers: MCPServer[], serverName: string, enabled: boolean): MCPServer[] {
  return servers.map(server =>
    server.name === serverName ? { ...server, enabled, runtimeEnabled: enabled } : server
  );
}

function upsertMcpServerInList(servers: MCPServer[], nextServer: MCPServer): MCPServer[] {
  const exists = servers.some(server => server.name === nextServer.name);
  const next = exists
    ? servers.map(server => server.name === nextServer.name ? nextServer : server)
    : [...servers, nextServer];
  return next.sort((a, b) => a.name.localeCompare(b.name));
}

function removeMcpServerFromList(servers: MCPServer[], serverName: string): MCPServer[] {
  return servers.filter(server => server.name !== serverName);
}

function stringifyJsonDraft(value: unknown): string {
  if (!value || (typeof value === 'object' && Object.keys(value as Record<string, unknown>).length === 0)) {
    return '';
  }
  return JSON.stringify(value, null, 2);
}

function createMcpServerDraft(name: string, config: MCPServerConfig): McpServerDraft {
  const transport = config.transport ?? (config.url?.startsWith('ws') ? 'websocket' : config.url ? 'streamableHttp' : 'stdio');
  return {
    name,
    transport,
    command: config.command || '',
    args: config.args?.length ? JSON.stringify(config.args) : '',
    env: stringifyJsonDraft(config.env),
    url: config.url || '',
    headers: stringifyJsonDraft(config.headers),
    timeout: typeof config.timeout === 'number' ? String(config.timeout) : '',
    disabled: !!config.disabled,
    oauthEnabled: !!config.oauth,
    oauthScope: config.oauth?.scope || '',
    oauthClientId: config.oauth?.clientId || '',
    oauthUseDiscovery: config.oauth?.useDiscovery ?? true,
    oauthUseDynamicRegistration: config.oauth?.useDynamicRegistration ?? true,
  };
}

function parseJsonObjectDraft(value: string, fieldLabel: string): Record<string, string> | undefined {
  const trimmed = value.trim();
  if (!trimmed) return undefined;
  const parsed = JSON.parse(trimmed);
  if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
    throw new Error(`${fieldLabel} must be a JSON object`);
  }
  return Object.fromEntries(
    Object.entries(parsed as Record<string, unknown>).map(([key, entry]) => [key, String(entry)])
  );
}

function parseArgsDraft(value: string): string[] {
  const trimmed = value.trim();
  if (!trimmed) return [];
  if (trimmed.startsWith('[')) {
    const parsed = JSON.parse(trimmed);
    if (!Array.isArray(parsed) || parsed.some(item => typeof item !== 'string')) {
      throw new Error('Args must be a JSON array of strings');
    }
    return parsed;
  }
  return trimmed.split(/\s+/).filter(Boolean);
}

function buildMcpServerConfigFromDraft(draft: McpServerDraft): MCPServerConfig {
  const timeout = draft.timeout.trim() ? Number(draft.timeout.trim()) : undefined;
  if (timeout !== undefined && (!Number.isFinite(timeout) || timeout < 0)) {
    throw new Error('Timeout must be a non-negative number');
  }

  const base: MCPServerConfig = {
    transport: draft.transport,
    disabled: draft.disabled,
    ...(timeout !== undefined ? { timeout: Math.round(timeout) } : {}),
  };

  if (draft.transport === 'stdio') {
    if (!draft.command.trim()) throw new Error('Command is required for command-based MCP servers');
    return {
      ...base,
      command: draft.command.trim(),
      args: parseArgsDraft(draft.args),
      env: parseJsonObjectDraft(draft.env, 'Environment'),
    };
  }

  if (!draft.url.trim()) throw new Error('URL is required for remote MCP servers');
  return {
    ...base,
    url: draft.url.trim(),
    headers: parseJsonObjectDraft(draft.headers, 'Headers'),
    ...(draft.transport === 'streamableHttp'
      ? {
          oauth: draft.oauthEnabled
            ? {
                ...(draft.oauthScope.trim() ? { scope: draft.oauthScope.trim() } : {}),
                ...(draft.oauthClientId.trim() ? { clientId: draft.oauthClientId.trim() } : {}),
                useDiscovery: draft.oauthUseDiscovery,
                useDynamicRegistration: draft.oauthUseDynamicRegistration,
              }
            : null,
        }
      : {}),
  };
}

function parseMcpConfigImport(value: string): MCPConfig {
  const parsed = JSON.parse(value.trim());
  const mcpServers = (parsed as { mcpServers?: unknown })?.mcpServers;
  if (!mcpServers || typeof mcpServers !== 'object' || Array.isArray(mcpServers)) {
    throw new Error('MCP import JSON must include a mcpServers object.');
  }
  return { mcpServers: mcpServers as Record<string, MCPServerConfig> };
}

export default function SettingsScreen({ navigation, route }: any) {
  const { width } = useWindowDimensions();
  const { theme, themeMode, setThemeMode } = useTheme();
  const { config, setConfig, ready } = useConfigContext();
  const [draft, setDraft] = useState<AppConfig>(config);
  const [handsFreeDebounceInput, setHandsFreeDebounceInput] = useState(
    String(config.handsFreeMessageDebounceMs ?? DEFAULT_HANDS_FREE_MESSAGE_DEBOUNCE_MS),
  );
  const [hasPendingLocalSave, setHasPendingLocalSave] = useState(false);
  const [pendingRemoteSaveKeys, setPendingRemoteSaveKeys] = useState<string[]>([]);
  const [isSavingAllSettings, setIsSavingAllSettings] = useState(false);
  const [saveStatusMessage, setSaveStatusMessage] = useState<string | null>(null);
  const { setCurrentProfile: setProfileContext } = useProfile();
  const sessionStore = useSessionContext();
  const connectionManager = useConnectionManager();

  // Push notification state
  const {
    permissionStatus: notificationPermission,
    isSupported: notificationsSupported,
    isRegistered: notificationsRegistered,
    isLoading: isNotificationLoading,
    register: registerPush,
    unregister: unregisterPush,
  } = usePushNotifications();

  // Remote settings state
  const [profiles, setProfiles] = useState<Profile[]>([]);
  const [currentProfileId, setCurrentProfileId] = useState<string | undefined>();
  const [mcpServers, setMcpServers] = useState<MCPServer[]>([]);
  const [remoteSettings, setRemoteSettings] = useState<Settings | null>(null);
  const [isLoadingRemote, setIsLoadingRemote] = useState(false);
  const [remoteError, setRemoteError] = useState<string | null>(null);
  const [isRefreshing, setIsRefreshing] = useState(false);
  // Track if the server is a DotAgents desktop server (supports our settings API)
  const [isDotAgentsServer, setIsDotAgentsServer] = useState(false);

  // Skills, Knowledge Notes, Agents, and Loops state
  const [skills, setSkills] = useState<Skill[]>([]);
  const [selectedSkillIds, setSelectedSkillIds] = useState<Set<string>>(new Set());
  const [showSkillImportModal, setShowSkillImportModal] = useState(false);
  const [showSkillGitHubImportModal, setShowSkillGitHubImportModal] = useState(false);
  const [skillImportMarkdownText, setSkillImportMarkdownText] = useState('');
  const [skillGitHubImportText, setSkillGitHubImportText] = useState('');
  const [isImportingSkillMarkdown, setIsImportingSkillMarkdown] = useState(false);
  const [isImportingSkillGitHub, setIsImportingSkillGitHub] = useState(false);
  const [isExportingSkillMarkdownId, setIsExportingSkillMarkdownId] = useState<string | null>(null);
  const [knowledgeNotes, setKnowledgeNotes] = useState<KnowledgeNote[]>([]);
  const [knowledgeNoteSearchQuery, setKnowledgeNoteSearchQuery] = useState('');
  const [knowledgeNoteSearchResults, setKnowledgeNoteSearchResults] = useState<KnowledgeNote[]>([]);
  const [selectedKnowledgeNoteIds, setSelectedKnowledgeNoteIds] = useState<Set<string>>(new Set());
  const [knowledgeNoteContextFilter, setKnowledgeNoteContextFilter] = useState<'all' | KnowledgeNoteContext>('all');
  const [knowledgeNoteDateFilter, setKnowledgeNoteDateFilter] = useState<KnowledgeNoteDateFilter>('all');
  const [knowledgeNoteSortOption, setKnowledgeNoteSortOption] = useState<KnowledgeNoteSort>('relevance');
  const [agentProfiles, setAgentProfiles] = useState<AgentProfile[]>([]);
  const [loops, setLoops] = useState<Loop[]>([]);
  const [isLoadingSkills, setIsLoadingSkills] = useState(false);
  const [isLoadingKnowledgeNotes, setIsLoadingKnowledgeNotes] = useState(false);
  const [isSearchingKnowledgeNotes, setIsSearchingKnowledgeNotes] = useState(false);
  const [isLoadingAgentProfiles, setIsLoadingAgentProfiles] = useState(false);
  const [isReloadingAgentProfiles, setIsReloadingAgentProfiles] = useState(false);
  const [isLoadingLoops, setIsLoadingLoops] = useState(false);
  const [isImportingLoopMarkdown, setIsImportingLoopMarkdown] = useState(false);
  const [isExportingLoopMarkdownId, setIsExportingLoopMarkdownId] = useState<string | null>(null);
  const [showLoopImportModal, setShowLoopImportModal] = useState(false);
  const [loopImportMarkdownText, setLoopImportMarkdownText] = useState('');
  const trimmedKnowledgeNoteSearchQuery = knowledgeNoteSearchQuery.trim();
  const knowledgeNoteFilterRequest = useMemo(() => ({
    context: knowledgeNoteContextFilter === 'all' ? undefined : knowledgeNoteContextFilter,
    dateFilter: knowledgeNoteDateFilter,
    sort: knowledgeNoteSortOption,
    limit: 100,
  }), [knowledgeNoteContextFilter, knowledgeNoteDateFilter, knowledgeNoteSortOption]);
  const displayedKnowledgeNotes = trimmedKnowledgeNoteSearchQuery ? knowledgeNoteSearchResults : knowledgeNotes;
  const displayedKnowledgeNoteIds = useMemo(
    () => new Set(displayedKnowledgeNotes.map(note => note.id)),
    [displayedKnowledgeNotes]
  );
  const visibleSelectedKnowledgeNoteIds = useMemo(
    () => Array.from(selectedKnowledgeNoteIds).filter(id => displayedKnowledgeNoteIds.has(id)),
    [displayedKnowledgeNoteIds, selectedKnowledgeNoteIds]
  );
  const sortedAgentProfiles = useMemo(() => sortAgentProfilesForSettings(agentProfiles), [agentProfiles]);
  const displaySkills = useMemo(() => [...skills].sort((a, b) => {
    const enabledDiff = Number(b.enabledForProfile) - Number(a.enabledForProfile);
    if (enabledDiff !== 0) return enabledDiff;
    return a.name.localeCompare(b.name);
  }), [skills]);
  const displaySkillIds = useMemo(
    () => new Set(displaySkills.map(skill => skill.id)),
    [displaySkills]
  );
  const visibleSelectedSkillIds = useMemo(
    () => Array.from(selectedSkillIds).filter(id => displaySkillIds.has(id)),
    [displaySkillIds, selectedSkillIds]
  );
  const availableAcpMainAgents = useMemo(
    () => getAcpxMainAgentOptions(remoteSettings, agentProfiles),
    [remoteSettings, agentProfiles]
  );

  // Profile import/export state
  const [isExportingProfile, setIsExportingProfile] = useState(false);
  const [isImportingProfile, setIsImportingProfile] = useState(false);
  const [showImportModal, setShowImportModal] = useState(false);
  const [importJsonText, setImportJsonText] = useState('');
  const [isExportingBundle, setIsExportingBundle] = useState(false);
  const [isPreviewingBundleImport, setIsPreviewingBundleImport] = useState(false);
  const [isImportingBundle, setIsImportingBundle] = useState(false);
  const [showBundleImportModal, setShowBundleImportModal] = useState(false);
  const [bundleImportJsonText, setBundleImportJsonText] = useState('');
  const [bundleImportPreview, setBundleImportPreview] = useState<BundleImportPreview | null>(null);
  const [bundleImportConflictStrategy, setBundleImportConflictStrategy] = useState<BundleImportConflictStrategy>('skip');
  const [bundleImportComponents, setBundleImportComponents] = useState<BundleImportComponentsState>(DEFAULT_BUNDLE_IMPORT_COMPONENTS);

  // MCP server editor state
  const [showMcpServerEditor, setShowMcpServerEditor] = useState(false);
  const [mcpServerEditorMode, setMcpServerEditorMode] = useState<McpServerEditorMode>('create');
  const [mcpServerDraft, setMcpServerDraft] = useState<McpServerDraft>(EMPTY_MCP_SERVER_DRAFT);
  const [isSavingMcpServer, setIsSavingMcpServer] = useState(false);
  const [isLoadingMcpServerConfig, setIsLoadingMcpServerConfig] = useState(false);
  const [mcpOAuthStatus, setMcpOAuthStatus] = useState<Record<string, McpOAuthStatusResponse>>({});
  const [pendingMcpOAuthAction, setPendingMcpOAuthAction] = useState<string | null>(null);
  const [showMcpImportModal, setShowMcpImportModal] = useState(false);
  const [mcpImportJsonText, setMcpImportJsonText] = useState('');
  const [isImportingMcpServers, setIsImportingMcpServers] = useState(false);
  const [isExportingMcpServers, setIsExportingMcpServers] = useState(false);
  const [chatGptWebAuthStatus, setChatGptWebAuthStatus] = useState<ChatGptWebAuthStatus | null>(null);
  const [pendingChatGptWebAuthAction, setPendingChatGptWebAuthAction] = useState<'login' | 'logout' | null>(null);

  // Model picker state
  const [availableModels, setAvailableModels] = useState<ModelInfo[]>([]);
  const [isLoadingModels, setIsLoadingModels] = useState(false);
  const [showModelPicker, setShowModelPicker] = useState(false);
  const [modelSearchQuery, setModelSearchQuery] = useState('');
  const [useCustomModel, setUseCustomModel] = useState(false);

  // Preset picker state
  const [showPresetPicker, setShowPresetPicker] = useState(false);
  const [showPresetEditor, setShowPresetEditor] = useState(false);
  const [presetEditorMode, setPresetEditorMode] = useState<ModelPresetEditorMode>('create');
  const [presetDraft, setPresetDraft] = useState<ModelPresetDraft>(EMPTY_MODEL_PRESET_DRAFT);
  const [isSavingPreset, setIsSavingPreset] = useState(false);

  // TTS voice/model picker state
  const [showTtsVoicePicker, setShowTtsVoicePicker] = useState(false);
  const [showTtsModelPicker, setShowTtsModelPicker] = useState(false);

  // Custom model input state (for debouncing)
  const [customModelDraft, setCustomModelDraft] = useState('');
  const modelUpdateTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  const routeInitialSettingsSection = isAppShellMobileSettingsSectionId(route?.params?.initialSection)
    ? route.params.initialSection
    : null;
  const isDesktopSettingsLayout = resolveAppShellLayout(width) === 'desktop';
  const [activeDesktopSettingsNavItemId, setActiveDesktopSettingsNavItemId] = useState<AppShellSettingsNavItemId>(
    routeInitialSettingsSection
      ? getAppShellDesktopSettingsNavItemIdForMobileSection(routeInitialSettingsSection)
      : 'general',
  );
  const activeDesktopSettingsSectionIds = useMemo(
    () => new Set(getAppShellMobileSettingsSectionIdsForDesktopNavItem(activeDesktopSettingsNavItemId)),
    [activeDesktopSettingsNavItemId],
  );

  useEffect(() => {
    if (!routeInitialSettingsSection) return;
    setActiveDesktopSettingsNavItemId(
      getAppShellDesktopSettingsNavItemIdForMobileSection(routeInitialSettingsSection),
    );
  }, [routeInitialSettingsSection]);

  // Collapsible section state - all new sections start collapsed
  const [expandedSections, setExpandedSections] = useState<Record<string, boolean>>(
    getAppShellMobileSettingsInitialExpandedState,
  );

  // Debounced input state for string/number fields
  const [inputDrafts, setInputDrafts] = useState<Record<string, string>>({});
  const inputTimeoutRefs = useRef<Record<string, ReturnType<typeof setTimeout>>>({});

  const styles = useMemo(() => createStyles(theme), [theme]);

  useEffect(() => {
    setDraft(config);
    setHandsFreeDebounceInput(
      String(config.handsFreeMessageDebounceMs ?? DEFAULT_HANDS_FREE_MESSAGE_DEBOUNCE_MS),
    );
    setHasPendingLocalSave(false);
  }, [ready, config]);

  const markRemotePending = useCallback((key: string) => {
    setPendingRemoteSaveKeys((prev) => (prev.includes(key) ? prev : [...prev, key]));
  }, []);

  const clearRemotePending = useCallback((key: string) => {
    setPendingRemoteSaveKeys((prev) => prev.filter((entry) => entry !== key));
  }, []);

  const clearAllRemoteTimeouts = useCallback(() => {
    Object.entries(inputTimeoutRefs.current).forEach(([key, timeout]) => {
      clearTimeout(timeout);
      delete inputTimeoutRefs.current[key];
    });
    if (modelUpdateTimeoutRef.current) {
      clearTimeout(modelUpdateTimeoutRef.current);
      modelUpdateTimeoutRef.current = null;
    }
  }, []);

  const updateDraftField = useCallback((patch: Partial<AppConfig>) => {
    setDraft((prev) => ({ ...prev, ...patch }));
    setHasPendingLocalSave(true);
    setSaveStatusMessage(null);
  }, []);

  // Keep a ref to the latest draft so multiple synchronous updateLocalConfig
  // calls within the same tick compose instead of clobbering each other.
  // This matters for flows like TTSSettings.handleVoiceSelect which calls
  // onTtsProviderChange + onEdgeTtsVoiceChange back-to-back: without the ref,
  // the second call would read a stale `draft` from closure and overwrite
  // the first call's patch.
  const draftRef = useRef(draft);
  useEffect(() => {
    draftRef.current = draft;
  }, [draft]);

  const updateLocalConfig = useCallback((patch: Partial<AppConfig>) => {
    const next = { ...draftRef.current, ...patch };
    draftRef.current = next;
    setDraft(next);
    setConfig(next);
    void saveConfig(next);
    setHasPendingLocalSave(false);
    setSaveStatusMessage('Saved');
  }, [setConfig]);

  const handleHandsFreeDebounceInputChange = useCallback((value: string) => {
    const sanitized = value.replace(/[^0-9]/g, '');
    setHandsFreeDebounceInput(sanitized);
    updateDraftField({
      handsFreeMessageDebounceMs: sanitized ? Number(sanitized) : undefined,
    });
  }, [updateDraftField]);

  const commitHandsFreeDebounceInput = useCallback(() => {
    const trimmed = handsFreeDebounceInput.trim();
    const fallbackValue = draft.handsFreeMessageDebounceMs ?? DEFAULT_HANDS_FREE_MESSAGE_DEBOUNCE_MS;

    if (!trimmed) {
      setHandsFreeDebounceInput(String(DEFAULT_HANDS_FREE_MESSAGE_DEBOUNCE_MS));
      updateLocalConfig({ handsFreeMessageDebounceMs: DEFAULT_HANDS_FREE_MESSAGE_DEBOUNCE_MS });
      return;
    }

    const parsed = Number(trimmed);
    if (!Number.isFinite(parsed) || parsed < 0) {
      setHandsFreeDebounceInput(String(fallbackValue));
      setDraft((current) => ({
        ...current,
        handsFreeMessageDebounceMs: fallbackValue,
      }));
      return;
    }

    const normalized = Math.round(parsed);
    setHandsFreeDebounceInput(String(normalized));
    updateLocalConfig({ handsFreeMessageDebounceMs: normalized });
  }, [draft.handsFreeMessageDebounceMs, handsFreeDebounceInput, updateLocalConfig]);

  // Create settings API client when we have valid credentials
  const settingsClient = useMemo(() => {
    if (config.baseUrl && config.apiKey) {
      return new ExtendedSettingsApiClient(config.baseUrl, config.apiKey);
    }
    return null;
  }, [config.baseUrl, config.apiKey]);

  const refreshMcpOAuthStatuses = useCallback(async (servers: MCPServer[]) => {
    if (!settingsClient || servers.length === 0) {
      setMcpOAuthStatus({});
      return;
    }

    const entries = await Promise.all(
      servers.map(async (server) => {
        try {
          return [server.name, await settingsClient.getMcpOAuthStatus(server.name)] as const;
        } catch (error: any) {
          return [
            server.name,
            {
              configured: false,
              authenticated: false,
              error: error?.message || 'Failed to load OAuth status',
            },
          ] as const;
        }
      }),
    );
    setMcpOAuthStatus(Object.fromEntries(entries));
  }, [settingsClient]);

  const refreshChatGptWebAuthStatus = useCallback(async () => {
    if (!settingsClient) {
      setChatGptWebAuthStatus(null);
      return null;
    }

    try {
      const status = await settingsClient.getChatGptWebAuthStatus();
      setChatGptWebAuthStatus(status);
      return status;
    } catch (error) {
      console.warn('[Settings] Failed to load ChatGPT Web auth status:', error);
      setChatGptWebAuthStatus(null);
      return null;
    }
  }, [settingsClient]);

  // Clear pending model update timeout when settingsClient changes
  // to prevent sending updates to the previous server
  useEffect(() => {
    if (modelUpdateTimeoutRef.current) {
      clearTimeout(modelUpdateTimeoutRef.current);
      modelUpdateTimeoutRef.current = null;
    }
  }, [settingsClient]);

  // Fetch remote settings from desktop
  const fetchRemoteSettings = useCallback(async () => {
    if (!settingsClient) {
      setProfiles([]);
      setMcpServers([]);
      setMcpOAuthStatus({});
      setChatGptWebAuthStatus(null);
      setRemoteSettings(null);
      setIsDotAgentsServer(false);
      return;
    }

    setIsLoadingRemote(true);
    setRemoteError(null);

    try {
      const errors: string[] = [];
      let successCount = 0;

      const [profilesRes, serversRes, settingsRes] = await Promise.all([
        settingsClient.getProfiles().catch((e) => { errors.push('profiles'); return null; }),
        settingsClient.getMCPServers().catch((e) => { errors.push('MCP servers'); return null; }),
        settingsClient.getSettings().catch((e) => { errors.push('settings'); return null; }),
      ]);

      if (profilesRes) {
        setProfiles(profilesRes.profiles);
        setCurrentProfileId(profilesRes.currentProfileId);
        successCount++;
      }
      if (serversRes) {
        setMcpServers(serversRes.servers);
        void refreshMcpOAuthStatuses(serversRes.servers);
        successCount++;
      }
      if (settingsRes) {
        setRemoteSettings(settingsRes);
        // Sync input drafts from fetched settings (only on explicit fetch,
        // not on optimistic local updates, to avoid overwriting user's typing)
        setInputDrafts({
          sttLanguage: settingsRes.sttLanguage || '',
          openaiSttLanguage: settingsRes.openaiSttLanguage || '',
          openaiSttModel: settingsRes.openaiSttModel || '',
          groqSttLanguage: settingsRes.groqSttLanguage || '',
          groqSttModel: settingsRes.groqSttModel || '',
          groqSttPrompt: settingsRes.groqSttPrompt || '',
          transcriptPostProcessingPrompt: settingsRes.transcriptPostProcessingPrompt || '',
          transcriptPostProcessingOpenaiModel: settingsRes.transcriptPostProcessingOpenaiModel || '',
          transcriptPostProcessingGroqModel: settingsRes.transcriptPostProcessingGroqModel || '',
          transcriptPostProcessingGeminiModel: settingsRes.transcriptPostProcessingGeminiModel || '',
          transcriptPostProcessingChatgptWebModel: settingsRes.transcriptPostProcessingChatgptWebModel || '',
          mcpMaxIterations: String(settingsRes.mcpMaxIterations ?? 10),
          mcpAutoPasteDelay: String(settingsRes.mcpAutoPasteDelay ?? DEFAULT_MCP_AUTO_PASTE_DELAY),
          maxConversationsToKeep: String(settingsRes.maxConversationsToKeep ?? DEFAULT_MAX_CONVERSATIONS_TO_KEEP),
          whatsappAllowFrom: formatConfigListInput(settingsRes.whatsappAllowFrom),
          discordBotToken: settingsRes.discordBotToken === SECRET_MASK ? '' : (settingsRes.discordBotToken || ''),
          discordAllowUserIds: formatConfigListInput(settingsRes.discordAllowUserIds, { separator: 'newline' }),
          discordAllowGuildIds: formatConfigListInput(settingsRes.discordAllowGuildIds, { separator: 'newline' }),
          discordAllowChannelIds: formatConfigListInput(settingsRes.discordAllowChannelIds, { separator: 'newline' }),
          discordAllowRoleIds: formatConfigListInput(settingsRes.discordAllowRoleIds, { separator: 'newline' }),
          discordDmAllowUserIds: formatConfigListInput(settingsRes.discordDmAllowUserIds, { separator: 'newline' }),
          openaiApiKey: settingsRes.openaiApiKey === SECRET_MASK ? '' : (settingsRes.openaiApiKey || ''),
          openaiBaseUrl: settingsRes.openaiBaseUrl || '',
          groqApiKey: settingsRes.groqApiKey === SECRET_MASK ? '' : (settingsRes.groqApiKey || ''),
          groqBaseUrl: settingsRes.groqBaseUrl || '',
          geminiApiKey: settingsRes.geminiApiKey === SECRET_MASK ? '' : (settingsRes.geminiApiKey || ''),
          geminiBaseUrl: settingsRes.geminiBaseUrl || '',
          chatgptWebAccessToken: settingsRes.chatgptWebAccessToken === SECRET_MASK ? '' : (settingsRes.chatgptWebAccessToken || ''),
          chatgptWebSessionToken: settingsRes.chatgptWebSessionToken === SECRET_MASK ? '' : (settingsRes.chatgptWebSessionToken || ''),
          chatgptWebBaseUrl: settingsRes.chatgptWebBaseUrl || '',
          langfusePublicKey: settingsRes.langfusePublicKey || '',
          langfuseSecretKey: settingsRes.langfuseSecretKey === SECRET_MASK ? '' : (settingsRes.langfuseSecretKey || ''),
          langfuseBaseUrl: settingsRes.langfuseBaseUrl || '',
          localTraceLogPath: settingsRes.localTraceLogPath || '',
        });
        successCount++;
      }

      // Consider it a DotAgents server if at least one endpoint succeeded
      // This gates the Desktop Settings section for non-DotAgents endpoints (e.g., OpenAI)
      setIsDotAgentsServer(successCount > 0);
      if (successCount > 0) {
        void refreshChatGptWebAuthStatus();
      }

      // Show error if any endpoint failed but at least one succeeded
      if (errors.length > 0 && successCount > 0) {
        setRemoteError(`Failed to load: ${errors.join(', ')}`);
      } else if (successCount === 0) {
        // All endpoints failed - not a DotAgents server
        setIsDotAgentsServer(false);
      }
    } catch (error: any) {
      console.error('[Settings] Failed to fetch remote settings:', error);
      setRemoteError(error.message || 'Failed to load remote settings');
      setIsDotAgentsServer(false);
    } finally {
      setIsLoadingRemote(false);
    }
  }, [refreshChatGptWebAuthStatus, refreshMcpOAuthStatuses, settingsClient]);

  // Fetch skills from desktop
  const fetchSkills = useCallback(async () => {
    if (!settingsClient) return;
    setIsLoadingSkills(true);
    try {
      const res = await settingsClient.getSkills();
      setSkills(res.skills);
    } catch (error: any) {
      console.error('[Settings] Failed to fetch skills:', error);
    } finally {
      setIsLoadingSkills(false);
    }
  }, [settingsClient]);

  // Fetch knowledge notes from desktop
  const fetchKnowledgeNotes = useCallback(async () => {
    if (!settingsClient) return;
    setIsLoadingKnowledgeNotes(true);
    try {
      const res = await settingsClient.getKnowledgeNotes(knowledgeNoteFilterRequest);
      setKnowledgeNotes(res.notes);
    } catch (error: any) {
      console.error('[Settings] Failed to fetch knowledge notes:', error);
    } finally {
      setIsLoadingKnowledgeNotes(false);
    }
  }, [settingsClient, knowledgeNoteFilterRequest]);

  // Fetch agent profiles from desktop
  const fetchAgentProfiles = useCallback(async () => {
    if (!settingsClient) return;
    setIsLoadingAgentProfiles(true);
    try {
      const res = await settingsClient.getAgentProfiles();
      setAgentProfiles(res.profiles);
    } catch (error: any) {
      console.error('[Settings] Failed to fetch agent profiles:', error);
    } finally {
      setIsLoadingAgentProfiles(false);
    }
  }, [settingsClient]);

  const handleAgentProfilesReload = useCallback(async () => {
    if (!settingsClient || isReloadingAgentProfiles) return;
    setIsReloadingAgentProfiles(true);
    try {
      const res = await settingsClient.reloadAgentProfiles();
      setAgentProfiles(res.profiles);
      setSaveStatusMessage('Agent profile files rescanned.');
    } catch (error: any) {
      console.error('[Settings] Failed to reload agent profiles:', error);
      setRemoteError(error.message || 'Failed to reload agent profile files');
    } finally {
      setIsReloadingAgentProfiles(false);
    }
  }, [isReloadingAgentProfiles, settingsClient]);

  // Fetch loops from desktop
  const fetchLoops = useCallback(async () => {
    if (!settingsClient) return;
    setIsLoadingLoops(true);
    try {
      const res = await settingsClient.getLoops();
      setLoops(res.loops);
    } catch (error: any) {
      console.error('[Settings] Failed to fetch loops:', error);
    } finally {
      setIsLoadingLoops(false);
    }
  }, [settingsClient]);

  // Fetch remote settings when client becomes available
  useEffect(() => {
    if (settingsClient) {
      fetchRemoteSettings();
    }
  }, [settingsClient, fetchRemoteSettings]);

  // Fetch DotAgents-specific data only after confirming it's a DotAgents server
  useEffect(() => {
    if (settingsClient && isDotAgentsServer) {
      fetchSkills();
      fetchKnowledgeNotes();
      fetchAgentProfiles();
      fetchLoops();
    }
  }, [settingsClient, isDotAgentsServer, fetchSkills, fetchKnowledgeNotes, fetchAgentProfiles, fetchLoops]);

  useEffect(() => {
    setSelectedKnowledgeNoteIds(new Set());
  }, [trimmedKnowledgeNoteSearchQuery, knowledgeNoteContextFilter, knowledgeNoteDateFilter, knowledgeNoteSortOption]);

  useEffect(() => {
    if (!settingsClient || !isDotAgentsServer) return;
    if (!trimmedKnowledgeNoteSearchQuery) {
      setKnowledgeNoteSearchResults([]);
      setIsSearchingKnowledgeNotes(false);
      return;
    }

    let cancelled = false;
    setIsSearchingKnowledgeNotes(true);
    const timeout = setTimeout(() => {
      settingsClient.searchKnowledgeNotes({
        ...knowledgeNoteFilterRequest,
        query: trimmedKnowledgeNoteSearchQuery,
        limit: 100,
      })
        .then((res) => {
          if (!cancelled) setKnowledgeNoteSearchResults(res.notes);
        })
        .catch((error: any) => {
          if (!cancelled) {
            console.error('[Settings] Failed to search knowledge notes:', error);
          }
        })
        .finally(() => {
          if (!cancelled) setIsSearchingKnowledgeNotes(false);
        });
    }, 250);

    return () => {
      cancelled = true;
      clearTimeout(timeout);
    };
  }, [settingsClient, isDotAgentsServer, trimmedKnowledgeNoteSearchQuery, knowledgeNoteFilterRequest]);

  // Refresh key remote data when returning from nested screens (e.g. agent editor)
  useEffect(() => {
    const unsubscribe = navigation.addListener('focus', () => {
      if (!settingsClient) return;
      fetchRemoteSettings();
      if (isDotAgentsServer) {
        fetchAgentProfiles();
        fetchKnowledgeNotes();
        fetchLoops();
      }
    });
    return unsubscribe;
  }, [navigation, settingsClient, isDotAgentsServer, fetchRemoteSettings, fetchAgentProfiles, fetchKnowledgeNotes, fetchLoops]);

  // Handle pull-to-refresh
  const onRefresh = useCallback(async () => {
    setIsRefreshing(true);
    const fetches: Promise<void>[] = [fetchRemoteSettings()];
    if (isDotAgentsServer) {
      fetches.push(fetchSkills(), fetchKnowledgeNotes(), fetchAgentProfiles(), fetchLoops());
    }
    await Promise.all(fetches);
    setIsRefreshing(false);
  }, [isDotAgentsServer, fetchRemoteSettings, fetchSkills, fetchKnowledgeNotes, fetchAgentProfiles, fetchLoops]);

  // Handle profile switch
  const handleProfileSwitch = async (profileId: string) => {
    if (!settingsClient || profileId === currentProfileId) return;

    try {
      await settingsClient.setCurrentProfile(profileId);
      setCurrentProfileId(profileId);
      // Update the profile context so the header badge updates immediately
      const selectedProfile = profiles.find(p => p.id === profileId);
      if (selectedProfile) {
        setProfileContext(selectedProfile);
      }
      // Refresh MCP servers and skills as they may have changed with the profile
      const serversRes = await settingsClient.getMCPServers();
      setMcpServers(serversRes.servers);
      void refreshMcpOAuthStatuses(serversRes.servers);
      // Skills enabledForProfile is profile-specific, so refetch after switch
      if (isDotAgentsServer) {
        fetchSkills();
      }
    } catch (error: any) {
      console.error('[Settings] Failed to switch profile:', error);
      setRemoteError(error.message || 'Failed to switch profile');
    }
  };

  // Handle profile export
  const handleExportProfile = async () => {
    if (!settingsClient || !currentProfileId) return;

    setIsExportingProfile(true);
    try {
      const result = await settingsClient.exportProfile(currentProfileId);
      await Share.share({
        message: result.profileJson,
        title: 'Export Profile',
      });
    } catch (error: any) {
      console.error('[Settings] Failed to export profile:', error);
      Alert.alert('Export Failed', error.message || 'Failed to export profile');
    } finally {
      setIsExportingProfile(false);
    }
  };

  // Handle profile import
  const handleImportProfile = async () => {
    if (!settingsClient || !importJsonText.trim()) return;

    setIsImportingProfile(true);
    try {
      const result = await settingsClient.importProfile(importJsonText.trim());
      // Import succeeded - close modal and show success first
      setShowImportModal(false);
      setImportJsonText('');
      Alert.alert('Success', `Profile "${result.profile.name}" imported successfully`);

      // Refresh profiles list separately - don't show import failure if only refresh fails
      try {
        const profilesRes = await settingsClient.getProfiles();
        setProfiles(profilesRes.profiles);
        setCurrentProfileId(profilesRes.currentProfileId);
      } catch (refreshError: any) {
        console.error('[Settings] Failed to refresh profiles after import:', refreshError);
        // Don't show error alert - import was successful, just log the refresh issue
      }
    } catch (error: any) {
      console.error('[Settings] Failed to import profile:', error);
      Alert.alert('Import Failed', error.message || 'Failed to import profile');
    } finally {
      setIsImportingProfile(false);
    }
  };

  const shareBundleExport = useCallback(async () => {
    if (!settingsClient || isExportingBundle) return;

    setIsExportingBundle(true);
    setRemoteError(null);
    try {
      const result = await settingsClient.exportBundle({ name: 'DotAgents Bundle' });
      const itemCount =
        result.bundle.agentProfiles.length
        + result.bundle.mcpServers.length
        + result.bundle.skills.length
        + result.bundle.repeatTasks.length
        + result.bundle.knowledgeNotes.length;
      await Share.share({
        message: result.bundleJson,
        title: `${result.bundle.manifest.name}.dotagents`,
      });
      setSaveStatusMessage(`Exported bundle with ${itemCount} item${itemCount === 1 ? '' : 's'}`);
    } catch (error: any) {
      console.error('[Settings] Failed to export bundle:', error);
      Alert.alert('Export Failed', error.message || 'Failed to export bundle');
    } finally {
      setIsExportingBundle(false);
    }
  }, [isExportingBundle, settingsClient]);

  const handleBundleExport = useCallback(() => {
    if (!settingsClient || isExportingBundle) return;

    const message = 'Bundles can include agents, MCP servers, skills, tasks, and knowledge notes. Review the JSON before sharing it.';
    if (Platform.OS === 'web') {
      const confirmFn = (globalThis as { confirm?: (text?: string) => boolean }).confirm;
      if (confirmFn?.(`Export DotAgents Bundle\n\n${message}`)) {
        void shareBundleExport();
      }
      return;
    }

    Alert.alert('Export DotAgents Bundle', message, [
      { text: 'Cancel', style: 'cancel' },
      { text: 'Export', onPress: () => { void shareBundleExport(); } },
    ]);
  }, [isExportingBundle, settingsClient, shareBundleExport]);

  const closeBundleImportModal = useCallback(() => {
    if (isPreviewingBundleImport || isImportingBundle) return;
    setShowBundleImportModal(false);
    setBundleImportJsonText('');
    setBundleImportPreview(null);
    setBundleImportConflictStrategy('skip');
    setBundleImportComponents(DEFAULT_BUNDLE_IMPORT_COMPONENTS);
  }, [isImportingBundle, isPreviewingBundleImport]);

  const handleBundleImportJsonChange = useCallback((value: string) => {
    setBundleImportJsonText(value);
    setBundleImportPreview(null);
  }, []);

  const handleBundleImportComponentToggle = useCallback((key: BundleComponentKey, value: boolean) => {
    setBundleImportComponents(prev => ({ ...prev, [key]: value }));
  }, []);

  const handleBundleImportPreview = useCallback(async () => {
    if (!settingsClient || !bundleImportJsonText.trim()) return;

    try {
      JSON.parse(bundleImportJsonText.trim());
    } catch {
      Alert.alert('Preview Failed', 'Bundle JSON is invalid');
      return;
    }

    setIsPreviewingBundleImport(true);
    setRemoteError(null);
    try {
      const result = await settingsClient.previewBundleImport({ bundleJson: bundleImportJsonText.trim() });
      setBundleImportPreview(result.preview);
      const components = result.preview.bundle.manifest.components;
      const itemCount = components.agentProfiles + components.mcpServers + components.skills + components.repeatTasks + components.knowledgeNotes;
      setSaveStatusMessage(`Previewed bundle with ${itemCount} item${itemCount === 1 ? '' : 's'}`);
    } catch (error: any) {
      console.error('[Settings] Failed to preview bundle import:', error);
      Alert.alert('Preview Failed', error.message || 'Failed to preview bundle');
    } finally {
      setIsPreviewingBundleImport(false);
    }
  }, [bundleImportJsonText, settingsClient]);

  const refreshAfterBundleImport = useCallback(async () => {
    const refreshes: Promise<void>[] = [fetchRemoteSettings()];
    if (isDotAgentsServer) {
      refreshes.push(fetchSkills(), fetchKnowledgeNotes(), fetchAgentProfiles(), fetchLoops());
    }
    await Promise.allSettled(refreshes);
  }, [fetchAgentProfiles, fetchKnowledgeNotes, fetchLoops, fetchRemoteSettings, fetchSkills, isDotAgentsServer]);

  const handleBundleImport = useCallback(async () => {
    if (!settingsClient || !bundleImportJsonText.trim()) return;
    if (!Object.values(bundleImportComponents).some(Boolean)) {
      Alert.alert('Import Failed', 'Select at least one bundle component to import');
      return;
    }

    setIsImportingBundle(true);
    setRemoteError(null);
    try {
      const result = await settingsClient.importBundle({
        bundleJson: bundleImportJsonText.trim(),
        conflictStrategy: bundleImportConflictStrategy,
        components: bundleImportComponents,
      });

      if (!result.success) {
        throw new Error(result.errors.join(', ') || 'Import failed');
      }

      const importedCount = [
        ...result.agentProfiles,
        ...result.mcpServers,
        ...result.skills,
        ...result.repeatTasks,
        ...result.knowledgeNotes,
      ].filter(item => item.action !== 'skipped').length;

      setShowBundleImportModal(false);
      setBundleImportJsonText('');
      setBundleImportPreview(null);
      setBundleImportConflictStrategy('skip');
      setBundleImportComponents(DEFAULT_BUNDLE_IMPORT_COMPONENTS);
      setSaveStatusMessage(`Imported ${importedCount} bundle item${importedCount === 1 ? '' : 's'}`);
      await refreshAfterBundleImport();
      Alert.alert('Import Complete', `Imported ${importedCount} item${importedCount === 1 ? '' : 's'} from the bundle.`);
    } catch (error: any) {
      console.error('[Settings] Failed to import bundle:', error);
      Alert.alert('Import Failed', error.message || 'Failed to import bundle');
    } finally {
      setIsImportingBundle(false);
    }
  }, [
    bundleImportComponents,
    bundleImportConflictStrategy,
    bundleImportJsonText,
    refreshAfterBundleImport,
    settingsClient,
  ]);

  // Handle MCP server toggle
  const handleServerToggle = async (serverName: string, enabled: boolean) => {
    if (!settingsClient) return;

    try {
      await settingsClient.toggleMCPServer(serverName, enabled);
      // Update local state optimistically
      setMcpServers(prev => setMcpServerRuntimeEnabledInList(prev, serverName, enabled));
    } catch (error: any) {
      console.error('[Settings] Failed to toggle server:', error);
      setRemoteError(error.message || 'Failed to toggle server');
      // Refresh to get actual state
      fetchRemoteSettings();
    }
  };

  const handleMcpOAuthStart = useCallback(async (serverName: string) => {
    if (!settingsClient || pendingMcpOAuthAction) return;

    setPendingMcpOAuthAction(`${serverName}:start`);
    setRemoteError(null);
    try {
      await settingsClient.initiateMcpOAuthFlow(serverName);
      setSaveStatusMessage(`Started OAuth for ${serverName}`);
      await refreshMcpOAuthStatuses(mcpServers);
    } catch (error: any) {
      console.error('[Settings] Failed to start MCP OAuth:', error);
      setRemoteError(error.message || 'Failed to start MCP OAuth');
    } finally {
      setPendingMcpOAuthAction(null);
    }
  }, [mcpServers, pendingMcpOAuthAction, refreshMcpOAuthStatuses, settingsClient]);

  const handleMcpOAuthRevoke = useCallback(async (serverName: string) => {
    if (!settingsClient || pendingMcpOAuthAction) return;

    setPendingMcpOAuthAction(`${serverName}:revoke`);
    setRemoteError(null);
    try {
      const result = await settingsClient.revokeMcpOAuthTokens(serverName);
      if (!result.success) {
        throw new Error(result.error || 'Failed to revoke MCP OAuth');
      }
      setSaveStatusMessage(`Revoked OAuth for ${serverName}`);
      await refreshMcpOAuthStatuses(mcpServers);
    } catch (error: any) {
      console.error('[Settings] Failed to revoke MCP OAuth:', error);
      setRemoteError(error.message || 'Failed to revoke MCP OAuth');
    } finally {
      setPendingMcpOAuthAction(null);
    }
  }, [mcpServers, pendingMcpOAuthAction, refreshMcpOAuthStatuses, settingsClient]);

  const openMcpServerCreateEditor = useCallback(() => {
    setMcpServerEditorMode('create');
    setMcpServerDraft(EMPTY_MCP_SERVER_DRAFT);
    setShowMcpServerEditor(true);
  }, []);

  const openMcpServerEditEditor = useCallback(async (server: MCPServer) => {
    if (!settingsClient) return;
    setMcpServerEditorMode('edit');
    setMcpServerDraft({ ...EMPTY_MCP_SERVER_DRAFT, name: server.name, disabled: !!server.configDisabled });
    setShowMcpServerEditor(true);
    setIsLoadingMcpServerConfig(true);
    try {
      const response = await settingsClient.getMCPServerConfig(server.name);
      setMcpServerDraft(createMcpServerDraft(response.name, response.config));
    } catch (error: any) {
      console.error('[Settings] Failed to load MCP server config:', error);
      setRemoteError(error.message || 'Failed to load MCP server config');
    } finally {
      setIsLoadingMcpServerConfig(false);
    }
  }, [settingsClient]);

  const closeMcpServerEditor = useCallback(() => {
    if (isSavingMcpServer) return;
    setShowMcpServerEditor(false);
    setMcpServerDraft(EMPTY_MCP_SERVER_DRAFT);
    setRemoteError(null);
  }, [isSavingMcpServer]);

  const handleMcpServerDraftChange = useCallback(<K extends keyof McpServerDraft>(key: K, value: McpServerDraft[K]) => {
    setMcpServerDraft(prev => ({ ...prev, [key]: value }));
  }, []);

  const handleSaveMcpServer = useCallback(async () => {
    if (!settingsClient || isSavingMcpServer) return;

    const serverName = mcpServerDraft.name.trim();
    if (!serverName) {
      Alert.alert('Missing Server Name', 'Enter a name for this MCP server.');
      return;
    }

    const existingNames = new Set(mcpServers.map(server => server.name));
    if (mcpServerEditorMode === 'create' && existingNames.has(serverName)) {
      Alert.alert('Server Exists', `An MCP server named "${serverName}" already exists.`);
      return;
    }

    let config: MCPServerConfig;
    try {
      config = buildMcpServerConfigFromDraft(mcpServerDraft);
    } catch (error: any) {
      Alert.alert('Invalid MCP Server', error?.message || 'Check the MCP server fields and try again.');
      return;
    }

    setIsSavingMcpServer(true);
    try {
      const response = await settingsClient.upsertMCPServerConfig(serverName, config);
      setMcpServers(prev => upsertMcpServerInList(prev, response.server));
      void refreshMcpOAuthStatuses(upsertMcpServerInList(mcpServers, response.server));
      setSaveStatusMessage(mcpServerEditorMode === 'create' ? 'MCP server added' : 'MCP server saved');
      setShowMcpServerEditor(false);
      setMcpServerDraft(EMPTY_MCP_SERVER_DRAFT);
    } catch (error: any) {
      console.error('[Settings] Failed to save MCP server:', error);
      Alert.alert('Save Failed', error.message || 'Failed to save MCP server');
    } finally {
      setIsSavingMcpServer(false);
    }
  }, [isSavingMcpServer, mcpServerDraft, mcpServerEditorMode, mcpServers, refreshMcpOAuthStatuses, settingsClient]);

  const handleDeleteMcpServer = useCallback((server: MCPServer) => {
    if (!settingsClient) return;
    const deleteServer = async () => {
      try {
        await settingsClient.deleteMCPServerConfig(server.name);
        setMcpServers(prev => removeMcpServerFromList(prev, server.name));
        setMcpOAuthStatus(prev => {
          const { [server.name]: _removed, ...next } = prev;
          return next;
        });
        setSaveStatusMessage('MCP server deleted');
      } catch (error: any) {
        console.error('[Settings] Failed to delete MCP server:', error);
        Alert.alert('Delete Failed', error.message || 'Failed to delete MCP server');
      }
    };

    if (Platform.OS === 'web') {
      const confirmFn = (globalThis as { confirm?: (text?: string) => boolean }).confirm;
      if (confirmFn?.(`Delete MCP Server\n\nDelete "${server.name}" from the desktop MCP configuration?`)) {
        void deleteServer();
      }
      return;
    }

    Alert.alert('Delete MCP Server', `Delete "${server.name}" from the desktop MCP configuration?`, [
      { text: 'Cancel', style: 'cancel' },
      { text: 'Delete', style: 'destructive', onPress: () => { void deleteServer(); } },
    ]);
  }, [settingsClient]);

  const closeMcpImportModal = useCallback(() => {
    if (isImportingMcpServers) return;
    setShowMcpImportModal(false);
    setMcpImportJsonText('');
  }, [isImportingMcpServers]);

  const handleMcpServerImport = useCallback(async () => {
    if (!settingsClient || !mcpImportJsonText.trim() || isImportingMcpServers) return;

    let importConfig: MCPConfig;
    try {
      importConfig = parseMcpConfigImport(mcpImportJsonText);
    } catch (error: any) {
      Alert.alert('Import Failed', error.message || 'Invalid MCP import JSON');
      return;
    }

    setIsImportingMcpServers(true);
    setRemoteError(null);
    try {
      const result = await settingsClient.importMCPServerConfigs(importConfig);
      setShowMcpImportModal(false);
      setMcpImportJsonText('');
      const serversRes = await settingsClient.getMCPServers();
      setMcpServers(serversRes.servers);
      void refreshMcpOAuthStatuses(serversRes.servers);
      setSaveStatusMessage(`Imported ${result.importedCount} MCP server${result.importedCount === 1 ? '' : 's'}`);
      if (result.skippedReservedServerNames.length > 0) {
        Alert.alert('Import Complete', `Imported ${result.importedCount} MCP server${result.importedCount === 1 ? '' : 's'}. Skipped reserved servers: ${result.skippedReservedServerNames.join(', ')}`);
      }
    } catch (error: any) {
      console.error('[Settings] Failed to import MCP servers:', error);
      Alert.alert('Import Failed', error.message || 'Failed to import MCP servers');
    } finally {
      setIsImportingMcpServers(false);
    }
  }, [isImportingMcpServers, mcpImportJsonText, refreshMcpOAuthStatuses, settingsClient]);

  const shareMcpServerExport = useCallback(async () => {
    if (!settingsClient || isExportingMcpServers) return;

    setIsExportingMcpServers(true);
    setRemoteError(null);
    try {
      const result = await settingsClient.exportMCPServerConfigs();
      const serverCount = Object.keys(result.config.mcpServers || {}).length;
      await Share.share({
        message: JSON.stringify(result.config, null, 2),
        title: 'mcp-servers.json',
      });
      setSaveStatusMessage(`Exported ${serverCount} MCP server${serverCount === 1 ? '' : 's'}`);
    } catch (error: any) {
      console.error('[Settings] Failed to export MCP servers:', error);
      Alert.alert('Export Failed', error.message || 'Failed to export MCP servers');
    } finally {
      setIsExportingMcpServers(false);
    }
  }, [isExportingMcpServers, settingsClient]);

  const handleMcpServerExport = useCallback(() => {
    if (!settingsClient || isExportingMcpServers) return;

    const message = 'MCP configs can include headers or environment values. Review the exported JSON before sharing it.';
    if (Platform.OS === 'web') {
      const confirmFn = (globalThis as { confirm?: (text?: string) => boolean }).confirm;
      if (confirmFn?.(`Export MCP Servers\n\n${message}`)) {
        void shareMcpServerExport();
      }
      return;
    }

    Alert.alert('Export MCP Servers', message, [
      { text: 'Cancel', style: 'cancel' },
      { text: 'Export', onPress: () => { void shareMcpServerExport(); } },
    ]);
  }, [isExportingMcpServers, settingsClient, shareMcpServerExport]);

  // Handle remote settings toggle
  const handleRemoteSettingToggle = async (key: keyof Settings, value: boolean) => {
    if (!settingsClient || !remoteSettings) return;

    try {
      await settingsClient.updateSettings({ [key]: value });
      setRemoteSettings(prev => prev ? { ...prev, [key]: value } : null);
    } catch (error: any) {
      console.error('[Settings] Failed to update setting:', error);
      setRemoteError(error.message || 'Failed to update setting');
    }
  };

  // Handle remote settings update for string/number fields (with debounce)
  const handleRemoteSettingUpdate = useCallback((key: keyof SettingsUpdate, value: string | number | string[]) => {
    // Update local draft immediately for responsive UI
    setInputDrafts(prev => ({ ...prev, [key]: String(value) }));
    setRemoteSettings(prev => prev ? { ...prev, [key]: value } : null);
    markRemotePending(String(key));
    setSaveStatusMessage(null);

    // Cancel any pending update for this key
    if (inputTimeoutRefs.current[key]) {
      clearTimeout(inputTimeoutRefs.current[key]);
    }

    // Debounce the actual API call by 1000ms
    inputTimeoutRefs.current[key] = setTimeout(async () => {
      if (!settingsClient) return;

      try {
        await settingsClient.updateSettings({ [key]: value });
        clearRemotePending(String(key));
        delete inputTimeoutRefs.current[key];
      } catch (error: any) {
        console.error(`[Settings] Failed to update ${key}:`, error);
        setRemoteError(error.message || `Failed to update ${key}`);
        // Refresh to get actual state
        fetchRemoteSettings();
      }
    }, 1000);
  }, [clearRemotePending, fetchRemoteSettings, markRemotePending, settingsClient]);

  const handleRemoteSecretDraftChange = useCallback((key: ProviderSecretSettingKey | 'discordBotToken' | 'langfuseSecretKey', value: string) => {
    setInputDrafts(prev => ({ ...prev, [key]: value }));
    markRemotePending(key);
    setSaveStatusMessage(null);
  }, [markRemotePending]);

  const commitRemoteSecretDraft = useCallback(async (key: ProviderSecretSettingKey | 'discordBotToken' | 'langfuseSecretKey') => {
    if (!settingsClient || !pendingRemoteSaveKeys.includes(key)) return;
    const value = inputDrafts[key] ?? '';

    try {
      await settingsClient.updateSettings({ [key]: value });
      setRemoteSettings(prev => prev ? { ...prev, [key]: value ? SECRET_MASK : '' } : null);
      setInputDrafts(prev => ({ ...prev, [key]: '' }));
      clearRemotePending(key);
    } catch (error: any) {
      console.error(`[Settings] Failed to update ${key}:`, error);
      setRemoteError(error.message || `Failed to update ${key}`);
      fetchRemoteSettings();
    }
  }, [clearRemotePending, fetchRemoteSettings, inputDrafts, pendingRemoteSaveKeys, settingsClient]);

  const handleRemoteListSettingUpdate = useCallback((key: DiscordListSettingKey, value: string) => {
    const parsedValues = parseConfigListInput(value, { unique: true });
    setInputDrafts(prev => ({ ...prev, [key]: value }));
    setRemoteSettings(prev => prev ? { ...prev, [key]: parsedValues } : null);
    markRemotePending(key);
    setSaveStatusMessage(null);

    if (inputTimeoutRefs.current[key]) {
      clearTimeout(inputTimeoutRefs.current[key]);
    }

    inputTimeoutRefs.current[key] = setTimeout(async () => {
      if (!settingsClient) return;

      try {
        await settingsClient.updateSettings({ [key]: parsedValues });
        clearRemotePending(key);
        delete inputTimeoutRefs.current[key];
      } catch (error: any) {
        console.error(`[Settings] Failed to update ${key}:`, error);
        setRemoteError(error.message || `Failed to update ${key}`);
        fetchRemoteSettings();
      }
    }, 1000);
  }, [clearRemotePending, fetchRemoteSettings, markRemotePending, settingsClient]);

  // Cleanup input timeouts on unmount
  useEffect(() => {
    return () => {
      Object.values(inputTimeoutRefs.current).forEach(clearTimeout);
    };
  }, []);

  // Toggle section collapse state
  const toggleSection = useCallback((section: string) => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
    setExpandedSections(prev => ({ ...prev, [section]: !prev[section] }));
  }, []);

  const activateDesktopSettingsNavItem = useCallback((itemId: AppShellSettingsNavItemId) => {
    setActiveDesktopSettingsNavItemId(itemId);
    const sections = getAppShellMobileSettingsSectionIdsForDesktopNavItem(itemId);
    setExpandedSections((prev) => ({
      ...prev,
      ...Object.fromEntries(sections.map((section) => [section, true])),
    }));
  }, []);

  // Handle skill toggle for current profile
  const handleSkillToggle = async (skillId: string) => {
    if (!settingsClient) return;
    try {
      const res = await settingsClient.toggleSkillForProfile(skillId);
      // Optimistically update the UI
      setSkills(prev =>
        prev.map(s => (s.id === skillId ? { ...s, enabledForProfile: res.enabledForProfile } : s))
      );
    } catch (error: any) {
      console.error('[Settings] Failed to toggle skill:', error);
      Alert.alert('Error', 'Failed to toggle skill');
    }
  };

  const handleSkillEdit = useCallback((skill?: Skill) => {
    navigation.navigate('SkillEdit', {
      skillId: skill?.id,
      skill,
    });
  }, [navigation]);

  const confirmDestructiveAction = useCallback(
    (title: string, message: string, onConfirm: () => Promise<void> | void, confirmLabel: string = 'Delete') => {
      if (Platform.OS === 'web') {
        const confirmFn = (globalThis as { confirm?: (text?: string) => boolean }).confirm;
        if (!confirmFn) {
          return;
        }
        if (confirmFn(`${title}\n\n${message}`)) {
          void onConfirm();
        }
        return;
      }

      Alert.alert(title, message, [
        { text: 'Cancel', style: 'cancel' },
        {
          text: confirmLabel,
          style: 'destructive',
          onPress: () => {
            void onConfirm();
          },
        },
      ]);
    },
    []
  );

  const toggleSkillSelection = useCallback((skillId: string) => {
    setSelectedSkillIds(prev => {
      const next = new Set(prev);
      if (next.has(skillId)) {
        next.delete(skillId);
      } else {
        next.add(skillId);
      }
      return next;
    });
  }, []);

  const closeSkillImportModal = useCallback(() => {
    if (isImportingSkillMarkdown) return;
    setShowSkillImportModal(false);
    setSkillImportMarkdownText('');
  }, [isImportingSkillMarkdown]);

  const closeSkillGitHubImportModal = useCallback(() => {
    if (isImportingSkillGitHub) return;
    setShowSkillGitHubImportModal(false);
    setSkillGitHubImportText('');
  }, [isImportingSkillGitHub]);

  const handleSkillMarkdownImport = useCallback(async () => {
    if (!settingsClient || isImportingSkillMarkdown || !skillImportMarkdownText.trim()) return;

    setIsImportingSkillMarkdown(true);
    setRemoteError(null);
    try {
      const result = await settingsClient.importSkillFromMarkdown(skillImportMarkdownText.trim());
      setShowSkillImportModal(false);
      setSkillImportMarkdownText('');
      setSaveStatusMessage(`Imported skill "${result.skill.name}"`);
      await fetchSkills();
    } catch (error: any) {
      console.error('[Settings] Failed to import skill from Markdown:', error);
      Alert.alert('Import Failed', error.message || 'Failed to import skill');
    } finally {
      setIsImportingSkillMarkdown(false);
    }
  }, [fetchSkills, isImportingSkillMarkdown, settingsClient, skillImportMarkdownText]);

  const handleSkillGitHubImport = useCallback(async () => {
    if (!settingsClient || isImportingSkillGitHub || !skillGitHubImportText.trim()) return;

    setIsImportingSkillGitHub(true);
    setRemoteError(null);
    try {
      const result = await settingsClient.importSkillFromGitHub(skillGitHubImportText.trim());
      setShowSkillGitHubImportModal(false);
      setSkillGitHubImportText('');
      setSaveStatusMessage(`Imported ${result.imported.length} skill${result.imported.length === 1 ? '' : 's'} from GitHub`);
      if (result.errors.length > 0) {
        Alert.alert('Import Finished With Errors', result.errors.join('\n'));
      }
      await fetchSkills();
    } catch (error: any) {
      console.error('[Settings] Failed to import skill from GitHub:', error);
      Alert.alert('Import Failed', error.message || 'Failed to import skills from GitHub');
    } finally {
      setIsImportingSkillGitHub(false);
    }
  }, [fetchSkills, isImportingSkillGitHub, settingsClient, skillGitHubImportText]);

  const handleSkillMarkdownExport = useCallback(async (skill: Skill) => {
    if (!settingsClient || isExportingSkillMarkdownId) return;

    setIsExportingSkillMarkdownId(skill.id);
    setRemoteError(null);
    try {
      const result = await settingsClient.exportSkillToMarkdown(skill.id);
      await Share.share({
        message: result.markdown,
        title: `${skill.name}.md`,
      });
      setSaveStatusMessage(`Exported skill "${skill.name}"`);
    } catch (error: any) {
      console.error('[Settings] Failed to export skill:', error);
      Alert.alert('Export Failed', error.message || 'Failed to export skill');
    } finally {
      setIsExportingSkillMarkdownId(null);
    }
  }, [isExportingSkillMarkdownId, settingsClient]);

  const handleSelectedSkillsDelete = useCallback(() => {
    if (!settingsClient || visibleSelectedSkillIds.length === 0) return;

    const count = visibleSelectedSkillIds.length;
    confirmDestructiveAction(
      `Delete ${count} skill${count === 1 ? '' : 's'}`,
      'Deleted skills are moved to the local .agents backup folder when possible.',
      async () => {
        try {
          const result = await settingsClient.deleteSkills(visibleSelectedSkillIds);
          const deletedIds = new Set(result.results.filter(item => item.success).map(item => item.id));
          setSelectedSkillIds(prev => new Set(Array.from(prev).filter(id => !deletedIds.has(id))));
          setSaveStatusMessage(`Deleted ${result.deletedCount} skill${result.deletedCount === 1 ? '' : 's'}`);
          await fetchSkills();
          const failed = result.results.filter(item => !item.success);
          if (failed.length > 0) {
            Alert.alert('Delete Finished With Errors', failed.map(item => `${item.id}: ${item.error || 'Failed'}`).join('\n'));
          }
        } catch (error: any) {
          console.error('[Settings] Failed to delete selected skills:', error);
          Alert.alert('Delete Failed', error.message || 'Failed to delete selected skills');
        }
      }
    );
  }, [confirmDestructiveAction, fetchSkills, settingsClient, visibleSelectedSkillIds]);

  const handleChatGptWebOAuthLogin = useCallback(async () => {
    if (!settingsClient || pendingChatGptWebAuthAction) return;

    setPendingChatGptWebAuthAction('login');
    setRemoteError(null);
    setSaveStatusMessage(null);
    try {
      const result = await settingsClient.loginChatGptWebOAuth();
      if (!result.success) {
        throw new Error(result.error || result.message || 'Failed to connect OpenAI Codex');
      }
      setChatGptWebAuthStatus(result.status);
      setSaveStatusMessage(result.message);
    } catch (error: any) {
      console.error('[Settings] Failed to connect ChatGPT Web OAuth:', error);
      setRemoteError(error.message || 'Failed to connect OpenAI Codex');
    } finally {
      setPendingChatGptWebAuthAction(null);
    }
  }, [pendingChatGptWebAuthAction, settingsClient]);

  const handleChatGptWebOAuthLogout = useCallback(() => {
    if (!settingsClient || pendingChatGptWebAuthAction) return;

    confirmDestructiveAction(
      'Disconnect OpenAI Codex',
      'Remove the saved OpenAI Codex OAuth tokens from this desktop app?',
      async () => {
        setPendingChatGptWebAuthAction('logout');
        setRemoteError(null);
        setSaveStatusMessage(null);
        try {
          const result = await settingsClient.logoutChatGptWebOAuth();
          if (!result.success) {
            throw new Error(result.error || result.message || 'Failed to disconnect OpenAI Codex');
          }
          setChatGptWebAuthStatus(result.status);
          setSaveStatusMessage(result.message);
        } catch (error: any) {
          console.error('[Settings] Failed to disconnect ChatGPT Web OAuth:', error);
          setRemoteError(error.message || 'Failed to disconnect OpenAI Codex');
        } finally {
          setPendingChatGptWebAuthAction(null);
        }
      },
      'Disconnect',
    );
  }, [confirmDestructiveAction, pendingChatGptWebAuthAction, settingsClient]);

  const handleClearAllChats = useCallback(() => {
    const hasDesktopConversations = sessionStore.sessions.some(session => !!session.serverConversationId);
    confirmDestructiveAction(
      'Clear All Chats',
      hasDesktopConversations && settingsClient
        ? 'Delete all chats from this mobile app and the connected desktop server? This cannot be undone.'
        : 'Are you sure you want to delete all chats from this mobile app? This cannot be undone.',
      async () => {
        try {
          if (hasDesktopConversations && settingsClient) {
            await settingsClient.deleteAllConversations();
          }
          connectionManager.manager.cleanupAll();
          await sessionStore.clearAllSessions();
        } catch (error: any) {
          Alert.alert('Clear Failed', error?.message || 'Failed to clear chats');
        }
      },
      'Delete All'
    );
  }, [confirmDestructiveAction, connectionManager, sessionStore, settingsClient]);

  const toggleKnowledgeNoteSelection = useCallback((noteId: string) => {
    setSelectedKnowledgeNoteIds(prev => {
      const next = new Set(prev);
      if (next.has(noteId)) {
        next.delete(noteId);
      } else {
        next.add(noteId);
      }
      return next;
    });
  }, []);

  const handleKnowledgeNoteSelectVisible = useCallback(() => {
    setSelectedKnowledgeNoteIds(prev => {
      const next = new Set(prev);
      const allVisibleSelected = displayedKnowledgeNotes.length > 0
        && displayedKnowledgeNotes.every(note => next.has(note.id));
      displayedKnowledgeNotes.forEach(note => {
        if (allVisibleSelected) {
          next.delete(note.id);
        } else {
          next.add(note.id);
        }
      });
      return next;
    });
  }, [displayedKnowledgeNotes]);

  // Handle knowledge note delete
  const handleKnowledgeNoteDelete = useCallback((noteId: string) => {
    if (!settingsClient) return;
    confirmDestructiveAction('Delete Note', 'Are you sure you want to delete this note?', async () => {
      try {
        await settingsClient.deleteKnowledgeNote(noteId);
        setKnowledgeNotes(prev => prev.filter(note => note.id !== noteId));
        setKnowledgeNoteSearchResults(prev => prev.filter(note => note.id !== noteId));
        setSelectedKnowledgeNoteIds(prev => {
          const next = new Set(prev);
          next.delete(noteId);
          return next;
        });
      } catch (error: any) {
        console.error('[Settings] Failed to delete knowledge note:', error);
        Alert.alert('Error', 'Failed to delete note');
      }
    });
  }, [confirmDestructiveAction, settingsClient]);

  const handleKnowledgeNoteDeleteMultiple = useCallback(() => {
    if (!settingsClient || visibleSelectedKnowledgeNoteIds.length === 0) return;

    confirmDestructiveAction(
      'Delete Notes',
      `Delete ${visibleSelectedKnowledgeNoteIds.length} selected notes? This cannot be undone.`,
      async () => {
        try {
          await settingsClient.deleteKnowledgeNotes(visibleSelectedKnowledgeNoteIds);
          const deletedIds = new Set(visibleSelectedKnowledgeNoteIds);
          setKnowledgeNotes(prev => prev.filter(note => !deletedIds.has(note.id)));
          setKnowledgeNoteSearchResults(prev => prev.filter(note => !deletedIds.has(note.id)));
          setSelectedKnowledgeNoteIds(prev => {
            const next = new Set(prev);
            visibleSelectedKnowledgeNoteIds.forEach(id => next.delete(id));
            return next;
          });
        } catch (error: any) {
          console.error('[Settings] Failed to delete selected knowledge notes:', error);
          Alert.alert('Error', 'Failed to delete selected notes');
        }
      }
    );
  }, [confirmDestructiveAction, settingsClient, visibleSelectedKnowledgeNoteIds]);

  const handleKnowledgeNoteDeleteAll = useCallback(() => {
    if (!settingsClient || knowledgeNotes.length === 0) return;

    confirmDestructiveAction(
      'Delete All Notes',
      'Delete every knowledge note from the connected desktop? This cannot be undone.',
      async () => {
        try {
          await settingsClient.deleteAllKnowledgeNotes();
          setKnowledgeNotes([]);
          setKnowledgeNoteSearchResults([]);
          setSelectedKnowledgeNoteIds(new Set());
        } catch (error: any) {
          console.error('[Settings] Failed to delete all knowledge notes:', error);
          Alert.alert('Error', 'Failed to delete all notes');
        }
      },
      'Delete All'
    );
  }, [confirmDestructiveAction, knowledgeNotes.length, settingsClient]);

  const handleKnowledgeNotePromote = useCallback(async (note: KnowledgeNote) => {
    if (!settingsClient || note.context === 'auto') return;
    try {
      await settingsClient.updateKnowledgeNote(note.id, { context: 'auto' });
      setKnowledgeNotes(prev =>
        prev.map(existing =>
          existing.id === note.id
            ? { ...existing, context: 'auto', updatedAt: Date.now() }
            : existing
        )
      );
      setKnowledgeNoteSearchResults(prev =>
        prev.map(existing =>
          existing.id === note.id
            ? { ...existing, context: 'auto', updatedAt: Date.now() }
            : existing
        )
      );
    } catch (error: any) {
      console.error('[Settings] Failed to promote knowledge note to auto context:', error);
      Alert.alert('Error', 'Failed to promote note to auto context');
    }
  }, [settingsClient]);

  // Navigate to knowledge note edit screen
  const handleKnowledgeNoteEdit = useCallback((note?: KnowledgeNote) => {
    navigation.navigate('KnowledgeNoteEdit', {
      noteId: note?.id,
      note,
    });
  }, [navigation]);

  // Handle agent profile toggle
  const handleAgentProfileToggle = async (profileId: string) => {
    if (!settingsClient) return;
    try {
      const res = await settingsClient.toggleAgentProfile(profileId);
      setAgentProfiles(prev => setAgentProfileEnabledInList(prev, profileId, res.enabled));
    } catch (error: any) {
      console.error('[Settings] Failed to toggle agent profile:', error);
      Alert.alert('Error', 'Failed to toggle agent profile');
    }
  };

  // Handle agent profile delete
  const handleAgentProfileDelete = useCallback(async (profile: AgentProfile) => {
    if (!settingsClient) return;
    if (profile.isBuiltIn) {
      Alert.alert('Cannot Delete', 'Built-in agents cannot be deleted');
      return;
    }

    confirmDestructiveAction('Delete Agent', `Are you sure you want to delete "${profile.displayName}"?`, async () => {
      try {
        await settingsClient.deleteAgentProfile(profile.id);
        setAgentProfiles(prev => removeAgentProfileFromList(prev, profile.id));
      } catch (error: any) {
        console.error('[Settings] Failed to delete agent profile:', error);
        Alert.alert('Error', error.message || 'Failed to delete agent profile');
      }
    });
  }, [settingsClient, confirmDestructiveAction]);

  // Navigate to agent edit screen
  const handleAgentProfileEdit = useCallback((agentId?: string) => {
    navigation.navigate('AgentEdit', { agentId });
  }, [navigation]);

  // Navigate to loop edit screen
  const handleLoopEdit = useCallback((loop?: Loop) => {
    navigation.navigate('LoopEdit', {
      loopId: loop?.id,
      loop,
    });
  }, [navigation]);

  const closeLoopImportModal = useCallback(() => {
    if (isImportingLoopMarkdown) return;
    setShowLoopImportModal(false);
    setLoopImportMarkdownText('');
  }, [isImportingLoopMarkdown]);

  const handleLoopMarkdownImport = useCallback(async () => {
    if (!settingsClient || isImportingLoopMarkdown || !loopImportMarkdownText.trim()) return;

    setIsImportingLoopMarkdown(true);
    setRemoteError(null);
    try {
      const result = await settingsClient.importLoopFromMarkdown(loopImportMarkdownText.trim());
      setShowLoopImportModal(false);
      setLoopImportMarkdownText('');
      setSaveStatusMessage(`Imported loop "${result.loop.name}"`);
      await fetchLoops();
      Alert.alert('Import Complete', `Imported "${result.loop.name}".`);
    } catch (error: any) {
      console.error('[Settings] Failed to import loop Markdown:', error);
      Alert.alert('Import Failed', error.message || 'Failed to import loop');
    } finally {
      setIsImportingLoopMarkdown(false);
    }
  }, [fetchLoops, isImportingLoopMarkdown, loopImportMarkdownText, settingsClient]);

  const handleLoopMarkdownExport = useCallback(async (loop: Loop) => {
    if (!settingsClient || isExportingLoopMarkdownId) return;

    setIsExportingLoopMarkdownId(loop.id);
    setRemoteError(null);
    try {
      const result = await settingsClient.exportLoopToMarkdown(loop.id);
      await Share.share({
        message: result.markdown,
        title: `${loop.name}.md`,
      });
      setSaveStatusMessage(`Exported loop "${loop.name}"`);
    } catch (error: any) {
      console.error('[Settings] Failed to export loop Markdown:', error);
      Alert.alert('Export Failed', error.message || 'Failed to export loop');
    } finally {
      setIsExportingLoopMarkdownId(null);
    }
  }, [isExportingLoopMarkdownId, settingsClient]);

  const handleLoopDelete = useCallback((loop: Loop) => {
    if (!settingsClient) return;
    confirmDestructiveAction('Delete Loop', `Are you sure you want to delete "${loop.name}"?`, async () => {
      try {
        await settingsClient.deleteLoop(loop.id);
        setLoops(prev => prev.filter(item => item.id !== loop.id));
      } catch (error: any) {
        console.error('[Settings] Failed to delete loop:', error);
        Alert.alert('Error', error.message || 'Failed to delete loop');
      }
    });
  }, [settingsClient, confirmDestructiveAction]);

  // Handle loop toggle
  const handleLoopToggle = async (loopId: string) => {
    if (!settingsClient) return;
    try {
      const res = await settingsClient.toggleLoop(loopId);
      setLoops(prev =>
        prev.map(l => (l.id === loopId ? { ...l, enabled: res.enabled } : l))
      );
    } catch (error: any) {
      console.error('[Settings] Failed to toggle loop:', error);
      Alert.alert('Error', 'Failed to toggle loop');
    }
  };

  // Handle loop run
  const handleLoopRun = async (loopId: string) => {
    if (!settingsClient) return;
    try {
      await settingsClient.runLoop(loopId);
      Alert.alert('Success', 'Loop triggered successfully');
      // Refresh loops to get updated lastRunAt
      fetchLoops();
    } catch (error: any) {
      console.error('[Settings] Failed to run loop:', error);
      Alert.alert('Error', error.message || 'Failed to run loop');
    }
  };

  // Handle push notification toggle
  const handleNotificationToggle = async (enabled: boolean) => {
    if (!config.baseUrl || !config.apiKey) {
      Alert.alert('Configuration Required', 'Please configure your server connection first.');
      return;
    }

    if (enabled) {
      const success = await registerPush(config.baseUrl, config.apiKey);
      if (!success) {
        Alert.alert(
          'Permission Required',
          'Push notifications require permission. Please enable notifications in your device settings.',
          [{ text: 'OK' }]
        );
      }
    } else {
      await unregisterPush(config.baseUrl, config.apiKey);
    }
  };

  // Fetch available models for the current provider
  const fetchModels = useCallback(async (providerId: 'openai' | 'groq' | 'gemini' | 'chatgpt-web') => {
    if (!settingsClient) return;

    setIsLoadingModels(true);
    try {
      const response = await settingsClient.getModels(providerId);
      setAvailableModels(response.models);
      // Check if current model is in the list, if not enable custom mode
      const currentModel = getCurrentModelValue();
      if (currentModel && response.models.length > 0) {
        const isInList = response.models.some(m => m.id === currentModel);
        setUseCustomModel(!isInList);
      }
    } catch (error: any) {
      console.error('[Settings] Failed to fetch models:', error);
      // Keep any existing models on error to avoid UI looking empty
      // Only log the error, don't clear the list
    } finally {
      setIsLoadingModels(false);
    }
  }, [settingsClient]);

  const getAgentProvider = () => remoteSettings?.agentProviderId || remoteSettings?.mcpToolsProviderId || 'openai';

  const getAgentModelKey = (provider: string) => provider === 'openai' ? 'agentOpenaiModel'
    : provider === 'groq' ? 'agentGroqModel'
    : provider === 'gemini' ? 'agentGeminiModel'
    : 'agentChatgptWebModel';

  // Fetch models when remote settings load or provider changes
  useEffect(() => {
    const providerId = remoteSettings?.agentProviderId || remoteSettings?.mcpToolsProviderId;
    if (providerId && settingsClient) {
      fetchModels(providerId);
    }
  }, [remoteSettings?.agentProviderId, remoteSettings?.mcpToolsProviderId, settingsClient, fetchModels]);

  // Handle provider change
  const handleProviderChange = async (provider: 'openai' | 'groq' | 'gemini' | 'chatgpt-web') => {
    if (!settingsClient || !remoteSettings || getAgentProvider() === provider) return;

    // Cancel any pending model update to avoid writing to the wrong provider's model key
    if (modelUpdateTimeoutRef.current) {
      clearTimeout(modelUpdateTimeoutRef.current);
      modelUpdateTimeoutRef.current = null;
    }

    try {
      await settingsClient.updateSettings({ agentProviderId: provider });
      setRemoteSettings(prev => prev ? { ...prev, agentProviderId: provider } : null);
      // Reset custom model mode when switching providers
      setUseCustomModel(false);
      // Models will be fetched via the useEffect above
    } catch (error: any) {
      console.error('[Settings] Failed to change provider:', error);
      setRemoteError(error.message || 'Failed to change provider');
    }
  };

  // Handle preset change (OpenAI compatible providers)
  const handlePresetChange = async (presetId: string) => {
    if (!settingsClient || !remoteSettings || remoteSettings.currentModelPresetId === presetId) return;

    // Cancel any pending model update to avoid writing to the wrong preset's context
    if (modelUpdateTimeoutRef.current) {
      clearTimeout(modelUpdateTimeoutRef.current);
      modelUpdateTimeoutRef.current = null;
    }

    setShowPresetPicker(false);
    try {
      await settingsClient.updateSettings({ currentModelPresetId: presetId });
      await fetchRemoteSettings();
      // Reset models and fetch new ones for the new preset
      setAvailableModels([]);
      setUseCustomModel(false);
      // Fetch models for the new preset
      if (getAgentProvider() === 'openai') {
        fetchModels('openai');
      }
    } catch (error: any) {
      console.error('[Settings] Failed to change preset:', error);
      setRemoteError(error.message || 'Failed to change preset');
    }
  };

  const getCurrentModelPreset = (): ModelPresetSummary | undefined => {
    if (!remoteSettings?.availablePresets || !remoteSettings.currentModelPresetId) return undefined;
    return remoteSettings.availablePresets.find(p => p.id === remoteSettings.currentModelPresetId);
  };

  // Get current preset display name
  const getCurrentPresetName = () => {
    if (!remoteSettings?.availablePresets || !remoteSettings.currentModelPresetId) return 'OpenAI';
    const preset = remoteSettings.availablePresets.find(p => p.id === remoteSettings.currentModelPresetId);
    return preset?.name || 'OpenAI';
  };

  const openPresetEditor = (mode: ModelPresetEditorMode, preset?: ModelPresetSummary) => {
    setPresetEditorMode(mode);
    setPresetDraft(mode === 'edit' && preset ? buildModelPresetDraftFromSummary(preset) : EMPTY_MODEL_PRESET_DRAFT);
    setShowPresetEditor(true);
  };

  const closePresetEditor = () => {
    if (isSavingPreset) return;
    setShowPresetEditor(false);
    setPresetDraft(EMPTY_MODEL_PRESET_DRAFT);
  };

  const handlePresetDraftChange = (key: keyof ModelPresetDraft, value: string) => {
    setPresetDraft(prev => ({ ...prev, [key]: value }));
  };

  const refreshSettingsAfterPresetMutation = async () => {
    await fetchRemoteSettings();
    if (getAgentProvider() === 'openai') {
      setAvailableModels([]);
      fetchModels('openai');
    }
  };

  const handlePresetEditorSave = async () => {
    if (!settingsClient) return;

    const draftPayload = buildModelPresetPayloadFromDraft(presetDraft);
    if (!draftPayload.ok) {
      setRemoteError(draftPayload.error);
      return;
    }

    setIsSavingPreset(true);
    setRemoteError(null);
    try {
      if (presetEditorMode === 'create') {
        await settingsClient.createModelPreset(draftPayload.payload);
      } else if (presetDraft.id) {
        await settingsClient.updateModelPreset(presetDraft.id, draftPayload.payload);
      }
      await refreshSettingsAfterPresetMutation();
      setShowPresetEditor(false);
      setPresetDraft(EMPTY_MODEL_PRESET_DRAFT);
      setSaveStatusMessage('Saved');
    } catch (error: any) {
      console.error('[Settings] Failed to save preset:', error);
      setRemoteError(error.message || 'Failed to save endpoint preset');
    } finally {
      setIsSavingPreset(false);
    }
  };

  const handlePresetDelete = () => {
    if (!settingsClient || !presetDraft.id || presetDraft.isBuiltIn) return;

    confirmDestructiveAction(
      'Delete Preset',
      `Delete "${presetDraft.name}" from desktop model presets?`,
      async () => {
        setIsSavingPreset(true);
        setRemoteError(null);
        try {
          await settingsClient.deleteModelPreset(presetDraft.id!);
          await refreshSettingsAfterPresetMutation();
          setShowPresetEditor(false);
          setPresetDraft(EMPTY_MODEL_PRESET_DRAFT);
          setSaveStatusMessage('Saved');
        } catch (error: any) {
          console.error('[Settings] Failed to delete preset:', error);
          setRemoteError(error.message || 'Failed to delete endpoint preset');
        } finally {
          setIsSavingPreset(false);
        }
      },
      'Delete',
    );
  };

  // Handle model name change with debouncing to avoid request storms per keystroke
  const handleModelNameChange = useCallback((modelName: string) => {
    // Update draft state immediately for responsive UI
    setCustomModelDraft(modelName);
    if (remoteSettings) {
      const pendingModelKey = getAgentModelKey(getAgentProvider());
      markRemotePending(pendingModelKey);
    }
    setSaveStatusMessage(null);

    // Cancel any pending update
    if (modelUpdateTimeoutRef.current) {
      clearTimeout(modelUpdateTimeoutRef.current);
    }

    // Debounce the actual API call by 500ms
    modelUpdateTimeoutRef.current = setTimeout(async () => {
      if (!settingsClient || !remoteSettings) return;

      const provider = getAgentProvider();
      const modelKey = getAgentModelKey(provider);

      // Update local state
      setRemoteSettings(prev => prev ? { ...prev, [modelKey]: modelName } : null);

      try {
        await settingsClient.updateSettings({ [modelKey]: modelName });
        clearRemotePending(modelKey);
        modelUpdateTimeoutRef.current = null;
      } catch (error: any) {
        console.error('[Settings] Failed to update model:', error);
        setRemoteError(error.message || 'Failed to update model');
        // Refresh to get actual state
        fetchRemoteSettings();
      }
    }, 500);
  }, [clearRemotePending, fetchRemoteSettings, markRemotePending, remoteSettings, settingsClient]);

  // Cleanup timeout on unmount
  useEffect(() => {
    return () => {
      if (modelUpdateTimeoutRef.current) {
        clearTimeout(modelUpdateTimeoutRef.current);
      }
    };
  }, []);

  // Sync customModelDraft with remoteSettings when it changes (e.g., on initial load or provider change)
  useEffect(() => {
    if (remoteSettings) {
      setCustomModelDraft(getCurrentModelValue());
    }
  }, [remoteSettings?.agentProviderId, remoteSettings?.mcpToolsProviderId, remoteSettings?.agentOpenaiModel, remoteSettings?.agentGroqModel, remoteSettings?.agentGeminiModel, remoteSettings?.agentChatgptWebModel, remoteSettings?.mcpToolsOpenaiModel, remoteSettings?.mcpToolsGroqModel, remoteSettings?.mcpToolsGeminiModel, remoteSettings?.mcpToolsChatgptWebModel]);

  // Get current model value based on provider
  const getCurrentModelValue = () => {
    if (!remoteSettings) return '';
    const provider = getAgentProvider();
    if (provider === 'openai') return remoteSettings.agentOpenaiModel || remoteSettings.mcpToolsOpenaiModel || '';
    if (provider === 'groq') return remoteSettings.agentGroqModel || remoteSettings.mcpToolsGroqModel || '';
    if (provider === 'gemini') return remoteSettings.agentGeminiModel || remoteSettings.mcpToolsGeminiModel || '';
    return remoteSettings.agentChatgptWebModel || remoteSettings.mcpToolsChatgptWebModel || '';
  };

  // Get placeholder based on provider
  const getModelPlaceholder = () => {
    if (!remoteSettings) return '';
    const provider = getAgentProvider();
    if (provider === 'openai') return 'gpt-4.1-mini';
    if (provider === 'groq') return 'openai/gpt-oss-120b';
    if (provider === 'gemini') return 'gemini-2.5-flash';
    return 'gpt-5.4-mini';
  };

  // Get display name for current model
  const getCurrentModelDisplayName = () => {
    const currentValue = getCurrentModelValue();
    if (!currentValue) return 'Select a model';
    const model = availableModels.find(m => m.id === currentValue);
    return model?.name || currentValue;
  };

  // Handle model selection from picker
  const handleModelSelect = async (modelId: string) => {
    setShowModelPicker(false);
    setModelSearchQuery('');
    await handleModelNameChange(modelId);
  };

  // Filter models by search query
  const filteredModels = useMemo(() => {
    if (!modelSearchQuery.trim()) return availableModels;
    const query = modelSearchQuery.toLowerCase();
    return availableModels.filter(
      m => m.id.toLowerCase().includes(query) || m.name.toLowerCase().includes(query)
    );
  }, [availableModels, modelSearchQuery]);

  const flushAllSettingsSaves = useCallback(async () => {
    if (isSavingAllSettings) return;

    setIsSavingAllSettings(true);
    setSaveStatusMessage('Saving…');

    try {
      setConfig(draft);
      await saveConfig(draft);
      setHasPendingLocalSave(false);

      if (settingsClient && remoteSettings) {
        clearAllRemoteTimeouts();

        const updates: SettingsUpdate = {};
        const pendingKeys = new Set(pendingRemoteSaveKeys);

        if (pendingKeys.has('sttLanguage')) {
          updates.sttLanguage = inputDrafts.sttLanguage ?? '';
        }
        if (pendingKeys.has('openaiSttLanguage')) {
          updates.openaiSttLanguage = inputDrafts.openaiSttLanguage ?? '';
        }
        if (pendingKeys.has('groqSttLanguage')) {
          updates.groqSttLanguage = inputDrafts.groqSttLanguage ?? '';
        }
        if (pendingKeys.has('openaiSttModel')) {
          updates.openaiSttModel = inputDrafts.openaiSttModel ?? '';
        }
        if (pendingKeys.has('groqSttModel')) {
          updates.groqSttModel = inputDrafts.groqSttModel ?? '';
        }
        if (pendingKeys.has('groqSttPrompt')) {
          updates.groqSttPrompt = inputDrafts.groqSttPrompt ?? '';
        }
        if (pendingKeys.has('transcriptPostProcessingPrompt')) {
          updates.transcriptPostProcessingPrompt = inputDrafts.transcriptPostProcessingPrompt ?? '';
        }
        if (pendingKeys.has('transcriptPostProcessingOpenaiModel')) {
          updates.transcriptPostProcessingOpenaiModel = inputDrafts.transcriptPostProcessingOpenaiModel ?? '';
        }
        if (pendingKeys.has('transcriptPostProcessingGroqModel')) {
          updates.transcriptPostProcessingGroqModel = inputDrafts.transcriptPostProcessingGroqModel ?? '';
        }
        if (pendingKeys.has('transcriptPostProcessingGeminiModel')) {
          updates.transcriptPostProcessingGeminiModel = inputDrafts.transcriptPostProcessingGeminiModel ?? '';
        }
        if (pendingKeys.has('transcriptPostProcessingChatgptWebModel')) {
          updates.transcriptPostProcessingChatgptWebModel = inputDrafts.transcriptPostProcessingChatgptWebModel ?? '';
        }
        if (pendingKeys.has('langfusePublicKey')) {
          updates.langfusePublicKey = inputDrafts.langfusePublicKey ?? '';
        }
        if (pendingKeys.has('langfuseBaseUrl')) {
          updates.langfuseBaseUrl = inputDrafts.langfuseBaseUrl ?? '';
        }
        if (pendingKeys.has('localTraceLogPath')) {
          updates.localTraceLogPath = inputDrafts.localTraceLogPath ?? '';
        }
        if (pendingKeys.has('mcpMaxIterations')) {
          const parsedIterations = parseInt(inputDrafts.mcpMaxIterations ?? '', 10);
          if (Number.isNaN(parsedIterations) || parsedIterations < 1 || parsedIterations > 100) {
            throw new Error('Max Iterations must be between 1 and 100 before saving.');
          }
          updates.mcpMaxIterations = parsedIterations;
        }
        if (pendingKeys.has('mcpAutoPasteDelay')) {
          const parsedDelay = parseMcpAutoPasteDelayDraft(inputDrafts.mcpAutoPasteDelay ?? '');
          if (parsedDelay === null) {
            throw new Error(formatMcpAutoPasteDelayValidationMessage());
          }
          updates.mcpAutoPasteDelay = parsedDelay;
        }
        if (pendingKeys.has('maxConversationsToKeep')) {
          const parsedMaxConversations = parseMaxConversationsToKeepDraft(inputDrafts.maxConversationsToKeep ?? '');
          if (parsedMaxConversations === null) {
            throw new Error(formatMaxConversationsToKeepValidationMessage());
          }
          updates.maxConversationsToKeep = parsedMaxConversations;
        }
        if (pendingKeys.has('whatsappAllowFrom')) {
          updates.whatsappAllowFrom = parseConfigListInput(inputDrafts.whatsappAllowFrom ?? '');
        }
        for (const key of DISCORD_LIST_SETTING_KEYS) {
          if (pendingKeys.has(key)) {
            updates[key] = parseConfigListInput(inputDrafts[key] ?? '', { unique: true });
          }
        }
        for (const section of PROVIDER_CREDENTIAL_SECTIONS) {
          if (pendingKeys.has(section.baseUrl)) {
            updates[section.baseUrl] = inputDrafts[section.baseUrl] ?? '';
          }
          for (const secret of section.secrets) {
            if (pendingKeys.has(secret.key)) {
              updates[secret.key] = inputDrafts[secret.key] ?? '';
            }
          }
        }

        const modelKey = getAgentModelKey(getAgentProvider());
        if (pendingKeys.has(modelKey)) {
          if (modelKey === 'agentOpenaiModel') {
            updates.agentOpenaiModel = customModelDraft;
          } else if (modelKey === 'agentGroqModel') {
            updates.agentGroqModel = customModelDraft;
          } else if (modelKey === 'agentGeminiModel') {
            updates.agentGeminiModel = customModelDraft;
          } else {
            updates.agentChatgptWebModel = customModelDraft;
          }
        }

        const langfuseSecretDraft = inputDrafts.langfuseSecretKey?.trim();
        if (pendingKeys.has('langfuseSecretKey') && langfuseSecretDraft) {
          updates.langfuseSecretKey = langfuseSecretDraft;
        }
        if (pendingKeys.has('discordBotToken')) {
          updates.discordBotToken = inputDrafts.discordBotToken ?? '';
        }

        if (Object.keys(updates).length > 0) {
          await settingsClient.updateSettings(updates);
          setRemoteSettings((prev) => prev ? {
            ...prev,
            ...updates,
            ...(updates.langfuseSecretKey ? { langfuseSecretKey: SECRET_MASK } : {}),
            ...(updates.discordBotToken !== undefined ? { discordBotToken: updates.discordBotToken ? SECRET_MASK : '' } : {}),
            ...(updates.openaiApiKey !== undefined ? { openaiApiKey: updates.openaiApiKey ? SECRET_MASK : '' } : {}),
            ...(updates.groqApiKey !== undefined ? { groqApiKey: updates.groqApiKey ? SECRET_MASK : '' } : {}),
            ...(updates.geminiApiKey !== undefined ? { geminiApiKey: updates.geminiApiKey ? SECRET_MASK : '' } : {}),
            ...(updates.chatgptWebAccessToken !== undefined ? { chatgptWebAccessToken: updates.chatgptWebAccessToken ? SECRET_MASK : '' } : {}),
            ...(updates.chatgptWebSessionToken !== undefined ? { chatgptWebSessionToken: updates.chatgptWebSessionToken ? SECRET_MASK : '' } : {}),
          } : null);

          const shouldClearSecretDrafts = updates.langfuseSecretKey
            || updates.discordBotToken !== undefined
            || updates.openaiApiKey !== undefined
            || updates.groqApiKey !== undefined
            || updates.geminiApiKey !== undefined
            || updates.chatgptWebAccessToken !== undefined
            || updates.chatgptWebSessionToken !== undefined;
          if (shouldClearSecretDrafts) {
            setInputDrafts((prev) => ({
              ...prev,
              ...(updates.langfuseSecretKey ? { langfuseSecretKey: '' } : {}),
              ...(updates.discordBotToken !== undefined ? { discordBotToken: '' } : {}),
              ...(updates.openaiApiKey !== undefined ? { openaiApiKey: '' } : {}),
              ...(updates.groqApiKey !== undefined ? { groqApiKey: '' } : {}),
              ...(updates.geminiApiKey !== undefined ? { geminiApiKey: '' } : {}),
              ...(updates.chatgptWebAccessToken !== undefined ? { chatgptWebAccessToken: '' } : {}),
              ...(updates.chatgptWebSessionToken !== undefined ? { chatgptWebSessionToken: '' } : {}),
            }));
          }
        }

        setPendingRemoteSaveKeys([]);
      }

      setSaveStatusMessage('Saved');
    } catch (error: any) {
      const message = error?.message || 'Failed to save settings';
      setRemoteError(message);
      setSaveStatusMessage(message);
    } finally {
      setIsSavingAllSettings(false);
    }
  }, [
    clearAllRemoteTimeouts,
    customModelDraft,
    draft,
    inputDrafts,
    isSavingAllSettings,
    pendingRemoteSaveKeys,
    remoteSettings,
    saveConfig,
    setConfig,
    settingsClient,
  ]);

  const hasPendingSaves = hasPendingLocalSave || pendingRemoteSaveKeys.length > 0;
  const saveButtonLabel = isSavingAllSettings
    ? 'Saving…'
    : hasPendingSaves
      ? 'Save changes'
      : 'Save settings now';
  const saveButtonHint = hasPendingSaves
    ? 'Save all current settings immediately, including typed edits that have not blurred yet.'
    : 'Save the current settings again if you want a clear confirmation.';


  // CollapsibleSection component
  const CollapsibleSection = ({
    id,
    title,
    children
  }: {
    id: AppShellMobileSettingsSectionId;
    title: string;
    children: React.ReactNode;
  }) => {
    if (isDesktopSettingsLayout && !activeDesktopSettingsSectionIds.has(id)) {
      return null;
    }

    const isExpanded = expandedSections[id] ?? false;

    if (isDesktopSettingsLayout) {
      return (
        <View style={[styles.collapsibleSection, styles.desktopSettingsActivePanel]}>
          <View style={[styles.collapsibleHeader, styles.desktopSettingsActiveHeader]}>
            <Text style={styles.collapsibleTitle}>{title}</Text>
          </View>
          <View style={styles.collapsibleContent}>
            {children}
          </View>
        </View>
      );
    }

    return (
      <View style={styles.collapsibleSection}>
        <TouchableOpacity
          style={styles.collapsibleHeader}
          onPress={() => toggleSection(id)}
          accessibilityRole="button"
          accessibilityState={{ expanded: isExpanded }}
        >
          <Text style={styles.collapsibleTitle}>{title}</Text>
          <Text style={styles.collapsibleChevron}>{isExpanded ? '▼' : '▶'}</Text>
        </TouchableOpacity>
        {isExpanded && (
          <View style={styles.collapsibleContent}>
            {children}
          </View>
        )}
      </View>
    );
  };

  function renderKnowledgeNoteFilterGroup<T extends string>(
    options: Array<{ label: string; value: T }>,
    value: T,
    onChange: (nextValue: T) => void,
    accessibilityPrefix: string,
  ) {
    return (
      <View style={styles.providerSelector}>
        {options.map((option) => (
          <TouchableOpacity
            key={option.value}
            style={[
              styles.providerOption,
              value === option.value && styles.providerOptionActive,
            ]}
            onPress={() => onChange(option.value)}
            accessibilityRole="button"
            accessibilityLabel={createButtonAccessibilityLabel(`${accessibilityPrefix} ${option.label}`)}
          >
            <Text style={[
              styles.providerOptionText,
              value === option.value && styles.providerOptionTextActive,
            ]}>
              {option.label}
            </Text>
          </TouchableOpacity>
        ))}
      </View>
    );
  }

  if (!ready) return null;

  const desktopSettingsNavItems = getDesktopSettingsNavItems({
    whatsappEnabled: remoteSettings?.whatsappEnabled ?? false,
    discordEnabled: remoteSettings?.discordEnabled ?? DEFAULT_DISCORD_ENABLED,
  });
  const isGeneralSettingsSectionActive = !isDesktopSettingsLayout || activeDesktopSettingsNavItemId === 'general';
  const settingsRefreshControl = (
    <RefreshControl
      refreshing={isRefreshing}
      onRefresh={onRefresh}
      tintColor={theme.colors.primary}
      colors={[theme.colors.primary]}
    />
  );
  const settingsFooter = (
    <>
      <TouchableOpacity
        style={[styles.primaryButton, styles.saveBarButton, isSavingAllSettings && styles.primaryButtonDisabled]}
        onPress={() => { void flushAllSettingsSaves(); }}
        disabled={isSavingAllSettings}
        activeOpacity={0.85}
        accessibilityRole="button"
        accessibilityLabel={saveButtonLabel}
        accessibilityHint={saveButtonHint}
      >
        <Text style={styles.primaryButtonText}>{saveButtonLabel}</Text>
      </TouchableOpacity>
      <Text style={styles.saveBarHint}>
        {saveStatusMessage || saveButtonHint}
      </Text>
    </>
  );

  return (
    <>
    <AppShellSettingsLayout
      isDesktopLayout={isDesktopSettingsLayout}
      navItems={desktopSettingsNavItems}
      activeNavItemId={activeDesktopSettingsNavItemId}
      onActivateNavItem={activateDesktopSettingsNavItem}
      refreshControl={settingsRefreshControl}
      footer={settingsFooter}
    >
      {isGeneralSettingsSectionActive && (
        <>
        {/* Connection Card - Tap to navigate to ConnectionSettings */}
        <TouchableOpacity
          style={styles.connectionCard}
          onPress={() => navigation.navigate('ConnectionSettings')}
          accessibilityRole="button"
          accessibilityLabel="Connection settings"
        >
          <View style={styles.connectionCardContent}>
            <View style={styles.connectionCardLeft}>
              <View style={styles.connectionStatusRow}>
                <View style={[
                  styles.statusDot,
                  { width: 10, height: 10, borderRadius: 5 },
                  config.baseUrl && config.apiKey
                    ? styles.statusConnected
                    : { backgroundColor: '#ef4444' }
                ]} />
                <Text style={styles.connectionCardTitle}>
                  {config.baseUrl && config.apiKey ? 'Connected' : 'Not connected'}
                </Text>
              </View>
              {config.baseUrl && (
                <Text style={styles.connectionCardUrl} numberOfLines={2}>
                  {config.baseUrl}
                </Text>
              )}
            </View>
            <Text style={styles.connectionCardChevron}>›</Text>
          </View>
        </TouchableOpacity>

        <TouchableOpacity
          style={[styles.connectionCard, !(config.baseUrl && config.apiKey) && styles.primaryButtonDisabled]}
          onPress={() => navigation.navigate('Operations')}
          disabled={!(config.baseUrl && config.apiKey)}
          accessibilityRole="button"
          accessibilityLabel="Operator console"
        >
          <View style={styles.connectionCardContent}>
            <View style={styles.connectionCardLeft}>
              <Text style={styles.connectionCardTitle}>Operator Console</Text>
              <Text style={styles.connectionCardUrl}>
                Health, recent errors, and safe restart actions for the connected desktop.
              </Text>
            </View>
            <Text style={styles.connectionCardChevron}>›</Text>
          </View>
        </TouchableOpacity>

        {/* Go to Chats button */}
        <TouchableOpacity
          style={[styles.primaryButton, !(config.baseUrl && config.apiKey) && styles.primaryButtonDisabled]}
          onPress={() => {
            if (navigation.canGoBack?.()) {
              navigation.goBack();
              return;
            }

            navigation.navigate('Sessions');
          }}
          disabled={!(config.baseUrl && config.apiKey)}
          accessibilityRole="button"
          accessibilityLabel="Go to Chats"
        >
          <Text style={styles.primaryButtonText}>Go to Chats</Text>
        </TouchableOpacity>

        <Text style={styles.sectionTitle}>Chats</Text>
        {remoteSettings && (
          <>
            <View style={styles.row}>
              <View style={{ flex: 1 }}>
                <Text style={styles.label}>Desktop History</Text>
                <Text style={[styles.helperText, { marginTop: 2 }]}>
                  Store desktop agent conversations so they can sync to mobile.
                </Text>
              </View>
              <Switch
                value={remoteSettings.conversationsEnabled ?? DEFAULT_CONVERSATIONS_ENABLED}
                onValueChange={(v) => handleRemoteSettingToggle('conversationsEnabled', v)}
                trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                thumbColor={(remoteSettings.conversationsEnabled ?? DEFAULT_CONVERSATIONS_ENABLED) ? theme.colors.primaryForeground : theme.colors.background}
                accessibilityLabel={createSwitchAccessibilityLabel('Desktop History')}
              />
            </View>

            <View style={styles.row}>
              <View style={{ flex: 1 }}>
                <Text style={styles.label}>Auto-Save Desktop Chats</Text>
                <Text style={[styles.helperText, { marginTop: 2 }]}>
                  Save desktop chats automatically when conversation storage is enabled.
                </Text>
              </View>
              <Switch
                value={remoteSettings.autoSaveConversations ?? DEFAULT_AUTO_SAVE_CONVERSATIONS}
                onValueChange={(v) => handleRemoteSettingToggle('autoSaveConversations', v)}
                trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                thumbColor={(remoteSettings.autoSaveConversations ?? DEFAULT_AUTO_SAVE_CONVERSATIONS) ? theme.colors.primaryForeground : theme.colors.background}
                disabled={!(remoteSettings.conversationsEnabled ?? DEFAULT_CONVERSATIONS_ENABLED)}
                accessibilityLabel={createSwitchAccessibilityLabel('Auto-Save Desktop Chats')}
              />
            </View>

            <Text style={styles.label}>Keep Recent Chats</Text>
            <TextInput
              style={[
                styles.input,
                !(remoteSettings.conversationsEnabled ?? DEFAULT_CONVERSATIONS_ENABLED) && styles.inputDisabled,
              ]}
              value={inputDrafts.maxConversationsToKeep ?? String(remoteSettings.maxConversationsToKeep ?? DEFAULT_MAX_CONVERSATIONS_TO_KEEP)}
              onChangeText={(v) => {
                markRemotePending('maxConversationsToKeep');
                setSaveStatusMessage(null);
                const parsedValue = parseMaxConversationsToKeepDraft(v);
                if (parsedValue === null) {
                  if (inputTimeoutRefs.current.maxConversationsToKeep) {
                    clearTimeout(inputTimeoutRefs.current.maxConversationsToKeep);
                    delete inputTimeoutRefs.current.maxConversationsToKeep;
                  }
                  setInputDrafts(prev => ({ ...prev, maxConversationsToKeep: v }));
                  return;
                }
                handleRemoteSettingUpdate('maxConversationsToKeep', parsedValue);
              }}
              placeholder={String(DEFAULT_MAX_CONVERSATIONS_TO_KEEP)}
              placeholderTextColor={theme.colors.mutedForeground}
              keyboardType="number-pad"
              editable={remoteSettings.conversationsEnabled ?? DEFAULT_CONVERSATIONS_ENABLED}
            />
            <Text style={styles.helperText}>
              Maximum desktop chats to keep before pruning older history.
            </Text>
          </>
        )}
        <View style={styles.serverRow}>
          <View style={styles.serverInfo}>
            <Text style={styles.serverName}>Clear all chats</Text>
            <Text style={styles.serverMeta}>
              Delete every chat saved in this mobile app, including pinned chats.
            </Text>
          </View>
          <TouchableOpacity
            style={[
              styles.dangerActionButton,
              sessionStore.sessions.length === 0 && styles.dangerActionButtonDisabled,
            ]}
            onPress={handleClearAllChats}
            disabled={sessionStore.sessions.length === 0}
            accessibilityRole="button"
            accessibilityLabel={createButtonAccessibilityLabel('Clear all chats')}
            accessibilityHint="Deletes every chat saved in this mobile app after confirmation."
          >
            <Text
              style={[
                styles.dangerActionButtonText,
                sessionStore.sessions.length === 0 && styles.dangerActionButtonTextDisabled,
              ]}
            >
              Clear All
            </Text>
          </TouchableOpacity>
        </View>

        <Text style={styles.sectionTitle}>Appearance</Text>
        <View style={styles.themeSelector}>
          {THEME_PREFERENCE_OPTIONS.map((option) => (
            <Pressable
              key={option.value}
              style={[
                styles.themeOption,
                themeMode === option.value && styles.themeOptionActive,
              ]}
              onPress={() => setThemeMode(option.value)}
            >
              <Text style={[
                styles.themeOptionText,
                themeMode === option.value && styles.themeOptionTextActive,
              ]}>
                {option.label}
              </Text>
            </Pressable>
          ))}
        </View>

        <View style={styles.row}>
          <Text style={styles.label}>Hands-free Voice Mode</Text>
          <Switch
            value={!!draft.handsFree}
            onValueChange={(v) => updateLocalConfig({ handsFree: v })}
            accessibilityLabel={createSwitchAccessibilityLabel('Hands-free Voice Mode')}
            trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
            thumbColor={draft.handsFree ? theme.colors.primaryForeground : theme.colors.background}
          />
        </View>
        <Text style={styles.helperText}>
          Mobile v1 only works while the app stays open on the Chat screen in the foreground.
        </Text>

        <Text style={[styles.label, { marginTop: spacing.md }]}>Wake phrase</Text>
        <TextInput
          style={styles.input}
          value={draft.handsFreeWakePhrase || 'hey dot agents'}
          onChangeText={(value) => updateDraftField({ handsFreeWakePhrase: value })}
          onEndEditing={() => updateLocalConfig({ handsFreeWakePhrase: draft.handsFreeWakePhrase || 'hey dot agents' })}
          placeholder='hey dot agents'
          placeholderTextColor={theme.colors.mutedForeground}
          autoCapitalize='none'
          autoCorrect={false}
        />

        <Text style={[styles.label, { marginTop: spacing.md }]}>Sleep phrase</Text>
        <TextInput
          style={styles.input}
          value={draft.handsFreeSleepPhrase || 'go to sleep'}
          onChangeText={(value) => updateDraftField({ handsFreeSleepPhrase: value })}
          onEndEditing={() => updateLocalConfig({ handsFreeSleepPhrase: draft.handsFreeSleepPhrase || 'go to sleep' })}
          placeholder='go to sleep'
          placeholderTextColor={theme.colors.mutedForeground}
          autoCapitalize='none'
          autoCorrect={false}
        />

        <Text style={[styles.label, { marginTop: spacing.md }]}>Send after silence</Text>
        <TextInput
          style={styles.input}
          value={handsFreeDebounceInput}
          onChangeText={handleHandsFreeDebounceInputChange}
          onEndEditing={commitHandsFreeDebounceInput}
          placeholder={`${DEFAULT_HANDS_FREE_MESSAGE_DEBOUNCE_MS}`}
          placeholderTextColor={theme.colors.mutedForeground}
          keyboardType='number-pad'
        />
        <Text style={styles.helperText}>
          Wait this many milliseconds without new speech before sending a hands-free message. Any value ≥ 0 works.
          Current: {Math.round((draft.handsFreeMessageDebounceMs ?? DEFAULT_HANDS_FREE_MESSAGE_DEBOUNCE_MS) / 10) / 100}s.
        </Text>

        <View style={styles.row}>
          <View style={{ flex: 1 }}>
            <Text style={styles.label}>Debug Voice State</Text>
            <Text style={[styles.helperText, { marginTop: 2 }]}>Show recent recognizer and handsfree events in Chat.</Text>
          </View>
          <Switch
            value={draft.handsFreeDebug === true}
            onValueChange={(v) => updateLocalConfig({ handsFreeDebug: v })}
            accessibilityLabel={createSwitchAccessibilityLabel('Debug Voice State')}
            trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
            thumbColor={draft.handsFreeDebug ? theme.colors.primaryForeground : theme.colors.background}
          />
        </View>

        <View style={styles.row}>
          <View style={{ flex: 1 }}>
            <Text style={styles.label}>Foreground Only</Text>
            <Text style={[styles.helperText, { marginTop: 2 }]}>Keep this on for the mobile MVP safety boundary.</Text>
          </View>
          <Switch
            value={draft.handsFreeForegroundOnly !== false}
            onValueChange={(v) => updateLocalConfig({ handsFreeForegroundOnly: v })}
            accessibilityLabel={createSwitchAccessibilityLabel('Foreground Only')}
            trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
            thumbColor={draft.handsFreeForegroundOnly !== false ? theme.colors.primaryForeground : theme.colors.background}
          />
        </View>

        {/* Microphone Device Selection */}
        <Text style={[styles.sectionTitle, { marginTop: spacing.lg }]}>Audio Input</Text>
        <MicrophoneSelector
          selectedDeviceId={draft.audioInputDeviceId}
          onDeviceChange={(deviceId) => updateLocalConfig({ audioInputDeviceId: deviceId })}
        />

        <View style={styles.row}>
          <Text style={styles.label}>Text-to-Speech</Text>
          <Switch
            value={draft.ttsEnabled !== false}
            onValueChange={(v) => updateLocalConfig({ ttsEnabled: v })}
            accessibilityLabel={createSwitchAccessibilityLabel('Text-to-Speech')}
            trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
            thumbColor={draft.ttsEnabled !== false ? theme.colors.primaryForeground : theme.colors.background}
          />
        </View>

        {/* TTS Voice Settings - shown when TTS is enabled */}
        {draft.ttsEnabled !== false && (
          <>
            <TTSSettings
              voiceId={draft.ttsVoiceId}
              rate={draft.ttsRate ?? 1.0}
              pitch={draft.ttsPitch ?? 1.0}
              ttsProvider={draft.ttsProvider ?? 'native'}
              edgeTtsVoice={draft.edgeTtsVoice}
              remoteBaseUrl={draft.baseUrl}
              remoteApiKey={draft.apiKey}
              onVoiceChange={(v) => updateLocalConfig({ ttsVoiceId: v })}
              onRateChange={(r) => updateLocalConfig({ ttsRate: r })}
              onPitchChange={(p) => updateLocalConfig({ ttsPitch: p })}
              onTtsProviderChange={(p) => updateLocalConfig({ ttsProvider: p })}
              onEdgeTtsVoiceChange={(v) => updateLocalConfig({ edgeTtsVoice: v })}
            />
            <Text style={styles.helperText}>
              Edge TTS voices route through your paired desktop. OpenAI, Groq, and Gemini cloud voices are under the desktop-connected Text-to-Speech section below.
            </Text>
          </>
        )}

        <View style={styles.row}>
          <Text style={styles.label}>Message Queuing</Text>
          <Switch
            value={draft.messageQueueEnabled !== false}
            onValueChange={(v) => updateLocalConfig({ messageQueueEnabled: v })}
            accessibilityLabel={createSwitchAccessibilityLabel('Message Queuing')}
            trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
            thumbColor={draft.messageQueueEnabled !== false ? theme.colors.primaryForeground : theme.colors.background}
          />
        </View>
        <Text style={styles.helperText}>
          Queue messages while the agent is busy processing
        </Text>

        {/* Push Notifications Section */}
        <View style={[styles.row, styles.sectionLeadRow]}>
          <View style={{ flex: 1 }}>
            <Text style={styles.label}>Push Notifications</Text>
            {!notificationsSupported && (
              <Text style={[styles.helperText, { marginTop: 2 }]}>
                Only available on physical devices
              </Text>
            )}
            {notificationsSupported && notificationPermission === 'denied' && (
              <Text style={[styles.helperText, { marginTop: 2, color: theme.colors.destructive }]}>
                Permission denied - enable in device settings
              </Text>
            )}
          </View>
          <Switch
            value={notificationsRegistered}
            onValueChange={handleNotificationToggle}
            accessibilityLabel={createSwitchAccessibilityLabel('Push Notifications')}
            trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
            thumbColor={notificationsRegistered ? theme.colors.primaryForeground : theme.colors.background}
            disabled={!notificationsSupported || isNotificationLoading}
          />
        </View>
        <Text style={styles.helperText}>
          Receive notifications when new messages arrive from your AI assistant
        </Text>
        </>
      )}

        {/* Remote Settings Section - only show when connected to a DotAgents desktop server */}
        {settingsClient && (isLoadingRemote || isDotAgentsServer) && (
          <>
            <Text style={styles.sectionTitle}>Desktop Settings</Text>

            {isLoadingRemote && !isRefreshing && (
              <View style={styles.loadingRow}>
                <ActivityIndicator size="small" color={theme.colors.primary} />
                <Text style={styles.loadingText}>Loading remote settings...</Text>
              </View>
            )}

            {remoteError && (
              <View style={styles.warningContainer}>
                <View style={styles.warningContent}>
                  <Text style={styles.warningTitle}>Desktop settings need attention</Text>
                  <Text style={styles.warningText}>{remoteError}</Text>
                  <Text style={styles.warningDetailText}>
                    Some desktop sections may be out of date until the retry finishes.
                  </Text>
                </View>
                <TouchableOpacity
                  style={styles.warningRetryButton}
                  onPress={fetchRemoteSettings}
                  accessibilityRole="button"
                  accessibilityLabel={createButtonAccessibilityLabel('Retry loading desktop settings')}
                  accessibilityHint="Reloads the desktop settings section and refreshes stale values."
                >
                  <Text style={styles.warningRetryButtonText}>Retry loading</Text>
                </TouchableOpacity>
              </View>
            )}

            {/* Provider Selection */}
            {remoteSettings && (
              <CollapsibleSection id="providerSelection" title="Provider Selection">
                {/* Voice Transcription (STT) Provider */}
                <Text style={styles.label}>Voice Transcription (STT)</Text>
                <View style={styles.providerSelector}>
                  {STT_PROVIDERS.map((provider) => (
                    <Pressable
                      key={provider.value}
                      style={[
                        styles.providerOption,
                        (remoteSettings.sttProviderId || DEFAULT_STT_PROVIDER_ID) === provider.value && styles.providerOptionActive,
                      ]}
                      onPress={() => handleRemoteSettingUpdate('sttProviderId', provider.value)}
                    >
                      <Text style={[
                        styles.providerOptionText,
                        (remoteSettings.sttProviderId || DEFAULT_STT_PROVIDER_ID) === provider.value && styles.providerOptionTextActive,
                      ]}>
                        {provider.label}
                      </Text>
                    </Pressable>
                  ))}
                </View>

                {/* Agent Provider */}
                <Text style={[styles.label, { marginTop: spacing.md }]}>Agent</Text>
                <View style={styles.providerSelector}>
                  {CHAT_PROVIDERS.map((provider) => (
                    <Pressable
                      key={provider.value}
                      style={[
                        styles.providerOption,
                        getAgentProvider() === provider.value && styles.providerOptionActive,
                      ]}
                      onPress={() => handleProviderChange(provider.value as 'openai' | 'groq' | 'gemini' | 'chatgpt-web')}
                    >
                      <Text style={[
                        styles.providerOptionText,
                        getAgentProvider() === provider.value && styles.providerOptionTextActive,
                      ]}>
                        {provider.label}
                      </Text>
                    </Pressable>
                  ))}
                </View>

                {/* Text-to-Speech (TTS) Provider */}
                <Text style={[styles.label, { marginTop: spacing.md }]}>Text-to-Speech (TTS)</Text>
                <View style={styles.providerSelector}>
                  {TTS_PROVIDERS.map((provider) => (
                    <Pressable
                      key={provider.value}
                      style={[
                        styles.providerOption,
                        (remoteSettings.ttsProviderId || DEFAULT_TTS_PROVIDER_ID) === provider.value && styles.providerOptionActive,
                      ]}
                      onPress={() => handleRemoteSettingUpdate('ttsProviderId', provider.value)}
                    >
                      <Text style={[
                        styles.providerOptionText,
                        (remoteSettings.ttsProviderId || DEFAULT_TTS_PROVIDER_ID) === provider.value && styles.providerOptionTextActive,
                      ]}>
                        {provider.label}
                      </Text>
                    </Pressable>
                  ))}
                </View>
              </CollapsibleSection>
            )}

            {/* Provider Setup */}
            {remoteSettings && (
              <CollapsibleSection id="providerSetup" title="Provider Setup">
                {PROVIDER_CREDENTIAL_SECTIONS.map((provider) => (
                  <View key={provider.id} style={styles.providerCredentialGroup}>
                    <Text style={styles.subsectionTitle}>{provider.label}</Text>

                    {provider.secrets.map((secret) => {
                      const hasConfiguredSecret = remoteSettings[secret.key] === SECRET_MASK;
                      return (
                        <View key={secret.key}>
                          <Text style={styles.label}>{secret.label}</Text>
                          <TextInput
                            style={styles.input}
                            value={inputDrafts[secret.key] ?? ''}
                            onChangeText={(v) => handleRemoteSecretDraftChange(secret.key, v)}
                            onBlur={() => {
                              void commitRemoteSecretDraft(secret.key);
                            }}
                            placeholder={hasConfiguredSecret ? 'Configured' : secret.placeholder}
                            placeholderTextColor={theme.colors.mutedForeground}
                            autoCapitalize="none"
                            autoCorrect={false}
                            secureTextEntry
                          />
                        </View>
                      );
                    })}

                    <Text style={styles.label}>Base URL</Text>
                    <TextInput
                      style={styles.input}
                      value={inputDrafts[provider.baseUrl] ?? ''}
                      onChangeText={(v) => handleRemoteSettingUpdate(provider.baseUrl, v)}
                      placeholder={provider.baseUrlPlaceholder}
                      placeholderTextColor={theme.colors.mutedForeground}
                      autoCapitalize="none"
                      autoCorrect={false}
                      keyboardType="url"
                    />
                  </View>
                ))}
              </CollapsibleSection>
            )}

            {/* 4a. Profile & Model */}
            {remoteSettings && (
              <CollapsibleSection id="profileModel" title="Profile & Model">
                {/* Profile Switching */}
                {profiles.length > 0 && (
                  <>
                    <Text style={styles.label}>Profile</Text>
                    <View style={styles.profileList}>
                      {profiles.map((profile) => (
                        <TouchableOpacity
                          key={profile.id}
                          style={[
                            styles.profileItem,
                            currentProfileId === profile.id && styles.profileItemActive,
                          ]}
                          onPress={() => handleProfileSwitch(profile.id)}
                        >
                          <Text style={[
                            styles.profileName,
                            currentProfileId === profile.id && styles.profileNameActive,
                          ]} numberOfLines={2}>
                            {profile.name}
                            {profile.isDefault && ' (Default)'}
                          </Text>
                          {currentProfileId === profile.id && (
                            <Text style={styles.checkmark}>✓</Text>
                          )}
                        </TouchableOpacity>
                      ))}
                    </View>
                    <View style={styles.profileActions}>
                      <TouchableOpacity
                        style={[styles.profileActionButton, isImportingProfile && styles.profileActionButtonDisabled]}
                        onPress={() => setShowImportModal(true)}
                        disabled={isImportingProfile}
                      >
                        <Text style={styles.profileActionButtonText}>
                          {isImportingProfile ? 'Importing...' : 'Import'}
                        </Text>
                      </TouchableOpacity>
                      <TouchableOpacity
                        style={[styles.profileActionButton, (!currentProfileId || isExportingProfile) && styles.profileActionButtonDisabled]}
                        onPress={handleExportProfile}
                        disabled={!currentProfileId || isExportingProfile}
                      >
                        <Text style={styles.profileActionButtonText}>
                          {isExportingProfile ? 'Exporting...' : 'Export'}
                        </Text>
                      </TouchableOpacity>
                    </View>
                  </>
                )}

                {/* Model Settings */}
                <Text style={styles.label}>Provider</Text>
                <View style={styles.providerSelector}>
                  {(['openai', 'groq', 'gemini', 'chatgpt-web'] as const).map((provider) => (
                    <Pressable
                      key={provider}
                      style={[
                        styles.providerOption,
                        getAgentProvider() === provider && styles.providerOptionActive,
                      ]}
                      onPress={() => handleProviderChange(provider)}
                    >
                      <Text style={[
                        styles.providerOptionText,
                        getAgentProvider() === provider && styles.providerOptionTextActive,
                      ]}>
                        {provider === 'chatgpt-web' ? 'OpenAI Codex' : provider.charAt(0).toUpperCase() + provider.slice(1)}
                      </Text>
                    </Pressable>
                  ))}
                </View>

                {getAgentProvider() === 'chatgpt-web' && (
                  <View style={styles.chatGptWebAuthBlock}>
                    <Text style={styles.label}>OpenAI Codex OAuth</Text>
                    <View style={styles.serverNameRow}>
                      <View style={[
                        styles.statusDot,
                        chatGptWebAuthStatus?.authenticated ? styles.statusConnected : styles.statusDisconnected,
                      ]} />
                      <Text style={styles.serverName}>
                        {chatGptWebAuthStatus?.authenticated ? 'Connected' : 'Not connected'}
                      </Text>
                    </View>
                    {chatGptWebAuthStatus?.authenticated && (
                      <Text style={styles.helperText}>
                        {[chatGptWebAuthStatus.email, chatGptWebAuthStatus.planType].filter(Boolean).join(' • ') || 'OpenAI Codex'}
                      </Text>
                    )}
                    {chatGptWebAuthStatus?.callbackUrl && (
                      <Text style={styles.helperText} numberOfLines={2}>
                        Callback URL: {chatGptWebAuthStatus.callbackUrl}
                      </Text>
                    )}
                    <View style={styles.profileActions}>
                      <TouchableOpacity
                        style={[styles.profileActionButton, pendingChatGptWebAuthAction && styles.profileActionButtonDisabled]}
                        onPress={handleChatGptWebOAuthLogin}
                        disabled={!!pendingChatGptWebAuthAction}
                        accessibilityRole="button"
                        accessibilityLabel={createButtonAccessibilityLabel('Connect OpenAI Codex OAuth')}
                      >
                        <Text style={styles.profileActionButtonText}>
                          {pendingChatGptWebAuthAction === 'login'
                            ? 'Connecting...'
                            : chatGptWebAuthStatus?.authenticated ? 'Reconnect' : 'Connect'}
                        </Text>
                      </TouchableOpacity>
                      <TouchableOpacity
                        style={[
                          styles.dangerActionButton,
                          (!chatGptWebAuthStatus?.authenticated || pendingChatGptWebAuthAction) && styles.dangerActionButtonDisabled,
                        ]}
                        onPress={handleChatGptWebOAuthLogout}
                        disabled={!chatGptWebAuthStatus?.authenticated || !!pendingChatGptWebAuthAction}
                        accessibilityRole="button"
                        accessibilityLabel={createButtonAccessibilityLabel('Disconnect OpenAI Codex OAuth')}
                      >
                        <Text style={[
                          styles.dangerActionButtonText,
                          !chatGptWebAuthStatus?.authenticated && styles.dangerActionButtonTextDisabled,
                        ]}>
                          {pendingChatGptWebAuthAction === 'logout' ? 'Disconnecting...' : 'Disconnect'}
                        </Text>
                      </TouchableOpacity>
                    </View>
                    <View style={styles.providerCredentialGroup}>
                      <Text style={styles.subsectionTitle}>Codex Options</Text>
                      <Text style={styles.label}>Reasoning Effort</Text>
                      <View style={styles.providerSelector}>
                        {OPENAI_REASONING_EFFORT_OPTIONS.map((option) => (
                          <Pressable
                            key={option.value}
                            style={[
                              styles.providerOption,
                              (remoteSettings.openaiReasoningEffort || DEFAULT_CODEX_REASONING_EFFORT) === option.value && styles.providerOptionActive,
                            ]}
                            onPress={() => handleRemoteSettingUpdate('openaiReasoningEffort', option.value)}
                          >
                            <Text style={[
                              styles.providerOptionText,
                              (remoteSettings.openaiReasoningEffort || DEFAULT_CODEX_REASONING_EFFORT) === option.value && styles.providerOptionTextActive,
                            ]}>
                              {option.label}
                            </Text>
                          </Pressable>
                        ))}
                      </View>
                      <Text style={[styles.label, { marginTop: spacing.sm }]}>Text Verbosity</Text>
                      <View style={styles.providerSelector}>
                        {CODEX_TEXT_VERBOSITY_OPTIONS.map((option) => (
                          <Pressable
                            key={option.value}
                            style={[
                              styles.providerOption,
                              (remoteSettings.codexTextVerbosity || DEFAULT_CODEX_TEXT_VERBOSITY) === option.value && styles.providerOptionActive,
                            ]}
                            onPress={() => handleRemoteSettingUpdate('codexTextVerbosity', option.value)}
                          >
                            <Text style={[
                              styles.providerOptionText,
                              (remoteSettings.codexTextVerbosity || DEFAULT_CODEX_TEXT_VERBOSITY) === option.value && styles.providerOptionTextActive,
                            ]}>
                              {option.label}
                            </Text>
                          </Pressable>
                        ))}
                      </View>
                    </View>
                  </View>
                )}

                {getAgentProvider() === 'openai' && remoteSettings.availablePresets && remoteSettings.availablePresets.length > 0 && (
                  <>
                    <Text style={styles.label}>OpenAI Compatible Endpoint</Text>
                    <TouchableOpacity
                      style={styles.modelSelector}
                      onPress={() => setShowPresetPicker(true)}
                    >
                      <View style={styles.modelSelectorContent}>
                        <Text style={styles.modelSelectorText}>
                          {getCurrentPresetName()}
                        </Text>
                        <Text style={styles.modelSelectorChevron}>▼</Text>
                      </View>
                    </TouchableOpacity>
                    {(() => {
                      const currentPreset = getCurrentModelPreset();
                      return (
                        <View style={styles.endpointPanel}>
                          <View style={styles.endpointMetaRow}>
                            <Text style={styles.endpointBaseUrl} numberOfLines={1}>
                              {currentPreset?.baseUrl || 'No URL set'}
                            </Text>
                            <Text style={[styles.endpointKeyBadge, currentPreset?.hasApiKey && styles.endpointKeyBadgeActive]}>
                              {currentPreset?.hasApiKey ? 'Key set' : 'No key'}
                            </Text>
                          </View>
                          <View style={styles.profileActions}>
                            <TouchableOpacity
                              style={styles.profileActionButton}
                              onPress={() => currentPreset && openPresetEditor('edit', currentPreset)}
                              disabled={!currentPreset}
                              accessibilityRole="button"
                              accessibilityLabel={`${
                                currentPreset?.isBuiltIn ? 'Configure' : 'Edit'
                              } preset ${currentPreset?.name || getCurrentPresetName()}`}
                            >
                              <Text style={styles.profileActionButtonText}>
                                {currentPreset?.isBuiltIn ? 'Configure' : 'Edit'}
                              </Text>
                            </TouchableOpacity>
                            <TouchableOpacity
                              style={styles.profileActionButton}
                              onPress={() => openPresetEditor('create')}
                              accessibilityRole="button"
                              accessibilityLabel={createButtonAccessibilityLabel('Create endpoint preset')}
                            >
                              <Text style={styles.profileActionButtonText}>New Preset</Text>
                            </TouchableOpacity>
                          </View>
                        </View>
                      );
                    })()}
                  </>
                )}

                <View style={styles.modelLabelRow}>
                  <Text style={styles.label}>Model Name</Text>
                  <View style={styles.modelActions}>
                    <TouchableOpacity
                      style={styles.modelActionButton}
                      onPress={() => setUseCustomModel(!useCustomModel)}
                      accessibilityRole="button"
                      accessibilityLabel={useCustomModel ? 'Show model list' : 'Enter custom model name'}
                    >
                      <Text style={styles.modelActionText}>
                        {useCustomModel ? 'List' : 'Custom'}
                      </Text>
                    </TouchableOpacity>
                    {!useCustomModel && (
                      <TouchableOpacity
                        style={[styles.modelActionButton, isLoadingModels && styles.modelActionButtonDisabled]}
                        onPress={() => remoteSettings && fetchModels(getAgentProvider())}
                        disabled={isLoadingModels}
                        accessibilityRole="button"
                        accessibilityLabel="Refresh available models"
                      >
                        <Text style={styles.modelActionText}>
                          {isLoadingModels ? 'Refreshing…' : 'Refresh'}
                        </Text>
                      </TouchableOpacity>
                    )}
                  </View>
                </View>

                {useCustomModel ? (
                  <TextInput
                    style={styles.input}
                    value={customModelDraft}
                    onChangeText={handleModelNameChange}
                    placeholder={getModelPlaceholder()}
                    placeholderTextColor={theme.colors.mutedForeground}
                    autoCapitalize='none'
                  />
                ) : (
                  <TouchableOpacity
                    style={styles.modelSelector}
                    onPress={() => setShowModelPicker(true)}
                    disabled={isLoadingModels}
                  >
                    {isLoadingModels ? (
                      <View style={styles.modelSelectorContent}>
                        <ActivityIndicator size="small" color={theme.colors.mutedForeground} />
                        <Text style={styles.modelSelectorPlaceholder}>Loading models...</Text>
                      </View>
                    ) : (
                      <View style={styles.modelSelectorContent}>
                        <Text style={[
                          styles.modelSelectorText,
                          !getCurrentModelValue() && styles.modelSelectorPlaceholder
                        ]}>
                          {getCurrentModelDisplayName()}
                        </Text>
                        <Text style={styles.modelSelectorChevron}>▼</Text>
                      </View>
                    )}
                  </TouchableOpacity>
                )}

                <Text style={[styles.label, { marginTop: spacing.lg }]}>Transcript Processing</Text>
                <Text style={styles.helperText}>
                  Clean up transcripts after speech-to-text and before they are used elsewhere.
                </Text>

                <View style={styles.row}>
                  <Text style={styles.label}>Enabled</Text>
                  <Switch
                    value={remoteSettings.transcriptPostProcessingEnabled ?? DEFAULT_TRANSCRIPT_POST_PROCESSING_ENABLED}
                    onValueChange={(v) => handleRemoteSettingToggle('transcriptPostProcessingEnabled', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={(remoteSettings.transcriptPostProcessingEnabled ?? DEFAULT_TRANSCRIPT_POST_PROCESSING_ENABLED) ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>

                <Text style={styles.label}>Provider</Text>
                <View style={styles.providerSelector}>
                  {CHAT_PROVIDERS.map((provider) => (
                    <Pressable
                      key={provider.value}
                      style={[
                        styles.providerOption,
                        (remoteSettings.transcriptPostProcessingProviderId || DEFAULT_TRANSCRIPT_POST_PROCESSING_PROVIDER_ID) === provider.value && styles.providerOptionActive,
                      ]}
                      onPress={() => handleRemoteSettingUpdate('transcriptPostProcessingProviderId', provider.value)}
                    >
                      <Text style={[
                        styles.providerOptionText,
                        (remoteSettings.transcriptPostProcessingProviderId || DEFAULT_TRANSCRIPT_POST_PROCESSING_PROVIDER_ID) === provider.value && styles.providerOptionTextActive,
                      ]}>
                        {provider.label}
                      </Text>
                    </Pressable>
                  ))}
                </View>

                {(remoteSettings.transcriptPostProcessingEnabled ?? DEFAULT_TRANSCRIPT_POST_PROCESSING_ENABLED) && (
                  <>
                    {(() => {
                      const providerId = remoteSettings.transcriptPostProcessingProviderId || DEFAULT_TRANSCRIPT_POST_PROCESSING_PROVIDER_ID;
                      const modelKey = providerId === 'openai'
                        ? 'transcriptPostProcessingOpenaiModel'
                        : providerId === 'groq'
                          ? 'transcriptPostProcessingGroqModel'
                          : providerId === 'gemini'
                            ? 'transcriptPostProcessingGeminiModel'
                            : 'transcriptPostProcessingChatgptWebModel';
                      return (
                        <>
                          <Text style={styles.label}>Model</Text>
                          <TextInput
                            style={styles.input}
                            value={inputDrafts[modelKey] ?? ''}
                            onChangeText={(v) => handleRemoteSettingUpdate(modelKey, v)}
                            placeholder={getModelPlaceholder()}
                            placeholderTextColor={theme.colors.mutedForeground}
                            autoCapitalize="none"
                            autoCorrect={false}
                          />
                        </>
                      );
                    })()}
                    <Text style={styles.label}>Prompt</Text>
                    <TextInput
                      style={[styles.input, { minHeight: 80, textAlignVertical: 'top' }]}
                      value={inputDrafts.transcriptPostProcessingPrompt ?? ''}
                      onChangeText={(v) => handleRemoteSettingUpdate('transcriptPostProcessingPrompt', v)}
                      placeholder="Custom instructions for transcript processing..."
                      placeholderTextColor={theme.colors.mutedForeground}
                      multiline
                      numberOfLines={3}
                    />
                  </>
                )}
              </CollapsibleSection>
            )}

            {/* 4b. Bundles */}
            {isDotAgentsServer && (
              <CollapsibleSection id="bundles" title="Bundles">
                <View style={styles.profileActions}>
                  <TouchableOpacity
                    style={[styles.profileActionButton, isImportingBundle && styles.profileActionButtonDisabled]}
                    onPress={() => setShowBundleImportModal(true)}
                    disabled={isImportingBundle}
                    accessibilityRole="button"
                    accessibilityLabel={createButtonAccessibilityLabel('Import DotAgents bundle JSON')}
                  >
                    <Text style={styles.profileActionButtonText}>
                      {isImportingBundle ? 'Importing...' : 'Import Bundle'}
                    </Text>
                  </TouchableOpacity>
                  <TouchableOpacity
                    style={[styles.profileActionButton, isExportingBundle && styles.profileActionButtonDisabled]}
                    onPress={handleBundleExport}
                    disabled={isExportingBundle}
                    accessibilityRole="button"
                    accessibilityLabel={createButtonAccessibilityLabel('Export DotAgents bundle JSON')}
                  >
                    <Text style={styles.profileActionButtonText}>
                      {isExportingBundle ? 'Exporting...' : 'Export Bundle'}
                    </Text>
                  </TouchableOpacity>
                </View>
                <Text style={styles.helperText}>
                  Bundle agents, MCP servers, skills, repeat tasks, and knowledge notes into portable .dotagents JSON.
                </Text>
              </CollapsibleSection>
            )}

            {/* 4b. Streamer Mode */}
            {remoteSettings && (
              <CollapsibleSection id="streamerMode" title="Streamer Mode">
                <View style={styles.row}>
                  <Text style={styles.label}>Streamer Mode</Text>
                  <Switch
                    value={remoteSettings.streamerModeEnabled ?? DEFAULT_STREAMER_MODE_ENABLED}
                    onValueChange={(v) => handleRemoteSettingToggle('streamerModeEnabled', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={(remoteSettings.streamerModeEnabled ?? DEFAULT_STREAMER_MODE_ENABLED) ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>
                <Text style={styles.helperText}>
                  Hide sensitive information when streaming or sharing screen
                </Text>
              </CollapsibleSection>
            )}

            {/* 4c. Speech-to-Text */}
            {remoteSettings && (
              <CollapsibleSection id="speechToText" title="Speech-to-Text">
                <Text style={styles.label}>STT Language</Text>
                <TextInput
                  style={styles.input}
                  value={inputDrafts.sttLanguage ?? ''}
                  onChangeText={(v) => handleRemoteSettingUpdate('sttLanguage', v)}
                  placeholder="en (default)"
                  placeholderTextColor={theme.colors.mutedForeground}
                  autoCapitalize='none'
                />
                <Text style={styles.helperText}>
                  Language code for speech-to-text (e.g., en, es, fr)
                </Text>

                {(remoteSettings.sttProviderId === 'openai' || remoteSettings.sttProviderId === 'groq') && (() => {
                  const providerId = remoteSettings.sttProviderId;
                  const languageKey = providerId === 'openai' ? 'openaiSttLanguage' : 'groqSttLanguage';
                  const languageLabel = providerId === 'openai' ? 'OpenAI Language Override' : 'Groq Language Override';
                  const modelKey = providerId === 'openai' ? 'openaiSttModel' : 'groqSttModel';
                  const currentModel = remoteSettings[modelKey] || getDefaultSttModel(providerId) || '';

                  return (
                    <>
                      <Text style={styles.label}>{languageLabel}</Text>
                      <TextInput
                        style={styles.input}
                        value={inputDrafts[languageKey] ?? ''}
                        onChangeText={(v) => handleRemoteSettingUpdate(languageKey, v)}
                        placeholder="Use global language"
                        placeholderTextColor={theme.colors.mutedForeground}
                        autoCapitalize="none"
                        autoCorrect={false}
                      />
                      <Text style={styles.helperText}>Leave blank to use STT Language.</Text>

                      <Text style={styles.label}>STT Model</Text>
                      <View style={styles.providerSelector}>
                        {KNOWN_STT_MODEL_IDS[providerId].map((modelId) => (
                          <Pressable
                            key={modelId}
                            style={[
                              styles.providerOption,
                              currentModel === modelId && styles.providerOptionActive,
                            ]}
                            onPress={() => handleRemoteSettingUpdate(modelKey, modelId)}
                            accessibilityRole="button"
                            accessibilityLabel={createButtonAccessibilityLabel(`Use ${modelId} for ${providerId} speech-to-text`)}
                          >
                            <Text style={[
                              styles.providerOptionText,
                              currentModel === modelId && styles.providerOptionTextActive,
                            ]}>
                              {modelId}
                            </Text>
                          </Pressable>
                        ))}
                      </View>
                    </>
                  );
                })()}

                {remoteSettings.sttProviderId === 'groq' && (
                  <>
                    <Text style={styles.label}>Groq Prompt</Text>
                    <TextInput
                      style={[styles.input, { minHeight: 88, textAlignVertical: 'top' }]}
                      value={inputDrafts.groqSttPrompt ?? ''}
                      onChangeText={(v) => handleRemoteSettingUpdate('groqSttPrompt', v)}
                      placeholder="Optional spelling or style hints for Groq transcription"
                      placeholderTextColor={theme.colors.mutedForeground}
                      multiline
                      numberOfLines={4}
                    />
                    <Text style={styles.helperText}>Optional hints for spelling unfamiliar words or names.</Text>
                  </>
                )}

                {remoteSettings.sttProviderId === 'parakeet' && (
                  <>
                    <Text style={styles.label}>Parakeet CPU Threads</Text>
                    <View style={styles.providerSelector}>
                      {PARAKEET_NUM_THREAD_OPTIONS.map((threadCount) => (
                        <Pressable
                          key={threadCount}
                          style={[
                            styles.providerOption,
                            (remoteSettings.parakeetNumThreads ?? DEFAULT_PARAKEET_NUM_THREADS) === threadCount && styles.providerOptionActive,
                          ]}
                          onPress={() => handleRemoteSettingUpdate('parakeetNumThreads', threadCount)}
                        >
                          <Text style={[
                            styles.providerOptionText,
                            (remoteSettings.parakeetNumThreads ?? DEFAULT_PARAKEET_NUM_THREADS) === threadCount && styles.providerOptionTextActive,
                          ]}>
                            {threadCount}
                          </Text>
                        </Pressable>
                      ))}
                    </View>
                    <Text style={styles.helperText}>Controls local Parakeet transcription CPU usage on the paired desktop.</Text>
                  </>
                )}

                <View style={styles.row}>
                  <Text style={styles.label}>Transcription Preview</Text>
                  <Switch
                    value={remoteSettings.transcriptionPreviewEnabled ?? DEFAULT_TRANSCRIPTION_PREVIEW_ENABLED}
                    onValueChange={(v) => handleRemoteSettingToggle('transcriptionPreviewEnabled', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={(remoteSettings.transcriptionPreviewEnabled ?? DEFAULT_TRANSCRIPTION_PREVIEW_ENABLED) ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>
                <Text style={styles.helperText}>
                  Show live transcription while recording
                </Text>

              </CollapsibleSection>
            )}

            {/* 4d. Text-to-Speech */}
            {remoteSettings && (
              <CollapsibleSection id="textToSpeech" title="Text-to-Speech">
                <View style={styles.row}>
                  <Text style={styles.label}>TTS Enabled</Text>
                  <Switch
                    value={remoteSettings.ttsEnabled ?? DEFAULT_TTS_ENABLED}
                    onValueChange={(v) => handleRemoteSettingToggle('ttsEnabled', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={(remoteSettings.ttsEnabled ?? DEFAULT_TTS_ENABLED) ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>
                <Text style={styles.helperText}>
                  Enable text-to-speech for responses on desktop
                </Text>

                {(remoteSettings.ttsEnabled ?? DEFAULT_TTS_ENABLED) && (
                  <>
                    {/* TTS Provider Selector */}
                    <Text style={[styles.label, { marginTop: spacing.md }]}>TTS Provider</Text>
                    <ScrollView horizontal showsHorizontalScrollIndicator={false} style={{ marginVertical: spacing.sm }}>
                      <View style={styles.providerSelector}>
                        {TTS_PROVIDERS.map((provider) => (
                          <Pressable
                            key={provider.value}
                            style={[
                              styles.providerOption,
                              (remoteSettings.ttsProviderId || DEFAULT_TTS_PROVIDER_ID) === provider.value && styles.providerOptionActive,
                            ]}
                            onPress={() => handleRemoteSettingUpdate('ttsProviderId', provider.value)}
                          >
                            <Text style={[
                              styles.providerOptionText,
                              (remoteSettings.ttsProviderId || DEFAULT_TTS_PROVIDER_ID) === provider.value && styles.providerOptionTextActive,
                            ]}>
                              {provider.label}
                            </Text>
                          </Pressable>
                        ))}
                      </View>
                    </ScrollView>

                    {/* Per-provider Voice/Model Settings */}
                    {(() => {
                      const remoteTtsProviderId = remoteSettings.ttsProviderId || DEFAULT_TTS_PROVIDER_ID;
                      const ttsModel = remoteTtsProviderId === 'groq' ? remoteSettings.groqTtsModel : undefined;
                      const models = getTtsModelsForProvider(remoteTtsProviderId);
                      const voices = getTtsVoicesForProvider(remoteTtsProviderId, ttsModel);
                      const modelValue = remoteTtsProviderId === 'openai' ? remoteSettings.openaiTtsModel
                        : remoteTtsProviderId === 'groq' ? remoteSettings.groqTtsModel
                        : remoteTtsProviderId === 'gemini' ? remoteSettings.geminiTtsModel
                        : remoteSettings.edgeTtsModel;
                      const voiceValue = remoteTtsProviderId === 'openai' ? remoteSettings.openaiTtsVoice
                        : remoteTtsProviderId === 'groq' ? remoteSettings.groqTtsVoice
                        : remoteTtsProviderId === 'gemini' ? remoteSettings.geminiTtsVoice
                        : remoteTtsProviderId === 'edge' ? remoteSettings.edgeTtsVoice
                        : remoteTtsProviderId === 'kitten' ? remoteSettings.kittenVoiceId
                        : remoteSettings.supertonicVoice;
                      const model = models.find(m => m.value === modelValue);
                      const voice = voices.find(v => String(v.value) === String(voiceValue));

                      return (
                        <>
                          {models.length > 0 && (
                            <>
                              <Text style={[styles.label, { marginTop: spacing.sm }]}>Model</Text>
                              <TouchableOpacity
                                style={styles.modelSelector}
                                onPress={() => setShowTtsModelPicker(true)}
                              >
                                <View style={styles.modelSelectorContent}>
                                  <Text style={styles.modelSelectorText}>
                                    {model?.label || modelValue || 'Select model'}
                                  </Text>
                                  <Text style={styles.modelSelectorChevron}>▼</Text>
                                </View>
                              </TouchableOpacity>
                            </>
                          )}

                          {voices.length > 0 && (
                            <>
                              <Text style={[styles.label, { marginTop: spacing.sm }]}>Voice</Text>
                              <TouchableOpacity
                                style={styles.modelSelector}
                                onPress={() => setShowTtsVoicePicker(true)}
                              >
                                <View style={styles.modelSelectorContent}>
                                  <Text style={styles.modelSelectorText}>
                                    {voice?.label || String(voiceValue ?? '') || 'Select voice'}
                                  </Text>
                                  <Text style={styles.modelSelectorChevron}>▼</Text>
                                </View>
                              </TouchableOpacity>
                            </>
                          )}

                          {remoteTtsProviderId === 'openai' && (
                            <View style={{ marginTop: spacing.sm }}>
                              <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
                                <Text style={styles.label}>Speed</Text>
                                <Text style={[styles.helperText, { marginTop: 0 }]}>
                                  {(remoteSettings.openaiTtsSpeed ?? 1.0).toFixed(1)}x
                                </Text>
                              </View>
                              <Slider
                                style={{ width: '100%', height: 40 }}
                                minimumValue={0.25}
                                maximumValue={4.0}
                                step={0.25}
                                value={remoteSettings.openaiTtsSpeed ?? 1.0}
                                onSlidingComplete={(v) => handleRemoteSettingUpdate('openaiTtsSpeed', v)}
                                minimumTrackTintColor={theme.colors.primary}
                                maximumTrackTintColor={theme.colors.muted}
                                thumbTintColor={theme.colors.primary}
                              />
                            </View>
                          )}

                          {remoteTtsProviderId === 'edge' && (
                            <View style={{ marginTop: spacing.sm }}>
                              <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
                                <Text style={styles.label}>Speed</Text>
                                <Text style={[styles.helperText, { marginTop: 0 }]}>
                                  {(remoteSettings.edgeTtsRate ?? 1.0).toFixed(1)}x
                                </Text>
                              </View>
                              <Slider
                                style={{ width: '100%', height: 40 }}
                                minimumValue={0.5}
                                maximumValue={2.0}
                                step={0.1}
                                value={remoteSettings.edgeTtsRate ?? 1.0}
                                onSlidingComplete={(v) => handleRemoteSettingUpdate('edgeTtsRate', v)}
                                minimumTrackTintColor={theme.colors.primary}
                                maximumTrackTintColor={theme.colors.muted}
                                thumbTintColor={theme.colors.primary}
                              />
                            </View>
                          )}

                          {remoteTtsProviderId === 'supertonic' && (
                            <>
                              <Text style={[styles.label, { marginTop: spacing.sm }]}>Language</Text>
                              <View style={styles.providerSelector}>
                                {SUPERTONIC_TTS_LANGUAGES.map((language) => (
                                  <Pressable
                                    key={language.value}
                                    style={[
                                      styles.providerOption,
                                      (remoteSettings.supertonicLanguage ?? DEFAULT_SUPERTONIC_TTS_LANGUAGE) === language.value && styles.providerOptionActive,
                                    ]}
                                    onPress={() => handleRemoteSettingUpdate('supertonicLanguage', language.value)}
                                  >
                                    <Text style={[
                                      styles.providerOptionText,
                                      (remoteSettings.supertonicLanguage ?? DEFAULT_SUPERTONIC_TTS_LANGUAGE) === language.value && styles.providerOptionTextActive,
                                    ]}>
                                      {language.label}
                                    </Text>
                                  </Pressable>
                                ))}
                              </View>

                              <View style={{ marginTop: spacing.sm }}>
                                <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
                                  <Text style={styles.label}>Quality Steps</Text>
                                  <Text style={[styles.helperText, { marginTop: 0 }]}>
                                    {Math.round(remoteSettings.supertonicSteps ?? DEFAULT_SUPERTONIC_TTS_STEPS)}
                                  </Text>
                                </View>
                                <Slider
                                  style={{ width: '100%', height: 40 }}
                                  minimumValue={MIN_SUPERTONIC_TTS_STEPS}
                                  maximumValue={MAX_SUPERTONIC_TTS_STEPS}
                                  step={1}
                                  value={remoteSettings.supertonicSteps ?? DEFAULT_SUPERTONIC_TTS_STEPS}
                                  onSlidingComplete={(v) => handleRemoteSettingUpdate('supertonicSteps', Math.round(v))}
                                  minimumTrackTintColor={theme.colors.primary}
                                  maximumTrackTintColor={theme.colors.muted}
                                  thumbTintColor={theme.colors.primary}
                                />
                              </View>
                            </>
                          )}
                        </>
                      );
                    })()}

                    <View style={[styles.row, { marginTop: spacing.md }]}>
                      <Text style={styles.label}>Auto-Play</Text>
                      <Switch
                        value={remoteSettings.ttsAutoPlay ?? DEFAULT_TTS_AUTO_PLAY}
                        onValueChange={(v) => handleRemoteSettingToggle('ttsAutoPlay', v)}
                        trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                        thumbColor={(remoteSettings.ttsAutoPlay ?? DEFAULT_TTS_AUTO_PLAY) ? theme.colors.primaryForeground : theme.colors.background}
                      />
                    </View>

                    <View style={styles.row}>
                      <Text style={styles.label}>TTS Preprocessing</Text>
                      <Switch
                        value={remoteSettings.ttsPreprocessingEnabled ?? DEFAULT_TTS_PREPROCESSING_ENABLED}
                        onValueChange={(v) => handleRemoteSettingToggle('ttsPreprocessingEnabled', v)}
                        trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                        thumbColor={(remoteSettings.ttsPreprocessingEnabled ?? DEFAULT_TTS_PREPROCESSING_ENABLED) ? theme.colors.primaryForeground : theme.colors.background}
                      />
                    </View>
                    <Text style={styles.helperText}>
                      Clean up text before speaking
                    </Text>

                    {(remoteSettings.ttsPreprocessingEnabled ?? DEFAULT_TTS_PREPROCESSING_ENABLED) && (
                      <>
                        <View style={[styles.row, { paddingLeft: spacing.md }]}>
                          <Text style={styles.label}>Remove Code Blocks</Text>
                          <Switch
                            value={remoteSettings.ttsRemoveCodeBlocks ?? DEFAULT_TTS_REMOVE_CODE_BLOCKS}
                            onValueChange={(v) => handleRemoteSettingToggle('ttsRemoveCodeBlocks', v)}
                            trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                            thumbColor={(remoteSettings.ttsRemoveCodeBlocks ?? DEFAULT_TTS_REMOVE_CODE_BLOCKS) ? theme.colors.primaryForeground : theme.colors.background}
                          />
                        </View>

                        <View style={[styles.row, { paddingLeft: spacing.md }]}>
                          <Text style={styles.label}>Remove URLs</Text>
                          <Switch
                            value={remoteSettings.ttsRemoveUrls ?? DEFAULT_TTS_REMOVE_URLS}
                            onValueChange={(v) => handleRemoteSettingToggle('ttsRemoveUrls', v)}
                            trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                            thumbColor={(remoteSettings.ttsRemoveUrls ?? DEFAULT_TTS_REMOVE_URLS) ? theme.colors.primaryForeground : theme.colors.background}
                          />
                        </View>

                        <View style={[styles.row, { paddingLeft: spacing.md }]}>
                          <Text style={styles.label}>Convert Markdown</Text>
                          <Switch
                            value={remoteSettings.ttsConvertMarkdown ?? DEFAULT_TTS_CONVERT_MARKDOWN}
                            onValueChange={(v) => handleRemoteSettingToggle('ttsConvertMarkdown', v)}
                            trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                            thumbColor={(remoteSettings.ttsConvertMarkdown ?? DEFAULT_TTS_CONVERT_MARKDOWN) ? theme.colors.primaryForeground : theme.colors.background}
                          />
                        </View>

                        <View style={[styles.row, { paddingLeft: spacing.md }]}>
                          <Text style={styles.label}>Use LLM Preprocessing</Text>
                          <Switch
                            value={remoteSettings.ttsUseLLMPreprocessing ?? DEFAULT_TTS_USE_LLM_PREPROCESSING}
                            onValueChange={(v) => handleRemoteSettingToggle('ttsUseLLMPreprocessing', v)}
                            trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                            thumbColor={(remoteSettings.ttsUseLLMPreprocessing ?? DEFAULT_TTS_USE_LLM_PREPROCESSING) ? theme.colors.primaryForeground : theme.colors.background}
                          />
                        </View>
                      </>
                    )}
                  </>
                )}
              </CollapsibleSection>
            )}

            {/* 4e. Agent Settings */}
            {remoteSettings && (
              <CollapsibleSection id="agentSettings" title="Agent Settings">
                <Text style={styles.label}>Main Agent Mode</Text>
                <View style={styles.providerSelector}>
                  {MAIN_AGENT_MODE_OPTIONS.map((option) => (
                    <Pressable
                      key={option.value}
                      style={[
                        styles.providerOption,
                        (remoteSettings.mainAgentMode ?? DEFAULT_MAIN_AGENT_MODE) === option.value && styles.providerOptionActive,
                      ]}
                      onPress={() => handleRemoteSettingUpdate('mainAgentMode', option.value)}
                    >
                      <Text style={[
                        styles.providerOptionText,
                        (remoteSettings.mainAgentMode ?? DEFAULT_MAIN_AGENT_MODE) === option.value && styles.providerOptionTextActive,
                      ]}>
                        {option.compactLabel}
                      </Text>
                    </Pressable>
                  ))}
                </View>
                <Text style={styles.helperText}>
                  API uses external LLMs, acpx routes to an acpx-managed agent
                </Text>

                {/* acpx-specific settings - only show when acpx mode selected */}
                {(remoteSettings.mainAgentMode ?? DEFAULT_MAIN_AGENT_MODE) === 'acpx' && (
                  <>
                    <Text style={styles.label}>acpx Agent</Text>
                    {availableAcpMainAgents.length > 0 ? (
                      <View style={styles.providerSelector}>
                        {availableAcpMainAgents.map((agent) => (
                          <Pressable
                            key={agent.name}
                            style={[
                              styles.providerOption,
                              remoteSettings.mainAgentName === agent.name && styles.providerOptionActive,
                            ]}
                            onPress={() => handleRemoteSettingUpdate('mainAgentName', agent.name)}
                          >
                            <Text style={[
                              styles.providerOptionText,
                              remoteSettings.mainAgentName === agent.name && styles.providerOptionTextActive,
                            ]}>
                              {agent.displayName || agent.name}
                            </Text>
                          </Pressable>
                        ))}
                      </View>
                    ) : (
                      <Text style={styles.helperText}>No acpx agents available</Text>
                    )}
                    <Text style={styles.helperText}>
                      Select which acpx agent handles requests
                    </Text>
                  </>
                )}

                <View style={styles.row}>
                  <Text style={styles.label}>Message Queue</Text>
                  <Switch
                    value={remoteSettings.mcpMessageQueueEnabled ?? true}
                    onValueChange={(v) => handleRemoteSettingToggle('mcpMessageQueueEnabled', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={remoteSettings.mcpMessageQueueEnabled ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>

                <View style={styles.row}>
                  <View style={{ flex: 1 }}>
                    <Text style={styles.label}>Auto Paste Results</Text>
                    <Text style={[styles.helperText, { marginTop: 2 }]}>Paste final agent results back into the active desktop input.</Text>
                  </View>
                  <Switch
                    value={remoteSettings.mcpAutoPasteEnabled ?? DEFAULT_MCP_AUTO_PASTE_ENABLED}
                    onValueChange={(v) => handleRemoteSettingToggle('mcpAutoPasteEnabled', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={(remoteSettings.mcpAutoPasteEnabled ?? DEFAULT_MCP_AUTO_PASTE_ENABLED) ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>
                <Text style={styles.label}>Auto Paste Delay</Text>
                <TextInput
                  style={styles.input}
                  value={inputDrafts.mcpAutoPasteDelay ?? String(DEFAULT_MCP_AUTO_PASTE_DELAY)}
                  onChangeText={(v) => {
                    markRemotePending('mcpAutoPasteDelay');
                    setSaveStatusMessage(null);
                    const parsedDelay = parseMcpAutoPasteDelayDraft(v);
                    if (parsedDelay === null) {
                      setInputDrafts(prev => ({ ...prev, mcpAutoPasteDelay: v }));
                      return;
                    }
                    handleRemoteSettingUpdate('mcpAutoPasteDelay', parsedDelay);
                  }}
                  placeholder={String(DEFAULT_MCP_AUTO_PASTE_DELAY)}
                  placeholderTextColor={theme.colors.mutedForeground}
                  keyboardType="number-pad"
                />
                <Text style={styles.helperText}>Wait this many milliseconds before auto-pasting desktop results.</Text>

                <View style={styles.row}>
                  <Text style={styles.label}>Require Tool Approval</Text>
                  <Switch
                    value={remoteSettings.mcpRequireApprovalBeforeToolCall ?? false}
                    onValueChange={(v) => handleRemoteSettingToggle('mcpRequireApprovalBeforeToolCall', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={remoteSettings.mcpRequireApprovalBeforeToolCall ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>
                <Text style={styles.helperText}>
                  Require approval before executing MCP tools
                </Text>

                <View style={styles.row}>
                  <Text style={styles.label}>Verify Completion</Text>
                  <Switch
                    value={remoteSettings.mcpVerifyCompletionEnabled ?? true}
                    onValueChange={(v) => handleRemoteSettingToggle('mcpVerifyCompletionEnabled', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={remoteSettings.mcpVerifyCompletionEnabled ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>

                <View style={styles.row}>
                  <Text style={styles.label}>Final Summary</Text>
                  <Switch
                    value={remoteSettings.mcpFinalSummaryEnabled ?? false}
                    onValueChange={(v) => handleRemoteSettingToggle('mcpFinalSummaryEnabled', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={remoteSettings.mcpFinalSummaryEnabled ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>
                <Text style={styles.helperText}>
                  Generate a summary after completing a task
                </Text>

                <Text style={styles.label}>Max Iterations</Text>
                <TextInput
                  style={styles.input}
                  value={inputDrafts.mcpMaxIterations ?? '10'}
                  onChangeText={(v) => {
                    markRemotePending('mcpMaxIterations');
                    setSaveStatusMessage(null);
                    const num = parseInt(v, 10);
                    if (!isNaN(num) && num >= 1 && num <= 100) {
                      handleRemoteSettingUpdate('mcpMaxIterations', num);
                    } else {
                      setInputDrafts(prev => ({ ...prev, mcpMaxIterations: v }));
                    }
                  }}
                  placeholder="10"
                  placeholderTextColor={theme.colors.mutedForeground}
                  keyboardType="number-pad"
                />

                <View style={styles.row}>
                  <Text style={styles.label}>Unlimited Iterations</Text>
                  <Switch
                    value={remoteSettings.mcpUnlimitedIterations ?? true}
                    onValueChange={(v) => handleRemoteSettingToggle('mcpUnlimitedIterations', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={remoteSettings.mcpUnlimitedIterations ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>
              </CollapsibleSection>
            )}

            {/* 4f. Tool Execution */}
            {remoteSettings && (
              <CollapsibleSection id="toolExecution" title="Tool Execution">
                <View style={styles.row}>
                  <Text style={styles.label}>Context Reduction</Text>
                  <Switch
                    value={remoteSettings.mcpContextReductionEnabled ?? false}
                    onValueChange={(v) => handleRemoteSettingToggle('mcpContextReductionEnabled', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={remoteSettings.mcpContextReductionEnabled ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>
                <Text style={styles.helperText}>
                  Reduce context size for tool responses
                </Text>

                <View style={styles.row}>
                  <Text style={styles.label}>Tool Response Processing</Text>
                  <Switch
                    value={remoteSettings.mcpToolResponseProcessingEnabled ?? false}
                    onValueChange={(v) => handleRemoteSettingToggle('mcpToolResponseProcessingEnabled', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={remoteSettings.mcpToolResponseProcessingEnabled ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>

                <View style={styles.row}>
                  <Text style={styles.label}>Parallel Tool Execution</Text>
                  <Switch
                    value={remoteSettings.mcpParallelToolExecution ?? false}
                    onValueChange={(v) => handleRemoteSettingToggle('mcpParallelToolExecution', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={remoteSettings.mcpParallelToolExecution ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>
                <Text style={styles.helperText}>
                  Execute multiple tools in parallel when possible
                </Text>
              </CollapsibleSection>
            )}

            {/* 4h. MCP Servers */}
            {isDotAgentsServer && (
              <CollapsibleSection id="mcpServers" title="MCP Servers">
                <View style={styles.sectionActionRow}>
                  <TouchableOpacity
                    style={[styles.createAgentButton, styles.sectionActionButton]}
                    onPress={openMcpServerCreateEditor}
                    accessibilityRole="button"
                    accessibilityLabel={createButtonAccessibilityLabel('Add MCP server')}
                  >
                    <Text style={styles.createAgentButtonText}>Add MCP server</Text>
                  </TouchableOpacity>
                  <TouchableOpacity
                    style={[styles.profileActionButton, styles.sectionActionButton]}
                    onPress={() => setShowMcpImportModal(true)}
                    accessibilityRole="button"
                    accessibilityLabel={createButtonAccessibilityLabel('Import MCP servers')}
                    disabled={isImportingMcpServers}
                  >
                    <Text style={styles.profileActionButtonText}>
                      {isImportingMcpServers ? 'Importing...' : 'Import'}
                    </Text>
                  </TouchableOpacity>
                  <TouchableOpacity
                    style={[styles.profileActionButton, styles.sectionActionButton, isExportingMcpServers && styles.profileActionButtonDisabled]}
                    onPress={handleMcpServerExport}
                    accessibilityRole="button"
                    accessibilityLabel={createButtonAccessibilityLabel('Export MCP servers')}
                    disabled={isExportingMcpServers}
                  >
                    <Text style={styles.profileActionButtonText}>
                      {isExportingMcpServers ? 'Exporting...' : 'Export'}
                    </Text>
                  </TouchableOpacity>
                </View>

                {mcpServers.length === 0 ? (
                  <Text style={styles.helperText}>No MCP servers configured</Text>
                ) : (
                  mcpServers.map((server) => {
                    const oauthStatus = mcpOAuthStatus[server.name];
                    const showOAuthControls = !!oauthStatus?.configured;
                    return (
                    <View key={server.name} style={styles.serverRow}>
                      <Pressable
                        style={styles.serverInfo}
                        onPress={() => { void openMcpServerEditEditor(server); }}
                        accessibilityRole="button"
                        accessibilityLabel={createButtonAccessibilityLabel(`Edit ${server.name} MCP server`)}
                      >
                        <View style={styles.serverNameRow}>
                          <View style={[
                            styles.statusDot,
                            server.connected ? styles.statusConnected : styles.statusDisconnected,
                          ]} />
                          <Text style={styles.serverName}>{server.name}</Text>
                        </View>
                        <Text style={styles.serverMeta}>
                          {server.toolCount} tool{server.toolCount !== 1 ? 's' : ''}
                          {server.configDisabled ? ' • disabled in config' : ''}
                          {showOAuthControls ? ` • OAuth ${oauthStatus.authenticated ? 'connected' : 'needs auth'}` : ''}
                          {server.error && ` • ${server.error}`}
                        </Text>
                      </Pressable>
                      <View style={styles.agentActions}>
                        <TouchableOpacity
                          style={styles.notePromoteButton}
                          onPress={() => { void openMcpServerEditEditor(server); }}
                          accessibilityRole="button"
                          accessibilityLabel={createButtonAccessibilityLabel(`Edit ${server.name} MCP server`)}
                        >
                          <Text style={styles.notePromoteButtonText}>Edit</Text>
                        </TouchableOpacity>
                        <TouchableOpacity
                          style={styles.agentDeleteButton}
                          onPress={() => handleDeleteMcpServer(server)}
                          accessibilityRole="button"
                          accessibilityLabel={createButtonAccessibilityLabel(`Delete ${server.name} MCP server`)}
                        >
                          <Text style={styles.agentDeleteButtonText}>Delete</Text>
                        </TouchableOpacity>
                        {showOAuthControls && (
                          <>
                            <TouchableOpacity
                              style={styles.notePromoteButton}
                              onPress={() => { void handleMcpOAuthStart(server.name); }}
                              accessibilityRole="button"
                              accessibilityLabel={createButtonAccessibilityLabel(`Start OAuth for ${server.name}`)}
                              disabled={pendingMcpOAuthAction !== null}
                            >
                              <Text style={styles.notePromoteButtonText}>
                                {pendingMcpOAuthAction === `${server.name}:start` ? 'Starting...' : 'OAuth'}
                              </Text>
                            </TouchableOpacity>
                            {oauthStatus.authenticated && (
                              <TouchableOpacity
                                style={styles.agentDeleteButton}
                                onPress={() => { void handleMcpOAuthRevoke(server.name); }}
                                accessibilityRole="button"
                                accessibilityLabel={createButtonAccessibilityLabel(`Revoke OAuth for ${server.name}`)}
                                disabled={pendingMcpOAuthAction !== null}
                              >
                                <Text style={styles.agentDeleteButtonText}>
                                  {pendingMcpOAuthAction === `${server.name}:revoke` ? 'Revoking...' : 'Revoke'}
                                </Text>
                              </TouchableOpacity>
                            )}
                          </>
                        )}
                        <Switch
                          value={server.enabled}
                          onValueChange={(v) => handleServerToggle(server.name, v)}
                          accessibilityLabel={createMcpServerSwitchAccessibilityLabel(server.name)}
                          trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                          thumbColor={server.enabled ? theme.colors.primaryForeground : theme.colors.background}
                          disabled={server.configDisabled}
                        />
                      </View>
                    </View>
                    );
                  })
                )}
              </CollapsibleSection>
            )}

            {/* 4i. Discord */}
            {remoteSettings && (
              <CollapsibleSection id="discord" title="Discord">
                <View style={styles.row}>
                  <Text style={styles.label}>Discord Integration</Text>
                  <Switch
                    value={remoteSettings.discordEnabled ?? DEFAULT_DISCORD_ENABLED}
                    onValueChange={(v) => handleRemoteSettingToggle('discordEnabled', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={(remoteSettings.discordEnabled ?? DEFAULT_DISCORD_ENABLED) ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>

                {(remoteSettings.discordEnabled ?? DEFAULT_DISCORD_ENABLED) && (
                  <>
                    <Text style={styles.label}>Bot Token</Text>
                    <TextInput
                      style={styles.input}
                      value={inputDrafts.discordBotToken ?? ''}
                      onChangeText={(v) => handleRemoteSecretDraftChange('discordBotToken', v)}
                      onBlur={() => {
                        void commitRemoteSecretDraft('discordBotToken');
                      }}
                      placeholder={remoteSettings.discordBotToken === SECRET_MASK ? 'Configured' : 'Paste your Discord bot token'}
                      placeholderTextColor={theme.colors.mutedForeground}
                      autoCapitalize="none"
                      autoCorrect={false}
                      secureTextEntry
                    />

                    {profiles.length > 0 && (
                      <>
                        <Text style={styles.label}>Default Agent</Text>
                        <View style={styles.providerSelector}>
                          <Pressable
                            style={[
                              styles.providerOption,
                              !remoteSettings.discordDefaultProfileId && styles.providerOptionActive,
                            ]}
                            onPress={() => handleRemoteSettingUpdate('discordDefaultProfileId', '')}
                          >
                            <Text style={[
                              styles.providerOptionText,
                              !remoteSettings.discordDefaultProfileId && styles.providerOptionTextActive,
                            ]}>
                              None
                            </Text>
                          </Pressable>
                          {profiles.map((profile) => (
                            <Pressable
                              key={profile.id}
                              style={[
                                styles.providerOption,
                                remoteSettings.discordDefaultProfileId === profile.id && styles.providerOptionActive,
                              ]}
                              onPress={() => handleRemoteSettingUpdate('discordDefaultProfileId', profile.id)}
                            >
                              <Text style={[
                                styles.providerOptionText,
                                remoteSettings.discordDefaultProfileId === profile.id && styles.providerOptionTextActive,
                              ]}>
                                {profile.name}
                              </Text>
                            </Pressable>
                          ))}
                        </View>
                      </>
                    )}

                    <View style={styles.row}>
                      <Text style={styles.label}>DMs Enabled</Text>
                      <Switch
                        value={remoteSettings.discordDmEnabled ?? DEFAULT_DISCORD_DM_ENABLED}
                        onValueChange={(v) => handleRemoteSettingToggle('discordDmEnabled', v)}
                        trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                        thumbColor={(remoteSettings.discordDmEnabled ?? DEFAULT_DISCORD_DM_ENABLED) ? theme.colors.primaryForeground : theme.colors.background}
                      />
                    </View>

                    <View style={styles.row}>
                      <Text style={styles.label}>Require Mention</Text>
                      <Switch
                        value={remoteSettings.discordRequireMention ?? DEFAULT_DISCORD_REQUIRE_MENTION}
                        onValueChange={(v) => handleRemoteSettingToggle('discordRequireMention', v)}
                        trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                        thumbColor={(remoteSettings.discordRequireMention ?? DEFAULT_DISCORD_REQUIRE_MENTION) ? theme.colors.primaryForeground : theme.colors.background}
                      />
                    </View>
                    <Text style={styles.helperText}>
                      When enabled, server messages must mention the bot before the agent responds.
                    </Text>

                    {DISCORD_LIST_SETTING_SECTIONS.map((section) => (
                      <View key={section.key} style={styles.providerCredentialGroup}>
                        <Text style={styles.label}>{section.label}</Text>
                        <TextInput
                          style={[styles.input, { minHeight: 76, textAlignVertical: 'top' }]}
                          value={inputDrafts[section.key] ?? ''}
                          onChangeText={(v) => handleRemoteListSettingUpdate(section.key, v)}
                          placeholder={section.placeholder}
                          placeholderTextColor={theme.colors.mutedForeground}
                          autoCapitalize="none"
                          autoCorrect={false}
                          multiline
                          numberOfLines={3}
                        />
                        <Text style={styles.helperText}>{section.helper}</Text>
                      </View>
                    ))}

                    <View style={styles.row}>
                      <Text style={styles.label}>Log Messages</Text>
                      <Switch
                        value={remoteSettings.discordLogMessages ?? DEFAULT_DISCORD_LOG_MESSAGES}
                        onValueChange={(v) => handleRemoteSettingToggle('discordLogMessages', v)}
                        trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                        thumbColor={(remoteSettings.discordLogMessages ?? DEFAULT_DISCORD_LOG_MESSAGES) ? theme.colors.primaryForeground : theme.colors.background}
                      />
                    </View>
                    <Text style={styles.helperText}>Log Discord message content on the desktop.</Text>
                  </>
                )}
              </CollapsibleSection>
            )}

            {/* 4i. WhatsApp */}
            {remoteSettings && (
              <CollapsibleSection id="whatsapp" title="WhatsApp">
                <View style={styles.row}>
                  <Text style={styles.label}>WhatsApp Integration</Text>
                  <Switch
                    value={remoteSettings.whatsappEnabled ?? DEFAULT_WHATSAPP_ENABLED}
                    onValueChange={(v) => handleRemoteSettingToggle('whatsappEnabled', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={(remoteSettings.whatsappEnabled ?? DEFAULT_WHATSAPP_ENABLED) ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>

                {(remoteSettings.whatsappEnabled ?? DEFAULT_WHATSAPP_ENABLED) && (
                  <>
                    <Text style={styles.label}>Allowed Numbers</Text>
                    <TextInput
                      style={styles.input}
                      value={inputDrafts.whatsappAllowFrom ?? ''}
                      onChangeText={(v) => {
                        markRemotePending('whatsappAllowFrom');
                        setSaveStatusMessage(null);
                        // Update the text draft immediately for responsive UI
                        setInputDrafts(prev => ({ ...prev, whatsappAllowFrom: v }));
                        // Parse comma-separated numbers and debounce the API update
                        // Don't update remoteSettings locally to avoid the sync effect
                        // rewriting the user's raw text (losing trailing commas/spaces)
                        const numbers = v.split(',').map(n => n.trim()).filter(n => n);
                        if (inputTimeoutRefs.current.whatsappAllowFrom) {
                          clearTimeout(inputTimeoutRefs.current.whatsappAllowFrom);
                        }
                        inputTimeoutRefs.current.whatsappAllowFrom = setTimeout(async () => {
                          if (!settingsClient) return;
                          try {
                            await settingsClient.updateSettings({ whatsappAllowFrom: numbers });
                            clearRemotePending('whatsappAllowFrom');
                            delete inputTimeoutRefs.current.whatsappAllowFrom;
                          } catch (error: any) {
                            console.error('[Settings] Failed to update whatsappAllowFrom:', error);
                            setRemoteError(error.message || 'Failed to update whatsappAllowFrom');
                            fetchRemoteSettings();
                          }
                        }, 1000);
                      }}
                      placeholder="1234567890, 0987654321"
                      placeholderTextColor={theme.colors.mutedForeground}
                      autoCapitalize='none'
                      keyboardType="phone-pad"
                    />
                    <Text style={styles.helperText}>
                      Comma-separated phone numbers (international format without +)
                    </Text>

                    <View style={styles.row}>
                      <Text style={styles.label}>Auto-Reply</Text>
                      <Switch
                        value={remoteSettings.whatsappAutoReply ?? DEFAULT_WHATSAPP_AUTO_REPLY}
                        onValueChange={(v) => handleRemoteSettingToggle('whatsappAutoReply', v)}
                        trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                        thumbColor={(remoteSettings.whatsappAutoReply ?? DEFAULT_WHATSAPP_AUTO_REPLY) ? theme.colors.primaryForeground : theme.colors.background}
                      />
                    </View>

                    <View style={styles.row}>
                      <Text style={styles.label}>Log Messages</Text>
                      <Switch
                        value={remoteSettings.whatsappLogMessages ?? DEFAULT_WHATSAPP_LOG_MESSAGES}
                        onValueChange={(v) => handleRemoteSettingToggle('whatsappLogMessages', v)}
                        trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                        thumbColor={(remoteSettings.whatsappLogMessages ?? DEFAULT_WHATSAPP_LOG_MESSAGES) ? theme.colors.primaryForeground : theme.colors.background}
                      />
                    </View>
                    <Text style={styles.helperText}>
                      Log message content (privacy concern)
                    </Text>
                  </>
                )}
              </CollapsibleSection>
            )}

            {/* 4j. Langfuse */}
            {remoteSettings && (
              <CollapsibleSection id="langfuse" title="Langfuse">
                <View style={styles.row}>
                  <Text style={styles.label}>{LOCAL_TRACE_LOGGING_LABEL}</Text>
                  <Switch
                    value={remoteSettings.localTraceLoggingEnabled ?? DEFAULT_LOCAL_TRACE_LOGGING_ENABLED}
                    onValueChange={(v) => handleRemoteSettingToggle('localTraceLoggingEnabled', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={(remoteSettings.localTraceLoggingEnabled ?? DEFAULT_LOCAL_TRACE_LOGGING_ENABLED) ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>
                <Text style={styles.helperText}>{LOCAL_TRACE_LOGGING_HELPER}</Text>

                {(remoteSettings.localTraceLoggingEnabled ?? DEFAULT_LOCAL_TRACE_LOGGING_ENABLED) && (
                  <>
                    <Text style={styles.label}>{LOCAL_TRACE_LOG_PATH_LABEL}</Text>
                    <TextInput
                      style={styles.input}
                      value={inputDrafts.localTraceLogPath ?? ''}
                      onChangeText={(v) => handleRemoteSettingUpdate('localTraceLogPath', v)}
                      placeholder={LOCAL_TRACE_LOG_PATH_PLACEHOLDER}
                      placeholderTextColor={theme.colors.mutedForeground}
                      autoCapitalize='none'
                    />
                    <Text style={styles.helperText}>{LOCAL_TRACE_LOG_PATH_HELPER}</Text>
                  </>
                )}

                <View style={styles.row}>
                  <Text style={styles.label}>Enable tracing</Text>
                  <Switch
                    value={remoteSettings.langfuseEnabled ?? false}
                    onValueChange={(v) => handleRemoteSettingToggle('langfuseEnabled', v)}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={remoteSettings.langfuseEnabled ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>

                {remoteSettings.langfuseEnabled && (
                  <>
                    <Text style={styles.label}>Public Key</Text>
                    <TextInput
                      style={styles.input}
                      value={inputDrafts.langfusePublicKey ?? ''}
                      onChangeText={(v) => handleRemoteSettingUpdate('langfusePublicKey', v)}
                      placeholder="pk-..."
                      placeholderTextColor={theme.colors.mutedForeground}
                      autoCapitalize='none'
                    />

                    <Text style={styles.label}>Secret Key</Text>
                    <TextInput
                      style={styles.input}
                      value={inputDrafts.langfuseSecretKey ?? ''}
                      onChangeText={(v) => {
                        markRemotePending('langfuseSecretKey');
                        setSaveStatusMessage(null);
                        setInputDrafts(prev => ({ ...prev, langfuseSecretKey: v }));
                      }}
                      onBlur={() => {
                        const value = inputDrafts.langfuseSecretKey;
                        if (value !== undefined && value !== '' && settingsClient) {
                          settingsClient.updateSettings({ langfuseSecretKey: value }).then(() => {
                            setRemoteSettings(prev => prev ? { ...prev, langfuseSecretKey: '••••••••' } : null);
                            clearRemotePending('langfuseSecretKey');
                            setInputDrafts(prev => ({ ...prev, langfuseSecretKey: '' }));
                          }).catch((error: any) => {
                            console.error('[Settings] Failed to update langfuseSecretKey:', error);
                            setRemoteError(error.message || 'Failed to update langfuseSecretKey');
                          });
                        }
                      }}
                      placeholder="sk-..."
                      placeholderTextColor={theme.colors.mutedForeground}
                      autoCapitalize='none'
                      secureTextEntry
                    />

                    <Text style={styles.label}>Base URL</Text>
                    <TextInput
                      style={styles.input}
                      value={inputDrafts.langfuseBaseUrl ?? ''}
                      onChangeText={(v) => handleRemoteSettingUpdate('langfuseBaseUrl', v)}
                      placeholder="https://cloud.langfuse.com (default)"
                      placeholderTextColor={theme.colors.mutedForeground}
                      autoCapitalize='none'
                      keyboardType="url"
                    />
                    <Text style={styles.helperText}>
                      Leave empty for Langfuse Cloud
                    </Text>
                  </>
                )}
              </CollapsibleSection>
            )}

            {/* 4k. Skills */}
            {isDotAgentsServer && (
              <CollapsibleSection id="skills" title="Skills">
                <View style={styles.sectionActionRow}>
                  <TouchableOpacity
                    style={[styles.createAgentButton, styles.sectionActionButton]}
                    onPress={() => handleSkillEdit()}
                    accessibilityRole="button"
                    accessibilityLabel={createButtonAccessibilityLabel('Create skill')}
                  >
                    <Text style={styles.createAgentButtonText}>+ Create Skill</Text>
                  </TouchableOpacity>
                  <TouchableOpacity
                    style={[styles.profileActionButton, styles.sectionActionButton]}
                    onPress={() => setShowSkillImportModal(true)}
                    accessibilityRole="button"
                    accessibilityLabel={createButtonAccessibilityLabel('Import skill from Markdown')}
                    disabled={isImportingSkillMarkdown}
                  >
                    <Text style={styles.profileActionButtonText}>
                      {isImportingSkillMarkdown ? 'Importing...' : 'Import Markdown'}
                    </Text>
                  </TouchableOpacity>
                  <TouchableOpacity
                    style={[styles.profileActionButton, styles.sectionActionButton]}
                    onPress={() => setShowSkillGitHubImportModal(true)}
                    accessibilityRole="button"
                    accessibilityLabel={createButtonAccessibilityLabel('Import skills from GitHub')}
                    disabled={isImportingSkillGitHub}
                  >
                    <Text style={styles.profileActionButtonText}>
                      {isImportingSkillGitHub ? 'Importing...' : 'Import GitHub'}
                    </Text>
                  </TouchableOpacity>
                  {visibleSelectedSkillIds.length > 0 && (
                    <TouchableOpacity
                      style={[styles.createAgentButton, styles.sectionActionButton, styles.sectionDangerButton]}
                      onPress={handleSelectedSkillsDelete}
                      accessibilityRole="button"
                      accessibilityLabel={createButtonAccessibilityLabel(`Delete ${visibleSelectedSkillIds.length} selected skills`)}
                    >
                      <Text style={[styles.createAgentButtonText, styles.sectionDangerButtonText]}>
                        Delete selected ({visibleSelectedSkillIds.length})
                      </Text>
                    </TouchableOpacity>
                  )}
                </View>
                {isLoadingSkills ? (
                  <ActivityIndicator size="small" color={theme.colors.primary} />
                ) : skills.length === 0 ? (
                  <Text style={styles.helperText}>No skills configured</Text>
                ) : (
                  displaySkills.map((skill) => {
                    const isSelected = selectedSkillIds.has(skill.id);
                    const isExporting = isExportingSkillMarkdownId === skill.id;
                    return (
                      <View key={skill.id} style={[styles.serverRow, !skill.enabled && { opacity: 0.5 }]}>
                        <TouchableOpacity
                          style={styles.agentInfoPressable}
                          onPress={() => handleSkillEdit(skill)}
                          activeOpacity={0.7}
                          accessibilityRole="button"
                          accessibilityLabel={createButtonAccessibilityLabel(`Edit ${skill.name} skill`)}
                        >
                          <View style={styles.serverInfo}>
                            <Text style={styles.serverName}>{skill.name}</Text>
                            <Text style={styles.serverMeta} numberOfLines={2}>
                              {!skill.enabled ? '(Globally disabled) ' : ''}{skill.description || 'No description'}
                            </Text>
                          </View>
                        </TouchableOpacity>
                        <View style={styles.noteActions}>
                          <TouchableOpacity
                            style={[styles.noteSelectButton, isSelected && styles.noteSelectButtonSelected]}
                            onPress={() => toggleSkillSelection(skill.id)}
                            accessibilityRole="button"
                            accessibilityLabel={`${isSelected ? 'Deselect' : 'Select'} skill ${skill.name}`}
                            hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
                          >
                            <Text style={[
                              styles.noteSelectButtonText,
                              isSelected && styles.noteSelectButtonTextSelected,
                            ]}>
                              {isSelected ? 'Selected' : 'Select'}
                            </Text>
                          </TouchableOpacity>
                          <TouchableOpacity
                            style={[styles.notePromoteButton, isExporting && styles.profileActionButtonDisabled]}
                            onPress={() => { void handleSkillMarkdownExport(skill); }}
                            accessibilityRole="button"
                            accessibilityLabel={createButtonAccessibilityLabel(`Export ${skill.name} skill as Markdown`)}
                            disabled={isExporting}
                          >
                            <Text style={styles.notePromoteButtonText}>
                              {isExporting ? 'Exporting...' : 'Export'}
                            </Text>
                          </TouchableOpacity>
                          <Switch
                            value={skill.enabledForProfile}
                            onValueChange={() => handleSkillToggle(skill.id)}
                            disabled={!skill.enabled}
                            trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                            thumbColor={skill.enabledForProfile && skill.enabled ? theme.colors.primaryForeground : theme.colors.background}
                          />
                        </View>
                      </View>
                    );
                  })
                )}
                <Text style={styles.helperText}>
                  Tap a skill to edit, import SKILL.md files, export Markdown, or toggle it for the Main Agent.
                </Text>
              </CollapsibleSection>
            )}

            {/* 4l. Knowledge Notes */}
            {isDotAgentsServer && (
              <CollapsibleSection id="knowledgeNotes" title="Knowledge Notes">
                <TextInput
                  style={[styles.input, styles.knowledgeNoteSearchInput]}
                  value={knowledgeNoteSearchQuery}
                  onChangeText={setKnowledgeNoteSearchQuery}
                  placeholder="Search notes"
                  placeholderTextColor={theme.colors.mutedForeground}
                  autoCapitalize="none"
                  autoCorrect={false}
                  accessibilityLabel="Search knowledge notes"
                />

                {renderKnowledgeNoteFilterGroup(
                  KNOWLEDGE_NOTE_CONTEXT_FILTER_OPTIONS,
                  knowledgeNoteContextFilter,
                  (value) => setKnowledgeNoteContextFilter(value),
                  'Filter notes by'
                )}
                {renderKnowledgeNoteFilterGroup(
                  KNOWLEDGE_NOTE_DATE_FILTER_OPTIONS,
                  knowledgeNoteDateFilter,
                  (value) => setKnowledgeNoteDateFilter(value),
                  'Filter notes by'
                )}
                {renderKnowledgeNoteFilterGroup(
                  KNOWLEDGE_NOTE_SORT_OPTIONS,
                  knowledgeNoteSortOption,
                  (value) => setKnowledgeNoteSortOption(value),
                  'Sort notes by'
                )}

                {displayedKnowledgeNotes.length > 0 && (
                  <View style={styles.knowledgeSelectionBar}>
                    <TouchableOpacity
                      style={styles.noteSelectButton}
                      onPress={handleKnowledgeNoteSelectVisible}
                      accessibilityRole="button"
                      accessibilityLabel={createButtonAccessibilityLabel(
                        visibleSelectedKnowledgeNoteIds.length === displayedKnowledgeNotes.length
                          ? 'Clear visible note selection'
                          : 'Select visible notes'
                      )}
                    >
                      <Text style={styles.noteSelectButtonText}>
                        {visibleSelectedKnowledgeNoteIds.length === displayedKnowledgeNotes.length ? 'Clear visible' : 'Select visible'}
                      </Text>
                    </TouchableOpacity>
                    <Text style={styles.knowledgeSelectionText}>
                      {visibleSelectedKnowledgeNoteIds.length
                        ? `${visibleSelectedKnowledgeNoteIds.length} selected`
                        : `${displayedKnowledgeNotes.length} shown`}
                    </Text>
                  </View>
                )}

                {isSearchingKnowledgeNotes && (
                  <View style={styles.loadingRow}>
                    <ActivityIndicator size="small" color={theme.colors.primary} />
                    <Text style={styles.loadingText}>Searching notes</Text>
                  </View>
                )}

                {isLoadingKnowledgeNotes && !trimmedKnowledgeNoteSearchQuery ? (
                  <ActivityIndicator size="small" color={theme.colors.primary} />
                ) : displayedKnowledgeNotes.length === 0 ? (
                  <Text style={styles.helperText}>
                    {trimmedKnowledgeNoteSearchQuery ? 'No notes match the active search and filters' : 'No notes saved'}
                  </Text>
                ) : (
                  displayedKnowledgeNotes.map((note) => {
                    const isSelected = selectedKnowledgeNoteIds.has(note.id);
                    return (
                      <View key={note.id} style={[styles.serverRow, { alignItems: 'flex-start' }]}>
                        <TouchableOpacity
                          style={styles.agentInfoPressable}
                          onPress={() => handleKnowledgeNoteEdit(note)}
                          activeOpacity={0.7}
                        >
                          <View style={[styles.serverInfo, { flex: 1 }]}>
                            <Text style={styles.serverName}>{note.title}</Text>
                            <Text style={styles.serverMeta} numberOfLines={2}>{note.summary || note.body}</Text>
                            <View style={{ flexDirection: 'row', flexWrap: 'wrap', marginTop: 4 }}>
                              {note.tags.map((tag, idx) => (
                                <View key={idx} style={[styles.providerOption, { paddingHorizontal: 6, paddingVertical: 2, marginRight: 4, marginTop: 2 }]}>
                                  <Text style={[styles.providerOptionText, { fontSize: 10 }]}>{tag}</Text>
                                </View>
                              ))}
                              <View style={[styles.providerOption, { paddingHorizontal: 6, paddingVertical: 2, marginRight: 4, marginTop: 2 }]}>
                                <Text style={[styles.providerOptionText, { fontSize: 10 }]}>{note.context}</Text>
                              </View>
                            </View>
                          </View>
                        </TouchableOpacity>
                        <View style={styles.noteActions}>
                          <TouchableOpacity
                            style={[styles.noteSelectButton, isSelected && styles.noteSelectButtonSelected]}
                            onPress={() => toggleKnowledgeNoteSelection(note.id)}
                            accessibilityRole="button"
                            accessibilityLabel={`${isSelected ? 'Deselect' : 'Select'} note ${note.title}`}
                            hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
                          >
                            <Text style={[
                              styles.noteSelectButtonText,
                              isSelected && styles.noteSelectButtonTextSelected,
                            ]}>
                              {isSelected ? 'Selected' : 'Select'}
                            </Text>
                          </TouchableOpacity>
                          {note.context === 'search-only' && (
                            <TouchableOpacity
                              style={styles.notePromoteButton}
                              onPress={() => handleKnowledgeNotePromote(note)}
                              accessibilityLabel={`Promote note ${note.title} to auto context`}
                              hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
                            >
                              <Text style={styles.notePromoteButtonText}>Promote to auto</Text>
                            </TouchableOpacity>
                          )}
                          <TouchableOpacity
                            style={styles.noteDeleteButton}
                            onPress={() => handleKnowledgeNoteDelete(note.id)}
                            accessibilityLabel={`Delete note ${note.title}`}
                            hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
                          >
                            <Text style={styles.noteDeleteButtonText}>Delete</Text>
                          </TouchableOpacity>
                        </View>
                      </View>
                    );
                  })
                )}
                <View style={styles.sectionActionRow}>
                  <TouchableOpacity
                    style={[styles.createAgentButton, styles.sectionActionButton]}
                    onPress={() => handleKnowledgeNoteEdit()}
                    accessibilityRole="button"
                    accessibilityLabel={createButtonAccessibilityLabel('Create note')}
                  >
                    <Text style={styles.createAgentButtonText}>+ Create Note</Text>
                  </TouchableOpacity>
                  {visibleSelectedKnowledgeNoteIds.length > 0 && (
                    <TouchableOpacity
                      style={[styles.createAgentButton, styles.sectionActionButton, styles.sectionDangerButton]}
                      onPress={handleKnowledgeNoteDeleteMultiple}
                      accessibilityRole="button"
                      accessibilityLabel={createButtonAccessibilityLabel(`Delete ${visibleSelectedKnowledgeNoteIds.length} selected notes`)}
                    >
                      <Text style={[styles.createAgentButtonText, styles.sectionDangerButtonText]}>
                        Delete selected ({visibleSelectedKnowledgeNoteIds.length})
                      </Text>
                    </TouchableOpacity>
                  )}
                  {knowledgeNotes.length > 0 && (
                    <TouchableOpacity
                      style={[styles.createAgentButton, styles.sectionActionButton, styles.sectionDangerButton]}
                      onPress={handleKnowledgeNoteDeleteAll}
                      accessibilityRole="button"
                      accessibilityLabel={createButtonAccessibilityLabel('Delete all knowledge notes')}
                    >
                      <Text style={[styles.createAgentButtonText, styles.sectionDangerButtonText]}>Delete all notes</Text>
                    </TouchableOpacity>
                  )}
                </View>
                <Text style={styles.helperText}>
                  Tap a note to edit it or create a new one. Canonical note fields are title, context, summary, body, tags, and references. Use auto context sparingly for high-signal notes.
                </Text>
              </CollapsibleSection>
            )}

            {/* 4m. Agents */}
            {isDotAgentsServer && (
              <CollapsibleSection id="agents" title="Agents">
                <View style={styles.sectionActionRow}>
                  <TouchableOpacity
                    style={[styles.profileActionButton, styles.sectionActionButton, isReloadingAgentProfiles && styles.profileActionButtonDisabled]}
                    onPress={handleAgentProfilesReload}
                    disabled={isReloadingAgentProfiles}
                    accessibilityRole="button"
                    accessibilityLabel={createButtonAccessibilityLabel('Rescan desktop agent profile files')}
                  >
                    <Text style={styles.profileActionButtonText}>
                      {isReloadingAgentProfiles ? 'Rescanning...' : 'Rescan files'}
                    </Text>
                  </TouchableOpacity>
                </View>
                {isLoadingAgentProfiles ? (
                  <ActivityIndicator size="small" color={theme.colors.primary} />
                ) : sortedAgentProfiles.length === 0 ? (
                  <Text style={styles.helperText}>No agents configured</Text>
                ) : (
                  sortedAgentProfiles.map((profile) => (
                    <View
                      key={profile.id}
                      style={styles.serverRow}
                    >
                      <TouchableOpacity
                        style={styles.agentInfoPressable}
                        onPress={() => handleAgentProfileEdit(profile.id)}
                        activeOpacity={0.7}
                      >
                        <View style={styles.agentListContent}>
                          <View style={styles.agentListAvatar}>
                            {profile.avatarDataUrl ? (
                              <Image
                                source={{ uri: profile.avatarDataUrl }}
                                style={styles.agentListAvatarImage}
                                accessibilityIgnoresInvertColors
                              />
                            ) : (
                              <Text style={styles.agentListAvatarInitial}>{getAgentListInitial(profile)}</Text>
                            )}
                          </View>
                          <View style={styles.serverInfo}>
                            <View style={styles.serverNameRow}>
                              <Text style={styles.serverName}>{profile.displayName}</Text>
                              {profile.isDefault && (
                                <View style={[styles.providerOption, { paddingHorizontal: 6, paddingVertical: 2, marginLeft: 6 }]}>
                                  <Text style={[styles.providerOptionText, { fontSize: 10 }]}>Default</Text>
                                </View>
                              )}
                              {profile.isBuiltIn && (
                                <View style={[styles.providerOption, { paddingHorizontal: 6, paddingVertical: 2, marginLeft: 6 }]}>
                                  <Text style={[styles.providerOptionText, { fontSize: 10 }]}>Built-in</Text>
                                </View>
                              )}
                              {profile.systemPrompt?.trim() && (
                                <View style={styles.customPromptBadge}>
                                  <Text style={styles.customPromptBadgeText}>Custom prompt</Text>
                                </View>
                              )}
                            </View>
                            <Text style={styles.serverMeta}>
                              {profile.connectionType} • {profile.role || 'agent'}
                            </Text>
                            {profile.systemPrompt?.trim() && !profile.isBuiltIn && (
                              <Text style={styles.serverMeta} numberOfLines={2}>
                                Default system prompt updates are blocked until this custom prompt is reset.
                              </Text>
                            )}
                            {profile.description && (
                              <Text style={styles.serverMeta} numberOfLines={2}>{profile.description}</Text>
                            )}
                          </View>
                        </View>
                      </TouchableOpacity>
                      <View style={styles.agentActions}>
                        <Switch
                          value={profile.enabled}
                          onValueChange={() => handleAgentProfileToggle(profile.id)}
                          trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                          thumbColor={profile.enabled ? theme.colors.primaryForeground : theme.colors.background}
                        />
                        {!profile.isBuiltIn && (
                          <TouchableOpacity
                            style={styles.agentDeleteButton}
                            onPress={() => handleAgentProfileDelete(profile)}
                            accessibilityLabel={`Delete agent ${profile.displayName}`}
                            hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
                          >
                            <Text style={styles.agentDeleteButtonText}>Delete</Text>
                          </TouchableOpacity>
                        )}
                      </View>
                    </View>
                  ))
                )}
                <TouchableOpacity
                  style={styles.createAgentButton}
                  onPress={() => handleAgentProfileEdit()}
                >
                  <Text style={styles.createAgentButtonText}>+ Create New Agent</Text>
                </TouchableOpacity>
                <Text style={styles.helperText}>
                  Tap an agent name to edit, toggle to enable or disable
                </Text>
              </CollapsibleSection>
            )}

            {/* 4n. Agent Loops */}
            {isDotAgentsServer && (
              <CollapsibleSection id="agentLoops" title="Agent Loops">
                {isLoadingLoops ? (
                  <ActivityIndicator size="small" color={theme.colors.primary} />
                ) : loops.length === 0 ? (
                  <Text style={styles.helperText}>No agent loops configured</Text>
                ) : (
                  loops.map((loop) => (
                    <View key={loop.id} style={[styles.serverRow, { alignItems: 'flex-start' }]}>
                      <TouchableOpacity
                        style={styles.agentInfoPressable}
                        onPress={() => handleLoopEdit(loop)}
                        activeOpacity={0.7}
                      >
                        <View style={[styles.serverInfo, { flex: 1 }]}> 
                          <View style={styles.serverNameRow}>
                            <View style={[
                              styles.statusDot,
                              loop.isRunning ? styles.statusConnected : styles.statusDisconnected,
                            ]} />
                            <Text style={styles.serverName}>{loop.name}</Text>
                          </View>
                          <Text style={styles.serverMeta} numberOfLines={2}>{loop.prompt}</Text>
                          <Text style={styles.serverMeta} numberOfLines={2}>
                            {describeLoopCadence(loop)}
                            {loop.profileName && ` • ${loop.profileName}`}
                            {loop.lastRunAt && ` • Last: ${new Date(loop.lastRunAt).toLocaleTimeString()}`}
                          </Text>
                        </View>
                      </TouchableOpacity>
                      <View style={styles.loopActions}>
                        <Switch
                          value={loop.enabled}
                          onValueChange={() => handleLoopToggle(loop.id)}
                          trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                          thumbColor={loop.enabled ? theme.colors.primaryForeground : theme.colors.background}
                        />
                        <TouchableOpacity
                          style={styles.loopActionButton}
                          onPress={() => handleLoopRun(loop.id)}
                          accessibilityRole="button"
                          accessibilityLabel={createButtonAccessibilityLabel(`Run ${loop.name} loop now`)}
                          accessibilityHint="Runs this loop immediately without waiting for the next scheduled interval."
                        >
                          <Text style={styles.loopActionButtonText}>Run now</Text>
                        </TouchableOpacity>
                        <TouchableOpacity
                          style={[
                            styles.loopActionButton,
                            isExportingLoopMarkdownId === loop.id && styles.profileActionButtonDisabled,
                          ]}
                          onPress={() => { void handleLoopMarkdownExport(loop); }}
                          disabled={isExportingLoopMarkdownId === loop.id}
                          accessibilityRole="button"
                          accessibilityLabel={createButtonAccessibilityLabel(`Export ${loop.name} loop as Markdown`)}
                        >
                          <Text style={styles.loopActionButtonText}>
                            {isExportingLoopMarkdownId === loop.id ? 'Exporting...' : 'Export'}
                          </Text>
                        </TouchableOpacity>
                        <TouchableOpacity
                          style={[styles.loopActionButton, styles.loopActionButtonDanger]}
                          onPress={() => handleLoopDelete(loop)}
                          accessibilityRole="button"
                          accessibilityLabel={createButtonAccessibilityLabel(`Delete ${loop.name} loop`)}
                          accessibilityHint="Opens a confirmation prompt before permanently deleting this loop."
                        >
                          <Text style={[styles.loopActionButtonText, styles.loopActionButtonTextDanger]}>Delete</Text>
                        </TouchableOpacity>
                      </View>
                    </View>
                  ))
                )}
                <View style={styles.sectionActionRow}>
                  <TouchableOpacity
                    style={[styles.createAgentButton, styles.sectionActionButton]}
                    onPress={() => handleLoopEdit()}
                  >
                    <Text style={styles.createAgentButtonText}>+ Create New Loop</Text>
                  </TouchableOpacity>
                  <TouchableOpacity
                    style={[styles.createAgentButton, styles.sectionActionButton]}
                    onPress={() => setShowLoopImportModal(true)}
                    accessibilityRole="button"
                    accessibilityLabel={createButtonAccessibilityLabel('Import loop Markdown')}
                  >
                    <Text style={styles.createAgentButtonText}>Import Loop</Text>
                  </TouchableOpacity>
                </View>
                <Text style={styles.helperText}>
                  Tap a loop to edit, run, export, toggle, or delete from the actions
                </Text>
              </CollapsibleSection>
            )}
          </>
        )}

    </AppShellSettingsLayout>

      {/* Bundle Import Modal */}
      <Modal
        visible={showBundleImportModal}
        animationType="slide"
        transparent={true}
        onRequestClose={closeBundleImportModal}
      >
        <View style={styles.importModalOverlay}>
          <View style={styles.importModalContainer}>
            <View style={styles.importModalHeader}>
              <Text style={styles.importModalTitle}>Import Bundle</Text>
              <TouchableOpacity
                style={styles.modalCloseButton}
                onPress={closeBundleImportModal}
                accessibilityRole="button"
                accessibilityLabel="Close bundle import modal"
                disabled={isPreviewingBundleImport || isImportingBundle}
              >
                <Text style={styles.modalCloseText}>Close</Text>
              </TouchableOpacity>
            </View>

            <ScrollView
              style={styles.bundleImportBody}
              keyboardShouldPersistTaps="handled"
              showsVerticalScrollIndicator={false}
            >
              <Text style={styles.importModalDescription}>
                Paste .dotagents JSON, preview conflicts, then choose what to import.
              </Text>
              <TextInput
                style={styles.importJsonInput}
                value={bundleImportJsonText}
                onChangeText={handleBundleImportJsonChange}
                placeholder='{"manifest":{"version":1,"name":"Bundle"},"agentProfiles":[]}'
                placeholderTextColor={theme.colors.mutedForeground}
                multiline
                numberOfLines={8}
                textAlignVertical="top"
                autoCorrect={false}
                autoCapitalize="none"
                spellCheck={false}
                editable={!isPreviewingBundleImport && !isImportingBundle}
              />

              <Text style={styles.label}>Handle conflicts</Text>
              <View style={styles.providerSelector}>
                {BUNDLE_IMPORT_CONFLICT_STRATEGIES.map((strategy) => (
                  <Pressable
                    key={strategy.value}
                    style={[
                      styles.providerOption,
                      bundleImportConflictStrategy === strategy.value && styles.providerOptionActive,
                    ]}
                    onPress={() => setBundleImportConflictStrategy(strategy.value)}
                    disabled={isImportingBundle}
                  >
                    <Text style={[
                      styles.providerOptionText,
                      bundleImportConflictStrategy === strategy.value && styles.providerOptionTextActive,
                    ]}>
                      {strategy.label}
                    </Text>
                  </Pressable>
                ))}
              </View>

              <Text style={styles.label}>Components</Text>
              {BUNDLE_IMPORT_COMPONENT_OPTIONS.map((component) => {
                const count = bundleImportPreview?.bundle.manifest.components[component.key] ?? 0;
                const conflicts = bundleImportPreview?.conflicts[component.key].length ?? 0;
                return (
                  <View key={component.key} style={styles.row}>
                    <View style={styles.serverInfo}>
                      <Text style={styles.serverName}>{component.label}</Text>
                      <Text style={styles.serverMeta}>
                        {bundleImportPreview
                          ? `${count} item${count === 1 ? '' : 's'}${conflicts > 0 ? `, ${conflicts} conflict${conflicts === 1 ? '' : 's'}` : ''}`
                          : 'Not previewed'}
                      </Text>
                    </View>
                    <Switch
                      value={bundleImportComponents[component.key]}
                      onValueChange={(value) => handleBundleImportComponentToggle(component.key, value)}
                      accessibilityLabel={createSwitchAccessibilityLabel(`Import bundle ${component.label}`)}
                      trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                      thumbColor={bundleImportComponents[component.key] ? theme.colors.primaryForeground : theme.colors.background}
                    />
                  </View>
                );
              })}

              {bundleImportPreview && (
                <View style={styles.bundlePreviewCard}>
                  <Text style={styles.serverName}>{bundleImportPreview.bundle.manifest.name}</Text>
                  {bundleImportPreview.bundle.manifest.description && (
                    <Text style={styles.serverMeta} numberOfLines={2}>
                      {bundleImportPreview.bundle.manifest.description}
                    </Text>
                  )}
                  <Text style={styles.serverMeta}>
                    Exported from {bundleImportPreview.bundle.manifest.exportedFrom}
                  </Text>
                </View>
              )}
            </ScrollView>

            <View style={styles.importModalActions}>
              <TouchableOpacity
                style={styles.importModalCancelButton}
                onPress={closeBundleImportModal}
                disabled={isPreviewingBundleImport || isImportingBundle}
              >
                <Text style={styles.importModalCancelText}>Cancel</Text>
              </TouchableOpacity>
              <TouchableOpacity
                style={[
                  styles.importModalCancelButton,
                  (!bundleImportJsonText.trim() || isPreviewingBundleImport || isImportingBundle) && styles.importModalImportButtonDisabled,
                ]}
                onPress={() => { void handleBundleImportPreview(); }}
                disabled={!bundleImportJsonText.trim() || isPreviewingBundleImport || isImportingBundle}
                accessibilityRole="button"
                accessibilityLabel="Preview DotAgents bundle JSON"
              >
                <Text style={styles.importModalCancelText}>
                  {isPreviewingBundleImport ? 'Previewing...' : 'Preview'}
                </Text>
              </TouchableOpacity>
              <TouchableOpacity
                style={[
                  styles.importModalImportButton,
                  (!bundleImportPreview || isImportingBundle || isPreviewingBundleImport) && styles.importModalImportButtonDisabled,
                ]}
                onPress={() => { void handleBundleImport(); }}
                disabled={!bundleImportPreview || isImportingBundle || isPreviewingBundleImport}
                accessibilityRole="button"
                accessibilityLabel="Import DotAgents bundle JSON"
              >
                <Text style={styles.importModalImportText}>
                  {isImportingBundle ? 'Importing...' : 'Import'}
                </Text>
              </TouchableOpacity>
            </View>
          </View>
        </View>
      </Modal>

      {/* MCP Server Editor Modal */}
      <Modal
        visible={showMcpServerEditor}
        animationType="slide"
        transparent={true}
        onRequestClose={closeMcpServerEditor}
      >
        <View style={styles.importModalOverlay}>
          <View style={styles.importModalContainer}>
            <View style={styles.importModalHeader}>
              <Text style={styles.importModalTitle}>
                {mcpServerEditorMode === 'create' ? 'Add MCP server' : 'Edit MCP server'}
              </Text>
              <TouchableOpacity
                style={styles.modalCloseButton}
                onPress={closeMcpServerEditor}
                accessibilityRole="button"
                accessibilityLabel="Close MCP server editor"
                disabled={isSavingMcpServer}
              >
                <Text style={styles.modalCloseText}>Close</Text>
              </TouchableOpacity>
            </View>

            {isLoadingMcpServerConfig ? (
              <View style={styles.loadingRow}>
                <ActivityIndicator size="small" color={theme.colors.primary} />
                <Text style={styles.helperText}>Loading MCP server config...</Text>
              </View>
            ) : (
              <ScrollView keyboardShouldPersistTaps="handled" style={{ maxHeight: 520 }}>
                <Text style={styles.label}>Name</Text>
                <TextInput
                  style={styles.input}
                  value={mcpServerDraft.name}
                  onChangeText={(value) => handleMcpServerDraftChange('name', value)}
                  editable={mcpServerEditorMode === 'create' && !isSavingMcpServer}
                  autoCapitalize="none"
                  autoCorrect={false}
                  placeholder="github"
                  placeholderTextColor={theme.colors.mutedForeground}
                />

                <Text style={styles.label}>Transport</Text>
                <View style={styles.providerSelector}>
                  {MCP_TRANSPORT_OPTIONS.map((transport) => (
                    <TouchableOpacity
                      key={transport.value}
                      style={[
                        styles.providerOption,
                        mcpServerDraft.transport === transport.value && styles.providerOptionActive,
                      ]}
                      onPress={() => handleMcpServerDraftChange('transport', transport.value)}
                      accessibilityRole="button"
                      accessibilityState={{ selected: mcpServerDraft.transport === transport.value }}
                      accessibilityLabel={createButtonAccessibilityLabel(`Use ${transport.label} MCP transport`)}
                      disabled={isSavingMcpServer}
                    >
                      <Text style={[
                        styles.providerOptionText,
                        mcpServerDraft.transport === transport.value && styles.providerOptionTextActive,
                      ]}>
                        {transport.label}
                      </Text>
                    </TouchableOpacity>
                  ))}
                </View>

                {mcpServerDraft.transport === 'stdio' ? (
                  <>
                    <Text style={styles.label}>Command</Text>
                    <TextInput
                      style={styles.input}
                      value={mcpServerDraft.command}
                      onChangeText={(value) => handleMcpServerDraftChange('command', value)}
                      editable={!isSavingMcpServer}
                      autoCapitalize="none"
                      autoCorrect={false}
                      placeholder="npx"
                      placeholderTextColor={theme.colors.mutedForeground}
                    />

                    <Text style={styles.label}>Args</Text>
                    <TextInput
                      style={styles.importJsonInput}
                      value={mcpServerDraft.args}
                      onChangeText={(value) => handleMcpServerDraftChange('args', value)}
                      editable={!isSavingMcpServer}
                      multiline
                      autoCapitalize="none"
                      autoCorrect={false}
                      placeholder={'["@modelcontextprotocol/server-github"]'}
                      placeholderTextColor={theme.colors.mutedForeground}
                    />

                    <Text style={styles.label}>Environment JSON</Text>
                    <TextInput
                      style={styles.importJsonInput}
                      value={mcpServerDraft.env}
                      onChangeText={(value) => handleMcpServerDraftChange('env', value)}
                      editable={!isSavingMcpServer}
                      multiline
                      autoCapitalize="none"
                      autoCorrect={false}
                      placeholder={'{"GITHUB_TOKEN":"..."}'}
                      placeholderTextColor={theme.colors.mutedForeground}
                    />
                  </>
                ) : (
                  <>
                    <Text style={styles.label}>URL</Text>
                    <TextInput
                      style={styles.input}
                      value={mcpServerDraft.url}
                      onChangeText={(value) => handleMcpServerDraftChange('url', value)}
                      editable={!isSavingMcpServer}
                      autoCapitalize="none"
                      autoCorrect={false}
                      keyboardType="url"
                      placeholder={mcpServerDraft.transport === 'websocket' ? 'wss://example.com/mcp' : 'https://example.com/mcp'}
                      placeholderTextColor={theme.colors.mutedForeground}
                    />

                    <Text style={styles.label}>Headers JSON</Text>
                    <TextInput
                      style={styles.importJsonInput}
                      value={mcpServerDraft.headers}
                      onChangeText={(value) => handleMcpServerDraftChange('headers', value)}
                      editable={!isSavingMcpServer}
                      multiline
                      autoCapitalize="none"
                      autoCorrect={false}
                      placeholder={'{"Authorization":"Bearer ..."}'}
                      placeholderTextColor={theme.colors.mutedForeground}
                    />

                    {mcpServerDraft.transport === 'streamableHttp' && (
                      <>
                        <View style={styles.row}>
                          <View style={styles.serverInfo}>
                            <Text style={styles.label}>OAuth</Text>
                            <Text style={styles.helperText}>Use this for protected HTTP MCP servers.</Text>
                          </View>
                          <Switch
                            value={mcpServerDraft.oauthEnabled}
                            onValueChange={(value) => handleMcpServerDraftChange('oauthEnabled', value)}
                            disabled={isSavingMcpServer}
                            accessibilityLabel={createSwitchAccessibilityLabel('MCP server OAuth')}
                            trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                            thumbColor={mcpServerDraft.oauthEnabled ? theme.colors.primaryForeground : theme.colors.background}
                          />
                        </View>

                        {mcpServerDraft.oauthEnabled && (
                          <>
                            <Text style={styles.label}>OAuth Scope</Text>
                            <TextInput
                              style={styles.input}
                              value={mcpServerDraft.oauthScope}
                              onChangeText={(value) => handleMcpServerDraftChange('oauthScope', value)}
                              editable={!isSavingMcpServer}
                              autoCapitalize="none"
                              autoCorrect={false}
                              placeholder="read write"
                              placeholderTextColor={theme.colors.mutedForeground}
                            />

                            <Text style={styles.label}>OAuth Client ID</Text>
                            <TextInput
                              style={styles.input}
                              value={mcpServerDraft.oauthClientId}
                              onChangeText={(value) => handleMcpServerDraftChange('oauthClientId', value)}
                              editable={!isSavingMcpServer}
                              autoCapitalize="none"
                              autoCorrect={false}
                              placeholder="Optional static client ID"
                              placeholderTextColor={theme.colors.mutedForeground}
                            />

                            <View style={styles.row}>
                              <Text style={styles.label}>Use discovery</Text>
                              <Switch
                                value={mcpServerDraft.oauthUseDiscovery}
                                onValueChange={(value) => handleMcpServerDraftChange('oauthUseDiscovery', value)}
                                disabled={isSavingMcpServer}
                                accessibilityLabel={createSwitchAccessibilityLabel('MCP OAuth discovery')}
                                trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                                thumbColor={mcpServerDraft.oauthUseDiscovery ? theme.colors.primaryForeground : theme.colors.background}
                              />
                            </View>

                            <View style={styles.row}>
                              <Text style={styles.label}>Dynamic registration</Text>
                              <Switch
                                value={mcpServerDraft.oauthUseDynamicRegistration}
                                onValueChange={(value) => handleMcpServerDraftChange('oauthUseDynamicRegistration', value)}
                                disabled={isSavingMcpServer}
                                accessibilityLabel={createSwitchAccessibilityLabel('MCP OAuth dynamic registration')}
                                trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                                thumbColor={mcpServerDraft.oauthUseDynamicRegistration ? theme.colors.primaryForeground : theme.colors.background}
                              />
                            </View>
                          </>
                        )}
                      </>
                    )}
                  </>
                )}

                <Text style={styles.label}>Timeout ms</Text>
                <TextInput
                  style={styles.input}
                  value={mcpServerDraft.timeout}
                  onChangeText={(value) => handleMcpServerDraftChange('timeout', value.replace(/[^0-9]/g, ''))}
                  editable={!isSavingMcpServer}
                  keyboardType="number-pad"
                  placeholder="10000"
                  placeholderTextColor={theme.colors.mutedForeground}
                />

                <View style={styles.row}>
                  <View style={styles.serverInfo}>
                    <Text style={styles.label}>Disabled in config</Text>
                    <Text style={styles.helperText}>Disabled servers are saved but not started.</Text>
                  </View>
                  <Switch
                    value={mcpServerDraft.disabled}
                    onValueChange={(value) => handleMcpServerDraftChange('disabled', value)}
                    disabled={isSavingMcpServer}
                    accessibilityLabel={createSwitchAccessibilityLabel('MCP server disabled in config')}
                    trackColor={{ false: theme.colors.muted, true: theme.colors.primary }}
                    thumbColor={mcpServerDraft.disabled ? theme.colors.primaryForeground : theme.colors.background}
                  />
                </View>
              </ScrollView>
            )}

            <View style={styles.importModalActions}>
              <TouchableOpacity
                style={styles.importModalCancelButton}
                onPress={closeMcpServerEditor}
                disabled={isSavingMcpServer}
              >
                <Text style={styles.importModalCancelText}>Cancel</Text>
              </TouchableOpacity>
              <TouchableOpacity
                style={[
                  styles.importModalImportButton,
                  (isSavingMcpServer || isLoadingMcpServerConfig) && styles.importModalImportButtonDisabled,
                ]}
                onPress={() => { void handleSaveMcpServer(); }}
                disabled={isSavingMcpServer || isLoadingMcpServerConfig}
              >
                <Text style={styles.importModalImportText}>
                  {isSavingMcpServer ? 'Saving...' : mcpServerEditorMode === 'create' ? 'Add server' : 'Save server'}
                </Text>
              </TouchableOpacity>
            </View>
          </View>
        </View>
      </Modal>

      {/* MCP Server Import Modal */}
      <Modal
        visible={showMcpImportModal}
        animationType="slide"
        transparent={true}
        onRequestClose={closeMcpImportModal}
      >
        <View style={styles.importModalOverlay}>
          <View style={styles.importModalContainer}>
            <View style={styles.importModalHeader}>
              <Text style={styles.importModalTitle}>Import MCP Servers</Text>
              <TouchableOpacity
                style={styles.modalCloseButton}
                onPress={closeMcpImportModal}
                accessibilityRole="button"
                accessibilityLabel="Close MCP server import modal"
                disabled={isImportingMcpServers}
              >
                <Text style={styles.modalCloseText}>Close</Text>
              </TouchableOpacity>
            </View>

            <Text style={styles.importModalDescription}>
              Paste JSON with a top-level mcpServers object. Existing server names are replaced.
            </Text>

            <TextInput
              style={styles.importJsonInput}
              value={mcpImportJsonText}
              onChangeText={setMcpImportJsonText}
              placeholder={'{"mcpServers":{"example":{"transport":"streamableHttp","url":"https://example.com/mcp"}}}'}
              placeholderTextColor={theme.colors.mutedForeground}
              multiline
              autoCapitalize="none"
              autoCorrect={false}
              editable={!isImportingMcpServers}
            />

            <View style={styles.importModalActions}>
              <TouchableOpacity
                style={styles.importModalCancelButton}
                onPress={closeMcpImportModal}
                disabled={isImportingMcpServers}
              >
                <Text style={styles.importModalCancelText}>Cancel</Text>
              </TouchableOpacity>
              <TouchableOpacity
                style={[
                  styles.importModalImportButton,
                  (!mcpImportJsonText.trim() || isImportingMcpServers) && styles.importModalImportButtonDisabled,
                ]}
                onPress={() => { void handleMcpServerImport(); }}
                disabled={!mcpImportJsonText.trim() || isImportingMcpServers}
              >
                <Text style={styles.importModalImportText}>
                  {isImportingMcpServers ? 'Importing...' : 'Import'}
                </Text>
              </TouchableOpacity>
            </View>
          </View>
        </View>
      </Modal>

      {/* Skill Markdown Import Modal */}
      <Modal
        visible={showSkillImportModal}
        animationType="slide"
        transparent={true}
        onRequestClose={closeSkillImportModal}
      >
        <View style={styles.importModalOverlay}>
          <View style={styles.importModalContainer}>
            <View style={styles.importModalHeader}>
              <Text style={styles.importModalTitle}>Import Skill Markdown</Text>
              <TouchableOpacity
                style={styles.modalCloseButton}
                onPress={closeSkillImportModal}
                accessibilityRole="button"
                accessibilityLabel="Close skill Markdown import modal"
                disabled={isImportingSkillMarkdown}
              >
                <Text style={styles.modalCloseText}>Close</Text>
              </TouchableOpacity>
            </View>

            <Text style={styles.importModalDescription}>
              Paste a SKILL.md file with frontmatter and instructions.
            </Text>

            <TextInput
              style={styles.importJsonInput}
              value={skillImportMarkdownText}
              onChangeText={setSkillImportMarkdownText}
              placeholder={'---\nname: Example Skill\ndescription: What this skill does\n---\n\nSkill instructions...'}
              placeholderTextColor={theme.colors.mutedForeground}
              multiline
              textAlignVertical="top"
              autoCapitalize="none"
              autoCorrect={false}
              spellCheck={false}
              editable={!isImportingSkillMarkdown}
            />

            <View style={styles.importModalActions}>
              <TouchableOpacity
                style={styles.importModalCancelButton}
                onPress={closeSkillImportModal}
                disabled={isImportingSkillMarkdown}
              >
                <Text style={styles.importModalCancelText}>Cancel</Text>
              </TouchableOpacity>
              <TouchableOpacity
                style={[
                  styles.importModalImportButton,
                  (!skillImportMarkdownText.trim() || isImportingSkillMarkdown) && styles.importModalImportButtonDisabled,
                ]}
                onPress={() => { void handleSkillMarkdownImport(); }}
                disabled={!skillImportMarkdownText.trim() || isImportingSkillMarkdown}
              >
                <Text style={styles.importModalImportText}>
                  {isImportingSkillMarkdown ? 'Importing...' : 'Import'}
                </Text>
              </TouchableOpacity>
            </View>
          </View>
        </View>
      </Modal>

      {/* Skill GitHub Import Modal */}
      <Modal
        visible={showSkillGitHubImportModal}
        animationType="slide"
        transparent={true}
        onRequestClose={closeSkillGitHubImportModal}
      >
        <View style={styles.importModalOverlay}>
          <View style={styles.importModalContainer}>
            <View style={styles.importModalHeader}>
              <Text style={styles.importModalTitle}>Import Skills From GitHub</Text>
              <TouchableOpacity
                style={styles.modalCloseButton}
                onPress={closeSkillGitHubImportModal}
                accessibilityRole="button"
                accessibilityLabel="Close GitHub skill import modal"
                disabled={isImportingSkillGitHub}
              >
                <Text style={styles.modalCloseText}>Close</Text>
              </TouchableOpacity>
            </View>

            <Text style={styles.importModalDescription}>
              Enter a GitHub repository, branch, or folder URL that contains SKILL.md files.
            </Text>

            <TextInput
              style={styles.input}
              value={skillGitHubImportText}
              onChangeText={setSkillGitHubImportText}
              placeholder="owner/repo or https://github.com/owner/repo/tree/main/skills/example"
              placeholderTextColor={theme.colors.mutedForeground}
              autoCapitalize="none"
              autoCorrect={false}
              spellCheck={false}
              editable={!isImportingSkillGitHub}
            />

            <View style={styles.importModalActions}>
              <TouchableOpacity
                style={styles.importModalCancelButton}
                onPress={closeSkillGitHubImportModal}
                disabled={isImportingSkillGitHub}
              >
                <Text style={styles.importModalCancelText}>Cancel</Text>
              </TouchableOpacity>
              <TouchableOpacity
                style={[
                  styles.importModalImportButton,
                  (!skillGitHubImportText.trim() || isImportingSkillGitHub) && styles.importModalImportButtonDisabled,
                ]}
                onPress={() => { void handleSkillGitHubImport(); }}
                disabled={!skillGitHubImportText.trim() || isImportingSkillGitHub}
              >
                <Text style={styles.importModalImportText}>
                  {isImportingSkillGitHub ? 'Importing...' : 'Import'}
                </Text>
              </TouchableOpacity>
            </View>
          </View>
        </View>
      </Modal>

      {/* Loop Markdown Import Modal */}
      <Modal
        visible={showLoopImportModal}
        animationType="slide"
        transparent={true}
        onRequestClose={closeLoopImportModal}
      >
        <View style={styles.importModalOverlay}>
          <View style={styles.importModalContainer}>
            <View style={styles.importModalHeader}>
              <Text style={styles.importModalTitle}>Import Loop</Text>
              <TouchableOpacity
                style={styles.modalCloseButton}
                onPress={closeLoopImportModal}
                accessibilityRole="button"
                accessibilityLabel="Close loop import modal"
                disabled={isImportingLoopMarkdown}
              >
                <Text style={styles.modalCloseText}>Close</Text>
              </TouchableOpacity>
            </View>

            <Text style={styles.importModalDescription}>
              Paste task.md content for a repeat task.
            </Text>

            <TextInput
              style={styles.importJsonInput}
              value={loopImportMarkdownText}
              onChangeText={setLoopImportMarkdownText}
              placeholder={'---\nkind: task\nname: morning-check\nintervalMinutes: 60\nenabled: true\n---\nSummarize overnight work.'}
              placeholderTextColor={theme.colors.mutedForeground}
              multiline
              numberOfLines={8}
              textAlignVertical="top"
              autoCorrect={false}
              autoCapitalize="none"
              spellCheck={false}
              editable={!isImportingLoopMarkdown}
            />

            <View style={styles.importModalActions}>
              <TouchableOpacity
                style={styles.importModalCancelButton}
                onPress={closeLoopImportModal}
                disabled={isImportingLoopMarkdown}
              >
                <Text style={styles.importModalCancelText}>Cancel</Text>
              </TouchableOpacity>
              <TouchableOpacity
                style={[
                  styles.importModalImportButton,
                  (!loopImportMarkdownText.trim() || isImportingLoopMarkdown) && styles.importModalImportButtonDisabled,
                ]}
                onPress={() => { void handleLoopMarkdownImport(); }}
                disabled={!loopImportMarkdownText.trim() || isImportingLoopMarkdown}
                accessibilityRole="button"
                accessibilityLabel="Import loop Markdown"
              >
                <Text style={styles.importModalImportText}>
                  {isImportingLoopMarkdown ? 'Importing...' : 'Import'}
                </Text>
              </TouchableOpacity>
            </View>
          </View>
        </View>
      </Modal>

      {/* Model Picker Modal */}
      <Modal
        visible={showModelPicker}
        animationType="slide"
        transparent={true}
        onRequestClose={() => {
          setShowModelPicker(false);
          setModelSearchQuery('');
        }}
      >
        <View style={styles.modelPickerOverlay}>
          <View style={styles.modelPickerContainer}>
            <View style={styles.modelPickerHeader}>
              <Text style={styles.modelPickerTitle}>Select Model</Text>
              <TouchableOpacity
                style={styles.modalCloseButton}
                onPress={() => {
                  setShowModelPicker(false);
                  setModelSearchQuery('');
                }}
                accessibilityRole="button"
                accessibilityLabel="Close model picker"
              >
                <Text style={styles.modalCloseText}>Close</Text>
              </TouchableOpacity>
            </View>

            {/* Search Input */}
            <View style={styles.modelSearchContainer}>
              <TextInput
                style={styles.modelSearchInput}
                value={modelSearchQuery}
                onChangeText={setModelSearchQuery}
                placeholder="Search models..."
                placeholderTextColor={theme.colors.mutedForeground}
                autoCapitalize="none"
                autoCorrect={false}
              />
            </View>

            {/* Models List */}
            <ScrollView style={styles.modelList} keyboardShouldPersistTaps="handled">
              {filteredModels.length === 0 ? (
                <View style={styles.modelListEmpty}>
                  <Text style={styles.modelListEmptyText}>
                    {modelSearchQuery ? `No models match "${modelSearchQuery}"` : 'No models available'}
                  </Text>
                </View>
              ) : (
                filteredModels.map((model) => {
                  const isSelected = getCurrentModelValue() === model.id;
                  return (
                    <TouchableOpacity
                      key={model.id}
                      style={[
                        styles.modelItem,
                        isSelected && styles.modelItemActive,
                      ]}
                      onPress={() => handleModelSelect(model.id)}
                    >
                      <View style={styles.modelItemContent}>
                        <Text style={[
                          styles.modelItemName,
                          isSelected && styles.modelItemNameActive,
                        ]}>
                          {model.name}
                        </Text>
                        {model.id !== model.name && (
                          <Text style={styles.modelItemId}>{model.id}</Text>
                        )}
                      </View>
                      {isSelected && (
                        <Text style={styles.modelItemCheck}>✓</Text>
                      )}
                    </TouchableOpacity>
                  );
                })
              )}
            </ScrollView>

            {/* Footer with model count */}
            <View style={styles.modelPickerFooter}>
              <Text style={styles.modelPickerFooterText}>
                {modelSearchQuery
                  ? `${filteredModels.length} of ${availableModels.length} models`
                  : `${availableModels.length} model${availableModels.length !== 1 ? 's' : ''} available`}
              </Text>
            </View>
          </View>
        </View>
      </Modal>

      {/* Preset Picker Modal */}
      <Modal
        visible={showPresetPicker}
        animationType="slide"
        transparent={true}
        onRequestClose={() => setShowPresetPicker(false)}
      >
        <View style={styles.modelPickerOverlay}>
          <View style={styles.modelPickerContainer}>
            <View style={styles.modelPickerHeader}>
              <Text style={styles.modelPickerTitle}>Select Endpoint</Text>
              <TouchableOpacity
                style={styles.modalCloseButton}
                onPress={() => setShowPresetPicker(false)}
                accessibilityRole="button"
                accessibilityLabel="Close endpoint picker"
              >
                <Text style={styles.modalCloseText}>Close</Text>
              </TouchableOpacity>
            </View>

            <ScrollView style={styles.modelList}>
              {remoteSettings?.availablePresets?.map((preset) => {
                const isSelected = remoteSettings.currentModelPresetId === preset.id;
                return (
                  <TouchableOpacity
                    key={preset.id}
                    style={[
                      styles.modelItem,
                      isSelected && styles.modelItemActive,
                    ]}
                    onPress={() => handlePresetChange(preset.id)}
                  >
                    <View style={styles.modelItemContent}>
                      <Text style={[
                        styles.modelItemName,
                        isSelected && styles.modelItemNameActive,
                      ]}>
                        {preset.name}
                      </Text>
                      <Text style={styles.modelItemId}>{preset.baseUrl}</Text>
                    </View>
                    {isSelected && (
                      <Text style={styles.modelItemCheck}>✓</Text>
                    )}
                  </TouchableOpacity>
                );
              })}
            </ScrollView>

            <View style={styles.modelPickerFooter}>
              <Text style={styles.modelPickerFooterText}>
                {remoteSettings?.availablePresets?.length || 0} endpoint{(remoteSettings?.availablePresets?.length || 0) !== 1 ? 's' : ''} available
              </Text>
            </View>
          </View>
        </View>
      </Modal>

      {/* Preset Editor Modal */}
      <Modal
        visible={showPresetEditor}
        animationType="slide"
        transparent={true}
        onRequestClose={closePresetEditor}
      >
        <View style={styles.importModalOverlay}>
          <View style={[styles.importModalContainer, styles.presetEditorContainer]}>
            <View style={styles.importModalHeader}>
              <Text style={styles.importModalTitle}>
                {presetEditorMode === 'create'
                  ? 'Create New Preset'
                  : presetDraft.isBuiltIn ? 'Configure Preset' : 'Edit Preset'}
              </Text>
              <TouchableOpacity
                style={styles.modalCloseButton}
                onPress={closePresetEditor}
                accessibilityRole="button"
                accessibilityLabel="Close preset editor"
                disabled={isSavingPreset}
              >
                <Text style={styles.modalCloseText}>Close</Text>
              </TouchableOpacity>
            </View>

            <ScrollView style={styles.presetEditorBody} keyboardShouldPersistTaps="handled">
              <Text style={styles.label}>Preset Name</Text>
              <TextInput
                style={[styles.input, presetDraft.isBuiltIn && styles.inputDisabled]}
                value={presetDraft.name}
                onChangeText={(v) => handlePresetDraftChange('name', v)}
                placeholder="e.g., My OpenRouter"
                placeholderTextColor={theme.colors.mutedForeground}
                editable={!presetDraft.isBuiltIn && !isSavingPreset}
              />

              <Text style={styles.label}>Base URL</Text>
              <TextInput
                style={[styles.input, presetDraft.isBuiltIn && styles.inputDisabled]}
                value={presetDraft.baseUrl}
                onChangeText={(v) => handlePresetDraftChange('baseUrl', v)}
                placeholder="https://openrouter.ai/api/v1"
                placeholderTextColor={theme.colors.mutedForeground}
                autoCapitalize="none"
                autoCorrect={false}
                editable={!presetDraft.isBuiltIn && !isSavingPreset}
              />

              <Text style={styles.label}>API Key</Text>
              <TextInput
                style={styles.input}
                value={presetDraft.apiKey}
                onChangeText={(v) => handlePresetDraftChange('apiKey', v)}
                placeholder={presetDraft.hasApiKey ? 'Leave blank to keep existing key' : 'sk-...'}
                placeholderTextColor={theme.colors.mutedForeground}
                secureTextEntry
                autoCapitalize="none"
                autoCorrect={false}
                editable={!isSavingPreset}
              />

              <Text style={styles.label}>Agent Model</Text>
              <TextInput
                style={styles.input}
                value={presetDraft.agentModel}
                onChangeText={(v) => handlePresetDraftChange('agentModel', v)}
                placeholder="gpt-4.1-mini"
                placeholderTextColor={theme.colors.mutedForeground}
                autoCapitalize="none"
                autoCorrect={false}
                editable={!isSavingPreset}
              />

              <Text style={styles.label}>Transcript Model</Text>
              <TextInput
                style={styles.input}
                value={presetDraft.transcriptProcessingModel}
                onChangeText={(v) => handlePresetDraftChange('transcriptProcessingModel', v)}
                placeholder="Optional"
                placeholderTextColor={theme.colors.mutedForeground}
                autoCapitalize="none"
                autoCorrect={false}
                editable={!isSavingPreset}
              />
            </ScrollView>

            <View style={styles.importModalActions}>
              {presetEditorMode === 'edit' && !presetDraft.isBuiltIn && (
                <TouchableOpacity
                  style={[styles.dangerActionButton, styles.presetDeleteButton, isSavingPreset && styles.dangerActionButtonDisabled]}
                  onPress={handlePresetDelete}
                  disabled={isSavingPreset}
                  accessibilityRole="button"
                  accessibilityLabel={`Delete preset ${presetDraft.name}`}
                >
                  <Text style={styles.dangerActionButtonText}>Delete</Text>
                </TouchableOpacity>
              )}
              <TouchableOpacity style={styles.importModalCancelButton} onPress={closePresetEditor} disabled={isSavingPreset}>
                <Text style={styles.importModalCancelText}>Cancel</Text>
              </TouchableOpacity>
              <TouchableOpacity
                style={[styles.importModalImportButton, isSavingPreset && styles.importModalImportButtonDisabled]}
                onPress={handlePresetEditorSave}
                disabled={isSavingPreset}
                accessibilityRole="button"
                accessibilityLabel={presetEditorMode === 'create' ? 'Create preset' : 'Save preset changes'}
              >
                <Text style={styles.importModalImportText}>
                  {isSavingPreset ? 'Saving...' : 'Save Changes'}
                </Text>
              </TouchableOpacity>
            </View>
          </View>
        </View>
      </Modal>

      {/* TTS Model Picker Modal */}
      <Modal
        visible={showTtsModelPicker}
        animationType="slide"
        transparent={true}
        onRequestClose={() => setShowTtsModelPicker(false)}
      >
        <View style={styles.modelPickerOverlay}>
          <View style={styles.modelPickerContainer}>
            <View style={styles.modelPickerHeader}>
              <Text style={styles.modelPickerTitle}>Select TTS Model</Text>
              <TouchableOpacity
                style={styles.modalCloseButton}
                onPress={() => setShowTtsModelPicker(false)}
                accessibilityRole="button"
                accessibilityLabel="Close TTS model picker"
              >
                <Text style={styles.modalCloseText}>Close</Text>
              </TouchableOpacity>
            </View>

            <ScrollView style={styles.modelList}>
              {getTtsModelsForProvider(remoteSettings?.ttsProviderId || DEFAULT_TTS_PROVIDER_ID).map((model) => {
                const currentValue = remoteSettings?.ttsProviderId === 'openai' ? remoteSettings.openaiTtsModel
                  : remoteSettings?.ttsProviderId === 'groq' ? remoteSettings.groqTtsModel
                  : remoteSettings?.ttsProviderId === 'gemini' ? remoteSettings.geminiTtsModel
                  : remoteSettings?.edgeTtsModel;
                const isSelected = currentValue === model.value;
                return (
                  <TouchableOpacity
                    key={model.value}
                    style={[
                      styles.modelItem,
                      isSelected && styles.modelItemActive,
                    ]}
                    onPress={() => {
                      const key = getTtsModelSettingKey(remoteSettings?.ttsProviderId);
                      handleRemoteSettingUpdate(key, model.value);
                      setShowTtsModelPicker(false);
                    }}
                  >
                    <View style={styles.modelItemContent}>
                      <Text style={[
                        styles.modelItemName,
                        isSelected && styles.modelItemNameActive,
                      ]}>
                        {model.label}
                      </Text>
                      <Text style={styles.modelItemId}>{model.value}</Text>
                    </View>
                    {isSelected && (
                      <Text style={styles.modelItemCheck}>✓</Text>
                    )}
                  </TouchableOpacity>
                );
              })}
            </ScrollView>

            <View style={styles.modelPickerFooter}>
              <Text style={styles.modelPickerFooterText}>
                {getTtsModelsForProvider(remoteSettings?.ttsProviderId || DEFAULT_TTS_PROVIDER_ID).length} model{getTtsModelsForProvider(remoteSettings?.ttsProviderId || DEFAULT_TTS_PROVIDER_ID).length !== 1 ? 's' : ''} available
              </Text>
            </View>
          </View>
        </View>
      </Modal>

      {/* TTS Voice Picker Modal */}
      <Modal
        visible={showTtsVoicePicker}
        animationType="slide"
        transparent={true}
        onRequestClose={() => setShowTtsVoicePicker(false)}
      >
        <View style={styles.modelPickerOverlay}>
          <View style={styles.modelPickerContainer}>
            <View style={styles.modelPickerHeader}>
              <Text style={styles.modelPickerTitle}>Select TTS Voice</Text>
              <TouchableOpacity
                style={styles.modalCloseButton}
                onPress={() => setShowTtsVoicePicker(false)}
                accessibilityRole="button"
                accessibilityLabel="Close TTS voice picker"
              >
                <Text style={styles.modalCloseText}>Close</Text>
              </TouchableOpacity>
            </View>

            <ScrollView style={styles.modelList}>
              {(() => {
                const ttsModel = remoteSettings?.ttsProviderId === 'groq' ? remoteSettings.groqTtsModel : undefined;
                const voices = getTtsVoicesForProvider(remoteSettings?.ttsProviderId || DEFAULT_TTS_PROVIDER_ID, ttsModel);
                return voices.map((voice) => {
                  const currentValue = remoteSettings?.ttsProviderId === 'openai' ? remoteSettings.openaiTtsVoice
                    : remoteSettings?.ttsProviderId === 'groq' ? remoteSettings.groqTtsVoice
                    : remoteSettings?.ttsProviderId === 'gemini' ? remoteSettings.geminiTtsVoice
                    : remoteSettings?.ttsProviderId === 'edge' ? remoteSettings.edgeTtsVoice
                    : remoteSettings?.ttsProviderId === 'kitten' ? remoteSettings.kittenVoiceId
                    : remoteSettings?.supertonicVoice;
                  const isSelected = String(currentValue) === String(voice.value);
                  return (
                    <TouchableOpacity
                      key={String(voice.value)}
                      style={[
                        styles.modelItem,
                        isSelected && styles.modelItemActive,
                      ]}
                      onPress={() => {
                        const key = getTtsVoiceSettingKey(remoteSettings?.ttsProviderId);
                        handleRemoteSettingUpdate(key, voice.value);
                        setShowTtsVoicePicker(false);
                      }}
                    >
                      <View style={styles.modelItemContent}>
                        <Text style={[
                          styles.modelItemName,
                          isSelected && styles.modelItemNameActive,
                        ]}>
                          {voice.label}
                        </Text>
                      </View>
                      {isSelected && (
                        <Text style={styles.modelItemCheck}>✓</Text>
                      )}
                    </TouchableOpacity>
                  );
                });
              })()}
            </ScrollView>

            <View style={styles.modelPickerFooter}>
              <Text style={styles.modelPickerFooterText}>
                {(() => {
                  const ttsModel = remoteSettings?.ttsProviderId === 'groq' ? remoteSettings?.groqTtsModel : undefined;
                  const count = getTtsVoicesForProvider(remoteSettings?.ttsProviderId || DEFAULT_TTS_PROVIDER_ID, ttsModel).length;
                  return `${count} voice${count !== 1 ? 's' : ''} available`;
                })()}
              </Text>
            </View>
          </View>
        </View>
      </Modal>

      {/* Profile Import Modal */}
      <Modal
        visible={showImportModal}
        animationType="slide"
        transparent={true}
        onRequestClose={() => {
          setShowImportModal(false);
          setImportJsonText('');
        }}
      >
        <View style={styles.importModalOverlay}>
          <View style={styles.importModalContainer}>
            <View style={styles.importModalHeader}>
              <Text style={styles.importModalTitle}>Import Profile</Text>
              <TouchableOpacity
                style={styles.modalCloseButton}
                onPress={() => {
                  setShowImportModal(false);
                  setImportJsonText('');
                }}
                accessibilityRole="button"
                accessibilityLabel="Close import profile modal"
              >
                <Text style={styles.modalCloseText}>Close</Text>
              </TouchableOpacity>
            </View>

            <Text style={styles.importModalDescription}>
              Paste the profile JSON below to import it.
            </Text>

            <TextInput
              style={styles.importJsonInput}
              value={importJsonText}
              onChangeText={setImportJsonText}
              placeholder='{"name": "My Profile", ...}'
              placeholderTextColor={theme.colors.mutedForeground}
              multiline
              numberOfLines={8}
              textAlignVertical="top"
              autoCorrect={false}
              autoCapitalize="none"
              spellCheck={false}
            />

            <View style={styles.importModalActions}>
              <TouchableOpacity
                style={styles.importModalCancelButton}
                onPress={() => {
                  setShowImportModal(false);
                  setImportJsonText('');
                }}
              >
                <Text style={styles.importModalCancelText}>Cancel</Text>
              </TouchableOpacity>
              <TouchableOpacity
                style={[
                  styles.importModalImportButton,
                  (!importJsonText.trim() || isImportingProfile) && styles.importModalImportButtonDisabled,
                ]}
                onPress={handleImportProfile}
                disabled={!importJsonText.trim() || isImportingProfile}
              >
                <Text style={styles.importModalImportText}>
                  {isImportingProfile ? 'Importing...' : 'Import'}
                </Text>
              </TouchableOpacity>
            </View>
          </View>
        </View>
      </Modal>
    </>
  );
}

function createStyles(theme: ReturnType<typeof useTheme>['theme']) {
  return StyleSheet.create({
    container: {
      padding: spacing.lg,
      gap: spacing.md,
    },
    // Connection card styles
    connectionCard: {
      backgroundColor: theme.colors.card,
      borderRadius: radius.lg,
      padding: spacing.md,
      borderWidth: 1,
      borderColor: theme.colors.border,
    },
    connectionCardContent: {
      flexDirection: 'row',
      alignItems: 'center',
      justifyContent: 'space-between',
      gap: spacing.sm,
    },
    connectionCardLeft: {
      flex: 1,
      minWidth: 0,
    },
    connectionStatusRow: {
      flexDirection: 'row',
      alignItems: 'center',
      flexWrap: 'wrap',
      gap: spacing.sm,
    },
    connectionCardTitle: {
      fontSize: 16,
      fontWeight: '600',
      color: theme.colors.foreground,
      flexShrink: 1,
    },
    connectionCardUrl: {
      fontSize: 12,
      color: theme.colors.mutedForeground,
      marginTop: spacing.xs,
      lineHeight: 16,
    },
    connectionCardChevron: {
      fontSize: 24,
      color: theme.colors.mutedForeground,
      marginLeft: spacing.sm,
      flexShrink: 0,
    },
    sectionTitle: {
      ...theme.typography.label,
      marginTop: spacing.lg,
      marginBottom: spacing.xs,
      textTransform: 'uppercase',
      fontSize: 12,
      letterSpacing: 0.5,
      color: theme.colors.mutedForeground,
    },
    label: {
      ...theme.typography.label,
      marginTop: spacing.sm,
      flexShrink: 1,
    },
    helperText: {
      fontSize: 12,
      color: theme.colors.mutedForeground,
      marginTop: -spacing.xs,
    },
    input: {
      ...theme.input,
    },
    inputDisabled: {
      opacity: 0.6,
    },
    row: {
      flexDirection: 'row',
      alignItems: 'flex-start',
      justifyContent: 'space-between',
      gap: spacing.md,
      marginTop: spacing.sm,
    },
    sectionLeadRow: {
      marginTop: spacing.lg,
    },
    themeSelector: {
      flexDirection: 'row',
      flexWrap: 'wrap',
      gap: spacing.sm,
      marginBottom: spacing.sm,
    },
    themeOption: {
      flexBasis: 76,
      flexGrow: 1,
      paddingVertical: spacing.sm,
      paddingHorizontal: spacing.sm,
      borderRadius: radius.lg,
      borderWidth: 1,
      borderColor: theme.colors.border,
      backgroundColor: theme.colors.background,
      alignItems: 'center',
      justifyContent: 'center',
      minWidth: 76,
      minHeight: 44,
    },
    themeOptionActive: {
      borderColor: theme.colors.primary,
      backgroundColor: theme.colors.primary,
    },
    themeOptionText: {
      fontSize: 13,
      lineHeight: 16,
      color: theme.colors.foreground,
      textAlign: 'center',
    },
    themeOptionTextActive: {
      color: theme.colors.primaryForeground,
      fontWeight: '600',
    },
    providerSelector: {
      flexDirection: 'row',
      flexWrap: 'wrap',
      gap: spacing.sm,
      marginTop: spacing.xs,
    },
    providerOption: {
      minWidth: 70,
      paddingVertical: spacing.sm,
      paddingHorizontal: spacing.md,
      borderRadius: radius.lg,
      borderWidth: 1,
      borderColor: theme.colors.border,
      backgroundColor: theme.colors.background,
      alignItems: 'center',
    },
    providerOptionActive: {
      borderColor: theme.colors.primary,
      backgroundColor: theme.colors.primary,
    },
    providerOptionText: {
      fontSize: 14,
      color: theme.colors.foreground,
    },
    providerOptionTextActive: {
      color: theme.colors.primaryForeground,
      fontWeight: '600',
    },
    customPromptBadge: {
      paddingHorizontal: 6,
      paddingVertical: 2,
      marginLeft: 6,
      borderRadius: radius.lg,
      borderWidth: 1,
      borderColor: '#f59e0b',
      backgroundColor: '#f59e0b20',
    },
    customPromptBadgeText: {
      color: '#92400e',
      fontSize: 10,
      fontWeight: '600',
    },
    primaryButton: {
      backgroundColor: theme.colors.primary,
      padding: spacing.md,
      borderRadius: radius.lg,
      alignItems: 'center',
      marginTop: spacing.lg,
    },
    primaryButtonDisabled: {
      opacity: 0.7,
    },
    primaryButtonText: {
      color: theme.colors.primaryForeground,
      fontSize: 16,
      fontWeight: '600',
    },
    dangerActionButton: {
      paddingVertical: spacing.sm,
      paddingHorizontal: spacing.md,
      borderRadius: radius.md,
      borderWidth: 1,
      borderColor: theme.colors.destructive,
      backgroundColor: theme.colors.destructive + '10',
    },
    dangerActionButtonDisabled: {
      opacity: 0.5,
    },
    dangerActionButtonText: {
      fontSize: 13,
      fontWeight: '600',
      color: theme.colors.destructive,
      textAlign: 'center',
    },
    dangerActionButtonTextDisabled: {
      color: theme.colors.mutedForeground,
    },
    chatGptWebAuthBlock: {
      gap: spacing.xs,
      marginTop: spacing.sm,
      paddingTop: spacing.sm,
      borderTopWidth: 1,
      borderTopColor: theme.colors.border,
    },
    saveBar: {
      borderTopWidth: 1,
      borderTopColor: theme.colors.border,
      backgroundColor: theme.colors.card,
      paddingHorizontal: spacing.md,
      paddingTop: spacing.sm,
      shadowColor: '#000',
      shadowOpacity: 0.08,
      shadowRadius: 10,
      shadowOffset: { width: 0, height: -2 },
      elevation: 8,
    },
    saveBarButton: {
      marginTop: 0,
    },
    saveBarHint: {
      marginTop: spacing.xs,
      color: theme.colors.mutedForeground,
      fontSize: 12,
      textAlign: 'center',
    },
    // Remote settings styles
    subsectionTitle: {
      ...theme.typography.label,
      marginTop: spacing.md,
      marginBottom: spacing.xs,
      fontSize: 14,
      fontWeight: '600',
      color: theme.colors.foreground,
    },
    providerCredentialGroup: {
      marginTop: spacing.md,
      gap: spacing.sm,
    },
    loadingRow: {
      flexDirection: 'row',
      alignItems: 'center',
      gap: spacing.sm,
      paddingVertical: spacing.sm,
    },
    loadingText: {
      color: theme.colors.mutedForeground,
      fontSize: 14,
    },
    warningContainer: {
      backgroundColor: '#f59e0b20', // amber-500 with opacity
      borderWidth: 1,
      borderColor: '#f59e0b', // amber-500
      borderRadius: radius.md,
      padding: spacing.md,
      width: '100%' as const,
      gap: spacing.md,
      alignItems: 'stretch',
    },
    warningContent: {
      gap: spacing.xs,
    },
    warningTitle: {
      fontSize: 15,
      fontWeight: '700',
      color: '#92400e', // amber-800
    },
    warningText: {
      color: '#d97706', // amber-600
      fontSize: 14,
      lineHeight: 20,
      alignSelf: 'stretch',
    },
    warningDetailText: {
      color: '#92400e', // amber-800
      fontSize: 14,
      lineHeight: 20,
    },
    warningRetryButton: {
      ...createMinimumTouchTargetStyle({
        minSize: 44,
        horizontalPadding: spacing.md,
        verticalPadding: spacing.sm,
        horizontalMargin: 0,
      }),
      width: '100%' as const,
      borderRadius: radius.md,
      borderWidth: 1,
      borderColor: '#f59e0b',
      backgroundColor: theme.colors.background,
    },
    warningRetryButtonText: {
      color: theme.colors.primary,
      textAlign: 'center',
      fontWeight: '600',
      fontSize: 14,
    },
    profileList: {
      gap: spacing.xs,
    },
    profileItem: {
      flexDirection: 'row',
      alignItems: 'flex-start',
      justifyContent: 'flex-start',
      gap: spacing.sm,
      paddingVertical: spacing.sm,
      paddingHorizontal: spacing.md,
      borderRadius: radius.md,
      borderWidth: 1,
      borderColor: theme.colors.border,
      backgroundColor: theme.colors.background,
    },
    profileItemActive: {
      borderColor: theme.colors.primary,
      backgroundColor: theme.colors.primary + '10',
    },
    profileName: {
      fontSize: 14,
      color: theme.colors.foreground,
      flex: 1,
      flexShrink: 1,
      lineHeight: 18,
    },
    profileNameActive: {
      fontWeight: '600',
      color: theme.colors.primary,
    },
    checkmark: {
      color: theme.colors.primary,
      fontSize: 16,
      fontWeight: '600',
      flexShrink: 0,
      marginTop: 1,
    },
    profileActions: {
      flexDirection: 'row',
      flexWrap: 'wrap',
      gap: spacing.sm,
      marginTop: spacing.sm,
    },
    profileActionButton: {
      flexBasis: 132,
      flexGrow: 1,
      paddingVertical: spacing.sm,
      paddingHorizontal: spacing.md,
      borderRadius: radius.md,
      borderWidth: 1,
      borderColor: theme.colors.border,
      backgroundColor: theme.colors.background,
      alignItems: 'center',
      justifyContent: 'center',
      minHeight: 44,
      minWidth: 132,
    },
    profileActionButtonDisabled: {
      opacity: 0.5,
    },
    profileActionButtonText: {
      fontSize: 14,
      color: theme.colors.foreground,
      fontWeight: '500',
      textAlign: 'center',
    },
    importModalOverlay: {
      flex: 1,
      backgroundColor: 'rgba(0, 0, 0, 0.5)',
      justifyContent: 'center',
      alignItems: 'center',
      padding: spacing.lg,
    },
    importModalContainer: {
      backgroundColor: theme.colors.background,
      borderRadius: radius.lg,
      padding: spacing.lg,
      width: '100%',
      maxWidth: 400,
    },
    presetEditorContainer: {
      maxHeight: '82%',
    },
    presetEditorBody: {
      maxHeight: 460,
    },
    importModalHeader: {
      flexDirection: 'row',
      justifyContent: 'space-between',
      alignItems: 'center',
      gap: spacing.sm,
      marginBottom: spacing.md,
    },
    importModalTitle: {
      flex: 1,
      flexShrink: 1,
      fontSize: 18,
      fontWeight: '600',
      color: theme.colors.foreground,
      paddingRight: spacing.xs,
    },
    importModalDescription: {
      fontSize: 14,
      color: theme.colors.mutedForeground,
      marginBottom: spacing.md,
    },
    importJsonInput: {
      borderWidth: 1,
      borderColor: theme.colors.border,
      borderRadius: radius.md,
      padding: spacing.md,
      fontSize: 14,
      color: theme.colors.foreground,
      backgroundColor: theme.colors.muted,
      minHeight: 150,
      fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
    },
    bundleImportBody: {
      maxHeight: 520,
    },
    bundlePreviewCard: {
      marginTop: spacing.md,
      padding: spacing.md,
      borderRadius: radius.md,
      borderWidth: 1,
      borderColor: theme.colors.border,
      backgroundColor: theme.colors.muted,
    },
    importModalActions: {
      flexDirection: 'row',
      flexWrap: 'wrap',
      gap: spacing.sm,
      marginTop: spacing.md,
    },
    importModalCancelButton: {
      flex: 1,
      paddingVertical: spacing.sm,
      paddingHorizontal: spacing.md,
      borderRadius: radius.md,
      borderWidth: 1,
      borderColor: theme.colors.border,
      alignItems: 'center',
    },
    importModalCancelText: {
      fontSize: 14,
      color: theme.colors.foreground,
      fontWeight: '500',
    },
    importModalImportButton: {
      flex: 1,
      paddingVertical: spacing.sm,
      paddingHorizontal: spacing.md,
      borderRadius: radius.md,
      backgroundColor: theme.colors.primary,
      alignItems: 'center',
    },
    importModalImportButtonDisabled: {
      opacity: 0.5,
    },
    importModalImportText: {
      fontSize: 14,
      color: theme.colors.primaryForeground,
      fontWeight: '600',
    },
    presetDeleteButton: {
      flexGrow: 1,
      alignItems: 'center',
      justifyContent: 'center',
      minHeight: 40,
    },
    serverRow: {
      flexDirection: 'row',
      alignItems: 'flex-start',
      justifyContent: 'flex-start',
      flexWrap: 'wrap',
      gap: spacing.sm,
      paddingVertical: spacing.sm,
      borderBottomWidth: 1,
      borderBottomColor: theme.colors.border,
    },
    serverInfo: {
      flex: 1,
      minWidth: 0,
    },
    serverNameRow: {
      flexDirection: 'row',
      alignItems: 'center',
      flexWrap: 'wrap',
      gap: spacing.xs,
    },
    serverName: {
      fontSize: 14,
      fontWeight: '500',
      color: theme.colors.foreground,
      flexShrink: 1,
      lineHeight: 18,
    },
    serverMeta: {
      fontSize: 12,
      color: theme.colors.mutedForeground,
      marginTop: 2,
      lineHeight: 16,
    },
    statusDot: {
      width: 8,
      height: 8,
      borderRadius: 4,
      flexShrink: 0,
      marginTop: 4,
    },
    statusConnected: {
      backgroundColor: '#22c55e', // green-500
    },
    statusDisconnected: {
      backgroundColor: theme.colors.muted,
    },
    // Agent management styles
    agentInfoPressable: {
      flex: 1,
      minWidth: 0,
    },
    agentListContent: {
      flexDirection: 'row',
      alignItems: 'flex-start',
      gap: spacing.sm,
      flex: 1,
      minWidth: 0,
    },
    agentListAvatar: {
      width: 36,
      height: 36,
      borderRadius: radius.sm,
      borderWidth: 1,
      borderColor: theme.colors.border,
      backgroundColor: theme.colors.muted,
      alignItems: 'center',
      justifyContent: 'center',
      overflow: 'hidden',
      flexShrink: 0,
    },
    agentListAvatarImage: {
      width: '100%',
      height: '100%',
    },
    agentListAvatarInitial: {
      color: theme.colors.foreground,
      fontSize: 14,
      fontWeight: '700',
    },
    agentActions: {
      flexDirection: 'row',
      alignItems: 'center',
      gap: spacing.sm,
      flexShrink: 0,
      alignSelf: 'flex-start',
    },
    agentDeleteButton: {
      ...createMinimumTouchTargetStyle({
        minSize: 44,
        horizontalPadding: spacing.sm,
        verticalPadding: spacing.xs,
        horizontalMargin: 0,
      }),
    },
    agentDeleteButtonText: {
      color: theme.colors.destructive,
      fontSize: 12,
      fontWeight: '500',
    },
    noteDeleteButton: {
      ...createMinimumTouchTargetStyle({
        minSize: 44,
        horizontalPadding: spacing.sm,
        verticalPadding: spacing.xs,
        horizontalMargin: 0,
      }),
      alignSelf: 'flex-start',
    },
    noteActions: {
      flexDirection: 'row',
      flexWrap: 'wrap',
      alignItems: 'center',
      gap: spacing.sm,
      flexShrink: 0,
      alignSelf: 'flex-start',
    },
    knowledgeNoteSearchInput: {
      marginBottom: spacing.sm,
    },
    knowledgeSelectionBar: {
      flexDirection: 'row',
      flexWrap: 'wrap',
      alignItems: 'center',
      gap: spacing.sm,
      marginTop: spacing.sm,
      paddingVertical: spacing.xs,
      paddingHorizontal: spacing.sm,
      borderRadius: radius.md,
      backgroundColor: theme.colors.muted,
    },
    knowledgeSelectionText: {
      fontSize: 12,
      color: theme.colors.mutedForeground,
      fontWeight: '500',
    },
    noteSelectButton: {
      ...createMinimumTouchTargetStyle({
        minSize: 44,
        horizontalPadding: spacing.sm,
        verticalPadding: spacing.xs,
        horizontalMargin: 0,
      }),
      alignSelf: 'flex-start',
      borderRadius: radius.md,
      borderWidth: 1,
      borderColor: theme.colors.border,
      backgroundColor: theme.colors.background,
    },
    noteSelectButtonSelected: {
      borderColor: theme.colors.primary,
      backgroundColor: theme.colors.primary,
    },
    noteSelectButtonText: {
      color: theme.colors.foreground,
      fontSize: 12,
      fontWeight: '500',
      textAlign: 'center',
    },
    noteSelectButtonTextSelected: {
      color: theme.colors.primaryForeground,
      fontWeight: '600',
    },
    notePromoteButton: {
      ...createMinimumTouchTargetStyle({
        minSize: 44,
        horizontalPadding: spacing.sm,
        verticalPadding: spacing.xs,
        horizontalMargin: 0,
      }),
      alignSelf: 'flex-start',
    },
    notePromoteButtonText: {
      color: theme.colors.primary,
      fontSize: 12,
      fontWeight: '500',
    },
    noteDeleteButtonText: {
      color: theme.colors.destructive,
      fontSize: 12,
      fontWeight: '500',
    },
    loopActions: {
      width: '100%' as const,
      flexDirection: 'row',
      flexWrap: 'wrap',
      alignItems: 'center',
      gap: spacing.sm,
      paddingTop: spacing.xs,
    },
    loopActionButton: {
      ...createMinimumTouchTargetStyle({
        minSize: 44,
        horizontalPadding: spacing.md,
        verticalPadding: spacing.sm,
        horizontalMargin: 0,
      }),
      minWidth: 92,
      borderRadius: radius.md,
      borderWidth: 1,
      borderColor: theme.colors.border,
      backgroundColor: theme.colors.background,
    },
    loopActionButtonDanger: {
      borderColor: theme.colors.destructive,
      backgroundColor: theme.colors.destructive + '10',
    },
    loopActionButtonText: {
      fontSize: 13,
      fontWeight: '600',
      color: theme.colors.primary,
      textAlign: 'center',
    },
    loopActionButtonTextDanger: {
      color: theme.colors.destructive,
    },
    createAgentButton: {
      marginTop: spacing.md,
      paddingVertical: spacing.sm,
      paddingHorizontal: spacing.md,
      borderRadius: radius.md,
      borderWidth: 1,
      borderColor: theme.colors.primary,
      borderStyle: 'dashed',
      alignItems: 'center',
    },
    createAgentButtonText: {
      fontSize: 14,
      color: theme.colors.primary,
      fontWeight: '500',
    },
    sectionActionRow: {
      flexDirection: 'row',
      flexWrap: 'wrap',
      gap: spacing.sm,
      marginTop: spacing.md,
    },
    sectionActionButton: {
      flexBasis: 150,
      flexGrow: 1,
      marginTop: 0,
    },
    sectionDangerButton: {
      borderColor: theme.colors.destructive,
      backgroundColor: theme.colors.destructive + '10',
    },
    sectionDangerButtonText: {
      color: theme.colors.destructive,
    },
    // Model picker styles
    modelLabelRow: {
      flexDirection: 'row',
      justifyContent: 'space-between',
      alignItems: 'center',
      marginTop: spacing.sm,
    },
    modelActions: {
      flexDirection: 'row',
      gap: spacing.xs,
    },
    modelActionButton: {
      paddingHorizontal: spacing.sm,
      paddingVertical: spacing.xs,
    },
    modelActionButtonDisabled: {
      opacity: 0.5,
    },
    modelActionText: {
      fontSize: 12,
      color: theme.colors.primary,
    },
    endpointPanel: {
      marginTop: spacing.sm,
      gap: spacing.sm,
    },
    endpointMetaRow: {
      flexDirection: 'row',
      alignItems: 'center',
      gap: spacing.sm,
    },
    endpointBaseUrl: {
      flex: 1,
      minWidth: 0,
      color: theme.colors.mutedForeground,
      fontSize: 12,
      lineHeight: 16,
    },
    endpointKeyBadge: {
      flexShrink: 0,
      borderRadius: radius.sm,
      borderWidth: 1,
      borderColor: theme.colors.border,
      paddingHorizontal: spacing.sm,
      paddingVertical: 3,
      color: theme.colors.mutedForeground,
      fontSize: 11,
      fontWeight: '600',
    },
    endpointKeyBadgeActive: {
      borderColor: theme.colors.primary,
      color: theme.colors.primary,
      backgroundColor: theme.colors.primary + '10',
    },
    modelSelector: {
      borderWidth: 1,
      borderColor: theme.colors.border,
      borderRadius: radius.md,
      backgroundColor: theme.colors.background,
      padding: spacing.md,
    },
    modelSelectorContent: {
      flexDirection: 'row',
      justifyContent: 'space-between',
      alignItems: 'center',
      gap: spacing.sm,
    },
    modelSelectorText: {
      flex: 1,
      fontSize: 14,
      color: theme.colors.foreground,
    },
    modelSelectorPlaceholder: {
      color: theme.colors.mutedForeground,
    },
    modelSelectorChevron: {
      fontSize: 10,
      color: theme.colors.mutedForeground,
    },
    // Model Picker Modal styles
    modelPickerOverlay: {
      flex: 1,
      backgroundColor: 'rgba(0,0,0,0.5)',
      justifyContent: 'flex-end',
    },
    modelPickerContainer: {
      backgroundColor: theme.colors.background,
      borderTopLeftRadius: radius.xl,
      borderTopRightRadius: radius.xl,
      maxHeight: '80%',
    },
    modelPickerHeader: {
      flexDirection: 'row',
      justifyContent: 'space-between',
      alignItems: 'center',
      gap: spacing.sm,
      paddingHorizontal: spacing.lg,
      paddingVertical: spacing.md,
      borderBottomWidth: 1,
      borderBottomColor: theme.colors.border,
    },
    modelPickerTitle: {
      flex: 1,
      flexShrink: 1,
      fontSize: 18,
      fontWeight: '600',
      color: theme.colors.foreground,
      paddingRight: spacing.xs,
    },
    modalCloseButton: {
      borderRadius: radius.md,
      paddingHorizontal: spacing.sm,
      paddingVertical: spacing.xs,
    },
    modalCloseText: {
      fontSize: 14,
      fontWeight: '500',
      color: theme.colors.primary,
    },
    modelSearchContainer: {
      padding: spacing.md,
      borderBottomWidth: 1,
      borderBottomColor: theme.colors.border,
    },
    modelSearchInput: {
      ...theme.input,
      marginTop: 0,
    },
    modelList: {
      maxHeight: 400,
    },
    modelListEmpty: {
      padding: spacing.xl,
      alignItems: 'center',
    },
    modelListEmptyText: {
      color: theme.colors.mutedForeground,
      fontSize: 14,
    },
    modelItem: {
      flexDirection: 'row',
      alignItems: 'center',
      justifyContent: 'space-between',
      padding: spacing.md,
      borderBottomWidth: 1,
      borderBottomColor: theme.colors.border,
    },
    modelItemActive: {
      backgroundColor: theme.colors.primary + '10',
    },
    modelItemContent: {
      flex: 1,
    },
    modelItemName: {
      fontSize: 14,
      color: theme.colors.foreground,
    },
    modelItemNameActive: {
      fontWeight: '600',
      color: theme.colors.primary,
    },
    modelItemId: {
      fontSize: 12,
      color: theme.colors.mutedForeground,
      marginTop: 2,
    },
    modelItemCheck: {
      fontSize: 16,
      fontWeight: '600',
      color: theme.colors.primary,
      marginLeft: spacing.sm,
    },
    modelPickerFooter: {
      padding: spacing.md,
      borderTopWidth: 1,
      borderTopColor: theme.colors.border,
      alignItems: 'center',
    },
    modelPickerFooterText: {
      fontSize: 12,
      color: theme.colors.mutedForeground,
    },
    // Collapsible section styles
    collapsibleSection: {
      marginTop: spacing.sm,
      borderWidth: 1,
      borderColor: theme.colors.border,
      borderRadius: radius.md,
      backgroundColor: theme.colors.card,
      overflow: 'hidden',
    },
    desktopSettingsActivePanel: {
      marginTop: 0,
      alignSelf: 'stretch',
    },
    collapsibleHeader: {
      flexDirection: 'row',
      alignItems: 'center',
      justifyContent: 'space-between',
      padding: spacing.md,
      backgroundColor: theme.colors.muted,
    },
    desktopSettingsActiveHeader: {
      backgroundColor: theme.colors.card,
      borderBottomWidth: 1,
      borderBottomColor: theme.colors.border,
    },
    collapsibleTitle: {
      fontSize: 14,
      fontWeight: '600',
      color: theme.colors.foreground,
    },
    collapsibleChevron: {
      fontSize: 12,
      color: theme.colors.mutedForeground,
    },
    collapsibleContent: {
      padding: spacing.md,
      paddingTop: spacing.sm,
    },
  });
}
