import { useEffect, useMemo, useRef, useState } from 'react';
import { Card } from 'primereact/card';
import { Button } from 'primereact/button';
import { Toast } from 'primereact/toast';
import { Dialog } from 'primereact/dialog';
import { TabView, TabPanel } from 'primereact/tabview';
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { Dropdown } from 'primereact/dropdown';
import { InputSwitch } from 'primereact/inputswitch';
import { api } from '../api/client';
import DynamicSettingsForm from '../components/DynamicSettingsForm';
import CronJobsTab from '../components/CronJobsTab';
import { confirmModal } from '../utils/confirmModal';

const EXPERT_MODE_SETTING_KEY = 'ui_expert_mode';
const USER_PRESET_DEFAULT_SLOTS = Object.freeze([
  { key: 'bluray_movie', label: 'Standard-Userpreset für BluRay Film', mediaType: 'bluray' },
  { key: 'bluray_series', label: 'Standard-Userpreset für BluRay Serie', mediaType: 'bluray' },
  { key: 'dvd_movie', label: 'Standard-Userpreset für DVD Film', mediaType: 'dvd' },
  { key: 'dvd_series', label: 'Standard-Userpreset für DVD Serie', mediaType: 'dvd' }
]);

const EMPTY_USER_PRESET_DEFAULTS = Object.freeze(
  USER_PRESET_DEFAULT_SLOTS.reduce((acc, slot) => ({ ...acc, [slot.key]: null }), {})
);

// Validates template/string fields: only letters, digits, space, _, -, and {placeholders} allowed
function validateSettingValue(key, value) {
  if (!key.includes('template')) return null;
  const str = String(value ?? '');
  if (!str) return null;
  // Strip valid placeholder patterns ${...} and {...}
  const stripped = str.replace(/\$\{[^}]*\}/g, '').replace(/\{[^}]*\}/g, '');
  // Standalone { or } after stripping = unclosed placeholder
  if (/[{}]/.test(stripped)) {
    return 'Geschweifte Klammern sind nur als vollständige Platzhalter erlaubt, z.B. {title}.';
  }
  // Find first invalid special char (anything beyond letters, digits, space, _, -, (), [], /)
  // . is not allowed (extensions are set separately); / is only for folder separators
  const invalid = stripped.match(/[^a-zA-Z0-9 ()[\]/_-]/);
  if (invalid) {
    return `Ungültiges Zeichen "${invalid[0]}". Erlaubt sind nur Buchstaben, Ziffern, Leerzeichen, _, -, und {Platzhalter}.`;
  }
  return null;
}

function buildValuesMap(categories) {
  const next = {};
  for (const category of categories || []) {
    for (const setting of category.settings || []) {
      next[setting.key] = setting.value;
    }
  }
  return next;
}

function normalizePositiveId(value) {
  if (value === null || value === undefined) {
    return null;
  }
  if (typeof value === 'string' && value.trim() === '') {
    return null;
  }
  const numeric = Number(value);
  if (!Number.isFinite(numeric) || numeric <= 0) {
    return null;
  }
  return Math.trunc(numeric);
}

function normalizeUserPresetDefaultsMap(raw = {}) {
  const source = raw && typeof raw === 'object' ? raw : {};
  const normalized = { ...EMPTY_USER_PRESET_DEFAULTS };
  for (const slot of USER_PRESET_DEFAULT_SLOTS) {
    normalized[slot.key] = normalizePositiveId(source[slot.key]);
  }
  return normalized;
}

function isPresetCompatibleWithSlot(preset, slotMediaType) {
  const mediaType = String(preset?.mediaType || '').trim().toLowerCase();
  if (mediaType === 'all') {
    return true;
  }
  return mediaType === slotMediaType;
}

function buildUserPresetDefaultOptions(userPresets, slotMediaType, selectedPresetId = null) {
  const presets = Array.isArray(userPresets) ? userPresets : [];
  const options = [{ label: '(keine Standardzuordnung)', value: null }];
  const seen = new Set([null]);

  for (const preset of presets) {
    const presetId = normalizePositiveId(preset?.id);
    if (!presetId || !isPresetCompatibleWithSlot(preset, slotMediaType)) {
      continue;
    }
    seen.add(presetId);
    options.push({
      label: `#${presetId} - ${preset?.name || 'Preset'}`,
      value: presetId
    });
  }

  const normalizedSelectedId = normalizePositiveId(selectedPresetId);
  if (normalizedSelectedId && !seen.has(normalizedSelectedId)) {
    const existingPreset = presets.find((preset) => normalizePositiveId(preset?.id) === normalizedSelectedId);
    options.push({
      label: existingPreset
        ? `#${normalizedSelectedId} - ${existingPreset?.name || 'Preset'} (nicht kompatibel)`
        : `#${normalizedSelectedId} - (nicht gefunden)`,
      value: normalizedSelectedId
    });
  }

  return options;
}

function isSameValue(a, b) {
  if (typeof a === 'number' && typeof b === 'number') {
    return Number(a) === Number(b);
  }
  return a === b;
}

function toBoolean(value) {
  if (typeof value === 'boolean') {
    return value;
  }
  if (typeof value === 'number') {
    return value !== 0;
  }
  const normalized = String(value || '').trim().toLowerCase();
  return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';
}

function reorderListById(items, sourceId, targetIndex) {
  const list = Array.isArray(items) ? items : [];
  const normalizedSourceId = Number(sourceId);
  const normalizedTargetIndex = Number(targetIndex);
  if (!Number.isFinite(normalizedSourceId) || normalizedSourceId <= 0 || !Number.isFinite(normalizedTargetIndex)) {
    return { changed: false, next: list };
  }
  const fromIndex = list.findIndex((item) => Number(item?.id) === normalizedSourceId);
  if (fromIndex < 0) {
    return { changed: false, next: list };
  }

  const boundedTarget = Math.max(0, Math.min(Math.trunc(normalizedTargetIndex), list.length));
  const insertAt = fromIndex < boundedTarget ? boundedTarget - 1 : boundedTarget;
  if (insertAt === fromIndex) {
    return { changed: false, next: list };
  }

  const next = [...list];
  const [moved] = next.splice(fromIndex, 1);
  next.splice(insertAt, 0, moved);
  return { changed: true, next };
}

function buildHandBrakePresetSelectOptions(sourceOptions, extraValues = []) {
  const rawOptions = Array.isArray(sourceOptions) ? sourceOptions : [];
  const rawExtraValues = Array.isArray(extraValues) ? extraValues : [];
  const normalizedOptions = [];
  const seenValues = new Set();
  const seenGroupLabels = new Set();

  const addGroupOption = (option) => {
    const rawLabel = String(option?.label || '').trim();
    if (!rawLabel || seenGroupLabels.has(rawLabel)) {
      return;
    }
    seenGroupLabels.add(rawLabel);
    normalizedOptions.push({
      ...option,
      label: rawLabel,
      value: String(option?.value || `__group__${rawLabel.toLowerCase().replace(/\s+/g, '_')}`),
      disabled: true
    });
  };

  const addSelectableOption = (optionValue, optionLabel = optionValue, option = null) => {
    const value = String(optionValue || '').trim();
    if (seenValues.has(value)) {
      return;
    }
    seenValues.add(value);
    normalizedOptions.push({
      ...(option && typeof option === 'object' ? option : {}),
      label: String(optionLabel ?? value),
      value,
      disabled: false
    });
  };

  normalizedOptions.push({ label: '(kein Preset – nur CLI-Parameter)', value: '', disabled: false });
  seenValues.add('');

  for (const option of rawOptions) {
    if (option?.disabled) {
      addGroupOption(option);
      continue;
    }
    addSelectableOption(option?.value, option?.label, option);
  }
  for (const value of rawExtraValues) {
    addSelectableOption(value);
  }

  return normalizedOptions;
}

function injectHandBrakePresetOptions(categories, presetPayload) {
  const list = Array.isArray(categories) ? categories : [];
  const sourceOptions = Array.isArray(presetPayload?.options) ? presetPayload.options : [];
  const presetSettingKeys = new Set(['handbrake_preset', 'handbrake_preset_bluray', 'handbrake_preset_dvd']);

  return list.map((category) => ({
    ...category,
    settings: (category?.settings || []).map((setting) => {
      if (!presetSettingKeys.has(String(setting?.key || '').trim().toLowerCase())) {
        return setting;
      }
      const normalizedOptions = buildHandBrakePresetSelectOptions(sourceOptions, [
        setting?.value,
        setting?.defaultValue
      ]);

      if (normalizedOptions.length <= 1) {
        return setting;
      }

      return {
        ...setting,
        type: 'select',
        options: normalizedOptions
      };
    })
  }));
}

export default function SettingsPage() {
  const [categories, setCategories] = useState([]);
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [testingPushover, setTestingPushover] = useState(false);
  const [coverArtRecoveryBusy, setCoverArtRecoveryBusy] = useState(false);
  const [updatingExpertMode, setUpdatingExpertMode] = useState(false);
  const [activeTabIndex, setActiveTabIndex] = useState(0);
  const [initialValues, setInitialValues] = useState({});
  const [draftValues, setDraftValues] = useState({});
  const [errors, setErrors] = useState({});
  const [scripts, setScripts] = useState([]);
  const [scriptsLoading, setScriptsLoading] = useState(false);
  const [scriptSaving, setScriptSaving] = useState(false);
  const [scriptReordering, setScriptReordering] = useState(false);
  const [scriptListDragSourceId, setScriptListDragSourceId] = useState(null);
  const [scriptActionBusyId, setScriptActionBusyId] = useState(null);
  const [scriptEditor, setScriptEditor] = useState({
    mode: 'none',
    id: null,
    name: '',
    scriptBody: ''
  });
  const [scriptErrors, setScriptErrors] = useState({});
  const [lastScriptTestResult, setLastScriptTestResult] = useState(null);

  // Script chains state
  const [chains, setChains] = useState([]);
  const [chainsLoading, setChainsLoading] = useState(false);
  const [chainSaving, setChainSaving] = useState(false);
  const [chainReordering, setChainReordering] = useState(false);
  const [chainListDragSourceId, setChainListDragSourceId] = useState(null);
  const [chainActionBusyId, setChainActionBusyId] = useState(null);
  const [lastChainTestResult, setLastChainTestResult] = useState(null);
  const [chainEditor, setChainEditor] = useState({ open: false, id: null, name: '', steps: [] });
  const [chainEditorErrors, setChainEditorErrors] = useState({});
  const [chainDragSource, setChainDragSource] = useState(null);

  // Activation Bytes state
  const [activationBytes, setActivationBytes] = useState([]);

  // User presets state
  const [userPresets, setUserPresets] = useState([]);
  const [userPresetsLoading, setUserPresetsLoading] = useState(false);
  const [userPresetSaving, setUserPresetSaving] = useState(false);
  const [userPresetDefaults, setUserPresetDefaults] = useState(EMPTY_USER_PRESET_DEFAULTS);
  const [userPresetDefaultsDraft, setUserPresetDefaultsDraft] = useState(EMPTY_USER_PRESET_DEFAULTS);
  const [userPresetDefaultsLoading, setUserPresetDefaultsLoading] = useState(false);
  const [userPresetDefaultsSaving, setUserPresetDefaultsSaving] = useState(false);
  const [userPresetEditor, setUserPresetEditor] = useState({
    open: false,
    id: null,
    name: '',
    mediaType: 'all',
    handbrakePreset: '',
    extraArgs: '',
    description: ''
  });
  const [userPresetErrors, setUserPresetErrors] = useState({});
  const [handBrakePresetSourceOptions, setHandBrakePresetSourceOptions] = useState([]);
  const [effectivePaths, setEffectivePaths] = useState(null);

  const toastRef = useRef(null);

  const userPresetHandBrakeOptions = useMemo(
    () => buildHandBrakePresetSelectOptions(
      handBrakePresetSourceOptions,
      [userPresetEditor.handbrakePreset]
    ),
    [handBrakePresetSourceOptions, userPresetEditor.handbrakePreset]
  );
  const userPresetDefaultsDirty = useMemo(
    () => USER_PRESET_DEFAULT_SLOTS.some((slot) => (
      normalizePositiveId(userPresetDefaultsDraft?.[slot.key]) !== normalizePositiveId(userPresetDefaults?.[slot.key])
    )),
    [userPresetDefaultsDraft, userPresetDefaults]
  );
  const userPresetDefaultOptionsBySlot = useMemo(() => {
    const next = {};
    for (const slot of USER_PRESET_DEFAULT_SLOTS) {
      next[slot.key] = buildUserPresetDefaultOptions(
        userPresets,
        slot.mediaType,
        userPresetDefaultsDraft?.[slot.key]
      );
    }
    return next;
  }, [userPresets, userPresetDefaultsDraft]);

  const loadScripts = async ({ silent = false } = {}) => {
    if (!silent) {
      setScriptsLoading(true);
    }
    try {
      const response = await api.getScripts();
      const next = Array.isArray(response?.scripts) ? response.scripts : [];
      setScripts(next);
    } catch (error) {
      if (!silent) {
        toastRef.current?.show({ severity: 'error', summary: 'Script-Liste', detail: error.message });
      }
    } finally {
      if (!silent) {
        setScriptsLoading(false);
      }
    }
  };

  const loadChains = async ({ silent = false } = {}) => {
    if (!silent) {
      setChainsLoading(true);
    }
    try {
      const response = await api.getScriptChains();
      setChains(Array.isArray(response?.chains) ? response.chains : []);
    } catch (error) {
      if (!silent) {
        toastRef.current?.show({ severity: 'error', summary: 'Skriptketten', detail: error.message });
      }
    } finally {
      if (!silent) {
        setChainsLoading(false);
      }
    }
  };

  const loadUserPresets = async ({ silent = false } = {}) => {
    if (!silent) {
      setUserPresetsLoading(true);
    }
    try {
      const response = await api.getUserPresets();
      setUserPresets(Array.isArray(response?.presets) ? response.presets : []);
    } catch (error) {
      if (!silent) {
        toastRef.current?.show({ severity: 'error', summary: 'User-Presets', detail: error.message });
      }
    } finally {
      if (!silent) {
        setUserPresetsLoading(false);
      }
    }
  };

  const loadUserPresetDefaults = async ({ silent = false } = {}) => {
    if (!silent) {
      setUserPresetDefaultsLoading(true);
    }
    try {
      const response = await api.getUserPresetDefaults();
      const normalized = normalizeUserPresetDefaultsMap(response?.defaults || {});
      setUserPresetDefaults(normalized);
      setUserPresetDefaultsDraft(normalized);
    } catch (error) {
      if (!silent) {
        toastRef.current?.show({ severity: 'error', summary: 'Preset-Standards', detail: error.message });
      }
    } finally {
      if (!silent) {
        setUserPresetDefaultsLoading(false);
      }
    }
  };

  const openNewUserPreset = () => {
    setUserPresetEditor({ open: true, id: null, name: '', mediaType: 'all', handbrakePreset: '', extraArgs: '', description: '' });
    setUserPresetErrors({});
  };

  const openEditUserPreset = (preset) => {
    setUserPresetEditor({
      open: true,
      id: preset.id,
      name: preset.name || '',
      mediaType: preset.mediaType || 'all',
      handbrakePreset: preset.handbrakePreset || '',
      extraArgs: preset.extraArgs || '',
      description: preset.description || ''
    });
    setUserPresetErrors({});
  };

  const closeUserPresetEditor = () => {
    setUserPresetEditor((prev) => ({ ...prev, open: false }));
    setUserPresetErrors({});
  };

  const handleSaveUserPreset = async () => {
    const errors = {};
    if (!userPresetEditor.name.trim()) {
      errors.name = 'Name ist erforderlich.';
    }
    if (Object.keys(errors).length > 0) {
      setUserPresetErrors(errors);
      return;
    }
    setUserPresetSaving(true);
    try {
      const payload = {
        name: userPresetEditor.name.trim(),
        mediaType: userPresetEditor.mediaType,
        handbrakePreset: userPresetEditor.handbrakePreset.trim(),
        extraArgs: userPresetEditor.extraArgs.trim(),
        description: userPresetEditor.description.trim()
      };
      if (userPresetEditor.id) {
        await api.updateUserPreset(userPresetEditor.id, payload);
        toastRef.current?.show({ severity: 'success', summary: 'Preset', detail: 'Preset aktualisiert.' });
      } else {
        await api.createUserPreset(payload);
        toastRef.current?.show({ severity: 'success', summary: 'Preset', detail: 'Preset erstellt.' });
      }
      closeUserPresetEditor();
      await loadUserPresets({ silent: true });
    } catch (error) {
      toastRef.current?.show({ severity: 'error', summary: 'Preset speichern', detail: error.message });
    } finally {
      setUserPresetSaving(false);
    }
  };

  const handleDeleteUserPreset = async (presetId) => {
    try {
      await api.deleteUserPreset(presetId);
      toastRef.current?.show({ severity: 'success', summary: 'Preset', detail: 'Preset gelöscht.' });
      await Promise.all([
        loadUserPresets({ silent: true }),
        loadUserPresetDefaults({ silent: true })
      ]);
    } catch (error) {
      toastRef.current?.show({ severity: 'error', summary: 'Preset löschen', detail: error.message });
    }
  };

  const handleChangeUserPresetDefault = (slotKey, rawValue) => {
    const normalized = normalizePositiveId(rawValue);
    setUserPresetDefaultsDraft((prev) => ({
      ...prev,
      [slotKey]: normalized
    }));
  };

  const handleResetUserPresetDefaults = () => {
    setUserPresetDefaultsDraft({ ...userPresetDefaults });
  };

  const handleSaveUserPresetDefaults = async () => {
    if (!userPresetDefaultsDirty) {
      toastRef.current?.show({
        severity: 'info',
        summary: 'Preset-Standards',
        detail: 'Keine Änderungen zum Speichern.'
      });
      return;
    }
    setUserPresetDefaultsSaving(true);
    try {
      const payload = normalizeUserPresetDefaultsMap(userPresetDefaultsDraft);
      const response = await api.updateUserPresetDefaults(payload);
      const normalized = normalizeUserPresetDefaultsMap(response?.defaults || payload);
      setUserPresetDefaults(normalized);
      setUserPresetDefaultsDraft(normalized);
      toastRef.current?.show({
        severity: 'success',
        summary: 'Preset-Standards',
        detail: 'Standard-Zuordnungen gespeichert.'
      });
    } catch (error) {
      toastRef.current?.show({
        severity: 'error',
        summary: 'Preset-Standards',
        detail: error.message
      });
    } finally {
      setUserPresetDefaultsSaving(false);
    }
  };

  const loadEffectivePaths = async ({ silent = false } = {}) => {
    try {
      const paths = await api.getEffectivePaths({ forceRefresh: true });
      setEffectivePaths(paths || null);
    } catch (_error) {
      if (!silent) {
        setEffectivePaths(null);
      }
    }
  };

  const load = async () => {
    setLoading(true);
    try {
      const settingsResponse = await api.getSettings();
      let nextCategories = settingsResponse?.categories || [];
      const values = buildValuesMap(nextCategories);
      setCategories(nextCategories);
      setInitialValues(values);
      setDraftValues(values);
      setErrors({});
      loadEffectivePaths({ silent: true });

      api.getActivationBytes().then(r => setActivationBytes(Array.isArray(r?.entries) ? r.entries : [])).catch(() => {});

      const presetsPromise = api.getHandBrakePresets();
      const scriptsPromise = api.getScripts();
      const chainsPromise = api.getScriptChains();
      const [scriptsResponse, chainsResponse] = await Promise.allSettled([scriptsPromise, chainsPromise]);
      if (scriptsResponse.status === 'fulfilled') {
        setScripts(Array.isArray(scriptsResponse.value?.scripts) ? scriptsResponse.value.scripts : []);
      } else {
        toastRef.current?.show({
          severity: 'warn',
          summary: 'Scripte',
          detail: 'Script-Liste konnte nicht geladen werden.'
        });
      }
      if (chainsResponse.status === 'fulfilled') {
        setChains(Array.isArray(chainsResponse.value?.chains) ? chainsResponse.value.chains : []);
      }

      presetsPromise
        .then((presetPayload) => {
          setHandBrakePresetSourceOptions(Array.isArray(presetPayload?.options) ? presetPayload.options : []);
          setCategories((prevCategories) => injectHandBrakePresetOptions(prevCategories, presetPayload));
          if (presetPayload?.message) {
            toastRef.current?.show({
              severity: presetPayload?.source === 'fallback' ? 'warn' : 'info',
              summary: 'HandBrake Presets',
              detail: presetPayload.message
            });
          }
        })
        .catch(() => {
          setHandBrakePresetSourceOptions([]);
          toastRef.current?.show({
            severity: 'warn',
            summary: 'HandBrake Presets',
            detail: 'Preset-Liste konnte nicht geladen werden. Aktueller Wert bleibt auswählbar.'
          });
        });
    } catch (error) {
      toastRef.current?.show({ severity: 'error', summary: 'Fehler', detail: error.message });
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    load();
    loadUserPresets();
    loadUserPresetDefaults();
  }, []);

  const dirtyKeys = useMemo(() => {
    const keys = new Set();
    const allKeys = new Set([...Object.keys(initialValues), ...Object.keys(draftValues)]);
    for (const key of allKeys) {
      if (!isSameValue(initialValues[key], draftValues[key])) {
        keys.add(key);
      }
    }
    return keys;
  }, [initialValues, draftValues]);

  const hasUnsavedChanges = dirtyKeys.size > 0;
  const expertModeEnabled = toBoolean(draftValues?.[EXPERT_MODE_SETTING_KEY]);

  const handleFieldChange = (key, value) => {
    setDraftValues((prev) => ({ ...prev, [key]: value }));
    const validationError = validateSettingValue(key, value);
    setErrors((prev) => ({ ...prev, [key]: validationError || null }));
  };

  const handleExpertModeToggle = async (checked) => {
    const previousDraftValue = draftValues?.[EXPERT_MODE_SETTING_KEY];
    const previousInitialValue = initialValues?.[EXPERT_MODE_SETTING_KEY];
    const nextValue = Boolean(checked);
    const currentValue = toBoolean(previousDraftValue);
    if (nextValue === currentValue) {
      return;
    }

    setUpdatingExpertMode(true);
    setDraftValues((prev) => ({ ...prev, [EXPERT_MODE_SETTING_KEY]: nextValue }));
    setInitialValues((prev) => ({ ...prev, [EXPERT_MODE_SETTING_KEY]: nextValue }));
    setErrors((prev) => ({ ...prev, [EXPERT_MODE_SETTING_KEY]: null }));
    try {
      await api.updateSetting(EXPERT_MODE_SETTING_KEY, nextValue);
    } catch (error) {
      setDraftValues((prev) => ({ ...prev, [EXPERT_MODE_SETTING_KEY]: previousDraftValue }));
      setInitialValues((prev) => ({ ...prev, [EXPERT_MODE_SETTING_KEY]: previousInitialValue }));
      toastRef.current?.show({
        severity: 'error',
        summary: 'Expertenmodus',
        detail: error.message
      });
    } finally {
      setUpdatingExpertMode(false);
    }
  };

  const handleSave = async () => {
    if (!hasUnsavedChanges) {
      toastRef.current?.show({
        severity: 'info',
        summary: 'Settings',
        detail: 'Keine Änderungen zum Speichern.'
      });
      return;
    }

    const hasValidationErrors = Object.values(errors).some(Boolean);
    if (hasValidationErrors) {
      toastRef.current?.show({
        severity: 'error',
        summary: 'Validierungsfehler',
        detail: 'Bitte korrigiere die fehlerhaften Felder vor dem Speichern.'
      });
      return;
    }

    const patch = {};
    for (const key of dirtyKeys) {
      patch[key] = draftValues[key];
    }

    setSaving(true);
    try {
      const response = await api.updateSettingsBulk(patch);
      setInitialValues((prev) => ({ ...prev, ...patch }));
      setErrors({});
      loadEffectivePaths({ silent: true });
      const reviewRefresh = response?.reviewRefresh || null;
      const reviewRefreshHint = reviewRefresh?.triggered
        ? ' Mediainfo-Prüfung wird mit den neuen Settings automatisch neu berechnet.'
        : '';
      toastRef.current?.show({
        severity: 'success',
        summary: 'Settings',
        detail: `${Object.keys(patch).length} Änderung(en) gespeichert.${reviewRefreshHint}`
      });
    } catch (error) {
      let detail = error?.message || 'Unbekannter Fehler';
      if (Array.isArray(error?.details)) {
        const nextErrors = {};
        for (const item of error.details) {
          if (item?.key) {
            nextErrors[item.key] = item.message || 'Ungültiger Wert';
          }
        }
        setErrors(nextErrors);
        detail = 'Mindestens ein Feld ist ungültig.';
      }
      toastRef.current?.show({ severity: 'error', summary: 'Speichern fehlgeschlagen', detail });
    } finally {
      setSaving(false);
    }
  };

  const handleDiscard = () => {
    setDraftValues(initialValues);
    setErrors({});
  };

  const handlePushoverTest = async () => {
    setTestingPushover(true);
    try {
      const response = await api.testPushover();
      const sent = response?.result?.sent;
      if (sent) {
        toastRef.current?.show({
          severity: 'success',
          summary: 'PushOver',
          detail: 'Testnachricht wurde versendet.'
        });
      } else {
        toastRef.current?.show({
          severity: 'warn',
          summary: 'PushOver',
          detail: `Nicht versendet (${response?.result?.reason || 'unbekannt'}).`
        });
      }
    } catch (error) {
      toastRef.current?.show({ severity: 'error', summary: 'PushOver Fehler', detail: error.message });
    } finally {
      setTestingPushover(false);
    }
  };

  const handleCoverArtRecovery = async () => {
    setCoverArtRecoveryBusy(true);
    try {
      const response = await api.recoverMissingCoverArt({});
      const result = response?.result || {};
      const recovered = Number(result?.recovered || 0);
      const failed = Number(result?.failed || 0);
      const scanned = Number(result?.scannedJobs || 0);
      toastRef.current?.show({
        severity: failed > 0 ? 'warn' : 'success',
        summary: 'Coverart-Prüfung',
        detail: `Geprüft: ${scanned} | Nachgeladen: ${recovered} | Fehlgeschlagen: ${failed}`,
        life: 5000
      });
    } catch (error) {
      toastRef.current?.show({
        severity: 'error',
        summary: 'Coverart-Prüfung fehlgeschlagen',
        detail: error.message
      });
    } finally {
      setCoverArtRecoveryBusy(false);
    }
  };

  const handleScriptEditorChange = (key, value) => {
    setScriptEditor((prev) => ({
      ...prev,
      [key]: value
    }));
    setScriptErrors((prev) => ({
      ...prev,
      [key]: null
    }));
  };

  const clearScriptEditor = () => {
    setScriptEditor({
      mode: 'none',
      id: null,
      name: '',
      scriptBody: ''
    });
    setScriptErrors({});
  };

  const startCreateScript = () => {
    setScriptEditor({
      mode: 'create',
      id: null,
      name: '',
      scriptBody: ''
    });
    setScriptErrors({});
    setLastScriptTestResult(null);
  };

  const startEditScript = (script) => {
    setScriptEditor({
      mode: 'edit',
      id: script?.id || null,
      name: script?.name || '',
      scriptBody: script?.scriptBody || ''
    });
    setScriptErrors({});
    setLastScriptTestResult(null);
  };

  const handleSaveScript = async () => {
    if (scriptEditor?.mode !== 'create' && scriptEditor?.mode !== 'edit') {
      return;
    }
    const payload = {
      name: String(scriptEditor?.name || '').trim(),
      scriptBody: String(scriptEditor?.scriptBody || '')
    };
    setScriptSaving(true);
    try {
      if (scriptEditor?.id) {
        await api.updateScript(scriptEditor.id, payload);
        toastRef.current?.show({
          severity: 'success',
          summary: 'Scripte',
          detail: 'Script aktualisiert.'
        });
      } else {
        await api.createScript(payload);
        toastRef.current?.show({
          severity: 'success',
          summary: 'Scripte',
          detail: 'Script angelegt.'
        });
      }
      await loadScripts({ silent: true });
      setScriptErrors({});
      clearScriptEditor();
    } catch (error) {
      const details = Array.isArray(error?.details) ? error.details : [];
      if (details.length > 0) {
        const nextErrors = {};
        for (const item of details) {
          if (item?.field) {
            nextErrors[item.field] = item.message || 'Ungültiger Wert';
          }
        }
        setScriptErrors(nextErrors);
      }
      toastRef.current?.show({
        severity: 'error',
        summary: 'Script speichern fehlgeschlagen',
        detail: error.message
      });
    } finally {
      setScriptSaving(false);
    }
  };

  const handleDeleteScript = async (script) => {
    const scriptId = Number(script?.id);
    if (!Number.isFinite(scriptId) || scriptId <= 0) {
      return;
    }
    const confirmed = await confirmModal({
      header: 'Script löschen',
      message: `Script "${script?.name || scriptId}" wirklich löschen?`,
      acceptLabel: 'Löschen',
      rejectLabel: 'Abbrechen',
      danger: true
    });
    if (!confirmed) {
      return;
    }
    setScriptActionBusyId(scriptId);
    try {
      await api.deleteScript(scriptId);
      toastRef.current?.show({
        severity: 'success',
        summary: 'Scripte',
        detail: 'Script gelöscht.'
      });
      await loadScripts({ silent: true });
      if (scriptEditor?.mode === 'edit' && Number(scriptEditor?.id) === scriptId) {
        clearScriptEditor();
      }
    } catch (error) {
      toastRef.current?.show({
        severity: 'error',
        summary: 'Script löschen fehlgeschlagen',
        detail: error.message
      });
    } finally {
      setScriptActionBusyId(null);
    }
  };

  const handleTestScript = async (script) => {
    const scriptId = Number(script?.id);
    if (!Number.isFinite(scriptId) || scriptId <= 0) {
      return;
    }
    setScriptActionBusyId(scriptId);
    try {
      const response = await api.testScript(scriptId);
      const result = response?.result || null;
      setLastScriptTestResult(result);
      if (result?.success) {
        toastRef.current?.show({
          severity: 'success',
          summary: 'Script-Test',
          detail: `"${script?.name || scriptId}" erfolgreich ausgeführt.`
        });
      } else {
        toastRef.current?.show({
          severity: 'warn',
          summary: 'Script-Test',
          detail: `"${script?.name || scriptId}" fehlgeschlagen (exit=${result?.exitCode ?? 'n/a'}).`
        });
      }
    } catch (error) {
      toastRef.current?.show({
        severity: 'error',
        summary: 'Script-Test fehlgeschlagen',
        detail: error.message
      });
    } finally {
      setScriptActionBusyId(null);
    }
  };

  const handleScriptListDragStart = (event, scriptId) => {
    if (scriptSaving || scriptsLoading || scriptReordering || scriptEditor?.mode === 'create' || Boolean(scriptActionBusyId)) {
      event.preventDefault();
      return;
    }
    setScriptListDragSourceId(Number(scriptId));
    event.dataTransfer.effectAllowed = 'move';
    event.dataTransfer.setData('text/plain', String(scriptId));
  };

  const handleScriptListDragOver = (event) => {
    const sourceId = Number(scriptListDragSourceId);
    if (!Number.isFinite(sourceId) || sourceId <= 0) {
      return;
    }
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
  };

  const handleScriptListDrop = async (event, targetIndex) => {
    event.preventDefault();
    if (scriptReordering) {
      setScriptListDragSourceId(null);
      return;
    }
    const sourceId = Number(scriptListDragSourceId);
    setScriptListDragSourceId(null);
    const { changed, next } = reorderListById(scripts, sourceId, targetIndex);
    if (!changed) {
      return;
    }

    const orderedScriptIds = next
      .map((script) => Number(script?.id))
      .filter((id) => Number.isFinite(id) && id > 0);
    setScripts(next);
    setScriptReordering(true);
    try {
      await api.reorderScripts(orderedScriptIds);
    } catch (error) {
      toastRef.current?.show({
        severity: 'error',
        summary: 'Script-Reihenfolge',
        detail: error.message
      });
      await loadScripts({ silent: true });
    } finally {
      setScriptReordering(false);
    }
  };

  const handleTestChain = async (chain) => {
    const chainId = Number(chain?.id);
    if (!Number.isFinite(chainId) || chainId <= 0) {
      return;
    }
    setChainActionBusyId(chainId);
    setLastChainTestResult(null);
    try {
      const response = await api.testScriptChain(chainId);
      const result = response?.result || null;
      setLastChainTestResult(result);
      if (!result?.aborted) {
        toastRef.current?.show({
          severity: 'success',
          summary: 'Ketten-Test',
          detail: `"${chain?.name || chainId}" erfolgreich ausgeführt (${result?.succeeded ?? 0}/${result?.steps ?? 0} Schritte).`
        });
      } else {
        toastRef.current?.show({
          severity: 'warn',
          summary: 'Ketten-Test',
          detail: `"${chain?.name || chainId}" abgebrochen (${result?.succeeded ?? 0}/${result?.steps ?? 0} Schritte OK).`
        });
      }
    } catch (error) {
      toastRef.current?.show({ severity: 'error', summary: 'Ketten-Test fehlgeschlagen', detail: error.message });
    } finally {
      setChainActionBusyId(null);
    }
  };

  // Chain editor handlers
  const openChainEditor = (chain = null) => {
    if (chain) {
      setChainEditor({ open: true, id: chain.id, name: chain.name, steps: (chain.steps || []).map((s, i) => ({ ...s, _key: `${s.id || i}-${Date.now()}` })) });
    } else {
      setChainEditor({ open: true, id: null, name: '', steps: [] });
    }
    setChainEditorErrors({});
  };

  const closeChainEditor = () => {
    setChainEditor({ open: false, id: null, name: '', steps: [] });
    setChainEditorErrors({});
  };

  const addChainStep = (stepType, scriptId = null, scriptName = null) => {
    setChainEditor((prev) => ({
      ...prev,
      steps: [
        ...prev.steps,
        {
          _key: `new-${Date.now()}-${Math.random()}`,
          stepType,
          scriptId: stepType === 'script' ? scriptId : null,
          scriptName: stepType === 'script' ? scriptName : null,
          waitSeconds: stepType === 'wait' ? 10 : null
        }
      ]
    }));
  };

  const removeChainStep = (index) => {
    setChainEditor((prev) => ({ ...prev, steps: prev.steps.filter((_, i) => i !== index) }));
  };

  const updateChainStepWait = (index, seconds) => {
    setChainEditor((prev) => ({
      ...prev,
      steps: prev.steps.map((s, i) => i === index ? { ...s, waitSeconds: seconds } : s)
    }));
  };

  const moveChainStep = (fromIndex, toIndex) => {
    if (fromIndex === toIndex) {
      return;
    }
    setChainEditor((prev) => {
      const steps = [...prev.steps];
      const [moved] = steps.splice(fromIndex, 1);
      steps.splice(toIndex, 0, moved);
      return { ...prev, steps };
    });
  };

  const handleSaveChain = async () => {
    const name = String(chainEditor.name || '').trim();
    if (!name) {
      setChainEditorErrors({ name: 'Name darf nicht leer sein.' });
      return;
    }
    const payload = {
      name,
      steps: chainEditor.steps.map((s) => ({
        stepType: s.stepType,
        scriptId: s.stepType === 'script' ? s.scriptId : null,
        waitSeconds: s.stepType === 'wait' ? Number(s.waitSeconds || 10) : null
      }))
    };
    setChainSaving(true);
    try {
      if (chainEditor.id) {
        await api.updateScriptChain(chainEditor.id, payload);
        toastRef.current?.show({ severity: 'success', summary: 'Skriptkette', detail: 'Kette aktualisiert.' });
      } else {
        await api.createScriptChain(payload);
        toastRef.current?.show({ severity: 'success', summary: 'Skriptkette', detail: 'Kette angelegt.' });
      }
      await loadChains({ silent: true });
      closeChainEditor();
    } catch (error) {
      const details = Array.isArray(error?.details) ? error.details : [];
      if (details.length > 0) {
        const errs = {};
        for (const item of details) {
          if (item?.field) {
            errs[item.field] = item.message || 'Ungültig';
          }
        }
        setChainEditorErrors(errs);
      }
      toastRef.current?.show({ severity: 'error', summary: 'Kette speichern fehlgeschlagen', detail: error.message });
    } finally {
      setChainSaving(false);
    }
  };

  const handleDeleteChain = async (chain) => {
    const chainId = Number(chain?.id);
    if (!Number.isFinite(chainId) || chainId <= 0) {
      return;
    }
    const confirmed = await confirmModal({
      header: 'Skriptkette löschen',
      message: `Skriptkette "${chain?.name || chainId}" wirklich löschen?`,
      acceptLabel: 'Löschen',
      rejectLabel: 'Abbrechen',
      danger: true
    });
    if (!confirmed) {
      return;
    }
    try {
      await api.deleteScriptChain(chainId);
      toastRef.current?.show({ severity: 'success', summary: 'Skriptketten', detail: 'Kette gelöscht.' });
      await loadChains({ silent: true });
    } catch (error) {
      toastRef.current?.show({ severity: 'error', summary: 'Kette löschen fehlgeschlagen', detail: error.message });
    }
  };

  const handleChainListDragStart = (event, chainId) => {
    if (chainSaving || chainsLoading || chainReordering || Boolean(chainActionBusyId)) {
      event.preventDefault();
      return;
    }
    setChainListDragSourceId(Number(chainId));
    event.dataTransfer.effectAllowed = 'move';
    event.dataTransfer.setData('text/plain', String(chainId));
  };

  const handleChainListDragOver = (event) => {
    const sourceId = Number(chainListDragSourceId);
    if (!Number.isFinite(sourceId) || sourceId <= 0) {
      return;
    }
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
  };

  const handleChainListDrop = async (event, targetIndex) => {
    event.preventDefault();
    if (chainReordering) {
      setChainListDragSourceId(null);
      return;
    }
    const sourceId = Number(chainListDragSourceId);
    setChainListDragSourceId(null);
    const { changed, next } = reorderListById(chains, sourceId, targetIndex);
    if (!changed) {
      return;
    }

    const orderedChainIds = next
      .map((chain) => Number(chain?.id))
      .filter((id) => Number.isFinite(id) && id > 0);
    setChains(next);
    setChainReordering(true);
    try {
      await api.reorderScriptChains(orderedChainIds);
    } catch (error) {
      toastRef.current?.show({
        severity: 'error',
        summary: 'Ketten-Reihenfolge',
        detail: error.message
      });
      await loadChains({ silent: true });
    } finally {
      setChainReordering(false);
    }
  };

  // Chain DnD handlers
  const handleChainPaletteDragStart = (event, data) => {
    setChainDragSource({ origin: 'palette', ...data });
    event.dataTransfer.effectAllowed = 'copy';
    event.dataTransfer.setData('text/plain', JSON.stringify(data));
  };

  const handleChainStepDragStart = (event, index) => {
    setChainDragSource({ origin: 'step', index });
    event.dataTransfer.effectAllowed = 'move';
    event.dataTransfer.setData('text/plain', String(index));
  };

  const handleChainDropzoneDrop = (event, targetIndex) => {
    event.preventDefault();
    if (!chainDragSource) {
      return;
    }
    if (chainDragSource.origin === 'palette') {
      const newStep = {
        _key: `new-${Date.now()}-${Math.random()}`,
        stepType: chainDragSource.stepType,
        scriptId: chainDragSource.stepType === 'script' ? chainDragSource.scriptId : null,
        scriptName: chainDragSource.stepType === 'script' ? chainDragSource.scriptName : null,
        waitSeconds: chainDragSource.stepType === 'wait' ? 10 : null
      };
      setChainEditor((prev) => {
        const steps = [...prev.steps];
        const insertAt = targetIndex != null ? targetIndex : steps.length;
        steps.splice(insertAt, 0, newStep);
        return { ...prev, steps };
      });
    } else if (chainDragSource.origin === 'step') {
      moveChainStep(chainDragSource.index, targetIndex != null ? targetIndex : chainEditor.steps.length - 1);
    }
    setChainDragSource(null);
  };

  const handleChainDragOver = (event) => {
    event.preventDefault();
    event.dataTransfer.dropEffect = chainDragSource?.origin === 'palette' ? 'copy' : 'move';
  };

  const scriptListDnDDisabled = scriptSaving
    || scriptsLoading
    || scriptReordering
    || scriptEditor?.mode === 'create'
    || Boolean(scriptActionBusyId);
  const chainListDnDDisabled = chainSaving
    || chainsLoading
    || chainReordering
    || Boolean(chainActionBusyId);

  return (
    <div className="page-grid">
      <Toast ref={toastRef} />

      <Card title="Einstellungen" subTitle="Änderungen werden erst beim Speichern in die Datenbank übernommen">
        <TabView
          className="settings-root-tabview"
          activeIndex={activeTabIndex}
          onTabChange={(event) => setActiveTabIndex(Number(event.index || 0))}
        >
          <TabPanel header="Konfiguration">
            <div className="actions-row">
              <Button
                label="Änderungen speichern"
                icon="pi pi-save"
                onClick={handleSave}
                loading={saving}
                disabled={!hasUnsavedChanges || updatingExpertMode}
              />
              <Button
                label="Änderungen verwerfen"
                icon="pi pi-undo"
                severity="secondary"
                outlined
                onClick={handleDiscard}
                disabled={!hasUnsavedChanges || saving || updatingExpertMode}
              />
              <Button
                label="Neu laden"
                icon="pi pi-refresh"
                severity="secondary"
                onClick={load}
                loading={loading}
                disabled={saving || updatingExpertMode}
              />
              <Button
                label="PushOver Test"
                icon="pi pi-send"
                severity="info"
                onClick={handlePushoverTest}
                loading={testingPushover}
                disabled={saving || updatingExpertMode}
              />
              <Button
                label="Coverarts prüfen"
                icon="pi pi-images"
                severity="help"
                onClick={handleCoverArtRecovery}
                loading={coverArtRecoveryBusy}
                disabled={saving || updatingExpertMode || testingPushover}
              />
              <div className="settings-expert-toggle">
                <span>Expertenmodus</span>
                <InputSwitch
                  checked={expertModeEnabled}
                  onChange={(event) => handleExpertModeToggle(event.value)}
                  disabled={loading || saving || updatingExpertMode}
                />
              </div>
            </div>

            {loading ? (
              <p>Lade Settings ...</p>
            ) : (
              <DynamicSettingsForm
                categories={categories}
                values={draftValues}
                errors={errors}
                dirtyKeys={dirtyKeys}
                onChange={handleFieldChange}
                effectivePaths={effectivePaths}
                onNotify={(msg) => toastRef.current?.show(msg)}
              />
            )}
          </TabPanel>
          <TabPanel header="Scripte">
            <div className="script-manager-wrap">
              <div className="actions-row">
                <Button
                  label="Neues Skript hinzufügen"
                  icon="pi pi-plus"
                  onClick={startCreateScript}
                  severity="success"
                  outlined
                  disabled={scriptSaving || scriptReordering || scriptEditor?.mode === 'create'}
                />
                <Button
                  label="Scripts neu laden"
                  icon="pi pi-refresh"
                  severity="secondary"
                  onClick={() => loadScripts()}
                  loading={scriptsLoading}
                  disabled={scriptSaving || scriptReordering}
                />
              </div>

              <small>
                Die ausgewählten Scripts werden später pro Job nach erfolgreichem Encode in Reihenfolge ausgeführt.
              </small>
              <small className="muted-inline">
                Reihenfolge per Drag & Drop ändern.
                {scriptReordering ? ' Speichere Reihenfolge ...' : ''}
              </small>

              <div className="script-list-box">
                <h4>Verfügbare Scripts</h4>
                {scriptsLoading ? (
                  <p>Lade Scripts ...</p>
                ) : (
                  <div className="script-list script-list--reorderable">
                    {scriptEditor?.mode === 'create' ? (
                      <div className="script-list-item script-list-item-editing">
                        <div className="script-list-main">
                          <div className="script-title-line">
                            <strong className="script-id-title">NEU - Titel</strong>
                            <InputText
                              id="script-name-new"
                              value={scriptEditor?.name || ''}
                              onChange={(event) => handleScriptEditorChange('name', event.target.value)}
                              placeholder="z.B. Library Refresh"
                              className="script-title-input"
                            />
                          </div>
                          {scriptErrors?.name ? <small className="error-text">{scriptErrors.name}</small> : null}
                        </div>
                        <div className="script-editor-fields">
                          <label htmlFor="script-body-new">Bash Script</label>
                          <InputTextarea
                            id="script-body-new"
                            value={scriptEditor?.scriptBody || ''}
                            onChange={(event) => handleScriptEditorChange('scriptBody', event.target.value)}
                            rows={12}
                            autoResize={false}
                            placeholder={'#!/usr/bin/env bash\necho "Post-Encode Script"'}
                          />
                          {scriptErrors?.scriptBody ? <small className="error-text">{scriptErrors.scriptBody}</small> : null}
                        </div>
                        <div className="script-list-actions">
                          <Button
                            label="Speichern"
                            icon="pi pi-save"
                            onClick={handleSaveScript}
                            loading={scriptSaving}
                          />
                          <Button
                            label="Verwerfen"
                            icon="pi pi-times"
                            severity="secondary"
                            outlined
                            onClick={clearScriptEditor}
                            disabled={scriptSaving}
                          />
                          <span className="script-action-spacer" aria-hidden />
                        </div>
                      </div>
                    ) : null}

                    {scripts.length === 0 ? (
                      <p>Keine Scripts vorhanden.</p>
                    ) : (
                      <div className="script-order-list">
                        {scripts.map((script, index) => {
                          const isDragging = Number(scriptListDragSourceId) === Number(script.id);
                          return (
                            <div key={script.id} className="script-order-wrapper">
                              <div
                                className="script-order-drop-zone"
                                onDragOver={handleScriptListDragOver}
                                onDrop={(event) => handleScriptListDrop(event, index)}
                              />
                              <div
                                className={`script-list-item${isDragging ? ' script-list-item--dragging' : ''}`}
                                draggable={!scriptListDnDDisabled}
                                onDragStart={(event) => handleScriptListDragStart(event, script.id)}
                                onDragEnd={() => setScriptListDragSourceId(null)}
                              >
                                <div
                                  className={`script-list-drag-handle${scriptListDnDDisabled ? ' disabled' : ''}`}
                                  title={scriptListDnDDisabled ? 'Sortierung aktuell nicht verfügbar' : 'Ziehen zum Sortieren'}
                                >
                                  <i className="pi pi-bars" />
                                </div>
                                <div className="script-list-main">
                                  <strong className="script-id-title">{`ID #${script.id} - ${script.name}`}</strong>
                                </div>

                                <div className="script-list-actions">
                                  <Button
                                    icon="pi pi-pencil"
                                    label="Bearbeiten"
                                    severity="secondary"
                                    outlined
                                    onClick={() => startEditScript(script)}
                                    disabled={Boolean(scriptActionBusyId) || scriptSaving || scriptReordering || scriptEditor?.mode === 'create'}
                                  />
                                  <Button
                                    icon="pi pi-play"
                                    label="Test"
                                    severity="info"
                                    onClick={() => handleTestScript(script)}
                                    loading={scriptActionBusyId === script.id}
                                    disabled={scriptReordering || (Boolean(scriptActionBusyId) && scriptActionBusyId !== script.id)}
                                  />
                                  <Button
                                    icon="pi pi-trash"
                                    label="Löschen"
                                    severity="danger"
                                    outlined
                                    onClick={() => handleDeleteScript(script)}
                                    loading={scriptActionBusyId === script.id}
                                    disabled={scriptReordering || (Boolean(scriptActionBusyId) && scriptActionBusyId !== script.id)}
                                  />
                                </div>
                              </div>
                            </div>
                          );
                        })}
                        <div
                          className="script-order-drop-zone script-order-drop-zone--end"
                          onDragOver={handleScriptListDragOver}
                          onDrop={(event) => handleScriptListDrop(event, scripts.length)}
                        />
                      </div>
                    )}
                  </div>
                )}
              </div>

              <Dialog
                header={scriptEditor?.id ? `Script bearbeiten (#${scriptEditor.id})` : 'Script bearbeiten'}
                visible={scriptEditor?.mode === 'edit'}
                onHide={clearScriptEditor}
                style={{ width: 'min(52rem, calc(100vw - 1.5rem))' }}
                className="script-edit-dialog"
                dismissableMask
                draggable={false}
              >
                <div className="script-editor-fields">
                  <label htmlFor="script-edit-name">Name</label>
                  <InputText
                    id="script-edit-name"
                    value={scriptEditor?.name || ''}
                    onChange={(event) => handleScriptEditorChange('name', event.target.value)}
                    placeholder="z.B. Library Refresh"
                  />
                  {scriptErrors?.name ? <small className="error-text">{scriptErrors.name}</small> : null}
                  <label htmlFor="script-edit-body">Bash Script</label>
                  <InputTextarea
                    id="script-edit-body"
                    value={scriptEditor?.scriptBody || ''}
                    onChange={(event) => handleScriptEditorChange('scriptBody', event.target.value)}
                    rows={14}
                    autoResize={false}
                    placeholder={'#!/usr/bin/env bash\necho "Post-Encode Script"'}
                  />
                  {scriptErrors?.scriptBody ? <small className="error-text">{scriptErrors.scriptBody}</small> : null}
                </div>
                <div className="actions-row">
                  <Button
                    label="Script aktualisieren"
                    icon="pi pi-save"
                    onClick={handleSaveScript}
                    loading={scriptSaving}
                  />
                  <Button
                    label="Abbrechen"
                    icon="pi pi-times"
                    severity="secondary"
                    outlined
                    onClick={clearScriptEditor}
                    disabled={scriptSaving}
                  />
                </div>
              </Dialog>

              {lastScriptTestResult ? (
                <div className="script-test-result">
                  <h4>Letzter Script-Test: {lastScriptTestResult.scriptName}</h4>
                  <small>
                    Status: {lastScriptTestResult.success ? 'Erfolgreich' : 'Fehler'}
                    {' | '}exit={lastScriptTestResult.exitCode ?? 'n/a'}
                    {' | '}timeout={lastScriptTestResult.timedOut ? 'ja' : 'nein'}
                    {' | '}dauer={Number(lastScriptTestResult.durationMs || 0)}ms
                  </small>
                  <pre>{`${lastScriptTestResult.stdout || ''}${lastScriptTestResult.stderr ? `\n${lastScriptTestResult.stderr}` : ''}`.trim() || 'Keine Ausgabe.'}</pre>
                </div>
              ) : null}
            </div>
          </TabPanel>

          <TabPanel header="Skriptketten">
            <div className="script-manager-wrap">
              <div className="actions-row">
                <Button
                  label="Neue Kette erstellen"
                  icon="pi pi-plus"
                  severity="success"
                  outlined
                  onClick={() => openChainEditor()}
                  disabled={chainReordering}
                />
                <Button
                  label="Ketten neu laden"
                  icon="pi pi-refresh"
                  severity="secondary"
                  onClick={() => loadChains()}
                  loading={chainsLoading}
                  disabled={chainReordering}
                />
              </div>

              <small>
                Skriptketten kombinieren einzelne Scripte und Systemblöcke (z.B. Warten) zu einer ausführbaren Sequenz.
                Ketten können an Jobs als Pre- oder Post-Encode-Aktion hinterlegt werden.
              </small>
              <small className="muted-inline">
                Reihenfolge per Drag & Drop ändern.
                {chainReordering ? ' Speichere Reihenfolge ...' : ''}
              </small>

              <div className="script-list-box">
                <h4>Verfügbare Skriptketten</h4>
                {chainsLoading ? (
                  <p>Lade Skriptketten...</p>
                ) : chains.length === 0 ? (
                  <p>Keine Skriptketten vorhanden.</p>
                ) : (
                  <div className="script-list script-list--reorderable">
                    <div className="script-order-list">
                      {chains.map((chain, index) => {
                        const isDragging = Number(chainListDragSourceId) === Number(chain.id);
                        return (
                          <div key={chain.id} className="script-order-wrapper">
                            <div
                              className="script-order-drop-zone"
                              onDragOver={handleChainListDragOver}
                              onDrop={(event) => handleChainListDrop(event, index)}
                            />
                            <div
                              className={`script-list-item${isDragging ? ' script-list-item--dragging' : ''}`}
                              draggable={!chainListDnDDisabled}
                              onDragStart={(event) => handleChainListDragStart(event, chain.id)}
                              onDragEnd={() => setChainListDragSourceId(null)}
                            >
                              <div
                                className={`script-list-drag-handle${chainListDnDDisabled ? ' disabled' : ''}`}
                                title={chainListDnDDisabled ? 'Sortierung aktuell nicht verfügbar' : 'Ziehen zum Sortieren'}
                              >
                                <i className="pi pi-bars" />
                              </div>
                              <div className="script-list-main">
                                <strong className="script-id-title">{`ID #${chain.id} - ${chain.name}`}</strong>
                                <small>
                                  {chain.steps?.length ?? 0} Schritt(e):
                                  {' '}
                                  {(chain.steps || []).map((s, i) => (
                                    <span key={i}>
                                      {i > 0 ? ' → ' : ''}
                                      {s.stepType === 'wait'
                                        ? `⏱ ${s.waitSeconds}s`
                                        : (s.scriptName || `Script #${s.scriptId}`)}
                                    </span>
                                  ))}
                                </small>
                              </div>
                              <div className="script-list-actions">
                                <Button
                                  icon="pi pi-pencil"
                                  label="Bearbeiten"
                                  severity="secondary"
                                  outlined
                                  onClick={() => openChainEditor(chain)}
                                  disabled={chainReordering || Boolean(chainActionBusyId)}
                                />
                                <Button
                                  icon="pi pi-play"
                                  label="Test"
                                  severity="info"
                                  onClick={() => handleTestChain(chain)}
                                  loading={chainActionBusyId === chain.id}
                                  disabled={chainReordering || (Boolean(chainActionBusyId) && chainActionBusyId !== chain.id)}
                                />
                                <Button
                                  icon="pi pi-trash"
                                  label="Löschen"
                                  severity="danger"
                                  outlined
                                  onClick={() => handleDeleteChain(chain)}
                                  disabled={chainReordering || Boolean(chainActionBusyId)}
                                />
                              </div>
                            </div>
                          </div>
                        );
                      })}
                      <div
                        className="script-order-drop-zone script-order-drop-zone--end"
                        onDragOver={handleChainListDragOver}
                        onDrop={(event) => handleChainListDrop(event, chains.length)}
                      />
                    </div>
                  </div>
                )}
              </div>
            {lastChainTestResult ? (
              <div className="script-test-result">
                <h4>Letzter Ketten-Test: {lastChainTestResult.chainName}</h4>
                <small>
                  Status: {lastChainTestResult.aborted ? 'Abgebrochen' : 'Erfolgreich'}
                  {' | '}Schritte: {lastChainTestResult.succeeded ?? 0}/{lastChainTestResult.steps ?? 0}
                  {lastChainTestResult.failed > 0 ? ` | Fehler: ${lastChainTestResult.failed}` : ''}
                </small>
                {(lastChainTestResult.results || []).map((step, i) => (
                  <div key={i} className="script-test-step">
                    <strong>
                      {`Schritt ${i + 1}: `}
                      {step.stepType === 'wait'
                        ? `⏱ Warten (${step.waitSeconds}s)`
                        : (step.scriptName || `Script #${step.scriptId}`)}
                      {' — '}
                      {step.skipped ? 'Übersprungen' : (step.success ? '✓ OK' : `✗ Fehler (exit=${step.exitCode ?? 'n/a'})`)}
                    </strong>
                    {(step.stdout || step.stderr) ? (
                      <pre>{`${step.stdout || ''}${step.stderr ? `\n${step.stderr}` : ''}`.trim()}</pre>
                    ) : null}
                  </div>
                ))}
              </div>
            ) : null}
            </div>

            {/* Chain editor dialog */}
            <Dialog
              header={chainEditor.id ? `Skriptkette bearbeiten (#${chainEditor.id})` : 'Neue Skriptkette'}
              visible={chainEditor.open}
              onHide={closeChainEditor}
              style={{ width: 'min(70rem, calc(100vw - 1.5rem))' }}
              className="script-edit-dialog chain-editor-dialog"
              dismissableMask={false}
              draggable={false}
            >
              <div className="chain-editor-name-row">
                <label htmlFor="chain-name">Name der Kette</label>
                <InputText
                  id="chain-name"
                  value={chainEditor.name}
                  onChange={(e) => {
                    setChainEditor((prev) => ({ ...prev, name: e.target.value }));
                    setChainEditorErrors((prev) => ({ ...prev, name: null }));
                  }}
                  placeholder="z.B. Plex-Refresh + Cleanup"
                />
                {chainEditorErrors.name ? <small className="error-text">{chainEditorErrors.name}</small> : null}
              </div>

              <div className="chain-editor-body">
                {/* Palette */}
                <div className="chain-palette">
                  <h4>Bausteine</h4>
                  <p className="chain-palette-hint">Auf Schritt klicken oder in die Kette ziehen</p>

                  <div className="chain-palette-section">
                    <strong>Systemblöcke</strong>
                    <div
                      className="chain-palette-item chain-palette-item--system"
                      draggable
                      onDragStart={(e) => handleChainPaletteDragStart(e, { stepType: 'wait' })}
                      onClick={() => addChainStep('wait')}
                      title="Wartezeit zwischen zwei Schritten"
                    >
                      <i className="pi pi-clock" />
                      {' '}Warten (Sekunden)
                    </div>
                  </div>

                  {scripts.length > 0 ? (
                    <div className="chain-palette-section">
                      <strong>Scripte</strong>
                      {scripts.map((script) => (
                        <div
                          key={script.id}
                          className="chain-palette-item chain-palette-item--script"
                          draggable
                          onDragStart={(e) => handleChainPaletteDragStart(e, { stepType: 'script', scriptId: script.id, scriptName: script.name })}
                          onClick={() => addChainStep('script', script.id, script.name)}
                          title={`Script #${script.id} hinzufügen`}
                        >
                          <i className="pi pi-code" />
                          {' '}{script.name}
                        </div>
                      ))}
                    </div>
                  ) : (
                    <small>Keine Scripte verfügbar. Zuerst Scripte anlegen.</small>
                  )}
                </div>

                {/* Chain canvas */}
                <div className="chain-canvas">
                  <h4>Kette ({chainEditor.steps.length} Schritt{chainEditor.steps.length !== 1 ? 'e' : ''})</h4>

                  {chainEditor.steps.length === 0 ? (
                    <div
                      className="chain-canvas-empty"
                      onDragOver={handleChainDragOver}
                      onDrop={(e) => handleChainDropzoneDrop(e, 0)}
                    >
                      Bausteine hierhin ziehen oder links anklicken
                    </div>
                  ) : (
                    <div className="chain-steps-list">
                      {chainEditor.steps.map((step, index) => (
                        <div key={step._key || index} className="chain-step-wrapper">
                          {/* Drop zone before step */}
                          <div
                            className="chain-drop-zone"
                            onDragOver={handleChainDragOver}
                            onDrop={(e) => handleChainDropzoneDrop(e, index)}
                          />
                          <div
                            className={`chain-step chain-step--${step.stepType}`}
                            draggable
                            onDragStart={(e) => handleChainStepDragStart(e, index)}
                            onDragEnd={() => setChainDragSource(null)}
                          >
                            <div className="chain-step-drag-handle">
                              <i className="pi pi-bars" />
                            </div>
                            <div className="chain-step-content">
                              {step.stepType === 'wait' ? (
                                <div className="chain-step-wait">
                                  <i className="pi pi-clock" />
                                  <span>Warten:</span>
                                  <input
                                    type="number"
                                    min="1"
                                    max="3600"
                                    value={step.waitSeconds ?? 10}
                                    onChange={(e) => updateChainStepWait(index, Number(e.target.value))}
                                    className="chain-wait-input"
                                    onClick={(e) => e.stopPropagation()}
                                  />
                                  <span>Sekunden</span>
                                </div>
                              ) : (
                                <div className="chain-step-script">
                                  <i className="pi pi-code" />
                                  <span>{step.scriptName || `Script #${step.scriptId}`}</span>
                                </div>
                              )}
                            </div>
                            <Button
                              icon="pi pi-times"
                              severity="danger"
                              text
                              rounded
                              className="chain-step-remove"
                              onClick={() => removeChainStep(index)}
                              title="Schritt entfernen"
                            />
                          </div>
                        </div>
                      ))}
                      {/* Drop zone after last step */}
                      <div
                        className="chain-drop-zone chain-drop-zone--end"
                        onDragOver={handleChainDragOver}
                        onDrop={(e) => handleChainDropzoneDrop(e, chainEditor.steps.length)}
                      />
                    </div>
                  )}
                </div>
              </div>

              <div className="actions-row" style={{ marginTop: '1rem' }}>
                <Button
                  label={chainEditor.id ? 'Kette aktualisieren' : 'Kette erstellen'}
                  icon="pi pi-save"
                  onClick={handleSaveChain}
                  loading={chainSaving}
                />
                <Button
                  label="Abbrechen"
                  icon="pi pi-times"
                  severity="secondary"
                  outlined
                  onClick={closeChainEditor}
                  disabled={chainSaving}
                />
              </div>
            </Dialog>
          </TabPanel>

          <TabPanel header="Encode-Presets">
            <div className="actions-row">
              <Button
                label="Neues Preset"
                icon="pi pi-plus"
                onClick={openNewUserPreset}
                severity="success"
                outlined
                disabled={userPresetSaving}
              />
              <Button
                label="Presets neu laden"
                icon="pi pi-refresh"
                severity="secondary"
                onClick={() => Promise.all([loadUserPresets(), loadUserPresetDefaults()])}
                loading={userPresetsLoading || userPresetDefaultsLoading}
                disabled={userPresetSaving || userPresetDefaultsSaving}
              />
            </div>

            <div className="script-list-box" style={{ marginTop: '1rem' }}>
              <h4>Standard-Zuordnungen</h4>
              <small>
                Weise für jeden Medientyp/Fall ein Standard-Userpreset zu. Diese Zuordnungen werden persistent gespeichert.
              </small>
              <div className="user-preset-default-grid" style={{ marginTop: '0.8rem' }}>
                {USER_PRESET_DEFAULT_SLOTS.map((slot) => (
                  <div key={slot.key} className="user-preset-default-grid-item">
                    <label htmlFor={`user-preset-default-${slot.key}`} style={{ display: 'block', marginBottom: '0.3rem' }}>
                      {slot.label}
                    </label>
                    <Dropdown
                      id={`user-preset-default-${slot.key}`}
                      value={normalizePositiveId(userPresetDefaultsDraft?.[slot.key])}
                      options={userPresetDefaultOptionsBySlot?.[slot.key] || []}
                      optionLabel="label"
                      optionValue="value"
                      onChange={(event) => handleChangeUserPresetDefault(slot.key, event.value)}
                      placeholder="Preset auswählen"
                      showClear
                      disabled={userPresetDefaultsLoading || userPresetDefaultsSaving}
                      style={{ width: '100%' }}
                    />
                  </div>
                ))}
              </div>
              <div className="actions-row" style={{ marginTop: '0.8rem' }}>
                <Button
                  label="Zuordnungen speichern"
                  icon="pi pi-save"
                  onClick={handleSaveUserPresetDefaults}
                  loading={userPresetDefaultsSaving}
                  disabled={userPresetDefaultsLoading || !userPresetDefaultsDirty}
                />
                <Button
                  label="Änderungen verwerfen"
                  icon="pi pi-undo"
                  severity="secondary"
                  outlined
                  onClick={handleResetUserPresetDefaults}
                  disabled={userPresetDefaultsLoading || userPresetDefaultsSaving || !userPresetDefaultsDirty}
                />
              </div>
            </div>

            <small>
              Encode-Presets fassen ein HandBrake-Preset und zusätzliche CLI-Argumente zusammen.
              Sie sind medienbezogen (Blu-ray, DVD oder Universell) und können vor dem Encode
              in der Mediainfo-Prüfung ausgewählt werden. Kein Preset gewählt = Fallback aus Einstellungen.
            </small>

            {userPresetsLoading ? (
              <p style={{ marginTop: '1rem' }}>Lade Presets ...</p>
            ) : userPresets.length === 0 ? (
              <p style={{ marginTop: '1rem' }}>Keine Presets vorhanden. Lege ein neues Preset an.</p>
            ) : (
              <div className="script-list script-list--reorderable" style={{ marginTop: '1rem' }}>
                {userPresets.map((preset) => (
                  <div key={preset.id} className="script-list-item">
                    <div className="script-list-main">
                      <div className="script-title-line">
                        <strong className="script-id-title">#{preset.id} - {preset.name}</strong>
                        <span className="preset-media-type-tag">
                          {preset.mediaType === 'bluray' ? 'Blu-ray'
                            : preset.mediaType === 'dvd' ? 'DVD'
                            : 'Universell'}
                        </span>
                      </div>
                      <small className="preset-description-line" title={preset.description || ''}>
                        {preset.description || '-'}
                      </small>
                    </div>
                    <div className="script-list-actions script-list-actions--two">
                      <Button
                        icon="pi pi-pencil"
                        label="Bearbeiten"
                        severity="secondary"
                        outlined
                        onClick={() => openEditUserPreset(preset)}
                      />
                      <Button
                        icon="pi pi-trash"
                        label="Löschen"
                        severity="danger"
                        outlined
                        onClick={() => handleDeleteUserPreset(preset.id)}
                      />
                    </div>
                  </div>
                ))}
              </div>
            )}

            <Dialog
              header={userPresetEditor.id ? 'Preset bearbeiten' : 'Neues Preset'}
              visible={userPresetEditor.open}
              style={{ width: '520px' }}
              onHide={closeUserPresetEditor}
              modal
            >
              <div className="script-editor-fields" style={{ display: 'flex', flexDirection: 'column', gap: '0.8rem' }}>
                <div>
                  <label htmlFor="preset-name" style={{ display: 'block', marginBottom: '0.3rem' }}>Name *</label>
                  <InputText
                    id="preset-name"
                    value={userPresetEditor.name}
                    onChange={(e) => setUserPresetEditor((prev) => ({ ...prev, name: e.target.value }))}
                    placeholder="z.B. Blu-ray HQ"
                    style={{ width: '100%' }}
                  />
                  {userPresetErrors.name && <small className="error-text">{userPresetErrors.name}</small>}
                </div>

                <div>
                  <label htmlFor="preset-media-type" style={{ display: 'block', marginBottom: '0.3rem' }}>Medientyp</label>
                  <select
                    id="preset-media-type"
                    value={userPresetEditor.mediaType}
                    onChange={(e) => setUserPresetEditor((prev) => ({ ...prev, mediaType: e.target.value }))}
                    style={{ width: '100%', padding: '0.5rem', borderRadius: '4px', border: '1px solid var(--surface-border, #ccc)', background: 'var(--surface-overlay, #fff)', color: 'var(--text-color, #000)' }}
                  >
                    <option value="all">Universell (alle Medien)</option>
                    <option value="bluray">Blu-ray</option>
                    <option value="dvd">DVD</option>
                  </select>
                </div>

                <div>
                  <label htmlFor="preset-hb-preset" style={{ display: 'block', marginBottom: '0.3rem' }}>HandBrake Preset (-Z)</label>
                  <Dropdown
                    id="preset-hb-preset"
                    value={userPresetEditor.handbrakePreset}
                    options={userPresetHandBrakeOptions}
                    optionLabel="label"
                    optionValue="value"
                    optionDisabled="disabled"
                    onChange={(e) => setUserPresetEditor((prev) => ({ ...prev, handbrakePreset: String(e.value || '') }))}
                    placeholder="Preset auswählen"
                    showClear
                    style={{ width: '100%' }}
                  />
                </div>

                <div>
                  <label htmlFor="preset-extra-args" style={{ display: 'block', marginBottom: '0.3rem' }}>Extra Args</label>
                  <InputText
                    id="preset-extra-args"
                    value={userPresetEditor.extraArgs}
                    onChange={(e) => setUserPresetEditor((prev) => ({ ...prev, extraArgs: e.target.value }))}
                    placeholder="z.B. -q 22 --encoder x264"
                    style={{ width: '100%' }}
                  />
                </div>

                <div>
                  <label htmlFor="preset-description" style={{ display: 'block', marginBottom: '0.3rem' }}>Beschreibung (optional)</label>
                  <InputTextarea
                    id="preset-description"
                    value={userPresetEditor.description}
                    onChange={(e) => setUserPresetEditor((prev) => ({ ...prev, description: e.target.value }))}
                    rows={3}
                    autoResize
                    placeholder="Kurzbeschreibung für dieses Preset"
                    style={{ width: '100%' }}
                  />
                </div>

                <div className="actions-row" style={{ marginTop: '0.5rem' }}>
                  <Button
                    label={userPresetEditor.id ? 'Aktualisieren' : 'Erstellen'}
                    icon="pi pi-save"
                    onClick={handleSaveUserPreset}
                    loading={userPresetSaving}
                  />
                  <Button
                    label="Abbrechen"
                    icon="pi pi-times"
                    severity="secondary"
                    outlined
                    onClick={closeUserPresetEditor}
                    disabled={userPresetSaving}
                  />
                </div>
              </div>
            </Dialog>
          </TabPanel>

          <TabPanel header="Cronjobs">
            <CronJobsTab />
          </TabPanel>
        </TabView>
      </Card>

      {expertModeEnabled && activationBytes.length > 0 && (
        <Card
          title="Activation Bytes Cache"
          subTitle="Lokal gespeicherte AAX-Activation Bytes. Werden beim ersten Upload automatisch über die Audible-Tools API ermittelt."
          style={{ marginTop: '1.5rem' }}
        >
          <table style={{ width: '100%', borderCollapse: 'collapse', fontFamily: 'monospace', fontSize: '0.875rem' }}>
            <thead>
              <tr style={{ borderBottom: '1px solid var(--surface-border)' }}>
                <th style={{ textAlign: 'left', padding: '0.5rem 0.75rem' }}>Checksum</th>
                <th style={{ textAlign: 'left', padding: '0.5rem 0.75rem' }}>Activation Bytes</th>
                <th style={{ textAlign: 'left', padding: '0.5rem 0.75rem' }}>Gespeichert am</th>
              </tr>
            </thead>
            <tbody>
              {activationBytes.map((entry) => (
                <tr key={entry.checksum} style={{ borderBottom: '1px solid var(--surface-border)' }}>
                  <td style={{ padding: '0.5rem 0.75rem', color: 'var(--text-color-secondary)' }}>{entry.checksum}</td>
                  <td style={{ padding: '0.5rem 0.75rem', fontWeight: 'bold' }}>{entry.activation_bytes}</td>
                  <td style={{ padding: '0.5rem 0.75rem', color: 'var(--text-color-secondary)' }}>{new Date(entry.created_at).toLocaleString('de-DE')}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </Card>
      )}

    </div>
  );
}
