(() => {
  function normalizeSearchText(value, options = {}) {
    const caseSensitive = Boolean(options.caseSensitive);
    const collapseWhitespace = Boolean(options.collapseWhitespace);
    let text = String(value ?? '');

    if (collapseWhitespace) text = text.trim().replace(/\s+/g, ' ');
    if (!caseSensitive) text = text.toLocaleLowerCase();

    const protectedGerman = text
      .replace(/ä/g, '\uE000')
      .replace(/ö/g, '\uE001')
      .replace(/ü/g, '\uE002')
      .replace(/ß/g, '\uE003');

    return protectedGerman
      .normalize('NFKD')
      .replace(/[\u0300-\u036f]/g, '')
      .replace(/\uE000/g, 'ä')
      .replace(/\uE001/g, 'ö')
      .replace(/\uE002/g, 'ü')
      .replace(/\uE003/g, 'ß');
  }

  function germanSearchKey(value, options = {}) {
    let text = normalizeSearchText(value, options);

    // Mark only plausible ASCII umlaut spellings. The marker keeps a real or
    // transliterated umlaut distinct from natural letter pairs in words such
    // as "Bauer", "teuer" and "euer".
    text = text.replace(/(^|[^aeiouäöü])([aou])e/g, '$1$2~e');

    return text
      .replace(/ä/g, 'a~e')
      .replace(/ö/g, 'o~e')
      .replace(/ü/g, 'u~e')
      .replace(/ß/g, 's~s');
  }

  function searchTextMatches(value, term, options = {}) {
    const directValue = normalizeSearchText(value, options);
    const directTerm = normalizeSearchText(term, options);
    if (!directTerm) return true;
    if (directValue.includes(directTerm)) return true;
    return germanSearchKey(value, options).includes(germanSearchKey(term, options));
  }

  // Standalone filters and duplicate detection use the exact same comparison
  // rules as the generic table filters.
  window.AssetManagerNormalizeSearchText = normalizeSearchText;
  window.AssetManagerGermanSearchKey = germanSearchKey;
  window.AssetManagerSearchTextMatches = searchTextMatches;

  const collator = new Intl.Collator(document.documentElement.lang || 'de', {
    numeric: true,
    sensitivity: 'base'
  });

  function controlValue(control) {
    if (control.tagName === 'SELECT') {
      const selected = control.options?.[control.selectedIndex];
      return [
        control.value || '',
        selected?.textContent || ''
      ].join(' ');
    }

    if (control.type === 'checkbox' || control.type === 'radio') {
      return control.checked
        ? [
            control.value || '',
            control.getAttribute('aria-label') || '',
            control.closest('label')?.textContent || ''
          ].join(' ')
        : '';
    }

    return [
      control.value || '',
      control.getAttribute('placeholder') || '',
      control.getAttribute('aria-label') || ''
    ].join(' ');
  }

  function textOutsideControls(cell) {
    const clone = cell.cloneNode(true);
    clone.querySelectorAll('input, select, textarea, option').forEach(node => node.remove());
    return (clone.textContent || '').trim();
  }

  function textValue(cell) {
    if (!cell) return '';

    const explicitFilterValue = cell.dataset.filterValue || '';

    if (cell.dataset.sortValue !== undefined) {
      return `${cell.dataset.sortValue} ${explicitFilterValue}`.trim();
    }

    const controls = [...cell.querySelectorAll('input, select, textarea')];
    if (controls.length) {
      const controlText = controls.map(controlValue).join(' ').trim();
      const additionalText = textOutsideControls(cell);
      return `${explicitFilterValue} ${controlText} ${additionalText}`.trim();
    }

    return `${explicitFilterValue} ${cell.textContent || ''}`.trim();
  }

  function parsedValue(value) {
    const text = value.trim();
    if (!text) return { type: 'empty', value: '' };

    if (/^\d{4}-\d{2}-\d{2}(?:[ T].*)?$/.test(text)) {
      const timestamp = Date.parse(text.replace(' ', 'T'));
      if (!Number.isNaN(timestamp)) return { type: 'date', value: timestamp };
    }

    if (/^\d{1,2}\.\d{1,2}\.\d{4}$/.test(text)) {
      const [day, month, year] = text.split('.').map(Number);
      return { type: 'date', value: new Date(year, month - 1, day).getTime() };
    }

    if (/^(?:25[0-5]|2[0-4]\d|1?\d?\d)(?:\.(?:25[0-5]|2[0-4]\d|1?\d?\d)){3}$/.test(text)) {
      return { type: 'ipv4', value: text.split('.').map(Number) };
    }

    const numeric = text.replace(/\s/g, '').replace(',', '.');
    if (/^[-+]?\d+(?:\.\d+)?$/.test(numeric)) {
      return { type: 'number', value: Number(numeric) };
    }

    return { type: 'text', value: text };
  }

  function compareValues(a, b) {
    const left = parsedValue(a);
    const right = parsedValue(b);

    if (left.type === 'empty' && right.type !== 'empty') return 1;
    if (right.type === 'empty' && left.type !== 'empty') return -1;

    if (left.type === right.type && (left.type === 'number' || left.type === 'date')) {
      return left.value - right.value;
    }

    if (left.type === 'ipv4' && right.type === 'ipv4') {
      for (let index = 0; index < 4; index += 1) {
        const difference = left.value[index] - right.value[index];
        if (difference) return difference;
      }
      return 0;
    }

    return collator.compare(String(left.value), String(right.value));
  }

  function initTable(table) {
    const thead = table.tHead;
    const tbody = table.tBodies[0];
    if (!thead || !tbody || !thead.rows.length) return;
    if (table.dataset.tableToolsReady === '1') return;

    const headerRow = thead.rows[0];

    // Add a purely visual row-number column before every real table column.
    // It is recalculated after filtering, sorting and partial server reloads and
    // therefore never represents a database identifier.
    if (!headerRow.querySelector('th[data-row-number-column="1"]')) {
      const numberHeader = document.createElement('th');
      numberHeader.className = 'table-row-number-header';
      numberHeader.dataset.rowNumberColumn = '1';
      numberHeader.dataset.noSort = '1';
      numberHeader.dataset.noFilter = '1';
      numberHeader.textContent = '#';
      numberHeader.title = document.body.dataset.rowNumberLabel || 'Row number';
      headerRow.insertBefore(numberHeader, headerRow.firstChild);
    }

    const numberHeader = headerRow.querySelector('th[data-row-number-column="1"]');
    const rowNumberStoragePart = table.dataset.storageKey || table.id || window.location.pathname;
    const rowNumberWidthKey = `assetmanager:table:${rowNumberStoragePart}:row-number-width`;
    const defaultRowNumberWidth = 64;
    const minimumRowNumberWidth = 48;
    const maximumRowNumberWidth = 180;

    function readStoredRowNumberWidth() {
      try {
        const stored = Number.parseInt(window.localStorage.getItem(rowNumberWidthKey) || '', 10);
        if (Number.isFinite(stored)) {
          return Math.min(maximumRowNumberWidth, Math.max(minimumRowNumberWidth, stored));
        }
      } catch (_) {}
      return defaultRowNumberWidth;
    }

    function applyRowNumberWidth(width) {
      const normalized = Math.min(maximumRowNumberWidth, Math.max(minimumRowNumberWidth, Math.round(width)));
      table.style.setProperty('--table-row-number-width', `${normalized}px`);
      table.dataset.rowNumberWidth = String(normalized);
      return normalized;
    }

    function installRowNumberResizer() {
      if (!numberHeader || numberHeader.querySelector('.table-row-number-resizer')) return;
      const handle = document.createElement('span');
      handle.className = 'table-row-number-resizer';
      handle.setAttribute('role', 'separator');
      handle.setAttribute('aria-orientation', 'vertical');
      handle.tabIndex = 0;
      handle.title = document.body.dataset.rowNumberResizeLabel || 'Drag to change column width';
      numberHeader.appendChild(handle);

      const saveWidth = width => {
        const normalized = applyRowNumberWidth(width);
        try { window.localStorage.setItem(rowNumberWidthKey, String(normalized)); } catch (_) {}
      };

      handle.addEventListener('pointerdown', event => {
        event.preventDefault();
        event.stopPropagation();
        const startX = event.clientX;
        const startWidth = Number.parseInt(table.dataset.rowNumberWidth || '', 10) || defaultRowNumberWidth;
        handle.setPointerCapture?.(event.pointerId);
        document.documentElement.classList.add('table-column-resizing');

        const move = moveEvent => applyRowNumberWidth(startWidth + moveEvent.clientX - startX);
        const finish = finishEvent => {
          saveWidth(Number.parseInt(table.dataset.rowNumberWidth || '', 10) || startWidth);
          handle.releasePointerCapture?.(finishEvent.pointerId);
          document.documentElement.classList.remove('table-column-resizing');
          handle.removeEventListener('pointermove', move);
          handle.removeEventListener('pointerup', finish);
          handle.removeEventListener('pointercancel', finish);
        };
        handle.addEventListener('pointermove', move);
        handle.addEventListener('pointerup', finish);
        handle.addEventListener('pointercancel', finish);
      });

      handle.addEventListener('keydown', event => {
        if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
        event.preventDefault();
        const current = Number.parseInt(table.dataset.rowNumberWidth || '', 10) || defaultRowNumberWidth;
        saveWidth(current + (event.key === 'ArrowRight' ? 8 : -8));
      });
    }

    applyRowNumberWidth(readStoredRowNumberWidth());
    installRowNumberResizer();


    // Every visible data column can be resized independently. Widths are
    // stored per table and per field/index so they survive navigation,
    // reloads and container updates. The visual row-number column keeps its
    // dedicated implementation above.
    const columnWidthStoragePrefix = `assetmanager:table:${rowNumberStoragePart}:column-width:`;
    const minimumColumnWidth = 56;
    const maximumColumnWidth = 1200;

    function columnStorageId(header, columnIndex) {
      const field = header?.dataset?.field;
      if (field) return field;
      return `index-${columnIndex}`;
    }

    function normalizeColumnWidth(width) {
      return Math.min(maximumColumnWidth, Math.max(minimumColumnWidth, Math.round(width)));
    }

    function applyColumnWidth(columnIndex, width) {
      const normalized = normalizeColumnWidth(width);
      const rows = [
        ...thead.rows,
        ...tbody.rows
      ];
      rows.forEach(row => {
        const cell = row.cells?.[columnIndex];
        if (!cell || cell.dataset.rowNumberColumn === '1') return;
        cell.style.width = `${normalized}px`;
        cell.style.minWidth = `${normalized}px`;
        cell.style.maxWidth = `${normalized}px`;
      });
      return normalized;
    }

    function readStoredColumnWidth(header, columnIndex) {
      try {
        const key = `${columnWidthStoragePrefix}${columnStorageId(header, columnIndex)}`;
        const stored = Number.parseInt(window.localStorage.getItem(key) || '', 10);
        if (Number.isFinite(stored)) return normalizeColumnWidth(stored);
      } catch (_) {}
      return null;
    }

    function saveColumnWidth(header, columnIndex, width) {
      const normalized = applyColumnWidth(columnIndex, width);
      try {
        const key = `${columnWidthStoragePrefix}${columnStorageId(header, columnIndex)}`;
        window.localStorage.setItem(key, String(normalized));
      } catch (_) {}
      return normalized;
    }

    function freezeTableGeometry() {
      if (table.id === 'asset-table') return;
      const widths = [...headerRow.cells].map(cell => Math.max(minimumColumnWidth, Math.round(cell.getBoundingClientRect().width || minimumColumnWidth)));
      const total = widths.reduce((sum, width) => sum + width, 0);
      widths.forEach((width, columnIndex) => {
        if (headerRow.cells[columnIndex]?.dataset.rowNumberColumn === '1') return;
        applyColumnWidth(columnIndex, width);
      });
      table.style.tableLayout = 'fixed';
      table.style.width = `${total}px`;
      table.style.minWidth = `${total}px`;
    }

    function synchronizeTableWidth() {
      if (table.id === 'asset-table') return;
      const total = [...headerRow.cells].reduce((sum, cell) => sum + Math.round(cell.getBoundingClientRect().width || 0), 0);
      if (total > 0) {
        table.style.width = `${total}px`;
        table.style.minWidth = `${total}px`;
      }
    }

    function applyStoredColumnWidths() {
      let restored = false;
      [...headerRow.cells].forEach((header, columnIndex) => {
        if (header.dataset.rowNumberColumn === '1') return;
        const stored = readStoredColumnWidth(header, columnIndex);
        if (stored !== null) {
          applyColumnWidth(columnIndex, stored);
          restored = true;
        }
      });
      if (restored && table.id !== 'asset-table') {
        table.style.tableLayout = 'fixed';
        requestAnimationFrame(synchronizeTableWidth);
      }
    }

    function installColumnResizers() {
      [...headerRow.cells].forEach((header, columnIndex) => {
        if (header.dataset.rowNumberColumn === '1') return;
        if (header.dataset.noResize === '1' || header.dataset.rowNumberColumn === '1') return;
        if (header.querySelector(':scope > .table-column-resizer')) return;

        header.classList.add('table-resizable-column-header');
        const handle = document.createElement('span');
        handle.className = 'table-column-resizer';
        handle.setAttribute('role', 'separator');
        handle.setAttribute('aria-orientation', 'vertical');
        handle.tabIndex = 0;
        handle.title = document.body.dataset.columnResizeLabel || 'Drag to change the column width';
        header.appendChild(handle);

        handle.addEventListener('click', event => {
          event.preventDefault();
          event.stopPropagation();
        });

        handle.addEventListener('pointerdown', event => {
          event.preventDefault();
          event.stopPropagation();
          const startX = event.clientX;
          freezeTableGeometry();

          const headers = [...headerRow.cells];
          const rightmostIndex = headers.length - 1;
          const startWidth = header.getBoundingClientRect().width || minimumColumnWidth;
          const rightmostHeader = headers[rightmostIndex];
          const rightmostStartWidth = rightmostHeader?.getBoundingClientRect().width || minimumColumnWidth;
          const keepTableWidth = columnIndex !== rightmostIndex;

          document.documentElement.classList.add('table-column-resizing');

          const resizePair = delta => {
            if (!keepTableWidth) {
              applyColumnWidth(columnIndex, startWidth + delta);
              synchronizeTableWidth();
              return;
            }

            // All columns between the dragged column and the far-right column
            // stay unchanged. The far-right column alone absorbs the delta.
            const minDelta = minimumColumnWidth - startWidth;
            const maxDelta = rightmostStartWidth - minimumColumnWidth;
            const effectiveDelta = Math.max(minDelta, Math.min(maxDelta, delta));
            applyColumnWidth(columnIndex, startWidth + effectiveDelta);
            applyColumnWidth(rightmostIndex, rightmostStartWidth - effectiveDelta);
          };

          const move = moveEvent => resizePair(moveEvent.clientX - startX);
          const finish = () => {
            saveColumnWidth(header, columnIndex, header.getBoundingClientRect().width || startWidth);
            if (keepTableWidth && rightmostHeader) {
              saveColumnWidth(rightmostHeader, rightmostIndex, rightmostHeader.getBoundingClientRect().width || rightmostStartWidth);
            } else {
              synchronizeTableWidth();
            }
            document.documentElement.classList.remove('table-column-resizing');
            window.removeEventListener('pointermove', move, true);
            window.removeEventListener('pointerup', finish, true);
            window.removeEventListener('pointercancel', finish, true);
          };
          window.addEventListener('pointermove', move, true);
          window.addEventListener('pointerup', finish, true);
          window.addEventListener('pointercancel', finish, true);
        });

        handle.addEventListener('keydown', event => {
          if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
          event.preventDefault();
          event.stopPropagation();
          freezeTableGeometry();
          const headers = [...headerRow.cells];
          const rightmostIndex = headers.length - 1;
          const step = event.key === 'ArrowRight' ? 12 : -12;
          const current = header.getBoundingClientRect().width || minimumColumnWidth;
          if (columnIndex === rightmostIndex) {
            saveColumnWidth(header, columnIndex, current + step);
            synchronizeTableWidth();
            return;
          }
          const rightmostHeader = headers[rightmostIndex];
          const rightmostWidth = rightmostHeader?.getBoundingClientRect().width || minimumColumnWidth;
          const minDelta = minimumColumnWidth - current;
          const maxDelta = rightmostWidth - minimumColumnWidth;
          const effectiveDelta = Math.max(minDelta, Math.min(maxDelta, step));
          saveColumnWidth(header, columnIndex, current + effectiveDelta);
          if (rightmostHeader) saveColumnWidth(rightmostHeader, rightmostIndex, rightmostWidth - effectiveDelta);
        });
      });
    }

    function ensureRowNumberCells() {
      [...tbody.rows].forEach(row => {
        if (row.querySelector('td[data-row-number-column="1"]')) return;
        const cell = document.createElement('td');
        cell.className = 'table-row-number-cell';
        cell.dataset.rowNumberColumn = '1';
        cell.setAttribute('aria-hidden', 'true');
        row.insertBefore(cell, row.firstChild);
      });
    }
    ensureRowNumberCells();

    const isAssetTable = table.id === 'asset-table';
    const usesServerFilters = table.dataset.serverFilters === '1';
    let serverFilterTimer = null;
    let serverFilterController = null;
    let serverFilterRequestId = 0;

    // Normal tables get stable synthetic keys. The asset table already has
    // real data-field keys on its movable columns; selection and action
    // columns must remain without a field key.
    if (!isAssetTable) {
      let syntheticIndex = 0;
      [...headerRow.cells].forEach(header => {
        if (header.dataset.rowNumberColumn === '1') return;
        if (!header.dataset.field) {
          header.dataset.field = `__col_${syntheticIndex}`;
          header.dataset.syntheticField = '1';
        }
        syntheticIndex += 1;
      });

      [...tbody.rows].forEach(row => {
        [...row.cells].forEach((cell, index) => {
          if (cell.dataset.rowNumberColumn === '1') return;
          const header = headerRow.cells[index];
          if (header && !cell.dataset.field) {
            cell.dataset.field = header.dataset.field;
            cell.dataset.syntheticField = '1';
          }
        });
      });
    }

    const filterRow = document.createElement('tr');
    filterRow.className = 'column-filter-row';
    filterRow.dataset.tableFilterRow = '1';

    const filterDefinitions = [];

    function filterStatusTarget() {
      const wrapper = table.closest(
        '#asset-table-scroll, .management-table-scroll, .table-scroll, .table-wrap, .sticky-table'
      );
      return wrapper || table;
    }

    function ensureFilterStatus() {
      let status = table._filterStatusElement;
      if (status?.isConnected) return status;

      status = document.createElement('div');
      status.className = 'table-filter-status';
      status.hidden = true;
      status.setAttribute('role', 'status');
      status.setAttribute('aria-live', 'polite');

      const icon = document.createElement('span');
      icon.className = 'table-filter-status-icon';
      icon.textContent = '⌕';
      icon.setAttribute('aria-hidden', 'true');

      const label = document.createElement('strong');
      label.className = 'table-filter-status-label';

      const details = document.createElement('span');
      details.className = 'table-filter-status-details';

      const clearButton = document.createElement('button');
      clearButton.type = 'button';
      clearButton.className = 'button button-secondary table-filter-clear';
      clearButton.textContent =
        document.body.dataset.clearFiltersLabel || 'Filter zurücksetzen';
      clearButton.addEventListener('click', () => {
        filterDefinitions.forEach(definition => {
          definition.input.value = '';
        });
        applyFilters();
        if (usesServerFilters) submitServerFilters(true);

        // Keep the persisted browser state in sync with the visible table.
        // Programmatic value changes do not trigger input/change automatically.
        window.dispatchEvent(new CustomEvent('asset-table-filters-cleared', {
          detail: { table }
        }));

        filterDefinitions[0]?.input?.focus();
      });

      status.append(icon, label, details, clearButton);

      const target = filterStatusTarget();
      target.parentNode?.insertBefore(status, target);
      table._filterStatusElement = status;
      return status;
    }

    function updateFilterPresentation(active, visibleRows, totalRows) {
      filterDefinitions.forEach(definition => {
        const hasValue = Boolean(definition.input.value.trim());
        definition.input.classList.toggle('filter-input-active', hasValue);
        definition.input.closest('th')?.classList.toggle('filter-cell-active', hasValue);
      });

      const status = ensureFilterStatus();
      const label = status.querySelector('.table-filter-status-label');
      const details = status.querySelector('.table-filter-status-details');
      const activeCount = active.length;

      status.hidden = activeCount === 0;
      status.classList.toggle('is-active', activeCount > 0);

      if (activeCount > 0) {
        const activeTemplate =
          document.body.dataset.filtersActiveTemplate || '{count} Filter aktiv';
        const statusTemplate =
          document.body.dataset.filterStatusTemplate ||
          'Gefiltert: {visible} von {total} Datensätzen';

        label.textContent = activeTemplate.replace('{count}', String(activeCount));
        details.textContent = statusTemplate
          .replace('{visible}', String(visibleRows))
          .replace('{total}', String(totalRows));
      }
    }

    function cellForDefinition(row, definition) {
      if (definition.field) {
        return row.querySelector(`td[data-field="${CSS.escape(definition.field)}"]`);
      }
      return row.cells[definition.columnIndex] || null;
    }

    function rebuildMissingCellKeys() {
      ensureRowNumberCells();
      if (isAssetTable) return;

      [...tbody.rows].forEach(row => {
        [...row.cells].forEach((cell, index) => {
          if (cell.dataset.rowNumberColumn === '1') return;
          const header = headerRow.cells[index];
          if (header && !cell.dataset.field) {
            cell.dataset.field = header.dataset.field;
          }
        });
      });
    }

    function numericFilterMatches(rawValue, rawTerm) {
      const value = Number(String(rawValue).trim().replace(',', '.'));
      if (!Number.isFinite(value)) return false;

      const term = String(rawTerm || '').trim().toLocaleLowerCase();
      if (!term) return true;

      const normalize = input => input.replace(',', '.').trim();
      let match = term.match(/^(<=|>=|<|>|=)\s*(-?\d+(?:[.,]\d+)?)$/);
      if (match) {
        const expected = Number(normalize(match[2]));
        if (match[1] === '<') return value < expected;
        if (match[1] === '<=') return value <= expected;
        if (match[1] === '>') return value > expected;
        if (match[1] === '>=') return value >= expected;
        return value === expected;
      }

      match = term.match(/^(-?\d+(?:[.,]\d+)?)\s*(?:\.\.|-)\s*(-?\d+(?:[.,]\d+)?)$/);
      if (!match) {
        match = term.match(/^(?:zwischen|between)\s+(-?\d+(?:[.,]\d+)?)\s+(?:und|and)\s+(-?\d+(?:[.,]\d+)?)$/);
      }
      if (match) {
        const first = Number(normalize(match[1]));
        const second = Number(normalize(match[2]));
        const minimum = Math.min(first, second);
        const maximum = Math.max(first, second);
        return value >= minimum && value <= maximum;
      }

      const exact = Number(normalize(term));
      return Number.isFinite(exact) ? value === exact : false;
    }


    function submitServerFilters(immediate = false) {
      if (!usesServerFilters) return;
      if (serverFilterTimer) window.clearTimeout(serverFilterTimer);

      const navigate = async () => {
        const target = new URL(
          table.dataset.serverFilterUrl || window.location.pathname,
          window.location.origin
        );
        const current = new URL(window.location.href);
        const limit = current.searchParams.get('job_limit');
        if (limit) target.searchParams.set('job_limit', limit);

        filterDefinitions.forEach(definition => {
          if (!definition.serverName) return;
          const value = definition.input.value.trim();
          if (value) target.searchParams.set(`job_filter_${definition.serverName}`, value);
        });

        const requestId = ++serverFilterRequestId;
        serverFilterController?.abort();
        serverFilterController = new AbortController();
        table.classList.add('server-filter-loading');
        table.setAttribute('aria-busy', 'true');

        try {
          const response = await fetch(target.toString(), {
            cache: 'no-store',
            headers: {'X-Requested-With': 'AssetManagerTableFilter'},
            signal: serverFilterController.signal,
          });
          if (!response.ok) throw new Error(`HTTP ${response.status}`);
          const html = await response.text();
          if (requestId !== serverFilterRequestId) return;

          const documentCopy = new DOMParser().parseFromString(html, 'text/html');
          const replacement = documentCopy.querySelector(
            `table[data-storage-key="${CSS.escape(table.dataset.storageKey || '')}"]`
          );
          const replacementBody = replacement?.tBodies?.[0];
          if (!replacement || !replacementBody) throw new Error('Filtered table not found');

          tbody.replaceChildren(...[...replacementBody.rows].map(row => row.cloneNode(true)));
          table.dataset.serverFilterTotal = replacement.dataset.serverFilterTotal || String(tbody.rows.length);
          window.history.replaceState({}, '', target.toString());
          window.AssetManagerTime?.scan(tbody);
          applyFilters();
          window.dispatchEvent(new CustomEvent('asset-table-content-reloaded', {
            detail: {table}
          }));
        } catch (error) {
          if (error?.name !== 'AbortError') {
            // Fall back to a normal navigation only when the partial reload fails.
            window.location.assign(target.toString());
          }
        } finally {
          if (requestId === serverFilterRequestId) {
            table.classList.remove('server-filter-loading');
            table.removeAttribute('aria-busy');
          }
        }
      };

      if (immediate) navigate();
      else serverFilterTimer = window.setTimeout(navigate, 700);
    }

    function updateRowNumbers() {
      let visibleIndex = 0;
      [...tbody.rows].forEach(row => {
        const cell = row.querySelector('td[data-row-number-column="1"]');
        if (!cell) return;
        if (row.hidden) {
          cell.textContent = '';
          return;
        }
        visibleIndex += 1;
        cell.textContent = String(visibleIndex);
      });
    }

    function applyFilters() {
      rebuildMissingCellKeys();

      const active = filterDefinitions
        .map(definition => ({
          definition,
          term: definition.input?.value.trim() || ''
        }))
        .filter(item => item.term);

      if (usesServerFilters) {
        const serverTotal = Number.parseInt(table.dataset.serverFilterTotal || '', 10);
        const total = Number.isFinite(serverTotal) ? serverTotal : tbody.rows.length;
        updateFilterPresentation(active, tbody.rows.length, total);
        updateRowNumbers();
        window.dispatchEvent(new CustomEvent('asset-table-filter-changed', {
          detail: {
            table,
            visible: tbody.rows.length,
            total,
            activeFilters: active.length
          }
        }));
        return;
      }

      let visibleRows = 0;

      [...tbody.rows].forEach(row => {
        const hidden = active.some(({ definition, term }) => {
          const cell = cellForDefinition(row, definition);
          const value = textValue(cell);
          if (definition.numericFilter) {
            return !numericFilterMatches(value, term);
          }
          return !searchTextMatches(value, term);
        });

        row.hidden = hidden;
        if (!hidden) visibleRows += 1;
      });

      updateFilterPresentation(active, visibleRows, tbody.rows.length);
      updateRowNumbers();

      window.dispatchEvent(new CustomEvent('asset-table-filter-changed', {
        detail: {
          table,
          visible: visibleRows,
          total: tbody.rows.length,
          activeFilters: active.length
        }
      }));
    }

    function applySort(field, direction, options = {}) {
      const header = [...headerRow.cells].find(item => item.dataset.field === field);
      if (!header || header.dataset.noSort) return false;

      const columnIndex = header.cellIndex;
      [...headerRow.cells].forEach(item => {
        delete item.dataset.sortDirection;
        item.classList.remove('sort-asc', 'sort-desc');
      });

      const normalizedDirection = direction === 'desc' ? 'desc' : 'asc';
      header.dataset.sortDirection = normalizedDirection;
      header.classList.add(normalizedDirection === 'asc' ? 'sort-asc' : 'sort-desc');

      const rows = [...tbody.rows];
      rows.sort((left, right) => {
        const definition = { field, columnIndex };
        const result = compareValues(
          textValue(cellForDefinition(left, definition)),
          textValue(cellForDefinition(right, definition))
        );
        return normalizedDirection === 'asc' ? result : -result;
      });

      const fragment = document.createDocumentFragment();
      rows.forEach(row => fragment.appendChild(row));
      tbody.appendChild(fragment);
      applyFilters();

      if (!options.silent) {
        window.dispatchEvent(new CustomEvent('asset-table-sort-changed', {
          detail: { table, field, direction: normalizedDirection }
        }));
      }
      return true;
    }

    [...headerRow.cells].forEach((header, columnIndex) => {
      const field = header.dataset.field || '';
      const serverName = header.dataset.serverFilterName || '';

      if (!header.dataset.noSort) {
        header.classList.add('sortable-column');
        header.title = header.dataset.sortTitle || 'Zum Sortieren anklicken';

        header.addEventListener('click', event => {
          if (event.target.closest('input,button,a,select,textarea,label')) return;
          const direction = header.dataset.sortDirection === 'asc' ? 'desc' : 'asc';
          applySort(field, direction);
        });
      }

      const filterCell = document.createElement('th');
      if (field) filterCell.dataset.field = field;
      filterCell.dataset.columnIndex = String(columnIndex);
      if (header.dataset.rowNumberColumn === '1') {
        filterCell.dataset.rowNumberColumn = '1';
        filterCell.className = 'table-row-number-filter';
      }

      if (!header.dataset.noFilter && (!usesServerFilters || serverName)) {
        const input = document.createElement('input');
        input.type = 'search';
        input.placeholder = header.dataset.filterPlaceholder || 'Filtern …';
        input.setAttribute('aria-label', `Filter ${textValue(header)}`);
        input.autocomplete = 'off';
        if (usesServerFilters) {
          input.name = `job_filter_${serverName}`;
          input.value = header.dataset.serverFilterValue || '';
          input.addEventListener('input', () => {
            applyFilters();
            submitServerFilters(false);
          });
          input.addEventListener('search', () => {
            applyFilters();
            submitServerFilters(false);
          });
          input.addEventListener('keydown', event => {
            if (event.key !== 'Enter') return;
            event.preventDefault();
            submitServerFilters(true);
          });
        } else {
          input.addEventListener('input', applyFilters);
          input.addEventListener('search', applyFilters);
        }

        filterCell.appendChild(input);
        filterDefinitions.push({
          field,
          columnIndex,
          input,
          serverName,
          numericFilter: header.dataset.numericFilter === '1'
        });
      }

      filterRow.appendChild(filterCell);
    });

    thead.appendChild(filterRow);
    if (isAssetTable) {
      installColumnResizers();
      applyStoredColumnWidths();
    }

    // Editable cells are always read live, so changed values immediately
    // participate in filtering without a stale cache.
    tbody.addEventListener('input', event => {
      if (event.target.matches('input,select,textarea')) applyFilters();
    });

    tbody.addEventListener('change', event => {
      if (event.target.matches('input,select,textarea')) applyFilters();
    });

    table.assetApplyFilters = applyFilters;
    table.assetApplySort = (field, direction, options = {}) => applySort(field, direction, options);
    table.assetUpdateRowNumbers = updateRowNumbers;
    table.assetRebuildFilterCache = rebuildMissingCellKeys;
    table.assetApplyStoredColumnWidths = applyStoredColumnWidths;
    table.dataset.tableToolsReady = '1';
    ensureFilterStatus();

    // Covers browser-restored values after reload/back navigation.
    requestAnimationFrame(() => {
      applyFilters();
      if (usesServerFilters) {
        try {
          const lastName = window.sessionStorage.getItem('assetmanager:software-jobs:last-server-filter');
          const lastInput = lastName
            ? filterDefinitions.find(definition => definition.input.name === lastName)?.input
            : null;
          if (lastInput) {
            lastInput.focus();
            lastInput.setSelectionRange(lastInput.value.length, lastInput.value.length);
          }
          window.sessionStorage.removeItem('assetmanager:software-jobs:last-server-filter');
        } catch (_) {}
      }
    });

    window.dispatchEvent(new CustomEvent('asset-table-structure-ready', {
      detail: { table }
    }));
  }

  function initUniversalTableResize(table) {
    if (!table || table.id === 'asset-table' || table.dataset.columnResizeReady === '1') return;
    const thead = table.tHead;
    const tbody = table.tBodies?.[0];
    if (!thead || !tbody || !thead.rows.length) return;

    const headerRow = thead.rows[0];
    const storagePart = table.dataset.storageKey || table.id || `${window.location.pathname}:table-${[...document.querySelectorAll('main table')].indexOf(table)}`;
    const storagePrefix = `assetmanager:table:${storagePart}:column-width:`;
    const minWidth = 56;
    const maxWidth = 1200;
    const normalize = width => Math.min(maxWidth, Math.max(minWidth, Math.round(width)));

    const columnKey = (header, index) => header.dataset.field || `index-${index}`;
    const applyWidth = (index, width) => {
      const normalized = normalize(width);
      [...thead.rows, ...tbody.rows].forEach(row => {
        const cell = row.cells?.[index];
        if (!cell) return;
        cell.style.width = `${normalized}px`;
        cell.style.minWidth = `${normalized}px`;
        cell.style.maxWidth = `${normalized}px`;
      });
      return normalized;
    };

    const freezeGeometry = () => {
      const widths = [...headerRow.cells].map(cell => normalize(cell.getBoundingClientRect().width || minWidth));
      const total = widths.reduce((sum, width) => sum + width, 0);
      widths.forEach((width, index) => applyWidth(index, width));
      table.style.tableLayout = 'fixed';
      table.style.width = `${total}px`;
      table.style.minWidth = `${total}px`;
    };

    const syncTableWidth = () => {
      const total = [...headerRow.cells].reduce((sum, cell) => sum + Math.round(cell.getBoundingClientRect().width || 0), 0);
      if (total > 0) {
        table.style.width = `${total}px`;
        table.style.minWidth = `${total}px`;
      }
    };

    [...headerRow.cells].forEach((header, index) => {
      if (header.dataset.noResize === '1' || header.dataset.rowNumberColumn === '1') return;
      const key = `${storagePrefix}${columnKey(header, index)}`;
      try {
        const stored = Number.parseInt(localStorage.getItem(key) || '', 10);
        if (Number.isFinite(stored)) applyWidth(index, stored);
      } catch (_) {}

      if (header.querySelector(':scope > .table-column-resizer')) return;
      header.classList.add('table-resizable-column-header');
      const handle = document.createElement('span');
      handle.className = 'table-column-resizer';
      handle.setAttribute('role', 'separator');
      handle.setAttribute('aria-orientation', 'vertical');
      handle.tabIndex = 0;
      handle.title = document.body.dataset.columnResizeLabel || 'Drag to change the column width';
      header.appendChild(handle);

      const save = width => {
        const normalized = applyWidth(index, width);
        try { localStorage.setItem(key, String(normalized)); } catch (_) {}
      };
      handle.addEventListener('click', event => { event.preventDefault(); event.stopPropagation(); });
      handle.addEventListener('pointerdown', event => {
        event.preventDefault(); event.stopPropagation();
        const startX = event.clientX;
        freezeGeometry();
        const headers = [...headerRow.cells];
        const rightmostIndex = headers.length - 1;
        const startWidth = header.getBoundingClientRect().width || minWidth;
        const rightmostHeader = headers[rightmostIndex];
        const rightmostStartWidth = rightmostHeader?.getBoundingClientRect().width || minWidth;
        const keepTableWidth = index !== rightmostIndex;
        document.documentElement.classList.add('table-column-resizing');

        const resizePair = delta => {
          if (!keepTableWidth) {
            applyWidth(index, startWidth + delta);
            syncTableWidth();
            return;
          }
          const minDelta = minWidth - startWidth;
          const maxDelta = rightmostStartWidth - minWidth;
          const effectiveDelta = Math.max(minDelta, Math.min(maxDelta, delta));
          applyWidth(index, startWidth + effectiveDelta);
          applyWidth(rightmostIndex, rightmostStartWidth - effectiveDelta);
        };

        const move = e => resizePair(e.clientX - startX);
        const finish = () => {
          save(header.getBoundingClientRect().width || startWidth);
          if (keepTableWidth && rightmostHeader) {
            const rightKey = `${storagePrefix}${columnKey(rightmostHeader, rightmostIndex)}`;
            const rightWidth = applyWidth(rightmostIndex, rightmostHeader.getBoundingClientRect().width || rightmostStartWidth);
            try { localStorage.setItem(rightKey, String(rightWidth)); } catch (_) {}
          } else {
            syncTableWidth();
          }
          document.documentElement.classList.remove('table-column-resizing');
          window.removeEventListener('pointermove', move, true);
          window.removeEventListener('pointerup', finish, true);
          window.removeEventListener('pointercancel', finish, true);
        };
        window.addEventListener('pointermove', move, true);
        window.addEventListener('pointerup', finish, true);
        window.addEventListener('pointercancel', finish, true);
      });
      handle.addEventListener('keydown', event => {
        if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
        event.preventDefault(); event.stopPropagation();
        freezeGeometry();
        const headers = [...headerRow.cells];
        const rightmostIndex = headers.length - 1;
        const step = event.key === 'ArrowRight' ? 12 : -12;
        const current = header.getBoundingClientRect().width || minWidth;
        if (index === rightmostIndex) {
          save(current + step);
          syncTableWidth();
          return;
        }
        const rightmostHeader = headers[rightmostIndex];
        const rightmostWidth = rightmostHeader?.getBoundingClientRect().width || minWidth;
        const minDelta = minWidth - current;
        const maxDelta = rightmostWidth - minWidth;
        const effectiveDelta = Math.max(minDelta, Math.min(maxDelta, step));
        save(current + effectiveDelta);
        if (rightmostHeader) {
          const rightKey = `${storagePrefix}${columnKey(rightmostHeader, rightmostIndex)}`;
          const rightWidth = applyWidth(rightmostIndex, rightmostWidth - effectiveDelta);
          try { localStorage.setItem(rightKey, String(rightWidth)); } catch (_) {}
        }
      });
    });
    table.dataset.columnResizeReady = '1';
  }

  document.querySelectorAll('table.data-table').forEach(initTable);
  document.querySelectorAll('main table:not(#asset-table):not([data-column-resize="off"])').forEach(initUniversalTableResize);

  window.addEventListener('asset-table-content-reloaded', event => {
    const table = event.detail?.table;
    if (table?.assetRebuildFilterCache) {
      table.assetRebuildFilterCache();
      const sortedHeader = table.tHead?.rows?.[0]?.querySelector('[data-sort-direction]');
      if (sortedHeader?.dataset.field && table.assetApplySort) {
        table.assetApplySort(sortedHeader.dataset.field, sortedHeader.dataset.sortDirection, {silent: true});
      } else {
        table.assetApplyFilters?.();
      }
      table.assetUpdateRowNumbers?.();
      table.assetApplyStoredColumnWidths?.();
    }
  });
})();
