(function () {
  let active = false;
  let initialized = false;
  const API_URL = (() => {
    if (window.API_URL) return window.API_URL;
    try {
      return `${window.location.origin}/api`;
    } catch (error) {
      return 'https://fb.srv.medeba-media.de/api';
    }
  })();
  const DEFAULT_INTERVAL_MINUTES = 60;

  const state = {
    requests: [],
    runs: [],
    selectedId: null,
    loading: false,
    saving: false,
    running: false
  };

  let editingId = null;
  let exclusionWindows = [];

  const form = document.getElementById('automationForm');
  const nameInput = document.getElementById('nameInput');
  const descriptionInput = document.getElementById('descriptionInput');
  const urlInput = document.getElementById('urlInput');
  const methodSelect = document.getElementById('methodSelect');
  const headersInput = document.getElementById('headersInput');
  const bodyInput = document.getElementById('bodyInput');
  const intervalPreset = document.getElementById('intervalPreset');
  const intervalMinutesInput = document.getElementById('intervalMinutesInput');
  const jitterInput = document.getElementById('jitterInput');
  const startAtInput = document.getElementById('startAtInput');
  const runUntilInput = document.getElementById('runUntilInput');
  const excludeStartInput = document.getElementById('excludeStartInput');
  const excludeEndInput = document.getElementById('excludeEndInput');
  const addExclusionBtn = document.getElementById('addExclusionBtn');
  const exclusionList = document.getElementById('exclusionList');
  const activeToggle = document.getElementById('activeToggle');
  const formStatus = document.getElementById('formStatus');
  const formModeLabel = document.getElementById('formModeLabel');
  const formModal = document.getElementById('formModal');
  const modalBackdrop = document.getElementById('formModalBackdrop');
  const modalCloseBtn = document.getElementById('modalCloseBtn');
  const typeSelect = document.getElementById('typeSelect');
  const emailSection = document.querySelector('[data-section="email"]');
  const flowSection = document.querySelector('[data-section="flow"]');
  const httpSection = document.querySelector('[data-section="http"]');
  const emailToInput = document.getElementById('emailToInput');
  const emailSubjectInput = document.getElementById('emailSubjectInput');
  const emailBodyInput = document.getElementById('emailBodyInput');
  const flowStep1Url = document.getElementById('flowStep1Url');
  const flowStep1Method = document.getElementById('flowStep1Method');
  const flowStep1Headers = document.getElementById('flowStep1Headers');
  const flowStep1Body = document.getElementById('flowStep1Body');
  const flowStep2Url = document.getElementById('flowStep2Url');
  const flowStep2Method = document.getElementById('flowStep2Method');
  const flowStep2Headers = document.getElementById('flowStep2Headers');
  const flowStep2Body = document.getElementById('flowStep2Body');

  const requestTableBody = document.getElementById('requestTableBody');
  const listStatus = document.getElementById('listStatus');
  const filterName = document.getElementById('filterName');
  const filterNext = document.getElementById('filterNext');
  const filterRunUntil = document.getElementById('filterRunUntil');
  const filterLast = document.getElementById('filterLast');
  const filterStatus = document.getElementById('filterStatus');
  const filterRuns = document.getElementById('filterRuns');
  const runsList = document.getElementById('runsList');
  const runsStatus = document.getElementById('runsStatus');
  const heroStats = document.getElementById('heroStats');

  const saveBtn = document.getElementById('saveBtn');
  const resetFormBtn = document.getElementById('resetFormBtn');
  const refreshBtn = document.getElementById('refreshBtn');
  const newAutomationBtn = document.getElementById('newAutomationBtn');
  const modalTitle = document.getElementById('modalTitle');
  const importInput = document.getElementById('importInput');
  const importStatus = document.getElementById('importStatus');
  const applyImportBtn = document.getElementById('applyImportBtn');
  const openImportBtn = document.getElementById('openImportBtn');
  const importModal = document.getElementById('importModal');
  const importModalBackdrop = document.getElementById('importModalBackdrop');
  const importCloseBtn = document.getElementById('importCloseBtn');
  const previewUrl = document.getElementById('previewUrl');
  const previewHeaders = document.getElementById('previewHeaders');
  const previewBody = document.getElementById('previewBody');
  const refreshPreviewBtn = document.getElementById('refreshPreviewBtn');
  const placeholderTableBody = document.getElementById('placeholderTableBody');

  let sortState = { key: 'next', dir: 'asc' };
  let listInstance = null;
  let sse = null;
  let relativeTimer = null;

  function handleUnauthorized(response) {
    if (response && response.status === 401) {
      if (typeof redirectToLogin === 'function') {
        redirectToLogin();
      } else {
        window.location.href = 'login.html';
      }
      return true;
    }
    return false;
  }

  function toDateTimeLocal(value) {
    if (!value) return '';
    const date = value instanceof Date ? value : new Date(value);
    if (Number.isNaN(date.getTime())) return '';
    const pad = (num) => String(num).padStart(2, '0');
    return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
  }

  function parseDateInput(value) {
    if (!value) return null;
    const date = new Date(value);
    return Number.isNaN(date.getTime()) ? null : date.toISOString();
  }

  async function apiFetchJSON(path, options = {}) {
    const opts = {
      credentials: 'include',
      ...options
    };
    opts.headers = {
      'Content-Type': 'application/json',
      ...(options.headers || {})
    };
    const response = await fetch(`${API_URL}${path}`, opts);
    if (handleUnauthorized(response)) {
      throw new Error('Nicht angemeldet');
    }
    if (!response.ok) {
      let message = 'Unbekannter Fehler';
      try {
        const err = await response.json();
        message = err.error || message;
      } catch (error) {
        // ignore
      }
      throw new Error(message);
    }
    if (response.status === 204) {
      return null;
    }
    return response.json();
  }

  function setStatus(target, message, type = 'info') {
    if (!target) return;
    target.textContent = message || '';
    target.classList.remove('error', 'success');
    if (type === 'error') target.classList.add('error');
    if (type === 'success') target.classList.add('success');
  }

  function formatDateTime(value) {
    if (!value) return '—';
    const date = new Date(value);
    if (Number.isNaN(date.getTime())) return '—';
    return date.toLocaleString('de-DE', {
      day: '2-digit',
      month: '2-digit',
      year: 'numeric',
      hour: '2-digit',
      minute: '2-digit'
    });
  }

  function formatRelative(value) {
    if (!value) return '—';
    const date = new Date(value);
    const now = new Date();
    const diffMs = date.getTime() - now.getTime();
    const diffMinutes = Math.round(diffMs / 60000);
    if (Number.isNaN(diffMinutes)) return '—';
    if (Math.abs(diffMinutes) < 1) return 'jetzt';
    if (diffMinutes > 0) {
      if (diffMinutes < 60) return `in ${diffMinutes} Min`;
      const hours = Math.round(diffMinutes / 60);
      if (hours < 48) return `in ${hours} Std`;
      const days = Math.round(hours / 24);
      return `in ${days} Tagen`;
    } else {
      const abs = Math.abs(diffMinutes);
      if (abs < 60) return `vor ${abs} Min`;
      const hours = Math.round(abs / 60);
      if (hours < 48) return `vor ${hours} Std`;
      const days = Math.round(hours / 24);
      return `vor ${days} Tagen`;
    }
  }

  function parseHeadersInput(text) {
    const trimmed = (text || '').trim();
    if (!trimmed) return {};
    if (trimmed.startsWith('{')) {
      try {
        const parsed = JSON.parse(trimmed);
        if (parsed && typeof parsed === 'object') {
          return parsed;
        }
      } catch (error) {
        // fall back to line parsing
      }
    }
    const headers = {};
    trimmed.split('\n').forEach((line) => {
      const idx = line.indexOf(':');
      if (idx === -1) return;
      const key = line.slice(0, idx).trim();
      const value = line.slice(idx + 1).trim();
      if (key) {
        headers[key] = value;
      }
    });
    return headers;
  }

  function stringifyHeaders(headers) {
    if (!headers || typeof headers !== 'object') return '';
    return Object.entries(headers)
      .map(([key, value]) => `${key}: ${value}`)
      .join('\n');
  }

  function buildTemplateContext() {
    const now = new Date();
    return {
      now,
      date: now.toISOString().slice(0, 10),
      today: now.toISOString().slice(0, 10),
      iso: now.toISOString(),
      datetime: now.toISOString(),
      timestamp: now.getTime(),
      year: now.getFullYear(),
      month: String(now.getMonth() + 1).padStart(2, '0'),
      day: String(now.getDate()).padStart(2, '0'),
      hour: String(now.getHours()).padStart(2, '0'),
      minute: String(now.getMinutes()).padStart(2, '0'),
      weekday: now.toLocaleDateString('de-DE', { weekday: 'long' }),
      weekday_short: now.toLocaleDateString('de-DE', { weekday: 'short' })
    };
  }

  function renderTemplate(template, context = {}) {
    if (typeof template !== 'string') return '';
    const baseDate = context.now instanceof Date ? context.now : new Date();
    const uuidFn = typeof crypto !== 'undefined' && crypto.randomUUID
      ? () => crypto.randomUUID()
      : () => Math.random().toString(16).slice(2, 10);

    return template.replace(/\{\{\s*([^}\s]+)\s*\}\}/g, (_, keyRaw) => {
      const key = String(keyRaw || '').trim();
      if (!key) return '';

      if (key === 'uuid') return uuidFn();

      const dateOffsetMatch = key.match(/^date([+-]\d+)?$/);
      if (dateOffsetMatch) {
        const offset = dateOffsetMatch[1] ? parseInt(dateOffsetMatch[1], 10) : 0;
        const shifted = new Date(baseDate);
        shifted.setDate(baseDate.getDate() + (Number.isNaN(offset) ? 0 : offset));
        return shifted.toISOString().slice(0, 10);
      }

      const dayOffsetMatch = key.match(/^day([+-]\d+)?$/);
      if (dayOffsetMatch) {
        const offset = dayOffsetMatch[1] ? parseInt(dayOffsetMatch[1], 10) : 0;
        const shifted = new Date(baseDate);
        shifted.setDate(baseDate.getDate() + (Number.isNaN(offset) ? 0 : offset));
        return String(shifted.getDate()).padStart(2, '0');
      }

      switch (key) {
        case 'today':
        case 'date':
          return baseDate.toISOString().slice(0, 10);
        case 'iso':
        case 'now':
        case 'datetime':
          return baseDate.toISOString();
        case 'timestamp':
          return String(baseDate.getTime());
        case 'year':
          return String(baseDate.getFullYear());
        case 'month':
          return String(baseDate.getMonth() + 1).padStart(2, '0');
        case 'day':
          return String(baseDate.getDate()).padStart(2, '0');
        case 'hour':
          return String(baseDate.getHours()).padStart(2, '0');
        case 'minute':
          return String(baseDate.getMinutes()).padStart(2, '0');
        case 'weekday':
          return baseDate.toLocaleDateString('de-DE', { weekday: 'long' });
        case 'weekday_short':
          return baseDate.toLocaleDateString('de-DE', { weekday: 'short' });
        default:
          return context[key] !== undefined && context[key] !== null
            ? String(context[key])
            : '';
      }
    });
  }

  function renderHeaderTemplates(rawHeaders, context) {
    const parsed = parseHeadersInput(rawHeaders);
    const rendered = {};
    Object.entries(parsed).forEach(([key, value]) => {
      const renderedKey = renderTemplate(key, context).trim();
      if (!renderedKey) return;
      rendered[renderedKey] = renderTemplate(
        value === undefined || value === null ? '' : String(value),
        context
      );
    });
    return rendered;
  }

  function renderPlaceholderTable(context) {
    if (!placeholderTableBody) return;
    const rows = [
      { key: '{{date}}', value: context.date },
      { key: '{{date+1}}', value: renderTemplate('{{date+1}}', context) },
      { key: '{{day}}', value: context.day },
      { key: '{{day-1}}', value: renderTemplate('{{day-1}}', context) },
      { key: '{{datetime}}', value: context.datetime },
      { key: '{{iso}}', value: context.iso },
      { key: '{{timestamp}}', value: context.timestamp },
      { key: '{{uuid}}', value: renderTemplate('{{uuid}}', context) },
      { key: '{{year}}', value: context.year },
      { key: '{{month}}', value: context.month },
      { key: '{{day}}', value: context.day },
      { key: '{{hour}}', value: context.hour },
      { key: '{{minute}}', value: context.minute },
      { key: '{{weekday}}', value: context.weekday },
      { key: '{{weekday_short}}', value: context.weekday_short }
    ];
    placeholderTableBody.innerHTML = rows.map((row) => `
      <tr>
        <td class="placeholder-key">${row.key}</td>
        <td>${row.value}</td>
      </tr>
    `).join('');
  }

  function buildPayloadFromForm() {
    const type = typeSelect ? typeSelect.value : 'request';
    const base = {
      type,
      name: nameInput.value.trim(),
      description: descriptionInput.value.trim(),
      interval_minutes: intervalPreset.value === 'custom'
        ? Math.max(5, parseInt(intervalMinutesInput.value, 10) || DEFAULT_INTERVAL_MINUTES)
        : undefined,
      schedule_type: intervalPreset.value !== 'custom' ? intervalPreset.value : undefined,
      jitter_minutes: Math.max(0, parseInt(jitterInput.value, 10) || 0),
      start_at: parseDateInput(startAtInput.value),
      run_until: parseDateInput(runUntilInput.value),
      active: activeToggle.checked,
      exclusion_windows: exclusionWindows.map((win) => ({ start: win.start, end: win.end }))
    };

    if (type === 'email') {
      return {
        ...base,
        email_to: emailToInput?.value.trim(),
        email_subject_template: emailSubjectInput?.value.trim(),
        email_body_template: emailBodyInput?.value
      };
    }

    if (type === 'flow') {
      const steps = [];
      const step1Url = flowStep1Url?.value.trim();
      if (step1Url) {
        steps.push({
          method: flowStep1Method?.value || 'GET',
          url: step1Url,
          headers: parseHeadersInput(flowStep1Headers?.value || ''),
          body: flowStep1Body?.value || ''
        });
      }
      const step2Url = flowStep2Url?.value.trim();
      if (step2Url) {
        steps.push({
          method: flowStep2Method?.value || 'GET',
          url: step2Url,
          headers: parseHeadersInput(flowStep2Headers?.value || ''),
          body: flowStep2Body?.value || ''
        });
      }
      return {
        ...base,
        steps
      };
    }

    return {
      ...base,
      method: methodSelect.value,
      url_template: urlInput.value.trim(),
      headers: parseHeadersInput(headersInput.value),
      body_template: bodyInput.value
    };
  }

  function renderExclusionChips() {
    if (!exclusionList) return;
    if (!exclusionWindows.length) {
      exclusionList.innerHTML = '<span class="placeholder-hint">Keine Ausschlusszeiten gesetzt.</span>';
      return;
    }
    exclusionList.innerHTML = exclusionWindows.map((win, idx) => `
      <span class="exclusion-chip">
        ${win.start} – ${win.end}
        <button type="button" aria-label="Entfernen" data-remove-exclusion="${idx}">×</button>
      </span>
    `).join('');
    exclusionList.querySelectorAll('[data-remove-exclusion]').forEach((btn) => {
      btn.addEventListener('click', () => {
        const index = Number(btn.getAttribute('data-remove-exclusion'));
        exclusionWindows.splice(index, 1);
        renderExclusionChips();
      });
    });
  }

  function addExclusion() {
    const start = excludeStartInput?.value || '';
    const end = excludeEndInput?.value || '';
    if (!start || !end) {
      setStatus(formStatus, 'Bitte Start- und Endzeit angeben.', 'error');
      return;
    }
    if (start >= end) {
      setStatus(formStatus, 'Endzeit muss nach der Startzeit liegen.', 'error');
      return;
    }
    exclusionWindows.push({ start, end });
    exclusionWindows.sort((a, b) => a.start.localeCompare(b.start));
    renderExclusionChips();
    setStatus(formStatus, '', 'info');
    if (excludeStartInput) excludeStartInput.value = '';
    if (excludeEndInput) excludeEndInput.value = '';
  }

  function applyPresetDisabling() {
    if (intervalPreset.value === 'custom') {
      intervalMinutesInput.disabled = false;
    } else {
      intervalMinutesInput.disabled = true;
      intervalMinutesInput.value = intervalPreset.value === 'daily' ? 1440 : DEFAULT_INTERVAL_MINUTES;
    }
  }

  function refreshPreview() {
    if (!previewUrl || !previewHeaders || !previewBody) return;
    const type = typeSelect ? typeSelect.value : 'request';
    const context = buildTemplateContext();
    renderPlaceholderTable(context);
    if (type === 'email') {
      const renderedTo = renderTemplate(emailToInput?.value || '', context);
      const renderedSubject = renderTemplate(emailSubjectInput?.value || '', context);
      const renderedEmailBody = renderTemplate(emailBodyInput?.value || '', context);
      previewUrl.textContent = `To: ${renderedTo || '—'}\nSubject: ${renderedSubject || '—'}`;
      previewHeaders.textContent = '—';
      previewBody.textContent = renderedEmailBody || '—';
      return;
    }

    if (type === 'flow') {
      const steps = [];
      if (flowStep1Url?.value) {
        steps.push({
          label: 'Step 1',
          url: renderTemplate(flowStep1Url.value, context),
          method: flowStep1Method?.value || 'GET',
          headers: renderHeaderTemplates(flowStep1Headers?.value || '', context),
          body: renderTemplate(flowStep1Body?.value || '', context)
        });
      }
      if (flowStep2Url?.value) {
        steps.push({
          label: 'Step 2',
          url: renderTemplate(flowStep2Url.value, context),
          method: flowStep2Method?.value || 'GET',
          headers: renderHeaderTemplates(flowStep2Headers?.value || '', context),
          body: renderTemplate(flowStep2Body?.value || '', context)
        });
      }
      previewUrl.textContent = steps.length
        ? steps.map((s) => `${s.label}: ${s.method} ${s.url || '—'}`).join('\n')
        : '—';
      previewHeaders.textContent = steps.length
        ? steps.map((s) => `${s.label} Headers:\n${stringifyHeaders(s.headers) || '—'}`).join('\n\n')
        : '—';
      previewBody.textContent = steps.length
        ? steps.map((s) => `${s.label} Body:\n${s.body || '—'}`).join('\n\n')
        : '—';
      return;
    }

    const renderedUrl = renderTemplate(urlInput.value || '', context);
    const headersObj = renderHeaderTemplates(headersInput.value || '', context);
    const renderedBody = renderTemplate(bodyInput.value || '', context);

    previewUrl.textContent = renderedUrl || '—';
    previewHeaders.textContent = Object.keys(headersObj).length
      ? stringifyHeaders(headersObj)
      : '—';
    previewBody.textContent = renderedBody || '—';
  }

  async function loadRequests() {
    if (!active) return;
    if (!state.loading) {
      setStatus(listStatus, 'Lade Automationen...');
    }
    state.loading = true;
    try {
      const data = await apiFetchJSON('/automation/requests');
      state.requests = Array.isArray(data) ? data : [];
      renderRequests();
      renderHero();
      if (state.selectedId) {
        const stillExists = state.requests.some((req) => req.id === state.selectedId);
        if (!stillExists) {
          state.selectedId = null;
          state.runs = [];
          renderRuns();
        }
      }
      if (!state.selectedId && state.requests.length) {
        selectRequest(state.requests[0].id, { focusForm: false });
      }
      setStatus(listStatus, state.requests.length ? '' : 'Noch keine Automationen angelegt.');
    } catch (error) {
      console.error(error);
      setStatus(listStatus, error.message || 'Automationen konnten nicht geladen werden', 'error');
    } finally {
      state.loading = false;
    }
  }

  function renderHero() {
    if (!heroStats) return;
    const active = state.requests.filter((req) => req.active);
    const nextRun = active
      .filter((req) => req.next_run_at)
      .sort((a, b) => new Date(a.next_run_at) - new Date(b.next_run_at))[0];
    const lastRun = [...state.requests]
      .filter((req) => req.last_run_at)
      .sort((a, b) => new Date(b.last_run_at) - new Date(a.last_run_at))[0];

    heroStats.innerHTML = `
      <div class="stat">
        <div class="stat-label">Aktive Automationen</div>
        <div class="stat-value">${active.length}/${state.requests.length}</div>
      </div>
      <div class="stat">
        <div class="stat-label">Nächster Lauf</div>
        <div class="stat-value">${nextRun ? formatRelative(nextRun.next_run_at) : '—'}</div>
      </div>
      <div class="stat">
        <div class="stat-label">Letzter Status</div>
        <div class="stat-value">${lastRun ? (lastRun.last_status || '—') : '—'}</div>
      </div>
    `;
  }

  function updateRelativeTimes() {
    if (!active) return;
    renderHero();
    if (!requestTableBody || !state.requests.length) return;
    const byId = new Map(state.requests.map((req) => [String(req.id), req]));
    requestTableBody.querySelectorAll('tr[data-id]').forEach((row) => {
      const req = byId.get(row.dataset.id);
      if (!req) return;
      const nextEl = row.querySelector('.next');
      if (nextEl) nextEl.textContent = formatRelative(req.next_run_at);
      const untilEl = row.querySelector('.until');
      if (untilEl) untilEl.textContent = req.run_until ? formatRelative(req.run_until) : '—';
      const lastEl = row.querySelector('.last');
      if (lastEl) lastEl.textContent = formatRelative(req.last_run_at);
    });
  }

  function renderRequests() {
    if (!requestTableBody) return;
    requestTableBody.innerHTML = '';
    if (!state.requests.length) {
      requestTableBody.innerHTML = '<tr><td colspan="7">Keine Automationen vorhanden.</td></tr>';
      listInstance = null;
      return;
    }

    const rows = state.requests.map((req) => {
      const isSelected = state.selectedId === req.id;
      const nextSort = req.next_run_at ? new Date(req.next_run_at).getTime() : Number.MAX_SAFE_INTEGER;
      const untilSort = req.run_until ? new Date(req.run_until).getTime() : Number.MAX_SAFE_INTEGER;
      const lastSort = req.last_run_at ? new Date(req.last_run_at).getTime() : -Number.MAX_SAFE_INTEGER;
      const runsCount = Array.isArray(req.runs) ? req.runs.length : (req.runs_count || req.runsCount || 0);
      const statusBadge = req.last_status === 'success'
        ? '<span class="badge success">OK</span>'
        : req.last_status === 'error'
          ? '<span class="badge error">Fehler</span>'
          : '<span class="badge">—</span>';
      let subline = '';
      if (req.type === 'email') {
        subline = `E-Mail → ${req.email_to || '—'}`;
      } else if (req.type === 'flow') {
        const stepCount = Array.isArray(req.steps) ? req.steps.length : 0;
        subline = `Flow (${stepCount || '0'} Schritte)`;
      } else {
        subline = `${req.method || 'GET'} · ${req.url_template || '—'}`;
      }

      return `
        <tr data-id="${req.id}" class="${isSelected ? 'is-selected' : ''}">
          <td data-sort="${req.name || ''}">
            <div class="row-title name">${req.name}</div>
            <div class="row-sub">${subline}</div>
            <span class="type hidden-value">${req.type || ''}</span>
            <span class="email hidden-value">${req.email_to || ''}</span>
            <span class="url hidden-value">${req.url_template || ''}</span>
            <span class="steps hidden-value">${req.steps && req.steps.map((s) => s.url || s.url_template || '').join(' | ')}</span>
          </td>
          <td data-sort="${nextSort}">
            <span class="next" data-sort="${nextSort}">${formatRelative(req.next_run_at)}</span>
            <br><small>${formatDateTime(req.next_run_at)}</small>
          </td>
          <td data-sort="${untilSort}">
            <span class="runUntil until" data-sort="${untilSort}">${req.run_until ? formatRelative(req.run_until) : '—'}</span>
            <br><small>${formatDateTime(req.run_until)}</small>
          </td>
        <td data-sort="${lastSort}">
          <span class="last" data-sort="${lastSort}">${formatRelative(req.last_run_at)}</span>
          <br><small>${req.last_status_code || '—'}</small>
        </td>
        <td data-sort="${req.last_status || ''}"><span class="status" data-sort="${req.last_status || ''}">${statusBadge}</span></td>
        <td data-sort="${runsCount}" class="runs-count">
          <span class="runs hidden-value" data-sort="${runsCount}">${runsCount}</span>
          ${runsCount}
        </td>
        <td>
          <div class="row-actions">
            <button class="icon-btn" title="Bearbeiten" data-action="edit" data-id="${req.id}" type="button">✏️</button>
            <button class="icon-btn" title="Run" data-action="run" data-id="${req.id}" type="button">▶️</button>
            <button class="icon-btn" title="${req.active ? 'Pause' : 'Aktivieren'}" data-action="toggle" data-id="${req.id}" type="button">${req.active ? '⏸' : '▶'}</button>
              <button class="icon-btn danger" title="Löschen" data-action="delete" data-id="${req.id}" type="button">🗑️</button>
            </div>
          </td>
        </tr>
      `;
    });

    requestTableBody.innerHTML = rows.join('');

    initListInstance();
  }

  function renderRuns() {
    if (!runsList) return;
    runsList.innerHTML = '';
    if (!state.runs.length) {
      runsList.innerHTML = '<li class="run-item">Noch keine Läufe.</li>';
      return;
    }

    runsList.innerHTML = state.runs.map((run) => {
      const badge = run.status === 'success'
        ? '<span class="badge success">OK</span>'
        : run.status === 'error'
          ? '<span class="badge error">Fehler</span>'
          : '<span class="badge">Offen</span>';
      return `
        <li class="run-item">
          <div class="run-top">
            <div class="run-meta">
              ${badge}
              <span>${formatDateTime(run.started_at)}</span>
              <span>Code: ${run.status_code ?? '—'}</span>
              <span>Dauer: ${run.duration_ms ? `${run.duration_ms} ms` : '—'}</span>
            </div>
          </div>
          ${run.error ? `<div class="run-body">Fehler: ${run.error}</div>` : ''}
          ${run.response_body ? `<div class="run-body">${run.response_body}</div>` : ''}
        </li>
      `;
    }).join('');
  }

  function resetForm() {
    form.reset();
    editingId = null;
    if (typeSelect) typeSelect.value = 'request';
    intervalPreset.value = 'hourly';
    intervalMinutesInput.value = DEFAULT_INTERVAL_MINUTES;
    applyPresetDisabling();
    const now = new Date();
    now.setMinutes(now.getMinutes() + 5);
    now.setSeconds(0, 0);
    startAtInput.value = toDateTimeLocal(now);
    runUntilInput.value = '';
    exclusionWindows = [];
    renderExclusionChips();
    formModeLabel.textContent = 'Neue Automation';
    setStatus(formStatus, '');
    if (emailToInput) emailToInput.value = '';
    if (emailSubjectInput) emailSubjectInput.value = '';
    if (emailBodyInput) emailBodyInput.value = '';
    [flowStep1Url, flowStep1Headers, flowStep1Body, flowStep2Url, flowStep2Headers, flowStep2Body]
      .forEach((el) => { if (el) el.value = ''; });
    if (flowStep1Method) flowStep1Method.value = 'GET';
    if (flowStep2Method) flowStep2Method.value = 'GET';
    applyTypeVisibility();
    refreshPreview();
  }

  function fillForm(request) {
    if (!request) return;
    editingId = request.id;
    formModeLabel.textContent = `Automation bearbeiten`;
    nameInput.value = request.name || '';
    descriptionInput.value = request.description || '';
    urlInput.value = request.url_template || '';
    methodSelect.value = request.method || 'GET';
    headersInput.value = stringifyHeaders(request.headers);
    bodyInput.value = request.body_template || '';
    activeToggle.checked = !!request.active;
    if (typeSelect) {
      typeSelect.value = request.type || 'request';
      applyTypeVisibility();
    }
    if (request.type === 'email') {
      emailToInput.value = request.email_to || '';
      emailSubjectInput.value = request.email_subject_template || '';
      emailBodyInput.value = request.email_body_template || '';
    } else if (request.type === 'flow') {
      const steps = Array.isArray(request.steps) ? request.steps : [];
      const [s1, s2] = steps;
      if (s1) {
        flowStep1Url.value = s1.url || s1.url_template || '';
        flowStep1Method.value = s1.method || s1.http_method || 'GET';
        flowStep1Headers.value = stringifyHeaders(s1.headers || {});
        flowStep1Body.value = s1.body || s1.body_template || '';
      }
      if (s2) {
        flowStep2Url.value = s2.url || s2.url_template || '';
        flowStep2Method.value = s2.method || s2.http_method || 'GET';
        flowStep2Headers.value = stringifyHeaders(s2.headers || {});
        flowStep2Body.value = s2.body || s2.body_template || '';
      }
    }

    if (request.interval_minutes === 60) {
      intervalPreset.value = 'hourly';
    } else if (request.interval_minutes === 1440) {
      intervalPreset.value = 'daily';
    } else {
      intervalPreset.value = 'custom';
    }
    intervalMinutesInput.value = request.interval_minutes || DEFAULT_INTERVAL_MINUTES;
    jitterInput.value = request.jitter_minutes || 0;
    startAtInput.value = toDateTimeLocal(request.start_at);
    runUntilInput.value = toDateTimeLocal(request.run_until);
    exclusionWindows = Array.isArray(request.exclusion_windows) ? [...request.exclusion_windows] : [];
    renderExclusionChips();
    applyPresetDisabling();
    refreshPreview();
  }

  async function handleSubmit(event) {
    event.preventDefault();
    if (state.saving) return;
    const payload = buildPayloadFromForm();
    if (!payload.name) {
      setStatus(formStatus, 'Name ist ein Pflichtfeld.', 'error');
      return;
    }
    if (payload.type === 'request' && !payload.url_template) {
      setStatus(formStatus, 'URL ist ein Pflichtfeld für HTTP-Requests.', 'error');
      return;
    }
    if (payload.type === 'email') {
      if (!payload.email_to || !payload.email_subject_template || !payload.email_body_template) {
        setStatus(formStatus, 'E-Mail: Empfänger, Betreff und Body sind Pflichtfelder.', 'error');
        return;
      }
    }
    if (payload.type === 'flow') {
      const hasStep = Array.isArray(payload.steps) && payload.steps.length > 0 && payload.steps[0].url;
      if (!hasStep) {
        setStatus(formStatus, 'Flow: Mindestens ein Schritt mit URL ist erforderlich.', 'error');
        return;
      }
    }

    state.saving = true;
    setStatus(formStatus, 'Speichere...');
    saveBtn.disabled = true;

    try {
      if (editingId) {
        await apiFetchJSON(`/automation/requests/${editingId}`, {
          method: 'PUT',
          body: JSON.stringify(payload)
        });
      } else {
        await apiFetchJSON('/automation/requests', {
          method: 'POST',
          body: JSON.stringify(payload)
        });
      }
      setStatus(formStatus, 'Gespeichert.', 'success');
      closeFormModal();
      await loadRequests();
    } catch (error) {
      console.error(error);
      setStatus(formStatus, error.message || 'Konnte nicht speichern', 'error');
    } finally {
      state.saving = false;
      saveBtn.disabled = false;
    }
  }

  async function loadRuns(requestId) {
    if (!requestId) {
      state.runs = [];
      renderRuns();
      return;
    }
    setStatus(runsStatus, 'Lade Runs...');
    try {
      const data = await apiFetchJSON(`/automation/requests/${requestId}/runs?limit=50`);
      state.runs = Array.isArray(data) ? data : [];
      renderRuns();
      setStatus(runsStatus, state.runs.length ? '' : 'Keine Läufe vorhanden.');
    } catch (error) {
      console.error(error);
      setStatus(runsStatus, error.message || 'Runs konnten nicht geladen werden', 'error');
    }
  }

  function selectRequest(id, options = {}) {
    state.selectedId = id;
    const request = state.requests.find((item) => item.id === id);
    if (!request) {
      state.runs = [];
      renderRuns();
      return;
    }
    renderRequests();
    loadRuns(id);
    if (options.focusForm) {
      nameInput.focus();
    }
  }

  async function runAutomation(id) {
    if (!id) {
      setStatus(formStatus, 'Keine Automation ausgewählt.', 'error');
      return;
    }
    if (state.running) return;
    state.running = true;
    setStatus(formStatus, 'Starte manuellen Run...');
    try {
      await apiFetchJSON(`/automation/requests/${id}/run`, { method: 'POST' });
      await loadRequests();
      await loadRuns(id);
      setStatus(formStatus, 'Run gestartet.', 'success');
    } catch (error) {
      console.error(error);
      setStatus(formStatus, error.message || 'Run fehlgeschlagen', 'error');
    } finally {
      state.running = false;
    }
  }

  async function toggleActive(id) {
    const request = state.requests.find((item) => item.id === id);
    if (!request) return;
    try {
      await apiFetchJSON(`/automation/requests/${id}`, {
        method: 'PUT',
        body: JSON.stringify({ active: !request.active })
      });
      await loadRequests();
    } catch (error) {
      console.error(error);
      setStatus(listStatus, error.message || 'Konnte Status nicht ändern', 'error');
    }
  }

  async function deleteAutomation(id) {
    const request = state.requests.find((item) => item.id === id);
    if (!request) return;
    if (!confirm(`Automation "${request.name}" wirklich löschen?`)) {
      return;
    }
    try {
      await apiFetchJSON(`/automation/requests/${id}`, { method: 'DELETE' });
      if (state.selectedId === id) {
        state.selectedId = null;
        state.runs = [];
        renderRuns();
      }
      await loadRequests();
    } catch (error) {
      console.error(error);
      setStatus(listStatus, error.message || 'Konnte nicht löschen', 'error');
    }
  }

  function parseCurlTemplate(text) {
    const result = {};
    const urlMatch = text.match(/curl\s+(['"]?)([^'"\s]+)\1/);
    if (urlMatch) result.url = urlMatch[2];
    const methodMatch = text.match(/-X\s+([A-Z]+)/i) || text.match(/--request\s+([A-Z]+)/i);
    if (methodMatch) result.method = methodMatch[1].toUpperCase();
    const headers = {};
    const headerRegex = /-H\s+(?:(?:"([^"]+)")|(?:'([^']+)'))/gi;
    let headerMatch;
    while ((headerMatch = headerRegex.exec(text)) !== null) {
      const raw = headerMatch[1] || headerMatch[2] || '';
      const idx = raw.indexOf(':');
      if (idx > -1) {
        const key = raw.slice(0, idx).trim();
        const value = raw.slice(idx + 1).trim();
        if (key) headers[key] = value;
      }
    }
    if (Object.keys(headers).length) result.headers = headers;
    const dataMatch = text.match(/--data(?:-raw)?\s+(?:"([^"]*)"|'([^']*)')/i);
    if (dataMatch) {
      result.body = dataMatch[1] || dataMatch[2] || '';
    }
    return result;
  }

  function parseFetchTemplate(text) {
    const result = {};
    const urlMatch = text.match(/fetch\(\s*['"]([^'"]+)['"]/i);
    if (urlMatch) result.url = urlMatch[1];
    const optionsMatch = text.match(/fetch\(\s*['"][^'"]+['"]\s*,\s*({[\s\S]*?})\s*\)/i);
    if (optionsMatch) {
      try {
        let optionsText = optionsMatch[1]
          .replace(/,\s*\}\s*$/, '}')
          .replace(/,\s*\]/g, ']');
        const options = JSON.parse(optionsText);
        if (options.method) result.method = options.method.toUpperCase();
        if (options.headers && typeof options.headers === 'object') result.headers = options.headers;
        if (options.body) result.body = options.body;
      } catch (error) {
        // ignore parse errors
      }
    }
    return result;
  }

  function parsePowerShellTemplate(text) {
    const result = {};
    const urlMatch = text.match(/-Uri\s+['"]([^'"]+)['"]/i);
    if (urlMatch) result.url = urlMatch[1];
    const methodMatch = text.match(/-Method\s+['"]?([A-Z]+)['"]?/i);
    if (methodMatch) result.method = methodMatch[1].toUpperCase();
    const headersMatch = text.match(/-Headers\s+@?\{([\s\S]*?)\}/i);
    if (headersMatch) {
      const headersText = headersMatch[1];
      const headers = {};
      headersText.split(';').forEach((pair) => {
        const idx = pair.indexOf('=');
        if (idx === -1) return;
        const key = pair.slice(0, idx).replace(/['\s]/g, '').trim();
        const value = pair.slice(idx + 1).replace(/['\s]/g, '').trim();
        if (key) headers[key] = value;
      });
      if (Object.keys(headers).length) result.headers = headers;
    }
    const bodyMatch = text.match(/-Body\s+['"]([\s\S]*?)['"]/i);
    if (bodyMatch) result.body = bodyMatch[1];
    return result;
  }

  function parseTemplate(raw) {
    if (!raw) return null;
    const text = raw.trim();
    if (text.startsWith('curl')) return parseCurlTemplate(text);
    if (text.includes('fetch(')) return parseFetchTemplate(text);
    if (/Invoke-WebRequest|Invoke-RestMethod/i.test(text)) return parsePowerShellTemplate(text);
    return null;
  }

  function applyImport() {
    if (!importInput) return;
    const raw = importInput.value;
    if (!raw || !raw.trim()) {
      setStatus(importStatus, 'Keine Vorlage eingegeben.', 'error');
      return;
    }
    const parsed = parseTemplate(raw);
    if (!parsed || (!parsed.url && !parsed.method && !parsed.headers && !parsed.body)) {
      setStatus(importStatus, 'Konnte die Vorlage nicht erkennen.', 'error');
      return;
    }
    // Neues Formular öffnen und mit der Vorlage befüllen
    openFormModal('create');
    if (parsed.url) urlInput.value = parsed.url;
    if (parsed.method) methodSelect.value = parsed.method.toUpperCase();
    if (parsed.headers) headersInput.value = stringifyHeaders(parsed.headers);
    if (parsed.body) bodyInput.value = parsed.body;
    setStatus(importStatus, 'Vorlage übernommen.', 'success');
    setStatus(formStatus, 'Vorlage importiert. Prüfen & speichern.', 'success');
    refreshPreview();
    closeImportModal();
  }

  function updateSortIndicators() {
    const headers = document.querySelectorAll('[data-sort-column]');
    headers.forEach((th) => {
      th.classList.remove('sort-asc', 'sort-desc');
      const col = th.getAttribute('data-sort-column');
      if (col === sortState.key) {
        th.classList.add(sortState.dir === 'desc' ? 'sort-desc' : 'sort-asc');
      }
    });
  }

  function initListInstance() {
    const container = document.getElementById('automationTable');
    if (!container) return;
    listInstance = null;
    const options = {
      listClass: 'list',
      valueNames: [
        'name',
        'type',
        'email',
        'url',
        'steps',
        { name: 'next', attr: 'data-sort' },
        { name: 'runUntil', attr: 'data-sort' },
        { name: 'last', attr: 'data-sort' },
        { name: 'status', attr: 'data-sort' },
        { name: 'runs', attr: 'data-sort' }
      ]
    };
    listInstance = new List(container, options);
    applyFilters();
    listInstance.sort(sortState.key, { order: sortState.dir === 'desc' ? 'desc' : 'asc' });
    updateSortIndicators();
  }

  function applyFilters() {
    if (!listInstance) return;
    const searchName = (filterName?.value || '').toLowerCase().trim();
    const searchNext = (filterNext?.value || '').toLowerCase().trim();
    const searchRunUntil = (filterRunUntil?.value || '').toLowerCase().trim();
    const searchLast = (filterLast?.value || '').toLowerCase().trim();
    const statusValue = (filterStatus?.value || '').toLowerCase().trim();
    const runsMin = filterRuns?.value ? Number(filterRuns.value) : null;

    listInstance.filter((item) => {
      const v = item.values();
      const matchName = !searchName || [
        v.name || '',
        v.type || '',
        v.email || '',
        v.url || '',
        v.steps || ''
      ].some((p) => String(p).toLowerCase().includes(searchName));

      const matchNext = !searchNext || String(v.next || '').toLowerCase().includes(searchNext);
      const matchRunUntil = !searchRunUntil || String(v.runUntil || '').toLowerCase().includes(searchRunUntil);
      const matchLast = !searchLast || `${v.last || ''}`.toLowerCase().includes(searchLast);
      const matchStatus = !statusValue || String(v.status || '').toLowerCase().includes(statusValue);
      const matchRuns = runsMin === null || Number(v.runs || 0) >= runsMin;

      return matchName && matchNext && matchRunUntil && matchLast && matchStatus && matchRuns;
    });
    updateSortIndicators();
  }

  function getSelectedRequest() {
    if (!state.selectedId) return null;
    return state.requests.find((item) => item.id === state.selectedId) || null;
  }

  function openFormModal(mode = 'create', request = null, options = {}) {
    const skipReset = !!options.skipReset;
    if (!skipReset) {
      resetForm();
    }
    if (mode === 'edit' && request) {
      fillForm(request);
      formModeLabel.textContent = 'Automation bearbeiten';
      modalTitle.textContent = 'Automation bearbeiten';
    } else {
      formModeLabel.textContent = 'Neue Automation';
      modalTitle.textContent = 'Neue Automation';
    }
    formModal.hidden = false;
    setTimeout(() => {
      nameInput.focus();
    }, 10);
    refreshPreview();
  }

  function closeFormModal() {
    formModal.hidden = true;
  }

  function openImportModal() {
    if (!importModal) return;
    if (importStatus) setStatus(importStatus, '');
    importModal.hidden = false;
    setTimeout(() => {
      importInput?.focus();
    }, 10);
  }

  function closeImportModal() {
    if (!importModal) return;
    importModal.hidden = true;
  }

  function applyTypeVisibility() {
    const type = typeSelect ? typeSelect.value : 'request';
    document.querySelectorAll('[data-section="http"]').forEach((el) => {
      el.style.display = type === 'request' ? 'grid' : 'none';
    });
    document.querySelectorAll('[data-section="email"]').forEach((el) => {
      el.style.display = type === 'email' ? 'grid' : 'none';
    });
    document.querySelectorAll('[data-section="flow"]').forEach((el) => {
      el.style.display = type === 'flow' ? 'grid' : 'none';
    });
    // required toggles
    urlInput.required = type === 'request';
    methodSelect.required = type === 'request';
    emailToInput && (emailToInput.required = type === 'email');
    emailSubjectInput && (emailSubjectInput.required = type === 'email');
    emailBodyInput && (emailBodyInput.required = type === 'email');
    flowStep1Url && (flowStep1Url.required = type === 'flow');
  }

  function handleTableClick(event) {
    const button = event.target.closest('[data-action]');
    if (button) {
      const { action, id } = button.dataset;
      if (!id) return;
      switch (action) {
        case 'edit':
          selectRequest(id);
          openFormModal('edit', state.requests.find((item) => item.id === id));
          break;
        case 'run':
          runAutomation(id);
          break;
        case 'toggle':
          toggleActive(id);
          break;
        case 'delete':
          deleteAutomation(id);
          break;
        default:
          break;
      }
      return;
    }

    const row = event.target.closest('tr[data-id]');
    if (row && row.dataset.id) {
      selectRequest(row.dataset.id);
    }
  }

  function handleSseMessage(payload) {
    if (!active) return;
    if (!payload || !payload.type) return;
    const { type, request_id: requestId } = payload;
    switch (payload.type) {
      case 'automation-run':
        loadRequests();
        if (requestId && state.selectedId && String(requestId) === String(state.selectedId)) {
          loadRuns(state.selectedId);
        }
        break;
      case 'automation-upsert':
        loadRequests();
        break;
      default:
        break;
    }
  }

  function startSse() {
    if (!active) return;
    if (sse || typeof EventSource === 'undefined') return;
    try {
      sse = new EventSource(`${API_URL}/events`, { withCredentials: true });
    } catch (error) {
      console.warn('Konnte SSE nicht starten:', error);
      sse = null;
      return;
    }
    sse.addEventListener('message', (event) => {
      if (!event || !event.data) return;
      try {
        const payload = JSON.parse(event.data);
        handleSseMessage(payload);
      } catch (error) {
        // ignore parse errors
      }
    });
    sse.addEventListener('error', () => {
      if (sse) {
        sse.close();
        sse = null;
      }
      setTimeout(startSse, 5000);
    });
  }

  function ensureRelativeTimer() {
    if (!relativeTimer) {
      updateRelativeTimes();
      relativeTimer = setInterval(updateRelativeTimes, 60000);
    }
  }

  function cleanup(options = {}) {
    const { keepActive = false } = options;
    if (!keepActive) {
      active = false;
    }
    if (relativeTimer) {
      clearInterval(relativeTimer);
      relativeTimer = null;
    }
    if (sse) {
      sse.close();
      sse = null;
    }
  }

  function init() {
    if (initialized) return;
    applyPresetDisabling();
    resetForm();
    ensureRelativeTimer();

    form.addEventListener('submit', handleSubmit);
    intervalPreset.addEventListener('change', applyPresetDisabling);
    resetFormBtn.addEventListener('click', () => {
      resetForm();
      nameInput.focus();
    });
    newAutomationBtn.addEventListener('click', () => openFormModal('create'));
    refreshBtn.addEventListener('click', loadRequests);
    applyImportBtn?.addEventListener('click', applyImport);
    refreshPreviewBtn?.addEventListener('click', refreshPreview);
    [urlInput, headersInput, bodyInput].forEach((el) => el?.addEventListener('input', refreshPreview));
    [emailToInput, emailSubjectInput, emailBodyInput].forEach((el) => el?.addEventListener('input', refreshPreview));
    [flowStep1Url, flowStep1Headers, flowStep1Body, flowStep1Method,
      flowStep2Url, flowStep2Headers, flowStep2Body, flowStep2Method].forEach((el) => {
      el?.addEventListener('input', refreshPreview);
      el?.addEventListener('change', refreshPreview);
    });
    if (addExclusionBtn) {
      addExclusionBtn.addEventListener('click', addExclusion);
    }
    typeSelect?.addEventListener('change', () => {
      applyTypeVisibility();
      refreshPreview();
    });
    [filterName, filterNext, filterRunUntil, filterLast, filterStatus].forEach((el) => {
      el?.addEventListener('input', applyFilters);
      el?.addEventListener('change', applyFilters);
    });
    filterRuns?.addEventListener('input', applyFilters);
    filterRuns?.addEventListener('change', applyFilters);
    document.querySelectorAll('[data-sort-column]').forEach((th) => {
      th.addEventListener('click', () => {
        const col = th.getAttribute('data-sort-column');
        if (!col) return;
        if (sortState.key === col) {
          sortState.dir = sortState.dir === 'asc' ? 'desc' : 'asc';
        } else {
          sortState.key = col;
          sortState.dir = 'asc';
        }
        if (listInstance) {
          listInstance.sort(col, { order: sortState.dir });
        }
        updateSortIndicators();
      });
    });
    applyTypeVisibility();
    openImportBtn?.addEventListener('click', openImportModal);
    importCloseBtn?.addEventListener('click', closeImportModal);
    importModalBackdrop?.addEventListener('click', closeImportModal);
    requestTableBody.addEventListener('click', handleTableClick);
    modalCloseBtn.addEventListener('click', closeFormModal);
    modalBackdrop.addEventListener('click', closeFormModal);
    document.addEventListener('keydown', (event) => {
      if (event.key === 'Escape') {
        if (!formModal.hidden) {
          closeFormModal();
        }
        if (importModal && !importModal.hidden) {
          closeImportModal();
        }
      }
    });
    document.addEventListener('visibilitychange', () => {
      if (document.hidden) {
        cleanup({ keepActive: true });
      } else {
        if (active) {
          ensureRelativeTimer();
          startSse();
        }
      }
    });
    window.addEventListener('beforeunload', cleanup);
    window.addEventListener('pagehide', cleanup);
    window.addEventListener('unload', cleanup);
    initialized = true;
  }

  function activate() {
    init();
    active = true;
    ensureRelativeTimer();
    startSse();
    loadRequests();
  }

  window.AutomationPage = {
    activate,
    deactivate: cleanup
  };

  const automationSection = document.querySelector('[data-view="automation"]');
  if (automationSection && automationSection.classList.contains('app-view--active')) {
    activate();
  }
})();
