/**
 * SettingsScreen - Einstellungen und Verbindungsverwaltung
 *
 * QR-Scanner fuer Pairing, Moduswahl, GPS-Toggle, Log-Viewer.
 */

import React, { useState, useEffect, useCallback, useRef } from 'react';
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  ScrollView,
  Switch,
  StyleSheet,
  Alert,
  Platform,
  ToastAndroid,
  ActivityIndicator,
  Modal,
  PermissionsAndroid,
  useWindowDimensions,
  DeviceEventEmitter,
  NativeModules,
} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import RNFS from 'react-native-fs';

const { FileOpener } = NativeModules as {
  FileOpener?: { open: (filePath: string, mimeType: string) => Promise<boolean> };
};

// MIME-Type aus Dateinamen schaetzen — fuer den FileOpener-Intent. Android
// nutzt den MIME-Type um die passende App zu finden. Unknown → octet-stream.
function guessMimeFromName(name: string): string {
  const lower = name.toLowerCase();
  if (lower.endsWith('.pdf')) return 'application/pdf';
  if (lower.endsWith('.jpg') || lower.endsWith('.jpeg')) return 'image/jpeg';
  if (lower.endsWith('.png')) return 'image/png';
  if (lower.endsWith('.gif')) return 'image/gif';
  if (lower.endsWith('.webp')) return 'image/webp';
  if (lower.endsWith('.mp3')) return 'audio/mpeg';
  if (lower.endsWith('.wav')) return 'audio/wav';
  if (lower.endsWith('.ogg') || lower.endsWith('.opus')) return 'audio/ogg';
  if (lower.endsWith('.mp4') || lower.endsWith('.m4a')) return 'audio/mp4';
  if (lower.endsWith('.webm')) return 'video/webm';
  if (lower.endsWith('.txt')) return 'text/plain';
  if (lower.endsWith('.md')) return 'text/markdown';
  if (lower.endsWith('.json')) return 'application/json';
  if (lower.endsWith('.csv')) return 'text/csv';
  if (lower.endsWith('.html') || lower.endsWith('.htm')) return 'text/html';
  if (lower.endsWith('.zip')) return 'application/zip';
  return 'application/octet-stream';
}
import DocumentPicker from 'react-native-document-picker';
import rvs, { ConnectionState, RVSMessage, ConnectionConfig, ConnectionLogEntry } from '../services/rvs';
import {
  TTS_PREROLL_DEFAULT_SEC,
  TTS_PREROLL_MIN_SEC,
  TTS_PREROLL_MAX_SEC,
  TTS_PREROLL_STORAGE_KEY,
  VAD_SILENCE_DEFAULT_SEC,
  VAD_SILENCE_MIN_SEC,
  VAD_SILENCE_MAX_SEC,
  VAD_SILENCE_STORAGE_KEY,
  CONV_WINDOW_DEFAULT_SEC,
  CONV_WINDOW_MIN_SEC,
  CONV_WINDOW_MAX_SEC,
  CONV_WINDOW_STORAGE_KEY,
  MAX_RECORDING_DEFAULT_SEC,
  MAX_RECORDING_MIN_SEC,
  MAX_RECORDING_MAX_SEC,
  MAX_RECORDING_STORAGE_KEY,
  VAD_SILENCE_DB_DEFAULT,
  VAD_SILENCE_DB_MIN,
  VAD_SILENCE_DB_MAX,
  VAD_SILENCE_DB_OVERRIDE_KEY,
  TTS_SPEED_DEFAULT,
  TTS_SPEED_MIN,
  TTS_SPEED_MAX,
  TTS_SPEED_STORAGE_KEY,
} from '../services/audio';
import audioService from '../services/audio';
import gpsTrackingService, {
  isBackgroundGpsEnabled,
  setBackgroundGpsEnabled,
  ensureBackgroundLocationPermission,
} from '../services/gpsTracking';
import { acquireBackgroundAudio, releaseBackgroundAudio } from '../services/backgroundAudio';
import MemoryBrowser from '../components/MemoryBrowser';
import TriggerBrowser from '../components/TriggerBrowser';
import SkillBrowser from '../components/SkillBrowser';
import OAuthBrowser from '../components/OAuthBrowser';
import VoiceIdEnrollment from '../components/VoiceIdEnrollment';
import ProjectsBrowser from '../components/ProjectsBrowser';
import brainApi from '../services/brainApi';
import { isVerboseLogging, setVerboseLogging, isDebugLogsToBridge, setDebugLogsToBridge, APP_LOG_EVENT } from '../services/logger';
import {
  isWakeReadySoundEnabled,
  setWakeReadySoundEnabled,
  playWakeReadySound,
} from '../services/wakeReadySound';
import wakeWordService, {
  WAKE_KEYWORDS,
  KEYWORD_LABELS,
  DEFAULT_KEYWORD,
  WAKE_KEYWORD_STORAGE,
} from '../services/wakeword';
import ModeSelector from '../components/ModeSelector';
import QRScanner from '../components/QRScanner';
import VoiceCloneModal from '../components/VoiceCloneModal';
import updateService from '../services/updater';

const STORAGE_PATH_KEY = 'aria_attachment_storage_path';
const DEFAULT_STORAGE_PATH = `${RNFS.DocumentDirectoryPath}/chat_attachments`;

// --- Typen ---

interface LogEntry {
  id: string;
  timestamp: number;
  source: string;
  message: string;
  level: 'info' | 'warn' | 'error';
}

interface EventEntry {
  id: string;
  timestamp: number;
  title: string;
  description: string;
}

type LogTab = 'live' | 'events';

// Settings-Sub-Screens. Reihenfolge im Hauptmenue.
const SETTINGS_SECTIONS = [
  { id: 'connection',   icon: '🔌', label: 'Verbindung',     desc: 'Server, Token, Status, Verbindungslog' },
  { id: 'general',      icon: '⚙️', label: 'Allgemein',      desc: 'Betriebsmodus, GPS-Standort' },
  { id: 'voice_input',  icon: '🎙️', label: 'Spracheingabe',  desc: 'Stille-Toleranz, Aufnahmedauer' },
  { id: 'wake_word',    icon: '👂', label: 'Wake-Word',      desc: 'Wake-Word-Auswahl' },
  { id: 'voice_id',     icon: '🎤', label: 'Stimme einrichten', desc: 'Sprecher-Erkennung — nur deine Stimme triggert ARIA' },
  { id: 'voice_output', icon: '🔊', label: 'Sprachausgabe',  desc: 'Stimmen, Pre-Roll, Geschwindigkeit' },
  { id: 'storage',      icon: '📁', label: 'Speicher',       desc: 'Anhang-Speicherort, Auto-Download' },
  { id: 'files',        icon: '📂', label: 'Dateien',        desc: 'ARIA- und User-Dateien — anzeigen, löschen' },
  { id: 'memory',       icon: '🧠', label: 'Gedächtnis',     desc: 'ARIA-Memories durchsuchen, anlegen, bearbeiten, löschen' },
  { id: 'projects',     icon: '📁', label: 'Projekte',       desc: 'Thread-Bündel im Hauptchat — verwalten, wechseln, beenden' },
  { id: 'triggers',     icon: '⏰', label: 'Trigger',        desc: 'Timer + Watcher anlegen, bearbeiten, löschen' },
  { id: 'skills',       icon: '🛠️', label: 'Skills',         desc: 'Skills ausführen, aktivieren, Logs ansehen, löschen' },
  { id: 'oauth',        icon: '🔑', label: 'OAuth-Apps',     desc: 'Spotify, Dropbox, ... — client_id/secret, autorisieren, abmelden' },
  { id: 'protocol',     icon: '📜', label: 'Protokoll',      desc: 'Privatsphaere, Backup' },
  { id: 'about',        icon: 'ℹ️', label: 'Ueber',          desc: 'App-Version, Update' },
] as const;

// Container-Farben fuer Live-Logs
const SOURCE_COLORS: Record<string, string> = {
  'aria-core': '#4A9EFF',   // Blau
  bridge: '#FFD60A',        // Gelb
  proxy: '#FFFFFF',         // Weiss
  rvs: '#34C759',           // Gruen
  default: '#8888AA',       // Grau
};

// --- Komponente ---

const SettingsScreen: React.FC = () => {
  const winDims = useWindowDimensions();
  const [connectionState, setConnectionState] = useState<ConnectionState>('disconnected');
  const [manualToken, setManualToken] = useState('');
  const [manualHost, setManualHost] = useState('');
  const [manualPort, setManualPort] = useState('8765');
  const [currentMode, setCurrentMode] = useState('normal');
  const [gpsEnabled, setGpsEnabled] = useState(false);
  const [gpsTracking, setGpsTracking] = useState(gpsTrackingService.isActive());
  const [bgGpsEnabled, setBgGpsEnabled] = useState(false);
  const [backgroundMode, setBackgroundMode] = useState(true); // Default an
  const [showSystemHints, setShowSystemHints] = useState(false); // Default aus
  const [scannerVisible, setScannerVisible] = useState(false);
  const [logTab, setLogTab] = useState<LogTab>('live');
  const [logs, setLogs] = useState<LogEntry[]>([]);
  const [events, setEvents] = useState<EventEntry[]>([]);
  const [connLog, setConnLog] = useState<ConnectionLogEntry[]>(rvs.getConnectionLog());
  const [storagePath, setStoragePath] = useState(DEFAULT_STORAGE_PATH);
  const [autoDownload, setAutoDownload] = useState(true);
  const [storageSize, setStorageSize] = useState('...');
  const [ttsEnabled, setTtsEnabled] = useState(true);
  const [ttsPrerollSec, setTtsPrerollSec] = useState<number>(TTS_PREROLL_DEFAULT_SEC);
  const [vadSilenceSec, setVadSilenceSec] = useState<number>(VAD_SILENCE_DEFAULT_SEC);
  const [convWindowSec, setConvWindowSec] = useState<number>(CONV_WINDOW_DEFAULT_SEC);
  const [maxRecordingSec, setMaxRecordingSec] = useState<number>(MAX_RECORDING_DEFAULT_SEC);
  // null = automatisch (adaptive Baseline), sonst manueller dB-Override
  const [vadSilenceDb, setVadSilenceDb] = useState<number | null>(null);
  const [showVadInfo, setShowVadInfo] = useState(false);
  const [apkCacheInfo, setApkCacheInfo] = useState<{count: number, totalMB: number} | null>(null);
  const [ttsCacheInfo, setTtsCacheInfo] = useState<{count: number, totalMB: number} | null>(null);
  const [verboseLogging, setVerboseLoggingState] = useState<boolean>(isVerboseLogging());
  const [debugLogsToBridge, setDebugLogsToBridgeState] = useState<boolean>(isDebugLogsToBridge());
  const [ttsSpeed, setTtsSpeed] = useState<number>(TTS_SPEED_DEFAULT);
  const [wakeKeyword, setWakeKeyword] = useState<string>(DEFAULT_KEYWORD);
  const [wakeStatus, setWakeStatus] = useState<string>('');
  const [wakeReadySound, setWakeReadySound] = useState<boolean>(true);
  const [editingPath, setEditingPath] = useState(false);
  const [xttsVoice, setXttsVoice] = useState('');
  const [loadingVoice, setLoadingVoice] = useState<string | null>(null);
  const [availableVoices, setAvailableVoices] = useState<Array<{name: string, size: number}>>([]);
  // Datei-Manager
  const [fileManagerOpen, setFileManagerOpen] = useState(false);
  const [fileManagerFiles, setFileManagerFiles] = useState<Array<{name: string; path: string; size: number; mtime: number; fromAria: boolean; projectId?: string}>>([]);
  const [fileFilterProjectId, setFileFilterProjectId] = useState<string>('__all__');
  const [fileFilterProjects, setFileFilterProjects] = useState<Array<{id: string; name: string}>>([]);
  const [fileManagerLoading, setFileManagerLoading] = useState(false);
  const [fileManagerError, setFileManagerError] = useState('');
  const [fileManagerSearch, setFileManagerSearch] = useState('');
  const [fileManagerFilter, setFileManagerFilter] = useState<'all' | 'aria' | 'user'>('all');
  const [fileManagerSelected, setFileManagerSelected] = useState<Set<string>>(new Set());
  const fileZipPending = useRef<string | null>(null); // requestId fuer ZIP-Antwort
  const [fileZipBusy, setFileZipBusy] = useState(false);
  // Versions-Modal — pro Datei eine kleine Historie aus dem auto-commit-git
  // im diagnostic-Container. Browser-Variante davon laeuft schon, hier App-
  // Side via RVS-Messages (file_version_list_request/...).
  const [versionsOpen, setVersionsOpen] = useState<{name: string; path: string} | null>(null);
  const [versionsList, setVersionsList] = useState<Array<{hash: string; ts: number; subject: string; isCurrent?: boolean}>>([]);
  const [versionsLoading, setVersionsLoading] = useState(false);
  const [versionsError, setVersionsError] = useState('');
  const versionDlPending = useRef<string | null>(null); // requestId beim Versions-Download
  const [voiceCloneVisible, setVoiceCloneVisible] = useState(false);
  const [tempPath, setTempPath] = useState('');
  // Sub-Screen Navigation: null = Hauptmenue, sonst eine der Section-IDs.
  // So bleibt aller geteilte State im selben Component-Closure und wir
  // brauchen keine react-navigation-Stack-Setup.
  const [currentSection, setCurrentSection] = useState<string | null>(null);

  let logIdCounter = 0;

  // Gespeicherte Config in die Felder laden
  useEffect(() => {
    const config = rvs.getConfig();
    if (config) {
      setManualHost(config.host);
      setManualPort(String(config.port));
      setManualToken(config.token);
    }
    // Speicherpfad + Auto-Download laden
    AsyncStorage.getItem(STORAGE_PATH_KEY).then(saved => {
      if (saved) setStoragePath(saved);
    });
    AsyncStorage.getItem('aria_auto_download').then(saved => {
      if (saved !== null) setAutoDownload(saved === 'true');
    });
    AsyncStorage.getItem('aria_tts_enabled').then(saved => {
      if (saved !== null) setTtsEnabled(saved === 'true');
    });
    AsyncStorage.getItem('aria_gps_enabled').then(saved => {
      if (saved !== null) setGpsEnabled(saved === 'true');
    });
    AsyncStorage.getItem('aria_background_mode').then(saved => {
      // Default ist an — nur explicit 'false' deaktiviert
      setBackgroundMode(saved !== 'false');
    });
    AsyncStorage.getItem('aria_show_hints').then(saved => {
      // Default ist aus — nur explicit 'true' aktiviert
      setShowSystemHints(saved === 'true');
    });
    // gpsTrackingService status syncen + auf Aenderungen lauschen
    setGpsTracking(gpsTrackingService.isActive());
    const offGps = gpsTrackingService.onChange(setGpsTracking);
    // Persistierten Status wiederherstellen (war Tracking beim letzten Mal an?)
    gpsTrackingService.restoreFromStorage().catch(() => {});
    // Background-GPS-Toggle initial laden
    isBackgroundGpsEnabled().then(setBgGpsEnabled).catch(() => {});
    AsyncStorage.getItem(TTS_PREROLL_STORAGE_KEY).then(saved => {
      if (saved != null) {
        const n = parseFloat(saved);
        if (isFinite(n) && n >= TTS_PREROLL_MIN_SEC && n <= TTS_PREROLL_MAX_SEC) {
          setTtsPrerollSec(n);
        }
      }
    });
    AsyncStorage.getItem(VAD_SILENCE_STORAGE_KEY).then(saved => {
      if (saved != null) {
        const n = parseFloat(saved);
        if (isFinite(n) && n >= VAD_SILENCE_MIN_SEC && n <= VAD_SILENCE_MAX_SEC) {
          setVadSilenceSec(n);
        }
      }
    });
    AsyncStorage.getItem(CONV_WINDOW_STORAGE_KEY).then(saved => {
      if (saved != null) {
        const n = parseFloat(saved);
        if (isFinite(n) && n >= CONV_WINDOW_MIN_SEC && n <= CONV_WINDOW_MAX_SEC) {
          setConvWindowSec(n);
        }
      }
    });
    AsyncStorage.getItem(MAX_RECORDING_STORAGE_KEY).then(saved => {
      if (saved != null) {
        const n = parseFloat(saved);
        if (isFinite(n) && n >= MAX_RECORDING_MIN_SEC && n <= MAX_RECORDING_MAX_SEC) {
          setMaxRecordingSec(n);
        }
      }
    });
    AsyncStorage.getItem(VAD_SILENCE_DB_OVERRIDE_KEY).then(saved => {
      if (saved != null && saved !== '') {
        const n = parseFloat(saved);
        if (isFinite(n) && n >= VAD_SILENCE_DB_MIN && n <= VAD_SILENCE_DB_MAX) {
          setVadSilenceDb(n);
        }
      }
    });
    AsyncStorage.getItem(TTS_SPEED_STORAGE_KEY).then(saved => {
      if (saved != null) {
        const n = parseFloat(saved);
        if (isFinite(n) && n >= TTS_SPEED_MIN && n <= TTS_SPEED_MAX) setTtsSpeed(n);
      }
    });
    AsyncStorage.getItem(WAKE_KEYWORD_STORAGE).then(saved => {
      if (saved && (WAKE_KEYWORDS as readonly string[]).includes(saved)) setWakeKeyword(saved);
    });
    isWakeReadySoundEnabled().then(setWakeReadySound);
    updateService.getApkCacheSize().then(setApkCacheInfo).catch(() => {});
    audioService.getTtsCacheSize().then(setTtsCacheInfo).catch(() => {});
    AsyncStorage.getItem('aria_xtts_voice').then(saved => {
      if (saved) setXttsVoice(saved);
    });
    // Voice-Liste vom XTTS-Server holen (via RVS)
    rvs.send('xtts_list_voices' as any, {});
    return () => {
      // gpsTrackingService-Listener abmelden (Variable offGps oben definiert)
      try { offGps(); } catch {}
    };
  }, []);

  // Speichergroesse berechnen
  useEffect(() => {
    const calcSize = async () => {
      try {
        const exists = await RNFS.exists(storagePath);
        if (!exists) { setStorageSize('0 KB'); return; }
        const items = await RNFS.readDir(storagePath);
        const totalBytes = items.reduce((sum, f) => sum + (f.size || 0), 0);
        if (totalBytes > 1024 * 1024) {
          setStorageSize(`${(totalBytes / 1024 / 1024).toFixed(1)} MB (${items.length} Dateien)`);
        } else {
          setStorageSize(`${Math.round(totalBytes / 1024)} KB (${items.length} Dateien)`);
        }
      } catch { setStorageSize('nicht verfuegbar'); }
    };
    calcSize();
  }, [storagePath]);

  const saveStoragePath = useCallback(async (newPath: string) => {
    const clean = newPath.trim();
    if (!clean) return;
    await AsyncStorage.setItem(STORAGE_PATH_KEY, clean);
    setStoragePath(clean);
    setEditingPath(false);
    Alert.alert('Gespeichert', `Neuer Speicherort:\n${clean}\n\nWird ab der naechsten Nachricht verwendet.`);
  }, []);

  const showPathPicker = useCallback(() => {
    Alert.alert(
      'Speicherort waehlen',
      'Wo sollen Anhaenge gespeichert werden?',
      [
        {
          text: 'Ordner auswaehlen...',
          onPress: async () => {
            try {
              const result = await DocumentPicker.pickDirectory();
              if (result?.uri) {
                // SAF URI decodieren (content://com.android.externalstorage...)
                const decoded = decodeURIComponent(result.uri);
                // Versuche einen lesbaren Pfad zu extrahieren
                const match = decoded.match(/primary[:%]3A(.+)/);
                const readablePath = match
                  ? `/storage/emulated/0/${match[1].replace(/%2F|%3A/g, '/')}`
                  : decoded;
                saveStoragePath(readablePath);
              }
            } catch (e: any) {
              if (!DocumentPicker.isCancel(e)) {
                Alert.alert('Fehler', 'Ordnerauswahl fehlgeschlagen');
              }
            }
          },
        },
        {
          text: 'App-intern (Standard)',
          onPress: () => saveStoragePath(DEFAULT_STORAGE_PATH),
        },
        {
          text: 'Pfad manuell eingeben',
          onPress: () => { setTempPath(storagePath); setEditingPath(true); },
        },
        { text: 'Abbrechen', style: 'cancel' as const },
      ],
    );
  }, [storagePath]);

  const clearStorageCache = useCallback(async () => {
    Alert.alert(
      'Cache loeschen',
      `Alle lokalen Anhaenge in\n${storagePath}\nloeschen?\n\nDateien koennen ueber RVS erneut heruntergeladen werden.`,
      [
        { text: 'Abbrechen', style: 'cancel' },
        {
          text: 'Loeschen',
          style: 'destructive',
          onPress: async () => {
            try {
              const exists = await RNFS.exists(storagePath);
              if (exists) await RNFS.unlink(storagePath);
              await RNFS.mkdir(storagePath);
              setStorageSize('0 KB (0 Dateien)');
              Alert.alert('Erledigt', 'Cache geleert. Anhaenge werden bei Bedarf neu geladen.');
            } catch (e: any) {
              Alert.alert('Fehler', e.message);
            }
          },
        },
      ],
    );
  }, [storagePath]);

  // RVS-Nachrichten und Verbindungslog abonnieren
  useEffect(() => {
    const unsubState = rvs.onStateChange(setConnectionState);
    setConnectionState(rvs.getState());

    const unsubLog = rvs.onLog((entry) => {
      setConnLog(prev => [...prev.slice(-99), entry]);
    });

    // Lokale App-Logs (reportAppDebug/Error) im Live-Logs-Tab anzeigen
    // — damit Stefan ohne curl direkt in der App sieht was passiert.
    const localLogSub = DeviceEventEmitter.addListener(APP_LOG_EVENT, (e: any) => {
      const entry: LogEntry = {
        id: `applog_${e.ts || Date.now()}_${logIdCounter++}`,
        timestamp: e.ts || Date.now(),
        source: e.scope || 'app',
        message: e.message || '',
        level: e.level || 'info',
      };
      setLogs(prev => [...prev.slice(-200), entry]);
    });

    const unsubMessage = rvs.onMessage((message: RVSMessage) => {
      if (message.type === 'log') {
        const entry: LogEntry = {
          id: `log_${Date.now()}_${logIdCounter++}`,
          timestamp: message.timestamp,
          source: (message.payload.source as string) || 'default',
          message: (message.payload.message as string) || '',
          level: (message.payload.level as 'info' | 'warn' | 'error') || 'info',
        };
        setLogs(prev => [...prev.slice(-200), entry]); // Max 200 Eintraege behalten
      }

      if (message.type === 'event') {
        const entry: EventEntry = {
          id: `evt_${Date.now()}_${logIdCounter++}`,
          timestamp: message.timestamp,
          title: (message.payload.title as string) || '',
          description: (message.payload.description as string) || '',
        };
        setEvents(prev => [...prev.slice(-100), entry]);
      }

      // Modus-Bestaetigung
      if (message.type === 'mode') {
        const mode = message.payload.mode as string;
        if (mode) setCurrentMode(mode);
      }

      // XTTS-Voice-Liste
      if (message.type === ('xtts_voices_list' as any)) {
        const voices = ((message.payload as any).voices || []) as Array<{name: string, size: number}>;
        setAvailableVoices(voices);
      }

      // Datei-Manager: Liste empfangen
      if (message.type === ('file_list_response' as any)) {
        const p: any = message.payload || {};
        if (p.ok) {
          setFileManagerFiles(p.files || []);
        } else {
          setFileManagerError(p.error || 'Unbekannter Fehler');
        }
        setFileManagerLoading(false);
      }

      // Datei-Manager: Datei wurde geloescht (vom Diagnostic oder dieser App)
      if (message.type === ('file_deleted' as any)) {
        const p: any = message.payload || {};
        if (p.path) {
          setFileManagerFiles(prev => prev.filter(f => f.path !== p.path));
          setFileManagerSelected(prev => {
            if (!prev.has(p.path)) return prev;
            const next = new Set(prev);
            next.delete(p.path);
            return next;
          });
        }
      }

      // ARIA bittet um GPS-Tracking An/Aus (Tool request_location_tracking)
      if (message.type === ('location_tracking' as any)) {
        const p: any = message.payload || {};
        const on = !!p.on;
        const reason = (p.reason as string) || 'ARIA';
        if (on) {
          gpsTrackingService.start(reason).catch(() => {});
        } else {
          gpsTrackingService.stop(reason);
        }
      }

      // Datei-Manager: ZIP-Response (Multi-Download)
      if (message.type === ('file_zip_response' as any)) {
        const p: any = message.payload || {};
        if (p.requestId && p.requestId !== fileZipPending.current) return; // veraltet
        fileZipPending.current = null;
        setFileZipBusy(false);
        if (!p.ok || !p.data) {
          ToastAndroid.show('ZIP fehlgeschlagen: ' + (p.error || 'unbekannt'), ToastAndroid.LONG);
          return;
        }
        // base64 → in Downloads-Ordner schreiben
        (async () => {
          try {
            const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
            const dir = RNFS.DownloadDirectoryPath;
            const filePath = `${dir}/aria-files-${ts}.zip`;
            await RNFS.writeFile(filePath, p.data, 'base64');
            ToastAndroid.show(`ZIP gespeichert: ${filePath} (${Math.round((p.size||0)/1024)} KB)`, ToastAndroid.LONG);
          } catch (e: any) {
            ToastAndroid.show('ZIP speichern fehlgeschlagen: ' + e.message, ToastAndroid.LONG);
          }
        })();
      }

      // Datei-Manager: Einzel-Datei-Download. ChatScreen subscribet auch auf
      // file_response — der versucht aber nur Chat-Bubble-Attachments zu
      // patchen und macht nix wenn die requestId nicht zu einer Nachricht
      // passt. Hier behandeln wir die Manager-initiierten Downloads
      // (requestId-Praefix 'single-' aus bulkDownload). Schreibt nach
      // ~/Download/ wie der ZIP-Pfad.
      if (message.type === ('file_response' as any)) {
        const p: any = message.payload || {};
        const reqId = (p.requestId as string) || '';
        const isDownload = reqId.startsWith('single-');
        const isOpen = reqId.startsWith('open-');
        if (!isDownload && !isOpen) return;  // andere Caller (ChatScreen etc.)
        if (p.error) {
          ToastAndroid.show((isOpen ? 'Öffnen' : 'Download') + ' fehlgeschlagen: ' + p.error, ToastAndroid.LONG);
          return;
        }
        const b64 = (p.base64 as string) || '';
        if (!b64) return;
        const fileName = (p.name as string) ||
                         (p.serverPath as string || '').split('/').pop() ||
                         'aria-download';
        (async () => {
          try {
            if (isOpen) {
              // Open-Pfad: nach Caches schreiben + per FileOpener mit System-
              // Viewer oeffnen. Caches damit der Speicher kein Dauer-Muell wird.
              const dir = RNFS.CachesDirectoryPath;
              const target = `${dir}/${fileName}`;
              await RNFS.writeFile(target, b64, 'base64');
              const mime = (p.mimeType as string) || guessMimeFromName(fileName);
              if (FileOpener?.open) {
                try {
                  await FileOpener.open(target, mime);
                } catch (e: any) {
                  ToastAndroid.show('Öffnen fehlgeschlagen: ' + (e?.message || e), ToastAndroid.LONG);
                }
              } else {
                ToastAndroid.show('FileOpener-Modul nicht verfügbar — APK neu bauen', ToastAndroid.LONG);
              }
              return;
            }
            // Download-Pfad: nach Downloads-Ordner schreiben, mit Suffix bei
            // Namens-Konflikt damit nichts ueberschrieben wird.
            const dir = RNFS.DownloadDirectoryPath;
            const filePath = `${dir}/${fileName}`;
            let target = filePath;
            let i = 1;
            while (await RNFS.exists(target)) {
              const dot = fileName.lastIndexOf('.');
              const base = dot > 0 ? fileName.slice(0, dot) : fileName;
              const ext = dot > 0 ? fileName.slice(dot) : '';
              target = `${dir}/${base} (${i})${ext}`;
              i++;
            }
            await RNFS.writeFile(target, b64, 'base64');
            const sizeKb = Math.round(((b64.length * 0.75)) / 1024);
            ToastAndroid.show(`Gespeichert: ${target.split('/').pop()} (${sizeKb} KB)`, ToastAndroid.LONG);
          } catch (e: any) {
            ToastAndroid.show('Speichern fehlgeschlagen: ' + e.message, ToastAndroid.LONG);
          }
        })();
      }

      // Datei-Manager: Versions-Liste einer Datei
      if (message.type === ('file_version_list_response' as any)) {
        const p: any = message.payload || {};
        setVersionsLoading(false);
        if (!p.ok) {
          setVersionsError(p.error || 'Unbekannter Fehler');
          setVersionsList([]);
        } else {
          setVersionsError('');
          setVersionsList(p.versions || []);
        }
      }

      // Datei-Manager: Versions-Inhalt (Download einer alten Version)
      if (message.type === ('file_version_download_response' as any)) {
        const p: any = message.payload || {};
        if (p.requestId && p.requestId !== versionDlPending.current) return;
        versionDlPending.current = null;
        if (!p.ok) {
          ToastAndroid.show('Download fehlgeschlagen: ' + (p.error || 'unbekannt'), ToastAndroid.LONG);
          return;
        }
        // base64 → Downloads-Ordner. Hash als Suffix damit Original nicht
        // ueberschrieben wird wenn beide Versionen nebeneinander vorliegen
        // sollen.
        (async () => {
          try {
            const baseName = (p.name as string) || 'aria-version';
            const shortHash = (p.hash as string || '').slice(0, 7);
            const dot = baseName.lastIndexOf('.');
            const stem = dot > 0 ? baseName.slice(0, dot) : baseName;
            const ext = dot > 0 ? baseName.slice(dot) : '';
            const dir = RNFS.DownloadDirectoryPath;
            let target = `${dir}/${stem}@${shortHash}${ext}`;
            let i = 1;
            while (await RNFS.exists(target)) {
              target = `${dir}/${stem}@${shortHash}_${i}${ext}`;
              i++;
            }
            await RNFS.writeFile(target, p.base64, 'base64');
            const sizeKb = Math.round(((p.base64.length * 0.75)) / 1024);
            ToastAndroid.show(`Gespeichert: ${target.split('/').pop()} (${sizeKb} KB)`, ToastAndroid.LONG);
          } catch (e: any) {
            ToastAndroid.show('Speichern fehlgeschlagen: ' + e.message, ToastAndroid.LONG);
          }
        })();
      }

      // Datei-Manager: Restore-Bestaetigung
      if (message.type === ('file_version_restore_response' as any)) {
        const p: any = message.payload || {};
        if (!p.ok) {
          ToastAndroid.show('Restore fehlgeschlagen: ' + (p.error || 'unbekannt'), ToastAndroid.LONG);
          return;
        }
        ToastAndroid.show(`Version ${(p.hash || '').slice(0,7)} ist jetzt aktiv`, ToastAndroid.SHORT);
        // Versions-Liste neu laden damit der neue restore-Commit auftaucht
        if (versionsOpen) {
          setVersionsLoading(true);
          rvs.send('file_version_list_request' as any, { path: versionsOpen.path });
        }
        // File-Liste auch refreshen (mtime hat sich geaendert)
        if (fileManagerOpen) {
          setFileManagerLoading(true);
          rvs.send('file_list_request' as any, {});
        }
      }

      // Voice wurde gespeichert → Liste neu laden + ggf. auswaehlen
      if (message.type === ('xtts_voice_saved' as any)) {
        const name = (message.payload as any).name as string;
        if (name) {
          setXttsVoice(name);
          AsyncStorage.setItem('aria_xtts_voice', name);
        }
        rvs.send('xtts_list_voices' as any, {});
      }

      // Diagnostic-Voice-Wechsel → lokale App-Stimme auf den neuen Default zuruecksetzen.
      // Zusaetzlich Preload triggern, damit der User weiss wann's geladen ist.
      if (message.type === ('config' as any)) {
        const newVoice = ((message.payload as any).xttsVoice as string) ?? '';
        setXttsVoice(newVoice);
        AsyncStorage.setItem('aria_xtts_voice', newVoice);
        if (newVoice) {
          setLoadingVoice(newVoice);
        }
      }

      // XTTS-Bridge meldet: Stimme fertig geladen
      if (message.type === ('voice_ready' as any)) {
        const v = ((message.payload as any).voice as string) ?? '';
        const err = (message.payload as any).error as string | undefined;
        const ms = (message.payload as any).loadMs as number | undefined;
        setLoadingVoice(null);
        if (err) {
          ToastAndroid.show(`Stimme "${v}" konnte nicht geladen werden: ${err}`, ToastAndroid.LONG);
        } else {
          const suffix = ms ? ` (${(ms / 1000).toFixed(1)}s)` : '';
          ToastAndroid.show(`Stimme "${v || 'Standard'}" bereit${suffix}`, ToastAndroid.SHORT);
        }
      }
    });

    return () => {
      unsubState();
      unsubMessage();
      unsubLog();
      localLogSub.remove();
    };
  }, []);

  // Datei-Manager: Auto-Reload bei RVS-Reconnect — sonst zeigt das offene
  // Modal den Fehler "Connection refused" ewig an, obwohl die Verbindung
  // schon wieder da ist. Triggered nur wenn das Modal gerade offen ist.
  useEffect(() => {
    const unsub = rvs.onStateChange((state) => {
      if (state === 'connected' && fileManagerOpen) {
        setFileManagerError('');
        setFileManagerLoading(true);
        rvs.send('file_list_request' as any, {});
      }
    });
    return () => unsub();
  }, [fileManagerOpen]);

  // Beim Oeffnen des Datei-Managers: Projekt-Liste laden fuer den Filter.
  useEffect(() => {
    if (!fileManagerOpen) return;
    brainApi.listProjects(true)
      .then(list => setFileFilterProjects(list.map(p => ({ id: p.id, name: p.name }))))
      .catch(() => {});
    // Default-Filter: fokussiertes Projekt aus AsyncStorage (falls Stefan
    // grade in einem drin ist), sonst "alle". Multi-Threading: Focus ist
    // App-lokal, kein Brain-Query mehr.
    AsyncStorage.getItem('aria_focused_project_id')
      .then(pid => { if (pid) setFileFilterProjectId(pid); })
      .catch(() => {});
  }, [fileManagerOpen]);

  // --- QR-Code scannen ---

  const openQRScanner = useCallback(() => {
    setScannerVisible(true);
  }, []);

  const handleQRScan = useCallback((config: ConnectionConfig) => {
    setScannerVisible(false);

    // Felder im UI aktualisieren
    setManualHost(config.host);
    setManualPort(String(config.port));
    setManualToken(config.token);

    // Direkt verbinden
    rvs.setConfig(config);
    rvs.connect();

    Alert.alert(
      'Pairing erfolgreich',
      `Verbinde mit ${config.host}:${config.port}...`,
    );
  }, []);

  // --- Manuelle Verbindung ---

  const connectManually = useCallback(() => {
    if (!manualHost.trim() || !manualToken.trim()) {
      Alert.alert('Fehler', 'Host und Token muessen angegeben werden.');
      return;
    }

    const config: ConnectionConfig = {
      host: manualHost.trim(),
      port: parseInt(manualPort, 10) || 8765,
      token: manualToken.trim(),
      useTLS: true,
    };

    rvs.setConfig(config);
    rvs.connect();
  }, [manualHost, manualPort, manualToken]);

  const disconnectRVS = useCallback(() => {
    rvs.disconnect();
  }, []);

  // --- GPS Toggle ---

  const handleGPSToggle = useCallback(async (value: boolean) => {
    if (value && Platform.OS === 'android') {
      try {
        const granted = await PermissionsAndroid.request(
          PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
          {
            title: 'ARIA — Standort an Anfragen anhaengen',
            message: 'Damit ARIA bei Anfragen wie "Wo ist der naechste...?" den '
              + 'Standort kennt, darf die App den ungefaehren Standort lesen. '
              + 'Wird nur bei jeder Anfrage einmal abgerufen, nicht im Hintergrund.',
            buttonPositive: 'Erlauben',
            buttonNegative: 'Abbrechen',
          },
        );
        if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
          ToastAndroid.show('Standort-Berechtigung abgelehnt', ToastAndroid.SHORT);
          return;
        }
      } catch (err) {
        console.warn('[Settings] GPS-Permission Request gescheitert:', err);
        return;
      }
    }
    setGpsEnabled(value);
    AsyncStorage.setItem('aria_gps_enabled', String(value)).catch(() => {});
  }, []);

  // --- Hintergrund-Modus Toggle ---

  const handleBackgroundModeToggle = useCallback(async (value: boolean) => {
    setBackgroundMode(value);
    AsyncStorage.setItem('aria_background_mode', String(value)).catch(() => {});
    try {
      if (value) {
        // Permission fuer Notification (Android 13+) — sonst sieht der User
        // den Hintergrund-Modus nicht und wundert sich
        if (Platform.OS === 'android' && Platform.Version >= 33) {
          await PermissionsAndroid.request(
            'android.permission.POST_NOTIFICATIONS' as any,
            {
              title: 'Hintergrund-Modus',
              message: 'ARIA zeigt eine Notification damit die App im Hintergrund laufen darf.',
              buttonPositive: 'Erlauben',
              buttonNegative: 'Spaeter',
            },
          );
        }
        await acquireBackgroundAudio('background');
        ToastAndroid.show('Hintergrund-Modus aktiv', ToastAndroid.SHORT);
      } else {
        await releaseBackgroundAudio('background');
        ToastAndroid.show('Hintergrund-Modus aus', ToastAndroid.SHORT);
      }
    } catch (err: any) {
      console.warn('[Settings] Background-Toggle gescheitert:', err?.message || err);
    }
  }, []);

  // --- System-Hints Toggle ---

  const handleShowSystemHintsToggle = useCallback((value: boolean) => {
    setShowSystemHints(value);
    AsyncStorage.setItem('aria_show_hints', String(value)).catch(() => {});
  }, []);

  // --- XTTS Voice ---

  const selectVoice = useCallback((voiceName: string) => {
    setXttsVoice(voiceName);
    AsyncStorage.setItem('aria_xtts_voice', voiceName);
    // Preload nur fuer Custom-Voices — "Standard" braucht keinen Ladevorgang
    if (voiceName) {
      setLoadingVoice(voiceName);
      rvs.send('voice_preload' as any, { voice: voiceName, source: 'app' });
    } else {
      setLoadingVoice(null);
    }
  }, []);

  const deleteVoice = useCallback((name: string) => {
    Alert.alert(
      'Stimme loeschen',
      `Stimme "${name}" vom Server endgueltig loeschen?\nAlle Apps verlieren sie.`,
      [
        { text: 'Abbrechen', style: 'cancel' },
        {
          text: 'Loeschen',
          style: 'destructive',
          onPress: () => {
            rvs.send('xtts_delete_voice' as any, { name });
            if (xttsVoice === name) {
              setXttsVoice('');
              AsyncStorage.setItem('aria_xtts_voice', '');
            }
            // Liste nach kurzer Wartezeit neu laden (XTTS-Bridge schickt eh neue Liste)
            setTimeout(() => rvs.send('xtts_list_voices' as any, {}), 500);
          },
        },
      ],
    );
  }, [xttsVoice]);

  // --- Modus aendern ---

  const handleModeChange = useCallback((modeId: string) => {
    setCurrentMode(modeId);
  }, []);

  // --- Zeitformat ---

  const formatTime = (ts: number): string => {
    return new Date(ts).toLocaleTimeString('de-DE', {
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
    });
  };

  // --- Verbindungsstatus ---

  const connectionDotColor =
    connectionState === 'connected' ? '#34C759' :
    connectionState === 'connecting' ? '#FFD60A' : '#FF3B30';

  const connectionLabel =
    connectionState === 'connected' ? 'Verbunden' :
    connectionState === 'connecting' ? 'Verbinde...' : 'Getrennt';

  return (
    <>
    <QRScanner
      visible={scannerVisible}
      onScan={handleQRScan}
      onClose={() => setScannerVisible(false)}
    />
    <VoiceCloneModal
      visible={voiceCloneVisible}
      onClose={() => setVoiceCloneVisible(false)}
    />
    {/* Datei-Manager Modal */}
    <Modal
      visible={fileManagerOpen}
      animationType="slide"
      onRequestClose={() => setFileManagerOpen(false)}
    >
      <View style={{flex:1, backgroundColor:'#080810', paddingTop:24}}>
        <View style={{flexDirection:'row', alignItems:'center', padding:12, borderBottomWidth:1, borderColor:'#1E1E2E'}}>
          <TouchableOpacity onPress={() => setFileManagerOpen(false)} style={{padding:8}}>
            <Text style={{color:'#0096FF', fontSize:24}}>‹</Text>
          </TouchableOpacity>
          <Text style={{color:'#E0E0F0', fontSize:18, fontWeight:'600', flex:1, marginLeft:8}}>Dateien</Text>
          <TouchableOpacity
            onPress={() => {
              setFileManagerError('');
              setFileManagerLoading(true);
              rvs.send('file_list_request' as any, {});
            }}
            style={{padding:8}}
          >
            <Text style={{color:'#0096FF', fontSize:14}}>🔄</Text>
          </TouchableOpacity>
        </View>
        <View style={{padding:12}}>
          <TextInput
            style={{backgroundColor:'#1E1E2E', borderRadius:8, padding:10, color:'#E0E0F0', fontSize:14}}
            placeholder="Suche..."
            placeholderTextColor="#555570"
            value={fileManagerSearch}
            onChangeText={setFileManagerSearch}
            autoCapitalize="none"
          />
          <View style={{flexDirection:'row', marginTop:8, gap:6}}>
            {(['all','aria','user'] as const).map(f => (
              <TouchableOpacity
                key={f}
                onPress={() => setFileManagerFilter(f)}
                style={{
                  paddingVertical:6, paddingHorizontal:12, borderRadius:14,
                  backgroundColor: fileManagerFilter === f ? '#0096FF' : '#1E1E2E',
                }}
              >
                <Text style={{color: fileManagerFilter === f ? '#fff' : '#8888AA', fontSize:12}}>
                  {f === 'all' ? 'Alle' : f === 'aria' ? 'Von ARIA' : 'Von dir'}
                </Text>
              </TouchableOpacity>
            ))}
          </View>
          {/* Projekt-Filter: scrollbare Pill-Reihe. „Alle Projekte" + „Hauptchat" +
              ein Pill pro Projekt. Default = aktives Projekt (siehe useEffect oben). */}
          <ScrollView horizontal showsHorizontalScrollIndicator={false}
                      style={{marginTop:6}} contentContainerStyle={{gap:6, paddingRight:8}}>
            {[
              { id: '__all__', name: '📁 Alle Projekte' },
              { id: '', name: '💬 Hauptchat' },
              ...fileFilterProjects,
            ].map(p => (
              <TouchableOpacity
                key={p.id || 'mainchat'}
                onPress={() => setFileFilterProjectId(p.id)}
                style={{
                  paddingVertical:6, paddingHorizontal:12, borderRadius:14,
                  backgroundColor: fileFilterProjectId === p.id ? '#34C759' : '#1E1E2E',
                }}
              >
                <Text style={{color: fileFilterProjectId === p.id ? '#fff' : '#8888AA', fontSize:12}}>
                  {p.name}
                </Text>
              </TouchableOpacity>
            ))}
          </ScrollView>
        </View>
        {fileManagerLoading ? (
          <Text style={{color:'#8888AA', textAlign:'center', marginTop:20}}>Lade...</Text>
        ) : fileManagerError ? (
          <Text style={{color:'#FF6B6B', textAlign:'center', marginTop:20}}>{fileManagerError}</Text>
        ) : (() => {
          // Visible files (Filter+Suche)
          let files = fileManagerFiles;
          if (fileManagerFilter === 'aria') files = files.filter(f => f.fromAria);
          else if (fileManagerFilter === 'user') files = files.filter(f => !f.fromAria);
          // Projekt-Filter: '__all__' = alles, '' = Hauptchat (kein project_id),
          // sonst exakte project_id-Match.
          if (fileFilterProjectId !== '__all__') {
            files = files.filter(f => (f.projectId || '') === fileFilterProjectId);
          }
          if (fileManagerSearch) {
            const q = fileManagerSearch.toLowerCase();
            files = files.filter(f => f.name.toLowerCase().includes(q));
          }
          const visiblePaths = files.map(f => f.path);
          const selectedHere = visiblePaths.filter(p => fileManagerSelected.has(p));
          const allSelected = visiblePaths.length > 0 && selectedHere.length === visiblePaths.length;
          const fmtSize = (b: number) => b < 1024 ? `${b} B` : b < 1024*1024 ? `${(b/1024).toFixed(1)} KB` : `${(b/1024/1024).toFixed(1)} MB`;

          const toggleSelectAll = () => {
            setFileManagerSelected(prev => {
              const next = new Set(prev);
              if (allSelected) visiblePaths.forEach(p => next.delete(p));
              else visiblePaths.forEach(p => next.add(p));
              return next;
            });
          };
          const toggleOne = (p: string) => {
            setFileManagerSelected(prev => {
              const next = new Set(prev);
              if (next.has(p)) next.delete(p);
              else next.add(p);
              return next;
            });
          };
          const bulkDelete = () => {
            const paths = [...fileManagerSelected];
            if (!paths.length) return;
            Alert.alert(
              `${paths.length} Dateien löschen?`,
              'In allen Chat-Bubbles werden sie als gelöscht markiert.',
              [
                { text: 'Abbrechen', style: 'cancel' },
                { text: 'Löschen', style: 'destructive', onPress: () => {
                  rvs.send('file_delete_batch_request' as any, { paths, requestId: 'batch-' + Date.now() });
                  setFileManagerSelected(new Set());
                  ToastAndroid.show(`${paths.length} Lösch-Befehle gesendet…`, ToastAndroid.SHORT);
                }},
              ],
            );
          };
          const bulkDownload = () => {
            const paths = [...fileManagerSelected];
            if (!paths.length) return;
            // 1 Datei: einfach via file_request (existing pattern). ZIP nur bei 2+.
            if (paths.length === 1) {
              rvs.send('file_request' as any, { serverPath: paths[0], requestId: 'single-' + Date.now() });
              ToastAndroid.show('Datei wird heruntergeladen…', ToastAndroid.SHORT);
              return;
            }
            const reqId = 'zip-' + Date.now();
            fileZipPending.current = reqId;
            setFileZipBusy(true);
            rvs.send('file_zip_request' as any, { paths, requestId: reqId });
            ToastAndroid.show(`ZIP wird erstellt (${paths.length} Dateien)…`, ToastAndroid.LONG);
          };

          return (
            <>
              {/* Bulk-Bar */}
              <View style={{paddingHorizontal:12, paddingBottom:8, flexDirection:'row', alignItems:'center', gap:8, flexWrap:'wrap'}}>
                <TouchableOpacity onPress={toggleSelectAll} style={{flexDirection:'row', alignItems:'center', gap:6, paddingVertical:4}}>
                  <View style={{
                    width:18, height:18, borderRadius:3,
                    borderWidth:2, borderColor: allSelected ? '#0096FF' : '#555570',
                    backgroundColor: allSelected ? '#0096FF' : 'transparent',
                    alignItems:'center', justifyContent:'center',
                  }}>
                    {allSelected && <Text style={{color:'#fff', fontSize:11, fontWeight:'bold'}}>✓</Text>}
                  </View>
                  <Text style={{color:'#E0E0F0', fontSize:13}}>Alle markieren</Text>
                </TouchableOpacity>
                {fileManagerSelected.size > 0 && (
                  <>
                    <Text style={{color:'#555570', fontSize:13}}>·</Text>
                    <Text style={{color:'#0096FF', fontSize:13, fontWeight:'600'}}>{fileManagerSelected.size} ausgewählt</Text>
                    <TouchableOpacity
                      onPress={bulkDownload}
                      disabled={fileZipBusy}
                      style={{paddingVertical:4, paddingHorizontal:10, borderRadius:6, backgroundColor:'#0096FF22', opacity: fileZipBusy ? 0.5 : 1}}
                    >
                      <Text style={{color:'#0096FF', fontSize:12}}>{fileZipBusy ? '⏳ ZIP…' : (fileManagerSelected.size > 1 ? '⬇ ZIP' : '⬇ Download')}</Text>
                    </TouchableOpacity>
                    <TouchableOpacity
                      onPress={bulkDelete}
                      style={{paddingVertical:4, paddingHorizontal:10, borderRadius:6, backgroundColor:'#FF6B6B22'}}
                    >
                      <Text style={{color:'#FF6B6B', fontSize:12}}>🗑 Löschen</Text>
                    </TouchableOpacity>
                  </>
                )}
              </View>

              <ScrollView style={{flex:1}} contentContainerStyle={{padding:12, paddingTop:0}}>
                {!files.length ? (
                  <Text style={{color:'#555570', textAlign:'center', marginTop:20}}>Keine Dateien</Text>
                ) : files.map(f => {
                  const selected = fileManagerSelected.has(f.path);
                  return (
                    <TouchableOpacity
                      key={f.path}
                      onPress={() => toggleOne(f.path)}
                      activeOpacity={0.7}
                      style={{
                        backgroundColor: selected ? '#1E2C44' : '#0D0D1A',
                        padding:12, borderRadius:8, marginBottom:8,
                        flexDirection:'row', alignItems:'center', gap:8,
                        borderWidth: selected ? 1 : 0, borderColor:'#0096FF',
                      }}
                    >
                      <View style={{
                        width:18, height:18, borderRadius:3,
                        borderWidth:2, borderColor: selected ? '#0096FF' : '#555570',
                        backgroundColor: selected ? '#0096FF' : 'transparent',
                        alignItems:'center', justifyContent:'center',
                      }}>
                        {selected && <Text style={{color:'#fff', fontSize:11, fontWeight:'bold'}}>✓</Text>}
                      </View>
                      <View style={{flex:1}}>
                        <View style={{flexDirection:'row', alignItems:'center'}}>
                          <View style={{
                            backgroundColor: f.fromAria ? '#0096FF22' : '#34C75922',
                            paddingHorizontal:6, paddingVertical:1, borderRadius:3, marginRight:6,
                          }}>
                            <Text style={{color: f.fromAria ? '#0096FF' : '#34C759', fontSize:9}}>
                              {f.fromAria ? 'ARIA' : 'USER'}
                            </Text>
                          </View>
                          <Text style={{color:'#E0E0F0', fontSize:13, flex:1}} numberOfLines={1}>{f.name}</Text>
                        </View>
                        <Text style={{color:'#555570', fontSize:10, marginTop:2}}>
                          {fmtSize(f.size)} · {new Date(f.mtime).toLocaleString('de-DE')}
                        </Text>
                      </View>
                      <TouchableOpacity
                        onPress={() => {
                          rvs.send('file_request' as any, {
                            serverPath: f.path,
                            requestId: 'open-' + Date.now(),
                          });
                          ToastAndroid.show('Öffne ' + f.name + '…', ToastAndroid.SHORT);
                        }}
                        style={{padding:8}}
                      >
                        <Text style={{color:'#0096FF', fontSize:18}}>👁</Text>
                      </TouchableOpacity>
                      <TouchableOpacity
                        onPress={() => {
                          rvs.send('file_request' as any, {
                            serverPath: f.path,
                            requestId: 'single-' + Date.now(),
                          });
                          ToastAndroid.show('Download läuft…', ToastAndroid.SHORT);
                        }}
                        style={{padding:8}}
                      >
                        <Text style={{color:'#34C759', fontSize:18}}>⬇</Text>
                      </TouchableOpacity>
                      <TouchableOpacity
                        onPress={() => {
                          // path-relativ-zu-uploads = nur der Dateiname,
                          // weil der File-Manager-Bereich flach ist
                          setVersionsOpen({name: f.name, path: f.name});
                          setVersionsList([]);
                          setVersionsError('');
                          setVersionsLoading(true);
                          rvs.send('file_version_list_request' as any, { path: f.name });
                        }}
                        style={{padding:8}}
                      >
                        <Text style={{color:'#0096FF', fontSize:18}}>🕒</Text>
                      </TouchableOpacity>
                      <TouchableOpacity
                        onPress={() => {
                          Alert.alert(
                            'Datei löschen?',
                            `"${f.name}"\n\nIn allen Chat-Bubbles wird sie als gelöscht markiert.`,
                            [
                              { text: 'Abbrechen', style: 'cancel' },
                              { text: 'Löschen', style: 'destructive', onPress: () => {
                                rvs.send('file_delete_request' as any, { path: f.path });
                                ToastAndroid.show('Lösch-Befehl gesendet…', ToastAndroid.SHORT);
                              }},
                            ],
                          );
                        }}
                        style={{padding:8}}
                      >
                        <Text style={{color:'#FF6B6B', fontSize:18}}>🗑</Text>
                      </TouchableOpacity>
                    </TouchableOpacity>
                  );
                })}
              </ScrollView>
            </>
          );
        })()}
      </View>
    </Modal>

    {/* Versions-Modal — Historie pro Datei (auto-commit-git im diagnostic) */}
    <Modal
      visible={versionsOpen !== null}
      transparent
      animationType="fade"
      onRequestClose={() => setVersionsOpen(null)}
    >
      <TouchableOpacity
        style={{flex:1, backgroundColor:'rgba(0,0,0,0.75)', justifyContent:'center', alignItems:'center'}}
        activeOpacity={1}
        onPress={() => setVersionsOpen(null)}
      >
        <TouchableOpacity
          activeOpacity={1}
          onPress={() => {}}
          style={{backgroundColor:'#0D0D1A', borderWidth:1, borderColor:'#1E1E2E', borderRadius:8, width:'90%', maxHeight:'80%'}}
        >
          <View style={{padding:12, borderBottomWidth:1, borderBottomColor:'#1E1E2E', flexDirection:'row', alignItems:'center'}}>
            <Text style={{color:'#E0E0F0', fontSize:13, fontWeight:'bold', flex:1}} numberOfLines={1}>
              Versionen — {versionsOpen?.name || ''}
            </Text>
            <TouchableOpacity onPress={() => setVersionsOpen(null)} style={{padding:6}}>
              <Text style={{color:'#888', fontSize:14}}>✕</Text>
            </TouchableOpacity>
          </View>
          <ScrollView style={{maxHeight:'85%'}} contentContainerStyle={{padding:8}}>
            {versionsLoading && (
              <Text style={{color:'#888', textAlign:'center', padding:20}}>Lade...</Text>
            )}
            {!!versionsError && (
              <Text style={{color:'#FF6B6B', padding:20}}>{versionsError}</Text>
            )}
            {!versionsLoading && !versionsError && versionsList.length === 0 && (
              <Text style={{color:'#888', textAlign:'center', padding:20}}>
                Noch keine Versions-Historie (Datei kommt erst nach dem nächsten Auto-Commit in den Index).
              </Text>
            )}
            {versionsList.map(v => (
              <View key={v.hash} style={{padding:10, borderBottomWidth:1, borderBottomColor:'#1E1E2E', flexDirection:'row', alignItems:'center', gap:8}}>
                <View style={{flex:1}}>
                  <View style={{flexDirection:'row', alignItems:'center', gap:6}}>
                    {v.isCurrent && (
                      <View style={{backgroundColor:'#34C75922', paddingHorizontal:6, paddingVertical:1, borderRadius:3}}>
                        <Text style={{color:'#34C759', fontSize:9}}>AKTIV</Text>
                      </View>
                    )}
                    <Text style={{color:'#0096FF', fontSize:11, fontFamily:'monospace'}}>
                      {v.hash.slice(0,7)}
                    </Text>
                    <Text style={{color:'#888', fontSize:11, flex:1}} numberOfLines={1}>
                      {v.subject || ''}
                    </Text>
                  </View>
                  <Text style={{color:'#555570', fontSize:10, marginTop:2}}>
                    {new Date(v.ts).toLocaleString('de-DE')}
                  </Text>
                </View>
                <TouchableOpacity
                  onPress={() => {
                    if (!versionsOpen) return;
                    const reqId = 'verdl_' + Date.now() + '_' + Math.floor(Math.random()*100000);
                    versionDlPending.current = reqId;
                    rvs.send('file_version_download_request' as any, {
                      path: versionsOpen.path,
                      hash: v.hash,
                      requestId: reqId,
                    });
                    ToastAndroid.show('Download läuft…', ToastAndroid.SHORT);
                  }}
                  style={{paddingVertical:4, paddingHorizontal:10, borderRadius:6, backgroundColor:'#0096FF22'}}
                >
                  <Text style={{color:'#0096FF', fontSize:11}}>⬇</Text>
                </TouchableOpacity>
                {!v.isCurrent && (
                  <TouchableOpacity
                    onPress={() => {
                      if (!versionsOpen) return;
                      Alert.alert(
                        'Version aktiv setzen?',
                        `Hash ${v.hash.slice(0,7)} wird als neue aktive Version gespeichert.\n\nDie aktuelle Version bleibt in der Historie und kann später ebenfalls wiederhergestellt werden.`,
                        [
                          { text: 'Abbrechen', style: 'cancel' },
                          { text: 'Restore', onPress: () => {
                            rvs.send('file_version_restore_request' as any, {
                              path: versionsOpen.path,
                              hash: v.hash,
                            });
                            ToastAndroid.show('Restore läuft…', ToastAndroid.SHORT);
                          }},
                        ],
                      );
                    }}
                    style={{paddingVertical:4, paddingHorizontal:10, borderRadius:6, backgroundColor:'#0096FF'}}
                  >
                    <Text style={{color:'#fff', fontSize:11}}>⟲</Text>
                  </TouchableOpacity>
                )}
              </View>
            ))}
          </ScrollView>
        </TouchableOpacity>
      </TouchableOpacity>
    </Modal>
    <ScrollView
      style={styles.container}
      contentContainerStyle={styles.content}
      nestedScrollEnabled={true}
      // Wenn eine Section eine eigene voll-hoch-scrollende Sub-Liste hat
      // (Memory, Trigger), den outer Scroll deaktivieren — Android-nested-
      // scrolling laesst sonst nur in eine Richtung scrollen.
      scrollEnabled={currentSection !== 'memory' && currentSection !== 'triggers' && currentSection !== 'skills' && currentSection !== 'oauth' && currentSection !== 'projects'}
    >

      {currentSection === null && (
        <>
          {SETTINGS_SECTIONS.map(s => (
            <TouchableOpacity
              key={s.id}
              style={styles.menuItem}
              onPress={() => setCurrentSection(s.id)}
            >
              <Text style={styles.menuItemIcon}>{s.icon}</Text>
              <View style={styles.menuItemTextWrap}>
                <Text style={styles.menuItemLabel}>{s.label}</Text>
                <Text style={styles.menuItemDesc}>{s.desc}</Text>
              </View>
              <Text style={styles.menuItemChevron}>›</Text>
            </TouchableOpacity>
          ))}
        </>
      )}

      {currentSection !== null && (
        <TouchableOpacity
          style={styles.subScreenHeader}
          onPress={() => setCurrentSection(null)}
        >
          <Text style={styles.subScreenBack}>‹</Text>
          <Text style={styles.subScreenTitle}>
            {SETTINGS_SECTIONS.find(s => s.id === currentSection)?.label || ''}
          </Text>
        </TouchableOpacity>
      )}

      {/* === Verbindung === */}
      {currentSection === 'connection' && (<>
      <Text style={styles.sectionTitle}>Verbindung</Text>
      <View style={styles.card}>
        {/* Status-Anzeige */}
        <View style={styles.statusRow}>
          <View style={[styles.statusDot, { backgroundColor: connectionDotColor }]} />
          <Text style={styles.statusLabel}>{connectionLabel}</Text>
          {connectionState === 'connected' && (
            <TouchableOpacity style={styles.disconnectButton} onPress={disconnectRVS}>
              <Text style={styles.disconnectText}>Trennen</Text>
            </TouchableOpacity>
          )}
        </View>

        {/* QR-Scanner */}
        <TouchableOpacity style={styles.qrButton} onPress={openQRScanner}>
          <Text style={styles.qrIcon}>{'\uD83D\uDCF1'}</Text>
          <Text style={styles.qrText}>QR-Code scannen (Pairing)</Text>
        </TouchableOpacity>

        {/* Manuelle Eingabe */}
        <Text style={styles.inputLabel}>Host</Text>
        <TextInput
          style={styles.input}
          value={manualHost}
          onChangeText={setManualHost}
          placeholder="z.B. aria.example.com"
          placeholderTextColor="#555570"
          autoCapitalize="none"
        />

        <Text style={styles.inputLabel}>Port</Text>
        <TextInput
          style={styles.input}
          value={manualPort}
          onChangeText={setManualPort}
          placeholder="8765"
          placeholderTextColor="#555570"
          keyboardType="numeric"
        />

        <Text style={styles.inputLabel}>Token</Text>
        <TextInput
          style={styles.input}
          value={manualToken}
          onChangeText={setManualToken}
          placeholder="Verbindungs-Token"
          placeholderTextColor="#555570"
          autoCapitalize="none"
          secureTextEntry
        />

        <TouchableOpacity style={styles.connectButton} onPress={connectManually}>
          <Text style={styles.connectButtonText}>Verbinden</Text>
        </TouchableOpacity>
      </View>

      {/* === Verbindungslog === */}
      <Text style={styles.sectionTitle}>Verbindungslog</Text>
      <View style={styles.card}>
        <ScrollView
          style={styles.connLogScroll}
          nestedScrollEnabled={true}
          ref={(ref) => {
            // Auto-Scroll nach unten bei neuen Eintraegen
            if (ref && connLog.length > 0) {
              setTimeout(() => ref.scrollToEnd({ animated: false }), 50);
            }
          }}
        >
          {connLog.length > 0 ? (
            connLog.slice(-50).map((entry, idx) => (
              <View key={`cl_${idx}`} style={styles.logEntry}>
                <Text style={styles.logTime}>{formatTime(entry.timestamp)}</Text>
                <Text
                  style={[
                    styles.logMessage,
                    entry.level === 'error' && styles.logError,
                    entry.level === 'warn' && styles.logWarn,
                  ]}
                  numberOfLines={3}
                >
                  {entry.message}
                </Text>
              </View>
            ))
          ) : (
            <Text style={styles.emptyLog}>Noch keine Verbindungsversuche</Text>
          )}
        </ScrollView>
        <TouchableOpacity
          style={styles.clearButton}
          onPress={() => setConnLog([])}
        >
          <Text style={styles.clearButtonText}>Log l{'\u00F6'}schen</Text>
        </TouchableOpacity>
      </View>
      </>)}

      {/* === Modus === */}
      {currentSection === 'general' && (<>
      <Text style={styles.sectionTitle}>Betriebsmodus</Text>
      <View style={styles.card}>
        <ModeSelector currentModeId={currentMode} onModeChange={handleModeChange} />
      </View>

      {/* === GPS === */}
      <Text style={styles.sectionTitle}>Standort</Text>
      <View style={styles.card}>
        <View style={styles.toggleRow}>
          <View style={styles.toggleInfo}>
            <Text style={styles.toggleLabel}>GPS-Position mitsenden</Text>
            <Text style={styles.toggleHint}>
              Position (lat/lon) wird mit jeder Nachricht an ARIA mitgeschickt.
              Sie sieht's nur intern und nutzt es bei standortbezogenen Fragen
              ("wo bin ich?", "Wetter hier?"), erwaehnt es sonst nicht.
              Im Chat-Verlauf bleibt die Bubble unveraendert — nur ARIAs
              Antwort kann darauf eingehen.
            </Text>
          </View>
          <Switch
            value={gpsEnabled}
            onValueChange={handleGPSToggle}
            trackColor={{ false: '#2A2A3E', true: '#0096FF' }}
            thumbColor={gpsEnabled ? '#FFFFFF' : '#666680'}
          />
        </View>

        {/* GPS-Tracking (kontinuierlich) — fuer near()-Watcher */}
        <View style={[styles.toggleRow, {marginTop: 12, borderTopWidth: 1, borderTopColor: '#1E1E2E', paddingTop: 12}]}>
          <View style={styles.toggleInfo}>
            <Text style={styles.toggleLabel}>GPS-Tracking (kontinuierlich)</Text>
            <Text style={styles.toggleHint}>
              Sendet alle ~15s deine Position an ARIA (wenn du dich {'>'}30m bewegt
              hast). Nur noetig fuer GPS-basierte Trigger wie Blitzer-Warner
              (near()-Conditions). ARIA kann das auch selbst an-/abschalten wenn
              sie einen GPS-Watcher anlegt. Akku-Verbrauch erhoeht — bei langer
              Fahrt einplanen.
            </Text>
          </View>
          <Switch
            value={gpsTracking}
            onValueChange={(v) => {
              if (v) gpsTrackingService.start('manuell').catch(() => {});
              else gpsTrackingService.stop('manuell');
            }}
            trackColor={{ false: '#2A2A3E', true: '#FF9500' }}
            thumbColor={gpsTracking ? '#FFFFFF' : '#666680'}
          />
        </View>

        {/* Background-GPS opt-in — Default AUS. Braucht ACCESS_BACKGROUND_LOCATION
            (User muss in Android-Settings 'Immer erlauben' aktivieren). */}
        <View style={[styles.toggleRow, {marginTop: 12, borderTopWidth: 1, borderTopColor: '#1E1E2E', paddingTop: 12}]}>
          <View style={styles.toggleInfo}>
            <Text style={styles.toggleLabel}>GPS auch im Hintergrund</Text>
            <Text style={styles.toggleHint}>
              Damit ARIA auch unterwegs deine aktuelle Position kennt wenn die
              App im Hintergrund ist (Auto, Handy-Tasche). Standard: aus.
              {'\n\n'}
              Android verlangt fuer Background-GPS, dass du in den
              System-Einstellungen unter Standort "Immer erlauben" auswaehlst.
              Beim Aktivieren wird Android-Settings geoeffnet falls noetig.
              {'\n\n'}
              Akku-Verbrauch: ~3-5% mehr pro Tag durch dauerhaftes Polling.
            </Text>
          </View>
          <Switch
            value={bgGpsEnabled}
            onValueChange={async (v) => {
              if (v) {
                const ok = await ensureBackgroundLocationPermission();
                if (!ok) {
                  // User muss in Android-Settings auf "Immer erlauben" — Toggle
                  // bleibt aus bis er zurueckkommt und nochmal tippt.
                  return;
                }
                await setBackgroundGpsEnabled(true);
                setBgGpsEnabled(true);
                // Wenn Tracking bereits laeuft: neu starten damit der
                // Foreground-Service jetzt mit location-Slot kommt
                if (gpsTrackingService.isActive()) {
                  gpsTrackingService.stop('bg-toggle');
                  gpsTrackingService.start('bg-aktiviert').catch(() => {});
                }
                ToastAndroid.show('Background-GPS aktiviert', ToastAndroid.SHORT);
              } else {
                await setBackgroundGpsEnabled(false);
                setBgGpsEnabled(false);
                ToastAndroid.show('Background-GPS aus — nur noch Foreground', ToastAndroid.SHORT);
              }
            }}
            trackColor={{ false: '#2A2A3E', true: '#FF3B30' }}
            thumbColor={bgGpsEnabled ? '#FFFFFF' : '#666680'}
          />
        </View>
      </View>

      {/* === Bubble-Anzeige === */}
      <Text style={styles.sectionTitle}>Chat-Bubbles</Text>
      <View style={styles.card}>
        <View style={styles.toggleRow}>
          <View style={styles.toggleInfo}>
            <Text style={styles.toggleLabel}>System-Hints in Bubbles anzeigen</Text>
            <Text style={styles.toggleHint}>
              Wenn aktiviert: GPS-Position, Barge-In-Hinweise und andere
              System-Praefixe in eckigen Klammern bleiben in der User-Bubble
              sichtbar (Debug). Standardmaessig versteckt — Brain bekommt sie
              trotzdem, sie sind nur fuer dich nicht relevant.
            </Text>
          </View>
          <Switch
            value={showSystemHints}
            onValueChange={handleShowSystemHintsToggle}
            trackColor={{ false: '#2A2A3E', true: '#0096FF' }}
            thumbColor={showSystemHints ? '#FFFFFF' : '#666680'}
          />
        </View>
      </View>

      {/* === Hintergrund-Modus === */}
      <Text style={styles.sectionTitle}>Hintergrund-Modus</Text>
      <View style={styles.card}>
        <View style={styles.toggleRow}>
          <View style={styles.toggleInfo}>
            <Text style={styles.toggleLabel}>App im Hintergrund weiterlaufen</Text>
            <Text style={styles.toggleHint}>
              Haelt die Verbindung zu ARIA auch dann offen wenn die App minimiert
              ist. Sonst pausiert Android nach ~30s die JS-Engine und Timer-/Watcher-
              Trigger kommen nicht durch. Notification "ARIA aktiv" bleibt sichtbar
              waehrend der Modus laeuft (das ist Android-Vorschrift fuer Foreground-
              Services). Akku-Mehrverbrauch minimal solange ARIA nichts tut.
              {'\n\n'}
              Wenn nach Akku-Optimierung Trigger trotzdem nicht durchkommen:
              Android-Einstellungen → Apps → ARIA Cockpit → Akku → "Uneingeschraenkt"
              setzen.
            </Text>
          </View>
          <Switch
            value={backgroundMode}
            onValueChange={handleBackgroundModeToggle}
            trackColor={{ false: '#2A2A3E', true: '#0096FF' }}
            thumbColor={backgroundMode ? '#FFFFFF' : '#666680'}
          />
        </View>
      </View>
      </>)}

      {/* === Spracheingabe (geraetelokal) === */}
      {currentSection === 'voice_input' && (<>
      <Text style={styles.sectionTitle}>Spracheingabe</Text>
      <View style={styles.card}>
        <Text style={styles.toggleLabel}>Stille-Toleranz</Text>
        <Text style={styles.toggleHint}>
          Wie lange du eine Sprechpause machen darfst, bevor die Aufnahme
          automatisch beendet und gesendet wird. Hoeher = mehr Zeit zum
          Nachdenken; niedriger = schnelleres Senden.
          Default: {VAD_SILENCE_DEFAULT_SEC.toFixed(1)}s.
        </Text>
        <View style={styles.prerollRow}>
          <TouchableOpacity
            style={styles.prerollButton}
            onPress={() => {
              const next = Math.max(VAD_SILENCE_MIN_SEC, Math.round((vadSilenceSec - 0.5) * 10) / 10);
              setVadSilenceSec(next);
              AsyncStorage.setItem(VAD_SILENCE_STORAGE_KEY, String(next));
            }}
            disabled={vadSilenceSec <= VAD_SILENCE_MIN_SEC}
          >
            <Text style={styles.prerollButtonText}>−0.5</Text>
          </TouchableOpacity>
          <Text style={styles.prerollValue}>{vadSilenceSec.toFixed(1)} s</Text>
          <TouchableOpacity
            style={styles.prerollButton}
            onPress={() => {
              const next = Math.min(VAD_SILENCE_MAX_SEC, Math.round((vadSilenceSec + 0.5) * 10) / 10);
              setVadSilenceSec(next);
              AsyncStorage.setItem(VAD_SILENCE_STORAGE_KEY, String(next));
            }}
            disabled={vadSilenceSec >= VAD_SILENCE_MAX_SEC}
          >
            <Text style={styles.prerollButtonText}>+0.5</Text>
          </TouchableOpacity>
        </View>

        <Text style={[styles.toggleLabel, {marginTop: 24}]}>Konversations-Fenster</Text>
        <Text style={styles.toggleHint}>
          Im Gespraechsmodus (Ohr-Button): nach ARIA's Antwort hast du so lange
          Zeit, weiter zu sprechen, bevor die Konversation automatisch beendet wird.
          Sprichst du nichts → Mikrofon zu.
          Default: {CONV_WINDOW_DEFAULT_SEC.toFixed(1)}s.
        </Text>
        <View style={styles.prerollRow}>
          <TouchableOpacity
            style={styles.prerollButton}
            onPress={() => {
              const next = Math.max(CONV_WINDOW_MIN_SEC, Math.round((convWindowSec - 1) * 10) / 10);
              setConvWindowSec(next);
              AsyncStorage.setItem(CONV_WINDOW_STORAGE_KEY, String(next));
            }}
            disabled={convWindowSec <= CONV_WINDOW_MIN_SEC}
          >
            <Text style={styles.prerollButtonText}>−1</Text>
          </TouchableOpacity>
          <Text style={styles.prerollValue}>{convWindowSec.toFixed(0)} s</Text>
          <TouchableOpacity
            style={styles.prerollButton}
            onPress={() => {
              const next = Math.min(CONV_WINDOW_MAX_SEC, Math.round((convWindowSec + 1) * 10) / 10);
              setConvWindowSec(next);
              AsyncStorage.setItem(CONV_WINDOW_STORAGE_KEY, String(next));
            }}
            disabled={convWindowSec >= CONV_WINDOW_MAX_SEC}
          >
            <Text style={styles.prerollButtonText}>+1</Text>
          </TouchableOpacity>
        </View>

        <Text style={[styles.toggleLabel, {marginTop: 24}]}>Maximale Aufnahmedauer</Text>
        <Text style={styles.toggleHint}>
          Notbremse: nach so vielen Minuten wird die Aufnahme automatisch beendet,
          auch wenn keine Stille erkannt wurde. Nuetzlich fuer lange Erklaerungen
          oder Diktate. Default: {Math.round(MAX_RECORDING_DEFAULT_SEC / 60)} Min, max {Math.round(MAX_RECORDING_MAX_SEC / 60)} Min.
        </Text>
        <View style={styles.prerollRow}>
          <TouchableOpacity
            style={styles.prerollButton}
            onPress={() => {
              const next = Math.max(MAX_RECORDING_MIN_SEC, maxRecordingSec - 60);
              setMaxRecordingSec(next);
              AsyncStorage.setItem(MAX_RECORDING_STORAGE_KEY, String(next));
            }}
            disabled={maxRecordingSec <= MAX_RECORDING_MIN_SEC}
          >
            <Text style={styles.prerollButtonText}>−1m</Text>
          </TouchableOpacity>
          <Text style={styles.prerollValue}>{Math.round(maxRecordingSec / 60)} min</Text>
          <TouchableOpacity
            style={styles.prerollButton}
            onPress={() => {
              const next = Math.min(MAX_RECORDING_MAX_SEC, maxRecordingSec + 60);
              setMaxRecordingSec(next);
              AsyncStorage.setItem(MAX_RECORDING_STORAGE_KEY, String(next));
            }}
            disabled={maxRecordingSec >= MAX_RECORDING_MAX_SEC}
          >
            <Text style={styles.prerollButtonText}>+1m</Text>
          </TouchableOpacity>
        </View>

        <View style={{flexDirection: 'row', alignItems: 'center', marginTop: 24, gap: 8}}>
          <Text style={styles.toggleLabel}>Stille-Pegel (dB)</Text>
          <TouchableOpacity onPress={() => setShowVadInfo(true)} style={styles.infoBtn}>
            <Text style={styles.infoBtnText}>i</Text>
          </TouchableOpacity>
        </View>
        <Text style={styles.toggleHint}>
          Welcher Mikro-Pegel als "Stille" gilt. Standard: automatisch (Baseline aus
          den ersten 500ms). Manuell setzen wenn Auto nicht zuverlaessig greift.
        </Text>
        <View style={styles.prerollRow}>
          <TouchableOpacity
            style={styles.prerollButton}
            onPress={() => {
              const next = vadSilenceDb == null
                ? VAD_SILENCE_DB_DEFAULT - 1
                : Math.max(VAD_SILENCE_DB_MIN, vadSilenceDb - 1);
              setVadSilenceDb(next);
              AsyncStorage.setItem(VAD_SILENCE_DB_OVERRIDE_KEY, String(next));
            }}
          >
            <Text style={styles.prerollButtonText}>−1</Text>
          </TouchableOpacity>
          <Text style={styles.prerollValue}>
            {vadSilenceDb == null ? 'auto' : `${vadSilenceDb} dB`}
          </Text>
          <TouchableOpacity
            style={styles.prerollButton}
            onPress={() => {
              const next = vadSilenceDb == null
                ? VAD_SILENCE_DB_DEFAULT + 1
                : Math.min(VAD_SILENCE_DB_MAX, vadSilenceDb + 1);
              setVadSilenceDb(next);
              AsyncStorage.setItem(VAD_SILENCE_DB_OVERRIDE_KEY, String(next));
            }}
          >
            <Text style={styles.prerollButtonText}>+1</Text>
          </TouchableOpacity>
        </View>
        {vadSilenceDb != null && (
          <TouchableOpacity
            onPress={() => {
              setVadSilenceDb(null);
              AsyncStorage.removeItem(VAD_SILENCE_DB_OVERRIDE_KEY);
            }}
            style={{alignSelf: 'center', marginTop: 8, paddingVertical: 6, paddingHorizontal: 12}}
          >
            <Text style={{color: '#0096FF', fontSize: 13}}>↻ Auf automatisch zuruecksetzen</Text>
          </TouchableOpacity>
        )}
      </View>

      <Modal
        visible={showVadInfo}
        transparent
        animationType="fade"
        onRequestClose={() => setShowVadInfo(false)}
      >
        <View style={styles.modalOverlay}>
          <View style={styles.modalCard}>
            <Text style={styles.modalTitle}>Stille-Pegel (dB)</Text>
            <Text style={styles.modalText}>
              Lautstaerken werden in Dezibel (dB) gemessen — negative Werte, je
              hoeher (naeher an 0), desto lauter.{'\n\n'}
              <Text style={{fontWeight: '700'}}>Standard:</Text> automatisch.
              Die App misst die ersten 500ms Hintergrundpegel und setzt die
              Stille-Schwelle auf Baseline + 6 dB. Funktioniert in den meisten
              Umgebungen.{'\n\n'}
              <Text style={{fontWeight: '700'}}>Manuell:</Text> Pegel unter dem
              eingestellten Wert gilt als "Stille" → Aufnahme stoppt.{'\n\n'}
              <Text style={{fontWeight: '700'}}>Faustregel:</Text>{'\n'}
              • <Text style={{color: '#FFD60A'}}>−45 dB</Text> sehr empfindlich (stoppt schnell, auch bei Atmen){'\n'}
              • <Text style={{color: '#34C759'}}>−38 dB</Text> ausgewogen (typische Bueroumgebung){'\n'}
              • <Text style={{color: '#FF6B6B'}}>−25 dB</Text> unempfindlich (laute Umgebung, nur klare Sprache zaehlt){'\n\n'}
              <Text style={{color: '#8888AA'}}>Niedrigere Zahl (z.B. −50) = sensibler.{'\n'}
              Hoehere Zahl (z.B. −20) = robuster gegen Hintergrundlaerm,
              braucht aber lautere Sprache.</Text>
            </Text>
            <TouchableOpacity
              style={[styles.connectButton, {marginTop: 16, alignSelf: 'stretch'}]}
              onPress={() => setShowVadInfo(false)}
            >
              <Text style={styles.connectButtonText}>OK</Text>
            </TouchableOpacity>
          </View>
        </View>
      </Modal>
      </>)}

      {/* === Wake-Word (komplett on-device, openWakeWord) === */}
      {currentSection === 'wake_word' && (<>
      <Text style={styles.sectionTitle}>Wake-Word</Text>
      <View style={styles.card}>
        <Text style={styles.toggleHint}>
          Lokale Erkennung via openWakeWord (ONNX, on-device). Kein API-Key,
          kein Cloud-Roundtrip — Audio verlaesst das Geraet nicht. Wenn das Ohr
          aktiv ist, hoerst du normal mit; sagst du das Wake-Word, startet eine
          Konversation mit ARIA.
        </Text>

        <Text style={[styles.toggleLabel, {marginTop: 16}]}>Wake-Word</Text>
        <Text style={styles.toggleHint}>
          Eigene Wake-Words via openWakeWord-Notebook trainierbar (gratis).
          Custom-Upload ueber Diagnostic kommt in einer spaeteren Version.
        </Text>
        <View style={{flexDirection: 'row', flexWrap: 'wrap', gap: 6, marginTop: 8}}>
          {WAKE_KEYWORDS.map(kw => (
            <TouchableOpacity
              key={kw}
              style={[
                styles.keywordChip,
                wakeKeyword === kw && styles.keywordChipActive,
              ]}
              onPress={() => setWakeKeyword(kw)}
            >
              <Text style={[
                styles.keywordChipText,
                wakeKeyword === kw && styles.keywordChipTextActive,
              ]}>
                {KEYWORD_LABELS[kw]}
              </Text>
            </TouchableOpacity>
          ))}
        </View>

        <View style={{flexDirection: 'row', gap: 8, marginTop: 16, alignItems: 'center'}}>
          <TouchableOpacity
            style={[styles.connectButton, {flex: 1}]}
            onPress={async () => {
              setWakeStatus('Initialisiere...');
              try {
                const ok = await wakeWordService.configure(wakeKeyword);
                setWakeStatus(ok ? `✅ "${KEYWORD_LABELS[wakeKeyword as keyof typeof KEYWORD_LABELS]}" bereit` : '❌ Init-Fehler — Logs pruefen');
              } catch (err: any) {
                setWakeStatus('❌ ' + String(err?.message || err).slice(0, 80));
              }
              setTimeout(() => setWakeStatus(''), 5000);
            }}
          >
            <Text style={styles.connectButtonText}>Speichern + Aktivieren</Text>
          </TouchableOpacity>
        </View>
        {!!wakeStatus && (
          <Text style={{marginTop: 8, fontSize: 12, color: '#8888AA'}}>{wakeStatus}</Text>
        )}

        <View style={[styles.toggleRow, {marginTop: 20, borderTopWidth: 1, borderTopColor: '#1E1E2E', paddingTop: 16}]}>
          <View style={styles.toggleInfo}>
            <Text style={styles.toggleLabel}>Bereit-Sound abspielen</Text>
            <Text style={styles.toggleHint}>
              Kurzer Ding-Dong wenn das Mikro nach Wake-Word offen ist —
              akustische Bestaetigung dass du jetzt sprechen darfst.
            </Text>
          </View>
          <Switch
            value={wakeReadySound}
            onValueChange={async (val) => {
              setWakeReadySound(val);
              await setWakeReadySoundEnabled(val);
              if (val) {
                // Direkt eine Vorschau abspielen damit der User weiss wie's klingt.
                // playWakeReadySound checked das gerade gesetzte Flag — wenn val=true,
                // wird abgespielt; bei false bleibt es still.
                setTimeout(() => playWakeReadySound().catch(() => {}), 150);
              }
            }}
            trackColor={{ false: '#2A2A3E', true: '#0096FF' }}
            thumbColor={wakeReadySound ? '#FFFFFF' : '#666680'}
          />
        </View>
      </View>
      </>)}

      {/* === Voice-ID Enrollment (Sprecher-Erkennung) === */}
      {currentSection === 'voice_id' && (<>
      <Text style={styles.sectionTitle}>Stimme einrichten</Text>
      <VoiceIdEnrollment />
      </>)}

      {/* === Sprachausgabe (geraetelokal) === */}
      {currentSection === 'voice_output' && (<>
      <Text style={styles.sectionTitle}>Sprachausgabe</Text>
      <View style={styles.card}>
        <View style={styles.toggleRow}>
          <View style={styles.toggleInfo}>
            <Text style={styles.toggleLabel}>Sprachausgabe auf diesem Geraet</Text>
            <Text style={styles.toggleHint}>
              Nur lokal — andere Geraete sind unabhaengig.
              Wenn aus, erscheint im Chat auch kein Mund-Button.
            </Text>
          </View>
          <Switch
            value={ttsEnabled}
            onValueChange={(val) => {
              setTtsEnabled(val);
              AsyncStorage.setItem('aria_tts_enabled', String(val));
            }}
            trackColor={{ false: '#2A2A3E', true: '#0096FF' }}
            thumbColor={ttsEnabled ? '#FFFFFF' : '#666680'}
          />
        </View>

        {ttsEnabled && (
          <View style={{marginTop: 20}}>
            <Text style={styles.toggleLabel}>Puffer vor Wiedergabestart</Text>
            <Text style={styles.toggleHint}>
              Wie viel Audio gesammelt wird bevor die Wiedergabe startet.
              Hoeher = robuster gegen Render-Pausen, aber mehr Startverzoegerung.
              Default: {TTS_PREROLL_DEFAULT_SEC.toFixed(1)}s.
            </Text>
            <View style={styles.prerollRow}>
              <TouchableOpacity
                style={styles.prerollButton}
                onPress={() => {
                  const next = Math.max(TTS_PREROLL_MIN_SEC, Math.round((ttsPrerollSec - 0.5) * 10) / 10);
                  setTtsPrerollSec(next);
                  AsyncStorage.setItem(TTS_PREROLL_STORAGE_KEY, String(next));
                }}
                disabled={ttsPrerollSec <= TTS_PREROLL_MIN_SEC}
              >
                <Text style={styles.prerollButtonText}>−0.5</Text>
              </TouchableOpacity>
              <Text style={styles.prerollValue}>{ttsPrerollSec.toFixed(1)} s</Text>
              <TouchableOpacity
                style={styles.prerollButton}
                onPress={() => {
                  const next = Math.min(TTS_PREROLL_MAX_SEC, Math.round((ttsPrerollSec + 0.5) * 10) / 10);
                  setTtsPrerollSec(next);
                  AsyncStorage.setItem(TTS_PREROLL_STORAGE_KEY, String(next));
                }}
                disabled={ttsPrerollSec >= TTS_PREROLL_MAX_SEC}
              >
                <Text style={styles.prerollButtonText}>+0.5</Text>
              </TouchableOpacity>
            </View>

            <Text style={[styles.toggleLabel, {marginTop: 24}]}>Sprechgeschwindigkeit</Text>
            <Text style={styles.toggleHint}>
              Wie schnell ARIA spricht. 1.0 = Normal. Niedriger = langsamer, hoeher = schneller.
              Wird an F5-TTS als speed-Param uebergeben und pro Geraet gespeichert.
              Default: {TTS_SPEED_DEFAULT.toFixed(1)}x.
            </Text>
            <View style={styles.prerollRow}>
              <TouchableOpacity
                style={styles.prerollButton}
                onPress={() => {
                  const next = Math.max(TTS_SPEED_MIN, Math.round((ttsSpeed - 0.1) * 10) / 10);
                  setTtsSpeed(next);
                  AsyncStorage.setItem(TTS_SPEED_STORAGE_KEY, String(next));
                }}
                disabled={ttsSpeed <= TTS_SPEED_MIN}
              >
                <Text style={styles.prerollButtonText}>−0.1</Text>
              </TouchableOpacity>
              <Text style={styles.prerollValue}>{ttsSpeed.toFixed(1)} x</Text>
              <TouchableOpacity
                style={styles.prerollButton}
                onPress={() => {
                  const next = Math.min(TTS_SPEED_MAX, Math.round((ttsSpeed + 0.1) * 10) / 10);
                  setTtsSpeed(next);
                  AsyncStorage.setItem(TTS_SPEED_STORAGE_KEY, String(next));
                }}
                disabled={ttsSpeed >= TTS_SPEED_MAX}
              >
                <Text style={styles.prerollButtonText}>+0.1</Text>
              </TouchableOpacity>
            </View>
          </View>
        )}

        {ttsEnabled && (
          <View style={{marginTop: 20}}>
            <Text style={styles.toggleLabel}>Stimme (geraetelokal)</Text>
            <Text style={styles.toggleHint}>
              Eine geklonte Stimme auswaehlen. F5-TTS braucht zwingend eine Referenz —
              ohne Auswahl gilt die in Diagnostic gewaehlte globale Stimme.
            </Text>

            {availableVoices.length === 0 ? (
              <Text style={[styles.toggleHint, {marginTop: 8, textAlign: 'center'}]}>
                Keine geklonten Stimmen vorhanden — unten "Eigene Stimme aufnehmen".
              </Text>
            ) : (
              availableVoices.map(v => (
                <View key={v.name} style={[styles.voiceRow, xttsVoice === v.name && styles.voiceRowActive]}>
                  <TouchableOpacity
                    style={{flex: 1}}
                    onPress={() => selectVoice(v.name)}
                  >
                    <Text style={[styles.voiceRowName, xttsVoice === v.name && styles.voiceRowNameActive]}>
                      {v.name}
                    </Text>
                    <Text style={styles.voiceRowMeta}>{(v.size / 1024).toFixed(0)} KB</Text>
                  </TouchableOpacity>
                  {loadingVoice === v.name && (
                    <ActivityIndicator size="small" color="#0096FF" style={{marginRight: 8}} />
                  )}
                  {xttsVoice === v.name && loadingVoice !== v.name && <Text style={styles.voiceRowCheck}>{'\u2713'}</Text>}
                  <TouchableOpacity onPress={() => deleteVoice(v.name)} style={styles.voiceRowDelete}>
                    <Text style={styles.voiceRowDeleteIcon}>X</Text>
                  </TouchableOpacity>
                </View>
              ))
            )}

            <View style={{flexDirection: 'row', gap: 8, marginTop: 12}}>
              <TouchableOpacity
                style={[styles.connectButton, {flex: 1}]}
                onPress={() => setVoiceCloneVisible(true)}
              >
                <Text style={styles.connectButtonText}>{'\uD83C\uDFA4'} Eigene Stimme aufnehmen</Text>
              </TouchableOpacity>
              <TouchableOpacity
                style={[styles.clearButton, {flex: 0.4, marginTop: 0}]}
                onPress={() => rvs.send('xtts_list_voices' as any, {})}
              >
                <Text style={styles.clearButtonText}>Aktualisieren</Text>
              </TouchableOpacity>
            </View>
          </View>
        )}
      </View>

      </>)}

      {/* === Speicher === */}
      {currentSection === 'storage' && (<>
      <Text style={styles.sectionTitle}>Anhang-Speicher</Text>
      <View style={styles.card}>
        <View style={styles.toggleRow}>
          <View style={styles.toggleInfo}>
            <Text style={styles.toggleLabel}>Auto-Download</Text>
            <Text style={styles.toggleHint}>
              Fehlende Anhaenge beim App-Start automatisch vom Server laden
            </Text>
          </View>
          <Switch
            value={autoDownload}
            onValueChange={(val) => {
              setAutoDownload(val);
              AsyncStorage.setItem('aria_auto_download', String(val));
            }}
            trackColor={{ false: '#2A2A3E', true: '#0096FF' }}
            thumbColor={autoDownload ? '#FFFFFF' : '#666680'}
          />
        </View>

        <View style={{height: 16}} />
        <Text style={styles.toggleLabel}>Lokaler Speicherort</Text>
        <Text style={styles.toggleHint}>
          Hier werden Bilder und Dateien aus dem Chat gespeichert.
          {autoDownload ? ' Fehlende Dateien werden automatisch nachgeladen.' : ' Fehlende Dateien koennen per Tippen geladen werden.'}
        </Text>

        {editingPath ? (
          <View style={{marginTop: 10}}>
            <TextInput
              style={styles.input}
              value={tempPath}
              onChangeText={setTempPath}
              placeholder="z.B. /storage/emulated/0/ARIA/attachments"
              placeholderTextColor="#555570"
              autoCapitalize="none"
            />
            <View style={{flexDirection: 'row', gap: 8}}>
              <TouchableOpacity
                style={[styles.connectButton, {flex: 1}]}
                onPress={() => saveStoragePath(tempPath)}
              >
                <Text style={styles.connectButtonText}>Speichern</Text>
              </TouchableOpacity>
              <TouchableOpacity
                style={[styles.clearButton, {flex: 1, marginTop: 0}]}
                onPress={() => setEditingPath(false)}
              >
                <Text style={styles.clearButtonText}>Abbrechen</Text>
              </TouchableOpacity>
            </View>
          </View>
        ) : (
          <View style={{marginTop: 10}}>
            <Text style={styles.storagePathText} numberOfLines={2}>{storagePath}</Text>
            <Text style={styles.storageSizeText}>{storageSize}</Text>
            <View style={{flexDirection: 'row', gap: 8, marginTop: 8}}>
              <TouchableOpacity
                style={[styles.clearButton, {flex: 1, marginTop: 0}]}
                onPress={showPathPicker}
              >
                <Text style={styles.clearButtonText}>Pfad aendern</Text>
              </TouchableOpacity>
              <TouchableOpacity
                style={[styles.clearButton, {flex: 1, marginTop: 0, backgroundColor: 'rgba(255,59,48,0.15)'}]}
                onPress={clearStorageCache}
              >
                <Text style={[styles.clearButtonText, {color: '#FF3B30'}]}>Cache leeren</Text>
              </TouchableOpacity>
            </View>
          </View>
        )}
      </View>

      {/* === Update-Cache === */}
      <Text style={[styles.sectionTitle, {marginTop: 16}]}>Update-Cache</Text>
      <View style={styles.card}>
        <Text style={styles.toggleHint}>
          Heruntergeladene APK-Dateien fuer App-Updates. Werden automatisch
          beim App-Start und vor jedem neuen Download geloescht — der Button
          ist fuer den Notfall (z.B. wenn ein Download haengen geblieben ist).
        </Text>
        <Text style={[styles.storageSizeText, {marginTop: 8}]}>
          {apkCacheInfo === null ? '...' :
           apkCacheInfo.count === 0 ? 'leer' :
           `${apkCacheInfo.count} APK${apkCacheInfo.count === 1 ? '' : 's'} · ${apkCacheInfo.totalMB.toFixed(1)}MB`}
        </Text>
        <TouchableOpacity
          style={[styles.clearButton, {marginTop: 8, backgroundColor: 'rgba(255,59,48,0.15)'}]}
          onPress={async () => {
            const res = await updateService.cleanupOldApks();
            ToastAndroid.show(
              res.removed === 0
                ? 'Update-Cache war schon leer'
                : `${res.removed} APK${res.removed === 1 ? '' : 's'} geloescht (${res.freedMB.toFixed(1)}MB frei)`,
              ToastAndroid.SHORT,
            );
            const info = await updateService.getApkCacheSize();
            setApkCacheInfo(info);
          }}
        >
          <Text style={[styles.clearButtonText, {color: '#FF3B30'}]}>Update-Cache leeren</Text>
        </TouchableOpacity>
      </View>

      {/* === TTS-Cache === */}
      <Text style={[styles.sectionTitle, {marginTop: 16}]}>TTS-Cache</Text>
      <View style={styles.card}>
        <Text style={styles.toggleHint}>
          Gespeicherte Sprachausgaben (WAV pro Antwort) — werden fuer den
          Play-Button und Auto-Resume nach Anrufen genutzt. Loeschen
          unterbricht keine laufende Wiedergabe, alte Antworten lassen sich
          danach nur nicht mehr abspielen.
        </Text>
        <Text style={[styles.storageSizeText, {marginTop: 8}]}>
          {ttsCacheInfo === null ? '...' :
           ttsCacheInfo.count === 0 ? 'leer' :
           `${ttsCacheInfo.count} WAV${ttsCacheInfo.count === 1 ? '' : 's'} · ${ttsCacheInfo.totalMB.toFixed(1)}MB`}
        </Text>
        <TouchableOpacity
          style={[styles.clearButton, {marginTop: 8, backgroundColor: 'rgba(255,59,48,0.15)'}]}
          onPress={async () => {
            const res = await audioService.clearTtsCache();
            ToastAndroid.show(
              res.removed === 0
                ? 'TTS-Cache war schon leer'
                : `${res.removed} WAV${res.removed === 1 ? '' : 's'} geloescht (${res.freedMB.toFixed(1)}MB frei)`,
              ToastAndroid.SHORT,
            );
            const info = await audioService.getTtsCacheSize();
            setTtsCacheInfo(info);
          }}
        >
          <Text style={[styles.clearButtonText, {color: '#FF3B30'}]}>TTS-Cache leeren</Text>
        </TouchableOpacity>
      </View>

      {/* === Reparatur === */}
      <Text style={[styles.sectionTitle, {marginTop: 16}]}>Reparatur</Text>
      <View style={styles.card}>
        <Text style={styles.toggleHint}>
          Container gezielt neu starten — wenn die Voice-Bridge, das Gehirn
          oder die Vector-DB haengt. Restart dauert wenige Sekunden,
          laufende Anfragen gehen verloren.
        </Text>
        {[
          { name: 'aria-bridge', label: '🚨 aria-bridge neu (Voice + RVS)' },
          { name: 'aria-brain',  label: '🚨 aria-brain neu (Agent + Memory)' },
          { name: 'aria-qdrant', label: '🚨 aria-qdrant neu (Vector-DB)' },
        ].map(c => (
          <TouchableOpacity
            key={c.name}
            style={[styles.clearButton, {marginTop: 8, backgroundColor: 'rgba(255,59,48,0.10)'}]}
            onPress={() => {
              Alert.alert(
                `${c.name} neu starten?`,
                'Restart in wenigen Sekunden. Laufende Anfragen gehen verloren.',
                [
                  { text: 'Abbrechen', style: 'cancel' },
                  { text: 'Neu starten', style: 'destructive', onPress: () => {
                    rvs.send('container_restart' as any, { name: c.name });
                    ToastAndroid.show(`${c.name} wird neu gestartet…`, ToastAndroid.LONG);
                  }},
                ],
              );
            }}
          >
            <Text style={[styles.clearButtonText, {color: '#FF3B30'}]}>{c.label}</Text>
          </TouchableOpacity>
        ))}
      </View>

      </>)}

      {/* === Datei-Manager === */}
      {currentSection === 'files' && (<>
      <Text style={styles.sectionTitle}>Dateien</Text>
      <View style={styles.card}>
        <Text style={styles.toggleHint}>
          Alle Dateien aus <Text style={{fontFamily:'monospace'}}>/shared/uploads/</Text>
          — was ARIA generiert hat und was du hochgeladen hast.
          Beim Löschen wird die Bubble in App + Diagnostic als gelöscht markiert.
        </Text>
        <TouchableOpacity
          style={[styles.clearButton, {marginTop: 8, backgroundColor: 'rgba(0,150,255,0.15)'}]}
          onPress={() => {
            setFileManagerError('');
            setFileManagerLoading(true);
            setFileManagerOpen(true);
            rvs.send('file_list_request' as any, {});
          }}
        >
          <Text style={[styles.clearButtonText, {color: '#0096FF'}]}>{'📂 Datei-Manager öffnen'}</Text>
        </TouchableOpacity>
      </View>
      </>)}

      {/* === Projekte === */}
      {currentSection === 'projects' && (<>
      <Text style={styles.sectionTitle}>Projekte</Text>
      <Text style={{color: '#8888AA', fontSize: 12, marginBottom: 8, paddingHorizontal: 4}}>
        Thread-Bündel im Hauptchat. Tap auf ein Projekt → aktivieren, alle weiteren Nachrichten gehen
        dort rein. Long-Press → bearbeiten. „+ Neu" oder zu ARIA: „lass uns ein Projekt anlegen".
      </Text>
      <View style={{height: winDims.height - 220, marginBottom: 8}}>
        <ProjectsBrowser />
      </View>
      </>)}

      {/* === Gedaechtnis === */}
      {currentSection === 'memory' && (<>
      <Text style={styles.sectionTitle}>Gedächtnis</Text>
      <Text style={{color: '#8888AA', fontSize: 12, marginBottom: 8, paddingHorizontal: 4}}>
        Alle Memory-Einträge aus ARIAs Vector-DB. Tippen zum Bearbeiten — mit Anhängen, pinned-Status,
        Tags. Neue Einträge anlegen via "+ Neu".
      </Text>
      <View style={{height: winDims.height - 220, marginBottom: 8}}>
        <MemoryBrowser />
      </View>
      </>)}

      {/* === Trigger === */}
      {currentSection === 'triggers' && (<>
      <Text style={styles.sectionTitle}>Trigger</Text>
      <Text style={{color: '#8888AA', fontSize: 12, marginBottom: 8, paddingHorizontal: 4}}>
        Timer (einmalige Erinnerung) + Watcher (recurring mit Condition, z.B. GPS-near). Toggle aktiv/inaktiv,
        Tap zum Bearbeiten, "+ Neu" zum Anlegen.
      </Text>
      <View style={{height: winDims.height - 220, marginBottom: 8}}>
        <TriggerBrowser />
      </View>
      </>)}

      {/* === Skills === */}
      {currentSection === 'skills' && (<>
      <Text style={styles.sectionTitle}>Skills</Text>
      <Text style={{color: '#8888AA', fontSize: 12, marginBottom: 8, paddingHorizontal: 4}}>
        Wiederverwendbare Python-Skills die ARIA selbst gebaut hat oder die Du importiert hast.
        Toggle aktiv/inaktiv, Tap fuer Details + Run + Logs. Code-Aenderungen macht ARIA via
        ihr skill_update Brain-Tool — hier nur Manifest-Felder + Run + Cleanup.
      </Text>
      <View style={{height: winDims.height - 220, marginBottom: 8}}>
        <SkillBrowser />
      </View>
      </>)}

      {/* === OAuth-Apps === */}
      {currentSection === 'oauth' && (<>
      <Text style={styles.sectionTitle}>OAuth-Apps</Text>
      <Text style={{color: '#8888AA', fontSize: 12, marginBottom: 8, paddingHorizontal: 4}}>
        Verbinde ARIA mit externen Services (Spotify, Dropbox, Discord, ...).
        Trag client_id + client_secret aus dem Developer-Dashboard des Anbieters ein,
        dann "Autorisieren ↗" tippen. Custom-Services kannst Du via "+ Custom" anlegen —
        ARIA kann das auch selbst per Chat ("verbinde mich mit X").
      </Text>
      <View style={{height: winDims.height - 220, marginBottom: 8}}>
        <OAuthBrowser />
      </View>
      </>)}

      {/* === Logs === */}
      {currentSection === 'protocol' && (<>
      <Text style={styles.sectionTitle}>Protokoll</Text>

      {/* Verbose-Logging-Toggle */}
      <View style={styles.card}>
        <View style={styles.toggleRow}>
          <Text style={styles.toggleLabel}>Verbose Logging</Text>
          <Switch
            value={verboseLogging}
            onValueChange={(v) => {
              setVerboseLogging(v);
              setVerboseLoggingState(v);
            }}
            trackColor={{ false: '#3A3A52', true: '#0096FF' }}
            thumbColor={verboseLogging ? '#FFFFFF' : '#666680'}
          />
        </View>
        <Text style={styles.toggleHint}>
          Wenn aus: console.log wird global stummgeschaltet (Speicher schonen).
          Warnungen und Fehler bleiben immer aktiv. Bei Bedarf einschalten zum
          Debuggen via adb logcat.
        </Text>

        {/* Debug-Logs an Bridge: scharf nur wenn aktiv gebraucht */}
        <View style={[styles.toggleRow, {marginTop: 12, borderTopWidth: 1, borderTopColor: '#1E1E2E', paddingTop: 12}]}>
          <Text style={styles.toggleLabel}>Debug-Logs an Bridge</Text>
          <Switch
            value={debugLogsToBridge}
            onValueChange={(v) => {
              setDebugLogsToBridge(v);
              setDebugLogsToBridgeState(v);
            }}
            trackColor={{ false: '#3A3A52', true: '#FF9500' }}
            thumbColor={debugLogsToBridge ? '#FFFFFF' : '#666680'}
          />
        </View>
        <Text style={styles.toggleHint}>
          Schickt detaillierte Diagnose-Logs (Wake-Word-Pipeline, Audio-Focus,
          Background-Service) per RVS an die Bridge — abrufbar via
          `curl /api/app-log?lines=N` ohne ADB. Default AUS damit kein
          unnoetiger Traffic + Disk-Schreiben. Crash-Reports (Errors) gehen
          IMMER, dieser Toggle betrifft nur Info-Logs.
        </Text>
      </View>

      <View style={styles.card}>
        {/* Tab-Umschalter */}
        <View style={styles.tabRow}>
          <TouchableOpacity
            style={[styles.tab, logTab === 'live' && styles.tabActive]}
            onPress={() => setLogTab('live')}
          >
            <Text style={[styles.tabText, logTab === 'live' && styles.tabTextActive]}>
              Live Logs
            </Text>
          </TouchableOpacity>
          <TouchableOpacity
            style={[styles.tab, logTab === 'events' && styles.tabActive]}
            onPress={() => setLogTab('events')}
          >
            <Text style={[styles.tabText, logTab === 'events' && styles.tabTextActive]}>
              Events
            </Text>
          </TouchableOpacity>
        </View>

        {/* Log-Inhalt */}
        <View style={styles.logContainer}>
          {logTab === 'live' ? (
            logs.length > 0 ? (
              logs.slice(-50).map(log => (
                <View key={log.id} style={styles.logEntry}>
                  <Text style={styles.logTime}>{formatTime(log.timestamp)}</Text>
                  <Text
                    style={[
                      styles.logSource,
                      { color: SOURCE_COLORS[log.source] || SOURCE_COLORS.default },
                    ]}
                  >
                    [{log.source}]
                  </Text>
                  <Text
                    style={[
                      styles.logMessage,
                      log.level === 'error' && styles.logError,
                      log.level === 'warn' && styles.logWarn,
                    ]}
                    numberOfLines={2}
                  >
                    {log.message}
                  </Text>
                </View>
              ))
            ) : (
              <Text style={styles.emptyLog}>Noch keine Logs empfangen</Text>
            )
          ) : (
            events.length > 0 ? (
              events.slice(-30).map(event => (
                <View key={event.id} style={styles.eventEntry}>
                  <Text style={styles.eventTime}>{formatTime(event.timestamp)}</Text>
                  <Text style={styles.eventTitle}>{event.title}</Text>
                  <Text style={styles.eventDescription}>{event.description}</Text>
                </View>
              ))
            ) : (
              <Text style={styles.emptyLog}>Noch keine Events empfangen</Text>
            )
          )}
        </View>

        {/* Log-Aktionen */}
        <TouchableOpacity
          style={styles.clearButton}
          onPress={() => {
            if (logTab === 'live') setLogs([]);
            else setEvents([]);
          }}
        >
          <Text style={styles.clearButtonText}>Protokoll l\u00F6schen</Text>
        </TouchableOpacity>
      </View>
      </>)}

      {/* === About === */}
      {currentSection === 'about' && (<>
      <Text style={styles.sectionTitle}>{'\u00DC'}ber</Text>
      <View style={styles.card}>
        <Text style={styles.aboutTitle}>ARIA Cockpit</Text>
        <Text style={styles.aboutVersion}>Version {require('../../package.json').version}</Text>
        <Text style={styles.aboutInfo}>
          ARIA {'\u2014'} Autonomous Reasoning & Intelligence Assistant.{'\n'}
          Stefans Kommandozentrale.{'\n'}
          Gebaut mit React Native + TypeScript.
        </Text>
        <TouchableOpacity
          style={[styles.connectButton, {marginTop: 12}]}
          onPress={() => {
            const updateService = require('../services/updater').default;
            updateService.checkForUpdate();
            Alert.alert('Update-Check', 'Pruefe auf neue Version...');
          }}
        >
          <Text style={styles.connectButtonText}>Auf Updates pr{'\u00FC'}fen</Text>
        </TouchableOpacity>
      </View>
      </>)}

      {/* Platz am Ende */}
      <View style={styles.bottomSpacer} />
    </ScrollView>
    </>
  );
};

// --- Styles ---

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#0D0D1A',
  },
  content: {
    padding: 16,
  },
  sectionTitle: {
    color: '#8888AA',
    fontSize: 13,
    fontWeight: '700',
    textTransform: 'uppercase',
    letterSpacing: 1,
    marginTop: 20,
    marginBottom: 8,
    marginLeft: 4,
  },
  menuItem: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#1E1E2E',
    borderRadius: 10,
    paddingVertical: 14,
    paddingHorizontal: 14,
    marginBottom: 8,
  },
  menuItemIcon: {
    fontSize: 22,
    marginRight: 14,
    width: 28,
    textAlign: 'center',
  },
  menuItemTextWrap: {
    flex: 1,
  },
  menuItemLabel: {
    color: '#FFFFFF',
    fontSize: 16,
    fontWeight: '600',
  },
  menuItemDesc: {
    color: '#8888AA',
    fontSize: 12,
    marginTop: 2,
  },
  menuItemChevron: {
    color: '#8888AA',
    fontSize: 24,
    fontWeight: '300',
    marginLeft: 8,
  },
  subScreenHeader: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingVertical: 8,
    marginBottom: 8,
  },
  subScreenBack: {
    color: '#0096FF',
    fontSize: 32,
    fontWeight: '300',
    marginRight: 12,
    lineHeight: 36,
  },
  subScreenTitle: {
    color: '#FFFFFF',
    fontSize: 20,
    fontWeight: '700',
  },
  card: {
    backgroundColor: '#12122A',
    borderRadius: 14,
    padding: 16,
    borderWidth: 1,
    borderColor: '#1E1E2E',
  },

  // Verbindungsstatus
  statusRow: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 16,
  },
  statusDot: {
    width: 10,
    height: 10,
    borderRadius: 5,
    marginRight: 10,
  },
  statusLabel: {
    color: '#FFFFFF',
    fontSize: 16,
    fontWeight: '600',
    flex: 1,
  },
  disconnectButton: {
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 6,
    backgroundColor: 'rgba(255, 59, 48, 0.2)',
  },
  disconnectText: {
    color: '#FF3B30',
    fontSize: 13,
    fontWeight: '600',
  },

  // QR-Button
  qrButton: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#1E1E2E',
    borderRadius: 10,
    padding: 14,
    marginBottom: 16,
  },
  qrIcon: {
    fontSize: 22,
    marginRight: 10,
  },
  qrText: {
    color: '#FFFFFF',
    fontSize: 15,
    fontWeight: '500',
  },

  // Eingabefelder
  inputLabel: {
    color: '#8888AA',
    fontSize: 12,
    marginBottom: 4,
    marginLeft: 2,
  },
  input: {
    backgroundColor: '#1E1E2E',
    borderRadius: 8,
    paddingHorizontal: 14,
    paddingVertical: 10,
    color: '#FFFFFF',
    fontSize: 15,
    marginBottom: 12,
    borderWidth: 1,
    borderColor: '#2A2A3E',
  },
  connectButton: {
    backgroundColor: '#0096FF',
    borderRadius: 10,
    padding: 14,
    alignItems: 'center',
    marginTop: 4,
  },
  connectButtonText: {
    color: '#FFFFFF',
    fontSize: 16,
    fontWeight: '700',
  },

  // Toggle
  toggleRow: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  toggleInfo: {
    flex: 1,
    marginRight: 12,
  },
  toggleLabel: {
    color: '#FFFFFF',
    fontSize: 15,
    fontWeight: '500',
  },
  toggleHint: {
    color: '#666680',
    fontSize: 12,
    marginTop: 2,
  },

  // XTTS Voice List
  voiceRow: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#1E1E2E',
    borderRadius: 8,
    padding: 10,
    marginTop: 6,
    borderWidth: 1,
    borderColor: 'transparent',
  },
  voiceRowActive: {
    borderColor: '#0096FF',
    backgroundColor: '#0D1A2E',
  },
  voiceRowName: {
    color: '#CCCCDD',
    fontSize: 14,
    fontWeight: '500',
  },
  voiceRowNameActive: {
    color: '#FFFFFF',
  },
  voiceRowMeta: {
    color: '#666680',
    fontSize: 11,
    marginTop: 2,
  },
  voiceRowCheck: {
    color: '#34C759',
    fontSize: 16,
    fontWeight: '700',
    marginHorizontal: 6,
  },
  voiceRowDelete: {
    width: 28,
    height: 28,
    borderRadius: 14,
    backgroundColor: 'rgba(255,59,48,0.2)',
    alignItems: 'center',
    justifyContent: 'center',
    marginLeft: 4,
  },
  voiceRowDeleteIcon: {
    color: '#FF3B30',
    fontSize: 12,
    fontWeight: '700',
  },

  // Stimmen
  voiceBtn: {
    flex: 1,
    padding: 12,
    borderRadius: 10,
    backgroundColor: '#1E1E2E',
    alignItems: 'center',
    borderWidth: 2,
    borderColor: 'transparent',
  },
  voiceBtnActive: {
    borderColor: '#0096FF',
    backgroundColor: '#0D1A2E',
  },
  voiceBtnIcon: {
    fontSize: 28,
    marginBottom: 4,
  },
  voiceBtnText: {
    color: '#8888AA',
    fontSize: 14,
    fontWeight: '600',
  },
  voiceBtnTextActive: {
    color: '#FFFFFF',
  },
  voiceBtnHint: {
    color: '#555570',
    fontSize: 11,
    marginTop: 2,
  },

  // Speicher
  storagePathText: {
    color: '#0096FF',
    fontSize: 12,
    fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
  },
  storageSizeText: {
    color: '#8888AA',
    fontSize: 12,
    marginTop: 4,
  },

  // Logs
  tabRow: {
    flexDirection: 'row',
    marginBottom: 12,
    borderRadius: 8,
    backgroundColor: '#1E1E2E',
    overflow: 'hidden',
  },
  tab: {
    flex: 1,
    paddingVertical: 10,
    alignItems: 'center',
  },
  tabActive: {
    backgroundColor: '#0096FF',
  },
  tabText: {
    color: '#666680',
    fontSize: 13,
    fontWeight: '600',
  },
  tabTextActive: {
    color: '#FFFFFF',
  },
  connLogScroll: {
    maxHeight: 200,
    backgroundColor: '#0A0A18',
    borderRadius: 8,
    padding: 10,
  },
  logContainer: {
    maxHeight: 300,
    backgroundColor: '#0A0A18',
    borderRadius: 8,
    padding: 10,
  },
  logEntry: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    marginBottom: 4,
  },
  logTime: {
    color: '#555570',
    fontSize: 11,
    fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
    marginRight: 6,
  },
  logSource: {
    fontSize: 11,
    fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
    fontWeight: '700',
    marginRight: 6,
  },
  logMessage: {
    color: '#CCCCDD',
    fontSize: 11,
    fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
    flex: 1,
  },
  logError: {
    color: '#FF3B30',
  },
  logWarn: {
    color: '#FFD60A',
  },
  emptyLog: {
    color: '#555570',
    fontSize: 13,
    textAlign: 'center',
    padding: 20,
  },
  eventEntry: {
    marginBottom: 10,
    paddingBottom: 10,
    borderBottomWidth: 1,
    borderBottomColor: '#1E1E2E',
  },
  eventTime: {
    color: '#555570',
    fontSize: 11,
  },
  eventTitle: {
    color: '#FFFFFF',
    fontSize: 14,
    fontWeight: '600',
    marginTop: 2,
  },
  eventDescription: {
    color: '#8888AA',
    fontSize: 13,
    marginTop: 2,
  },
  clearButton: {
    marginTop: 10,
    padding: 10,
    alignItems: 'center',
    borderRadius: 8,
    backgroundColor: '#1E1E2E',
  },
  clearButtonText: {
    color: '#666680',
    fontSize: 13,
    fontWeight: '500',
  },

  // About
  aboutTitle: {
    color: '#FFFFFF',
    fontSize: 18,
    fontWeight: '700',
  },
  aboutVersion: {
    color: '#0096FF',
    fontSize: 13,
    marginTop: 2,
  },
  aboutInfo: {
    color: '#666680',
    fontSize: 13,
    marginTop: 8,
    lineHeight: 20,
  },

  bottomSpacer: {
    height: 40,
  },

  prerollRow: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    marginTop: 12,
    gap: 16,
  },
  prerollButton: {
    backgroundColor: '#2A2A3E',
    paddingHorizontal: 18,
    paddingVertical: 10,
    borderRadius: 8,
    minWidth: 72,
    alignItems: 'center',
  },
  prerollButtonText: {
    color: '#FFFFFF',
    fontSize: 16,
    fontWeight: '600',
  },
  prerollValue: {
    color: '#FFFFFF',
    fontSize: 20,
    fontWeight: '700',
    minWidth: 80,
    textAlign: 'center',
  },

  infoBtn: {
    width: 22,
    height: 22,
    borderRadius: 11,
    borderWidth: 1.5,
    borderColor: '#0096FF',
    alignItems: 'center',
    justifyContent: 'center',
  },
  infoBtnText: {
    color: '#0096FF',
    fontSize: 13,
    fontWeight: '700',
    fontStyle: 'italic',
    lineHeight: 16,
  },
  modalOverlay: {
    flex: 1,
    backgroundColor: 'rgba(0,0,0,0.7)',
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  modalCard: {
    backgroundColor: '#1E1E2E',
    borderRadius: 14,
    padding: 20,
    maxWidth: 460,
    width: '100%',
  },
  modalTitle: {
    color: '#FFFFFF',
    fontSize: 18,
    fontWeight: '700',
    marginBottom: 12,
  },
  modalText: {
    color: '#E0E0F0',
    fontSize: 14,
    lineHeight: 20,
  },

  keywordChip: {
    backgroundColor: '#1E1E2E',
    borderWidth: 1,
    borderColor: '#2A2A3E',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 14,
  },
  keywordChipActive: {
    backgroundColor: '#0096FF',
    borderColor: '#0096FF',
  },
  keywordChipText: {
    color: '#8888AA',
    fontSize: 13,
    fontWeight: '500',
  },
  keywordChipTextActive: {
    color: '#FFFFFF',
    fontWeight: '700',
  },
});

export default SettingsScreen;
