/**
 * AutomationsDashboardPage
 *
 * System-level automation page with two tabs:
 * - Dashboard: Metrics + workflow runs table (backend-paginated)
 * - Workflows: Central management of all RBAC-accessible workflows across instances
 */

import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { FaSync, FaPlay, FaCog, FaChartBar, FaDownload, FaCheck, FaBan, FaPen, FaEye, FaTimes, FaStream, FaStop } from 'react-icons/fa';
import { FormGeneratorTable, type ColumnConfig } from '../components/FormGenerator';
import { Tabs } from '../components/UiComponents/Tabs';
import { useToast } from '../contexts/ToastContext';
import { usePrompt } from '../hooks/usePrompt';
import { useApiRequest } from '../hooks/useApi';
import { formatUnixTimestamp } from '../utils/time';
import { updateWorkflow, executeGraph, deleteSystemWorkflow, fetchWorkspaceRunDetail } from '../api/workflowApi';
import { fetchAttributes } from '../api/attributesApi';
import type { AttributeDefinition } from '../api/attributesApi';
import { resolveColumnTypes } from '../utils/columnTypeResolver';
import api from '../api';
import { useLanguage } from '../providers/language/LanguageContext';
import { useNavigation, type DynamicBlock } from '../hooks/useNavigation';
import styles from './admin/Admin.module.css';

// ---------------------------------------------------------------------------
// Shared types & helpers
// ---------------------------------------------------------------------------

interface WorkflowRunMetrics {
  totalRuns: number;
  runsByStatus: Record<string, number>;
  totalTokens: number;
  totalCredits: number;
  workflowCount: number;
  activeWorkflows: number;
}

interface WorkflowRun {
  id: string;
  workflowId: string;
  workflowLabel?: string;
  mandateId?: string;
  mandateLabel?: string;
  featureInstanceId?: string;
  instanceLabel?: string;
  ownerId?: string;
  status: string;
  costTokens?: number;
  costCredits?: number;
  sysCreatedAt?: number;
  sysModifiedAt?: number;
}

interface SystemWorkflow {
  id: string;
  mandateId: string;
  featureInstanceId: string;
  featureCode?: string;
  label: string;
  active: boolean;
  isRunning?: boolean;
  activeRunId?: string;
  stuckAtNodeLabel?: string;
  stuckAtNodeId?: string;
  createdAt?: number;
  sysCreatedAt?: number;
  lastStartedAt?: number;
  runCount?: number;
  mandateLabel?: string;
  instanceLabel?: string;
  canEdit?: boolean;
  canDelete?: boolean;
  canExecute?: boolean;
  invocations?: Array<{ id: string; enabled: boolean; kind: string }>;
  graph?: Record<string, any>;
}

const _FEATURES_WITH_EDITOR = new Set(['graphicalEditor', 'workspace']);

const _ROLE_PRIORITY: Record<string, number> = { admin: 3, user: 2, viewer: 1 };

function _bestEditorInstance(
  dynamicBlock: DynamicBlock | null,
  mandateId: string,
): { instanceId: string; featureCode: string } | null {
  if (!dynamicBlock) return null;
  const mandate = dynamicBlock.mandates.find((m) => m.id === mandateId);
  if (!mandate) return null;

  let best: { instanceId: string; featureCode: string; score: number } | null = null;
  for (const feat of mandate.features) {
    for (const inst of feat.instances) {
      const fc = inst.featureCode
        || feat.uiComponent.replace(/^feature\./, '');
      if (!_FEATURES_WITH_EDITOR.has(fc)) continue;
      let score = 0;
      if (inst.isAdmin) {
        score = 10;
      } else {
        for (const v of inst.views) {
          const key = v.objectKey || '';
          for (const [suffix, prio] of Object.entries(_ROLE_PRIORITY)) {
            if (key.endsWith(suffix) && prio > score) score = prio;
          }
        }
      }
      if (!best || score > best.score) {
        best = { instanceId: inst.id, featureCode: fc, score };
      }
    }
  }
  return best ? { instanceId: best.instanceId, featureCode: best.featureCode } : null;
}

function _formatTs(ts?: number): string {
  if (ts == null || ts <= 0) return '—';
  const sec = ts < 1e12 ? ts : ts / 1000;
  const { time } = formatUnixTimestamp(sec, undefined, {
    day: '2-digit',
    month: '2-digit',
    year: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
  });
  return time;
}

const _STATUS_COLORS: Record<string, string> = {
  completed: 'var(--success-color, #28a745)',
  failed: 'var(--danger-color, #dc3545)',
  running: 'var(--primary-color, #007bff)',
  paused: 'var(--warning-color, #ffc107)',
  stopped: 'var(--warning-color, #ffc107)',
  cancelled: 'var(--text-secondary, #666)',
};

// ---------------------------------------------------------------------------
// MetricCard
// ---------------------------------------------------------------------------

interface MetricCardProps {
  icon: React.ReactNode;
  label: string;
  value: string | number;
  color?: string;
}

const MetricCard: React.FC<MetricCardProps> = ({ icon, label, value, color }) => (
  <div
    style={{
      background: 'var(--bg-primary, #fff)',
      border: '1px solid var(--border-color, #e0e0e0)',
      borderRadius: 8,
      padding: '16px 20px',
      display: 'flex',
      alignItems: 'center',
      gap: 14,
      minWidth: 180,
      flex: '1 1 180px',
    }}
  >
    <div style={{ fontSize: 22, color: color || 'var(--primary-color, #007bff)', display: 'flex', alignItems: 'center' }}>
      {icon}
    </div>
    <div>
      <div style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #666)', marginBottom: 2 }}>{label}</div>
      <div style={{ fontSize: '1.3rem', fontWeight: 700 }}>{value}</div>
    </div>
  </div>
);

// ===========================================================================
// Live Run Tracing Modal (SSE-based, can be opened/closed freely)
// ===========================================================================

interface _TracingStep {
  id: string;
  nodeId: string;
  nodeType: string;
  status: string;
  startedAt?: number;
  completedAt?: number;
  durationMs?: number;
  error?: string;
  tokensUsed?: number;
  inputSnapshot?: Record<string, any>;
  output?: Record<string, any>;
  retryCount?: number;
}

const _STATUS_ICONS: Record<string, string> = {
  pending: '○', running: '◉', completed: '✓', failed: '✗', stopped: '■', skipped: '—',
};

function _formatStepTs(ts: number | string | null | undefined): string {
  if (!ts) return '';
  const d = typeof ts === 'number' ? new Date(ts * 1000) : new Date(ts);
  if (isNaN(d.getTime())) return '';
  return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
}

function _truncateJson(obj: unknown, maxLen = 300): string {
  if (!obj || (typeof obj === 'object' && Object.keys(obj as object).length === 0)) return '';
  try {
    const s = JSON.stringify(obj, null, 2);
    return s.length > maxLen ? s.slice(0, maxLen) + '\n...' : s;
  } catch {
    return String(obj);
  }
}

const _CollapsibleSection: React.FC<{ label: string; content: string }> = ({ label, content }) => {
  const [open, setOpen] = useState(false);
  if (!content) return null;
  return (
    <div style={{ marginTop: 4 }}>
      <button
        onClick={(e) => { e.stopPropagation(); setOpen(!open); }}
        style={{
          background: 'none', border: 'none', cursor: 'pointer', padding: 0,
          color: 'var(--text-link, #0969da)', fontSize: 11, textDecoration: 'underline',
        }}
      >
        {open ? '▾' : '▸'} {label}
      </button>
      {open && (
        <pre style={{
          margin: '4px 0 0', padding: 6, borderRadius: 4,
          background: 'var(--bg-secondary, #f6f8fa)', fontSize: 11,
          whiteSpace: 'pre-wrap', wordBreak: 'break-all', maxHeight: 200, overflowY: 'auto',
        }}>
          {content}
        </pre>
      )}
    </div>
  );
};

interface _RunTracingModalProps {
  run: WorkflowRun;
  onClose: () => void;
}

const _RunTracingModal: React.FC<_RunTracingModalProps> = ({ run, onClose }) => {
  const { t } = useLanguage();
  const [steps, setSteps] = useState<_TracingStep[]>([]);
  const [loading, setLoading] = useState(false);
  const [sseConnected, setSseConnected] = useState(false);
  const eventSourceRef = useRef<EventSource | null>(null);
  const scrollRef = useRef<HTMLDivElement>(null);

  const _loadSteps = useCallback(async () => {
    setLoading(true);
    try {
      const resp = await api.get(`/api/system/workflow-runs/${run.id}/steps`);
      setSteps(resp.data?.steps || []);
    } catch (e) {
      console.error('[RunTracing] Failed to load steps:', e);
    } finally {
      setLoading(false);
    }
  }, [run.id]);

  const isRunning = run.status === 'running' || run.status === 'paused';

  useEffect(() => {
    _loadSteps();

    if (!isRunning) return;

    const baseUrl = api.defaults.baseURL || '';
    const url = `${baseUrl}/api/system/workflow-runs/${run.id}/stream`;
    const es = new EventSource(url, { withCredentials: true });
    eventSourceRef.current = es;

    es.onopen = () => setSseConnected(true);
    es.onmessage = (event) => {
      try {
        const payload = JSON.parse(event.data);
        if (payload.type === 'keepalive') return;
        if (payload.type === 'run_complete' || payload.type === 'run_failed') {
          _loadSteps();
          es.close();
          setSseConnected(false);
          return;
        }
        if (payload.status === 'running') {
          setSteps((prev) => {
            const exists = prev.some((s) => s.id === payload.id);
            if (exists) return prev.map((s) => s.id === payload.id ? { ...s, ...payload } : s);
            return [...prev, payload as _TracingStep];
          });
        } else {
          setSteps((prev) => prev.map((s) => s.id === payload.id ? { ...s, ...payload } : s));
        }
      } catch { /* ignore parse errors */ }
    };
    es.onerror = () => {
      setSseConnected(false);
      es.close();
    };

    return () => {
      es.close();
      eventSourceRef.current = null;
      setSseConnected(false);
    };
  }, [run.id, run.status]); // eslint-disable-line react-hooks/exhaustive-deps

  // Polling fallback: reload steps periodically while run is active
  useEffect(() => {
    if (!isRunning) return;
    const interval = setInterval(() => { _loadSteps(); }, 5000);
    return () => clearInterval(interval);
  }, [isRunning, _loadSteps]);

  useEffect(() => {
    if (scrollRef.current) {
      scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
    }
  }, [steps]);

  return (
    <div className={styles.modalOverlay}>
      <div
        className={styles.modal}
        style={{ maxWidth: 800, height: '80vh' }}
      >
        <div className={styles.modalHeader}>
          <div>
            <h3 className={styles.modalTitle}>
              {t('Run-Tracing')}: {run.workflowLabel || run.workflowId}
            </h3>
            <div style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #666)', marginTop: 2 }}>
              <span style={{ color: _STATUS_COLORS[run.status] || 'inherit', fontWeight: 600 }}>
                {run.status}
              </span>
              {sseConnected && (
                <span style={{ marginLeft: 8, color: 'var(--success-color, #28a745)' }}>● {t('Live')}</span>
              )}
            </div>
          </div>
          <button className={styles.modalClose} onClick={onClose} title={t('Schliessen')}>
            <FaTimes />
          </button>
        </div>
        <div className={styles.modalContent} ref={scrollRef} style={{ overflowY: 'auto', flex: 1 }}>
          {loading && steps.length === 0 && (
            <div style={{ color: 'var(--text-secondary, #888)', fontSize: 13 }}>{t('Wird geladen…')}</div>
          )}
          {!loading && steps.length === 0 && (
            <div style={{ color: 'var(--text-secondary, #888)', fontSize: 13 }}>{t('Noch keine Schritte aufgezeichnet')}</div>
          )}
          {steps.map((step) => {
            const startStr = _formatStepTs(step.startedAt);
            const endStr = _formatStepTs(step.completedAt);
            const inputStr = _truncateJson(step.inputSnapshot);
            const outputStr = _truncateJson(step.output);
            const isLoop = step.inputSnapshot?._loopIndex != null;

            return (
              <div
                key={step.id}
                style={{
                  padding: '8px 12px', marginBottom: 6, borderRadius: 6,
                  border: `1px solid ${_STATUS_COLORS[step.status] || '#ddd'}`,
                  background: 'var(--bg-primary, #fff)', fontSize: 13,
                  marginLeft: isLoop ? 16 : 0,
                }}
              >
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                  <span>
                    <span style={{ color: _STATUS_COLORS[step.status] || '#999', marginRight: 6 }}>
                      {_STATUS_ICONS[step.status] || '?'}
                    </span>
                    <strong>{step.nodeType}</strong>
                    <span style={{ color: '#888', marginLeft: 6 }}>({step.nodeId})</span>
                    {isLoop && (
                      <span style={{ color: '#666', marginLeft: 6, fontSize: 11 }}>
                        [iter {step.inputSnapshot!._loopIndex}]
                      </span>
                    )}
                  </span>
                  <span style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
                    {(step.retryCount ?? 0) > 0 && (
                      <span style={{ color: '#f0ad4e', fontSize: 11 }}>
                        {step.retryCount}x {t('Wiederholung')}
                      </span>
                    )}
                    {step.durationMs != null && (
                      <span style={{ color: '#888', fontSize: 12 }}>{step.durationMs}ms</span>
                    )}
                  </span>
                </div>
                {(startStr || endStr) && (
                  <div style={{ color: '#888', fontSize: 11, marginTop: 2 }}>
                    {startStr && <span>{startStr}</span>}
                    {startStr && endStr && <span> → </span>}
                    {endStr && <span>{endStr}</span>}
                  </div>
                )}
                {step.error && (
                  <div style={{ color: '#dc3545', fontSize: 12, marginTop: 4 }}>{step.error}</div>
                )}
                {(step.tokensUsed ?? 0) > 0 && (
                  <div style={{ color: '#888', fontSize: 11, marginTop: 2 }}>
                    {step.tokensUsed} {t('Tokens')}
                  </div>
                )}
                <_CollapsibleSection label={t('Eingabe')} content={inputStr} />
                <_CollapsibleSection label={t('Ausgabe')} content={outputStr} />
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
};

// ===========================================================================
// DashboardTab — Metrics + Runs table with backend pagination
// ===========================================================================

interface _DashboardTabProps {
  workflowFilter?: string | null;
  onRunClick?: (runId: string) => void;
}

const _DashboardTab: React.FC<_DashboardTabProps> = ({ workflowFilter, onRunClick }) => {
  const { t } = useLanguage();
  const { request } = useApiRequest();
  const { showError } = useToast();

  const [metrics, setMetrics] = useState<WorkflowRunMetrics | null>(null);
  const [runs, setRuns] = useState<WorkflowRun[]>([]);
  const [loading, setLoading] = useState(true);
  const [paginationMeta, setPaginationMeta] = useState<any>(null);
  const [tracingRun, setTracingRun] = useState<WorkflowRun | null>(null);
  const lastPaginationParamsRef = useRef<any>(null);
  const [backendAttributes, setBackendAttributes] = useState<AttributeDefinition[]>([]);

  useEffect(() => {
    fetchAttributes(request, 'AutoRun')
      .then(setBackendAttributes)
      .catch((err) => { console.error('[automations] fetchAttributes AutoRun failed', err); });
  }, [request]);

  const _loadMetrics = useCallback(async () => {
    try {
      const resp = await api.get('/api/system/workflow-runs/metrics');
      setMetrics(resp.data);
    } catch (e: any) {
      const msg = e?.response?.data?.detail || e?.message || String(e);
      console.error('[automations] metrics load failed', e);
      showError(t('Metriken konnten nicht geladen werden: {msg}', { msg }));
    }
  }, [showError, t]);

  const _loadRuns = useCallback(async (paginationParams?: any) => {
    if (paginationParams !== undefined) {
      lastPaginationParamsRef.current = paginationParams;
    }
    const effectiveParams = paginationParams ?? lastPaginationParamsRef.current;
    setLoading(true);
    try {
      const defaultSort = [{ field: 'sysCreatedAt', direction: 'desc' }];
      const pag = {
        page: effectiveParams?.page || 1,
        pageSize: effectiveParams?.pageSize || 25,
        sort: effectiveParams?.sort || defaultSort,
        ...(effectiveParams?.search ? { search: effectiveParams.search } : {}),
        ...(effectiveParams?.filters ? { filters: effectiveParams.filters } : {}),
      };
      const params: Record<string, any> = { pagination: JSON.stringify(pag) };
      const resp = await api.get('/api/system/workflow-runs', { params });
      const data = resp.data;
      setRuns(data?.runs || []);
      const total = data?.total ?? 0;
      const pageSize = pag.pageSize;
      setPaginationMeta({
        currentPage: pag.page,
        pageSize,
        totalItems: total,
        totalPages: Math.ceil(total / pageSize),
      });
    } catch (e) {
      console.error('[automations] runs load failed', e);
      showError(t('Fehler beim Laden der Workflow-Runs'));
    } finally {
      setLoading(false);
    }
  }, [showError, t]);

  useEffect(() => {
    _loadMetrics();
  }, [_loadMetrics]);

  const hasRunningRuns = runs.some((r) => r.status === 'running' || r.status === 'paused');
  useEffect(() => {
    if (!hasRunningRuns) return;
    const interval = setInterval(() => {
      _loadRuns();
      _loadMetrics();
    }, 5000);
    return () => clearInterval(interval);
  }, [hasRunningRuns, _loadRuns, _loadMetrics]);

  const _downloadRunTracing = useCallback(async (run: WorkflowRun) => {
    if (!run.id) return;
    try {
      const resp = await api.get(`/api/system/workflow-runs/${run.id}/steps`);
      const steps = resp.data?.steps || [];
      const report = {
        runId: run.id,
        workflowId: run.workflowId,
        workflowLabel: run.workflowLabel,
        status: run.status,
        startedAt: _formatTs(run.sysCreatedAt),
        endedAt: _formatTs(run.sysModifiedAt),
        steps,
      };
      const blob = new Blob([JSON.stringify(report, null, 2)], { type: 'application/json' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `run-tracing-${run.id.slice(0, 8)}.json`;
      a.click();
      URL.revokeObjectURL(url);
    } catch (e) {
      console.error('[automations] download tracing failed', e);
      showError(t('Download fehlgeschlagen'));
    }
  }, [showError, t]);

  const _initialFilters = useMemo(() => {
    if (!workflowFilter) return undefined;
    return { workflowId: workflowFilter };
  }, [workflowFilter]);

  const _rawRunColumns: ColumnConfig[] = useMemo(() => [
    {
      key: 'workflowId',
      label: t('Workflow'),
      width: 200,
      sortable: true,
      filterable: true,
      displayField: 'workflowLabel',
    },
    {
      key: 'mandateId',
      label: t('Mandant'),
      width: 140,
      sortable: true,
      filterable: true,
      displayField: 'mandateLabel',
    },
    {
      key: 'featureInstanceId',
      label: t('Instanz'),
      width: 140,
      sortable: true,
      filterable: true,
      displayField: 'instanceLabel',
    },
    { key: 'status', width: 110, sortable: true, filterable: true },
    {
      key: 'startedAt',
      label: t('Gestartet'),
      width: 150,
      sortable: true,
      filterable: true,
      formatter: (v: number) => _formatTs(v),
    },
    {
      key: 'completedAt',
      label: t('Beendet'),
      width: 150,
      sortable: true,
      filterable: true,
      formatter: (v: number) => _formatTs(v),
    },
  ], [t]);

  const _runColumns = useMemo(
    () => resolveColumnTypes(_rawRunColumns, backendAttributes),
    [_rawRunColumns, backendAttributes],
  );

  const _hookData = useMemo(() => ({
    refetch: _loadRuns,
    pagination: paginationMeta,
  }), [_loadRuns, paginationMeta]);

  return (
    <>
      <div className={styles.pageHeader}>
        <div>
          <p className={styles.pageSubtitle}>{t('Workflow-Runs über alle Features und Mandanten')}</p>
        </div>
        <div className={styles.headerActions}>
          <button className={styles.secondaryButton} onClick={() => { _loadMetrics(); _loadRuns(); }} disabled={loading}>
            <FaSync className={loading ? 'spinning' : ''} /> {t('Aktualisieren')}
          </button>
        </div>
      </div>

      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, marginBottom: 24, flexShrink: 0 }}>
        <MetricCard icon={<FaCog />} label={t('Workflows')} value={metrics?.workflowCount ?? t('—')} />
        <MetricCard icon={<FaPlay />} label={t('Aktive Workflows')} value={metrics?.activeWorkflows ?? t('—')} color="var(--success-color, #28a745)" />
        <MetricCard icon={<FaChartBar />} label={t('Runs gesamt')} value={metrics?.totalRuns ?? t('—')} />
      </div>

      {metrics?.runsByStatus && Object.keys(metrics.runsByStatus).length > 0 && (
        <div style={{ marginBottom: 24, flexShrink: 0 }}>
          <h3 style={{ fontSize: '0.95rem', fontWeight: 600, marginBottom: 8 }}>{t('Läufe nach Status')}</h3>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {Object.entries(metrics.runsByStatus).map(([status, count]) => (
              <span
                key={status}
                style={{
                  padding: '4px 12px',
                  borderRadius: 12,
                  fontSize: '0.85rem',
                  fontWeight: 600,
                  background: 'var(--bg-secondary, #f5f5f5)',
                  color: _STATUS_COLORS[status] || 'inherit',
                }}
              >
                {status}: {count}
              </span>
            ))}
          </div>
        </div>
      )}

      {metrics && (metrics.totalTokens > 0 || metrics.totalCredits > 0) && (
        <div style={{ marginBottom: 24, display: 'flex', gap: 24, flexShrink: 0 }}>
          {metrics.totalTokens > 0 && (
            <div>
              <span style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #666)' }}>{t('Tokens gesamt:')} </span>
              <strong>{metrics.totalTokens.toLocaleString('de-DE')}</strong>
            </div>
          )}
          {metrics.totalCredits > 0 && (
            <div>
              <span style={{ fontSize: '0.8rem', color: 'var(--text-secondary, #666)' }}>{t('Credits gesamt:')} </span>
              <strong>{metrics.totalCredits.toLocaleString('de-DE', { minimumFractionDigits: 2 })}</strong>
            </div>
          )}
        </div>
      )}

      <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 8, flexShrink: 0 }}>
        <h3 style={{ fontSize: '0.95rem', fontWeight: 600, margin: 0 }}>{t('Letzte Runs')}</h3>
      </div>
      <div className={styles.tableContainer}>
        <FormGeneratorTable<WorkflowRun>
          data={runs}
          columns={_runColumns}
          loading={loading}
          pagination={true}
          pageSize={25}
          searchable={true}
          filterable={true}
          sortable={true}
          selectable={true}
          initialSort={[{ key: 'startedAt', direction: 'desc' }]}
          initialFilters={_initialFilters}
          apiEndpoint="/api/system/workflow-runs"
          onRowClick={(row) => onRunClick?.(row.id)}
          customActions={[
            {
              id: 'tracing',
              icon: <FaStream />,
              title: t('Run-Tracing anzeigen'),
              onClick: (row) => setTracingRun(row),
            },
            {
              id: 'download',
              icon: <FaDownload />,
              title: t('Tracing-Protokoll herunterladen'),
              onClick: (row) => _downloadRunTracing(row),
            },
          ]}
          hookData={_hookData}
          emptyMessage={t('Noch keine Workflow-Runs vorhanden.')}
        />
      </div>
      {tracingRun && (
        <_RunTracingModal run={tracingRun} onClose={() => setTracingRun(null)} />
      )}
    </>
  );
};

// ===========================================================================
// WorkflowsTab — Central workflow management across all instances
// ===========================================================================

interface _WorkflowsTabProps {
  onWorkflowClick?: (workflowId: string) => void;
}

const _WorkflowsTab: React.FC<_WorkflowsTabProps> = ({ onWorkflowClick }) => {
  const { t } = useLanguage();
  const navigate = useNavigate();
  const { request } = useApiRequest();
  const { showSuccess, showError } = useToast();
  const { prompt: promptInput, PromptDialog } = usePrompt();
  const { dynamicBlock } = useNavigation();

  const [workflows, setWorkflows] = useState<SystemWorkflow[]>([]);
  const [loading, setLoading] = useState(true);
  const [executingId, setExecutingId] = useState<string | null>(null);
  const [togglingId, setTogglingId] = useState<string | null>(null);
  const [activeFilter, setActiveFilter] = useState<'all' | 'active' | 'inactive'>('all');
  const [paginationMeta, setPaginationMeta] = useState<any>(null);
  const lastPaginationParamsRef = useRef<any>(null);
  const [backendAttributes, setBackendAttributes] = useState<AttributeDefinition[]>([]);

  useEffect(() => {
    fetchAttributes(request, 'Automation2WorkflowView')
      .then(setBackendAttributes)
      .catch((err) => { console.error('[automations] fetchAttributes Automation2WorkflowView failed', err); });
  }, [request]);

  const _load = useCallback(async (paginationParams?: any) => {
    if (paginationParams !== undefined) {
      lastPaginationParamsRef.current = paginationParams;
    }
    const effectiveParams = paginationParams ?? lastPaginationParamsRef.current;
    setLoading(true);
    try {
      const params: Record<string, any> = {};
      if (activeFilter === 'active') params.active = true;
      if (activeFilter === 'inactive') params.active = false;

      const defaultSort = [{ field: 'sysCreatedAt', direction: 'desc' }];
      const pag = {
        page: effectiveParams?.page || 1,
        pageSize: effectiveParams?.pageSize || 25,
        sort: effectiveParams?.sort || defaultSort,
        ...(effectiveParams?.search ? { search: effectiveParams.search } : {}),
        ...(effectiveParams?.filters ? { filters: effectiveParams.filters } : {}),
      };
      params.pagination = JSON.stringify(pag);

      const resp = await api.get('/api/system/workflow-runs/workflows', { params });
      const data = resp.data;
      setWorkflows(data?.items || []);
      setPaginationMeta(data?.pagination || null);
    } catch (e) {
      console.error('[automations] load system workflows failed', e);
      showError(t('Fehler beim Laden der Workflows'));
    } finally {
      setLoading(false);
    }
  }, [activeFilter, showError, t]);

  useEffect(() => {
    _load();
  }, [_load]);

  const hasRunningWorkflows = workflows.some((w) => w.isRunning);
  useEffect(() => {
    if (!hasRunningWorkflows) return;
    const interval = setInterval(() => { _load(); }, 5000);
    return () => clearInterval(interval);
  }, [hasRunningWorkflows, _load]);

  const _handleEdit = useCallback((row: SystemWorkflow) => {
    if (!row.mandateId) return;
    const fc = row.featureCode || '';
    if (_FEATURES_WITH_EDITOR.has(fc)) {
      navigate(`/mandates/${row.mandateId}/${fc}/${row.featureInstanceId}/editor?workflowId=${row.id}`);
      return;
    }
    const editor = _bestEditorInstance(dynamicBlock, row.mandateId);
    if (!editor) {
      showError(t('Kein Editor verfügbar für diesen Mandanten'));
      return;
    }
    navigate(`/mandates/${row.mandateId}/${editor.featureCode}/${editor.instanceId}/editor?workflowId=${row.id}`);
  }, [navigate, showError, t, dynamicBlock]);

  const _handleDelete = useCallback(async (workflowId: string): Promise<boolean> => {
    try {
      await deleteSystemWorkflow(request, workflowId);
      showSuccess(t('Workflow gelöscht'));
      await _load();
      return true;
    } catch (e: any) {
      showError(t('Fehler: {msg}', { msg: e?.message || t('Löschen fehlgeschlagen') }));
      return false;
    }
  }, [request, showSuccess, showError, _load, t]);

  const _handleToggleActive = useCallback(async (row: SystemWorkflow) => {
    if (!row.featureInstanceId) return;
    const next = !(row.active !== false);
    setTogglingId(row.id);
    try {
      await updateWorkflow(request, row.featureInstanceId, row.id, { active: next });
      showSuccess(next ? t('Workflow aktiviert') : t('Workflow deaktiviert'));
      await _load();
    } catch (e: any) {
      showError(t('Fehler: {msg}', { msg: e?.message || t('Status-Update fehlgeschlagen') }));
    } finally {
      setTogglingId(null);
    }
  }, [request, showSuccess, showError, _load, t]);

  const _handleRename = useCallback(async (row: SystemWorkflow) => {
    if (!row.featureInstanceId) return;
    const newLabel = await promptInput(t('Neuer Name:'), {
      title: t('Workflow umbenennen'),
      defaultValue: row.label,
      placeholder: t('Workflow-Name'),
    });
    if (!newLabel || newLabel.trim() === row.label) return;
    try {
      await updateWorkflow(request, row.featureInstanceId, row.id, { label: newLabel.trim() });
      showSuccess(t('Workflow umbenannt'));
      await _load();
    } catch (e: any) {
      showError(t('Fehler: {msg}', { msg: e?.message || t('Umbenennen fehlgeschlagen') }));
    }
  }, [request, promptInput, showSuccess, showError, _load, t]);

  const _handleExecute = useCallback(async (row: SystemWorkflow) => {
    if (!row.featureInstanceId) return;
    setExecutingId(row.id);
    // Track outcome of the fire-and-forget executeGraph promise so the
    // intermediate "Workflow gestartet" toast is only shown when the call has
    // not already failed/finished within the 1s observation window. Without
    // this we always toasted "gestartet" — even when the run had already
    // errored — producing contradictory toasts and hiding real failures.
    let observedFailure = false;
    let observedSuccess = false;
    try {
      const invs = row.invocations || [];
      const primary =
        invs.find((i) => i.enabled && i.kind === 'manual') ||
        invs.find((i) => i.enabled && (i.kind === 'form' || i.kind === 'api'));
      const emptyGraph = { nodes: [], connections: [] };
      const exec = executeGraph(request, row.featureInstanceId, emptyGraph as any, row.id, {
        ...(primary ? { entryPointId: primary.id } : {}),
      }).then((result) => {
        if (result?.success) {
          observedSuccess = true;
          showSuccess(result?.paused
            ? t('Workflow pausiert bei Human Task.')
            : t('Workflow abgeschlossen'));
        } else {
          observedFailure = true;
          showError(result?.error || t('Ausführung fehlgeschlagen'));
        }
        _load();
      }).catch((e: any) => {
        observedFailure = true;
        showError(t('Fehler: {msg}', { msg: e?.message || t('Ausführung fehlgeschlagen') }));
        _load();
      });
      await Promise.race([
        exec,
        new Promise((r) => setTimeout(r, 1000)),
      ]);
      await _load();
      if (!observedFailure && !observedSuccess) {
        showSuccess(t('Workflow gestartet'));
      }
    } finally {
      setExecutingId(null);
    }
  }, [request, showSuccess, showError, _load, t]);

  const [stoppingId, setStoppingId] = useState<string | null>(null);

  const _handleStop = useCallback(async (row: SystemWorkflow) => {
    if (!row.activeRunId) return;
    setStoppingId(row.id);
    try {
      await api.post(`/api/system/workflow-runs/${row.activeRunId}/stop`);
      showSuccess(t('Stop-Signal gesendet'));
      await _load();
    } catch (e: any) {
      showError(t('Fehler: {msg}', { msg: e?.message || t('Stoppen fehlgeschlagen') }));
    } finally {
      setStoppingId(null);
    }
  }, [showSuccess, showError, _load, t]);

  const _hasManualTrigger = useCallback((row: SystemWorkflow): boolean => {
    const invs = row.invocations || [];
    return invs.some((i) => i.enabled && (i.kind === 'manual' || i.kind === 'api'));
  }, []);

  const _rawColumns: ColumnConfig[] = useMemo(() => [
    { key: 'label', label: t('Workflow'), width: 200, sortable: true, filterable: true },
    {
      key: 'mandateId',
      label: t('Mandant'),
      width: 140,
      sortable: true,
      filterable: true,
      displayField: 'mandateLabel',
    },
    {
      key: 'featureInstanceId',
      label: t('Instanz'),
      width: 140,
      sortable: true,
      filterable: true,
      displayField: 'instanceLabel',
    },
    {
      key: 'active',
      label: t('Aktiv'),
      width: 80,
      sortable: true,
      filterable: true,
    },
    {
      key: 'isRunning',
      label: t('Läuft'),
      width: 80,
      sortable: true,
      filterable: true,
    },
    {
      key: 'sysCreatedAt',
      label: t('Erstellt'),
      width: 140,
      sortable: true,
      filterable: true,
      formatter: (v: number) => _formatTs(v),
    },
    {
      key: 'lastStartedAt',
      label: t('Zuletzt gestartet'),
      width: 160,
      sortable: true,
      filterable: true,
      formatter: (v: number) => _formatTs(v),
    },
    {
      key: 'runCount',
      label: t('Läufe'),
      width: 80,
      sortable: true,
      filterable: true,
      formatter: (v: number) => (v != null ? String(v) : '0'),
    },
  ], [t]);

  const _columns = useMemo(
    () => resolveColumnTypes(_rawColumns, backendAttributes),
    [_rawColumns, backendAttributes],
  );

  const _hookData = useMemo(() => ({
    refetch: _load,
    handleDelete: (id: string) => _handleDelete(id),
    pagination: paginationMeta,
  }), [_load, _handleDelete, paginationMeta]);

  return (
    <>
      <div className={styles.pageHeader}>
        <div>
          <p className={styles.pageSubtitle}>
            {t('Alle Workflows über alle Features und Mandanten')}
          </p>
        </div>
        <div className={styles.headerActions} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <div style={{ display: 'flex', gap: 4 }}>
            {(['all', 'active', 'inactive'] as const).map((f) => (
              <button
                key={f}
                className={activeFilter === f ? styles.primaryButton : styles.secondaryButton}
                onClick={() => setActiveFilter(f)}
                disabled={loading}
              >
                {f === 'all' ? t('Alle') : f === 'active' ? t('Aktiv') : t('Inaktiv')}
              </button>
            ))}
          </div>
          <button className={styles.secondaryButton} onClick={() => _load()} disabled={loading}>
            <FaSync className={loading ? 'spinning' : ''} /> {t('Aktualisieren')}
          </button>
        </div>
      </div>

      <div className={styles.tableContainer}>
        <FormGeneratorTable<SystemWorkflow>
          data={workflows}
          columns={_columns}
          loading={loading}
          pagination={true}
          pageSize={25}
          searchable={true}
          filterable={true}
          sortable={true}
          selectable={true}
          initialSort={[{ key: 'sysCreatedAt', direction: 'desc' }]}
          apiEndpoint="/api/system/workflow-runs/workflows"
          actionButtons={[
            {
              type: 'edit',
              title: t('bearbeiten'),
              onAction: _handleEdit,
              visible: (row: SystemWorkflow) => row.canEdit === true,
            },
            {
              type: 'delete',
              title: t('löschen'),
              visible: (row: SystemWorkflow) => row.canDelete === true,
            },
          ]}
          customActions={[
            {
              id: 'view',
              icon: <FaEye />,
              title: t('anzeigen'),
              onClick: (row) => _handleEdit(row),
              visible: (row) => row.canEdit !== true,
            },
            {
              id: 'rename',
              icon: <FaPen />,
              title: t('umbenennen'),
              onClick: (row) => _handleRename(row),
              visible: (row) => row.canEdit === true,
            },
            {
              id: 'activate',
              icon: <FaCheck />,
              title: t('aktivieren'),
              onClick: (row) => _handleToggleActive(row),
              loading: (row) => togglingId === row.id,
              visible: (row) => row.canEdit === true && row.active === false,
            },
            {
              id: 'deactivate',
              icon: <FaBan />,
              title: t('deaktivieren'),
              onClick: (row) => _handleToggleActive(row),
              loading: (row) => togglingId === row.id,
              visible: (row) => row.canEdit === true && row.active !== false,
            },
            {
              id: 'execute',
              icon: <FaPlay />,
              title: t('ausführen'),
              onClick: (row) => _handleExecute(row),
              loading: (row) => executingId === row.id,
              visible: (row) => row.canExecute === true && _hasManualTrigger(row) && !row.isRunning,
            },
            {
              id: 'stop',
              icon: <FaStop />,
              title: t('stoppen'),
              onClick: (row) => _handleStop(row),
              loading: (row) => stoppingId === row.id,
              visible: (row) => row.isRunning === true && !!row.activeRunId,
            },
          ]}
          onDelete={(row) => _handleDelete(row.id)}
          onRowClick={(row) => onWorkflowClick?.(row.id)}
          hookData={_hookData}
          emptyMessage={t('Keine Workflows gefunden.')}
        />
      </div>
      <PromptDialog />
    </>
  );
};

// ===========================================================================
// Workspace Tab (run detail only — no table)
// ===========================================================================

const _FILE_REF_KEYS = new Set(['fileId', 'documentId', 'fileIds', 'documents']);

function _isPlainObject(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null && !Array.isArray(v);
}

function _stripFileRefKeys(value: unknown): unknown {
  if (_isPlainObject(value)) {
    const out: Record<string, unknown> = {};
    for (const [k, v] of Object.entries(value)) {
      if (_FILE_REF_KEYS.has(k)) continue;
      const stripped = _stripFileRefKeys(v);
      if (stripped !== undefined) out[k] = stripped;
    }
    return Object.keys(out).length > 0 ? out : undefined;
  }
  if (Array.isArray(value)) {
    const out = value.map((v) => _stripFileRefKeys(v)).filter((v) => v !== undefined);
    return out.length > 0 ? out : undefined;
  }
  return value;
}

function _formatScalar(v: unknown): string {
  if (v === null || v === undefined) return '—';
  if (typeof v === 'string') return v;
  if (typeof v === 'number' || typeof v === 'boolean') return String(v);
  return JSON.stringify(v);
}

const _DataBlock: React.FC<{ data: unknown; emptyHint?: string }> = ({ data, emptyHint }) => {
  if (data === undefined || data === null) {
    return emptyHint ? <p style={{ fontSize: '0.8rem', color: 'var(--text-secondary)', margin: 0 }}>{emptyHint}</p> : null;
  }

  if (_isPlainObject(data)) {
    const entries = Object.entries(data);
    if (entries.length === 0) {
      return emptyHint ? <p style={{ fontSize: '0.8rem', color: 'var(--text-secondary)', margin: 0 }}>{emptyHint}</p> : null;
    }
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: '0.25rem' }}>
        {entries.map(([k, v]) => {
          const isComplex = _isPlainObject(v) || Array.isArray(v);
          if (isComplex) {
            return (
              <details key={k} style={{ fontSize: '0.8rem' }}>
                <summary style={{ cursor: 'pointer', color: 'var(--text-secondary)' }}>
                  <code style={{ fontWeight: 500 }}>{k}</code>
                </summary>
                <pre style={{ fontSize: '0.75rem', maxHeight: 240, overflow: 'auto', margin: '0.25rem 0 0 1rem', background: 'var(--bg-secondary)', padding: '0.5rem', borderRadius: 4 }}>
                  {JSON.stringify(v, null, 2)}
                </pre>
              </details>
            );
          }
          return (
            <div key={k} style={{ display: 'flex', gap: '0.5rem', fontSize: '0.8rem', alignItems: 'baseline' }}>
              <code style={{ color: 'var(--text-secondary)', minWidth: 140, flexShrink: 0 }}>{k}</code>
              <span style={{ wordBreak: 'break-word' }}>{_formatScalar(v)}</span>
            </div>
          );
        })}
      </div>
    );
  }

  return (
    <pre style={{ fontSize: '0.75rem', maxHeight: 240, overflow: 'auto', margin: 0, background: 'var(--bg-secondary)', padding: '0.5rem', borderRadius: 4 }}>
      {JSON.stringify(data, null, 2)}
    </pre>
  );
};

const _FileLinkList: React.FC<{ files: Array<{ id: string; fileName?: string }> }> = ({ files }) => {
  if (!files.length) return null;
  const baseUrl = api.defaults.baseURL || '';
  return (
    <div style={{ display: 'flex', gap: '0.4rem', flexWrap: 'wrap', marginTop: '0.25rem' }}>
      {files.map((f) => (
        <a
          key={f.id}
          href={`${baseUrl}/api/files/${f.id}/download`}
          download
          style={{ padding: '0.3rem 0.6rem', border: '1px solid var(--border-color)', borderRadius: 4, textDecoration: 'none', color: 'var(--primary-color)', fontSize: '0.78rem' }}
        >
          <FaDownload style={{ marginRight: 4 }} />
          {f.fileName || f.id}
        </a>
      ))}
    </div>
  );
};

const _INTERNAL_EXTRACT_FILENAME_SUBSTR = 'extracted_content_transient';

/** Hide persisted transient extract JSON from user-facing Workspace file lists */
function _isHiddenWorkflowArtifactFile(f: { fileName?: string }): boolean {
  return (f.fileName ?? '').toLowerCase().includes(_INTERNAL_EXTRACT_FILENAME_SUBSTR);
}

const _ProducedFilesSection: React.FC<{
  steps: Array<{ outputFiles?: Array<{ id: string; fileName?: string }> }>;
  unassignedFiles?: Array<{ id: string; fileName?: string }>;
}> = ({ steps, unassignedFiles }) => {
  const { t } = useLanguage();
  const seen = new Set<string>();
  const allFiles: Array<{ id: string; fileName?: string }> = [];
  for (const step of steps) {
    for (const f of step.outputFiles ?? []) {
      if (_isHiddenWorkflowArtifactFile(f)) continue;
      if (!seen.has(f.id)) { seen.add(f.id); allFiles.push(f); }
    }
  }
  for (const f of unassignedFiles ?? []) {
    if (_isHiddenWorkflowArtifactFile(f)) continue;
    if (!seen.has(f.id)) { seen.add(f.id); allFiles.push(f); }
  }
  if (!allFiles.length) return null;
  const baseUrl = api.defaults.baseURL || '';
  return (
    <div style={{ marginBottom: '1rem', padding: '0.75rem', background: 'var(--surface-secondary, rgba(0,123,255,0.04))', border: '1px solid var(--border-color)', borderRadius: 8 }}>
      <div style={{ fontSize: '0.82rem', fontWeight: 600, marginBottom: '0.5rem', display: 'flex', alignItems: 'center', gap: 6 }}>
        <FaDownload style={{ opacity: 0.6 }} />
        {t('Ergebnisse')} ({allFiles.length})
      </div>
      <div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
        {allFiles.map(f => (
          <a
            key={f.id}
            href={`${baseUrl}/api/files/${f.id}/download`}
            download
            style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '0.35rem 0.7rem', background: 'var(--surface-primary, #fff)', border: '1px solid var(--border-color)', borderRadius: 6, textDecoration: 'none', color: 'var(--primary-color)', fontSize: '0.82rem', fontWeight: 500 }}
          >
            <FaDownload style={{ fontSize: '0.7rem' }} />
            {f.fileName || f.id}
          </a>
        ))}
      </div>
    </div>
  );
};

function _downloadJson(data: unknown, fileName: string) {
  const json = JSON.stringify(data, null, 2);
  const blob = new Blob([json], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = fileName;
  a.click();
  URL.revokeObjectURL(url);
}

interface _WorkspaceTabProps {
  runId: string | null;
  onBack: () => void;
}

const _TERMINAL_STATUSES = new Set(['completed', 'failed', 'cancelled', 'error', 'stopped']);
const _POLL_INTERVAL_MS = 3000;

const _WorkspaceTab: React.FC<_WorkspaceTabProps> = ({ runId, onBack }) => {
  const { t } = useLanguage();
  const { request } = useApiRequest();
  const [runDetail, setRunDetail] = useState<Awaited<ReturnType<typeof fetchWorkspaceRunDetail>> | null>(null);
  const [detailLoading, setDetailLoading] = useState(false);

  const _loadDetail = useCallback(async (id: string) => {
    setDetailLoading(true);
    try {
      const detail = await fetchWorkspaceRunDetail(request, id);
      setRunDetail(detail);
    } catch (e) {
      console.error('Workspace run detail failed', e);
    } finally {
      setDetailLoading(false);
    }
  }, [request]);

  useEffect(() => {
    if (runId) _loadDetail(runId);
    else setRunDetail(null);
  }, [runId, _loadDetail]);

  useEffect(() => {
    if (!runId || !runDetail) return;
    const status = runDetail.run?.status;
    if (status && _TERMINAL_STATUSES.has(status)) return;
    const timer = setInterval(() => {
      fetchWorkspaceRunDetail(request, runId)
        .then(detail => setRunDetail(detail))
        .catch(() => {});
    }, _POLL_INTERVAL_MS);
    return () => clearInterval(timer);
  }, [runId, runDetail, request]);

  if (!runId) {
    return (
      <div style={{ padding: '1rem', flex: 1, color: 'var(--text-secondary)' }}>
        <p>{t('Wähle einen Run im Dashboard aus, um die Details anzuzeigen.')}</p>
      </div>
    );
  }

  if (detailLoading || !runDetail) {
    return <div style={{ padding: '1rem', flex: 1 }}><p>{t('Laden…')}</p></div>;
  }

  const { run, steps, workflow, unassignedFiles } = runDetail;

  return (
    <div style={{ padding: '1rem', flex: 1, minHeight: 0, overflowY: 'auto' }}>
      <button type="button" className={styles.secondaryButton} onClick={onBack} style={{ marginBottom: '1rem' }}>
        ← {t('Zurück zum Dashboard')}
      </button>
      <h3 style={{ margin: '0.5rem 0' }}>{run.workflowLabel || run.workflowId}</h3>
      <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap', fontSize: '0.85rem', color: 'var(--text-secondary)', marginBottom: '1rem' }}>
        <span><strong>{t('Status')}:</strong> {run.status}</span>
        {run.startedAt && <span><strong>{t('Start')}:</strong> {_formatTs(run.startedAt)}</span>}
        {run.completedAt && <span><strong>{t('Ende')}:</strong> {_formatTs(run.completedAt)}</span>}
        {workflow?.targetFeatureInstanceId && <span><strong>{t('Ziel-Instanz')}:</strong> {run.targetInstanceLabel || workflow.targetFeatureInstanceId}</span>}
        {(run.costTokens ?? 0) > 0 && <span><strong>Tokens:</strong> {run.costTokens}</span>}
      </div>
      {run.error && (
        <div style={{ padding: '0.5rem', background: 'rgba(220,53,69,0.1)', borderRadius: 6, marginBottom: '1rem', color: 'var(--danger-color)' }}>
          {run.error}
        </div>
      )}
      <_ProducedFilesSection steps={steps} unassignedFiles={unassignedFiles} />
      <h4 style={{ margin: '1rem 0 0.5rem' }}>{t('Schritte')}</h4>
      {steps.length === 0 ? (
        <p style={{ color: 'var(--text-secondary)' }}>{t('Keine Schritte protokolliert.')}</p>
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
          {steps.map((step) => {
            const inputData = _stripFileRefKeys(step.inputSnapshot ?? {});
            const outputData = _stripFileRefKeys(step.output ?? {});
            const inputFiles = (step.inputFiles ?? []).filter((f) => !_isHiddenWorkflowArtifactFile(f));
            const outputFiles = (step.outputFiles ?? []).filter((f) => !_isHiddenWorkflowArtifactFile(f));
            const hasInput = inputData !== undefined || inputFiles.length > 0;
            const hasOutput = outputData !== undefined || outputFiles.length > 0;
            return (
              <details key={step.id} style={{ border: '1px solid var(--border-color)', borderRadius: 6, padding: '0.5rem' }}>
                <summary style={{ cursor: 'pointer', fontWeight: 500 }}>
                  <span style={{ marginRight: '0.5rem', fontSize: '0.75rem', padding: '2px 6px', borderRadius: 4, background: step.status === 'completed' ? 'rgba(40,167,69,0.15)' : step.status === 'failed' ? 'rgba(220,53,69,0.15)' : 'rgba(0,123,255,0.15)', color: step.status === 'completed' ? 'var(--success-color)' : step.status === 'failed' ? 'var(--danger-color)' : 'var(--primary-color)' }}>
                    {step.status}
                  </span>
                  {step.nodeType} ({step.nodeId})
                  {step.durationMs != null && <span style={{ marginLeft: '0.5rem', fontSize: '0.75rem', color: 'var(--text-secondary)' }}>{step.durationMs}ms</span>}
                  {(step.tokensUsed ?? 0) > 0 && <span style={{ marginLeft: '0.5rem', fontSize: '0.75rem', color: 'var(--text-secondary)' }}>{step.tokensUsed} tokens</span>}
                </summary>
                <div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', marginTop: '0.5rem' }}>
                  {hasInput && (
                    <section>
                      <div style={{ fontSize: '0.78rem', fontWeight: 600, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: '0.25rem', display: 'flex', alignItems: 'center', gap: 6 }}>
                        {t('Input')}
                        {inputData !== undefined && inputData !== null && (
                          <button type="button" onClick={() => _downloadJson(inputData, `${step.nodeId}-input.json`)} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0, color: 'var(--primary-color)', fontSize: '0.7rem', display: 'inline-flex', alignItems: 'center', gap: 3 }} title={t('Als JSON herunterladen')}>
                            <FaDownload /> JSON
                          </button>
                        )}
                      </div>
                      <_DataBlock data={inputData} />
                      <_FileLinkList files={inputFiles} />
                    </section>
                  )}
                  {hasOutput && (
                    <section>
                      <div style={{ fontSize: '0.78rem', fontWeight: 600, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: '0.25rem', display: 'flex', alignItems: 'center', gap: 6 }}>
                        {t('Output')}
                        {outputData !== undefined && outputData !== null && (
                          <button type="button" onClick={() => _downloadJson(outputData, `${step.nodeId}-output.json`)} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0, color: 'var(--primary-color)', fontSize: '0.7rem', display: 'inline-flex', alignItems: 'center', gap: 3 }} title={t('Als JSON herunterladen')}>
                            <FaDownload /> JSON
                          </button>
                        )}
                      </div>
                      <_DataBlock data={outputData} />
                      <_FileLinkList files={outputFiles} />
                    </section>
                  )}
                  {step.error && (
                    <section>
                      <div style={{ fontSize: '0.78rem', fontWeight: 600, color: 'var(--danger-color)', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: '0.25rem' }}>
                        {t('Fehler')}
                      </div>
                      <p style={{ color: 'var(--danger-color)', margin: 0, fontSize: '0.85rem' }}>{step.error}</p>
                    </section>
                  )}
                  <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap', fontSize: '0.75rem', color: 'var(--text-secondary)', borderTop: '1px solid var(--border-color)', paddingTop: '0.4rem' }}>
                    {step.startedAt && <span>{t('Start')}: {_formatTs(step.startedAt)}</span>}
                    {step.completedAt && <span>{t('Ende')}: {_formatTs(step.completedAt)}</span>}
                    {(step.retryCount ?? 0) > 0 && <span>{t('Wiederholungen')}: {step.retryCount}</span>}
                  </div>
                </div>
              </details>
            );
          })}
        </div>
      )}
      {(() => {
        const visibleUnassigned = (unassignedFiles ?? []).filter((f) => !_isHiddenWorkflowArtifactFile(f));
        if (!visibleUnassigned.length) return null;
        return (
        <>
          <h4 style={{ margin: '1rem 0 0.5rem' }}>{t('Sonstige Dokumente')}</h4>
          <_FileLinkList files={visibleUnassigned} />
        </>
        );
      })()}
    </div>
  );
};

// ===========================================================================
// Main page with Tabs (Workflows → Dashboard → Workspace)
// ===========================================================================

export const AutomationsDashboardPage: React.FC = () => {
  const { t } = useLanguage();
  const [searchParams] = useSearchParams();

  const initialTab = searchParams.get('tab') || 'workflows';
  const initialRunId = searchParams.get('runId') || null;

  const [activeTab, setActiveTab] = useState<string>(initialRunId ? 'workspace' : initialTab);
  const [selectedRunId, setSelectedRunId] = useState<string | null>(initialRunId);
  const [workflowFilter, setWorkflowFilter] = useState<string | null>(null);

  const _handleWorkflowClick = useCallback((workflowId: string) => {
    setWorkflowFilter(workflowId);
    setActiveTab('dashboard');
  }, []);

  useEffect(() => {
    if (workflowFilter) setWorkflowFilter(null);
  }, [workflowFilter]);

  const _handleRunClick = useCallback((runId: string) => {
    setSelectedRunId(runId);
    setActiveTab('workspace');
  }, []);

  const _handleBackFromWorkspace = useCallback(() => {
    setSelectedRunId(null);
    setActiveTab('dashboard');
  }, []);

  const tabs = useMemo(() => [
    {
      id: 'workflows',
      label: t('Workflows'),
      content: <_WorkflowsTab onWorkflowClick={_handleWorkflowClick} />,
    },
    {
      id: 'dashboard',
      label: t('Workflow-Durchläufe'),
      content: <_DashboardTab workflowFilter={workflowFilter} onRunClick={_handleRunClick} />,
    },
    {
      id: 'workspace',
      label: t('Durchlauf-Details'),
      content: <_WorkspaceTab runId={selectedRunId} onBack={_handleBackFromWorkspace} />,
    },
  ], [t, _handleWorkflowClick, workflowFilter, _handleRunClick, selectedRunId, _handleBackFromWorkspace]);

  return (
    <div className={`${styles.adminPage} ${styles.adminPageFill}`}>
      <h1 className={styles.pageTitle} style={{ flexShrink: 0 }}>{t('Automatisierung')}</h1>
      <Tabs tabs={tabs} activeTabId={activeTab} onTabChange={setActiveTab} />
    </div>
  );
};

export default AutomationsDashboardPage;
