import { useEffect, useMemo, useRef, useState } from 'react';
import {
  ArrowRight,
  ArrowUp,
  CaretDown,
  CaretRight,
  Cube,
  ContactlessPayment,
  FileAudio,
  FloppyDisk,
  Folder,
  FolderOpen,
  GearSix,
  House,
  PencilSimple,
  Plus,
  Trash,
  UploadSimple,
  Wrench,
} from '@phosphor-icons/react';
import { Avatar } from 'primereact/avatar';
import { Badge } from 'primereact/badge';
import { Button } from 'primereact/button';
import { Card } from 'primereact/card';
import { Divider } from 'primereact/divider';
import { Dropdown } from 'primereact/dropdown';
import { FileUpload } from 'primereact/fileupload';
import { InputText } from 'primereact/inputtext';
import { Message } from 'primereact/message';
import { MeterGroup } from 'primereact/metergroup';
import { Messages } from 'primereact/messages';
import { ProgressBar } from 'primereact/progressbar';
import { Slider } from 'primereact/slider';
import { Stepper } from 'primereact/stepper';
import { StepperPanel } from 'primereact/stepperpanel';
import { Tag } from 'primereact/tag';
import { Toast } from 'primereact/toast';
import {
  assignTag,
  claimTag,
  createMediaFolder,
  deleteMedia,
  deleteTag,
  getBoxes,
  getBoxLocalTags,
  getBoxTags,
  getBoxStorage,
  getMediaTree,
  getStatus,
  getTagBlocks,
  getTags,
  markTagWritten,
  moveMedia,
  pairBox,
  pullTagFromBox,
  renameMedia,
  sendCommand,
  setBoxAlias,
  setTagAlias,
  setTagBlock,
  setTagMedia,
  unassignTag,
  unpairBox,
  uploadMedia,
} from './api.js';

const BOX_POLL_MS = 1500;
const STATUS_POLL_MS = 1000;
const STORAGE_POLL_MS = 5000;
const ONLINE_THRESHOLD_SEC = 60;

const NAV_ITEMS = [
  { id: 'dashboard', label: 'Dashboard', icon: House },
  { id: 'boxes', label: 'Boxen', icon: Cube },
  { id: 'media', label: 'Medien', icon: FolderOpen },
  { id: 'tags', label: 'Tags', icon: ContactlessPayment },
  { id: 'settings', label: 'Einstellungen', icon: GearSix },
];

function formatTime(ts) {
  if (!ts) return '-';
  return new Date(ts * 1000).toLocaleString();
}

function getAvailability(lastSeen) {
  if (!lastSeen) {
    return { label: 'Offline', severity: 'danger' };
  }
  const nowSec = Date.now() / 1000;
  const isOnline = nowSec - lastSeen <= ONLINE_THRESHOLD_SEC;
  return { label: isOnline ? 'Online' : 'Offline', severity: isOnline ? 'success' : 'danger' };
}

function parseCapabilities(raw) {
  if (!raw) return '-';
  try {
    const data = JSON.parse(raw);
    return Object.entries(data)
      .map(([key, value]) => `${key}:${value ? '1' : '0'}`)
      .join(' ');
  } catch (error) {
    return '-';
  }
}

function formatSize(bytes) {
  if (bytes === 0) return '0 B';
  if (bytes === null || bytes === undefined || Number.isNaN(bytes)) return '-';
  const units = ['B', 'KB', 'MB', 'GB'];
  let size = bytes;
  let idx = 0;
  while (size >= 1024 && idx < units.length - 1) {
    size /= 1024;
    idx += 1;
  }
  return `${size.toFixed(idx === 0 ? 0 : 1)} ${units[idx]}`;
}

function formatDuration(seconds) {
  if (seconds === null || seconds === undefined || Number.isNaN(seconds)) return '-';
  const total = Math.max(0, Math.floor(seconds));
  const mins = Math.floor(total / 60);
  const secs = total % 60;
  return `${mins}:${secs.toString().padStart(2, '0')}`;
}

function formatClock(seconds) {
  if (seconds === null || seconds === undefined || Number.isNaN(seconds)) return '-';
  const total = Math.max(0, Math.floor(seconds));
  const hrs = Math.floor(total / 3600);
  const mins = Math.floor((total % 3600) / 60);
  const secs = total % 60;
  if (hrs > 0) {
    return `${hrs}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  }
  return `${mins}:${secs.toString().padStart(2, '0')}`;
}

function capacityScale(percent) {
  const p = Math.max(0, Math.min(100, percent || 0));
  if (p === 0) return 1;
  return 100 / p;
}

function getNfcKey(nfc) {
  if (!nfc) return '';
  const at = nfc.at || '';
  const uid = nfc.uid || '';
  const hardware = nfc.hardwareUid || nfc.hardware_uid || '';
  return `${at}:${uid}:${hardware}`;
}

function generateTagId() {
  const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789';
  let generated = '';
  for (let i = 0; i < 10; i += 1) {
    generated += alphabet[Math.floor(Math.random() * alphabet.length)];
  }
  return generated;
}

function generateHardwareUid() {
  const bytes = [];
  for (let i = 0; i < 7; i += 1) {
    bytes.push(Math.floor(Math.random() * 256));
  }
  return bytes
    .map((value) => value.toString(16).padStart(2, '0').toUpperCase())
    .join(':');
}

function isValidTagUid(uid) {
  return /^(?:[a-z0-9]{10}|TAG_[a-z0-9]{8})$/.test(uid);
}

function SectionHeader({ title, subtitle, actions }) {
  return (
    <div className="section-header">
      <div>
        <h1>{title}</h1>
        <p>{subtitle}</p>
      </div>
      <div className="section-actions">{actions}</div>
    </div>
  );
}

function StatGrid({ items }) {
  return (
    <div className="stat-grid">
      {items.map((card) => (
        <Card key={card.label} className="stat-card">
          <p className="stat-label">{card.label}</p>
          <div className="stat-value">{card.value}</div>
          <span className="stat-helper">{card.helper}</span>
        </Card>
      ))}
    </div>
  );
}

export default function App() {
  const [activeSection, setActiveSection] = useState('dashboard');
  const [searchValue, setSearchValue] = useState('');
  const [boxes, setBoxes] = useState([]);
  const [boxStorage, setBoxStorage] = useState({});
  const [selectedId, setSelectedId] = useState('');
  const [status, setStatus] = useState(null);
  const [error, setError] = useState('');
  const [nfcUid, setNfcUid] = useState('UID_1');
  const [mediaTree, setMediaTree] = useState(null);
  const [mediaError, setMediaError] = useState('');
  const [currentPath, setCurrentPath] = useState('');
  const [selectedPaths, setSelectedPaths] = useState([]);
  const [newFolderName, setNewFolderName] = useState('');
  const [renameName, setRenameName] = useState('');
  const [moveTarget, setMoveTarget] = useState('');
  const [activeModal, setActiveModal] = useState('');
  const [tagDeleteTarget, setTagDeleteTarget] = useState('');
  const [expandedFolders, setExpandedFolders] = useState(() => new Set(['__root__']));
  const [sidebarQuery, setSidebarQuery] = useState('');
  const [uploadAfterCreate, setUploadAfterCreate] = useState(false);
  const [pendingUploadFiles, setPendingUploadFiles] = useState([]);
  const [uploadProgress, setUploadProgress] = useState(0);
  const [uploadInProgress, setUploadInProgress] = useState(false);
  const [activeUploadLabel, setActiveUploadLabel] = useState('');
  const [uploadSize, setUploadSize] = useState(0);
  const [uploadNameError, setUploadNameError] = useState('');
  const transferTimerRef = useRef(null);
  const explorerRef = useRef(null);
  const modalRef = useRef(null);
  const toastRef = useRef(null);
  const fileUploadRef = useRef(null);
  const dragSelectRef = useRef(false);
  const lastDblClickRef = useRef(0);
  const lastAnchorRef = useRef('');
  const explorerInitRef = useRef(false);
  const expandedInitRef = useRef(false);
  const volumeRef = useRef(null);
  const [tags, setTags] = useState([]);
  const [boxTags, setBoxTags] = useState([]);
  const [blockedByBox, setBlockedByBox] = useState({});
  const [dbTagMedia, setDbTagMedia] = useState({});
  const [localBoxTags, setLocalBoxTags] = useState([]);
  const [localBoxError, setLocalBoxError] = useState('');
  const [importTargetFolder, setImportTargetFolder] = useState('');
  const [importTargetUid, setImportTargetUid] = useState('');
  const [scanTagUid, setScanTagUid] = useState('');
  const [tagUidMode, setTagUidMode] = useState('keep');
  const [tagUidError, setTagUidError] = useState('');
  const [tagStep, setTagStep] = useState(0);
  const [tagStepOneDone, setTagStepOneDone] = useState(false);
  const [tagStepTwoDone, setTagStepTwoDone] = useState(false);
  const [tagStepMax, setTagStepMax] = useState(0);
  const tagStepperRef = useRef(null);
  const tagWizardRestoredRef = useRef('');
  const tagUidMsgRef = useRef(null);
  const mediaMsgRef = useRef(null);
  const autoReplaceMsg = 'Ungueltige ID erkannt – wird automatisch ersetzt.';
  const [dismissedNfcKey, setDismissedNfcKey] = useState('');
  const [scanTagLabel, setScanTagLabel] = useState('');
  const [scanMediaPath, setScanMediaPath] = useState('');
  const [reuseTagUid, setReuseTagUid] = useState('');
  const lastNfcKeyRef = useRef('');
  const [tagAliasDrafts, setTagAliasDrafts] = useState({});
  const [boxAliasDrafts, setBoxAliasDrafts] = useState({});
  const [showSessionSheet, setShowSessionSheet] = useState(false);
  const [drawerTab, setDrawerTab] = useState('boxes');
  const [boxDeleteTarget, setBoxDeleteTarget] = useState('');
  const [lastHardwareUid, setLastHardwareUid] = useState({ uid: '', hardwareUid: '' });
  const [simulatedNfc, setSimulatedNfc] = useState(null);
  const [showVolume, setShowVolume] = useState(false);
  const [volumeDraft, setVolumeDraft] = useState(50);
  const [seekPercent, setSeekPercent] = useState(0);
  const [isSeeking, setIsSeeking] = useState(false);

  function addToast(type, message) {
    toastRef.current?.show({
      severity: type,
      summary: type === 'error' ? 'Fehler' : 'Info',
      detail: message,
      life: 4000,
    });
  }

  useEffect(() => {
    let active = true;

    async function refreshBoxes() {
      const response = await getBoxes();
      if (!active) return;
      if (!response.ok) {
        setError(response.data.detail || 'Fehler beim Laden der Boxen.');
        return;
      }
      setBoxes(response.data.boxes || []);
      setError('');
    }

    refreshBoxes();
    const handle = setInterval(refreshBoxes, BOX_POLL_MS);
    return () => {
      active = false;
      clearInterval(handle);
    };
  }, []);

  useEffect(() => {
    let active = true;

    async function refreshStorage() {
      if (!boxes.length) {
        if (active) setBoxStorage({});
        return;
      }
      const results = await Promise.all(
        boxes.map(async (box) => {
          const response = await getBoxStorage(box.box_id);
          if (!response.ok) {
            return { boxId: box.box_id, storage: null };
          }
          return { boxId: box.box_id, storage: response.data.storage || null };
        })
      );
      if (!active) return;
      const next = {};
      results.forEach(({ boxId, storage }) => {
        if (!storage) {
          next[boxId] = null;
          return;
        }
        const total = storage.total_bytes ?? null;
        const free = storage.free_bytes ?? null;
        const used =
          storage.used_bytes ?? (total !== null && free !== null ? total - free : null);
        next[boxId] = { total, free, used };
      });
      setBoxStorage(next);
    }

    refreshStorage();
    const handle = setInterval(refreshStorage, STORAGE_POLL_MS);
    return () => {
      active = false;
      clearInterval(handle);
    };
  }, [boxes]);

  useEffect(() => {
    function handleOutside(event) {
      if (activeModal) return;
      if (!explorerRef.current) return;
      if (modalRef.current && modalRef.current.contains(event.target)) return;
      if (!explorerRef.current.contains(event.target)) {
        setSelectedPaths([]);
        setRenameName('');
      }
    }
    function handleMouseUp() {
      dragSelectRef.current = false;
    }
    window.addEventListener('mousedown', handleOutside);
    window.addEventListener('mouseup', handleMouseUp);
    return () => {
      window.removeEventListener('mousedown', handleOutside);
      window.removeEventListener('mouseup', handleMouseUp);
    };
  }, [activeModal]);

  useEffect(() => {
    if (!selectedId) {
      setStatus(null);
      setMediaTree(null);
      setMediaError('');
      setSelectedPaths([]);
      setNewFolderName('');
      setRenameName('');
      setActiveModal('');
      setBoxTags([]);
      setDbTagMedia({});
      setScanTagUid('');
      setScanTagLabel('');
      setScanMediaPath('');
      setReuseTagUid('');
      setLocalBoxTags([]);
      setLocalBoxError('');
      return;
    }

    let active = true;

    async function refreshStatus() {
      const response = await getStatus(selectedId);
      if (!active) return;
      if (!response.ok) {
        setStatus({ error: response.data.detail || 'Status nicht verfuegbar.' });
        return;
      }
      setStatus(response.data);
    }

    refreshStatus();
    const handle = setInterval(refreshStatus, STATUS_POLL_MS);
    return () => {
      active = false;
      clearInterval(handle);
    };
  }, [selectedId]);

  useEffect(() => {
    let active = true;

    async function refreshMedia() {
      const response = await getMediaTree();
      if (!active) return;
      if (!response.ok) {
        setMediaError(response.data.detail || 'Medien nicht verfuegbar.');
        setMediaTree(null);
        return;
      }
      setMediaTree(response.data);
      setMediaError('');
    }

    refreshMedia();
    return () => {
      active = false;
    };
  }, []);

  function findPathChain(node, target, chain = []) {
    if (!node) return null;
    const currentPath = node.path || '';
    const nextChain = [...chain, currentPath];
    if (currentPath === target) {
      return nextChain;
    }
    if (!Array.isArray(node.children)) return null;
    for (const child of node.children) {
      if (child.type !== 'folder') continue;
      const result = findPathChain(child, target, nextChain);
      if (result) return result;
    }
    return null;
  }

  useEffect(() => {
    if (!mediaTree) return;
    const saved = localStorage.getItem('klangkiste_explorer_path');
    if (saved === null) return;
    const chain = findPathChain(mediaTree, saved);
    if (!chain) return;
    setCurrentPath(saved);
    if (!expandedInitRef.current) {
      const savedExpandedRaw = localStorage.getItem('klangkiste_explorer_expanded');
      let savedExpanded = [];
      if (savedExpandedRaw) {
        try {
          const parsed = JSON.parse(savedExpandedRaw);
          if (Array.isArray(parsed)) {
            savedExpanded = parsed;
          }
        } catch (error) {
          savedExpanded = [];
        }
      }
      const merged = new Set([
        ...savedExpanded,
        ...chain.map((p) => (p ? p : '__root__')),
      ]);
      setExpandedFolders(merged);
      expandedInitRef.current = true;
    }
    explorerInitRef.current = true;
  }, [mediaTree]);

  useEffect(() => {
    if (!explorerInitRef.current) return;
    localStorage.setItem('klangkiste_explorer_path', currentPath || '');
  }, [currentPath]);

  useEffect(() => {
    if (!expandedInitRef.current) return;
    localStorage.setItem(
      'klangkiste_explorer_expanded',
      JSON.stringify(Array.from(expandedFolders))
    );
  }, [expandedFolders]);

  useEffect(() => {
    const lastNfc = status?.last_nfc;
    if (!lastNfc || !lastNfc.uid) {
      return;
    }
    if (!lastNfc.known) {
      const key = `${status?.last_nfc_at ?? ''}:${lastNfc.uid ?? ''}`;
      if (lastNfcKeyRef.current === key) {
        return;
      }
      lastNfcKeyRef.current = key;
      setScanTagLabel('');
      setScanMediaPath('');
      if (isValidTagUid(lastNfc.uid)) {
        setScanTagUid(lastNfc.uid);
        setTagUidMode('keep');
        setTagUidError('');
      } else {
        setScanTagUid(generateTagId());
        setTagUidMode('new');
        setTagUidError(autoReplaceMsg);
      }
      setDismissedNfcKey('');
      setTagStep(0);
      setTagStepOneDone(false);
      setTagStepTwoDone(false);
      setTagStepMax(0);
    }
  }, [status, tags]);

  useEffect(() => {
    tagStepperRef.current?.setActiveStep(tagStep);
  }, [tagStep]);

  useEffect(() => {
    const ref = tagUidMsgRef.current;
    if (!ref) return;
    ref.clear();
    if (tagStep === 0 && tagUidError) {
      const showMessage = () => {
        if (!tagUidMsgRef.current) return false;
        tagUidMsgRef.current.clear();
        tagUidMsgRef.current.show({
          severity: 'warn',
          summary: 'Ungueltige ID',
          detail: tagUidError,
          sticky: true,
          closable: false,
        });
        return true;
      };
      if (!showMessage()) {
        const timer = setTimeout(() => {
          showMessage();
        }, 0);
        return () => clearTimeout(timer);
      }
    }
  }, [tagUidError, tagStep]);

  useEffect(() => {
    const ref = mediaMsgRef.current;
    if (!ref) return;
    ref.clear();
    if (tagStep === 1 && !scanMediaPath) {
      const showMessage = () => {
        if (!mediaMsgRef.current) return false;
        mediaMsgRef.current.clear();
        mediaMsgRef.current.show({
          severity: 'info',
          summary: 'Keine Medienzuordnung',
          detail: 'Ohne Medienauswahl wird der Tag ohne Zuordnung gespeichert.',
          sticky: true,
          closable: false,
        });
        return true;
      };
      if (!showMessage()) {
        const timer = setTimeout(() => {
          showMessage();
        }, 0);
        return () => clearTimeout(timer);
      }
    }
  }, [scanMediaPath, tagStep]);

  useEffect(() => {
    let active = true;

    async function refreshTags() {
      const response = await getTags();
      if (!active) return;
      if (!response.ok) {
        setError(response.data.detail || 'Tags nicht verfuegbar.');
        return;
      }
      setTags(response.data.tags || []);
    }

    refreshTags();
    return () => {
      active = false;
    };
  }, []);

  useEffect(() => {
    if (!selectedId) return;
    let active = true;

    async function refreshBoxTags() {
      const response = await getBoxTags(selectedId);
      if (!active) return;
      if (!response.ok) {
        setError(response.data.detail || 'Box-Tags nicht verfuegbar.');
        return;
      }
      setBoxTags(response.data.tags || []);
    }

    refreshBoxTags();
    return () => {
      active = false;
    };
  }, [selectedId]);

  useEffect(() => {
    if (!selectedId) return;
    let active = true;

    async function refreshLocalTags() {
      const response = await getBoxLocalTags(selectedId);
      if (!active) return;
      if (!response.ok) {
        setLocalBoxError(response.data.detail || 'Lokale Box-Tags nicht verfuegbar.');
        return;
      }
      setLocalBoxTags(response.data.tags || []);
      setLocalBoxError('');
    }

    refreshLocalTags();
    return () => {
      active = false;
    };
  }, [selectedId]);

  useEffect(() => {
    let active = true;

    async function refreshBlocked() {
      if (!boxes.length) {
        setBlockedByBox({});
        return;
      }
      const results = await Promise.all(
        boxes.map(async (box) => {
          const response = await getTagBlocks(box.box_id);
          return { boxId: box.box_id, response };
        })
      );
      if (!active) return;
      const next = {};
      results.forEach(({ boxId, response }) => {
        if (response.ok) {
          next[boxId] = response.data.blocked || [];
        }
      });
      setBlockedByBox(next);
    }

    refreshBlocked();
    return () => {
      active = false;
    };
  }, [boxes]);

  const unpaired = useMemo(
    () => boxes.filter((box) => box.state === 'UNPAIRED'),
    [boxes]
  );
  const paired = useMemo(
    () => boxes.filter((box) => box.state === 'PAIRED'),
    [boxes]
  );

  const mediaTagCounts = useMemo(() => {
    const counts = {};
    tags.forEach((tag) => {
      const mediaPath = tag.media_path || '';
      if (!mediaPath) return;
      counts[mediaPath] = (counts[mediaPath] || 0) + 1;
    });
    return counts;
  }, [tags]);

  const topLevelFolders = useMemo(() => {
    if (!mediaTree || !Array.isArray(mediaTree.children)) return [];
    const folders = mediaTree.children.filter((child) => child.type === 'folder');
    const query = sidebarQuery.trim().toLowerCase();
    if (!query) return folders;
    return folders.filter((folder) => (folder.name || '').toLowerCase().includes(query));
  }, [mediaTree, sidebarQuery]);

  const filteredTree = useMemo(() => {
    if (!mediaTree) return null;
    return {
      ...mediaTree,
      children: topLevelFolders,
    };
  }, [mediaTree, topLevelFolders]);

  const dashboardStats = useMemo(() => {
    const sizeLabel = mediaTree ? formatSize(mediaTree.size) : '-';
    const freeLabel = mediaTree ? formatSize(mediaTree.free_bytes) : '-';
    return [
      {
        label: 'Gepairte Boxen',
        value: paired.length,
        helper: `${unpaired.length} neu`,
      },
      {
        label: 'Tags gesamt',
        value: tags.length,
        helper: `${boxTags.length} auf Box`,
      },
      {
        label: 'Medien gesamt',
        value: sizeLabel,
        helper: `frei ${freeLabel}`,
      },
      {
        label: 'Letzter NFC',
        value: status?.last_nfc?.uid || '-',
        helper: status?.last_nfc_at ? formatTime(status.last_nfc_at) : '-',
      },
    ];
  }, [paired.length, unpaired.length, tags.length, boxTags.length, mediaTree, status]);

  async function handlePair(boxId) {
    const response = await pairBox(boxId);
    if (!response.ok) {
      setError(response.data.detail || 'Box pairing fehlgeschlagen.');
      addToast('error', response.data.detail || 'Box pairing fehlgeschlagen.');
      return;
    }
    addToast('success', 'Box gepairt.');
    const updated = await getBoxes();
    if (updated.ok) {
      setBoxes(updated.data.boxes || []);
    }
  }

  async function handleCommand(command, payload = {}) {
    if (!selectedId) {
      setError('Bitte zuerst eine Box auswaehlen.');
      addToast('error', 'Bitte zuerst eine Box auswaehlen.');
      return;
    }
    let nextPayload = payload;
    if (command === 'nfc_on' || command === 'nfc_off') {
      const rawUid = typeof payload?.uid === 'string' ? payload.uid.trim() : '';
      let uid = rawUid;
      if (!uid) {
        if (command === 'nfc_on') {
          setSimulatedNfc({
            uid: '',
            known: false,
            hardwareUid: generateHardwareUid(),
            at: Date.now(),
          });
          setScanTagLabel('');
          setScanMediaPath('');
          if (!scanTagUid) {
            setScanTagUid(generateTagId());
          }
          setTagUidMode('new');
          setTagStep(0);
          setTagStepOneDone(false);
          setTagStepTwoDone(false);
          return;
        }
        setError('Bitte eine UID angeben.');
        addToast('error', 'Bitte eine UID angeben.');
        return;
      }
      nextPayload = { ...payload, uid };
      if (command === 'nfc_on') {
        setLastHardwareUid({ uid, hardwareUid: generateHardwareUid() });
        setSimulatedNfc(null);
      }
    }
    const response = await sendCommand(selectedId, command, nextPayload);
    if (!response.ok) {
      setError(response.data.detail || 'Command fehlgeschlagen.');
      addToast('error', response.data.detail || 'Command fehlgeschlagen.');
      return;
    }
    setError('');
    addToast('success', 'Command gesendet.');
  }

  async function handlePlayerCommand(command, payload = {}) {
    if (!selectedId) {
      return;
    }
    let nextPayload = payload;
    if (command === 'nfc_on' || command === 'nfc_off') {
      const rawUid = typeof payload?.uid === 'string' ? payload.uid.trim() : '';
      const uid = rawUid;
      if (!uid) {
        if (command === 'nfc_on') {
          setSimulatedNfc({
            uid: '',
            known: false,
            hardwareUid: generateHardwareUid(),
            at: Date.now(),
          });
          setScanTagLabel('');
          setScanMediaPath('');
          if (!scanTagUid) {
            setScanTagUid(generateTagId());
          }
          setTagUidMode('new');
          setTagStep(0);
          setTagStepOneDone(false);
          setTagStepTwoDone(false);
          setTagStepMax(0);
        } else if (command === 'nfc_off') {
          setSimulatedNfc(null);
          setScanTagUid('');
          setScanTagLabel('');
          setScanMediaPath('');
          setLastHardwareUid({ uid: '', hardwareUid: '' });
          setTagUidMode('keep');
          setTagStep(0);
          setTagStepOneDone(false);
          setTagStepTwoDone(false);
          setTagStepMax(0);
        }
        return;
      }
      if (command === 'nfc_on') {
        setLastHardwareUid({ uid, hardwareUid: generateHardwareUid() });
        setSimulatedNfc(null);
      }
      nextPayload = { ...payload, uid };
    }
    await sendCommand(selectedId, command, nextPayload);
  }

  async function handleUnpair(boxId) {
    const response = await unpairBox(boxId);
    if (!response.ok) {
      setError(response.data.detail || 'Unpair fehlgeschlagen.');
      addToast('error', response.data.detail || 'Unpair fehlgeschlagen.');
      return;
    }
    addToast('success', 'Box unpaired.');
    const updated = await getBoxes();
    if (updated.ok) {
      setBoxes(updated.data.boxes || []);
    }
  }

  async function handleMediaRefresh() {
    const response = await getMediaTree();
    if (!response.ok) {
      setMediaError(response.data.detail || 'Medien nicht verfuegbar.');
      setMediaTree(null);
      return;
    }
    setMediaTree(response.data);
    setMediaError('');
  }

  function getNodeByPath(node, targetPath) {
    if (!node) return null;
    if ((node.path || '') === targetPath) return node;
    if (!Array.isArray(node.children)) return null;
    for (const child of node.children) {
      const found = getNodeByPath(child, targetPath);
      if (found) return found;
    }
    return null;
  }

  function listChildren(node) {
    if (!node) return [];
    if (!Array.isArray(node.children)) return [];
    return node.children;
  }

  function buildBreadcrumb(pathValue) {
    if (!pathValue) return [];
    const parts = pathValue.split('/').filter(Boolean);
    return parts.map((part, index) => ({
      name: part,
      path: parts.slice(0, index + 1).join('/'),
    }));
  }

  function collectTopLevelFolders(node) {
    if (!node || !Array.isArray(node.children)) return [];
    return node.children
      .filter((child) => child.type === 'folder')
      .map((child) => child.path);
  }

  function collectFolderPaths(node) {
    if (!node || node.type !== 'folder') return [];
    const entries = [node.path || ''];
    if (!Array.isArray(node.children)) return entries;
    node.children.forEach((child) => {
      if (child.type !== 'folder') return;
      entries.push(...collectFolderPaths(child));
    });
    return entries;
  }

  function isSelected(pathValue) {
    return selectedPaths.includes(pathValue || '');
  }

  async function handleCreateFolder() {
    const trimmedName = newFolderName.trim();
    if (uploadAfterCreate && pendingUploadFiles.length > 0) {
      if (!trimmedName && !currentPath) {
        addToast('error', 'Im Medien-Hauptordner ist ein Ordnername erforderlich.');
        setUploadNameError('Im Medien-Hauptordner ist ein Ordnername erforderlich.');
        return;
      }
      const targetPath = trimmedName ? `${currentPath}/${trimmedName}` : currentPath;
      setUploadInProgress(true);
      setActiveUploadLabel(
        pendingUploadFiles.length ? `Upload: ${pendingUploadFiles.length} Datei(en)` : ''
      );
      const uploadResponse = await uploadMedia(
        targetPath,
        pendingUploadFiles,
        (percent) => setUploadProgress(percent)
      );
      setUploadInProgress(false);
      setUploadProgress(0);
      if (!uploadResponse.ok) {
        setMediaError(uploadResponse.data.detail || 'Upload fehlgeschlagen.');
        addToast('error', uploadResponse.data.detail || 'Upload fehlgeschlagen.');
        return;
      }
      addToast('success', 'Upload abgeschlossen.');
      await handleMediaRefresh();
      setActiveModal('');
      setUploadAfterCreate(false);
      setPendingUploadFiles([]);
      setNewFolderName('');
      return;
    }

    if (!trimmedName && !uploadAfterCreate) {
      setMediaError('Bitte einen Ordnernamen angeben.');
      return;
    }

    const response = await createMediaFolder(currentPath, trimmedName);
    if (!response.ok) {
      setMediaError(response.data.detail || 'Ordner anlegen fehlgeschlagen.');
      addToast('error', response.data.detail || 'Ordner anlegen fehlgeschlagen.');
      return;
    }
    addToast('success', 'Ordner angelegt.');
    setNewFolderName('');
    setActiveModal('');
    await handleMediaRefresh();
  }

  async function handleRename() {
    if (!selectedPaths.length) return;
    const response = await renameMedia(selectedPaths[0], renameName.trim());
    if (!response.ok) {
      setMediaError(response.data.detail || 'Umbenennen fehlgeschlagen.');
      addToast('error', response.data.detail || 'Umbenennen fehlgeschlagen.');
      return;
    }
    addToast('success', 'Eintrag umbenannt.');
    setRenameName('');
    setActiveModal('');
    await handleMediaRefresh();
  }

  async function handleDeleteSelected() {
    if (!selectedPaths.length) return;
    for (const pathValue of selectedPaths) {
      const response = await deleteMedia(pathValue);
      if (!response.ok) {
        setMediaError(response.data.detail || 'Loeschen fehlgeschlagen.');
        addToast('error', response.data.detail || 'Loeschen fehlgeschlagen.');
        return;
      }
    }
    addToast('success', 'Eintraege geloescht.');
    setSelectedPaths([]);
    setActiveModal('');
    await handleMediaRefresh();
  }

  async function handleMoveSelected() {
    if (!selectedPaths.length) return;
    const pathValue = selectedPaths[0];
    const isRootTarget = moveTarget === '__root__' || moveTarget === '';
    const response = await moveMedia(pathValue, isRootTarget ? '' : moveTarget);
    if (!response.ok) {
      setMediaError(response.data.detail || 'Verschieben fehlgeschlagen.');
      addToast('error', response.data.detail || 'Verschieben fehlgeschlagen.');
      return;
    }
    addToast('success', 'Eintrag verschoben.');
    setMoveTarget('');
    setSelectedPaths([]);
    setActiveModal('');
    await handleMediaRefresh();
  }

  async function handleUpload(event) {
    const files = Array.from(event.target.files || []);
    const audioFiles = files.filter((file) => file.type.startsWith('audio/'));
    if (!audioFiles.length) return;
    if (!currentPath) {
      addToast('error', 'Im Medien-Hauptordner ist ein Ordnername erforderlich.');
      setUploadNameError('Im Medien-Hauptordner ist ein Ordnername erforderlich.');
      return;
    }
    setUploadInProgress(true);
    setActiveUploadLabel(
      audioFiles.length ? `Upload: ${audioFiles.length} Datei(en)` : ''
    );
    const response = await uploadMedia(currentPath, audioFiles, (percent) =>
      setUploadProgress(percent)
    );
    setUploadInProgress(false);
    setUploadProgress(0);
    if (!response.ok) {
      setMediaError(response.data.detail || 'Upload fehlgeschlagen.');
      addToast('error', response.data.detail || 'Upload fehlgeschlagen.');
      return;
    }
    addToast('success', 'Upload abgeschlossen.');
    await handleMediaRefresh();
  }

  const fileHeaderTemplate = (options) => {
    const { className, chooseButton, cancelButton } = options;
    return (
      <div className={className} style={{ backgroundColor: 'transparent', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
        {chooseButton}
        {cancelButton}
      </div>
    );
  };

  const fileItemTemplate = (file, props) => (
    <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', width: '100%', padding: '0.5rem 0', borderBottom: '1px solid rgba(255, 255, 255, 0.08)' }}>
      <Button
        icon="pi pi-times"
        severity="danger"
        text
        rounded
        aria-label="Entfernen"
        onClick={() => {
          setUploadSize((prev) => Math.max(0, prev - file.size));
          setPendingUploadFiles((prev) =>
            prev.filter((entry) => entry.name !== file.name)
          );
          props.onRemove(file);
        }}
      />
      <div className="upload-item-meta">
        <strong className="upload-item-name">{file.name}</strong>
        <small className="upload-item-size">{formatSize(file.size)}</small>
      </div>
    </div>
  );

  const fileEmptyTemplate = () => (
    <div className="upload-item-footer">
      <span className="upload-item-total">Gesamt: {formatSize(uploadSize)}</span>
    </div>
  );

  function handleSelect(item, event) {
    const itemPath = item.path || '';
    if (event.shiftKey && lastAnchorRef.current) {
      const items = listChildren(getNodeByPath(mediaTree, currentPath));
      const anchorIndex = items.findIndex((entry) => entry.path === lastAnchorRef.current);
      const currentIndex = items.findIndex((entry) => entry.path === itemPath);
      if (anchorIndex !== -1 && currentIndex !== -1) {
        const [start, end] = anchorIndex < currentIndex
          ? [anchorIndex, currentIndex]
          : [currentIndex, anchorIndex];
        const range = items.slice(start, end + 1).map((entry) => entry.path || '');
        setSelectedPaths(Array.from(new Set([...selectedPaths, ...range])));
        return;
      }
    }
    if (event.metaKey || event.ctrlKey) {
      setSelectedPaths((prev) =>
        prev.includes(itemPath)
          ? prev.filter((pathValue) => pathValue !== itemPath)
          : [...prev, itemPath]
      );
      lastAnchorRef.current = itemPath;
      return;
    }
    const now = Date.now();
    const isDoubleClick = now - lastDblClickRef.current < 250;
    if (!isDoubleClick) {
      setSelectedPaths([itemPath]);
      lastAnchorRef.current = itemPath;
    }
  }

  function handleDragSelect(item) {
    if (!dragSelectRef.current) return;
    const itemPath = item.path || '';
    setSelectedPaths((prev) =>
      prev.includes(itemPath) ? prev : [...prev, itemPath]
    );
  }

  function handleOpen(item) {
    if (item.type !== 'folder') return;
    setCurrentPath(item.path || '');
    setSelectedPaths([]);
    setRenameName('');
    const key = item.path || '__root__';
    setExpandedFolders((prev) => new Set(prev).add(key));
  }

  function handleGoUp() {
    if (!currentPath) return;
    const parts = currentPath.split('/').filter(Boolean);
    parts.pop();
    const next = parts.join('/');
    setCurrentPath(next);
    setSelectedPaths([]);
    setRenameName('');
  }

  function toggleFolder(pathValue) {
    const key = pathValue || '__root__';
    setExpandedFolders((prev) => {
      const next = new Set(prev);
      if (next.has(key)) {
        next.delete(key);
      } else {
        next.add(key);
      }
      return next;
    });
  }

  function renderFolderTree(node, depth = 0) {
    if (!node || node.type !== 'folder') return null;
    const isActive = (node.path || '') === currentPath;
    const key = node.path || '__root__';
    const isExpanded = expandedFolders.has(key);
    const childFolders =
      Array.isArray(node.children) && node.children.filter((child) => child.type === 'folder');
    const hasChildren = childFolders && childFolders.length > 0;
    const tagCount = mediaTagCounts[node.path || ''] || 0;
    return (
      <div key={node.path || 'root'} className={`tree-node depth-${depth}`}>
        <div
          className={`tree-row folder ${isActive ? 'active' : ''}`}
          onClick={() => handleOpen(node)}
          role="button"
          tabIndex={0}
          onKeyDown={(event) => {
            if (event.key === 'Enter') handleOpen(node);
          }}
        >
          {hasChildren ? (
            <div
              className="tree-caret"
              role="button"
              tabIndex={0}
              onClick={(event) => {
                event.stopPropagation();
                toggleFolder(node.path || '');
              }}
              onKeyDown={(event) => {
                if (event.key === 'Enter') {
                  event.stopPropagation();
                  toggleFolder(node.path || '');
                }
              }}
              aria-label="Toggle"
            >
              {isExpanded ? <CaretDown size={14} /> : <CaretRight size={14} />}
            </div>
          ) : (
            <span className="tree-caret disabled" aria-hidden="true" />
          )}
          <span className="tree-icon folder">
            <Folder size={16} weight="fill" />
          </span>
          <span className="tree-label">{node.name}</span>
          {tagCount > 0 && (
            <span className="tree-tag-count" aria-label={`Zugewiesene Tags: ${tagCount}`}>
              {tagCount}
            </span>
          )}
        </div>
        {isExpanded &&
          hasChildren &&
          childFolders.map((child) => renderFolderTree(child, depth + 1))}
      </div>
    );
  }

  async function handleClaimTagForScan() {
    setTagUidError('');
    const currentUid = scanTagUid.trim();
    const existing = tags.find((tag) => tag.uid === currentUid);
    if (existing) {
      if (existing.status === 'NEW') {
        const written = await markTagWritten(currentUid);
        if (!written.ok) {
          if (written.data.detail === 'invalid uid') {
            setTagUidError(
              'UID ungueltig. Bitte 10 Zeichen (a-z, 0-9) oder TAG_ + 8 Zeichen verwenden.'
            );
            return;
          }
          setError(written.data.detail || 'Tag schreiben fehlgeschlagen.');
          addToast('error', written.data.detail || 'Tag schreiben fehlgeschlagen.');
          return;
        }
      }
    } else {
      const response = await claimTag(currentUid, scanTagLabel.trim());
      if (!response.ok) {
        if (response.data.detail === 'invalid uid') {
          setTagUidError(
            'UID ungueltig. Bitte 10 Zeichen (a-z, 0-9) oder TAG_ + 8 Zeichen verwenden.'
          );
          return;
        }
        setError(response.data.detail || 'Tag schreiben fehlgeschlagen.');
        addToast('error', response.data.detail || 'Tag schreiben fehlgeschlagen.');
        return;
      }
      setScanTagUid(response.data.uid || '');
    }
    setError('');
    addToast('success', 'Tag geschrieben.');
    if (activeNfc) {
      setDismissedNfcKey(getNfcKey(activeNfc));
    }
    const shouldAssign =
      selectedId && status?.last_nfc?.known === false && scanMediaPath;
    if (shouldAssign) {
      const mediaSet = await setTagMedia(currentUid, scanMediaPath);
      if (!mediaSet.ok) {
        setError(mediaSet.data.detail || 'Medium setzen fehlgeschlagen.');
        addToast('error', mediaSet.data.detail || 'Medium setzen fehlgeschlagen.');
        return;
      }
      const assigned = await assignTag(currentUid, selectedId);
      if (!assigned.ok) {
        setError(assigned.data.detail || 'Zuordnung fehlgeschlagen.');
        addToast('error', assigned.data.detail || 'Zuordnung fehlgeschlagen.');
        return;
      }
      addToast('success', 'Tag zugeordnet.');
      if (activeNfc?.known === false) {
        await handlePlayerCommand('nfc_on', { uid: currentUid });
      }
      setScanTagUid('');
      setScanTagLabel('');
      setScanMediaPath('');
      setSimulatedNfc(null);
      setLastHardwareUid({ uid: '', hardwareUid: '' });
    } else if (selectedId && status?.last_nfc?.known === false) {
      addToast('success', 'Tag gespeichert. Medium fehlt noch.');
      setSimulatedNfc(null);
      setLastHardwareUid({ uid: '', hardwareUid: '' });
    }
    const updated = await getTags();
    if (updated.ok) {
      setTags(updated.data.tags || []);
    }
    if (selectedId) {
      const updatedBoxTags = await getBoxTags(selectedId);
      if (updatedBoxTags.ok) {
        setBoxTags(updatedBoxTags.data.tags || []);
      }
    }
  }

  async function handleReuseImportedTag() {
    if (!reuseTagUid) {
      setError('Bitte eine gespeicherte Tag-ID waehlen.');
      addToast('error', 'Bitte eine gespeicherte Tag-ID waehlen.');
      return;
    }
    if (!selectedId) {
      setError('Bitte zuerst eine Box auswaehlen.');
      addToast('error', 'Bitte zuerst eine Box auswaehlen.');
      return;
    }
    const tag = tags.find((entry) => entry.uid === reuseTagUid);
    if (!tag || !tag.media_path) {
      setError('Tag hat keine Medienzuordnung.');
      addToast('error', 'Tag hat keine Medienzuordnung.');
      return;
    }
    const response = await markTagWritten(reuseTagUid);
    if (!response.ok) {
      setError(response.data.detail || 'Tag schreiben fehlgeschlagen.');
      addToast('error', response.data.detail || 'Tag schreiben fehlgeschlagen.');
      return;
    }
    const assigned = await assignTag(reuseTagUid, selectedId);
    if (!assigned.ok) {
      setError(assigned.data.detail || 'Zuordnung fehlgeschlagen.');
      addToast('error', assigned.data.detail || 'Zuordnung fehlgeschlagen.');
      return;
    }
    addToast('success', 'Tag geschrieben und zugeordnet.');
    const updated = await getTags();
    if (updated.ok) {
      setTags(updated.data.tags || []);
    }
    const updatedBoxTags = await getBoxTags(selectedId);
    if (updatedBoxTags.ok) {
      setBoxTags(updatedBoxTags.data.tags || []);
    }
    setScanTagUid(reuseTagUid);
    setReuseTagUid('');
  }

  async function handleWriteTag(uid) {
    const response = await markTagWritten(uid);
    if (!response.ok) {
      setError(response.data.detail || 'Tag schreiben fehlgeschlagen.');
      addToast('error', response.data.detail || 'Tag schreiben fehlgeschlagen.');
      return;
    }
    addToast('success', 'Tag geschrieben.');
    const updated = await getTags();
    if (updated.ok) {
      setTags(updated.data.tags || []);
    }
  }

  async function handleStoreTagOnly() {
    if (!activeNfc?.uid) {
      setError('Keine UID erkannt.');
      addToast('error', 'Keine UID erkannt.');
      return;
    }
    const existing = tags.find((tag) => tag.uid === activeNfc.uid);
    if (existing) {
      addToast('success', 'Tag ist bereits in der Datenbank.');
      if (activeNfc) {
        setDismissedNfcKey(getNfcKey(activeNfc));
      }
      return;
    }
    const response = await claimTag(activeNfc.uid, '');
    if (!response.ok) {
      setError(response.data.detail || 'Tag speichern fehlgeschlagen.');
      addToast('error', response.data.detail || 'Tag speichern fehlgeschlagen.');
      return;
    }
    addToast('success', 'Tag in der Datenbank gespeichert.');
    if (activeNfc) {
      setDismissedNfcKey(getNfcKey(activeNfc));
    }
    setSimulatedNfc(null);
    setLastHardwareUid({ uid: '', hardwareUid: '' });
    const updated = await getTags();
    if (updated.ok) {
      setTags(updated.data.tags || []);
    }
  }

  async function handleAssignFromScan() {
    if (!scanMediaPath) {
      setError('Bitte zuerst einen Medienordner waehlen.');
      addToast('error', 'Bitte zuerst einen Medienordner waehlen.');
      return;
    }
    const uid = scanTagUid.trim();
    if (!uid) {
      setError('Bitte zuerst eine Tag-ID schreiben.');
      addToast('error', 'Bitte zuerst eine Tag-ID schreiben.');
      return;
    }
    const matching = tags.find((tag) => tag.uid === uid);
    if (!matching) {
      setError('Tag-ID existiert nicht. Bitte zuerst schreiben.');
      addToast('error', 'Tag-ID existiert nicht. Bitte zuerst schreiben.');
      return;
    }
    const mediaSet = await setTagMedia(uid, scanMediaPath);
    if (!mediaSet.ok) {
      setError(mediaSet.data.detail || 'Medium setzen fehlgeschlagen.');
      addToast('error', mediaSet.data.detail || 'Medium setzen fehlgeschlagen.');
      return;
    }
    const assigned = await assignTag(uid, selectedId);
    if (!assigned.ok) {
      setError(assigned.data.detail || 'Zuordnung fehlgeschlagen.');
      addToast('error', assigned.data.detail || 'Zuordnung fehlgeschlagen.');
      return;
    }
    addToast('success', 'Tag zugeordnet.');
    if (activeNfc?.known === false) {
      await handlePlayerCommand('nfc_on', { uid });
    }
    if (activeNfc) {
      setDismissedNfcKey(getNfcKey(activeNfc));
    }
    setScanTagUid('');
    setScanTagLabel('');
    setScanMediaPath('');
    setSimulatedNfc(null);
    setLastHardwareUid({ uid: '', hardwareUid: '' });
    const updatedTags = await getTags();
    if (updatedTags.ok) {
      setTags(updatedTags.data.tags || []);
    }
    const updatedBoxTags = await getBoxTags(selectedId);
    if (updatedBoxTags.ok) {
      setBoxTags(updatedBoxTags.data.tags || []);
    }
  }

  async function handlePullTagFromBox() {
    if (!selectedId) {
      setError('Bitte zuerst eine Box auswaehlen.');
      addToast('error', 'Bitte zuerst eine Box auswaehlen.');
      return;
    }
    if (!importTargetUid) {
      setError('Kein Tag ausgewaehlt.');
      addToast('error', 'Kein Tag ausgewaehlt.');
      return;
    }
    const response = await pullTagFromBox(
      selectedId,
      importTargetUid,
      importTargetFolder.trim()
    );
    if (!response.ok) {
      setError(response.data.detail || 'Import fehlgeschlagen.');
      addToast('error', response.data.detail || 'Import fehlgeschlagen.');
      return;
    }
    addToast('success', 'Medien vom Box-Tag uebertragen.');
    setImportTargetUid('');
    setImportTargetFolder('');
    setActiveModal('');
    const updated = await getTags();
    if (updated.ok) {
      setTags(updated.data.tags || []);
    }
    const updatedLocal = await getBoxLocalTags(selectedId);
    if (updatedLocal.ok) {
      setLocalBoxTags(updatedLocal.data.tags || []);
    }
    await handleMediaRefresh();
  }

  async function handleUnassignTag(uid) {
    const response = await unassignTag(uid, selectedId);
    if (!response.ok) {
      setError(response.data.detail || 'Tag loesen fehlgeschlagen.');
      addToast('error', response.data.detail || 'Tag loesen fehlgeschlagen.');
      return;
    }
    addToast('success', 'Tag getrennt.');
    const updated = await getBoxTags(selectedId);
    if (updated.ok) {
      setBoxTags(updated.data.tags || []);
    }
  }

  async function handleDeleteTag(uid) {
    const response = await deleteTag(uid);
    if (!response.ok) {
      setError(response.data.detail || 'Tag loeschen fehlgeschlagen.');
      addToast('error', response.data.detail || 'Tag loeschen fehlgeschlagen.');
      return;
    }
    addToast('success', 'Tag geloescht.');
    const updatedTags = await getTags();
    if (updatedTags.ok) {
      setTags(updatedTags.data.tags || []);
    }
    if (selectedId) {
      const updatedBoxTags = await getBoxTags(selectedId);
      if (updatedBoxTags.ok) {
        setBoxTags(updatedBoxTags.data.tags || []);
      }
    }
    if (boxes.length) {
      const results = await Promise.all(
        boxes.map(async (box) => ({
          boxId: box.box_id,
          response: await getTagBlocks(box.box_id),
        }))
      );
      const next = {};
      results.forEach(({ boxId, response }) => {
        if (response.ok) {
          next[boxId] = response.data.blocked || [];
        }
      });
      setBlockedByBox(next);
    }
  }

  async function handleSetTagMedia(uid) {
    const mediaPath = dbTagMedia[uid] || '';
    const response = await setTagMedia(uid, mediaPath);
    if (!response.ok) {
      setError(response.data.detail || 'Medium setzen fehlgeschlagen.');
      addToast('error', response.data.detail || 'Medium setzen fehlgeschlagen.');
      return;
    }
    const lastNfcUid = status?.last_nfc?.uid || '';
    const shouldAssign =
      selectedId &&
      status?.last_nfc?.known === false &&
      (lastNfcUid === uid || (!lastNfcUid && scanTagUid && scanTagUid === uid));
    if (shouldAssign) {
      const assigned = await assignTag(uid, selectedId);
      if (!assigned.ok) {
        setError(assigned.data.detail || 'Zuordnung fehlgeschlagen.');
        addToast('error', assigned.data.detail || 'Zuordnung fehlgeschlagen.');
        return;
      }
      addToast('success', 'Medium gespeichert und Tag zugeordnet.');
    } else {
      addToast('success', 'Medium gespeichert.');
    }
    const updatedTags = await getTags();
    if (updatedTags.ok) {
      setTags(updatedTags.data.tags || []);
    }
    if (selectedId) {
      const updatedBoxTags = await getBoxTags(selectedId);
      if (updatedBoxTags.ok) {
        setBoxTags(updatedBoxTags.data.tags || []);
      }
    }
    if (boxes.length) {
      const results = await Promise.all(
        boxes.map(async (box) => ({
          boxId: box.box_id,
          response: await getTagBlocks(box.box_id),
        }))
      );
      const next = {};
      results.forEach(({ boxId, response }) => {
        if (response.ok) {
          next[boxId] = response.data.blocked || [];
        }
      });
      setBlockedByBox(next);
    }
  }

  async function handleClearTagMedia(uid) {
    const response = await setTagMedia(uid, '');
    if (!response.ok) {
      setError(response.data.detail || 'Medium entfernen fehlgeschlagen.');
      addToast('error', response.data.detail || 'Medium entfernen fehlgeschlagen.');
      return;
    }
    setError('');
    addToast('success', 'Medienzuweisung entfernt.');
    setDbTagMedia((prev) => ({ ...prev, [uid]: '' }));
    const updatedTags = await getTags();
    if (updatedTags.ok) {
      setTags(updatedTags.data.tags || []);
    }
    if (selectedId) {
      const updatedBoxTags = await getBoxTags(selectedId);
      if (updatedBoxTags.ok) {
        setBoxTags(updatedBoxTags.data.tags || []);
      }
    }
    if (boxes.length) {
      const results = await Promise.all(
        boxes.map(async (box) => ({
          boxId: box.box_id,
          response: await getTagBlocks(box.box_id),
        }))
      );
      const next = {};
      results.forEach(({ boxId, response }) => {
        if (response.ok) {
          next[boxId] = response.data.blocked || [];
        }
      });
      setBlockedByBox(next);
    }
  }

  async function handleToggleTagBlock(boxId, uid, nextBlocked) {
    const response = await setTagBlock(boxId, uid, nextBlocked);
    if (!response.ok) {
      addToast('error', response.data.detail || 'Tag-Sperre fehlgeschlagen.');
      return;
    }
    setBlockedByBox((prev) => {
      const current = new Set(prev[boxId] || []);
      if (nextBlocked) {
        current.add(uid);
      } else {
        current.delete(uid);
      }
      return { ...prev, [boxId]: Array.from(current) };
    });
    addToast(
      'success',
      nextBlocked ? 'Tag gesperrt.' : 'Tag freigegeben.'
    );
  }

  async function handleSaveTagAlias(uid) {
    const value = (tagAliasDrafts[uid] ?? '').trim();
    const response = await setTagAlias(uid, value || null);
    if (!response.ok) {
      addToast('error', response.data.detail || 'Tag-Alias speichern fehlgeschlagen.');
      return;
    }
    const updated = await getTags();
    if (updated.ok) {
      setTags(updated.data.tags || []);
    }
    addToast('success', 'Tag-Alias gespeichert.');
  }

  async function handleSaveBoxAlias(boxId) {
    const value = (boxAliasDrafts[boxId] ?? '').trim();
    const response = await setBoxAlias(boxId, value || null);
    if (!response.ok) {
      addToast('error', response.data.detail || 'Box-Alias speichern fehlgeschlagen.');
      return;
    }
    const updated = await getBoxes();
    if (updated.ok) {
      setBoxes(updated.data.boxes || []);
    }
    addToast('success', 'Box-Alias gespeichert.');
  }

  const currentItems = mediaTree
    ? listChildren(getNodeByPath(mediaTree, currentPath)).filter(
      (item) => !(item.name || '').startsWith('.')
    )
    : [];
  const showMeta = currentItems.some((item) => item.type === 'file');
  const mediaBytes = mediaTree?.size ?? null;
  const freeBytes = mediaTree?.free_bytes ?? null;
  const systemStorage = useMemo(() => {
    if (!mediaTree || mediaTree.size === undefined || mediaTree.free_bytes === undefined) {
      return null;
    }
    const used = Math.max(0, mediaTree.size || 0);
    const free = Math.max(0, mediaTree.free_bytes || 0);
    const total = used + free;
    const percent = total > 0 ? Math.min(100, Math.round((used / total) * 100)) : 0;
    return { used, free, total, percent, scale: capacityScale(percent) };
  }, [mediaTree]);

  const activeMeta = useMemo(
    () => NAV_ITEMS.find((item) => item.id === activeSection),
    [activeSection]
  );
  const activeBoxLabel = useMemo(() => {
    const activeBox = boxes.find((box) => box.box_id === selectedId);
    return activeBox?.alias || activeBox?.box_id || '';
  }, [boxes, selectedId]);
  const activeNfc = useMemo(() => {
    if (status?.last_nfc && status.last_nfc.known === false) {
      return { ...status.last_nfc, at: status.last_nfc_at };
    }
    return simulatedNfc;
  }, [status, simulatedNfc]);

  const activeNfcKey = useMemo(
    () => (activeNfc ? getNfcKey(activeNfc) : ''),
    [activeNfc]
  );

  useEffect(() => {
    if (!activeNfcKey) return;
    if (tagWizardRestoredRef.current === activeNfcKey) return;
    try {
      const raw = sessionStorage.getItem('klangkiste_tag_wizard');
      if (!raw) return;
      const parsed = JSON.parse(raw);
      if (!parsed || parsed.key !== activeNfcKey) return;
      const data = parsed.data || {};
      if (typeof data.scanTagUid === 'string') setScanTagUid(data.scanTagUid);
      if (typeof data.scanTagLabel === 'string') setScanTagLabel(data.scanTagLabel);
      if (typeof data.scanMediaPath === 'string') setScanMediaPath(data.scanMediaPath);
      if (typeof data.tagUidMode === 'string') setTagUidMode(data.tagUidMode);
      if (typeof data.tagStep === 'number') setTagStep(data.tagStep);
      setTagStepOneDone(Boolean(data.tagStepOneDone));
      setTagStepTwoDone(Boolean(data.tagStepTwoDone));
      if (typeof data.tagStepMax === 'number') setTagStepMax(data.tagStepMax);
      if (typeof data.tagUidError === 'string') setTagUidError(data.tagUidError);
      if (data.lastHardwareUid?.uid || data.lastHardwareUid?.hardwareUid) {
        setLastHardwareUid(data.lastHardwareUid);
      }
      if (!status?.last_nfc && data.simulatedNfc) {
        setSimulatedNfc(data.simulatedNfc);
      }
      if (typeof data.dismissedNfcKey === 'string') {
        setDismissedNfcKey(data.dismissedNfcKey);
      }
      tagWizardRestoredRef.current = activeNfcKey;
    } catch (error) {
      // ignore storage restore errors
    }
  }, [activeNfcKey, status?.last_nfc]);

  useEffect(() => {
    if (!activeNfcKey) return;
    try {
      const payload = {
        key: activeNfcKey,
        data: {
          scanTagUid,
          scanTagLabel,
          scanMediaPath,
          tagUidMode,
          tagStep,
          tagStepOneDone,
          tagStepTwoDone,
          tagStepMax,
          tagUidError,
          lastHardwareUid,
          simulatedNfc,
          dismissedNfcKey,
        },
      };
      sessionStorage.setItem('klangkiste_tag_wizard', JSON.stringify(payload));
    } catch (error) {
      // ignore storage write errors
    }
  }, [
    activeNfcKey,
    scanTagUid,
    scanTagLabel,
    scanMediaPath,
    tagUidMode,
    tagStep,
    tagStepOneDone,
    tagStepTwoDone,
    tagStepMax,
    tagUidError,
    lastHardwareUid,
    simulatedNfc,
    dismissedNfcKey,
  ]);
  const statusWithHardware = useMemo(() => {
    if (!status) return null;
    const hardwareUid =
      lastHardwareUid.uid === status?.last_nfc?.uid && lastHardwareUid.hardwareUid
        ? lastHardwareUid.hardwareUid
        : activeNfc?.hardwareUid || null;
    if (!status.last_nfc) return status;
    return {
      ...status,
      last_nfc: {
        ...status.last_nfc,
        hardware_uid: hardwareUid,
      },
    };
  }, [status, lastHardwareUid, activeNfc]);
  const activeTag = useMemo(() => {
    const uid = status?.last_nfc?.uid || activeNfc?.uid || '';
    if (!uid) return null;
    return tags.find((tag) => tag.uid === uid) || null;
  }, [status, activeNfc, tags]);
  const playlistRoot = activeTag?.media_path || '';
  const playlistTitle = playlistRoot ? playlistRoot.split('/').filter(Boolean).pop() : '';
  const playlistItems = useMemo(() => {
    if (!mediaTree || !playlistRoot) return [];
    const node = getNodeByPath(mediaTree, playlistRoot);
    if (!node) return [];
    const stack = [node];
    const collected = [];
    while (stack.length) {
      const current = stack.pop();
      if (!current) continue;
      if (current.type === 'file') {
        collected.push(current);
        continue;
      }
      if (Array.isArray(current.children)) {
        for (let i = current.children.length - 1; i >= 0; i -= 1) {
          stack.push(current.children[i]);
        }
      }
    }
    return collected;
  }, [mediaTree, playlistRoot]);
  const playlistDuration = useMemo(() => {
    if (!playlistItems.length) return 0;
    return playlistItems.reduce((sum, item) => sum + (item.duration || 0), 0);
  }, [playlistItems]);
  const playbackState = status?.playback_state || {};
  const currentIndex =
    typeof playbackState.file_index === 'number' ? playbackState.file_index : null;
  const currentTrack =
    currentIndex !== null && playlistItems[currentIndex]
      ? playlistItems[currentIndex]
      : null;
  const currentTitle =
    currentTrack?.title ||
    currentTrack?.name ||
    (playbackState.current_file
      ? playbackState.current_file.split('/').pop()
      : '');
  const currentArtist = currentTrack?.artist || '';
  const playbackPosition =
    typeof playbackState.position === 'number' ? playbackState.position : 0;
  const playbackDuration =
    typeof playbackState.duration === 'number' ? playbackState.duration : 0;
  const playbackProgress =
    playbackDuration > 0
      ? Math.min(100, Math.round((playbackPosition / playbackDuration) * 100))
      : 0;
  const isPlaying = playbackState.state === 'PLAYING';
  const volumeValue =
    typeof playbackState.volume === 'number' ? playbackState.volume : null;

  function handleSeekCommit(percent) {
    if (!playbackDuration) return;
    const targetSeconds = Math.round((percent / 100) * playbackDuration);
    handlePlayerCommand('seek', { position: targetSeconds });
  }

  function handleVolumeCommit(nextValue) {
    handlePlayerCommand('set_volume', { volume: nextValue });
  }

  useEffect(() => {
    if (isSeeking) return;
    setSeekPercent(playbackProgress);
  }, [playbackProgress, isSeeking]);

  useEffect(() => {
    setIsSeeking(false);
    setSeekPercent(playbackProgress);
  }, [currentIndex, playbackProgress]);

  useEffect(() => {
    setVolumeDraft(volumeValue ?? 50);
  }, [volumeValue]);

  useEffect(() => {
    if (!showVolume) return;
    function handleOutside(event) {
      if (!volumeRef.current) return;
      if (volumeRef.current.contains(event.target)) return;
      setShowVolume(false);
    }
    document.addEventListener('mousedown', handleOutside);
    document.addEventListener('touchstart', handleOutside);
    return () => {
      document.removeEventListener('mousedown', handleOutside);
      document.removeEventListener('touchstart', handleOutside);
    };
  }, [showVolume]);

  function renderSection() {
    if (activeSection === 'dashboard') {
      return (
        <div className="section-stack">
          <StatGrid items={dashboardStats} />
          <div className="split-grid">
            <Card className="wide-card">
              <h3>Live-Status</h3>
              {!selectedId && <p className="muted">Waehle eine gepairte Box aus.</p>}
              {selectedId && !status && <p className="muted">Status wird geladen...</p>}
              {status && status.error && <p className="error">{status.error}</p>}
              {status && !status.error && (
                <pre className="status">{JSON.stringify(statusWithHardware, null, 2)}</pre>
              )}
            </Card>
            <Card className="wide-card">
              <div className="player-shell">
                <div className="player-header">
                  <div className="player-cover" />
                  <div className="player-meta">
                    <h3>{currentTitle || playlistTitle || 'Playlist'}</h3>
                    <p>{currentArtist || playlistRoot || 'Kein Medium zugeordnet'}</p>
                    <div className="player-sub">
                      <span>{playlistItems.length} Tracks</span>
                      <span>{formatClock(playlistDuration)}</span>
                    </div>
                  </div>
                  <div className="player-controls">
                    <Button
                      className="player-btn"
                      icon="pi pi-step-backward"
                      onClick={() => handlePlayerCommand('prev')}
                      aria-label="Zurueck"
                    />
                    <Button
                      className="player-btn primary"
                      icon={isPlaying ? 'pi pi-pause' : 'pi pi-play'}
                      onClick={() => handlePlayerCommand('play_pause')}
                      aria-label={isPlaying ? 'Pause' : 'Play'}
                    />
                    <Button
                      className="player-btn"
                      icon="pi pi-step-forward"
                      onClick={() => handlePlayerCommand('next')}
                      aria-label="Weiter"
                    />
                    <Button
                      className="player-btn"
                      icon="pi pi-stop"
                      onClick={() => handlePlayerCommand('stop')}
                      aria-label="Stopp"
                    />
                    <div className="volume-wrap" ref={volumeRef}>
                      <Button
                        className="player-btn"
                        icon="pi pi-volume-up"
                        onClick={() => setShowVolume((prev) => !prev)}
                        aria-label="Lautstaerke"
                      />
                      {showVolume && (
                        <div className="volume-popover">
                          <Slider
                            orientation="vertical"
                            min={0}
                            max={100}
                            step={1}
                            value={volumeDraft}
                            onChange={(event) => {
                              setVolumeDraft(event.value ?? 0);
                            }}
                            onSlideEnd={(event) => {
                              handleVolumeCommit(event.value ?? 0);
                            }}
                          />
                          <span className="volume-label">{volumeDraft}</span>
                        </div>
                      )}
                    </div>
                  </div>
                </div>
                <div className="player-progress">
                  <span>{formatClock(playbackPosition)}</span>
                  <Slider
                    value={seekPercent}
                    min={0}
                    max={100}
                    step={1}
                    onChange={(event) => {
                      setSeekPercent(event.value ?? 0);
                      setIsSeeking(true);
                    }}
                    onSlideEnd={(event) => {
                      setIsSeeking(false);
                      handleSeekCommit(event.value ?? 0);
                    }}
                  />
                  <span>{formatClock(playbackDuration)}</span>
                </div>
                <div className="player-nfc">
                  <InputText
                    value={nfcUid}
                    onChange={(event) => setNfcUid(event.target.value)}
                    placeholder="UID_1"
                    className="p-inputtext-sm"
                  />
                  <Button label="NFC on" onClick={() => handlePlayerCommand('nfc_on', { uid: nfcUid })} />
                  <Button label="NFC off" onClick={() => handlePlayerCommand('nfc_off', { uid: nfcUid })} />
                </div>
                <div className="player-list">
                  <div className="player-row header">
                    <span>#</span>
                    <span>Titel</span>
                    <span>Interpret</span>
                    <span>Dauer</span>
                  </div>
                  {playlistItems.length === 0 && (
                    <div className="player-row empty">
                      <span />
                      <span>Keine Playlist gefunden.</span>
                      <span />
                      <span />
                    </div>
                  )}
                  {playlistItems.map((track, index) => (
                    <div
                      key={track.path || track.name || index}
                      className={`player-row${currentIndex === index ? ' active' : ''}`}
                      onDoubleClick={(event) => {
                        event.preventDefault();
                        handlePlayerCommand('jump_to_index', { index });
                      }}
                      role="button"
                      tabIndex={0}
                      onKeyDown={(event) => {
                        if (event.key === 'Enter') {
                          handlePlayerCommand('jump_to_index', { index });
                        }
                      }}
                    >
                      <span>{index + 1}</span>
                      <span>{track.title || track.name}</span>
                      <span>{track.artist || '-'}</span>
                      <span>{formatClock(track.duration)}</span>
                    </div>
                  ))}
                </div>
                <p className="muted">
                  Steuerung ist nur moeglich, wenn die Box gepairt ist.
                </p>
              </div>
            </Card>
          </div>
        </div>
      );
    }

    if (activeSection === 'boxes') {
      return (
        <div className="section-stack legacy">
          {unpaired.length > 0 && (
            <Card className="panel-card">
              <h3>Neue Boxen</h3>
              {unpaired.map((box) => (
                <div key={box.box_id} className="card">
                  <div>
                    <strong>{box.alias || box.box_id}</strong>
                    <div className="meta">Zuletzt gesehen: {formatTime(box.last_seen)}</div>
                    <div className="meta">Firmware: {box.firmware_version}</div>
                    <div className="meta">ID: {box.box_id}</div>
                  </div>
                  <Button label="Pairen" onClick={() => handlePair(box.box_id)} />
                </div>
              ))}
            </Card>
          )}
          <Card className="panel-card">
            <h3>Gepairte Boxen</h3>
            {paired.length === 0 && <p className="muted">Noch keine gepairten Boxen.</p>}
            {paired.length > 0 && (
              <div className="boxes-grid">
                {paired.map((box) => (
                  <div
                    key={box.box_id}
                    className={`card ${selectedId === box.box_id ? 'selected' : ''}`}
                    onClick={() => setSelectedId(box.box_id)}
                    role="button"
                    tabIndex={0}
                    onKeyDown={(event) => {
                      if (event.key === 'Enter') setSelectedId(box.box_id);
                    }}
                  >
                    <div>
                <div className="box-title">
                  <strong>{box.alias || box.box_id}</strong>
                  <span className="pill">{getAvailability(box.last_seen).label}</span>
                </div>
                      <div className="meta">Zuletzt gesehen: {formatTime(box.last_seen)}</div>
                      <div className="meta">Capabilities: {parseCapabilities(box.capabilities_json)}</div>
                      <div className="meta">ID: {box.box_id}</div>
                    </div>
                    <div className="stack box-actions">
                      <div className="box-actions-row">
                        <InputText
                          className="alias-input p-inputtext-sm"
                          placeholder="Alias"
                          value={
                            boxAliasDrafts[box.box_id] !== undefined
                              ? boxAliasDrafts[box.box_id]
                              : box.alias || ''
                          }
                          onClick={(event) => event.stopPropagation()}
                          onChange={(event) => {
                            event.stopPropagation();
                            setBoxAliasDrafts((prev) => ({
                              ...prev,
                              [box.box_id]: event.target.value,
                            }));
                          }}
                        />
                        <Button
                          className="icon-button"
                          icon={<FloppyDisk size={16} />}
                          onClick={(event) => {
                            event.stopPropagation();
                            handleSaveBoxAlias(box.box_id);
                          }}
                          title="Alias speichern"
                          aria-label="Alias speichern"
                        />
                        <Button
                          className="icon-button danger"
                          icon={<Trash size={16} />}
                          onClick={(event) => {
                            event.stopPropagation();
                            setBoxDeleteTarget(box.box_id);
                          }}
                          title="Unpair"
                          aria-label="Unpair"
                        />
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            )}
          </Card>
        </div>
      );
    }

    if (activeSection === 'media') {
      return (
        <div className="section-stack legacy">
          <Card className="panel-card">
            <div className="panel-header">
              <h3>Medien Explorer</h3>
              <Button className="button-ghost" label="Refresh" onClick={handleMediaRefresh} />
            </div>
            {uploadInProgress && (
              <div className="upload-status">
                <span>{activeUploadLabel || 'Upload laeuft...'}</span>
                <div className="upload-progress">
                  <div style={{ width: `${uploadProgress}%` }} />
                </div>
              </div>
            )}
            <p className="muted">Ordnerverwaltung, Upload und Datei-Listen im Server-Medienordner.</p>
            {mediaError && <p className="error">{mediaError}</p>}
            {!mediaError && !mediaTree && <p className="muted">Medienliste wird geladen...</p>}
            {!mediaError && mediaTree && (
              <div className="explorer" ref={explorerRef}>
                <div className="explorer-sidebar">
                  <div className="explorer-toolbar sidebar-toolbar">
                      <InputText
                        className="sidebar-search p-inputtext-sm"
                        type="search"
                        placeholder="Suchen..."
                        value={sidebarQuery}
                        onChange={(event) => setSidebarQuery(event.target.value)}
                        aria-label="Ordner suchen"
                      />
                  </div>
                  <div className="sidebar-tree">
                    {topLevelFolders.length === 0 && (
                      <div className="muted">Keine Ordner gefunden.</div>
                    )}
                    {filteredTree && renderFolderTree(filteredTree, 0)}
                  </div>
                </div>
                <div className="explorer-main">
                  <div className="explorer-toolbar">
                    <Button
                      className="icon-button"
                      icon={<ArrowUp size={16} />}
                      onClick={handleGoUp}
                      title="Hoch"
                      aria-label="Hoch"
                    />
                    <div className="explorer-path">
                      <span
                        className="breadcrumb-root path-link"
                        onClick={() => setCurrentPath('')}
                        role="button"
                        tabIndex={0}
                        onKeyDown={(event) => {
                          if (event.key === 'Enter') setCurrentPath('');
                        }}
                      >
                        media
                      </span>
                      {buildBreadcrumb(currentPath).map((crumb) => (
                        <span
                          key={crumb.path}
                          className="path-link"
                          onClick={() => setCurrentPath(crumb.path)}
                          role="button"
                          tabIndex={0}
                          onKeyDown={(event) => {
                            if (event.key === 'Enter') setCurrentPath(crumb.path);
                          }}
                        >
                          / {crumb.name}
                        </span>
                      ))}
                    </div>
                    <div className="toolbar-actions">
                      <Button
                        className="icon-button"
                        icon={<Plus size={16} />}
                        onClick={() => setActiveModal('new-folder')}
                        title="Neuer Ordner"
                        aria-label="Neuer Ordner"
                      />
                      <Button
                        className="icon-button"
                        icon={<PencilSimple size={16} />}
                        onClick={() => setActiveModal('rename')}
                        title="Umbenennen"
                        disabled={selectedPaths.length !== 1}
                        aria-label="Umbenennen"
                      />
                      <Button
                        className="icon-button danger"
                        icon={<Trash size={16} />}
                        onClick={() => setActiveModal('delete')}
                        title="Loeschen"
                        disabled={selectedPaths.length === 0}
                        aria-label="Loeschen"
                      />
                      <Button
                        className="icon-button"
                        icon={<ArrowRight size={16} />}
                        onClick={() => {
                          setMoveTarget('');
                          setActiveModal('move');
                        }}
                        title="Verschieben"
                        disabled={selectedPaths.length === 0}
                        aria-label="Verschieben"
                      />
                      <Button
                        className="icon-button upload"
                        icon={<UploadSimple size={16} />}
                        title="Upload"
                        onClick={(event) => {
                          event.preventDefault();
                          event.stopPropagation();
                          setUploadAfterCreate(true);
                          setPendingUploadFiles([]);
                          setNewFolderName('');
                          setActiveModal('new-folder');
                        }}
                        aria-label="Upload"
                      />
                    </div>
                  </div>
                  <div className={`explorer-list ${showMeta ? 'has-meta' : ''}`}>
                    <div className="explorer-row header">
                      <span>Name</span>
                      {showMeta ? (
                        <>
                          <span>Interpret</span>
                          <span>Titel</span>
                          <span>Laenge</span>
                          <span>Groesse</span>
                        </>
                      ) : (
                        <>
                          <span>Typ</span>
                          <span>Groesse</span>
                        </>
                      )}
                    </div>
                    {currentItems.map((item) => (
                      <div
                        key={item.path || item.name}
                        className={`explorer-row ${
                          isSelected(item.path) ? 'selected' : ''
                        }`}
                        onMouseDown={(event) => {
                          if (event.button !== 0) return;
                          if (event.metaKey || event.ctrlKey || event.shiftKey) {
                            return;
                          }
                          dragSelectRef.current = true;
                          handleSelect(item, event);
                        }}
                        onMouseEnter={() => handleDragSelect(item)}
                        onClick={(event) => handleSelect(item, event)}
                        onDoubleClick={(event) => {
                          event.preventDefault();
                          event.stopPropagation();
                          lastDblClickRef.current = Date.now();
                          handleOpen(item);
                        }}
                        role="button"
                        tabIndex={0}
                        onKeyDown={(event) => {
                          if (event.key === 'Enter') handleOpen(item);
                        }}
                      >
                        <span className="row-name">
                          <span className="row-icon">
                            {item.type === 'folder' ? (
                              <Folder size={16} weight="fill" />
                            ) : (
                              <FileAudio size={16} />
                            )}
                          </span>
                          {item.name}
                        </span>
                        {showMeta ? (
                          <>
                            <span>{item.type === 'folder' ? '-' : item.artist || '-'}</span>
                            <span>{item.type === 'folder' ? '-' : item.title || '-'}</span>
                            <span>
                              {item.type === 'folder' ? '-' : formatDuration(item.duration)}
                            </span>
                            <span>{formatSize(item.size)}</span>
                          </>
                        ) : (
                          <>
                            <span>{item.type === 'folder' ? 'Ordner' : 'Datei'}</span>
                            <span>{formatSize(item.size)}</span>
                          </>
                        )}
                      </div>
                    ))}
                    {selectedPaths.length > 0 && (
                      <div className="explorer-row footer">
                        <span />
                        {showMeta ? (
                          <>
                            <span />
                            <span />
                            <span />
                            <span className="footer-count">
                              {selectedPaths.length} ausgewaehlt
                            </span>
                          </>
                        ) : (
                          <>
                            <span />
                            <span className="footer-count">
                              {selectedPaths.length} ausgewaehlt
                            </span>
                          </>
                        )}
                      </div>
                    )}
                  </div>
                </div>
                <div className="explorer-footer">
                  <span>
                    Medienordner: {formatSize(mediaBytes)} · Verfuegbar:{' '}
                    {formatSize(freeBytes)}
                  </span>
                </div>
              </div>
            )}
          </Card>
        </div>
      );
    }

    if (activeSection === 'tags') {
      return (
        <div className="section-stack legacy">
          {(localBoxError || localBoxTags.length > 0) && (
            <Card className="panel-card">
              <div className="panel-header">
                <h3>Tags nur auf dieser Box</h3>
              </div>
              {localBoxError && <p className="error">{localBoxError}</p>}
              {localBoxTags.map((tag) => (
                <div key={tag.uid} className="box-tag-row">
                  <div>
                    <strong>{tag.uid}</strong>
                    <div className="muted">
                      Dateien: {tag.file_count} · {formatSize(tag.total_size)}
                    </div>
                    {tag.media_exists && tag.files?.length > 0 && (
                      <ul className="file-list">
                        {tag.files.map((file) => (
                          <li key={file}>{file}</li>
                        ))}
                      </ul>
                    )}
                    {!tag.media_exists && (
                      <p className="muted">Keine Dateien im Box-Ordner gefunden.</p>
                    )}
                  </div>
                  <div className="box-tag-actions">
                    <Button
                      label="Auf Server uebertragen"
                      onClick={() => {
                        setImportTargetUid(tag.uid);
                        setActiveModal('import-tag');
                      }}
                      disabled={!tag.media_exists}
                    />
                  </div>
                </div>
              ))}
            </Card>
          )}

          <Card className="panel-card">
            <h3>Tags (Datenbank)</h3>
            {tags.length === 0 && <p className="muted">Keine Tags vorhanden.</p>}
            {tags.length > 0 && (
              <div className="tags-grid">
                {tags.map((tag) => (
                  <div key={tag.uid} className="card compact">
                    <div className="tag-row">
                      <div className="tag-info">
                        <strong>
                          {tag.alias ? `${tag.alias} (${tag.uid})` : tag.uid}
                        </strong>
                        <div className="meta">Status: {tag.status}</div>
                        <div className="meta">Medium: {tag.media_path || '-'}</div>
                        {tag.label ? <div className="meta">Label: {tag.label}</div> : null}
                      </div>
                      <div className="stack stack-inline tag-actions">
                        <div className="tag-actions-row">
                          <InputText
                            className="alias-input p-inputtext-sm"
                            placeholder="Alias"
                            value={
                              tagAliasDrafts[tag.uid] !== undefined
                                ? tagAliasDrafts[tag.uid]
                                : tag.alias || ''
                            }
                            onChange={(event) =>
                              setTagAliasDrafts((prev) => ({
                                ...prev,
                                [tag.uid]: event.target.value,
                              }))
                            }
                          />
                          <Button
                            className="icon-button"
                            icon={<FloppyDisk size={16} />}
                            onClick={() => handleSaveTagAlias(tag.uid)}
                            title="Alias speichern"
                            aria-label="Alias speichern"
                          />
                        </div>
                        <div className="tag-actions-row">
                          <Dropdown
                            value={dbTagMedia[tag.uid] ?? tag.media_path ?? null}
                            onChange={(event) =>
                              setDbTagMedia((prev) => ({
                                ...prev,
                                [tag.uid]: event.value,
                              }))
                            }
                            options={collectTopLevelFolders(mediaTree).map((folderPath) => ({
                              label: folderPath,
                              value: folderPath,
                            }))}
                            placeholder="Medienordner waehlen"
                            filter
                            className="p-inputtext-sm"
                          />
                          <Button
                            className="icon-button danger"
                            icon={<Trash size={16} />}
                            onClick={() => {
                              setTagDeleteTarget(tag.uid);
                              setActiveModal('tag-delete');
                            }}
                            title="Loeschen"
                            aria-label="Loeschen"
                          />
                        </div>
                      </div>
                    </div>
                  </div>
                ))}
              </div>
            )}
          </Card>

          <Card className="panel-card">
            <h3>Tag-Matrix (Sperren)</h3>
            {paired.length === 0 && (
              <p className="muted">Keine gepairten Boxen vorhanden.</p>
            )}
            {tags.length === 0 && <p className="muted">Keine Tags vorhanden.</p>}
            {paired.length > 0 && tags.length > 0 && (
              <div className="tag-matrix">
                <div className="matrix-row header">
                  <span className="matrix-cell label">Tag</span>
                  {paired.map((box) => (
                    <span key={box.box_id} className="matrix-cell">
                      {box.alias || box.box_id}
                    </span>
                  ))}
                </div>
                {tags.map((tag) => (
                  <div key={tag.uid} className="matrix-row">
                    <span className="matrix-cell label">{tag.alias || tag.uid}</span>
                    {paired.map((box) => {
                      const blocked = (blockedByBox[box.box_id] || []).includes(tag.uid);
                      return (
                        <Message
                          key={box.box_id}
                          className={`matrix-cell toggle${blocked ? ' is-blocked' : ''}`}
                          severity={blocked ? 'error' : 'success'}
                          text={blocked ? 'Gesperrt' : 'OK'}
                          role="button"
                          tabIndex={0}
                          onClick={() =>
                            handleToggleTagBlock(box.box_id, tag.uid, !blocked)
                          }
                          onKeyDown={(event) => {
                            if (event.key === 'Enter' || event.key === ' ') {
                              event.preventDefault();
                              handleToggleTagBlock(box.box_id, tag.uid, !blocked);
                            }
                          }}
                        />
                      );
                    })}
                  </div>
                ))}
              </div>
            )}
          </Card>
        </div>
      );
    }

    return (
      <div className="section-stack">
        <Card className="wide-card">
          <h3>System</h3>
          <p>Backend: {import.meta.env.VITE_BACKEND_URL || 'http://127.0.0.1:5001'}</p>
          <div className="settings-row">
            <span>Gepairte Boxen</span>
            <span>{paired.length}</span>
          </div>
          <div className="settings-row">
            <span>Tags</span>
            <span>{tags.length}</span>
          </div>
        </Card>
      </div>
    );
  }

  const systemPanels = (
    <>
      <Card className="panel-card">
        <div className="card-title-row">
          <h3>Live Status</h3>
          <Tag value="Stable" severity="success" />
        </div>
        <p>System Health und wichtige Events.</p>
        <div className="status-row">
          <span>API</span>
          <Tag value="OK" severity="success" />
        </div>
        <div className="status-row">
          <span>Speicher</span>
          <Tag value={mediaTree ? formatSize(mediaTree.free_bytes) : '-'} severity="warning" />
        </div>
        <div className="status-row">
          <span>Letzter Sync</span>
          <span>{status?.last_sync_at ? formatTime(status.last_sync_at) : '-'}</span>
        </div>
        <div className="status-row">
          <span>Hardware-UID</span>
          <span>
            {lastHardwareUid.uid === status?.last_nfc?.uid && lastHardwareUid.hardwareUid
              ? lastHardwareUid.hardwareUid
              : activeNfc?.hardwareUid || '-'}
          </span>
        </div>
      </Card>
      <Card className="panel-card">
        <h3>Speicherkapazitaet</h3>
        <div className="capacity-row">
          <div className="capacity-header">
            <strong>System</strong>
            <span>
              {systemStorage
                ? `${formatSize(systemStorage.used)} / ${formatSize(systemStorage.total)}`
                : '-'}
            </span>
          </div>
          <MeterGroup
            className="capacity-bar"
            values={[{ label: '', value: systemStorage?.percent || 0 }]}
            style={{ '--capacity-scale': systemStorage?.scale || 1 }}
          />
          <div className="capacity-meta">
            Frei: {systemStorage ? formatSize(systemStorage.free) : '-'}
          </div>
        </div>
        <Divider />
        {boxes.length === 0 && <p className="muted">Keine Boxen verbunden.</p>}
        {boxes.map((box) => {
          const storage = boxStorage[box.box_id] || null;
          const used = storage?.used ?? null;
          const free = storage?.free ?? null;
          const total = storage?.total ?? null;
          const totalValue =
            total !== null ? total : used !== null && free !== null ? used + free : null;
          const usedValue =
            used !== null ? used : totalValue !== null && free !== null ? totalValue - free : null;
          const percent =
            totalValue && usedValue !== null
              ? Math.min(100, Math.round((usedValue / totalValue) * 100))
              : 0;
          const scale = capacityScale(percent);
          return (
            <div key={box.box_id} className="capacity-row">
              <div className="capacity-header">
                <strong>{box.alias || box.box_id}</strong>
                <span>
                  {totalValue !== null && usedValue !== null
                    ? `${formatSize(usedValue)} / ${formatSize(totalValue)}`
                    : '-'}
                </span>
              </div>
              <MeterGroup
                className="capacity-bar"
                values={[{ label: '', value: percent }]}
                style={{ '--capacity-scale': scale }}
              />
              <div className="capacity-meta">
                {free !== null ? `Frei: ${formatSize(free)}` : 'Keine Kapazitaet gemeldet'}
              </div>
            </div>
          );
        })}
      </Card>
    </>
  );

  return (
    <div className="app-shell">
      <header className="topbar">
        <div className="brand">
          <div className="brand-mark">K</div>
          <div>
            <strong>Klangkiste</strong>
            <span>PrimeReact GUI</span>
          </div>
        </div>
        {selectedId && (
          <div className="header-box-indicator">
            <i className="pi pi-check-circle" />
            <span>{activeBoxLabel || selectedId}</span>
          </div>
        )}
        <div className="topbar-actions">
          <Button icon="pi pi-bell" text rounded aria-label="Benachrichtigungen" />
          <Avatar label="MK" shape="circle" className="user-avatar" />
        </div>
      </header>

      <Toast ref={toastRef} position="top-right" />

      <div className="layout">
        <aside className="sidebar">
          <div className="sidebar-section">
            <p className="sidebar-title">Navigation</p>
            <div className="nav-list">
              {NAV_ITEMS.map((item) => {
                const Icon = item.icon;
                const iconProp = typeof Icon === 'string' ? Icon : <Icon size={16} />;
                return (
                  <Button
                    key={item.id}
                    label={item.label}
                    icon={iconProp}
                    text
                    className={`nav-button${activeSection === item.id ? ' active' : ''}`}
                    onClick={() => setActiveSection(item.id)}
                  />
                );
              })}
            </div>
          </div>
          <Divider />
          {/* <div className="sidebar-section">
            <p className="sidebar-title">Quick Actions</p>
            <div className="button-stack">
              <Button label="NFC Tag erstellen" icon="pi pi-plus" className="p-button-sm" />
              <Button label="Upload starten" icon="pi pi-upload" outlined className="p-button-sm" />
              <Button label="Box koppeln" icon="pi pi-link" text className="p-button-sm" />
            </div>
          </div>
          <Divider /> */}
          <div className="sidebar-section">
            <p className="sidebar-title">Aktive Sessions</p>
            {paired.length === 0 && (
              <p className="muted">Keine gepairten Boxen.</p>
            )}
            <div className="session-list">
              {paired.map((box) => {
                const isActive = selectedId === box.box_id;
                const label = (box.alias || box.box_id || '').slice(0, 2).toUpperCase();
                const availability = getAvailability(box.last_seen);
                return (
                  <Button
                    key={box.box_id}
                    className={`session-card${isActive ? ' active' : ''}`}
                    onClick={() => setSelectedId(box.box_id)}
                    title={`Aktiv setzen: ${box.alias || box.box_id}`}
                  >
                    <Avatar label={label || 'BX'} shape="circle" />
                    <div>
                      <strong>{box.alias || box.box_id}</strong>
                      {isActive && <span className="meta">Aktiv</span>}
                    </div>
                    <Tag value={availability.label} severity={availability.severity} rounded />
                  </Button>
                );
              })}
            </div>
          </div>
        </aside>

        <main className="content">
          <SectionHeader
            title={activeMeta?.label || 'Bereich'}
            subtitle="Live Daten und Steuerung der Klangkiste."
          />
          {error && <div className="error">{error}</div>}
          {activeNfc && dismissedNfcKey !== getNfcKey(activeNfc) && (
            <div className="legacy">
              <Card className="panel-card">
                <h3>
                  {activeNfc.uid &&
                  tags.find((tag) => tag.uid === activeNfc.uid && !tag.media_path)
                    ? 'Leerer Tag erkannt'
                    : 'Neuer Tag erkannt'}
                </h3>
                <p className="muted">
                  {activeNfc.uid ? (
                    <>
                      UID erkannt: <strong>{activeNfc.uid}</strong>
                    </>
                  ) : (
                    <>keine UID erkannt</>
                  )}
                </p>
                {activeNfc.uid &&
                lastHardwareUid.uid === activeNfc.uid &&
                lastHardwareUid.hardwareUid && (
                  <p className="muted">
                    Hardware-UID erkannt: <strong>{lastHardwareUid.hardwareUid}</strong>
                  </p>
                )}
                {!activeNfc.uid && activeNfc.hardwareUid && (
                  <p className="muted">
                    Hardware-UID erkannt: <strong>{activeNfc.hardwareUid}</strong>
                  </p>
                )}
                {activeNfc.uid && tags.find((tag) => tag.uid === activeNfc.uid) && (
                  <p className="muted">
                    Dieser Tag ist bekannt, aber noch nicht zugewiesen.
                  </p>
                )}
                <div className="step-messages">
                  <Messages ref={tagUidMsgRef} />
                  <Messages ref={mediaMsgRef} />
                </div>
                <Stepper
                  linear={false}
                  ref={tagStepperRef}
                  onChangeStep={(event) => {
                    if (event.index <= tagStepMax) {
                      setTagStep(event.index);
                      return;
                    }
                    tagStepperRef.current?.setActiveStep(tagStep);
                  }}
                >
                  <StepperPanel header="IDs">
                    <div className="controls">
                      <div className="step-row">
                        <InputText
                          value={scanTagUid}
                          onChange={(event) => setScanTagUid(event.target.value)}
                          readOnly={tagUidMode === 'keep'}
                          placeholder="Tag-ID (10 Zeichen)"
                          className="p-inputtext-sm"
                        />
                        <Button
                          label="Neue UID vergeben"
                          size="small"
                          outlined={tagUidMode !== 'new'}
                          onClick={() => {
                            setTagUidMode('new');
                            setScanTagUid(generateTagId());
                          }}
                        />
                        <span className="step-row-spacer" />
                        <Button
                          label="Weiter zu Medien"
                          size="small"
                          onClick={() => {
                            const uid = scanTagUid.trim();
                            if (!isValidTagUid(uid)) {
                              setTagUidError(
                                'UID ungueltig. Bitte 10 Zeichen (a-z, 0-9) oder TAG_ + 8 Zeichen verwenden.'
                              );
                              return;
                            }
                          if (tagUidError && tagUidError !== autoReplaceMsg) {
                            setTagUidError('');
                          }
                            setTagStepOneDone(true);
                            setTagStepMax(1);
                            setTagStep(1);
                          }}
                        />
                      </div>
                      <InputText
                        value={scanTagLabel}
                        onChange={(event) => setScanTagLabel(event.target.value)}
                        placeholder="Label (optional)"
                        className="p-inputtext-sm is-hidden"
                      />
                    </div>
                  </StepperPanel>
                  <StepperPanel header="Medienzuordnung">
                    <div className="controls">
                      <div className="step-row">
                        <Dropdown
                          value={scanMediaPath}
                          onChange={(event) => setScanMediaPath(event.value)}
                          options={collectTopLevelFolders(mediaTree).map((folderPath) => ({
                            label: folderPath,
                            value: folderPath,
                          }))}
                          placeholder="Medienordner waehlen"
                          filter
                          className="p-inputtext-sm"
                          disabled={!tagStepOneDone}
                        />
                        <span className="step-row-spacer" />
                        <Button
                          label="Weiter zur Anlage"
                          size="small"
                          disabled={!tagStepOneDone}
                          onClick={() => {
                            setTagStepTwoDone(true);
                            setTagStepMax(2);
                            setTagStep(2);
                          }}
                        />
                      </div>
                    </div>
                  </StepperPanel>
                  <StepperPanel header="Anlage">
                    <div className="controls">
                      <div className="summary">
                        <p className="summary-title">Zusammenfassung</p>
                        <div className="summary-grid">
                          <span>System-UID</span>
                          <strong>{scanTagUid || '-'}</strong>
                          <span>UID-Modus</span>
                          <strong>{tagUidMode === 'keep' ? 'Beibehalten' : 'Neu vergeben'}</strong>
                          <span>Label</span>
                          <strong>{scanTagLabel?.trim() || '-'}</strong>
                          <span>Medienordner</span>
                          <strong>{scanMediaPath || 'Spaeter zuordnen'}</strong>
                          <span>Hardware-UID</span>
                          <strong>
                            {lastHardwareUid.uid === activeNfc?.uid && lastHardwareUid.hardwareUid
                              ? lastHardwareUid.hardwareUid
                              : activeNfc?.hardwareUid || '-'}
                          </strong>
                        </div>
                      </div>
                      <div className="step-actions">
                        <Button
                          label="Daten zum Tag speichern"
                          size="small"
                          disabled={!tagStepOneDone || !tagStepTwoDone}
                          onClick={handleClaimTagForScan}
                        />
                      </div>
                    </div>
                  </StepperPanel>
                </Stepper>
                {tags.some((tag) => tag.status === 'IMPORTED') && (
                  <div className="controls">
                    <select className="p-inputtext-sm"
                      value={reuseTagUid}
                      onChange={(event) => setReuseTagUid(event.target.value)}
                    >
                      <option value="" disabled>
                        Gespeicherte Tag-ID waehlen
                      </option>
                      {tags
                        .filter((tag) => tag.status === 'IMPORTED')
                        .map((tag) => (
                          <option key={tag.uid} value={tag.uid}>
                            {tag.alias ? `${tag.alias} (${tag.uid})` : tag.uid}
                          </option>
                        ))}
                    </select>
                    <Button
                      label="Vorhandene ID schreiben"
                      size="small"
                      onClick={handleReuseImportedTag}
                    />
                  </div>
                )}
            </Card>
          </div>
          )}
          {renderSection()}
        </main>

        <aside className="right-panel">{systemPanels}</aside>
      </div>

      <nav className="bottom-nav">
        {NAV_ITEMS.map((item) => {
          const Icon = item.icon;
          return (
            <Button
              key={item.id}
              className={activeSection === item.id ? 'active' : ''}
              onClick={() => setActiveSection(item.id)}
            >
              {typeof Icon === 'string' ? (
                <i className={Icon} />
              ) : (
                <span className="nav-icon">
                  <Icon size={18} />
                </span>
              )}
              <span>{item.label}</span>
            </Button>
          );
        })}
        <Button
          className="bottom-action"
          onClick={() => setShowSessionSheet(true)}
        >
          <span className="nav-icon">
            <Wrench size={18} />
          </span>
          <span>ToolHub</span>
        </Button>
      </nav>

      {activeModal === 'new-folder' && (
        <div
          className="modal-backdrop"
          onClick={() => {
            setActiveModal('');
            setUploadAfterCreate(false);
            setPendingUploadFiles([]);
            setUploadSize(0);
            setUploadNameError('');
            setTagDeleteTarget('');
          }}
        >
          <div
            className="modal-card modal-upload"
            onClick={(event) => event.stopPropagation()}
            ref={modalRef}
          >
            <h3>{uploadAfterCreate ? 'Upload vorbereiten' : 'Neuen Ordner anlegen'}</h3>
            <InputText
              value={newFolderName}
              onChange={(event) => {
                setNewFolderName(event.target.value);
                if (uploadNameError) setUploadNameError('');
              }}
              placeholder={
                uploadAfterCreate && !currentPath
                  ? 'Ordnername'
                  : uploadAfterCreate
                    ? 'Optionaler Ordnername (leer = aktueller Ordner)'
                    : 'Ordnername'
              }
              className="p-inputtext-sm"
            />
            {uploadAfterCreate && uploadNameError && (
              <p className="error">{uploadNameError}</p>
            )}
            {uploadAfterCreate && (
              <>
                <FileUpload
                  ref={fileUploadRef}
                  name="files[]"
                  multiple
                  accept="audio/*"
                  customUpload
                  auto={false}
                  disabled={uploadInProgress}
                  chooseOptions={{ icon: 'pi pi-file-arrow-up', iconOnly: true, className: 'p-button-rounded p-button-outlined' }}
                  uploadOptions={{ icon: 'pi pi-cloud-upload', iconOnly: true, className: 'p-button-success p-button-rounded p-button-outlined' }}
                  cancelOptions={{ icon: 'pi pi-times', iconOnly: true, className: 'p-button-danger p-button-rounded p-button-outlined' }}
                  headerTemplate={fileHeaderTemplate}
                  itemTemplate={fileItemTemplate}
                  onSelect={(event) => {
                    const audioFiles = (event.files || []).filter((file) =>
                      file.type.startsWith('audio/')
                    );
                    setPendingUploadFiles(audioFiles);
                    const nextSize = audioFiles.reduce((sum, file) => sum + file.size, 0);
                    setUploadSize(nextSize);
                    setActiveUploadLabel(
                      audioFiles.length
                        ? `Upload: ${audioFiles.length} Datei(en)`
                        : ''
                    );
                  }}
                  uploadHandler={(event) => {
                    const audioFiles = (event.files || []).filter((file) =>
                      file.type.startsWith('audio/')
                    );
                    setPendingUploadFiles(audioFiles);
                    const nextSize = audioFiles.reduce((sum, file) => sum + file.size, 0);
                    setUploadSize(nextSize);
                    setActiveUploadLabel(
                      audioFiles.length
                        ? `Upload: ${audioFiles.length} Datei(en)`
                        : ''
                    );
                    handleCreateFolder(audioFiles);
                  }}
                  onClear={() => {
                    setPendingUploadFiles([]);
                    setUploadSize(0);
                  }}
                  emptyTemplate={
                    <div style={{ display: 'flex', alignItems: 'center', flexDirection: 'column', gap: '0.75rem' }}>
                      <i className="pi pi-file-arrow-up" style={{ fontSize: '5em' }} />
                      <span style={{ fontSize: '1.2em', color: 'var(--text-color-secondary)' }}>
                        Drag and Drop Audio Here
                      </span>
                      <div className="upload-item-footer">
                        <span className="upload-item-total">Gesamt: {formatSize(uploadSize)}</span>
                      </div>
                    </div>
                  }
                />
                {pendingUploadFiles.length > 0 && (
                  <div className="upload-item-footer">
                    <span className="upload-item-total">Gesamt: {formatSize(uploadSize)}</span>
                    <span className="upload-item-count">
                      {pendingUploadFiles.length} Datei(en)
                    </span>
                  </div>
                )}
              </>
            )}
            <div className="modal-actions">
              <Button
                className="button-ghost"
                label="Abbrechen"
                onClick={() => {
                  setActiveModal('');
                  setUploadAfterCreate(false);
                  setPendingUploadFiles([]);
                  setUploadSize(0);
                  setUploadNameError('');
                  setTagDeleteTarget('');
                }}
              />
              <Button
                label={uploadAfterCreate ? 'Upload starten' : 'Ordner anlegen'}
                onClick={() => {
                  if (uploadAfterCreate) {
                    setActiveModal('');
                  }
                  handleCreateFolder();
                }}
              />
            </div>
          </div>
        </div>
      )}

      {activeModal === 'rename' && (
        <div
          className="modal-backdrop"
          onClick={() => {
            setActiveModal('');
            setRenameName('');
          }}
        >
          <div
            className="modal-card modal-tag-delete"
            onClick={(event) => event.stopPropagation()}
            ref={modalRef}
          >
            <h3>Umbenennen</h3>
            <InputText
              value={renameName}
              onChange={(event) => setRenameName(event.target.value)}
              placeholder="Neuer Name"
              className="p-inputtext-sm"
            />
            <div className="modal-actions">
              <Button
                className="button-ghost"
                label="Abbrechen"
                onClick={() => {
                  setActiveModal('');
                  setRenameName('');
                }}
              />
              <Button label="Speichern" onClick={handleRename} />
            </div>
          </div>
        </div>
      )}

      {activeModal === 'delete' && (
        <div
          className="modal-backdrop"
          onClick={() => {
            setActiveModal('');
          }}
        >
          <div
            className="modal-card"
            onClick={(event) => event.stopPropagation()}
            ref={modalRef}
          >
            <h3>Eintraege loeschen</h3>
            <p className="muted">{selectedPaths.length} Eintraege werden geloescht.</p>
            <div className="modal-actions">
              <Button className="button-ghost" label="Abbrechen" onClick={() => setActiveModal('')} />
              <Button severity="danger" onClick={handleDeleteSelected} label="Loeschen" />
            </div>
          </div>
        </div>
      )}

      {activeModal === 'move' && (
        <div
          className="modal-backdrop"
          onClick={() => {
            setActiveModal('');
            setMoveTarget('');
          }}
        >
          <div
            className="modal-card"
            onClick={(event) => event.stopPropagation()}
            ref={modalRef}
          >
            <h3>Verschieben</h3>
            <Dropdown
              value={moveTarget || null}
              onChange={(event) => setMoveTarget(event.value)}
              options={[
                { label: 'media/', value: '__root__' },
                ...collectFolderPaths(mediaTree).map((folder) => ({
                  label: folder,
                  value: folder,
                })),
              ]}
              placeholder="Zielordner waehlen"
              filter
              className="p-inputtext-sm"
            />
            <div className="modal-actions">
              <Button
                className="button-ghost"
                label="Abbrechen"
                onClick={() => {
                  setActiveModal('');
                  setMoveTarget('');
                }}
              />
              <Button label="Verschieben" onClick={handleMoveSelected} />
            </div>
          </div>
        </div>
      )}

      {activeModal === 'tag-delete' && (
        <div
          className="modal-backdrop"
          onClick={() => {
            setActiveModal('');
            setTagDeleteTarget('');
          }}
        >
          <div
            className="modal-card"
            onClick={(event) => event.stopPropagation()}
            ref={modalRef}
          >
            <h3>Tag entfernen</h3>
            <p className="muted">
              Soll der Tag komplett geloescht werden oder nur die Medienzuweisung?
            </p>
            <div className="modal-actions">
              <Button
                className="button-ghost"
                label="Abbrechen"
                onClick={() => {
                  setActiveModal('');
                  setTagDeleteTarget('');
                }}
              />
              <Button
                label="Nur Medienzuordnung entfernen"
                className="modal-long-button"
                onClick={async () => {
                  if (!tagDeleteTarget) return;
                  await handleClearTagMedia(tagDeleteTarget);
                  setTagDeleteTarget('');
                  setActiveModal('');
                }}
              />
              <Button
                severity="danger"
                label="Tag loeschen"
                className="modal-long-button"
                onClick={async () => {
                  if (!tagDeleteTarget) return;
                  await handleClearTagMedia(tagDeleteTarget);
                  await handleDeleteTag(tagDeleteTarget);
                  setTagDeleteTarget('');
                  setActiveModal('');
                }}
              />
            </div>
          </div>
        </div>
      )}

      {boxDeleteTarget && (
        <div
          className="modal-backdrop"
          onClick={() => setBoxDeleteTarget('')}
        >
          <div
            className="modal-card"
            onClick={(event) => event.stopPropagation()}
            ref={modalRef}
          >
            <h3>Box entfernen</h3>
            <p className="muted">Soll die Box wirklich entkoppelt werden?</p>
            <div className="modal-actions">
              <Button
                className="button-ghost"
                label="Abbrechen"
                onClick={() => setBoxDeleteTarget('')}
              />
              <Button
                severity="danger"
                label="Box entfernen"
                onClick={async () => {
                  if (!boxDeleteTarget) return;
                  await handleUnpair(boxDeleteTarget);
                  setBoxDeleteTarget('');
                }}
              />
            </div>
          </div>
        </div>
      )}

      {activeModal === 'import-tag' && (
        <div className="modal-backdrop" role="presentation">
          <div className="modal-card" ref={modalRef}>
            <div className="modal-header">
              <h3>Tag vom Box-Speicher uebertragen</h3>
            </div>
            <div className="modal-body">
              <label className="modal-label" htmlFor="import-folder">
                Neuer Ordnername auf dem Server
              </label>
              <InputText
                id="import-folder"
                value={importTargetFolder}
                onChange={(event) => setImportTargetFolder(event.target.value)}
                placeholder="z. B. grimm_volume_3"
                className="p-inputtext-sm"
              />
              {uploadInProgress && (
                <div className="upload-progress">
                  <div style={{ width: `${uploadProgress}%` }} />
                </div>
              )}
            </div>
            <div className="modal-actions">
              <Button
                className="button-ghost"
                label="Abbrechen"
                onClick={() => {
                  setActiveModal('');
                  setImportTargetFolder('');
                  setImportTargetUid('');
                }}
              />
              <Button label="Uebertragen" onClick={handlePullTagFromBox} />
            </div>
          </div>
        </div>
      )}

      {showSessionSheet && (
        <div
          className="sheet-backdrop"
          onClick={() => setShowSessionSheet(false)}
        >
          <div
            className="sheet-card"
            onClick={(event) => event.stopPropagation()}
          >
            <div className="sheet-header">
              <strong>ToolHub</strong>
              <Button
                className="icon-button"
                icon="pi pi-times"
                onClick={() => setShowSessionSheet(false)}
                aria-label="Schliessen"
              />
            </div>
            {drawerTab === 'boxes' && (
              <>
                {paired.length === 0 && (
                  <p className="muted">Keine gepairten Boxen.</p>
                )}
                <div className="session-list">
                  {paired.map((box) => {
                    const isActive = selectedId === box.box_id;
                    const label = (box.alias || box.box_id || '').slice(0, 2).toUpperCase();
                    const availability = getAvailability(box.last_seen);
                    return (
                      <Button
                        key={box.box_id}
                        className={`session-card${isActive ? ' active' : ''}`}
                        onClick={() => {
                          setSelectedId(box.box_id);
                          setShowSessionSheet(false);
                        }}
                      >
                        <Avatar label={label || 'BX'} shape="circle" />
                        <div>
                          <strong>{box.alias || box.box_id}</strong>
                          {isActive && <span className="meta">Aktiv</span>}
                        </div>
                        <Tag value={availability.label} severity={availability.severity} rounded />
                      </Button>
                    );
                  })}
                </div>
              </>
            )}
            {drawerTab === 'system' && (
              <div className="drawer-system">{systemPanels}</div>
            )}
            <div className="drawer-tabs">
              <Button
                className={`drawer-tab${drawerTab === 'boxes' ? ' active' : ''}`}
                onClick={() => setDrawerTab('boxes')}
              >
                <span className="nav-icon">
                  <Cube size={18} />
                </span>
                <span>Boxen</span>
              </Button>
              <Button
                className={`drawer-tab${drawerTab === 'system' ? ' active' : ''}`}
                onClick={() => setDrawerTab('system')}
              >
                <span className="nav-icon">
                  <GearSix size={18} />
                </span>
                <span>System</span>
              </Button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
