const VENDOO_UI_BUILD = '1.41.2';

const state = {
  photos: [],
  platform: '',
  aiProvider: 'claude',
  aiModel: '',
  language: 'de',
  variants: 1,
  currentListing: null,
  allVariants: null,
  platforms: [],
  providers: [],
  templates: [],
  editingTemplateId: null,
  historyPage: 1,
  historyPageSize: 10,
  publishPage: 1,
  publishPageSize: 10,
  aiModelPhotos: [],
  aiModelGenerations: [],
  aiPromptScope: 'generator',
  aiPromptDrafts: {
    generator: { customPrompt: '', preserveLogos: true },
    detail: { customPrompt: '', preserveLogos: true },
  },
  fluxStudioCurrent: null,
  fluxStudioHistory: [],
  fluxHistoryPage: 1,
  fluxHistoryPageSize: 5,
  fluxHistoryPagination: { page: 1, total_pages: 1, total: 0 },
  fluxStudioBusy: false,
  fluxJobs: [],
  fluxJobSummary: null,
  fluxRuntime: null,
  fluxStudioWatchers: new Map(),
  fluxJobExpanded: new Set(),
  fluxJobManuallyToggled: new Set(),
  fluxJobCenterCollapsed: false,
  fluxJobArchiveMode: false,
  fluxJobPage: 1,
  fluxJobPageSize: 10,
  fluxJobPagination: { page: 1, total_pages: 1, total: 0 },
  fluxJobSelected: new Set(),
  mediaItems: [],
  mediaSelected: new Set(),
  mediaPage: 1,
  mediaPageSize: 24,
  mediaPagination: { page: 1, total_pages: 1, total: 0 },
  mediaSummary: null,
  mediaView: 'grid',
  mediaCurrent: null,
  imageFactoryProfiles: [],
  imageFactorySources: [],
  imageFactorySelectedSources: new Set(),
  imageFactorySelectedProfiles: new Set(),
  imageFactoryJobs: [],
  imageFactoryJobPage: 1,
  imageFactoryJobPageSize: 10,
  imageFactoryJobPagination: { page: 1, total_pages: 1, total: 0 },
  imageFactorySummary: null,
  imageFactoryExpanded: new Set(),
  qualityItems: [],
  qualityPage: 1,
  qualityPageSize: 10,
  qualityPagination: { page: 1, total_pages: 1, total: 0 },
  qualitySummary: null,
  qualityCurrent: null,
  qualityIssueFilter: 'all',
  qualityAiDraft: null,
};

const editors = {};

const htmlSyncState = { generatorLock: false, detailLock: false };
let detailEditorState = null;
const listingEditLock = { listingId: null, token: null, lock: null, timer: null };
const mobileUploadState = { session: null, pollTimer: null, target: null, imported: new Set() };


const QUILL_TOOLBAR = [
  [{ header: [1, 2, 3, false] }],
  ['bold', 'italic', 'underline', 'strike'],
  [{ color: [] }, { background: [] }],
  [{ list: 'ordered' }, { list: 'bullet' }],
  [{ align: [] }],
  ['blockquote', 'link'],
  ['clean'],
];

function initQuill(container, placeholder) {
  if (typeof Quill === 'undefined') return null;
  return new Quill(container, {
    theme: 'snow',
    placeholder: placeholder || '',
    modules: { toolbar: QUILL_TOOLBAR },
  });
}

const RICH_ALLOWED_TAGS = new Set([
  'P', 'BR', 'STRONG', 'B', 'EM', 'I', 'U', 'S', 'STRIKE', 'OL', 'UL', 'LI',
  'BLOCKQUOTE', 'A', 'H1', 'H2', 'H3', 'SPAN', 'DIV', 'SUB', 'SUP', 'CODE', 'PRE',
]);
const RICH_DANGEROUS_TAGS = new Set([
  'SCRIPT', 'STYLE', 'IFRAME', 'OBJECT', 'EMBED', 'FORM', 'INPUT', 'BUTTON', 'TEXTAREA',
  'SELECT', 'OPTION', 'SVG', 'MATH', 'VIDEO', 'AUDIO', 'LINK', 'META',
]);

function sanitizeStyleClient(style) {
  return String(style || '').split(';').map(part => part.trim()).filter(Boolean).map(part => {
    const index = part.indexOf(':');
    if (index < 1) return '';
    const name = part.slice(0, index).trim().toLowerCase();
    const value = part.slice(index + 1).trim();
    if (!['color', 'background-color', 'text-align'].includes(name)) return '';
    if (/url\s*\(|expression\s*\(|javascript:/i.test(value)) return '';
    if (name === 'text-align' && !/^(left|right|center|justify)$/i.test(value)) return '';
    if (['color', 'background-color'].includes(name) && !/^(#[0-9a-f]{3,8}|rgba?\([\d\s.,%]+\)|hsla?\([\d\s.,%]+\)|[a-z]{3,20})$/i.test(value)) return '';
    return `${name}:${value}`;
  }).filter(Boolean).join(';');
}

function sanitizeRichHtmlClient(input) {
  const doc = new DOMParser().parseFromString(`<body>${String(input || '')}</body>`, 'text/html');
  const elements = [...doc.body.querySelectorAll('*')].reverse();
  for (const element of elements) {
    if (RICH_DANGEROUS_TAGS.has(element.tagName)) {
      element.remove();
      continue;
    }
    if (!RICH_ALLOWED_TAGS.has(element.tagName)) {
      element.replaceWith(...element.childNodes);
      continue;
    }
    for (const attr of [...element.attributes]) {
      const name = attr.name.toLowerCase();
      const value = attr.value;
      let keep = false;
      if (element.tagName === 'A' && name === 'href' && /^(https?:|mailto:|tel:|#|\/)/i.test(value.trim())) keep = true;
      if (element.tagName === 'A' && name === 'target' && ['_blank', '_self'].includes(value)) keep = true;
      if (element.tagName === 'A' && name === 'rel') { element.setAttribute('rel', 'noopener noreferrer'); keep = true; }
      if (name === 'style') {
        const safeStyle = sanitizeStyleClient(value);
        if (safeStyle) { element.setAttribute('style', safeStyle); keep = true; }
      }
      if (name === 'class' && String(value).split(/\s+/).every(cls => /^ql-[a-z0-9-]+$/i.test(cls))) keep = true;
      if (!keep) element.removeAttribute(attr.name);
    }
    if (element.tagName === 'A' && element.getAttribute('target') === '_blank') element.setAttribute('rel', 'noopener noreferrer');
  }
  return doc.body.innerHTML.trim();
}

function plainTextToRichHtml(text) {
  const value = String(text || '').trim();
  if (!value) return '';
  return value.split(/\n{2,}/).map(paragraph => `<p>${escapeHtml(paragraph).replace(/\n/g, '<br>')}</p>`).join('');
}

function richHtmlToText(html) {
  const container = document.createElement('div');
  container.innerHTML = sanitizeRichHtmlClient(html);
  container.querySelectorAll('br').forEach(br => br.replaceWith('\n'));
  container.querySelectorAll('p,div,h1,h2,h3,li,blockquote,pre').forEach(node => node.append('\n'));
  return (container.textContent || '').replace(/\n{3,}/g, '\n\n').trim();
}

function getEditorHtml(editor) {
  if (!editor) return '';
  const html = sanitizeRichHtmlClient(editor.root.innerHTML);
  return /^(<p><br><\/p>|<p><\/p>)$/i.test(html) ? '' : html;
}

function setEditorHtml(editor, html) {
  if (!editor) return;
  const safe = sanitizeRichHtmlClient(html);
  editor.setText('');
  if (safe) editor.clipboard.dangerouslyPasteHTML(safe);
}

function setSandboxPreview(frame, html) {
  if (!frame) return;
  frame.srcdoc = `<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>html{background:#fff}body{margin:0;padding:18px;font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#25231f;font-size:14px;line-height:1.55;overflow-wrap:anywhere}table{max-width:100%}a{color:#d93616}</style></head><body>${String(html || '')}</body></html>`;
}

function setHtmlSyncStatus(id, text, tone = '') {
  const el = document.getElementById(id);
  if (!el) return;
  el.textContent = text;
  el.dataset.tone = tone;
}

const clientDiagnostics = { incidents: [], lastSystemReport: null, online: true, healthTimer: null };

function recordClientIncident(type, message, details = {}) {
  const incident = {
    at: new Date().toISOString(),
    type: String(type || 'client'),
    message: String(message || 'Unbekannter Fehler').slice(0, 500),
    view: document.querySelector('.tab-content.active')?.id || 'unknown',
    build: VENDOO_UI_BUILD,
    ...details,
  };
  clientDiagnostics.incidents.unshift(incident);
  clientDiagnostics.incidents = clientDiagnostics.incidents.slice(0, 30);
  try { localStorage.setItem('vendoo_client_incidents', JSON.stringify(clientDiagnostics.incidents)); } catch {}
  return incident;
}

function setConnectionState(online, detail = '') {
  clientDiagnostics.online = !!online;
  const banner = document.getElementById('connection-banner');
  const title = document.getElementById('connection-banner-title');
  const description = document.getElementById('connection-banner-detail');
  if (!banner) return;
  banner.classList.toggle('hidden', online);
  banner.classList.toggle('is-online', online);
  if (title) title.textContent = online ? 'Verbindung wiederhergestellt' : 'Vendoo ist nicht erreichbar';
  if (description) description.textContent = detail || (online ? 'Die Anwendung ist wieder verbunden.' : 'Verbindung wird automatisch erneut geprüft.');
}

async function checkVendooHealth({ quiet = true } = {}) {
  const controller = new AbortController();
  const timer = window.setTimeout(() => controller.abort(), 3500);
  try {
    const response = await fetch(`/healthz?t=${Date.now()}`, { cache: 'no-store', signal: controller.signal });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    const result = await response.json();
    const wasOffline = !clientDiagnostics.online;
    setConnectionState(true, `Server erreichbar · Version ${result.version || VENDOO_UI_BUILD}`);
    if (wasOffline && !quiet) toast('Verbindung zu Vendoo wiederhergestellt.');
    return true;
  } catch (error) {
    setConnectionState(false, error?.name === 'AbortError' ? 'Zeitüberschreitung beim Serverzugriff.' : 'Serververbindung unterbrochen. Automatische Wiederholung läuft.');
    return false;
  } finally {
    window.clearTimeout(timer);
  }
}

function setupGlobalResilience() {
  try {
    const stored = JSON.parse(localStorage.getItem('vendoo_client_incidents') || '[]');
    if (Array.isArray(stored)) clientDiagnostics.incidents = stored.slice(0, 30);
  } catch {}
  window.addEventListener('error', event => {
    recordClientIncident('javascript', event.message, { file: event.filename || '', line: event.lineno || 0, column: event.colno || 0 });
  });
  window.addEventListener('unhandledrejection', event => {
    const reason = event.reason;
    recordClientIncident('promise', reason?.message || String(reason || 'Unbehandelte Promise-Ablehnung'));
  });
  window.addEventListener('offline', () => setConnectionState(false, 'Der Browser meldet keine Netzwerkverbindung.'));
  window.addEventListener('online', () => checkVendooHealth({ quiet: false }));
  document.getElementById('connection-retry-btn')?.addEventListener('click', () => checkVendooHealth({ quiet: false }));
  checkVendooHealth();
  clientDiagnostics.healthTimer = window.setInterval(() => checkVendooHealth(), 30000);
  window.addEventListener('beforeunload', () => window.clearInterval(clientDiagnostics.healthTimer), { once: true });
}

function applyResponsiveContract() {
  const main = document.querySelector('.main-wrapper');
  if (!main) return;
  const width = Math.round(main.getBoundingClientRect().width);
  const bucket = width < 560 ? 'phone' : width < 820 ? 'narrow' : width < 1120 ? 'compact' : width < 1480 ? 'standard' : 'wide';
  main.dataset.layoutWidth = bucket;
  main.style.setProperty('--workspace-width', `${width}px`);
  document.querySelectorAll('.tab-content').forEach(section => {
    section.dataset.layoutWidth = bucket;
  });
}

function setupResponsiveContracts() {
  applyResponsiveContract();
  const main = document.querySelector('.main-wrapper');
  if (main && 'ResizeObserver' in window) {
    const observer = new ResizeObserver(() => window.requestAnimationFrame(applyResponsiveContract));
    observer.observe(main);
    window.addEventListener('beforeunload', () => observer.disconnect(), { once: true });
  } else {
    window.addEventListener('resize', debounce(applyResponsiveContract, 100));
  }
}

async function init() {
  document.documentElement.dataset.vendooBuild = VENDOO_UI_BUILD;
  console.info(`[Vendoo] UI-Build ${VENDOO_UI_BUILD}`);
  setupGlobalResilience();
  setupResponsiveContracts();
  // Auth check — redirect to login if not authenticated or setup needed
  try {
    const meCheck = await fetch('/api/me', { credentials: 'same-origin' });
    if (meCheck.status === 401) { window.location.href = '/login.html'; return; }
    const meData = await meCheck.json();
    if (meData.setupMode) { window.location.href = '/login.html'; return; }
  } catch {}

  const [platforms, settings, providers, templates] = await Promise.all([
    api('GET', '/api/platforms'),
    api('GET', '/api/settings'),
    api('GET', '/api/providers'),
    api('GET', '/api/templates'),
  ]);

  state.platforms = platforms;
  state.providers = providers;
  state.templates = templates;
  state.platform = settings.default_platform || 'vinted';
  state.aiProvider = settings.default_ai || 'claude';
  state.language = settings.default_language || 'de';

  renderPlatformButtons();
  renderProviderButtons();
  populateSettingsForm(settings, platforms, providers);
  populateTemplateSelects(platforms);
  renderTemplateList();
  updateTemplateDropdown();
  document.getElementById('language-select').value = state.language;

  editors.result = initQuill('#result-description-editor', 'Beschreibung wird hier angezeigt...');
  editors.tplNotes = initQuill('#tpl-notes-editor', 'Verkäufernotizen...');

  setupGeneratorHtmlSync();
  setupHtmlViewSwitches();
  setupMobileUpload();

  addQuillTooltips();

  setupTabs();
  setupAnalyticsDashboard();
  setupTodayFlow();
  setupUpload();
  setupImageTools();
  setupGenerate();
  setupGeneratorStudio();
  setupAiPromptModal();
  setupFluxStudio();
  setupMediaLibrary();
  setupImageFactory();
  setupQualityCenter();
  setupExtensions();
  setupCopy();
  setupHistory();
  setupPublish();
  setupTrash();
  setupTemplates();
  setupFeeCalculator();
  setupSettings();
  setupDarkMode();
  setupPhotoEditor();
  setupBackup();
  setupNotifications();
  setupUserMenu();
  setupAdmin();
  loadAnalyticsDashboard();
  updateTrashBadge();
}

// --- API ---
let _csrfToken = '';
function getCsrf() {
  if (_csrfToken) return _csrfToken;
  const m = document.cookie.match(/csrf_token=([^;]+)/);
  return m ? m[1] : '';
}
async function api(method, url, body, options = {}) {
  const verb = String(method || 'GET').toUpperCase();
  const attempts = verb === 'GET' && options.retry !== false ? 2 : 1;
  const timeoutMs = Number(options.timeoutMs || (verb === 'GET' ? 20000 : 90000));
  const requestId = globalThis.crypto?.randomUUID?.() || `vd-${Date.now()}-${Math.random().toString(16).slice(2)}`;
  let lastError;

  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    const controller = new AbortController();
    const timer = window.setTimeout(() => controller.abort(), timeoutMs);
    const opts = { method: verb, headers: { 'X-Vendoo-Request-ID': requestId, ...(options.headers || {}) }, credentials: 'same-origin', signal: controller.signal };
    if (body !== undefined && body !== null) { opts.headers['Content-Type'] = 'application/json'; opts.body = JSON.stringify(body); }
    if (!['GET', 'HEAD'].includes(verb)) opts.headers['X-CSRF-Token'] = getCsrf();
    try {
      const res = await fetch(url, opts);
      if (res.status === 401) { window.location.href = '/login.html'; return; }
      const contentType = res.headers.get('content-type') || '';
      const payload = contentType.includes('application/json')
        ? await res.json().catch(() => ({}))
        : await res.text().catch(() => '');
      if (!res.ok) {
        const error = new Error(payload?.error || payload?.message || (typeof payload === 'string' && payload) || res.statusText || 'Fehler');
        error.status = res.status;
        error.requestId = requestId;
        if (attempt < attempts && res.status >= 500) { lastError = error; continue; }
        throw error;
      }
      if (!clientDiagnostics.online) setConnectionState(true, 'Serververbindung wiederhergestellt.');
      return payload;
    } catch (error) {
      lastError = error?.name === 'AbortError' ? new Error(`Zeitüberschreitung nach ${Math.round(timeoutMs / 1000)} Sekunden`) : error;
      lastError.requestId = requestId;
      const retryable = attempt < attempts && (error?.name === 'AbortError' || error instanceof TypeError || Number(error?.status || 0) >= 500);
      if (retryable) { await new Promise(resolve => window.setTimeout(resolve, 250)); continue; }
      recordClientIncident('api', lastError.message, { method: verb, url, requestId, status: lastError.status || 0 });
      if (error instanceof TypeError || error?.name === 'AbortError') setConnectionState(false, `${lastError.message} · Anfrage ${requestId.slice(0, 8)}`);
      throw lastError;
    } finally {
      window.clearTimeout(timer);
    }
  }
  throw lastError || new Error('Anfrage fehlgeschlagen');
}


function aiModelStatusClass(tone = 'neutral') {
  return `ai-model-status ${tone ? `is-${tone}` : ''}`.trim();
}

function setAiModelStatus(elementId, message, tone = 'neutral') {
  const element = document.getElementById(elementId);
  if (!element) return;
  element.textContent = message;
  element.className = aiModelStatusClass(tone);
}


function setAiModelJobProgress(elementId, job) {
  const element = document.getElementById(elementId);
  if (!element) return;
  const progress = Math.max(0, Math.min(100, Number(job?.progress || 0)));
  const message = job?.status === 'failed'
    ? (job.error_message || 'AI-Bildjob fehlgeschlagen')
    : (job?.progress_message || formatAiJobStatus(job));
  const tone = job?.status === 'failed' ? 'danger' : job?.status === 'completed' ? 'success' : 'working';
  element.className = `${aiModelStatusClass(tone)} ai-job-progress-card`;
  element.innerHTML = `
    <div class="ai-job-progress-head"><strong>${escapeHtml(message)}</strong><span>${progress}%</span></div>
    <div class="ai-job-progress-track"><div style="width:${progress}%"></div></div>
    <small>${job?.status === 'queued' ? 'Warteschlange' : job?.status === 'running' ? 'Lokale Verarbeitung läuft' : job?.status === 'completed' ? 'Abgeschlossen' : 'Aktion beendet'}</small>
  `;
}

function getGeneratorAiModelPayload() {
  return {
    photos: [...state.photos],
    platform: state.platform,
    title: document.getElementById('result-title')?.value || state.currentListing?.title || '',
    category: metaCategoryPicker?.getValue?.() || state.currentListing?.category || '',
    brand: document.getElementById('meta-brand')?.value || state.currentListing?.brand || '',
    color: document.getElementById('meta-color')?.value || state.currentListing?.color || '',
    size: document.getElementById('meta-size')?.value || state.currentListing?.size || '',
    condition: document.getElementById('meta-condition')?.value || state.currentListing?.condition || '',
    seller_notes: document.getElementById('generator-seller-notes')?.value || state.currentListing?.seller_notes || '',
    mode: document.getElementById('generator-ai-model-mode')?.value || 'flatlay',
    preset: document.getElementById('generator-ai-model-preset')?.value || 'mixed',
    variants: parseInt(document.getElementById('generator-ai-model-variants')?.value || '1', 10),
  };
}

function buildAiModelCardHtml(items, scope) {
  const prefix = scope === 'detail' ? 'detail' : 'generator';
  return `
    <div class="ai-model-grid-head">
      <strong>${items.length} sichere Bildvariante${items.length === 1 ? '' : 'n'}</strong>
      <button type="button" class="small-btn" data-ai-model-add-all="${scope}">Alle zu Fotos hinzufügen</button>
    </div>
    <div class="ai-model-grid-cards">
      ${items.map((photo, index) => `
        <article class="ai-model-shot-card">
          <img src="${escapeHtml(photoUrl(photo.image_path || photo.filename))}" alt="Produktbild-Variante ${index + 1}" loading="lazy">
          <div class="ai-model-shot-copy">
            <strong>Variante ${photo.variant_index || index + 1}</strong>
            <span>${escapeHtml((photo.mode || 'flatlay').replace('_', ' ').toUpperCase())} · ${escapeHtml((photo.preset || 'mixed').toUpperCase())}</span>
          </div>
          <button type="button" class="small-btn" data-ai-model-add-one="${escapeHtml(photo.image_path || photo.filename)}" data-ai-model-scope="${prefix}">Zu Fotos hinzufügen</button>
        </article>`).join('')}
    </div>`;
}


function renderAiModelGrid(scope, items = []) {
  const grid = document.getElementById(scope === 'detail' ? 'detail-ai-model-grid' : 'generator-ai-model-grid');
  if (!grid) return;
  if (!items.length) {
    grid.innerHTML = '';
    grid.classList.add('hidden');
    return;
  }
  grid.innerHTML = buildAiModelCardHtml(items, scope);
  grid.classList.remove('hidden');

  const addOne = filename => {
    if (scope === 'detail') {
      if (!detailEditorState) return;
      if (!detailEditorState.photos.includes(filename)) detailEditorState.photos.push(filename);
      detailEditorState.activePhotoIndex = detailEditorState.photos.length - 1;
      renderDetailPhotos();
    } else {
      if (!state.photos.includes(filename)) state.photos.push(filename);
      renderPhotoPreviews();
      updateGenerateButton();
    }
    toast('AI-Model-Foto hinzugefügt');
  };

  grid.querySelectorAll('[data-ai-model-add-one]').forEach(button => button.addEventListener('click', () => addOne(button.dataset.aiModelAddOne)));
  grid.querySelector('[data-ai-model-add-all]')?.addEventListener('click', () => {
    items.forEach(photo => addOne(photo.image_path || photo.filename));
    toast('Alle AI-Model-Fotos hinzugefügt');
  });
}

async function pollAiModelJob(jobId, onProgress) {
  const started = Date.now();
  while (Date.now() - started < 360000) {
    const job = await api('GET', `/api/ai-model-photos/jobs/${jobId}`);
    if (typeof onProgress === 'function') onProgress(job);
    if (job.status === 'completed' || job.status === 'failed') return job;
    await new Promise(resolve => setTimeout(resolve, 1800));
  }
  throw new Error('Zeitüberschreitung bei der AI-Bildgenerierung');
}

function formatAiJobStatus(job) {
  if (!job) return 'AI-Bildjob wird vorbereitet …';
  if (job.status === 'queued') return job.progress_message || 'AI-Bildjob steht in der Warteschlange …';
  if (job.status === 'running') return job.progress_message || `AI-Bilder werden erzeugt … ${job.progress || 1}%`;
  if (job.status === 'failed') return job.error_message || 'AI-Bildjob fehlgeschlagen';
  return job.progress_message || 'AI-Bildjob abgeschlossen';
}


function getAiPromptDraft(scope = 'generator') {
  if (!state.aiPromptDrafts[scope]) state.aiPromptDrafts[scope] = { customPrompt: '', preserveLogos: true };
  return state.aiPromptDrafts[scope];
}

function getAiPromptContext(scope = 'generator') {
  if (scope === 'detail') {
    return {
      mode: document.getElementById('detail-ai-model-mode')?.value || 'flatlay',
      preset: document.getElementById('detail-ai-model-preset')?.value || 'mixed',
      variants: document.getElementById('detail-ai-model-variants')?.value || '1',
    };
  }
  return {
    mode: document.getElementById('generator-ai-model-mode')?.value || 'flatlay',
    preset: document.getElementById('generator-ai-model-preset')?.value || 'mixed',
    variants: document.getElementById('generator-ai-model-variants')?.value || '1',
  };
}

function updateAiPromptSummary(scope = 'generator') {
  const ctx = getAiPromptContext(scope);
  const modeLabel = ctx.mode === 'ghost_mannequin' ? 'Ghost Mannequin' : 'Flatlay';
  const lookLabel = ctx.preset === 'mixed' ? 'Mix' : ctx.preset === 'studio' ? 'Studio' : ctx.preset === 'indoor' ? 'Indoor' : 'Outdoor';
  document.getElementById('ai-prompt-mode-chip').textContent = `Modus: ${modeLabel}`;
  document.getElementById('ai-prompt-look-chip').textContent = `Look: ${lookLabel}`;
  document.getElementById('ai-prompt-variants-chip').textContent = `Varianten: ${ctx.variants}`;
  const logoCheck = document.getElementById('ai-prompt-preserve-logos');
  if (logoCheck) {
    logoCheck.disabled = false;
  }
}

function closeAiPromptModal() {
  const modal = document.getElementById('ai-prompt-modal');
  modal?.classList.add('hidden');
  modal?.setAttribute('aria-hidden', 'true');
  document.body.classList.remove('ai-prompt-open');
}

function syncAiPromptDraftFromModal() {
  const scope = state.aiPromptScope || 'generator';
  const draft = getAiPromptDraft(scope);
  draft.customPrompt = document.getElementById('ai-prompt-textarea')?.value || '';
  draft.preserveLogos = document.getElementById('ai-prompt-preserve-logos')?.checked !== false;
}

function openAiPromptModal(scope = 'generator') {
  const modal = document.getElementById('ai-prompt-modal');
  if (!modal) return;
  state.aiPromptScope = scope;
  const draft = getAiPromptDraft(scope);
  document.getElementById('ai-prompt-scope-copy').textContent = scope === 'detail' ? 'Zusatzanweisung für das aktuelle Listing' : 'Zusatzanweisung für den Generator';
  document.getElementById('ai-prompt-textarea').value = draft.customPrompt || '';
  document.getElementById('ai-prompt-preserve-logos').checked = draft.preserveLogos !== false;
  updateAiPromptSummary(scope);
  modal.classList.remove('hidden');
  modal.setAttribute('aria-hidden', 'false');
  document.body.classList.add('ai-prompt-open');
  setTimeout(() => document.getElementById('ai-prompt-textarea')?.focus(), 30);
}

function setupAiPromptModal() {
  const modal = document.getElementById('ai-prompt-modal');
  if (!modal) return;
  modal.querySelectorAll('[data-ai-prompt-close]').forEach(btn => btn.addEventListener('click', closeAiPromptModal));
  document.getElementById('ai-prompt-textarea')?.addEventListener('input', syncAiPromptDraftFromModal);
  document.getElementById('ai-prompt-preserve-logos')?.addEventListener('change', syncAiPromptDraftFromModal);
  document.getElementById('ai-prompt-reset-btn')?.addEventListener('click', () => {
    const draft = getAiPromptDraft(state.aiPromptScope || 'generator');
    draft.customPrompt = '';
    draft.preserveLogos = true;
    document.getElementById('ai-prompt-textarea').value = '';
    document.getElementById('ai-prompt-preserve-logos').checked = true;
  });
  document.getElementById('ai-prompt-run-btn')?.addEventListener('click', async () => {
    syncAiPromptDraftFromModal();
    const scope = state.aiPromptScope || 'generator';
    closeAiPromptModal();
    if (scope === 'detail') await runDetailAiModelPhotos();
    else await runGeneratorAiModelPhotos();
  });
  document.addEventListener('keydown', event => {
    if (event.key === 'Escape' && !modal.classList.contains('hidden')) closeAiPromptModal();
  });
}



const FLUX_PROMPT_TEMPLATES = {
  product: 'Professionelle Produktfotografie, klares Hauptmotiv, neutraler Studiohintergrund, weiches Licht, realistische Materialien, hochwertige kommerzielle Bildsprache',
  social: 'Modernes Social-Media-Motiv, klarer visueller Fokus, starke aber ausgewogene Komposition, hochwertige Beleuchtung, sofort verständlich',
  editorial: 'Premium Editorial-Fotografie, anspruchsvolle Magazinkomposition, kontrolliertes Licht, elegante Farbwelt, hochwertige realistische Details',
  background: 'Minimalistischer heller Studiohintergrund, weiche natürliche Schatten, dezente Tiefe, sauber, hochwertig, ohne Text und ohne Personen',
  lifestyle: 'Authentische hochwertige Lifestyle-Szene, natürliches Tageslicht, glaubwürdige Umgebung, harmonische Komposition, fotorealistisch',
  banner: 'Breites hochwertiges Werbemotiv, klarer freier Bereich für spätere Texte, kontrollierte Beleuchtung, moderne kommerzielle Gestaltung',
};

const FLUX_PROFILE_PRESETS = {
  fast: { format: '768x768', steps: '4' },
  balanced: { format: '1024x1024', steps: '4' },
  quality: { format: '1024x1024', steps: '6' },
  custom: null,
};

const FLUX_TERMINAL_JOB_STATUSES = new Set(['completed', 'completed_with_errors', 'failed', 'cancelled']);
const FLUX_ACTIVE_JOB_STATUSES = new Set(['queued', 'running', 'cancelling']);

function analyzeFluxPrompt(value = '') {
  const text = String(value || '').trim();
  if (!text) return { level: 'empty', copy: 'Beschreibe zuerst Motiv, Umgebung, Licht und Perspektive.' };
  const words = text.split(/\s+/).filter(Boolean).length;
  const signals = [
    /licht|lighting|beleuchtung/i,
    /hintergrund|background|umgebung|scene|studio/i,
    /kamera|camera|perspektive|perspective|close-up|nahaufnahme|wide shot/i,
    /farbe|color|material|texture|oberfläche/i,
  ].filter(pattern => pattern.test(text)).length;
  if (words < 8) return { level: 'weak', copy: 'Noch sehr kurz. Ergänze Motiv, Umgebung, Licht und Bildausschnitt.' };
  if (words < 18 || signals < 2) return { level: 'good', copy: 'Gute Basis. Der Prompt-Assistent kann Komposition und technische Bildsprache ergänzen.' };
  return { level: 'strong', copy: 'Starker Prompt mit ausreichend visueller Führung.' };
}

function refreshFluxPromptQuality() {
  const input = document.getElementById('flux-studio-prompt');
  const box = document.getElementById('flux-prompt-quality');
  const copy = document.getElementById('flux-prompt-quality-copy');
  if (!box || !copy) return;
  const result = analyzeFluxPrompt(input?.value || '');
  box.dataset.level = result.level;
  copy.textContent = result.copy;
}

function optimizeFluxPromptLocally(value = '') {
  let text = String(value || '').trim().replace(/\s+/g, ' ');
  if (!text) return text;
  const additions = [];
  if (!/licht|lighting|beleuchtung/i.test(text)) additions.push('weiches gerichtetes Licht mit realistischen Schatten');
  if (!/kamera|camera|perspektive|perspective|aufnahme|shot/i.test(text)) additions.push('klarer professioneller Bildausschnitt auf Augenhöhe');
  if (!/hintergrund|background|umgebung|scene|studio/i.test(text)) additions.push('ruhige, zum Motiv passende Umgebung');
  if (!/detail|material|texture|oberfläche/i.test(text)) additions.push('realistische Materialien und feine Oberflächendetails');
  if (additions.length) text = `${text.replace(/[,. ]+$/, '')}, ${additions.join(', ')}`;
  return text;
}

function applyFluxProfile(profile, { persist = true } = {}) {
  const normalized = FLUX_PROFILE_PRESETS[profile] ? profile : 'custom';
  const hidden = document.getElementById('flux-studio-profile');
  if (hidden) hidden.value = normalized;
  document.querySelectorAll('[data-flux-profile]').forEach(button => button.classList.toggle('active', button.dataset.fluxProfile === normalized));
  const preset = FLUX_PROFILE_PRESETS[normalized];
  if (preset) {
    const format = document.getElementById('flux-studio-format');
    const steps = document.getElementById('flux-studio-steps');
    if (format) format.value = preset.format;
    if (steps) steps.value = preset.steps;
  }
  if (persist) { try { localStorage.setItem('vendoo-flux-profile', normalized); } catch {} }
}

function setFluxVariantCount(value, { persist = true } = {}) {
  const normalized = [1, 2, 4].includes(Number(value)) ? Number(value) : 1;
  const input = document.getElementById('flux-studio-variants');
  if (input) input.value = String(normalized);
  document.querySelectorAll('[data-flux-variants]').forEach(button => button.classList.toggle('active', Number(button.dataset.fluxVariants) === normalized));
  if (persist) { try { localStorage.setItem('vendoo-flux-variants', String(normalized)); } catch {} }
}

function resetFluxStudioWorkspace({ clearSavedDefaults = false, announce = false } = {}) {
  state.fluxStudioCurrent = null;
  renderFluxStudioPreview(null);
  const variants = document.getElementById('flux-studio-variant-results');
  if (variants) { variants.innerHTML = ''; variants.classList.add('hidden'); }

  const prompt = document.getElementById('flux-studio-prompt');
  if (prompt) { prompt.value = ''; prompt.dispatchEvent(new Event('input')); }
  const mode = document.getElementById('flux-studio-prompt-mode');
  if (mode) mode.value = 'precise';
  const style = document.getElementById('flux-studio-style');
  if (style) style.value = 'photorealistic';
  const seed = document.getElementById('flux-studio-seed');
  if (seed) seed.value = '42';
  const lock = document.getElementById('flux-studio-seed-lock');
  if (lock) lock.checked = false;
  applyFluxProfile('balanced', { persist: !clearSavedDefaults });
  setFluxVariantCount(1, { persist: !clearSavedDefaults });
  if (clearSavedDefaults) {
    try {
      localStorage.removeItem('vendoo-flux-profile');
      localStorage.removeItem('vendoo-flux-variants');
    } catch {}
  }
  setFluxStudioStatus('Bereit. Noch keine Auswahl.', '');
  setFluxStudioProgress('', false);
  renderFluxStudioHistory();
  if (announce) toast('FLUX Studio wurde auf einen leeren Arbeitsbereich zurückgesetzt. Verlauf und Bilder bleiben erhalten.');
}

function parseFluxFormat() {
  const value = document.getElementById('flux-studio-format')?.value || '768x768';
  const [width, height] = value.split('x').map(Number);
  return { width: width || 768, height: height || 768 };
}

function setFluxStudioStatus(message, tone = '') {
  const status = document.getElementById('flux-studio-status');
  if (!status) return;
  status.textContent = message;
  status.dataset.tone = tone;
}

function setFluxStudioProgress(label = 'Vorbereitung', visible = true, stateName = 'active') {
  const shell = document.getElementById('flux-studio-progress');
  const value = document.getElementById('flux-studio-progress-value');
  const text = document.getElementById('flux-studio-progress-label');
  shell?.classList.toggle('hidden', !visible);
  if (shell) shell.dataset.state = stateName;
  if (value) value.textContent = stateName === 'done' ? 'Fertig' : stateName === 'error' ? 'Fehler' : stateName === 'cancelled' ? 'Abgebrochen' : 'Aktive Phase';
  if (text) text.textContent = label || 'FLUX arbeitet …';
}

function formatFluxDate(value) {
  if (!value) return '–';
  const normalized = String(value).includes('T') ? String(value) : `${String(value).replace(' ', 'T')}Z`;
  const date = new Date(normalized);
  return Number.isNaN(date.getTime()) ? String(value) : date.toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' });
}

function formatFluxDuration(ms) {
  const seconds = Math.max(0, Math.round(Number(ms || 0) / 1000));
  if (seconds < 60) return `${seconds} s`;
  return `${Math.floor(seconds / 60)} min ${seconds % 60} s`;
}

function fluxStatusLabel(status) {
  return ({
    queued: 'Wartend', running: 'Läuft', cancelling: 'Wird abgebrochen', completed: 'Erfolgreich',
    completed_with_errors: 'Teilweise fertig', failed: 'Fehlgeschlagen', cancelled: 'Abgebrochen',
  })[status] || status || 'Unbekannt';
}

async function refreshFluxStudioEngineStatus() {
  const chip = document.getElementById('flux-studio-engine-chip');
  try {
    const status = await api('GET', '/api/local-image/status');
    const ready = !!status.reachable;
    if (chip) {
      chip.textContent = status.engine_label || (ready ? 'Aktiv und bereit' : 'Nicht bereit');
      chip.className = `settings-status-chip ${ready ? 'is-ready' : ['stopped','starting'].includes(status.engine_state) ? 'is-warning' : 'is-disabled'}`;
    }
    document.getElementById('flux-studio-generate')?.toggleAttribute('disabled', !ready || state.fluxStudioBusy);
    return status;
  } catch (error) {
    if (chip) { chip.textContent = 'Status nicht verfügbar'; chip.className = 'settings-status-chip is-disabled'; }
    return null;
  }
}

async function waitForFluxEngineReady({ statusElement = null, timeoutMs = 190000 } = {}) {
  const startedAt = Date.now();
  while (Date.now() - startedAt < timeoutMs) {
    const status = await refreshFluxStudioEngineStatus();
    if (status?.reachable) {
      if (statusElement) statusElement.textContent = 'Lokaler FLUX/ComfyUI-Server ist aktiv und bereit.';
      return status;
    }
    const runtime = status?.runtime_state || {};
    const elapsed = Math.max(0, Math.round((Date.now() - startedAt) / 1000));
    const detail = runtime.message || status?.message || 'ComfyUI wird initialisiert.';
    if (statusElement) statusElement.textContent = `FLUX startet seit ${elapsed}s · ${detail}`;
    setFluxStudioStatus(`FLUX startet seit ${elapsed}s · ${detail}`, 'working');
    if (runtime.status === 'failed' || status?.engine_state === 'failed') {
      const tail = [...(runtime.stderr_tail || []), ...(runtime.stdout_tail || [])].filter(Boolean).slice(-6).join(' | ');
      throw new Error(runtime.error || tail || runtime.message || 'ComfyUI wurde während des Starts beendet.');
    }
    await new Promise(resolve => setTimeout(resolve, 2000));
  }
  throw new Error('ComfyUI war nach 190 Sekunden noch nicht erreichbar. Prüfe die Runtime-Protokolle in Vendoo/logs.');
}

function fluxVariantToImage(job, variant) {
  if (!variant?.image_path) return null;
  return {
    id: variant.image_id,
    job_id: job.id,
    variant_index: variant.variant_index,
    prompt: job.prompt,
    final_prompt: job.final_prompt,
    image_path: variant.image_path,
    public_url: variant.public_url,
    width: job.width,
    height: job.height,
    seed: variant.seed,
    steps: job.steps,
    style: job.style,
    profile: job.profile,
    prompt_mode: job.prompt_mode,
    duration_ms: variant.duration_ms,
    favorite: variant.favorite,
    parameters: {
      width: job.width, height: job.height, steps: job.steps, style: job.style, profile: job.profile,
      prompt_mode: job.prompt_mode, variants: job.variants_count, seed_lock: job.seed_lock,
    },
  };
}

function renderFluxVariantResults(job) {
  const container = document.getElementById('flux-studio-variant-results');
  if (!container) return;
  const completed = (job?.variants || []).filter(variant => variant.image_path);
  container.classList.toggle('hidden', !completed.length);
  if (!completed.length) { container.innerHTML = ''; return; }
  container.innerHTML = completed.map(variant => {
    const item = fluxVariantToImage(job, variant);
    const active = Number(state.fluxStudioCurrent?.id) === Number(item.id);
    return `<button type="button" class="flux-variant-tile ${active ? 'active' : ''}" data-flux-variant-image="${escapeHtml(String(item.id))}">
      <img src="${escapeHtml(item.public_url || photoUrl(item.image_path))}" alt="Variante ${variant.variant_index}">
      <span>Variante ${variant.variant_index}</span><small>Seed ${variant.seed}</small>
    </button>`;
  }).join('');
  container.querySelectorAll('[data-flux-variant-image]').forEach(button => button.addEventListener('click', () => {
    const variant = completed.find(entry => String(entry.image_id) === button.dataset.fluxVariantImage);
    const item = fluxVariantToImage(job, variant);
    if (item) { renderFluxStudioPreview(item); renderFluxVariantResults(job); }
  }));
}

function renderFluxStudioPreview(item) {
  state.fluxStudioCurrent = item || null;
  const image = document.getElementById('flux-studio-preview-image');
  const empty = document.getElementById('flux-studio-preview-empty');
  const actions = document.getElementById('flux-studio-preview-actions');
  const meta = document.getElementById('flux-studio-preview-meta');
  const download = document.getElementById('flux-studio-download');
  const favorite = document.getElementById('flux-studio-favorite');
  if (!item?.image_path) {
    image?.classList.add('hidden');
    empty?.classList.remove('hidden');
    actions?.classList.add('hidden');
    if (meta) meta.textContent = 'Noch kein Bild';
    return;
  }
  if (image) {
    image.src = item.public_url || photoUrl(item.image_path);
    image.alt = item.prompt ? `FLUX: ${item.prompt}` : 'FLUX-Vorschau';
    image.classList.remove('hidden');
  }
  empty?.classList.add('hidden');
  actions?.classList.remove('hidden');
  if (meta) meta.textContent = `${item.width || '–'} × ${item.height || '–'} · Seed ${item.seed ?? '–'} · ${item.steps || '–'} Schritte${item.duration_ms ? ` · ${formatFluxDuration(item.duration_ms)}` : ''}`;
  if (download) {
    download.href = item.public_url || photoUrl(item.image_path);
    download.download = `vendoo-flux-${item.id || Date.now()}.${String(item.image_path).split('.').pop() || 'png'}`;
  }
  if (favorite) favorite.textContent = item.favorite ? '★ Favorit' : '☆ Favorit';
}

function renderFluxStudioHistory() {
  const container = document.getElementById('flux-studio-history');
  if (!container) return;
  if (!state.fluxStudioHistory.length) {
    container.innerHTML = '<div class="empty-state">Noch keine passenden FLUX-Bilder gespeichert.</div>';
    return;
  }
  container.innerHTML = state.fluxStudioHistory.map(item => `<article class="flux-history-card ${state.fluxStudioCurrent?.id === item.id ? 'active' : ''}">
    <button type="button" class="flux-history-image" data-flux-select="${item.id}"><img src="${escapeHtml(item.public_url || photoUrl(item.image_path))}" alt="${escapeHtml(item.prompt || 'FLUX-Bild')}"><span>${item.favorite ? '★' : ''}</span></button>
    <div><strong title="${escapeHtml(item.prompt || '')}">${escapeHtml(item.prompt || 'FLUX-Bild')}</strong><small>${item.width} × ${item.height} · Seed ${item.seed ?? '–'} · ${formatFluxDate(item.created_at)}</small></div>
    <div class="flux-history-actions"><button type="button" class="small-btn" data-flux-favorite="${item.id}">${item.favorite ? '★' : '☆'}</button><button type="button" class="small-btn" data-flux-similar="${item.id}">Ähnlich</button><button type="button" class="small-btn" data-flux-edit="${item.id}">Bearbeiten</button><button type="button" class="small-btn danger" data-flux-delete="${item.id}">Löschen</button></div>
  </article>`).join('');
  container.querySelectorAll('[data-flux-select]').forEach(button => button.addEventListener('click', () => {
    const item = state.fluxStudioHistory.find(entry => String(entry.id) === button.dataset.fluxSelect);
    if (item) { renderFluxStudioPreview(item); renderFluxStudioHistory(); }
  }));
  container.querySelectorAll('[data-flux-favorite]').forEach(button => button.addEventListener('click', async () => {
    const item = state.fluxStudioHistory.find(entry => String(entry.id) === button.dataset.fluxFavorite);
    if (item) await toggleFluxFavorite(item);
  }));
  container.querySelectorAll('[data-flux-similar]').forEach(button => button.addEventListener('click', () => {
    const item = state.fluxStudioHistory.find(entry => String(entry.id) === button.dataset.fluxSimilar);
    if (item) createSimilarFluxJob(item);
  }));
  container.querySelectorAll('[data-flux-edit]').forEach(button => button.addEventListener('click', () => {
    const item = state.fluxStudioHistory.find(entry => String(entry.id) === button.dataset.fluxEdit);
    if (item) { renderFluxStudioPreview(item); openPhotoEditor(0, 'flux'); }
  }));
  container.querySelectorAll('[data-flux-delete]').forEach(button => button.addEventListener('click', async () => {
    const item = state.fluxStudioHistory.find(entry => String(entry.id) === button.dataset.fluxDelete);
    if (item) await deleteFluxStudioImage(item);
  }));
}

function renderFluxHistoryPagination() {
  const pagination = state.fluxHistoryPagination || {};
  const page = Number(pagination.page || 1);
  const pages = Number(pagination.total_pages || 1);
  const total = Number(pagination.total || 0);
  document.getElementById('flux-history-page-info')?.replaceChildren(document.createTextNode(`Seite ${page} von ${pages} · ${total} Bilder`));
  const prev = document.getElementById('flux-history-prev');
  const next = document.getElementById('flux-history-next');
  if (prev) prev.disabled = page <= 1;
  if (next) next.disabled = page >= pages;
}

async function loadFluxStudioHistory({ silent = false } = {}) {
  const container = document.getElementById('flux-studio-history');
  if (!silent && container && !state.fluxStudioHistory.length) container.innerHTML = '<div class="empty-state">FLUX-Bibliothek wird geladen …</div>';
  try {
    const favorites = document.getElementById('flux-studio-favorites-only')?.checked ? '1' : '0';
    const result = await api('GET', `/api/flux-studio/images?page=${state.fluxHistoryPage}&page_size=${state.fluxHistoryPageSize}&favorites=${favorites}`);
    state.fluxStudioHistory = result.items || [];
    state.fluxHistoryPagination = result.pagination || { page: 1, total_pages: 1, total: state.fluxStudioHistory.length };
    state.fluxHistoryPage = Number(state.fluxHistoryPagination.page || 1);
    if (state.fluxStudioCurrent) {
      const refreshed = state.fluxStudioHistory.find(item => item.id === state.fluxStudioCurrent.id);
      if (refreshed) renderFluxStudioPreview(refreshed);
      else renderFluxStudioPreview(null);
    }
    renderFluxStudioHistory();
    renderFluxHistoryPagination();
  } catch (error) {
    if (container) container.innerHTML = `<div class="empty-state">Bibliothek konnte nicht geladen werden: ${escapeHtml(error.message)}</div>`;
  }
}

async function toggleFluxFavorite(item = state.fluxStudioCurrent) {
  if (!item?.id) return;
  try {
    const updated = await api('PATCH', `/api/flux-studio/images/${item.id}/favorite`, { favorite: !item.favorite });
    state.fluxStudioHistory = state.fluxStudioHistory.map(entry => entry.id === updated.id ? updated : entry);
    if (state.fluxStudioCurrent?.id === updated.id) renderFluxStudioPreview(updated);
    await loadFluxStudioHistory({ silent: true });
    toast(updated.favorite ? 'Als Favorit gespeichert.' : 'Favorit entfernt.');
  } catch (error) { toast(`Favorit konnte nicht geändert werden: ${error.message}`); }
}

async function deleteFluxStudioImage(item = state.fluxStudioCurrent) {
  if (!item?.id) return;
  if (!window.confirm('Dieses FLUX-Bild wirklich dauerhaft löschen?')) return;
  try {
    await api('DELETE', `/api/flux-studio/images/${item.id}`);
    if (state.fluxStudioCurrent?.id === item.id) renderFluxStudioPreview(null);
    await loadFluxStudioHistory({ silent: true });
    toast('FLUX-Bild gelöscht.');
  } catch (error) { toast(`Löschen fehlgeschlagen: ${error.message}`); }
}

function getFluxStudioPayload() {
  const { width, height } = parseFluxFormat();
  return {
    prompt: document.getElementById('flux-studio-prompt')?.value.trim() || '',
    width,
    height,
    seed: Number(document.getElementById('flux-studio-seed')?.value || 42),
    steps: Number(document.getElementById('flux-studio-steps')?.value || 4),
    style: document.getElementById('flux-studio-style')?.value || 'none',
    profile: document.getElementById('flux-studio-profile')?.value || 'balanced',
    prompt_mode: document.getElementById('flux-studio-prompt-mode')?.value || 'precise',
    assistant: true,
    variants: Number(document.getElementById('flux-studio-variants')?.value || 1),
    seed_lock: !!document.getElementById('flux-studio-seed-lock')?.checked,
  };
}

function applyFluxJobToForm(jobOrImage) {
  const item = jobOrImage || {};
  const params = item.parameters || {};
  const prompt = document.getElementById('flux-studio-prompt');
  if (prompt && item.prompt) { prompt.value = item.prompt; prompt.dispatchEvent(new Event('input')); }
  const format = `${item.width || params.width || 768}x${item.height || params.height || 768}`;
  const formatSelect = document.getElementById('flux-studio-format');
  if (formatSelect && [...formatSelect.options].some(option => option.value === format)) formatSelect.value = format;
  const steps = document.getElementById('flux-studio-steps');
  if (steps && (item.steps || params.steps)) steps.value = String(item.steps || params.steps);
  const style = document.getElementById('flux-studio-style'); if (style) style.value = item.style || params.style || 'none';
  const mode = document.getElementById('flux-studio-prompt-mode'); if (mode) mode.value = item.prompt_mode || params.prompt_mode || 'precise';
  const seed = document.getElementById('flux-studio-seed'); if (seed && Number.isFinite(Number(item.seed ?? item.seed_base))) seed.value = String(item.seed ?? item.seed_base);
  const lock = document.getElementById('flux-studio-seed-lock'); if (lock) lock.checked = !!(item.seed_lock ?? params.seed_lock);
  setFluxVariantCount(item.variants_count || params.variants || 1);
  applyFluxProfile(item.profile || params.profile || 'custom');
}

async function watchFluxJob(jobId) {
  if (!jobId || state.fluxStudioWatchers.has(jobId)) return;
  state.fluxStudioWatchers.set(jobId, true);
  try {
    const deadline = Date.now() + 12 * 60 * 1000;
    while (Date.now() < deadline) {
      const job = await api('GET', `/api/flux-prompt/jobs/${encodeURIComponent(jobId)}`);
      const activeVariant = (job.variants || []).find(variant => variant.status === 'running') || (job.variants || []).find(variant => variant.status === 'queued');
      const label = activeVariant?.message || job.message || fluxStatusLabel(job.status);
      const stateName = job.status === 'failed' ? 'error' : job.status === 'cancelled' ? 'cancelled' : FLUX_TERMINAL_JOB_STATUSES.has(job.status) ? 'done' : 'active';
      setFluxStudioProgress(label, true, stateName);
      setFluxStudioStatus(label, stateName === 'error' ? 'error' : stateName === 'done' ? 'success' : 'working');
      const images = (job.variants || []).filter(variant => variant.image_path);
      if (images.length) {
        const selected = fluxVariantToImage(job, images[0]);
        if (selected) renderFluxStudioPreview(selected);
        renderFluxVariantResults(job);
      }
      await loadFluxJobs({ silent: true });
      if (FLUX_TERMINAL_JOB_STATUSES.has(job.status)) {
        await loadFluxStudioHistory();
        await refreshFluxRuntimeInfo({ silent: true });
        if (job.status === 'failed') toast(`FLUX-Auftrag fehlgeschlagen: ${job.error || 'Unbekannter Fehler'}`);
        else if (job.status === 'cancelled') toast('FLUX-Auftrag wurde abgebrochen.');
        else toast(job.status === 'completed_with_errors' ? 'FLUX-Auftrag teilweise abgeschlossen.' : 'FLUX-Auftrag abgeschlossen.');
        return job;
      }
      await new Promise(resolve => setTimeout(resolve, 1400));
    }
    setFluxStudioStatus('Der Auftrag läuft weiter im Job-Center.', 'working');
  } catch (error) {
    setFluxStudioStatus(error.message, 'error');
  } finally {
    state.fluxStudioWatchers.delete(jobId);
  }
}

async function runFluxStudioGeneration(payloadOverride = null) {
  if (state.fluxStudioBusy) return;
  const payload = payloadOverride || getFluxStudioPayload();
  if (String(payload.prompt || '').trim().length < 3) return toast('Bitte einen aussagekräftigen Prompt eingeben.');
  const button = document.getElementById('flux-studio-generate');
  state.fluxStudioBusy = true;
  if (button) button.disabled = true;
  setFluxStudioProgress('Auftrag wird dauerhaft gespeichert …', true, 'active');
  setFluxStudioStatus('FLUX-Auftrag wird erstellt …', 'working');
  try {
    const queued = await api('POST', '/api/flux-prompt/jobs', payload);
    setFluxStudioStatus(`${queued.variants_count} Variante${queued.variants_count === 1 ? '' : 'n'} wurde${queued.variants_count === 1 ? '' : 'n'} eingereiht.`, 'success');
    await loadFluxJobs({ silent: true });
    watchFluxJob(queued.id);
    return queued;
  } catch (error) {
    setFluxStudioStatus(error.message, 'error');
    setFluxStudioProgress('Auftrag konnte nicht erstellt werden.', true, 'error');
    toast(`FLUX-Fehler: ${error.message}`);
    return null;
  } finally {
    state.fluxStudioBusy = false;
    if (button) button.disabled = false;
    refreshFluxStudioEngineStatus();
  }
}

async function createSimilarFluxJob(item = state.fluxStudioCurrent) {
  if (!item?.prompt) return;
  applyFluxJobToForm(item);
  const seed = Math.floor(Math.random() * 2147483647);
  const seedInput = document.getElementById('flux-studio-seed'); if (seedInput) seedInput.value = String(seed);
  toast('Prompt und Parameter wurden übernommen; der neue Basis-Seed erzeugt ähnliche Varianten.');
  await runFluxStudioGeneration();
}

function renderFluxJobSummary() {
  const container = document.getElementById('flux-job-summary');
  if (!container) return;
  const summary = state.fluxJobSummary || {};
  const jobs = summary.jobs || {};
  const variants = summary.variants || {};
  document.getElementById('flux-job-archive-count')?.replaceChildren(document.createTextNode(String(summary.archived_total || 0)));
  container.innerHTML = state.fluxJobArchiveMode
    ? `<div><span>Archivierte Aufträge</span><strong>${summary.archived_total || 0}</strong></div>
       <div><span>Auf dieser Seite</span><strong>${state.fluxJobs.length}</strong></div>
       <div><span>Seiten</span><strong>${state.fluxJobPagination.total_pages || 1}</strong></div>
       <div><span>Bilder bleiben erhalten</span><strong>Ja</strong></div>`
    : `<div><span>Aktive Worker</span><strong>${summary.active_workers || 0} / ${summary.max_parallel || 1}</strong></div>
       <div><span>Wartende Varianten</span><strong>${variants.queued || 0}</strong></div>
       <div><span>Laufende Varianten</span><strong>${variants.running || 0}</strong></div>
       <div><span>Fertige Aufträge</span><strong>${(jobs.completed || 0) + (jobs.completed_with_errors || 0)}</strong></div>`;
}

function updateFluxJobToolbar() {
  const count = state.fluxJobSelected.size;
  const archiveMode = state.fluxJobArchiveMode;
  document.getElementById('flux-job-selected-count')?.replaceChildren(document.createTextNode(`${count} ausgewählt`));
  const selectPage = document.getElementById('flux-job-select-page');
  if (selectPage) {
    const selectedOnPage = state.fluxJobs.filter(job => state.fluxJobSelected.has(job.id)).length;
    selectPage.checked = state.fluxJobs.length > 0 && selectedOnPage === state.fluxJobs.length;
    selectPage.indeterminate = selectedOnPage > 0 && selectedOnPage < state.fluxJobs.length;
  }
  const archive = document.getElementById('flux-job-archive-selected');
  const restore = document.getElementById('flux-job-restore-selected');
  const remove = document.getElementById('flux-job-delete-selected');
  const removeAll = document.getElementById('flux-job-delete-all-archive');
  archive?.classList.toggle('hidden', archiveMode);
  restore?.classList.toggle('hidden', !archiveMode);
  remove?.classList.toggle('hidden', !archiveMode);
  removeAll?.classList.toggle('hidden', !archiveMode);
  if (archive) archive.disabled = count === 0;
  if (restore) restore.disabled = count === 0;
  if (remove) remove.disabled = count === 0;
  document.getElementById('flux-job-clear-queue')?.classList.toggle('hidden', archiveMode);
  document.getElementById('flux-job-filter')?.classList.toggle('hidden', archiveMode);
  document.getElementById('flux-job-view-active')?.classList.toggle('active', !archiveMode);
  document.getElementById('flux-job-view-archive')?.classList.toggle('active', archiveMode);
}

function renderFluxJobPagination() {
  const pagination = state.fluxJobPagination || {};
  const page = Number(pagination.page || 1);
  const pages = Number(pagination.total_pages || 1);
  const total = Number(pagination.total || 0);
  const info = document.getElementById('flux-job-page-info');
  if (info) info.textContent = `Seite ${page} von ${pages} · ${total} Auftrag${total === 1 ? '' : 'e'}`;
  const prev = document.getElementById('flux-job-prev');
  const next = document.getElementById('flux-job-next');
  if (prev) prev.disabled = page <= 1;
  if (next) next.disabled = page >= pages;
}

async function archiveFluxJobs(ids) {
  if (!ids.length) return;
  const result = await api('POST', '/api/flux-prompt/jobs/archive', { ids });
  state.fluxJobSelected.clear();
  toast(`${result.archived || 0} Auftrag/Aufträge archiviert${result.skipped ? ` · ${result.skipped} aktiv oder nicht archiviert` : ''}.`);
  await loadFluxJobs();
}

async function restoreFluxJobs(ids) {
  if (!ids.length) return;
  const result = await api('POST', '/api/flux-prompt/jobs/restore', { ids });
  state.fluxJobSelected.clear();
  toast(`${result.restored || 0} Auftrag/Aufträge wiederhergestellt.`);
  await loadFluxJobs();
}

async function deleteArchivedFluxJobs(ids = [], all = false) {
  const message = all
    ? 'Das komplette FLUX-Auftragsarchiv endgültig löschen? Die erzeugten Bilder bleiben in der Bildergalerie erhalten.'
    : `${ids.length} archivierte Auftrag/Aufträge endgültig löschen? Die erzeugten Bilder bleiben erhalten.`;
  if (!window.confirm(message)) return;
  const result = await api('DELETE', `/api/flux-prompt/jobs/archive${all ? '?all=1' : ''}`, { ids });
  state.fluxJobSelected.clear();
  toast(`${result.deleted || 0} archivierte Auftrag/Aufträge gelöscht.`);
  await loadFluxJobs();
}

function renderFluxJobList() {
  const container = document.getElementById('flux-job-list');
  if (!container) return;
  const jobs = state.fluxJobs;
  if (!jobs.length) {
    container.innerHTML = `<div class="empty-state">${state.fluxJobArchiveMode ? 'Das Auftragsarchiv ist leer.' : 'Keine Aufträge für diesen Filter.'}</div>`;
    updateFluxJobToolbar();
    return;
  }
  container.innerHTML = jobs.map(job => {
    const variants = job.variants || [];
    const expanded = state.fluxJobExpanded.has(job.id);
    const selected = state.fluxJobSelected.has(job.id);
    const thumbs = variants.map(variant => variant.image_path
      ? `<button type="button" class="flux-job-thumb" data-flux-job-image="${job.id}:${variant.variant_index}" title="Variante ${variant.variant_index} · Seed ${variant.seed}${variant.prompt_id ? ` · Prompt-ID ${escapeHtml(variant.prompt_id)}` : ''}"><img src="${escapeHtml(variant.public_url || photoUrl(variant.image_path))}" alt="Variante ${variant.variant_index}"><span>${variant.variant_index}</span><small>Seed ${variant.seed}</small></button>`
      : `<div class="flux-job-thumb is-placeholder" title="${escapeHtml(variant.message || '')}"><span>${variant.variant_index}</span><small>${escapeHtml(fluxStatusLabel(variant.status))} · Seed ${variant.seed}</small></div>`).join('');
    const ledger = variants.map(variant => `<div><b>V${variant.variant_index}</b><span>Seed ${variant.seed}</span><span>${escapeHtml(variant.phase || variant.status)}</span>${variant.prompt_id ? `<code title="${escapeHtml(variant.prompt_id)}">${escapeHtml(String(variant.prompt_id).slice(0, 12))}${String(variant.prompt_id).length > 12 ? '…' : ''}</code>` : '<code>Noch keine Prompt-ID</code>'}</div>`).join('');
    const canCancel = FLUX_ACTIVE_JOB_STATUSES.has(job.status);
    const canRetry = ['failed', 'cancelled', 'completed_with_errors'].includes(job.status) && !state.fluxJobArchiveMode;
    const canArchive = !state.fluxJobArchiveMode && !canCancel;
    return `<article class="flux-job-card${expanded ? ' is-expanded' : ' is-collapsed'}${selected ? ' is-selected' : ''}" data-status="${escapeHtml(job.status)}">
      <div class="flux-job-card-header">
        <label class="flux-job-card-select"><input type="checkbox" data-flux-job-select="${job.id}" ${selected ? 'checked' : ''} aria-label="Auftrag auswählen"></label>
        <button type="button" class="flux-job-card-toggle" data-flux-job-toggle="${job.id}" aria-expanded="${expanded}">
          <span class="flux-job-card-toggle-main"><span class="flux-job-status is-${escapeHtml(job.status)}">${escapeHtml(fluxStatusLabel(job.status))}</span><strong class="flux-job-prompt" title="${escapeHtml(job.prompt)}">${escapeHtml(job.prompt)}</strong></span>
          <span class="flux-job-card-toggle-meta">${formatFluxDate(job.archived_at || job.created_at)}${job.elapsed_ms ? ` · ${formatFluxDuration(job.elapsed_ms)}` : ''}<i aria-hidden="true">⌄</i></span>
        </button>
      </div>
      <div class="flux-job-card-body">
        <div class="flux-job-card-main"><div class="flux-job-params"><span>${job.width} × ${job.height}</span><span>${job.steps} Schritte</span><span>${escapeHtml(job.style)}</span><span>${job.variants_count} Variante${job.variants_count === 1 ? '' : 'n'}</span><span>Seed ${job.seed_base}${job.seed_lock ? ' · Lock' : ''}</span></div><p>${escapeHtml(job.message || '')}${job.error ? `<br><b>${escapeHtml(job.error)}</b>` : ''}</p></div>
        <div class="flux-job-variants">${thumbs}</div><div class="flux-job-variant-ledger">${ledger}</div>
        <div class="flux-job-actions">
          <button type="button" class="small-btn" data-flux-job-load="${job.id}">Parameter laden</button>
          ${canCancel ? `<button type="button" class="small-btn danger" data-flux-job-cancel="${job.id}">Abbrechen</button>` : ''}
          ${canRetry ? `<button type="button" class="small-btn" data-flux-job-retry="${job.id}">Wiederholen</button>` : ''}
          ${canArchive ? `<button type="button" class="small-btn" data-flux-job-archive="${job.id}">Archivieren</button>` : ''}
          ${state.fluxJobArchiveMode ? `<button type="button" class="small-btn" data-flux-job-restore="${job.id}">Wiederherstellen</button><button type="button" class="small-btn danger" data-flux-job-delete="${job.id}">Endgültig löschen</button>` : ''}
        </div>
      </div>
    </article>`;
  }).join('');
  container.querySelectorAll('[data-flux-job-select]').forEach(input => input.addEventListener('change', () => {
    if (input.checked) state.fluxJobSelected.add(input.dataset.fluxJobSelect); else state.fluxJobSelected.delete(input.dataset.fluxJobSelect);
    input.closest('.flux-job-card')?.classList.toggle('is-selected', input.checked);
    updateFluxJobToolbar();
  }));
  container.querySelectorAll('[data-flux-job-toggle]').forEach(button => button.addEventListener('click', () => {
    const jobId = button.dataset.fluxJobToggle;
    state.fluxJobManuallyToggled.add(jobId);
    if (state.fluxJobExpanded.has(jobId)) state.fluxJobExpanded.delete(jobId); else state.fluxJobExpanded.add(jobId);
    renderFluxJobList();
  }));
  container.querySelectorAll('[data-flux-job-load]').forEach(button => button.addEventListener('click', () => {
    const job = state.fluxJobs.find(entry => entry.id === button.dataset.fluxJobLoad);
    if (job) { applyFluxJobToForm(job); setFluxStudioStatus('Prompt und Parameter wurden aus dem Verlauf geladen.', 'success'); }
  }));
  container.querySelectorAll('[data-flux-job-cancel]').forEach(button => button.addEventListener('click', async () => {
    try { await api('POST', `/api/flux-prompt/jobs/${button.dataset.fluxJobCancel}/cancel`, {}); toast('Abbruch wurde angefordert.'); await loadFluxJobs(); } catch (error) { toast(`Abbruch fehlgeschlagen: ${error.message}`); }
  }));
  container.querySelectorAll('[data-flux-job-retry]').forEach(button => button.addEventListener('click', async () => {
    try { const job = await api('POST', `/api/flux-prompt/jobs/${button.dataset.fluxJobRetry}/retry`, {}); state.fluxJobExpanded.add(job.id); toast('Auftrag wurde erneut eingereiht.'); await loadFluxJobs(); watchFluxJob(job.id); } catch (error) { toast(`Wiederholung fehlgeschlagen: ${error.message}`); }
  }));
  container.querySelectorAll('[data-flux-job-archive]').forEach(button => button.addEventListener('click', () => archiveFluxJobs([button.dataset.fluxJobArchive])));
  container.querySelectorAll('[data-flux-job-restore]').forEach(button => button.addEventListener('click', () => restoreFluxJobs([button.dataset.fluxJobRestore])));
  container.querySelectorAll('[data-flux-job-delete]').forEach(button => button.addEventListener('click', () => deleteArchivedFluxJobs([button.dataset.fluxJobDelete])));
  container.querySelectorAll('[data-flux-job-image]').forEach(button => button.addEventListener('click', () => {
    const [jobId, variantIndex] = button.dataset.fluxJobImage.split(':');
    const job = state.fluxJobs.find(entry => entry.id === jobId);
    const variant = job?.variants?.find(entry => String(entry.variant_index) === variantIndex);
    const item = fluxVariantToImage(job, variant);
    if (item) { renderFluxStudioPreview(item); renderFluxVariantResults(job); }
  }));
  updateFluxJobToolbar();
}

async function loadFluxJobs({ silent = false } = {}) {
  const container = document.getElementById('flux-job-list');
  if (!silent && container && !state.fluxJobs.length) container.innerHTML = '<div class="empty-state">Job-Center wird geladen …</div>';
  try {
    const filter = state.fluxJobArchiveMode ? '' : (document.getElementById('flux-job-filter')?.value || '');
    const params = new URLSearchParams({ page: String(state.fluxJobPage), page_size: String(state.fluxJobPageSize), archived: state.fluxJobArchiveMode ? '1' : '0' });
    if (filter) params.set('status', filter);
    const result = await api('GET', `/api/flux-prompt/jobs?${params}`);
    state.fluxJobs = result.jobs || [];
    state.fluxJobSummary = result.summary || null;
    state.fluxJobPagination = result.pagination || { page: 1, total_pages: 1, total: state.fluxJobs.length };
    state.fluxJobPage = Number(state.fluxJobPagination.page || 1);
    state.fluxJobSelected = new Set([...state.fluxJobSelected].filter(id => state.fluxJobs.some(job => job.id === id)));
    for (const job of state.fluxJobs) if (FLUX_ACTIVE_JOB_STATUSES.has(job.status) && !state.fluxJobManuallyToggled.has(job.id)) state.fluxJobExpanded.add(job.id);
    renderFluxJobSummary(); renderFluxJobList(); renderFluxJobPagination(); updateFluxJobToolbar();
    if (!state.fluxJobArchiveMode) for (const job of state.fluxJobs.filter(entry => FLUX_ACTIVE_JOB_STATUSES.has(entry.status))) watchFluxJob(job.id);
  } catch (error) {
    if (!silent && container) container.innerHTML = `<div class="empty-state">Job-Center konnte nicht geladen werden: ${escapeHtml(error.message)}</div>`;
  }
}

function bytesToGiB(value) {
  const number = Number(value || 0);
  return number > 0 ? `${(number / 1024 / 1024 / 1024).toFixed(1)} GB` : '–';
}

function renderFluxRuntimeInfo() {
  const container = document.getElementById('flux-runtime-summary');
  if (!container) return;
  const runtime = state.fluxRuntime;
  if (!runtime) { container.innerHTML = '<div class="empty-state">Keine Systemdaten verfügbar.</div>'; return; }
  const stats = runtime.system_stats || {};
  const system = stats.system || stats;
  const devices = Array.isArray(stats.devices) ? stats.devices : Array.isArray(system.devices) ? system.devices : [];
  const gpu = devices[0] || {};
  const queue = runtime.queue_summary || {};
  const historyCount = runtime.history ? Object.keys(runtime.history).length : 0;
  container.innerHTML = `
    <div class="flux-runtime-grid">
      <div><span>ComfyUI</span><strong>${runtime.reachable ? 'Erreichbar' : 'Nicht erreichbar'}</strong><small>${escapeHtml(runtime.url || '')}</small></div>
      <div><span>ComfyUI-Queue</span><strong>${queue.running || 0} aktiv · ${queue.pending || 0} wartend</strong><small>${historyCount} Historieneinträge geladen</small></div>
      <div><span>GPU</span><strong>${escapeHtml(gpu.name || gpu.type || 'Nicht gemeldet')}</strong><small>${gpu.vram_total ? `${bytesToGiB(gpu.vram_free)} frei von ${bytesToGiB(gpu.vram_total)}` : 'VRAM-Daten nicht gemeldet'}</small></div>
      <div><span>System</span><strong>${escapeHtml(system.os || system.system || system.python_version || 'ComfyUI-System')}</strong><small>${escapeHtml(system.comfyui_version || system.version || '')}</small></div>
    </div>`;
}

async function refreshFluxRuntimeInfo({ silent = false } = {}) {
  const container = document.getElementById('flux-runtime-summary');
  if (!silent && container) container.innerHTML = '<div class="empty-state">ComfyUI-Warteschlange, Historie und GPU-Daten werden geladen …</div>';
  try {
    state.fluxRuntime = await api('GET', '/api/flux-prompt/runtime');
    renderFluxRuntimeInfo();
  } catch (error) {
    state.fluxRuntime = null;
    if (container) container.innerHTML = `<div class="empty-state">Systemdaten nicht verfügbar: ${escapeHtml(error.message)}</div>`;
  }
}

async function useFluxImageInGenerator() {
  const item = state.fluxStudioCurrent;
  if (!item?.id) return;
  try {
    const result = await api('POST', `/api/flux-studio/images/${item.id}/copy-to-generator`, {});
    if (result.filename && !state.photos.includes(result.filename)) state.photos.push(result.filename);
    renderPhotoPreview();
    switchToTab('generator');
    toast('Eine unabhängige Kopie wurde als Artikelfoto übernommen.');
  } catch (error) { toast(`Übernahme fehlgeschlagen: ${error.message}`); }
}


function aiProviderDisplayName(provider = state.aiProvider) {
  return state.providers.find(entry => entry.id === provider)?.name || ({ claude: 'Claude', openai: 'OpenAI', openrouter: 'OpenRouter', local: 'Ollama' })[provider] || provider || 'AI';
}

function openFluxPromptLab() {
  const prompt = document.getElementById('flux-studio-prompt');
  if (!prompt?.value.trim()) return toast('Bitte zuerst einen Prompt eingeben.');
  const modal = document.getElementById('flux-prompt-lab');
  document.getElementById('flux-prompt-lab-original').value = prompt.value.trim();
  document.getElementById('flux-prompt-lab-result').value = '';
  document.getElementById('flux-prompt-lab-notes').textContent = '';
  document.getElementById('flux-prompt-lab-status').textContent = 'Bereit für lokale Strukturierung oder AI-Veredelung.';
  document.getElementById('flux-prompt-lab-provider').textContent = `${aiProviderDisplayName()}${state.aiModel ? ` · ${state.aiModel}` : ' · Auto'}`;
  modal?.classList.remove('hidden');
  modal?.setAttribute('aria-hidden', 'false');
  document.body.classList.add('modal-open');
  setTimeout(() => document.getElementById('flux-prompt-lab-original')?.focus(), 40);
}

function closeFluxPromptLab() {
  const modal = document.getElementById('flux-prompt-lab');
  modal?.classList.add('hidden');
  modal?.setAttribute('aria-hidden', 'true');
  if (document.getElementById('smart-copy-modal')?.classList.contains('hidden')) document.body.classList.remove('modal-open');
}

function setupFluxPromptLab() {
  document.querySelectorAll('[data-flux-prompt-lab-close]').forEach(button => button.addEventListener('click', closeFluxPromptLab));
  document.getElementById('flux-prompt-lab-local')?.addEventListener('click', () => {
    const original = document.getElementById('flux-prompt-lab-original')?.value || '';
    const optimized = optimizeFluxPromptLocally(original);
    if (!optimized) return toast('Bitte zuerst einen Prompt eingeben.');
    document.getElementById('flux-prompt-lab-result').value = optimized;
    document.getElementById('flux-prompt-lab-status').textContent = 'Lokal strukturiert · keine API-Anfrage.';
    document.getElementById('flux-prompt-lab-notes').textContent = 'Motiv, Licht, Komposition und Qualitätsmerkmale wurden in eine klarere Reihenfolge gebracht.';
  });
  document.getElementById('flux-prompt-lab-ai')?.addEventListener('click', async event => {
    const original = document.getElementById('flux-prompt-lab-original')?.value.trim() || '';
    if (!original) return toast('Bitte zuerst einen Prompt eingeben.');
    const button = event.currentTarget;
    const status = document.getElementById('flux-prompt-lab-status');
    button.disabled = true;
    button.textContent = `${aiProviderDisplayName()} arbeitet …`;
    status.textContent = `Prompt wird mit ${aiProviderDisplayName()} veredelt …`;
    try {
      const payload = {
        prompt: original,
        provider: state.aiProvider,
        model: state.aiModel || undefined,
        goal: document.getElementById('flux-prompt-lab-goal')?.value || 'product',
        preserve_subject: document.getElementById('flux-prompt-lab-preserve')?.checked !== false,
        avoid_text: document.getElementById('flux-prompt-lab-avoid-text')?.checked !== false,
      };
      const result = await api('POST', '/api/flux-prompt/improve', payload);
      document.getElementById('flux-prompt-lab-result').value = result.improved_prompt || '';
      document.getElementById('flux-prompt-lab-notes').textContent = result.notes || 'Prompt wurde für FLUX veredelt.';
      status.textContent = `Fertig · ${aiProviderDisplayName(result.provider)}${result.model ? ` · ${result.model}` : ''}`;
    } catch (error) {
      status.textContent = `AI-Veredelung fehlgeschlagen: ${error.message}`;
      toast(`Prompt Lab: ${error.message}`);
    } finally {
      button.disabled = false;
      button.textContent = 'Mit AI veredeln';
    }
  });
  document.getElementById('flux-prompt-lab-apply')?.addEventListener('click', () => {
    const result = document.getElementById('flux-prompt-lab-result')?.value.trim();
    if (!result) return toast('Erzeuge zuerst einen optimierten Prompt.');
    const prompt = document.getElementById('flux-studio-prompt');
    prompt.value = result.slice(0, 2000);
    prompt.dispatchEvent(new Event('input', { bubbles: true }));
    prompt.focus();
    closeFluxPromptLab();
    toast('Optimierter Prompt wurde übernommen.');
  });
}

function setupFluxStudio() {
  const prompt = document.getElementById('flux-studio-prompt');
  if (!prompt) return;
  const updateCount = () => { const count = document.getElementById('flux-studio-prompt-count'); if (count) count.textContent = String(prompt.value.length); };
  prompt.addEventListener('input', () => { updateCount(); refreshFluxPromptQuality(); });
  updateCount();
  refreshFluxPromptQuality();
  document.getElementById('flux-studio-optimize-prompt')?.addEventListener('click', openFluxPromptLab);
  setupFluxPromptLab();
  document.querySelectorAll('[data-flux-profile]').forEach(button => button.addEventListener('click', () => applyFluxProfile(button.dataset.fluxProfile)));
  document.querySelectorAll('[data-flux-variants]').forEach(button => button.addEventListener('click', () => setFluxVariantCount(button.dataset.fluxVariants)));
  document.getElementById('flux-studio-format')?.addEventListener('change', () => applyFluxProfile('custom'));
  document.getElementById('flux-studio-steps')?.addEventListener('change', () => applyFluxProfile('custom'));
  let savedFluxProfile = 'balanced'; try { savedFluxProfile = localStorage.getItem('vendoo-flux-profile') || savedFluxProfile; } catch {}
  let savedFluxVariants = 1; try { savedFluxVariants = Number(localStorage.getItem('vendoo-flux-variants') || 1); } catch {}
  applyFluxProfile(savedFluxProfile, { persist: false });
  setFluxVariantCount(savedFluxVariants, { persist: false });
  renderFluxStudioPreview(null);
  const initialVariantResults = document.getElementById('flux-studio-variant-results');
  if (initialVariantResults) { initialVariantResults.innerHTML = ''; initialVariantResults.classList.add('hidden'); }
  document.querySelectorAll('[data-flux-template]').forEach(button => button.addEventListener('click', () => {
    const template = FLUX_PROMPT_TEMPLATES[button.dataset.fluxTemplate];
    if (!template) return;
    prompt.value = prompt.value.trim() ? `${prompt.value.trim()}, ${template}` : template;
    updateCount(); refreshFluxPromptQuality(); prompt.focus();
  }));
  document.getElementById('flux-studio-random-seed')?.addEventListener('click', () => {
    document.getElementById('flux-studio-seed').value = String(Math.floor(Math.random() * 2147483647));
  });
  document.getElementById('flux-studio-generate')?.addEventListener('click', () => runFluxStudioGeneration());
  document.getElementById('flux-studio-reset')?.addEventListener('click', () => {
    if (state.fluxStudioBusy && !window.confirm('Ein FLUX-Auftrag läuft noch. Nur den sichtbaren Arbeitsbereich zurücksetzen? Der Auftrag läuft im Job-Center weiter.')) return;
    resetFluxStudioWorkspace({ clearSavedDefaults: true, announce: true });
  });
  document.getElementById('flux-studio-refresh-engine')?.addEventListener('click', async () => { await refreshFluxStudioEngineStatus(); await refreshFluxRuntimeInfo(); });
  document.getElementById('flux-runtime-refresh')?.addEventListener('click', () => refreshFluxRuntimeInfo());
  document.getElementById('flux-job-refresh')?.addEventListener('click', () => loadFluxJobs());
  document.getElementById('flux-job-filter')?.addEventListener('change', () => { state.fluxJobPage = 1; state.fluxJobSelected.clear(); loadFluxJobs(); });
  document.getElementById('flux-job-page-size')?.addEventListener('change', event => { state.fluxJobPageSize = Number(event.target.value) || 10; state.fluxJobPage = 1; loadFluxJobs(); });
  document.getElementById('flux-job-view-active')?.addEventListener('click', () => { state.fluxJobArchiveMode = false; state.fluxJobPage = 1; state.fluxJobSelected.clear(); loadFluxJobs(); });
  document.getElementById('flux-job-view-archive')?.addEventListener('click', () => { state.fluxJobArchiveMode = true; state.fluxJobPage = 1; state.fluxJobSelected.clear(); loadFluxJobs(); });
  document.getElementById('flux-job-prev')?.addEventListener('click', () => { if (state.fluxJobPage > 1) { state.fluxJobPage -= 1; loadFluxJobs(); } });
  document.getElementById('flux-job-next')?.addEventListener('click', () => { if (state.fluxJobPage < Number(state.fluxJobPagination.total_pages || 1)) { state.fluxJobPage += 1; loadFluxJobs(); } });
  document.getElementById('flux-job-select-page')?.addEventListener('change', event => { for (const job of state.fluxJobs) { if (event.target.checked) state.fluxJobSelected.add(job.id); else state.fluxJobSelected.delete(job.id); } renderFluxJobList(); });
  document.getElementById('flux-job-archive-selected')?.addEventListener('click', () => archiveFluxJobs([...state.fluxJobSelected]));
  document.getElementById('flux-job-restore-selected')?.addEventListener('click', () => restoreFluxJobs([...state.fluxJobSelected]));
  document.getElementById('flux-job-delete-selected')?.addEventListener('click', () => deleteArchivedFluxJobs([...state.fluxJobSelected]));
  document.getElementById('flux-job-delete-all-archive')?.addEventListener('click', () => deleteArchivedFluxJobs([], true));
  document.getElementById('flux-job-collapse-all')?.addEventListener('click', () => {
    for (const job of state.fluxJobs) state.fluxJobManuallyToggled.add(job.id);
    state.fluxJobExpanded.clear();
    renderFluxJobList();
  });
  const jobCenterToggle = document.getElementById('flux-job-center-toggle');
  const jobCenterBody = document.getElementById('flux-job-center-body');
  const applyJobCenterState = () => {
    jobCenterBody?.classList.toggle('hidden', state.fluxJobCenterCollapsed);
    if (jobCenterToggle) {
      jobCenterToggle.setAttribute('aria-expanded', String(!state.fluxJobCenterCollapsed));
      jobCenterToggle.textContent = state.fluxJobCenterCollapsed ? 'Aufträge aufklappen' : 'Aufträge einklappen';
    }
  };
  try { state.fluxJobCenterCollapsed = localStorage.getItem('vendoo-flux-job-center-collapsed') === '1'; } catch {}
  applyJobCenterState();
  jobCenterToggle?.addEventListener('click', () => {
    state.fluxJobCenterCollapsed = !state.fluxJobCenterCollapsed;
    try { localStorage.setItem('vendoo-flux-job-center-collapsed', state.fluxJobCenterCollapsed ? '1' : '0'); } catch {}
    applyJobCenterState();
  });
  document.getElementById('flux-job-clear-queue')?.addEventListener('click', async () => {
    if (!window.confirm('Alle noch wartenden FLUX-Varianten aus der Warteschlange entfernen? Laufende Varianten bleiben unberührt.')) return;
    try { const result = await api('DELETE', '/api/flux-prompt/jobs/queue'); toast(`${result.cleared_variants || 0} wartende Variante(n) entfernt.`); await loadFluxJobs(); }
    catch (error) { toast(`Warteschlange konnte nicht geleert werden: ${error.message}`); }
  });
  document.getElementById('flux-studio-refresh-history')?.addEventListener('click', () => loadFluxStudioHistory());
  document.getElementById('flux-history-page-size')?.addEventListener('change', event => { state.fluxHistoryPageSize = Number(event.target.value) || 5; state.fluxHistoryPage = 1; loadFluxStudioHistory(); });
  document.getElementById('flux-history-prev')?.addEventListener('click', () => { if (state.fluxHistoryPage > 1) { state.fluxHistoryPage -= 1; loadFluxStudioHistory(); } });
  document.getElementById('flux-history-next')?.addEventListener('click', () => { if (state.fluxHistoryPage < Number(state.fluxHistoryPagination.total_pages || 1)) { state.fluxHistoryPage += 1; loadFluxStudioHistory(); } });
  document.getElementById('flux-studio-favorites-only')?.addEventListener('change', () => { state.fluxHistoryPage = 1; loadFluxStudioHistory(); });
  document.getElementById('flux-studio-edit')?.addEventListener('click', () => openPhotoEditor(0, 'flux'));
  document.getElementById('flux-studio-similar')?.addEventListener('click', () => createSimilarFluxJob());
  document.getElementById('flux-studio-favorite')?.addEventListener('click', () => toggleFluxFavorite());
  document.getElementById('flux-studio-use-in-generator')?.addEventListener('click', useFluxImageInGenerator);
  document.getElementById('flux-studio-delete')?.addEventListener('click', () => deleteFluxStudioImage());
  document.getElementById('open-flux-studio-page-btn')?.addEventListener('click', () => switchToTab('flux-studio'));
  document.getElementById('flux-studio-start-engine')?.addEventListener('click', async () => {
    const button = document.getElementById('flux-studio-start-engine');
    if (button) button.disabled = true;
    try {
      const profile = document.getElementById('setting-flux-runtime-profile')?.value || 'auto';
      const result = await api('POST', '/api/local-image/start', { profile });
      toast(result.message || 'FLUX wird gestartet.');
      await waitForFluxEngineReady();
      setFluxStudioStatus('FLUX ist aktiv und bereit.', 'success');
      await refreshFluxRuntimeInfo();
      toast('FLUX ist aktiv und bereit.');
    } catch (error) {
      setFluxStudioStatus(`FLUX-Start fehlgeschlagen: ${error.message}`, 'error');
      toast(`FLUX-Start fehlgeschlagen: ${error.message}`);
    } finally {
      if (button) button.disabled = false;
    }
  });
  document.getElementById('flux-studio-stop-engine')?.addEventListener('click', async () => {
    try { const result = await api('POST', '/api/local-image/stop', {}); toast(result.message || 'FLUX wird gestoppt.'); setTimeout(refreshFluxStudioEngineStatus, 1200); }
    catch (error) { toast(`FLUX-Stopp fehlgeschlagen: ${error.message}`); }
  });
  refreshFluxStudioEngineStatus();
  loadFluxStudioHistory();
  loadFluxJobs();
  refreshFluxRuntimeInfo();
  const poller = window.setInterval(() => {
    const page = document.getElementById('flux-studio');
    if (page?.classList.contains('active')) {
      loadFluxJobs({ silent: true });
      refreshFluxRuntimeInfo({ silent: true });
    }
  }, 5000);
  window.addEventListener('beforeunload', () => window.clearInterval(poller), { once: true });
}


function updateAiModeControls(scope = 'generator') {
  const promptButton = document.getElementById(`${scope}-ai-model-prompt-btn`);
  if (promptButton) {
    promptButton.disabled = false;
    promptButton.title = 'Optionale Zusatzanweisung';
  }
}

async function runGeneratorAiModelPhotos() {
  if (!state.photos.length) return toast('Bitte zuerst Produktfotos hochladen');
  const button = document.getElementById('generator-ai-model-btn');
  button.disabled = true;
  setAiModelStatus('generator-ai-model-status', 'Sicherheitsprüfung läuft …', 'working');
  try {
    const draft = getAiPromptDraft('generator');
    const payload = { ...getGeneratorAiModelPayload(), custom_prompt: draft.customPrompt || '', preserve_logos: draft.preserveLogos ? '1' : '0' };
    const precheck = await api('POST', '/api/ai-model-photos/precheck', payload);
    if (!precheck.allowed) throw new Error(precheck.reason || 'AI-Model-Fotos blockiert');
    setAiModelStatus('generator-ai-model-status', `Sicherheitsprüfung bestanden. Job wird eingereiht (${precheck.mode}, ${precheck.variants} Bild/Bilder) …`, 'working');
    const queued = await api('POST', '/api/ai-model-photos/jobs', payload);
    const finalJob = await pollAiModelJob(queued.job_id, (job) => {
      setAiModelJobProgress('generator-ai-model-status', job);
    });
    if (finalJob.status !== 'completed') throw new Error(finalJob.error_message || 'AI-Model-Fotos konnten nicht erzeugt werden.');
    state.aiModelPhotos = finalJob.photos || [];
    renderAiModelGrid('generator', state.aiModelPhotos);
    const providerLabel = state.aiModelPhotos[0]?.provider_model || state.aiModelPhotos[0]?.provider || 'AI';
    setAiModelStatus('generator-ai-model-status', `${state.aiModelPhotos.length} sichere AI-Bildvariante(n) erzeugt · ${providerLabel}`, 'success');
  } catch (err) {
    setAiModelStatus('generator-ai-model-status', err.message || 'AI-Model-Fotos konnten nicht erzeugt werden.', 'danger');
    toast(err.message || 'AI-Model-Fotos fehlgeschlagen');
  } finally {
    button.disabled = false;
  }
}

async function runDetailAiModelPhotos() {
  if (!detailEditorState?.photos?.length) return toast('Bitte zuerst Produktfotos hinzufügen');
  const button = document.getElementById('detail-ai-model-btn');
  if (button) button.disabled = true;
  setAiModelStatus('detail-ai-model-status', 'Sicherheitsprüfung läuft …', 'working');
  try {
    const draft = getAiPromptDraft('detail');
    const payload = {
      listing_id: detailEditorState.listing.id,
      photos: [...detailEditorState.photos],
      platform: document.getElementById('detail-platform')?.value || detailEditorState.listing.platform,
      title: document.getElementById('detail-title')?.value || detailEditorState.listing.title || '',
      category: detailEditorState.categoryPicker?.getValue?.() || detailEditorState.listing.category || '',
      brand: document.getElementById('detail-brand')?.value || detailEditorState.listing.brand || '',
      color: document.getElementById('detail-color')?.value || detailEditorState.listing.color || '',
      size: document.getElementById('detail-size')?.value || detailEditorState.listing.size || '',
      condition: document.getElementById('detail-condition')?.value || detailEditorState.listing.condition || '',
      seller_notes: document.getElementById('detail-seller-notes')?.value || detailEditorState.listing.seller_notes || '',
      mode: document.getElementById('detail-ai-model-mode')?.value || 'flatlay',
      preset: document.getElementById('detail-ai-model-preset')?.value || 'mixed',
      variants: parseInt(document.getElementById('detail-ai-model-variants')?.value || '1', 10),
      custom_prompt: draft.customPrompt || '',
      preserve_logos: draft.preserveLogos ? '1' : '0',
    };
    const precheck = await api('POST', '/api/ai-model-photos/precheck', payload);
    if (!precheck.allowed) throw new Error(precheck.reason || 'AI-Model-Fotos blockiert');
    setAiModelStatus('detail-ai-model-status', `Sicherheitsprüfung bestanden. Job wird eingereiht (${precheck.mode}, ${precheck.variants} Bild/Bilder) …`, 'working');
    const queued = await api('POST', '/api/ai-model-photos/jobs', payload);
    const finalJob = await pollAiModelJob(queued.job_id, (job) => {
      setAiModelJobProgress('detail-ai-model-status', job);
    });
    if (finalJob.status !== 'completed') throw new Error(finalJob.error_message || 'AI-Model-Fotos konnten nicht erzeugt werden.');
    detailEditorState.aiModelPhotos = finalJob.photos || [];
    renderAiModelGrid('detail', detailEditorState.aiModelPhotos);
    const providerLabel = detailEditorState.aiModelPhotos[0]?.provider_model || detailEditorState.aiModelPhotos[0]?.provider || 'AI';
    setAiModelStatus('detail-ai-model-status', `${detailEditorState.aiModelPhotos.length} sichere AI-Bildvariante(n) erzeugt · ${providerLabel}`, 'success');
    loadDetailAiModelGenerations(detailEditorState.listing.id);
  } catch (err) {
    setAiModelStatus('detail-ai-model-status', err.message || 'AI-Model-Fotos konnten nicht erzeugt werden.', 'danger');
    toast(err.message || 'AI-Model-Fotos fehlgeschlagen');
  } finally {
    if (button) button.disabled = false;
  }
}

async function loadDetailAiModelPhotos(listingId) {
  try {
    const items = await api('GET', `/api/listings/${listingId}/ai-model-photos`);
    detailEditorState.aiModelPhotos = items || [];
    renderAiModelGrid('detail', detailEditorState.aiModelPhotos);
    if (detailEditorState.aiModelPhotos.length) {
      setAiModelStatus('detail-ai-model-status', `${detailEditorState.aiModelPhotos.length} vorhandene AI-Bilder geladen.`, 'neutral');
    }
  } catch {}
}

function renderDetailAiModelGenerations(generations = []) {
  const wrap = document.getElementById('detail-ai-model-history');
  if (!wrap) return;
  if (!generations.length) {
    wrap.innerHTML = '<div class="ai-generation-empty">Noch keine AI-Bildhistorie vorhanden.</div>';
    return;
  }
  wrap.innerHTML = generations.slice(0, 6).map(gen => `
    <article class="ai-generation-card">
      <header><strong>${escapeHtml((gen.mode || 'model').replace('_', ' '))}</strong><span>${escapeHtml((gen.preset || 'mixed').toUpperCase())} · ${gen.variants || 0} Bild/Bilder</span></header>
      <div class="ai-generation-mini-grid">
        ${(gen.photos || []).slice(0, 3).map(photo => `<button type="button" class="ai-generation-thumb" data-ai-model-add-one="${escapeHtml(photo.image_path)}" data-ai-model-scope="detail"><img src="${escapeHtml(photoUrl(photo.image_path))}" alt="AI-Historie"></button>`).join('')}
      </div>
    </article>
  `).join('');
  wrap.querySelectorAll('[data-ai-model-add-one]').forEach(button => button.addEventListener('click', () => {
    const file = button.dataset.aiModelAddOne;
    if (!detailEditorState.photos.includes(file)) detailEditorState.photos.push(file);
    renderDetailPhotos();
    toast('AI-Bild aus Historie hinzugefügt');
  }));
}

async function loadDetailAiModelGenerations(listingId) {
  try {
    const items = await api('GET', `/api/listings/${listingId}/ai-model-generations`);
    detailEditorState.aiModelGenerations = items || [];
    renderDetailAiModelGenerations(detailEditorState.aiModelGenerations);
  } catch {}
}



// --- Tab titles for topbar ---
const TAB_TITLES = {
  today: 'Studio Flow',
  dashboard: 'Dashboard',
  generator: 'Neuer Artikel',
  templates: 'Vorlagen',
  history: 'Artikel',
  'quality-center': 'Qualitätscenter',
  publish: 'Publish',
  trash: 'Papierkorb',
  inventory: 'Lager',
  fees: 'Gebühren',
  admin: 'Admin / Benutzer & System',
  'flux-studio': 'FLUX Studio',
  'media-library': 'Bildergalerie',
  'image-factory': 'Bildproduktion',
  extensions: 'Extensions',
  settings: 'Einstellungen',
};

const TAB_KICKERS = {
  today: 'Arbeitsplatz',
  dashboard: 'Analytics',
  generator: 'Generator',
  templates: 'Inhalte',
  history: 'Übersicht',
  'quality-center': 'Artikelprüfung',
  publish: 'Control Center',
  trash: 'Wiederherstellung',
  inventory: 'Bestand',
  fees: 'Kalkulation',
  admin: 'Verwaltung',
  'flux-studio': 'Bildgenerator',
  'media-library': 'Medienverwaltung',
  'image-factory': 'Batch Image Factory',
  extensions: 'Browser-Integration',
  settings: 'Konfiguration',
};

function setMobileSidebarOpen(open) {
  const sidebar = document.getElementById('sidebar');
  const overlay = document.getElementById('sidebar-overlay');
  const toggle = document.getElementById('sidebar-toggle');
  if (!sidebar || !overlay) return;
  sidebar.classList.toggle('open', !!open);
  overlay.classList.toggle('show', !!open);
  document.body.classList.toggle('mobile-nav-open', !!open);
  toggle?.setAttribute('aria-expanded', open ? 'true' : 'false');
  sidebar.setAttribute('aria-hidden', (!open && window.matchMedia('(max-width: 768px)').matches) ? 'true' : 'false');
  if (open) {
    window.setTimeout(() => sidebar.querySelector('.nav-item.active, .nav-item')?.focus(), 60);
  } else {
    toggle?.focus({ preventScroll: true });
  }
}

function closeMobileSidebar({ restoreFocus = false } = {}) {
  const toggle = document.getElementById('sidebar-toggle');
  const sidebar = document.getElementById('sidebar');
  const overlay = document.getElementById('sidebar-overlay');
  sidebar?.classList.remove('open');
  overlay?.classList.remove('show');
  document.body.classList.remove('mobile-nav-open');
  toggle?.setAttribute('aria-expanded', 'false');
  if (sidebar && window.matchMedia('(max-width: 768px)').matches) sidebar.setAttribute('aria-hidden', 'true');
  if (restoreFocus) toggle?.focus({ preventScroll: true });
}

function switchToTab(tabId, adminTab) {
  if (tabId !== 'history' && listingEditLock.listingId) releaseListingLock({ quiet: true });
  document.querySelectorAll('.nav-item').forEach(b => b.classList.remove('active'));
  document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
  const section = document.getElementById(tabId);
  if (section) section.classList.add('active');
  window.requestAnimationFrame(applyResponsiveContract);

  // Highlight the correct sidebar item
  const selector = adminTab
    ? `.nav-item[data-tab="${tabId}"][data-admin-tab="${adminTab}"]`
    : `.nav-item[data-tab="${tabId}"]:not([data-admin-tab])`;
  const activeBtn = document.querySelector(selector)
    || document.querySelector(`.nav-item[data-tab="${tabId}"]`);
  if (activeBtn) activeBtn.classList.add('active');

  // Update topbar title
  const titleEl = document.getElementById('topbar-title');
  if (titleEl) titleEl.textContent = TAB_TITLES[tabId] || tabId;
  const kickerEl = document.getElementById('topbar-kicker');
  if (kickerEl) kickerEl.textContent = TAB_KICKERS[tabId] || '';

  // Switch admin sub-tab if specified
  if (tabId === 'admin' && adminTab) {
    switchAdminTab(adminTab);
    if (adminTab === 'users') loadAdminUsers();
    if (adminTab === 'roles') loadRoleMatrix();
    if (adminTab === 'sessions') loadAdminSessions();
    if (adminTab === 'smtp') loadSmtpSettings();
    if (adminTab === 'system') { loadSystemInfo(); loadOperationsCenter(); }
    if (adminTab === 'modules') globalThis.VendooModuleManager?.load?.();
    if (adminTab === 'security') loadSecurityCenter();
    if (adminTab === 'audit') loadAdminAudit();
  }

  // Load data for the tab
  if (tabId === 'today') loadToday();
  if (tabId === 'dashboard') loadAnalyticsDashboard();
  if (tabId === 'history') { setHistoryDetailMode(false); loadHistory(); }
  if (tabId === 'quality-center') loadQualityCenter();
  if (tabId === 'publish') loadPublishList();
  if (tabId === 'trash') loadTrash();
  if (tabId === 'templates') renderTemplateList();
  if (tabId === 'inventory') loadInventory();
  if (tabId === 'fees') updateFeeCalculator();
  if (tabId === 'flux-studio') { refreshFluxStudioEngineStatus(); loadFluxStudioHistory(); loadFluxJobs({ silent: true }); }
  if (tabId === 'media-library') loadMediaLibrary();
  if (tabId === 'image-factory') loadImageFactory();
  if (tabId === 'extensions') loadExtensions();

  // Close mobile sidebar
  closeMobileSidebar();
}

// --- Tabs ---
function setupTabs() {
  document.querySelectorAll('.nav-item').forEach(btn => {
    btn.addEventListener('click', () => {
      switchToTab(btn.dataset.tab, btn.dataset.adminTab);
    });
  });

  // Mobile sidebar toggle
  document.getElementById('sidebar-toggle')?.addEventListener('click', event => {
    event.preventDefault();
    event.stopPropagation();
    const open = !document.getElementById('sidebar')?.classList.contains('open');
    setMobileSidebarOpen(open);
  });
  document.getElementById('sidebar-close')?.addEventListener('click', () => closeMobileSidebar({ restoreFocus: true }));
  document.getElementById('sidebar-overlay')?.addEventListener('click', () => closeMobileSidebar({ restoreFocus: true }));
  document.addEventListener('keydown', event => {
    if (event.key === 'Escape' && document.getElementById('sidebar')?.classList.contains('open')) {
      closeMobileSidebar({ restoreFocus: true });
    }
  });
  window.addEventListener('resize', debounce(() => {
    const mobile = window.matchMedia('(max-width: 768px)').matches;
    const sidebar = document.getElementById('sidebar');
    if (!mobile) {
      closeMobileSidebar();
      sidebar?.setAttribute('aria-hidden', 'false');
    } else if (!sidebar?.classList.contains('open')) {
      sidebar?.setAttribute('aria-hidden', 'true');
    }
  }, 120));
  if (window.matchMedia('(max-width: 768px)').matches) document.getElementById('sidebar')?.setAttribute('aria-hidden', 'true');

  // Topbar "Neuer Artikel" button
  document.getElementById('topbar-new-btn')?.addEventListener('click', () => {
    switchToTab('generator');
  });

  // Topbar search button — focus search in history
  document.getElementById('topbar-search-btn')?.addEventListener('click', () => {
    switchToTab('history');
    setTimeout(() => document.getElementById('search-input')?.focus(), 100);
  });

  document.addEventListener('keydown', event => {
    if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {
      event.preventDefault();
      switchToTab('history');
      setTimeout(() => document.getElementById('search-input')?.focus(), 100);
    }
  });

  document.addEventListener('click', event => {
    const target = event.target.closest('[data-tab-target]');
    if (target) switchToTab(target.dataset.tabTarget);
  });
}

// --- Platforms ---
function renderPlatformButtons() {
  const c = document.getElementById('platform-buttons');
  c.innerHTML = '';
  for (const p of state.platforms) {
    const btn = document.createElement('button');
    btn.className = 'toggle-btn' + (p.id === state.platform ? ' active' : '');
    btn.textContent = p.name;
    btn.addEventListener('click', () => {
      c.querySelectorAll('.toggle-btn').forEach(b => b.classList.remove('active'));
      btn.classList.add('active');
      state.platform = p.id;
      updateGeneratorReadiness();
      refreshGeneratorPlatformPreview();
      if (!document.getElementById('meta-section').classList.contains('hidden') && metaCategoryPicker) {
        const currentVal = metaCategoryPicker.getValue();
        metaCategoryPicker = createCategoryPicker('meta-category-picker', p.id, null);
        if (currentVal) {
          const name = currentVal.split(' > ').pop();
          api('GET', `/api/categories/${p.id}/match?q=${encodeURIComponent(name)}`).then(matches => {
            if (matches.length && matches[0].score >= 50) metaCategoryPicker.setValue(matches[0].pathStr);
          }).catch(() => {});
        }
      }
    });
    c.appendChild(btn);
  }
}

// --- Providers ---
function renderProviderButtons() {
  const c = document.getElementById('provider-buttons');
  c.innerHTML = '';
  for (const p of state.providers) {
    const btn = document.createElement('button');
    btn.className = 'toggle-btn' + (p.id === state.aiProvider ? ' active' : '');
    btn.textContent = p.name;
    btn.addEventListener('click', () => {
      c.querySelectorAll('.toggle-btn').forEach(b => b.classList.remove('active'));
      btn.classList.add('active');
      state.aiProvider = p.id;
      state.aiModel = '';
      updateModelDropdown();
    });
    c.appendChild(btn);
  }
  updateModelDropdown();
}

function updateModelDropdown() {
  const wrap = document.getElementById('model-select-wrap');
  const sel = document.getElementById('model-select');
  const prov = state.providers.find(p => p.id === state.aiProvider);
  if (prov?.hasModels && prov.models?.length) {
    const isOpenRouter = prov.id === 'openrouter';
    const options = isOpenRouter
      ? `<option value="">Auto (Gemini zuerst, dann Free)</option>` + prov.models.map(m => `<option value="${m.id}">${m.name}</option>`).join('')
      : prov.models.map(m => `<option value="${m.id}">${m.name}${m.free ? ' *' : ''}</option>`).join('');
    sel.innerHTML = options;
    state.aiModel = isOpenRouter ? '' : prov.models[0].id;
    sel.onchange = () => { state.aiModel = sel.value; };
    wrap.classList.remove('hidden');
  } else {
    state.aiModel = '';
    wrap.classList.add('hidden');
  }
}

// --- Upload ---
function setupUpload() {
  const dz = document.getElementById('dropzone');
  const fi = document.getElementById('file-input');
  dz.addEventListener('click', () => fi.click());
  dz.addEventListener('dragover', e => { e.preventDefault(); dz.classList.add('dragover'); });
  dz.addEventListener('dragleave', () => dz.classList.remove('dragover'));
  dz.addEventListener('drop', e => { e.preventDefault(); dz.classList.remove('dragover'); handleFiles(e.dataTransfer.files); });
  fi.addEventListener('change', () => { handleFiles(fi.files); fi.value = ''; });
  enablePhotoDragSort();
}

async function handleFiles(files) {
  const available = Math.max(0, 10 - state.photos.length);
  const imageFiles = Array.from(files).filter(file => file.type.startsWith('image/'));
  if (!available) { toast('Maximal 10 Fotos pro Listing'); return; }
  const accepted = imageFiles.slice(0, available);
  if (imageFiles.length > available) toast(`Es wurden nur ${available} weitere Fotos übernommen`);
  const fd = new FormData();
  fd.append('platform', state.platform || 'allgemein');
  for (const file of accepted) fd.append('photos', file);
  if (!fd.has('photos')) return;
  try {
    const response = await fetch(`/api/upload?platform=${encodeURIComponent(state.platform || 'allgemein')}`, {
      method: 'POST',
      body: fd,
      headers: { 'X-CSRF-Token': getCsrf() },
      credentials: 'same-origin',
    });
    const data = await response.json().catch(() => ({}));
    if (!response.ok) throw new Error(data.error || `Upload fehlgeschlagen (${response.status})`);
    state.photos.push(...(data.filenames || []));
    renderPhotoPreviews();
    updateGenerateButton();
  } catch (err) { toast('Upload fehlgeschlagen: ' + err.message); }
}

function renderPhotoPreviews() {
  renderPhotoPreview();
}

function renderPhotoPreview() {
  const c = document.getElementById('photo-preview');
  c.innerHTML = '';
  for (let i = 0; i < state.photos.length; i++) {
    const div = document.createElement('div');
    div.className = 'thumb';
    div.draggable = true;
    div.dataset.index = String(i + 1);
    const safeUrl = photoUrl(state.photos[i]);
    div.innerHTML = `<img src="${safeUrl}" alt="Foto ${i + 1}"><button class="remove-photo" aria-label="Foto entfernen">&times;</button><button class="edit-photo" title="Im Advanced Editor bearbeiten" aria-label="Im Advanced Editor bearbeiten">✎</button>`;
    div.querySelector('img').addEventListener('click', e => {
      e.stopPropagation();
      if (i > 0) {
        const selected = state.photos.splice(i, 1)[0];
        state.photos.unshift(selected);
        renderPhotoPreview();
        toast('Coverfoto geändert');
      }
    });
    div.querySelector('.remove-photo').addEventListener('click', e => {
      e.stopPropagation();
      state.photos.splice(i, 1);
      renderPhotoPreview();
      updateGenerateButton();
    });
    div.querySelector('.edit-photo').addEventListener('click', e => {
      e.stopPropagation();
      openPhotoEditor(i);
    });
    c.appendChild(div);
  }
  document.getElementById('image-tools').classList.toggle('hidden', state.photos.length === 0);
  updateGeneratorPhotoStage();
}

function updateGenerateButton() {
  const ready = state.photos.length > 0;
  document.getElementById('generate-btn').disabled = !ready;
  if (!ready) {
    state.aiModelPhotos = [];
    renderAiModelGrid('generator', []);
    setAiModelStatus('generator-ai-model-status', 'Wartet auf Produktfotos.', 'neutral');
  } else if (!state.aiModelPhotos.length) {
    setAiModelStatus('generator-ai-model-status', 'Bereit für sichere AI-Model-Fotos.', 'neutral');
  }
  updateGeneratorReadiness();
}

// --- Image tools ---
function setupImageTools() {
  document.getElementById('advanced-image-editor-btn')?.addEventListener('click', () => {
    if (!state.photos.length) return toast('Bitte zuerst mindestens ein Foto hochladen.');
    openPhotoEditor(0, 'generator');
  });
}

// --- Generate ---
function setupGenerate() {
  document.getElementById('generate-btn').addEventListener('click', generateListing);
  document.getElementById('reset-generator-btn').addEventListener('click', resetGenerator);
  document.getElementById('language-select').addEventListener('change', e => { state.language = e.target.value; updateGeneratorReadiness(); });
  document.getElementById('variants-select').addEventListener('change', e => { state.variants = parseInt(e.target.value); });
  document.getElementById('template-select').addEventListener('change', e => applyGeneratorTemplate(e.target.value));
  document.getElementById('generator-ai-model-btn')?.addEventListener('click', runGeneratorAiModelPhotos);
  document.getElementById('generator-ai-model-prompt-btn')?.addEventListener('click', () => openAiPromptModal('generator'));
  document.getElementById('generator-ai-model-mode')?.addEventListener('change', () => updateAiModeControls('generator'));
  updateAiModeControls('generator');
}

let generatorRenderSequence = 0;

function getGeneratorListingForHtml() {
  const tags = (document.getElementById('result-tags')?.value || '').split(',').map(tag => tag.trim()).filter(Boolean);
  return {
    platform: state.platform,
    title: document.getElementById('result-title')?.value || '',
    description: editors.result ? editors.result.getText().trim() : '',
    description_html: getEditorHtml(editors.result),
    tags,
    brand: document.getElementById('meta-brand')?.value || null,
    category: metaCategoryPicker?.getValue?.() || null,
    size: document.getElementById('meta-size')?.value || null,
    color: document.getElementById('meta-color')?.value || null,
    condition: document.getElementById('meta-condition')?.value || null,
  };
}

const refreshGeneratorPlatformPreview = debounce(async () => {
  if (!state.currentListing || document.getElementById('result-section')?.classList.contains('hidden')) return;
  const sequence = ++generatorRenderSequence;
  setHtmlSyncStatus('generator-html-sync-status', 'Wird aktualisiert …', 'working');
  try {
    const listing = getGeneratorListingForHtml();
    const data = await api('POST', '/api/html-render', { platform: state.platform, listing });
    if (sequence !== generatorRenderSequence) return;
    state.currentHtml = data.html || listing.description_html;
    setSandboxPreview(document.getElementById('html-preview'), state.currentHtml);
    setHtmlSyncStatus('generator-html-sync-status', 'Synchron', 'ok');
  } catch (err) {
    if (sequence !== generatorRenderSequence) return;
    setSandboxPreview(document.getElementById('html-preview'), getEditorHtml(editors.result));
    setHtmlSyncStatus('generator-html-sync-status', 'Vorschau lokal', 'warning');
  }
}, 140);

function setupGeneratorHtmlSync() {
  const editor = editors.result;
  const source = document.getElementById('html-source');
  if (!editor || !source) return;

  editor.on('text-change', () => {
    if (htmlSyncState.generatorLock) return;
    source.value = getEditorHtml(editor);
    updateGeneratorResultMetrics();
    refreshGeneratorPlatformPreview();
  });

  const applySourceToEditor = debounce(() => {
    htmlSyncState.generatorLock = true;
    const safe = sanitizeRichHtmlClient(source.value);
    setEditorHtml(editor, safe);
    htmlSyncState.generatorLock = false;
    source.value = safe;
    updateGeneratorResultMetrics();
    refreshGeneratorPlatformPreview();
  }, 100);

  source.addEventListener('input', () => {
    setHtmlSyncStatus('generator-html-sync-status', 'Code wird übernommen …', 'working');
    setSandboxPreview(document.getElementById('html-preview'), sanitizeRichHtmlClient(source.value));
    applySourceToEditor();
  });
}


function setHtmlView(scope, view) {
  document.querySelectorAll(`.html-view-tab[data-html-scope="${scope}"]`).forEach(button => {
    const active = button.dataset.htmlView === view;
    button.classList.toggle('is-active', active);
    button.setAttribute('aria-selected', active ? 'true' : 'false');
  });
  const root = scope === 'generator' ? document.getElementById('html-preview-wrap') : document.querySelector('.detail-platform-workbench');
  root?.querySelectorAll('.html-switch-view').forEach(panel => panel.classList.toggle('is-active', panel.dataset.htmlPanel === view));
}

function bindHtmlViewTabs(scope) {
  document.querySelectorAll(`.html-view-tab[data-html-scope="${scope}"]`).forEach(button => {
    if (button.dataset.bound === '1') return;
    button.dataset.bound = '1';
    button.addEventListener('click', () => setHtmlView(scope, button.dataset.htmlView));
  });
}

function setupHtmlViewSwitches() {
  bindHtmlViewTabs('generator');
  setHtmlView('generator', 'preview');
}

function setupMobileUpload() {
  document.getElementById('generator-mobile-upload')?.addEventListener('click', () => openMobileUpload('generator'));
  document.getElementById('mobile-upload-close')?.addEventListener('click', closeMobileUpload);
  document.getElementById('mobile-upload-done')?.addEventListener('click', closeMobileUpload);
  document.getElementById('mobile-upload-new-session')?.addEventListener('click', () => openMobileUpload(mobileUploadState.target?.context || 'generator', mobileUploadState.target?.listingId, true));
  document.getElementById('mobile-upload-copy-link')?.addEventListener('click', async () => {
    const input = document.getElementById('mobile-upload-link');
    if (!input?.value) return;
    await navigator.clipboard.writeText(input.value);
    toast('Upload-Link kopiert');
  });
  document.getElementById('mobile-upload-overlay')?.addEventListener('click', event => {
    if (event.target.id === 'mobile-upload-overlay') closeMobileUpload();
  });
}

async function openMobileUpload(context = 'generator', listingId = null, renew = false) {
  if (renew && mobileUploadState.session?.id) {
    try { await api('DELETE', `/api/mobile-upload-sessions/${mobileUploadState.session.id}`); } catch {}
  }
  stopMobileUploadPolling();
  mobileUploadState.target = { context, listingId: listingId || null };
  mobileUploadState.imported = new Set(context === 'listing' ? (detailEditorState?.photos || []) : state.photos);
  const overlay = document.getElementById('mobile-upload-overlay');
  overlay?.classList.remove('hidden');
  overlay?.setAttribute('aria-hidden', 'false');
  setMobileUploadDialogState('Link wird vorbereitet …', 'working');
  try {
    const session = await api('POST', '/api/mobile-upload-sessions', { context, listing_id: listingId || null });
    mobileUploadState.session = session;
    const link = document.getElementById('mobile-upload-link');
    if (link) link.value = session.url;
    const qrCanvas = document.getElementById('mobile-upload-qr');
    if (typeof QRious !== 'undefined' && qrCanvas) new QRious({ element: qrCanvas, value: session.url, size: 240, level: 'M', foreground: '#171714', background: '#ffffff' });
    const expiry = new Date(session.expires_at);
    document.getElementById('mobile-upload-expiry').textContent = `Gültig bis ${expiry.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}`;
    const localOnly = /localhost|127\.0\.0\.1|0\.0\.0\.0/i.test(session.url);
    const networkNote = document.getElementById('mobile-upload-network-note');
    if (networkNote) {
      networkNote.textContent = localOnly
        ? 'Keine LAN-IP erkannt. Prüfe Windows-Firewall und Netzwerkadapter oder setze PUBLIC_BASE_URL manuell.'
        : `Handy und Vendoo-PC müssen im selben Netzwerk sein. Erkannte Adresse: ${session.base_url || session.url.split('/mobile-upload/')[0]}`;
    }
    setMobileUploadDialogState(localOnly ? 'LAN-Adresse prüfen' : 'Bereit zum Scannen', localOnly ? 'warning' : 'ready');
    updateMobileUploadCount([]);
    mobileUploadState.pollTimer = window.setInterval(pollMobileUploadSession, 1500);
  } catch (error) {
    setMobileUploadDialogState(error.message, 'error');
  }
}

function setMobileUploadDialogState(text, tone) {
  const element = document.getElementById('mobile-upload-session-state');
  if (!element) return;
  element.lastChild.textContent = text;
  element.dataset.tone = tone;
}

function updateMobileUploadCount(files) {
  const count = document.getElementById('mobile-upload-file-count');
  if (count) count.textContent = `${files.length} ${files.length === 1 ? 'Bild' : 'Bilder'} empfangen`;
}

async function pollMobileUploadSession() {
  const session = mobileUploadState.session;
  if (!session?.id) return;
  try {
    const data = await api('GET', `/api/mobile-upload-sessions/${session.id}`);
    const files = Array.isArray(data.files) ? data.files : [];
    const fresh = files.filter(file => !mobileUploadState.imported.has(file));
    fresh.forEach(file => mobileUploadState.imported.add(file));
    if (fresh.length) {
      if (mobileUploadState.target?.context === 'listing' && detailEditorState) {
        detailEditorState.photos = [...new Set([...detailEditorState.photos, ...fresh])].slice(0, 10);
        renderDetailPhotos();
      } else {
        state.photos = [...new Set([...state.photos, ...fresh])].slice(0, 10);
        renderPhotoPreviews();
        updateGeneratorReadiness();
      }
      toast(`${fresh.length} ${fresh.length === 1 ? 'Bild empfangen' : 'Bilder empfangen'}`);
    }
    updateMobileUploadCount(files);
    if (files.length) setMobileUploadDialogState('Bilder werden live übertragen', 'ready');
  } catch (error) {
    setMobileUploadDialogState(error.message, 'error');
    stopMobileUploadPolling();
  }
}

function stopMobileUploadPolling() {
  if (mobileUploadState.pollTimer) window.clearInterval(mobileUploadState.pollTimer);
  mobileUploadState.pollTimer = null;
}

function closeMobileUpload() {
  stopMobileUploadPolling();
  const overlay = document.getElementById('mobile-upload-overlay');
  overlay?.classList.add('hidden');
  overlay?.setAttribute('aria-hidden', 'true');
}

function setupGeneratorStudio() {
  const notes = document.getElementById('generator-seller-notes');
  notes?.addEventListener('input', () => {
    const count = document.getElementById('generator-notes-count');
    if (count) count.textContent = `${notes.value.length} / 500`;
  });
  document.getElementById('result-title')?.addEventListener('input', () => { updateGeneratorResultMetrics(); refreshGeneratorPlatformPreview(); });
  document.getElementById('result-tags')?.addEventListener('input', () => { updateGeneratorResultMetrics(); refreshGeneratorPlatformPreview(); });
  document.getElementById('meta-price')?.addEventListener('input', debounce(updateGeneratorFeePreview, 250));
  document.getElementById('generator-download-photos')?.addEventListener('click', () => downloadPublishPhotos(state.photos));
  ['meta-brand', 'meta-size', 'meta-color', 'meta-condition'].forEach(id => {
    document.getElementById(id)?.addEventListener('input', () => { updateGeneratorResultMetrics(); refreshGeneratorPlatformPreview(); });
  });
  updateGeneratorPhotoStage();
  updateGeneratorReadiness();
  updateGeneratorResultMetrics();
}

function applyGeneratorTemplate(value) {
  const tpl = state.templates.find(t => t.id === parseInt(value));
  const notes = document.getElementById('generator-seller-notes');
  const tags = document.getElementById('generator-template-tags');
  if (!tpl) {
    if (notes) notes.value = '';
    if (tags) { tags.innerHTML = ''; tags.classList.add('hidden'); }
    const count = document.getElementById('generator-notes-count');
    if (count) count.textContent = '0 / 500';
    return;
  }
  if (tpl.platform) {
    state.platform = tpl.platform;
    renderPlatformButtons();
  }
  if (tpl.language) {
    state.language = tpl.language;
    document.getElementById('language-select').value = tpl.language;
  }
  if (notes) notes.value = (tpl.seller_notes || '').slice(0, 500);
  const count = document.getElementById('generator-notes-count');
  if (count) count.textContent = `${notes?.value.length || 0} / 500`;
  const defaultTags = Array.isArray(tpl.default_tags) ? tpl.default_tags : [];
  if (tags) {
    tags.innerHTML = defaultTags.map(tag => `<span>${escapeHtml(tag)}</span>`).join('');
    tags.classList.toggle('hidden', defaultTags.length === 0);
  }
  updateGeneratorReadiness();
}

function updateGeneratorPhotoStage() {
  const count = state.photos.length;
  const cover = document.getElementById('generator-cover');
  const empty = document.getElementById('generator-photo-empty');
  const badge = document.getElementById('generator-cover-badge');
  const countEl = document.getElementById('generator-photo-count');
  if (countEl) countEl.textContent = `${count} / 10`;
  if (cover && empty && badge) {
    if (count) {
      cover.src = photoUrl(state.photos[0]);
      cover.classList.remove('hidden');
      empty.classList.add('hidden');
      badge.classList.remove('hidden');
    } else {
      cover.removeAttribute('src');
      cover.classList.add('hidden');
      empty.classList.remove('hidden');
      badge.classList.add('hidden');
    }
  }
  const completed = {
    count: count >= 3,
    cover: count >= 1,
    details: count >= 4,
    background: count >= 2,
    order: count >= 2,
  };
  let score = 0;
  document.querySelectorAll('#generator-photo-tips [data-photo-tip]').forEach(item => {
    const done = Boolean(completed[item.dataset.photoTip]);
    item.classList.toggle('is-complete', done);
    if (done) score += 1;
  });
  const scoreEl = document.getElementById('generator-photo-quality-score');
  if (scoreEl) scoreEl.textContent = `${score} / 5`;
  document.getElementById('generator-step-photos')?.classList.toggle('is-complete', count > 0);
}

function updateGeneratorReadiness() {
  const wrapper = document.querySelector('.generator-readiness');
  const title = document.getElementById('generator-readiness-title');
  const copy = document.getElementById('generator-readiness-copy');
  const ready = state.photos.length > 0;
  wrapper?.classList.toggle('is-ready', ready);
  if (title) title.textContent = ready ? 'Bereit zur Analyse' : 'Fotos fehlen';
  if (copy) copy.textContent = ready
    ? `${state.photos.length} Foto${state.photos.length === 1 ? '' : 's'} · ${platformLabel(state.platform)} · ${state.language.toUpperCase()}`
    : 'Lade mindestens ein Produktfoto hoch.';
  document.getElementById('generator-step-ai')?.classList.toggle('is-active', ready && !state.currentListing);
}

function updateGeneratorResultMetrics() {
  const title = document.getElementById('result-title')?.value || '';
  const description = editors.result ? editors.result.getText().trim() : '';
  const tagValue = document.getElementById('result-tags')?.value || '';
  const tags = tagValue.split(',').map(tag => tag.trim()).filter(Boolean);
  const titleCount = document.getElementById('result-title-count');
  const descriptionCount = document.getElementById('result-description-count');
  const tagsCount = document.getElementById('result-tags-count');
  if (titleCount) titleCount.textContent = `${title.length} / 80`;
  if (descriptionCount) descriptionCount.textContent = `${description.length} Zeichen`;
  if (tagsCount) tagsCount.textContent = `${tags.length} Tag${tags.length === 1 ? '' : 's'}`;

  let score = 0;
  if (title.length >= 30 && title.length <= 80) score += 25;
  else if (title.length >= 15) score += 16;
  if (description.length >= 180) score += 30;
  else if (description.length >= 80) score += 18;
  if (tags.length >= 5) score += 15;
  else if (tags.length) score += 8;
  if (document.getElementById('meta-brand')?.value) score += 8;
  if (document.getElementById('meta-category-picker')?.textContent?.trim()) score += 8;
  if (parseFloat(document.getElementById('meta-price')?.value)) score += 8;
  if (state.photos.length >= 3) score += 6;
  score = Math.min(100, score);
  const scoreEl = document.getElementById('generator-quality-score');
  const ring = document.getElementById('generator-quality-ring');
  const label = document.getElementById('generator-quality-label');
  if (scoreEl) scoreEl.textContent = `${score}%`;
  if (ring) ring.style.setProperty('--quality', `${score}%`);
  if (label) {
    label.textContent = score >= 85 ? 'Sehr gut' : score >= 65 ? 'Gut' : score >= 40 ? 'Ausbaufähig' : 'Unvollständig';
    label.style.color = score >= 65 ? 'var(--vd-success)' : score >= 40 ? 'var(--vd-warning)' : 'var(--vd-danger)';
  }
}

async function updateGeneratorFeePreview() {
  const price = parseFloat(document.getElementById('meta-price')?.value) || 0;
  const feeEl = document.getElementById('fee-info');
  if (!feeEl || !price) {
    feeEl?.classList.add('hidden');
    updateGeneratorResultMetrics();
    return;
  }
  try {
    const fees = await api('GET', `/api/fees?platform=${encodeURIComponent(state.platform)}&price=${encodeURIComponent(price)}`);
    feeEl.innerHTML = `Gebühren: ${fees.fees} € (${escapeHtml(fees.breakdown || '')}) → <span class="fee-net">Netto: ${fees.net} €</span>`;
    feeEl.classList.remove('hidden');
  } catch {}
  updateGeneratorResultMetrics();
}

async function generateListing() {
  const btn = document.getElementById('generate-btn');
  const loading = document.getElementById('loading');

  btn.disabled = true;
  document.getElementById('result-section').classList.add('hidden');
  document.getElementById('meta-section').classList.add('hidden');
  document.getElementById('variants-section').classList.add('hidden');
  document.getElementById('generator-preview-empty')?.classList.remove('hidden');
  const previewStatus = document.getElementById('generator-preview-status');
  if (previewStatus) { previewStatus.textContent = 'Analysiert'; previewStatus.className = 'generator-preview-status is-working'; }
  document.getElementById('generator-step-ai')?.classList.add('is-active');
  loading.classList.remove('hidden');

  try {
    const body = {
      photos: state.photos,
      platform: state.platform,
      ai_provider: state.aiProvider,
      language: state.language,
      variants: state.variants,
      template_id: parseInt(document.getElementById('template-select')?.value) || null,
      seller_notes: document.getElementById('generator-seller-notes')?.value.trim() || '',
    };
    if (state.aiModel) body.ai_model = state.aiModel;

    const data = await api('POST', '/api/generate', body);

    if (data.meta) showMeta(data.meta, data.fees);

    if (data.variants) {
      state.allVariants = data.variants;
      showVariantTabs(data.variants);
      showListing(data.variants[0], data.variants[0].description_html);
    } else {
      state.allVariants = null;
      document.getElementById('variants-section').classList.add('hidden');
      showListing(data.listing, data.description_html);
    }
    setTimeout(fetchPriceSuggestion, 500);
  } catch (err) {
    const previewStatus = document.getElementById('generator-preview-status');
    if (previewStatus) { previewStatus.textContent = 'Fehler'; previewStatus.className = 'generator-preview-status'; }
    document.getElementById('generator-step-ai')?.classList.remove('is-active');
    toast('Fehler: ' + err.message);
  } finally {
    loading.classList.add('hidden');
    btn.disabled = state.photos.length === 0;
    updateGeneratorReadiness();
  }
}

async function showMeta(meta, fees) {
  document.getElementById('meta-sku').value = state.currentListing?.sku || '';
  document.getElementById('meta-brand').value = meta.brand || '';
  document.getElementById('meta-size').value = meta.size || '';
  document.getElementById('meta-color').value = meta.color || '';
  document.getElementById('meta-condition').value = meta.condition || '';
  document.getElementById('meta-price').value = meta.suggested_price || '';
  document.getElementById('meta-section').classList.remove('hidden');
  updateGeneratorResultMetrics();

  let matchedCategory = null;
  if (meta.category) {
    try {
      const matches = await api('GET', `/api/categories/${state.platform}/match?q=${encodeURIComponent(meta.category)}`);
      if (matches.length && matches[0].score >= 50) matchedCategory = matches[0].pathStr;
    } catch {}
  }
  metaCategoryPicker = createCategoryPicker('meta-category-picker', state.platform, matchedCategory || meta.category || null);

  const feeEl = document.getElementById('fee-info');
  if (fees) {
    feeEl.innerHTML = `Gebühren: ${fees.fees} € (${fees.breakdown}) → <span class="fee-net">Netto: ${fees.net} €</span>`;
    feeEl.classList.remove('hidden');
  } else { feeEl.classList.add('hidden'); }
}

function getEditedMeta() {
  return {
    brand: document.getElementById('meta-brand').value || null,
    category: (metaCategoryPicker ? metaCategoryPicker.getValue() : null) || null,
    size: document.getElementById('meta-size').value || null,
    color: document.getElementById('meta-color').value || null,
    condition: document.getElementById('meta-condition').value || null,
    price: parseFloat(document.getElementById('meta-price').value) || null,
    storage_location: document.getElementById('meta-storage-location').value || null,
  };
}

function showVariantTabs(variants) {
  const c = document.getElementById('variant-tabs');
  c.innerHTML = '';
  variants.forEach((v, i) => {
    const btn = document.createElement('button');
    btn.className = 'variant-tab' + (i === 0 ? ' active' : '');
    btn.textContent = `Variante ${i + 1}`;
    btn.addEventListener('click', () => {
      c.querySelectorAll('.variant-tab').forEach(b => b.classList.remove('active'));
      btn.classList.add('active');
      showListing(v, v.description_html);
    });
    c.appendChild(btn);
  });
  document.getElementById('variants-section').classList.remove('hidden');
}

function showListing(listing, platformHtml) {
  state.currentListing = listing;
  const editorHtml = listing.id && listing.description_html
    ? sanitizeRichHtmlClient(listing.description_html)
    : plainTextToRichHtml(listing.description || '');
  state.currentHtml = platformHtml || '';

  const skuEl = document.getElementById('meta-sku');
  if (skuEl && listing.sku) skuEl.value = listing.sku;
  document.getElementById('result-title').value = listing.title || '';
  htmlSyncState.generatorLock = true;
  setEditorHtml(editors.result, editorHtml);
  htmlSyncState.generatorLock = false;
  document.getElementById('html-source').value = getEditorHtml(editors.result);
  setSandboxPreview(document.getElementById('html-preview'), state.currentHtml || editorHtml);
  document.getElementById('html-preview-wrap')?.classList.remove('hidden');
  setHtmlSyncStatus('generator-html-sync-status', 'Synchron', 'ok');

  const tags = Array.isArray(listing.tags) ? listing.tags.join(', ') : (listing.tags || '');
  document.getElementById('result-tags').value = tags;
  const plat = state.platforms.find(p => p.id === state.platform);
  document.getElementById('tags-label').textContent = plat?.fields.includes('hashtags') ? 'Hashtags' : 'Tags';
  document.getElementById('save-btn').textContent = listing.id ? 'Änderungen speichern' : 'Speichern & Hinzufügen';
  document.getElementById('generator-preview-empty')?.classList.add('hidden');
  document.getElementById('result-section').classList.remove('hidden');
  const previewStatus = document.getElementById('generator-preview-status');
  if (previewStatus) { previewStatus.textContent = 'Bereit zur Prüfung'; previewStatus.className = 'generator-preview-status is-ready'; }
  document.getElementById('generator-step-ai')?.classList.remove('is-active');
  document.getElementById('generator-step-ai')?.classList.add('is-complete');
  document.getElementById('generator-step-result')?.classList.add('is-active');
  updateGeneratorResultMetrics();
  refreshGeneratorPlatformPreview();
}

// --- Copy ---
function setupCopy() {
  document.querySelectorAll('.copy-btn[data-target]').forEach(btn => {
    btn.addEventListener('click', () => {
      navigator.clipboard.writeText(document.getElementById(btn.dataset.target).value);
      toast('Kopiert!');
    });
  });
  document.getElementById('copy-desc-text').addEventListener('click', () => {
    if (editors.result) {
      navigator.clipboard.writeText(editors.result.getText().trim());
      toast('Text kopiert!');
    }
  });
  document.getElementById('copy-desc-html').addEventListener('click', () => {
    const src = document.getElementById('html-source').value;
    if (src) {
      navigator.clipboard.writeText(src);
      toast('Editor-HTML kopiert!');
    }
  });
  document.getElementById('copy-platform-html')?.addEventListener('click', () => {
    const src = state.currentHtml || document.getElementById('html-source').value;
    if (src) {
      navigator.clipboard.writeText(src);
      toast('Plattform-HTML kopiert!');
    }
  });
  document.getElementById('format-html-source')?.addEventListener('click', () => {
    const source = document.getElementById('html-source');
    const safe = sanitizeRichHtmlClient(source.value);
    source.value = safe;
    htmlSyncState.generatorLock = true;
    setEditorHtml(editors.result, safe);
    htmlSyncState.generatorLock = false;
    refreshGeneratorPlatformPreview();
    toast('HTML bereinigt und synchronisiert');
  });
  document.getElementById('copy-all-btn').addEventListener('click', () => {
    const t = document.getElementById('result-title').value;
    const d = editors.result ? editors.result.getText().trim() : '';
    const tg = document.getElementById('result-tags').value;
    navigator.clipboard.writeText(`${t}\n\n${d}\n\n${tg}`);
    toast('Alles kopiert!');
  });
  document.getElementById('save-btn').addEventListener('click', async () => {
    if (!state.currentListing) return;
    const tags = document.getElementById('result-tags').value.split(',').map(t => t.trim()).filter(Boolean);
    const meta = getEditedMeta();
    const descriptionHtml = getEditorHtml(editors.result);
    const description = richHtmlToText(descriptionHtml);
    const payload = {
      title: document.getElementById('result-title').value,
      description,
      description_html: descriptionHtml,
      tags,
      photos: [...state.photos],
      platform: state.platform,
      ai_provider: state.aiProvider,
      language: state.language,
      ...meta,
    };
    try {
      if (state.currentListing.id) {
        await api('PUT', `/api/listings/${state.currentListing.id}`, { ...payload, _edit_lock: listingEditLock.listingId === Number(state.currentListing.id) ? listingEditLock.token : null });
        toast('Gespeichert!');
      } else {
        await api('POST', '/api/listings', {
          ...payload,
          seller_notes: state.currentListing?.seller_notes || null,
        });
        toast('Listing hinzugefügt!');
        notify('Vendoo', `"${payload.title}" gespeichert`);
        resetGenerator();
      }
    } catch (err) { toast('Fehler: ' + err.message); }
  });
}

function resetGenerator() {
  state.currentListing = null;
  state.currentHtml = '';
  state.allVariants = null;
  state.photos = [];
  state.aiModelPhotos = [];
  metaCategoryPicker = null;
  document.getElementById('photo-preview').innerHTML = '';
  document.getElementById('image-tools').classList.add('hidden');
  document.getElementById('result-section').classList.add('hidden');
  document.getElementById('meta-section').classList.add('hidden');
  document.getElementById('variants-section').classList.add('hidden');
  document.getElementById('generator-preview-empty')?.classList.remove('hidden');
  document.getElementById('save-btn').textContent = 'Speichern & Hinzufügen';
  document.getElementById('generate-btn').disabled = true;
  renderAiModelGrid('generator', []);
  setAiModelStatus('generator-ai-model-status', 'Wartet auf Produktfotos.', 'neutral');
  document.getElementById('template-select').value = '';
  const notes = document.getElementById('generator-seller-notes');
  if (notes) notes.value = '';
  const notesCount = document.getElementById('generator-notes-count');
  if (notesCount) notesCount.textContent = '0 / 500';
  const templateTags = document.getElementById('generator-template-tags');
  if (templateTags) { templateTags.innerHTML = ''; templateTags.classList.add('hidden'); }
  if (editors.result) editors.result.setText('');
  const htmlSource = document.getElementById('html-source');
  if (htmlSource) htmlSource.value = '';
  setSandboxPreview(document.getElementById('html-preview'), '');
  document.getElementById('result-title').value = '';
  document.getElementById('result-tags').value = '';
  const previewStatus = document.getElementById('generator-preview-status');
  if (previewStatus) { previewStatus.textContent = 'Wartet'; previewStatus.className = 'generator-preview-status'; }
  ['generator-step-ai', 'generator-step-result'].forEach(id => {
    document.getElementById(id)?.classList.remove('is-active', 'is-complete');
  });
  document.getElementById('generator-step-photos')?.classList.remove('is-complete');
  document.getElementById('generator-step-photos')?.classList.add('is-active');
  updateGeneratorPhotoStage();
  updateGeneratorReadiness();
  updateGeneratorResultMetrics();
}

// --- Listings / Historie ---
const historyState = {
  selected: new Set(),
  listings: [],
  allFiltered: [],
  publishStatus: {},
  hiddenColumns: new Set(),
};

const HISTORY_COLUMN_STORAGE = 'vendoo-history-columns';

function setupHistory() {
  const search = document.getElementById('search-input');
  const platform = document.getElementById('filter-platform');
  const status = document.getElementById('filter-status');
  const period = document.getElementById('history-period');
  const dateFrom = document.getElementById('filter-date-from');
  const dateTo = document.getElementById('filter-date-to');

  search.addEventListener('input', debounce(() => { state.historyPage = 1; loadHistory(); }, 300));
  platform.addEventListener('change', () => { state.historyPage = 1; loadHistory(); });
  status.addEventListener('change', () => { state.historyPage = 1; loadHistory(); });
  period.addEventListener('change', () => {
    applyHistoryPeriod(period.value);
    state.historyPage = 1;
    loadHistory();
  });
  dateFrom.addEventListener('change', () => { state.historyPage = 1; loadHistory(); });
  dateTo.addEventListener('change', () => { state.historyPage = 1; loadHistory(); });

  document.getElementById('history-reset-filters').addEventListener('click', () => {
    search.value = '';
    platform.value = '';
    status.value = '';
    period.value = '90';
    applyHistoryPeriod('90');
    state.historyPage = 1;
    loadHistory();
  });

  document.getElementById('back-to-list').addEventListener('click', async () => { await releaseListingLock({ quiet: true }); setHistoryDetailMode(false); });

  document.getElementById('history-select-all').addEventListener('change', e => {
    const checked = e.target.checked;
    historyState.listings.forEach(listing => {
      if (checked) historyState.selected.add(listing.id);
      else historyState.selected.delete(listing.id);
    });
    document.querySelectorAll('#history-list .history-row-select').forEach(cb => { cb.checked = checked; });
    document.querySelectorAll('#history-list .history-row').forEach(row => row.classList.toggle('selected', checked));
    updateHistoryToolbar();
  });

  document.getElementById('history-clear-selection').addEventListener('click', () => {
    historyState.selected.clear();
    document.querySelectorAll('#history-list .history-row-select').forEach(cb => { cb.checked = false; });
    document.querySelectorAll('#history-list .history-row').forEach(row => row.classList.remove('selected'));
    document.getElementById('history-select-all').checked = false;
    updateHistoryToolbar();
  });

  document.getElementById('history-delete-selected').addEventListener('click', deleteSelectedHistoryListings);
  document.getElementById('history-delete-all').addEventListener('click', async () => {
    if (!historyState.allFiltered.length) return;
    if (!confirm(`Alle ${historyState.allFiltered.length} gefilterten Artikel in den Papierkorb verschieben?`)) return;
    for (const listing of historyState.allFiltered) await api('DELETE', `/api/listings/${listing.id}`);
    historyState.selected.clear();
    toast('Alle gefilterten Artikel wurden in den Papierkorb verschoben');
    await loadHistory();
    updateTrashBadge();
  });

  document.getElementById('history-bulk-status').addEventListener('click', bulkUpdateHistoryStatus);
  document.getElementById('history-price-selected').addEventListener('click', bulkUpdateHistoryPrice);
  document.getElementById('history-duplicate-selected').addEventListener('click', bulkDuplicateHistoryListings);
  document.getElementById('history-edit-selected').addEventListener('click', () => {
    const ids = [...historyState.selected];
    if (ids.length !== 1) { toast('Zum Nachbearbeiten bitte genau ein Artikel auswählen'); return; }
    showDetail(ids[0]);
  });
  document.getElementById('history-publish-selected').addEventListener('click', () => {
    const ids = [...historyState.selected];
    if (!ids.length) return;
    publishState.selected = new Set(ids);
    switchToTab('publish');
    toast(`${ids.length} Listing${ids.length === 1 ? '' : 's'} für Publish ausgewählt`);
  });
  document.getElementById('history-print-labels').addEventListener('click', () => {
    const selected = historyState.allFiltered.filter(listing => historyState.selected.has(listing.id));
    if (selected.length) printLabels(selected);
  });

  document.getElementById('export-csv-btn').addEventListener('click', () => {
    const params = new URLSearchParams();
    if (platform.value) params.set('platform', platform.value);
    if (status.value) params.set('status', status.value);
    window.open(`/api/export/csv?${params}`, '_blank');
    closeHistoryPopovers();
  });
  document.getElementById('export-json-btn').addEventListener('click', () => {
    const data = historyState.allFiltered;
    if (!data.length) { toast('Keine Artikel zum Exportieren'); return; }
    const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
    const anchor = document.createElement('a');
    anchor.href = URL.createObjectURL(blob);
    anchor.download = `vendoo-export-${new Date().toISOString().slice(0, 10)}.json`;
    anchor.click();
    URL.revokeObjectURL(anchor.href);
    toast(`${data.length} Artikel als JSON exportiert`);
    closeHistoryPopovers();
  });

  setupHistoryPopovers();
  setupHistoryColumns();

  const pageSizeSelect = document.getElementById('history-page-size-select');
  pageSizeSelect.value = String(state.historyPageSize);
  pageSizeSelect.addEventListener('change', () => {
    state.historyPageSize = Number(pageSizeSelect.value);
    state.historyPage = 1;
    loadHistory();
  });

  setupPagination('history');
  applyHistoryPeriod(period.value || '90');
}

function applyHistoryPeriod(value) {
  const custom = document.getElementById('history-custom-dates');
  const fromInput = document.getElementById('filter-date-from');
  const toInput = document.getElementById('filter-date-to');
  custom.classList.toggle('hidden', value !== 'custom');
  if (value === 'custom') return;

  const today = new Date();
  const iso = date => date.toISOString().slice(0, 10);
  toInput.value = value === 'all' ? '' : iso(today);

  if (value === 'all') {
    fromInput.value = '';
  } else if (value === 'year') {
    fromInput.value = `${today.getFullYear()}-01-01`;
  } else {
    const days = Number(value) || 90;
    const from = new Date(today);
    from.setDate(from.getDate() - days + 1);
    fromInput.value = iso(from);
  }
}

function setupHistoryPopovers() {
  const exportButton = document.getElementById('history-export-toggle');
  const exportMenu = document.getElementById('history-export-menu');
  const columnsButton = document.getElementById('history-columns-btn');
  const columnsPanel = document.getElementById('history-columns-panel');

  exportButton.addEventListener('click', event => {
    event.stopPropagation();
    const open = exportMenu.classList.toggle('hidden') === false;
    columnsPanel.classList.add('hidden');
    exportButton.setAttribute('aria-expanded', String(open));
    columnsButton.setAttribute('aria-expanded', 'false');
  });
  columnsButton.addEventListener('click', event => {
    event.stopPropagation();
    const open = columnsPanel.classList.toggle('hidden') === false;
    exportMenu.classList.add('hidden');
    columnsButton.setAttribute('aria-expanded', String(open));
    exportButton.setAttribute('aria-expanded', 'false');
  });
  exportMenu.addEventListener('click', event => event.stopPropagation());
  columnsPanel.addEventListener('click', event => event.stopPropagation());
  document.addEventListener('click', closeHistoryPopovers);
}

function closeHistoryPopovers() {
  document.getElementById('history-export-menu')?.classList.add('hidden');
  document.getElementById('history-columns-panel')?.classList.add('hidden');
  document.getElementById('history-export-toggle')?.setAttribute('aria-expanded', 'false');
  document.getElementById('history-columns-btn')?.setAttribute('aria-expanded', 'false');
  document.querySelectorAll('.history-row-menu').forEach(menu => menu.classList.add('hidden'));
}

function setupHistoryColumns() {
  try {
    const saved = JSON.parse(localStorage.getItem(HISTORY_COLUMN_STORAGE) || '[]');
    historyState.hiddenColumns = new Set(Array.isArray(saved) ? saved : []);
  } catch { historyState.hiddenColumns = new Set(); }

  document.querySelectorAll('[data-history-column]').forEach(input => {
    input.checked = !historyState.hiddenColumns.has(input.dataset.historyColumn);
    input.addEventListener('change', () => {
      const column = input.dataset.historyColumn;
      if (input.checked) historyState.hiddenColumns.delete(column);
      else historyState.hiddenColumns.add(column);
      localStorage.setItem(HISTORY_COLUMN_STORAGE, JSON.stringify([...historyState.hiddenColumns]));
      applyHistoryColumns();
    });
  });
  applyHistoryColumns();
}

function applyHistoryColumns() {
  const table = document.getElementById('history-table');
  if (!table) return;
  ['platform', 'provider', 'price', 'status', 'location', 'published'].forEach(column => {
    table.classList.toggle(`history-hide-${column}`, historyState.hiddenColumns.has(column));
  });
}

function setHistoryDetailMode(enabled) {
  document.querySelector('.listings-heading')?.classList.toggle('hidden', enabled);
  document.querySelector('.history-filterbar')?.classList.toggle('hidden', enabled);
  document.querySelector('.history-table-shell')?.classList.toggle('hidden', enabled);
  document.getElementById('history-detail')?.classList.toggle('hidden', !enabled);
}

async function deleteSelectedHistoryListings() {
  const ids = [...historyState.selected];
  if (!ids.length) return;
  if (!confirm(`${ids.length} Listing${ids.length === 1 ? '' : 's'} in den Papierkorb verschieben?`)) return;
  for (const id of ids) await api('DELETE', `/api/listings/${id}`);
  historyState.selected.clear();
  toast(`${ids.length} Listing${ids.length === 1 ? '' : 's'} in den Papierkorb verschoben`);
  await loadHistory();
  updateTrashBadge();
}

async function bulkUpdateHistoryStatus() {
  const ids = [...historyState.selected];
  if (!ids.length) return;
  const answer = prompt('Neuer Status: aktiv, verkauft oder reserviert');
  if (!answer) return;
  const normalized = answer.trim().toLowerCase();
  const map = { aktiv: 'active', active: 'active', verkauft: 'sold', sold: 'sold', reserviert: 'reserved', reserved: 'reserved' };
  const newStatus = map[normalized];
  if (!newStatus) { toast('Unbekannter Status'); return; }
  await Promise.all(ids.map(id => api('PUT', `/api/listings/${id}`, { status: newStatus })));
  toast(`Status für ${ids.length} Artikel aktualisiert`);
  loadHistory();
}

async function bulkUpdateHistoryPrice() {
  const ids = [...historyState.selected];
  if (!ids.length) return;
  const answer = prompt('Neuer Preis in Euro für alle ausgewählten Artikel:');
  if (answer === null) return;
  const price = Number(String(answer).replace(',', '.'));
  if (!Number.isFinite(price) || price < 0) { toast('Bitte einen gültigen Preis eingeben'); return; }
  await Promise.all(ids.map(id => api('PUT', `/api/listings/${id}`, { price })));
  toast(`Preis für ${ids.length} Artikel aktualisiert`);
  loadHistory();
}

async function bulkDuplicateHistoryListings() {
  const ids = [...historyState.selected];
  if (!ids.length) return;
  for (const id of ids) await api('POST', `/api/listings/${id}/duplicate`);
  historyState.selected.clear();
  toast(`${ids.length} Listing${ids.length === 1 ? '' : 's'} dupliziert`);
  loadHistory();
}

function setupPagination(prefix) {
  const topBar = document.getElementById(`${prefix}-pagination-top`);
  if (!topBar) return;
  topBar.querySelectorAll('.page-size-btn').forEach(btn => {
    btn.addEventListener('click', () => {
      topBar.querySelectorAll('.page-size-btn').forEach(b => b.classList.remove('active'));
      btn.classList.add('active');
      const size = parseInt(btn.dataset.size);
      if (prefix === 'history') { state.historyPageSize = size; state.historyPage = 1; loadHistory(); }
      else { state.publishPageSize = size; state.publishPage = 1; loadPublishList(); }
    });
  });
  document.getElementById(`${prefix}-prev`)?.addEventListener('click', () => {
    if (prefix === 'history') { state.historyPage--; loadHistory(); }
    else { state.publishPage--; loadPublishList(); }
  });
  document.getElementById(`${prefix}-next`)?.addEventListener('click', () => {
    if (prefix === 'history') { state.historyPage++; loadHistory(); }
    else { state.publishPage++; loadPublishList(); }
  });
  document.getElementById(`${prefix}-prev-bottom`)?.addEventListener('click', () => {
    if (prefix === 'history') { state.historyPage--; loadHistory(); }
    else { state.publishPage--; loadPublishList(); }
  });
  document.getElementById(`${prefix}-next-bottom`)?.addEventListener('click', () => {
    if (prefix === 'history') { state.historyPage++; loadHistory(); }
    else { state.publishPage++; loadPublishList(); }
  });
}

function updatePaginationUI(prefix, page, totalPages, totalItems) {
  const info = `Seite ${page} von ${totalPages} (${totalItems} Einträge)`;
  document.getElementById(`${prefix}-page-info`)?.replaceChildren(document.createTextNode(info));
  document.getElementById(`${prefix}-page-info-bottom`)?.replaceChildren(document.createTextNode(info));

  const prevDisabled = page <= 1;
  const nextDisabled = page >= totalPages;
  const prev = document.getElementById(`${prefix}-prev`);
  const next = document.getElementById(`${prefix}-next`);
  const prevBottom = document.getElementById(`${prefix}-prev-bottom`);
  const nextBottom = document.getElementById(`${prefix}-next-bottom`);
  if (prev) prev.disabled = prevDisabled;
  if (next) next.disabled = nextDisabled;
  if (prevBottom) prevBottom.disabled = prevDisabled;
  if (nextBottom) nextBottom.disabled = nextDisabled;

  if (prefix === 'history') {
    const totalInfo = document.getElementById('history-total-info');
    if (totalInfo) totalInfo.textContent = `von ${totalItems} Einträgen`;
    renderHistoryPageButtons(page, totalPages);
  }
}

function renderHistoryPageButtons(page, totalPages) {
  const container = document.getElementById('history-page-buttons');
  if (!container) return;
  const pages = new Set([1, totalPages, page - 2, page - 1, page, page + 1, page + 2]);
  const valid = [...pages].filter(value => value >= 1 && value <= totalPages).sort((a, b) => a - b);
  const parts = [];
  let previous = 0;
  for (const value of valid) {
    if (previous && value - previous > 1) parts.push('<span class="history-page-gap">…</span>');
    parts.push(`<button class="history-page-button${value === page ? ' active' : ''}" type="button" data-page="${value}"${value === page ? ' aria-current="page"' : ''}>${value}</button>`);
    previous = value;
  }
  container.innerHTML = parts.join('');
  container.querySelectorAll('[data-page]').forEach(button => button.addEventListener('click', () => {
    state.historyPage = Number(button.dataset.page);
    loadHistory();
  }));
}

function paginateList(allItems, page, pageSize) {
  if (!pageSize || pageSize <= 0) return { items: allItems, page: 1, totalPages: 1 };
  const totalPages = Math.max(1, Math.ceil(allItems.length / pageSize));
  const safePage = Math.min(Math.max(1, page), totalPages);
  const start = (safePage - 1) * pageSize;
  return { items: allItems.slice(start, start + pageSize), page: safePage, totalPages };
}

async function loadHistory() {
  const query = document.getElementById('search-input').value.trim();
  const platform = document.getElementById('filter-platform').value;
  const status = document.getElementById('filter-status').value;
  const dateFrom = document.getElementById('filter-date-from').value;
  const dateTo = document.getElementById('filter-date-to').value;

  const params = new URLSearchParams();
  if (query) params.set('q', query);
  if (platform) params.set('platform', platform);
  if (status) params.set('status', status);
  let listings = await api('GET', `/api/listings?${params.toString()}`);

  if (dateFrom) {
    const from = new Date(`${dateFrom}T00:00:00`);
    listings = listings.filter(listing => historyParseDate(listing.created_at) >= from);
  }
  if (dateTo) {
    const to = new Date(`${dateTo}T23:59:59.999`);
    listings = listings.filter(listing => historyParseDate(listing.created_at) <= to);
  }

  historyState.allFiltered = listings;
  const { items, page, totalPages } = paginateList(listings, state.historyPage, state.historyPageSize);
  state.historyPage = page;
  historyState.listings = items;
  updatePaginationUI('history', page, totalPages, listings.length);

  if (items.length) {
    const ids = items.map(listing => listing.id).join(',');
    try { historyState.publishStatus = await api('GET', `/api/publish/status?ids=${ids}`); }
    catch { historyState.publishStatus = {}; }
  } else {
    historyState.publishStatus = {};
  }

  renderHistoryList(items);
}

function historyParseDate(value) {
  if (!value) return new Date(0);
  const normalized = /(?:Z|[+-]\d\d:\d\d)$/.test(value) ? value : `${value.replace(' ', 'T')}Z`;
  const date = new Date(normalized);
  return Number.isNaN(date.getTime()) ? new Date(0) : date;
}

function historyFormatDate(value, withTime = false) {
  const date = historyParseDate(value);
  if (!date.getTime()) return '—';
  return date.toLocaleString('de-DE', withTime
    ? { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }
    : { day: '2-digit', month: '2-digit', year: 'numeric' });
}

function historyProviderLabel(provider) {
  return ({ claude: 'Claude', openai: 'OpenAI', openrouter: 'OpenRouter', local: 'Ollama' })[provider] || provider || '—';
}

function historyStatusMeta(status) {
  return ({
    active: { label: 'Aktiv', className: 'active' },
    sold: { label: 'Verkauft', className: 'sold' },
    reserved: { label: 'Reserviert', className: 'reserved' },
  })[status] || { label: status || 'Aktiv', className: 'active' };
}

function historyPublishSummary(listing) {
  const statuses = historyState.publishStatus[listing.id] || {};
  const entries = Object.entries(statuses);
  if (!entries.length) return '<span class="history-publish-empty">Nicht veröffentlicht</span>';
  return entries.slice(0, 2).map(([platform, status]) => {
    const meta = {
      published: ['Live', 'published'],
      copied: ['Kopiert', 'copied'],
      pending: ['Ausstehend', 'pending'],
      failed: ['Fehler', 'failed'],
    }[status] || [status, 'pending'];
    return `<span class="history-publish-badge ${meta[1]}"><span>${escapeHtml(platformLabel(platform))}</span>${escapeHtml(meta[0])}</span>`;
  }).join('');
}

function renderHistoryList(listings) {
  const container = document.getElementById('history-list');
  if (!listings.length) {
    container.innerHTML = `<div class="history-empty-state">
      <div class="history-empty-icon"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 8h8M8 12h6"/></svg></div>
      <h3>Keine Artikel gefunden</h3>
      <p>Ändere die Filter oder erstelle ein neues Listing.</p>
      <button class="vd-button primary" type="button" data-empty-new-listing>Neuer Artikel</button>
    </div>`;
    container.querySelector('[data-empty-new-listing]')?.addEventListener('click', () => switchToTab('generator'));
    updateHistoryToolbar();
    applyHistoryColumns();
    return;
  }

  container.innerHTML = listings.map(listing => {
    const checked = historyState.selected.has(listing.id);
    const photo = listing.photos?.[0]
      ? `<img class="history-thumb" src="${escapeHtml(photoUrl(listing.photos[0]))}" alt="" loading="lazy">`
      : '<div class="history-thumb history-thumb-placeholder" aria-hidden="true"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="m4 7 8-4 8 4-8 4Z"/><path d="m4 7 8 4 8-4v10l-8 4-8-4Z"/><path d="M12 11v10"/></svg></div>';
    const status = historyStatusMeta(listing.status || 'active');
    const provider = historyProviderLabel(listing.ai_provider);
    const platform = platformLabel(listing.platform);
    const price = listing.price !== null && listing.price !== undefined ? formatCurrency(listing.price) : '—';
    const sku = listing.sku || `VD-${String(listing.id).padStart(4, '0')}`;

    return `<div class="history-row${checked ? ' selected' : ''}" data-id="${listing.id}" role="row">
      <div class="history-cell history-cell-select" role="cell"><input class="history-row-select" type="checkbox" data-id="${listing.id}" ${checked ? 'checked' : ''} aria-label="${escapeHtml(listing.title || 'Listing')} auswählen"></div>
      <div class="history-cell history-cell-article" role="cell">
        ${photo}
        <div class="history-article-copy">
          <button class="history-article-title" type="button" data-open-listing="${listing.id}">${escapeHtml(listing.title || 'Ohne Titel')}</button>
          <div class="history-article-sku">SKU&nbsp;&nbsp;${escapeHtml(sku)}</div>
        </div>
      </div>
      <div class="history-cell history-cell-platform" data-column="platform" role="cell"><span class="history-platform-name platform-${escapeHtml(listing.platform || '')}">${escapeHtml(platform)}</span><span class="history-cell-sub">${escapeHtml((listing.language || 'de').toUpperCase())}</span></div>
      <div class="history-cell history-cell-provider" data-column="provider" role="cell"><span class="history-provider-mark">${escapeHtml(provider.slice(0, 1))}</span><span><strong>${escapeHtml(provider)}</strong><small>Vision AI</small></span></div>
      <div class="history-cell history-cell-price" data-column="price" role="cell"><strong>${escapeHtml(price)}</strong><span class="history-cell-sub">Festpreis</span></div>
      <div class="history-cell history-cell-status" data-column="status" role="cell">
        <select class="history-status-select status-${status.className}" data-id="${listing.id}" aria-label="Status ändern">
          <option value="active"${listing.status === 'active' || !listing.status ? ' selected' : ''}>Aktiv</option>
          <option value="sold"${listing.status === 'sold' ? ' selected' : ''}>Verkauft</option>
          <option value="reserved"${listing.status === 'reserved' ? ' selected' : ''}>Reserviert</option>
        </select>
        <span class="history-cell-sub">Seit ${escapeHtml(historyFormatDate(listing.updated_at || listing.created_at))}</span>
      </div>
      <div class="history-cell history-cell-location" data-column="location" role="cell"><strong>${escapeHtml(listing.storage_location || 'Nicht zugewiesen')}</strong><span class="history-cell-sub">${escapeHtml(listing.category || '—')}</span></div>
      <div class="history-cell history-cell-published" data-column="published" role="cell"><span class="history-publish-date">${escapeHtml(historyFormatDate(listing.updated_at || listing.created_at, true))}</span><div class="history-publish-badges">${historyPublishSummary(listing)}</div></div>
      <div class="history-cell history-cell-menu" role="cell">
        <button class="history-row-menu-button" type="button" data-row-menu="${listing.id}" aria-label="Aktionen für ${escapeHtml(listing.title || 'Listing')}">•••</button>
        <div class="history-row-menu hidden" data-row-menu-panel="${listing.id}">
          <button type="button" data-row-action="open" data-id="${listing.id}">Öffnen</button>
          <button type="button" data-row-action="duplicate" data-id="${listing.id}">Duplizieren</button>
          <button type="button" data-row-action="delete" data-id="${listing.id}" class="danger">Löschen</button>
        </div>
      </div>
    </div>`;
  }).join('');

  container.querySelectorAll('.history-row-select').forEach(checkbox => {
    checkbox.addEventListener('click', event => event.stopPropagation());
    checkbox.addEventListener('change', () => {
      const id = Number(checkbox.dataset.id);
      if (checkbox.checked) historyState.selected.add(id);
      else historyState.selected.delete(id);
      checkbox.closest('.history-row')?.classList.toggle('selected', checkbox.checked);
      updateHistoryToolbar();
    });
  });

  container.querySelectorAll('[data-open-listing]').forEach(button => button.addEventListener('click', event => {
    event.stopPropagation();
    showDetail(Number(button.dataset.openListing));
  }));

  container.querySelectorAll('.history-row').forEach(row => row.addEventListener('dblclick', event => {
    if (!event.target.closest('button, input, select')) showDetail(Number(row.dataset.id));
  }));

  container.querySelectorAll('.history-status-select').forEach(select => {
    select.addEventListener('click', event => event.stopPropagation());
    select.addEventListener('change', async () => {
      await api('PUT', `/api/listings/${select.dataset.id}`, { status: select.value });
      toast('Status aktualisiert');
      loadHistory();
    });
  });

  container.querySelectorAll('[data-row-menu]').forEach(button => button.addEventListener('click', event => {
    event.stopPropagation();
    const panel = container.querySelector(`[data-row-menu-panel="${button.dataset.rowMenu}"]`);
    const willOpen = panel?.classList.contains('hidden');
    container.querySelectorAll('.history-row-menu').forEach(menu => menu.classList.add('hidden'));
    if (willOpen) panel?.classList.remove('hidden');
  }));

  container.querySelectorAll('[data-row-action]').forEach(button => button.addEventListener('click', async event => {
    event.stopPropagation();
    const id = Number(button.dataset.id);
    const action = button.dataset.rowAction;
    if (action === 'open') showDetail(id);
    if (action === 'duplicate') {
      const copy = await api('POST', `/api/listings/${id}/duplicate`);
      toast(`Kopie erstellt: „${copy.title}“`);
      loadHistory();
    }
    if (action === 'delete') {
      if (!confirm('Listing in den Papierkorb verschieben?')) return;
      await api('DELETE', `/api/listings/${id}`);
      historyState.selected.delete(id);
      toast('Listing in den Papierkorb verschoben');
      await loadHistory();
      updateTrashBadge();
    }
  }));

  document.getElementById('history-select-all').checked = listings.every(listing => historyState.selected.has(listing.id));
  updateHistoryToolbar();
  applyHistoryColumns();
}

function updateHistoryToolbar() {
  const count = historyState.selected.size;
  const countLabel = document.getElementById('history-selected-count');
  if (countLabel) countLabel.textContent = `${count} ausgewählt`;
  ['history-delete-selected', 'history-bulk-status', 'history-print-labels', 'history-publish-selected', 'history-edit-selected', 'history-price-selected', 'history-duplicate-selected']
    .forEach(id => { const button = document.getElementById(id); if (button) button.disabled = count === 0; });
  const clear = document.getElementById('history-clear-selection');
  clear?.classList.toggle('hidden', count === 0);
  document.getElementById('history-batch-bar')?.classList.toggle('has-selection', count > 0);
  const selectAll = document.getElementById('history-select-all');
  if (selectAll) {
    const selectedOnPage = historyState.listings.filter(listing => historyState.selected.has(listing.id)).length;
    selectAll.checked = historyState.listings.length > 0 && selectedOnPage === historyState.listings.length;
    selectAll.indeterminate = selectedOnPage > 0 && selectedOnPage < historyState.listings.length;
  }
}

let detailRenderSequence = 0;
let detailPhotoDragIndex = -1;

function detailPlatformOptions(selected) {
  return state.platforms.map(platform => `<option value="${escapeHtml(platform.id)}"${platform.id === selected ? ' selected' : ''}>${escapeHtml(platform.name)}</option>`).join('');
}

function detailProviderOptions(selected) {
  return state.providers.map(provider => `<option value="${escapeHtml(provider.id)}"${provider.id === selected ? ' selected' : ''}>${escapeHtml(provider.name)}</option>`).join('');
}

function detailLanguageOptions(selected) {
  const languages = [['de', 'Deutsch'], ['en', 'English'], ['fr', 'Français'], ['nl', 'Nederlands'], ['pl', 'Polski'], ['es', 'Español'], ['it', 'Italiano'], ['pt', 'Português']];
  return languages.map(([value, label]) => `<option value="${value}"${value === selected ? ' selected' : ''}>${label}</option>`).join('');
}

function getDetailListingForHtml() {
  if (!detailEditorState) return null;
  const tags = (document.getElementById('detail-tags')?.value || '').split(',').map(tag => tag.trim()).filter(Boolean);
  return {
    platform: document.getElementById('detail-platform')?.value || detailEditorState.listing.platform,
    title: document.getElementById('detail-title')?.value || '',
    description: editors.detail ? editors.detail.getText().trim() : '',
    description_html: getEditorHtml(editors.detail),
    tags,
    brand: document.getElementById('detail-brand')?.value || null,
    category: detailEditorState.categoryPicker?.getValue?.() || null,
    size: document.getElementById('detail-size')?.value || null,
    color: document.getElementById('detail-color')?.value || null,
    condition: document.getElementById('detail-condition')?.value || null,
  };
}

const refreshDetailPlatformPreview = debounce(async () => {
  const listing = getDetailListingForHtml();
  if (!listing) return;
  const sequence = ++detailRenderSequence;
  setHtmlSyncStatus('detail-html-sync-status', 'Wird aktualisiert …', 'working');
  try {
    const data = await api('POST', '/api/html-render', { platform: listing.platform, listing });
    if (sequence !== detailRenderSequence) return;
    detailEditorState.platformHtml = data.html || listing.description_html;
    setSandboxPreview(document.getElementById('detail-html-preview'), detailEditorState.platformHtml);
    setHtmlSyncStatus('detail-html-sync-status', 'Synchron', 'ok');
  } catch {
    if (sequence !== detailRenderSequence) return;
    setSandboxPreview(document.getElementById('detail-html-preview'), listing.description_html);
    setHtmlSyncStatus('detail-html-sync-status', 'Vorschau lokal', 'warning');
  }
}, 140);

function setupDetailHtmlSync() {
  const editor = editors.detail;
  const source = document.getElementById('detail-html-source');
  if (!editor || !source) return;

  editor.on('text-change', () => {
    if (htmlSyncState.detailLock) return;
    source.value = getEditorHtml(editor);
    const count = document.getElementById('detail-description-count');
    if (count) count.textContent = `${editor.getText().trim().length} Zeichen`;
    refreshDetailPlatformPreview();
  });

  const applySource = debounce(() => {
    htmlSyncState.detailLock = true;
    const safe = sanitizeRichHtmlClient(source.value);
    setEditorHtml(editor, safe);
    htmlSyncState.detailLock = false;
    source.value = safe;
    const count = document.getElementById('detail-description-count');
    if (count) count.textContent = `${editor.getText().trim().length} Zeichen`;
    refreshDetailPlatformPreview();
  }, 100);

  source.addEventListener('input', () => {
    setHtmlSyncStatus('detail-html-sync-status', 'Code wird übernommen …', 'working');
    setSandboxPreview(document.getElementById('detail-html-preview'), sanitizeRichHtmlClient(source.value));
    applySource();
  });
}

function renderDetailPhotos() {
  if (!detailEditorState) return;
  const photos = detailEditorState.photos;
  const hero = document.getElementById('detail-photo-hero');
  const empty = document.getElementById('detail-photo-empty');
  const strip = document.getElementById('detail-photo-strip');
  const count = document.getElementById('detail-photo-count');
  const activeIndex = Math.min(detailEditorState.activePhotoIndex || 0, Math.max(0, photos.length - 1));
  detailEditorState.activePhotoIndex = activeIndex;
  if (count) count.textContent = `${photos.length} / 10`;
  if (hero && empty) {
    if (photos.length) {
      hero.src = photoUrl(photos[activeIndex]);
      hero.alt = `Produktfoto ${activeIndex + 1}`;
      hero.classList.remove('hidden');
      empty.classList.add('hidden');
    } else {
      hero.removeAttribute('src');
      hero.classList.add('hidden');
      empty.classList.remove('hidden');
    }
  }
  ['detail-edit-photo', 'detail-set-cover', 'detail-remove-photo'].forEach(id => {
    const button = document.getElementById(id);
    if (button) button.disabled = photos.length === 0;
  });
  if (!strip) return;
  strip.innerHTML = photos.map((photo, index) => `
    <button type="button" class="detail-photo-thumb${index === activeIndex ? ' is-active' : ''}${index === 0 ? ' is-cover' : ''}" data-detail-photo-index="${index}" draggable="true" title="Foto ${index + 1}">
      <img src="${escapeHtml(photoUrl(photo))}" alt="Foto ${index + 1}" loading="lazy">
      ${index === 0 ? '<span>Cover</span>' : ''}
    </button>`).join('');
  strip.querySelectorAll('[data-detail-photo-index]').forEach(button => {
    const index = Number(button.dataset.detailPhotoIndex);
    button.addEventListener('click', () => { detailEditorState.activePhotoIndex = index; renderDetailPhotos(); });
    button.addEventListener('dragstart', event => { detailPhotoDragIndex = index; event.dataTransfer.effectAllowed = 'move'; button.classList.add('is-dragging'); });
    button.addEventListener('dragend', () => { detailPhotoDragIndex = -1; button.classList.remove('is-dragging'); });
    button.addEventListener('dragover', event => { event.preventDefault(); event.dataTransfer.dropEffect = 'move'; });
    button.addEventListener('drop', event => {
      event.preventDefault();
      if (detailPhotoDragIndex < 0 || detailPhotoDragIndex === index) return;
      const [moved] = detailEditorState.photos.splice(detailPhotoDragIndex, 1);
      detailEditorState.photos.splice(index, 0, moved);
      detailEditorState.activePhotoIndex = index;
      detailPhotoDragIndex = -1;
      renderDetailPhotos();
    });
  });
}

async function uploadDetailPhotos(files) {
  if (!detailEditorState) return;
  const available = Math.max(0, 10 - detailEditorState.photos.length);
  const accepted = Array.from(files || []).filter(file => file.type.startsWith('image/')).slice(0, available);
  if (!available) return toast('Maximal 10 Fotos pro Listing');
  if (!accepted.length) return;
  const platform = document.getElementById('detail-platform')?.value || detailEditorState.listing.platform || 'allgemein';
  const formData = new FormData();
  formData.append('platform', platform);
  accepted.forEach(file => formData.append('photos', file));
  try {
    const response = await fetch(`/api/upload?platform=${encodeURIComponent(platform)}`, {
      method: 'POST', body: formData, headers: { 'X-CSRF-Token': getCsrf() }, credentials: 'same-origin',
    });
    const data = await response.json().catch(() => ({}));
    if (!response.ok) throw new Error(data.error || 'Upload fehlgeschlagen');
    detailEditorState.photos.push(...(data.filenames || []));
    detailEditorState.activePhotoIndex = Math.max(0, detailEditorState.photos.length - (data.filenames || []).length);
    renderDetailPhotos();
    toast(`${(data.filenames || []).length} Foto${(data.filenames || []).length === 1 ? '' : 's'} hinzugefügt`);
  } catch (err) { toast('Upload fehlgeschlagen: ' + err.message); }
}

function setupDetailPhotoActions() {
  const fileInput = document.getElementById('detail-photo-input');
  document.getElementById('detail-add-photo')?.addEventListener('click', () => fileInput?.click());
  document.getElementById('detail-mobile-photo')?.addEventListener('click', () => openMobileUpload('listing', detailEditorState?.listing?.id));
  fileInput?.addEventListener('change', () => { uploadDetailPhotos(fileInput.files); fileInput.value = ''; });
  document.getElementById('detail-edit-photo')?.addEventListener('click', () => openPhotoEditor(detailEditorState.activePhotoIndex, 'detail'));
  document.getElementById('detail-set-cover')?.addEventListener('click', () => {
    const index = detailEditorState.activePhotoIndex;
    if (index <= 0 || !detailEditorState.photos[index]) return;
    const [photo] = detailEditorState.photos.splice(index, 1);
    detailEditorState.photos.unshift(photo);
    detailEditorState.activePhotoIndex = 0;
    renderDetailPhotos();
    toast('Coverfoto festgelegt');
  });
  document.getElementById('detail-download-photos')?.addEventListener('click', () => downloadPublishPhotos(detailEditorState?.photos || []));
  document.getElementById('detail-remove-photo')?.addEventListener('click', () => {
    const index = detailEditorState.activePhotoIndex;
    if (!detailEditorState.photos[index]) return;
    detailEditorState.photos.splice(index, 1);
    detailEditorState.activePhotoIndex = Math.max(0, Math.min(index, detailEditorState.photos.length - 1));
    renderDetailPhotos();
  });
}

async function showDetail(id) {
  const numericId = Number(id);
  if (!Number.isInteger(numericId) || numericId <= 0) {
    toast('Ungültige Listing-ID');
    return;
  }

  const historySection = document.getElementById('history');
  if (!historySection?.classList.contains('active')) {
    switchToTab('history');
    await new Promise(resolve => requestAnimationFrame(resolve));
  }

  const l = await api('GET', `/api/listings/${numericId}`);
  const tags = Array.isArray(l.tags) ? l.tags.join(', ') : (l.tags || '');
  const skuVal = l.sku || `VD-${String(l.id).padStart(4, '0')}`;
  detailEditorState = {
    listing: l,
    photos: [...(Array.isArray(l.photos) ? l.photos : [])],
    activePhotoIndex: 0,
    categoryPicker: null,
    platformHtml: '',
    aiModelPhotos: [],
    aiModelGenerations: [],
  };

  document.getElementById('detail-content').innerHTML = `
    <div class="listing-editor-shell">
      <header class="listing-editor-header">
        <div>
          <span class="listing-editor-kicker">Artikel vollständig bearbeiten</span>
          <h2>${escapeHtml(l.title || 'Unbenanntes Listing')}</h2>
          <p>${escapeHtml(skuVal)} · ${escapeHtml(new Date((l.created_at || '').replace(' ', 'T') + 'Z').toLocaleString('de-DE'))}</p>
        </div>
        <div class="listing-editor-header-actions">
          <button id="detail-copy-all" class="vd-button secondary" type="button">Alles kopieren</button>
          <button id="detail-save" class="vd-button primary" type="button">Änderungen speichern</button>
        </div>
      </header>

      <div class="listing-editor-grid">
        <aside class="listing-media-editor">
          <div class="listing-section-heading"><div><span>01</span><h3>Fotos &amp; Medien</h3></div><b id="detail-photo-count">0 / 10</b></div>
          <div class="detail-photo-stage">
            <img id="detail-photo-hero" class="hidden" alt="Produktfoto">
            <div id="detail-photo-empty" class="detail-photo-empty">Noch keine Fotos</div>
          </div>
          <div id="detail-photo-strip" class="detail-photo-strip"></div>
          <input id="detail-photo-input" type="file" accept="image/*" multiple hidden>
          <div class="detail-photo-actions">
            <button id="detail-add-photo" class="vd-button compact" type="button">Fotos hinzufügen</button>
            <button id="detail-mobile-photo" class="vd-button compact" type="button">Handy-Upload · QR</button>
            <button id="detail-edit-photo" class="vd-button compact" type="button">Bild bearbeiten</button>
            <button id="detail-set-cover" class="vd-button compact" type="button">Als Cover</button>
            <button id="detail-remove-photo" class="vd-button compact danger" type="button">Entfernen</button>
            <button id="detail-download-photos" class="vd-button compact" type="button">Fotos herunterladen</button>
          </div>
          <p class="detail-photo-help">Vorschaubilder ziehen, um die Reihenfolge zu ändern. Das erste Bild ist das Cover.</p>
          <section class="ai-model-card detail-ai-model-card">
            <div class="ai-model-card-head">
              <div>
                <span class="generator-panel-index">AI</span>
                <div>
                  <h4>Produktbild-Werkzeuge</h4>
                  <p>Erzeuge Ghost-Mannequin- oder Flatlay-Varianten aus den aktuellen Listing-Fotos. Freie Prompt-Bilder werden im FLUX Studio erstellt.</p>
                </div>
              </div>
              <span class="generator-beta">Safe</span>
            </div>
            <div class="ai-model-controls">
              <label>Look
                <select id="detail-ai-model-preset" class="model-select">
                  <option value="mixed">Mix</option>
                  <option value="studio">Studio</option>
                  <option value="indoor">Indoor</option>
                  <option value="outdoor">Outdoor</option>
                </select>
              </label>
              <label>Modus
                <select id="detail-ai-model-mode" class="model-select">
                  <option value="ghost_mannequin">Ghost Mannequin</option>
                  <option value="flatlay" selected>Flatlay</option>
                </select>
              </label>
              <label>Varianten
                <select id="detail-ai-model-variants" class="model-select">
                  <option value="1" selected>1 Bild</option>
                  <option value="2">2 Bilder</option>
                  <option value="3">3 Bilder</option>
                </select>
              </label>
              <button id="detail-ai-model-prompt-btn" class="small-btn ghost" type="button">Prompt…</button>
              <button id="detail-ai-model-btn" class="small-btn" type="button">Bild erzeugen</button>
            </div>
            <div id="detail-ai-model-status" class="ai-model-status">Nutzt die aktuellen Listing-Fotos als Vorlage.</div>
            <div id="detail-ai-model-grid" class="ai-model-grid hidden"></div>
            <div class="ai-generation-history-wrap">
              <div class="ai-generation-history-head"><strong>Historie</strong><span>Vorherige Generierungen schnell übernehmen</span></div>
              <div id="detail-ai-model-history" class="ai-generation-history"></div>
            </div>
          </section>
        </aside>

        <main class="listing-content-editor">
          <section class="listing-editor-section">
            <div class="listing-section-heading"><div><span>02</span><h3>Listing-Daten</h3></div></div>
            <div class="listing-form-grid three">
              <label>Plattform<select id="detail-platform">${detailPlatformOptions(l.platform)}</select></label>
              <label>AI-Provider<select id="detail-ai-provider">${detailProviderOptions(l.ai_provider)}</select></label>
              <label>Sprache<select id="detail-language">${detailLanguageOptions(l.language || 'de')}</select></label>
              <label>SKU<input id="detail-sku" value="${escapeHtml(skuVal)}"></label>
              <label>Status<select id="detail-status"><option value="active"${(l.status || 'active') === 'active' ? ' selected' : ''}>Aktiv</option><option value="sold"${l.status === 'sold' ? ' selected' : ''}>Verkauft</option><option value="reserved"${l.status === 'reserved' ? ' selected' : ''}>Reserviert</option></select></label>
              <label>Lagerort<input id="detail-storage-location" value="${escapeHtml(l.storage_location || '')}" placeholder="z. B. Regal A2"></label>
            </div>
            <label class="listing-full-field">Titel <span id="detail-title-count">${String(l.title || '').length} / 120</span><input id="detail-title" maxlength="120" value="${escapeHtml(l.title || '')}"></label>
            <label class="listing-full-field">Tags / Hashtags<input id="detail-tags" value="${escapeHtml(tags)}" placeholder="Tags durch Komma trennen"></label>
          </section>

          <section class="listing-editor-section listing-description-workbench">
            <div class="listing-section-heading"><div><span>03</span><h3>Beschreibung</h3></div><b id="detail-description-count">0 Zeichen</b></div>
            <div class="detail-description-pane detail-wysiwyg-pane"><div class="detail-pane-head"><strong>WYSIWYG</strong><span>Formatierte Bearbeitung</span></div><div id="detail-description-editor"></div></div>
            <div class="detail-platform-workbench">
              <div class="html-view-toolbar detail-html-toolbar">
                <div class="html-view-tabs" role="tablist" aria-label="Plattformdarstellung">
                  <button class="html-view-tab is-active" type="button" data-html-scope="detail" data-html-view="preview">Plattformvorschau</button>
                  <button class="html-view-tab" type="button" data-html-scope="detail" data-html-view="code">HTML-Code</button>
                </div>
                <span id="detail-html-sync-status">Synchron</span>
              </div>
              <div class="html-switch-stage detail-html-switch-stage">
                <div id="detail-preview-view" class="html-switch-view is-active" data-html-panel="preview"><iframe id="detail-html-preview" class="detail-html-preview" sandbox title="Plattformvorschau"></iframe></div>
                <div id="detail-code-view" class="html-switch-view" data-html-panel="code"><textarea id="detail-html-source" spellcheck="false" aria-label="HTML-Code der Beschreibung"></textarea></div>
              </div>
            </div>
          </section>

          <section class="listing-editor-section">
            <div class="listing-section-heading"><div><span>04</span><h3>Eigenschaften &amp; Preis</h3></div></div>
            <div class="listing-form-grid two">
              <label>Marke<input id="detail-brand" value="${escapeHtml(l.brand || '')}"></label>
              <label>Kategorie<div id="detail-category-picker" class="cat-picker"></div></label>
              <label>Größe<input id="detail-size" value="${escapeHtml(l.size || '')}"></label>
              <label>Farbe<input id="detail-color" value="${escapeHtml(l.color || '')}"></label>
              <label>Zustand<input id="detail-condition" value="${escapeHtml(l.condition || '')}"></label>
              <label>Preis (€)<input type="number" id="detail-price" step="0.01" min="0" value="${l.price ?? ''}"></label>
            </div>
            <label class="listing-full-field">Verkäufernotizen<textarea id="detail-seller-notes" rows="3">${escapeHtml(l.seller_notes || '')}</textarea></label>
          </section>
        </main>

        <aside class="listing-context-panel">
          <section class="listing-context-card">
            <h3>Artikelidentität</h3>
            <canvas id="detail-qr-canvas"></canvas>
            <strong id="detail-sku-display">${escapeHtml(skuVal)}</strong>
          </section>
          <section class="listing-context-card">
            <h3>HTML-Aktionen</h3>
            <button id="detail-copy-editor-html" class="vd-button full" type="button">Editor-HTML kopieren</button>
            <button id="detail-copy-platform-html" class="vd-button full" type="button">Plattform-HTML kopieren</button>
            <button id="detail-clean-html" class="vd-button full" type="button">HTML bereinigen</button>
          </section>
          <section class="listing-context-card">
            <h3>Ausgabe</h3>
            <button id="detail-print-label" class="vd-button full" type="button">Etikett erstellen</button>
            <button id="detail-print-sheet" class="vd-button full" type="button">Datenblatt erstellen</button>
          </section>
          <section class="listing-context-card danger-zone">
            <h3>Gefahrenzone</h3>
            <button id="detail-delete" class="vd-button full danger" type="button">In Papierkorb verschieben</button>
          </section>
        </aside>
      </div>
    </div>`;

  const lockResult = await acquireListingLock(numericId);
  if (!userCan('listings.edit') || lockResult.readOnly) {
    document.querySelectorAll('#history-detail input, #history-detail textarea, #history-detail select').forEach(element => { element.disabled = true; });
    document.getElementById('detail-save')?.setAttribute('disabled', 'disabled');
    if (!lockResult.lock && !userCan('listings.edit')) showListingLockBanner({ user_name: 'Deine Rolle erlaubt nur Lesezugriff' }, false);
  }

  editors.detail = initQuill('#detail-description-editor', 'Beschreibung...');
  const detailReadOnly = !userCan('listings.edit') || lockResult.readOnly;
  if (detailReadOnly) editors.detail?.enable?.(false);
  const initialHtml = l.description_html ? sanitizeRichHtmlClient(l.description_html) : plainTextToRichHtml(l.description || '');
  htmlSyncState.detailLock = true;
  setEditorHtml(editors.detail, initialHtml);
  htmlSyncState.detailLock = false;
  document.getElementById('detail-html-source').value = getEditorHtml(editors.detail);
  document.getElementById('detail-description-count').textContent = `${editors.detail ? editors.detail.getText().trim().length : 0} Zeichen`;
  detailEditorState.categoryPicker = createCategoryPicker('detail-category-picker', l.platform, l.category || null);
  setupDetailHtmlSync();
  bindHtmlViewTabs('detail');
  setupDetailPhotoActions();
  if (detailReadOnly) {
    const mutationButtons = [
      'detail-save', 'detail-delete', 'detail-add-photo', 'detail-mobile-photo',
      'detail-edit-photo', 'detail-set-cover', 'detail-remove-photo',
      'detail-ai-model-prompt-btn', 'detail-ai-model-btn', 'detail-clean-html',
    ];
    mutationButtons.forEach(id => {
      const element = document.getElementById(id);
      if (element) { element.disabled = true; element.setAttribute('aria-disabled', 'true'); }
    });
  }
  document.getElementById('detail-ai-model-btn')?.addEventListener('click', runDetailAiModelPhotos);
  document.getElementById('detail-ai-model-prompt-btn')?.addEventListener('click', () => openAiPromptModal('detail'));
  document.getElementById('detail-ai-model-mode')?.addEventListener('change', () => updateAiModeControls('detail'));
  updateAiModeControls('detail');
  renderDetailPhotos();
  renderAiModelGrid('detail', []);
  addQuillTooltips();
  refreshDetailPlatformPreview();
  loadDetailAiModelPhotos(numericId);
  loadDetailAiModelGenerations(numericId);

  if (typeof QRious !== 'undefined') {
    new QRious({ element: document.getElementById('detail-qr-canvas'), value: JSON.stringify({ sku: skuVal, id: l.id }), size: 118, level: 'M' });
  }

  const refreshFields = ['detail-title', 'detail-tags', 'detail-brand', 'detail-size', 'detail-color', 'detail-condition'];
  refreshFields.forEach(fieldId => document.getElementById(fieldId)?.addEventListener('input', refreshDetailPlatformPreview));
  document.getElementById('detail-title')?.addEventListener('input', event => {
    document.getElementById('detail-title-count').textContent = `${event.target.value.length} / 120`;
  });
  document.getElementById('detail-platform')?.addEventListener('change', event => {
    const currentCategory = detailEditorState.categoryPicker?.getValue?.() || null;
    detailEditorState.categoryPicker = createCategoryPicker('detail-category-picker', event.target.value, currentCategory);
    refreshDetailPlatformPreview();
  });
  document.getElementById('detail-sku')?.addEventListener('input', event => { document.getElementById('detail-sku-display').textContent = event.target.value || 'Ohne SKU'; });

  document.getElementById('detail-copy-editor-html').addEventListener('click', () => { navigator.clipboard.writeText(getEditorHtml(editors.detail)); toast('Editor-HTML kopiert'); });
  document.getElementById('detail-copy-platform-html').addEventListener('click', () => { navigator.clipboard.writeText(detailEditorState.platformHtml || getEditorHtml(editors.detail)); toast('Plattform-HTML kopiert'); });
  document.getElementById('detail-clean-html').addEventListener('click', () => {
    const source = document.getElementById('detail-html-source');
    const safe = sanitizeRichHtmlClient(source.value);
    source.value = safe;
    htmlSyncState.detailLock = true;
    setEditorHtml(editors.detail, safe);
    htmlSyncState.detailLock = false;
    refreshDetailPlatformPreview();
    toast('HTML bereinigt');
  });

  document.getElementById('detail-print-label').addEventListener('click', () => printLabels([{ ...l, photos: detailEditorState.photos, sku: document.getElementById('detail-sku').value, title: document.getElementById('detail-title').value, price: parseFloat(document.getElementById('detail-price').value) || null }]));
  document.getElementById('detail-print-sheet').addEventListener('click', () => printDatasheet({ ...l, photos: detailEditorState.photos, sku: document.getElementById('detail-sku').value, title: document.getElementById('detail-title').value, description: editors.detail?.getText().trim() || '', price: parseFloat(document.getElementById('detail-price').value) || null }));

  document.getElementById('detail-save').addEventListener('click', async () => {
    const descriptionHtml = getEditorHtml(editors.detail);
    const update = {
      platform: document.getElementById('detail-platform').value,
      ai_provider: document.getElementById('detail-ai-provider').value,
      language: document.getElementById('detail-language').value,
      title: document.getElementById('detail-title').value,
      description: richHtmlToText(descriptionHtml),
      description_html: descriptionHtml,
      tags: document.getElementById('detail-tags').value.split(',').map(tag => tag.trim()).filter(Boolean),
      photos: [...detailEditorState.photos],
      seller_notes: document.getElementById('detail-seller-notes').value || null,
      status: document.getElementById('detail-status').value,
      sku: document.getElementById('detail-sku').value || null,
      brand: document.getElementById('detail-brand').value || null,
      category: detailEditorState.categoryPicker?.getValue?.() || null,
      size: document.getElementById('detail-size').value || null,
      color: document.getElementById('detail-color').value || null,
      condition: document.getElementById('detail-condition').value || null,
      price: parseFloat(document.getElementById('detail-price').value) || null,
      storage_location: document.getElementById('detail-storage-location').value || null,
    };
    try {
      const saved = await api('PUT', `/api/listings/${numericId}`, { ...update, _edit_lock: listingEditLock.listingId === numericId ? listingEditLock.token : null });
      detailEditorState.listing = saved;
      toast('Alle Änderungen gespeichert');
      loadHistory();
    } catch (err) { toast('Fehler: ' + err.message); }
  });

  document.getElementById('detail-copy-all').addEventListener('click', () => {
    const content = `${document.getElementById('detail-title').value}\n\n${editors.detail?.getText().trim() || ''}\n\n${document.getElementById('detail-tags').value}`;
    navigator.clipboard.writeText(content);
    toast('Alles kopiert!');
  });

  document.getElementById('detail-delete').addEventListener('click', async () => {
    if (!confirm('Listing in den Papierkorb verschieben?')) return;
    await api('DELETE', `/api/listings/${numericId}`);
    toast('In Papierkorb verschoben');
    updateTrashBadge();
    document.getElementById('back-to-list').click();
    loadHistory();
  });

  setHistoryDetailMode(true);
  document.getElementById('history-detail')?.scrollIntoView({ block: 'start' });
}

// --- Templates ---
function setupTemplates() {
  document.getElementById('add-template-btn').addEventListener('click', () => {
    state.editingTemplateId = null;
    document.getElementById('template-form-title').textContent = 'Neue Vorlage';
    document.getElementById('tpl-name').value = '';
    document.getElementById('tpl-platform').value = '';
    document.getElementById('tpl-language').value = 'de';
    if (editors.tplNotes) editors.tplNotes.setText('');
    document.getElementById('tpl-tags').value = '';
    document.getElementById('template-form').classList.remove('hidden');
  });
  document.getElementById('cancel-template-btn').addEventListener('click', () => {
    document.getElementById('template-form').classList.add('hidden');
  });
  document.getElementById('save-template-btn').addEventListener('click', async () => {
    const data = {
      name: document.getElementById('tpl-name').value,
      platform: document.getElementById('tpl-platform').value || null,
      language: document.getElementById('tpl-language').value,
      seller_notes: editors.tplNotes ? editors.tplNotes.getText().trim() : '',
      default_tags: document.getElementById('tpl-tags').value.split(',').map(t => t.trim()).filter(Boolean),
    };
    if (!data.name) { toast('Name ist Pflichtfeld'); return; }
    try {
      if (state.editingTemplateId) {
        await api('PUT', `/api/templates/${state.editingTemplateId}`, data);
      } else {
        await api('POST', '/api/templates', data);
      }
      state.templates = await api('GET', '/api/templates');
      renderTemplateList();
      updateTemplateDropdown();
      document.getElementById('template-form').classList.add('hidden');
      toast('Vorlage gespeichert!');
    } catch (err) { toast('Fehler: ' + err.message); }
  });
}

function renderTemplateList() {
  const c = document.getElementById('template-list');
  if (!state.templates.length) { c.innerHTML = '<div class="empty-state">Keine Vorlagen vorhanden</div>'; return; }
  c.innerHTML = state.templates.map(t => {
    const pName = state.platforms.find(p => p.id === t.platform)?.name || 'Alle Plattformen';
    return `<div class="template-item">
      <div class="tpl-info">
        <div class="tpl-name">${escapeHtml(t.name)}</div>
        <div class="tpl-meta">${pName} · ${t.language.toUpperCase()}</div>
      </div>
      <div class="tpl-actions">
        <button class="small-btn tpl-edit" data-id="${t.id}">Bearbeiten</button>
        <button class="small-btn tpl-delete" data-id="${t.id}">Löschen</button>
      </div>
    </div>`;
  }).join('');

  c.querySelectorAll('.tpl-edit').forEach(btn => {
    btn.addEventListener('click', () => {
      const t = state.templates.find(x => x.id === parseInt(btn.dataset.id));
      if (!t) return;
      state.editingTemplateId = t.id;
      document.getElementById('template-form-title').textContent = 'Vorlage bearbeiten';
      document.getElementById('tpl-name').value = t.name;
      document.getElementById('tpl-platform').value = t.platform || '';
      document.getElementById('tpl-language').value = t.language || 'de';
      if (editors.tplNotes) editors.tplNotes.setText(t.seller_notes || '');
      document.getElementById('tpl-tags').value = (t.default_tags || []).join(', ');
      document.getElementById('template-form').classList.remove('hidden');
    });
  });
  c.querySelectorAll('.tpl-delete').forEach(btn => {
    btn.addEventListener('click', async () => {
      if (!confirm('Vorlage löschen?')) return;
      await api('DELETE', `/api/templates/${btn.dataset.id}`);
      state.templates = await api('GET', '/api/templates');
      renderTemplateList();
      updateTemplateDropdown();
      toast('Vorlage gelöscht');
    });
  });
}

function updateTemplateDropdown() {
  const sel = document.getElementById('template-select');
  sel.innerHTML = '<option value="">Keine Vorlage</option>' +
    state.templates.map(t => `<option value="${t.id}">${escapeHtml(t.name)}</option>`).join('');
}

function populateTemplateSelects(platforms) {
  const sel = document.getElementById('tpl-platform');
  sel.innerHTML = '<option value="">Alle</option>' +
    platforms.map(p => `<option value="${p.id}">${p.name}</option>`).join('');
}

// --- Inventory / Storage ---
async function loadInventory() {
  const locations = await api('GET', '/api/inventory/locations');
  const unassigned = await api('GET', '/api/inventory/unassigned');

  const statsEl = document.getElementById('inv-stats');
  const totalItems = locations.reduce((s, l) => s + l.count, 0) + unassigned.length;
  const totalActive = locations.reduce((s, l) => s + l.active, 0) + unassigned.filter(x => x.status === 'active').length;
  const totalValue = locations.reduce((s, l) => s + l.total_value, 0) + unassigned.filter(x => x.status === 'active').reduce((s, x) => s + (x.price || 0), 0);
  statsEl.innerHTML = `
    <div class="inv-stat-card"><div class="inv-stat-value">${totalItems}</div><div class="inv-stat-label">Artikel gesamt</div></div>
    <div class="inv-stat-card"><div class="inv-stat-value">${locations.length}</div><div class="inv-stat-label">Lagerorte</div></div>
    <div class="inv-stat-card"><div class="inv-stat-value">${totalActive}</div><div class="inv-stat-label">Aktive Artikel</div></div>
    <div class="inv-stat-card"><div class="inv-stat-value">${totalValue.toFixed(0)} €</div><div class="inv-stat-label">Lagerwert</div></div>`;

  const locsEl = document.getElementById('inv-locations');
  let html = '';
  for (const loc of locations) {
    html += `<div class="inv-location-card" data-location="${escapeHtml(loc.storage_location)}">
      <div class="inv-loc-header">
        <div class="inv-loc-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg></div>
        <div class="inv-loc-name">${escapeHtml(loc.storage_location)}</div>
      </div>
      <div class="inv-loc-stats">
        <span class="inv-loc-count">${loc.count} Artikel</span>
        <span class="inv-loc-active">${loc.active} aktiv</span>
        ${loc.sold ? `<span class="inv-loc-sold">${loc.sold} verkauft</span>` : ''}
        ${loc.reserved ? `<span class="inv-loc-reserved">${loc.reserved} reserviert</span>` : ''}
      </div>
      <div class="inv-loc-value">${loc.total_value.toFixed(0)} €</div>
    </div>`;
  }
  if (unassigned.length) {
    html += `<div class="inv-location-card inv-unassigned" data-location="">
      <div class="inv-loc-header">
        <div class="inv-loc-icon"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
        <div class="inv-loc-name">Ohne Lagerort</div>
      </div>
      <div class="inv-loc-stats"><span class="inv-loc-count">${unassigned.length} Artikel</span></div>
    </div>`;
  }
  if (!html) html = '<div class="empty-state">Noch keine Lagerorte vorhanden. Weise Artikeln einen Lagerort zu.</div>';
  locsEl.innerHTML = html;

  document.getElementById('inv-detail').classList.add('hidden');
  locsEl.classList.remove('hidden');

  locsEl.querySelectorAll('.inv-location-card').forEach(card => {
    card.addEventListener('click', () => showInventoryDetail(card.dataset.location));
  });

  document.getElementById('inv-add-location-btn').onclick = () => {
    const name = prompt('Neuer Lagerort (z.B. Regal A1, Karton 5, Schrank oben):');
    if (!name?.trim()) return;
    toast(`Lagerort "${name.trim()}" angelegt. Weise jetzt Artikel zu.`);
    loadInventory();
  };
}

async function showInventoryDetail(location) {
  const items = location
    ? await api('GET', `/api/inventory/location/${encodeURIComponent(location)}`)
    : await api('GET', '/api/inventory/unassigned');

  document.getElementById('inv-locations').classList.add('hidden');
  const detail = document.getElementById('inv-detail');
  detail.classList.remove('hidden');
  document.getElementById('inv-detail-title').textContent = location || 'Ohne Lagerort';

  const list = document.getElementById('inv-detail-list');
  if (!items.length) {
    list.innerHTML = '<div class="empty-state">Keine Artikel an diesem Lagerort</div>';
  } else {
    list.innerHTML = items.map(l => {
      const pName = state.platforms.find(p => p.id === l.platform)?.name || l.platform;
      const statusCls = l.status === 'sold' ? 'sold' : l.status === 'reserved' ? 'reserved' : '';
      return `<div class="history-item">
        <input type="checkbox" class="inv-item-check" data-id="${l.id}">
        <div class="listing-info">
          <div class="listing-title">${l.sku ? `<span class="sku-badge">${escapeHtml(l.sku)}</span>` : ''} ${escapeHtml(l.title || 'Ohne Titel')}</div>
          <div class="listing-meta">${pName} · ${l.price ? l.price.toFixed(2) + ' €' : '–'} · <span class="status-dot ${statusCls}">${l.status || 'active'}</span></div>
        </div>
      </div>`;
    }).join('');
  }

  document.getElementById('inv-back').onclick = () => loadInventory();

  document.getElementById('inv-rename-btn').onclick = async () => {
    if (!location) return toast('Ohne Lagerort kann nicht umbenannt werden');
    const newName = prompt('Lagerort umbenennen:', location);
    if (!newName?.trim() || newName.trim() === location) return;
    const ids = items.map(l => l.id);
    await api('PUT', '/api/inventory/bulk-location', { ids, location: newName.trim() });
    toast(`Umbenannt zu "${newName.trim()}"`);
    loadInventory();
  };

  document.getElementById('inv-move-btn').onclick = async () => {
    const checked = [...detail.querySelectorAll('.inv-item-check:checked')].map(c => parseInt(c.dataset.id));
    if (!checked.length) return toast('Bitte Artikel auswählen');
    const dest = prompt('Ziel-Lagerort:');
    if (!dest?.trim()) return;
    await api('PUT', '/api/inventory/bulk-location', { ids: checked, location: dest.trim() });
    toast(`${checked.length} Artikel verschoben nach "${dest.trim()}"`);
    loadInventory();
  };
}

// --- Fee Calculator ---
function setupFeeCalculator() {
  document.getElementById('fee-price').addEventListener('input', debounce(updateFeeCalculator, 200));
}

async function updateFeeCalculator() {
  const price = parseFloat(document.getElementById('fee-price').value) || 0;
  if (price <= 0) { document.getElementById('fee-results').innerHTML = ''; return; }
  try {
    const fees = await api('GET', `/api/fees?price=${price}`);
    const c = document.getElementById('fee-results');
    c.innerHTML = Object.entries(fees).map(([id, f]) => {
      const name = state.platforms.find(p => p.id === id)?.name || id;
      return `<div class="fee-card${Number(f.fees) === 0 ? ' is-free' : ''}">
        <div>
          <div class="fee-platform">${escapeHtml(name)} <span>${f.seller_type === 'privat' ? 'Privat' : 'Shop'}</span></div>
          <div class="fee-breakdown">${escapeHtml(f.breakdown || '')}</div>
          ${f.note ? `<div class="fee-note">${escapeHtml(f.note)}</div>` : ''}
        </div>
        <div class="fee-amounts">
          <div class="fee-net-amount">${Number(f.net || 0).toFixed(2)} €</div>
          <div class="fee-deducted">${Number(f.fees) === 0 ? 'Keine Verkäufergebühr' : `-${Number(f.fees).toFixed(2)} € Gebühren`}</div>
        </div>
      </div>`;
    }).join('');
  } catch {}
}

// --- Publish Center ---
const publishState = {
  selected: new Set(),
  listings: [],
  allFiltered: [],
  publishStatus: {},
  queue: [],
  scheduled: [],
  workflow: null,
  integrations: { ebay: null, etsy: null },
  selectedId: null,
  view: 'smart',
  renderToken: 0,
  smartCopyToken: 0,
};

function setupPublish() {
  const search = document.getElementById('publish-search');
  if (!search) return;

  search.addEventListener('input', debounce(loadPublishList, 250));
  document.getElementById('publish-filter-platform')?.addEventListener('change', loadPublishList);
  document.getElementById('publish-filter-status')?.addEventListener('change', loadPublishList);
  document.getElementById('publish-sort')?.addEventListener('change', () => {
    sortPublishListings();
    renderPublishList();
  });
  document.getElementById('publish-select-all')?.addEventListener('change', event => {
    for (const listing of publishState.listings) {
      if (event.target.checked) publishState.selected.add(listing.id);
      else publishState.selected.delete(listing.id);
    }
    renderPublishList();
    updatePublishToolbar();
  });

  const platformSelect = document.getElementById('publish-filter-platform');
  platformSelect.innerHTML = '<option value="">Alle Plattformen</option>' +
    state.platforms.map(platform => `<option value="${platform.id}">${escapeHtml(platform.name)}</option>`).join('');

  document.querySelectorAll('[data-publish-view]').forEach(button => {
    button.addEventListener('click', () => {
      setPublishView(button.dataset.publishView);
      if (button.dataset.publishView === 'smart' && selectedPublishListing()) openSmartCopyModal(selectedPublishListing());
    });
  });
  document.querySelectorAll('[data-status-target]').forEach(button => {
    button.addEventListener('click', () => setPublishView(button.dataset.statusTarget));
  });

  document.getElementById('publish-refresh')?.addEventListener('click', loadPublishList);
  document.getElementById('publish-status-refresh')?.addEventListener('click', loadPublishList);
  document.getElementById('publish-batch-copy')?.addEventListener('click', batchSmartCopy);
  document.getElementById('publish-batch-etsy')?.addEventListener('click', batchEtsyPublish);
  document.getElementById('publish-batch-ebay')?.addEventListener('click', batchEbayAdd);
  document.getElementById('publish-add-selected-ebay')?.addEventListener('click', batchEbayAdd);
  document.getElementById('publish-batch-schedule')?.addEventListener('click', openBatchSchedule);
  document.getElementById('publish-download-photos')?.addEventListener('click', batchDownloadPhotos);
  document.getElementById('publish-ebay-queue-btn')?.addEventListener('click', openEbayQueue);
  document.getElementById('publish-schedule-overview')?.addEventListener('click', openScheduleOverview);
  document.getElementById('publish-ebay-queue-process')?.addEventListener('click', processEbayQueue);
  document.getElementById('publish-ebay-queue-retry')?.addEventListener('click', retryEbayQueue);

  document.getElementById('schedule-modal-close')?.addEventListener('click', closePublishSchedule);
  document.getElementById('schedule-cancel')?.addEventListener('click', closePublishSchedule);
  document.getElementById('schedule-modal')?.addEventListener('click', event => {
    if (event.target === event.currentTarget) closePublishSchedule();
  });
  document.querySelectorAll('[data-smart-copy-close]').forEach(button => button.addEventListener('click', closeSmartCopyModal));
  document.addEventListener('keydown', event => {
    if (event.key !== 'Escape') return;
    if (isSmartCopyModalOpen()) closeSmartCopyModal();
    if (!document.getElementById('flux-prompt-lab')?.classList.contains('hidden')) closeFluxPromptLab();
  });
}

async function publishSafeGet(url, fallback, attempts = 1) {
  let lastError = null;
  for (let attempt = 0; attempt < attempts; attempt++) {
    try { return await api('GET', url); }
    catch (error) {
      lastError = error;
      if (attempt < attempts - 1) await new Promise(resolve => setTimeout(resolve, 220 * (attempt + 1)));
    }
  }
  if (fallback === PUBLISH_REQUIRED) throw lastError || new Error('Daten konnten nicht geladen werden');
  return fallback;
}

const PUBLISH_REQUIRED = Symbol('publish-required');

async function loadPublishListLegacy() {
  const search = document.getElementById('publish-search');
  const listContainer = document.getElementById('publish-list');
  if (!search || !listContainer) return;
  const renderToken = ++publishState.renderToken;

  const q = search.value.trim();
  const platform = document.getElementById('publish-filter-platform')?.value || '';
  const status = document.getElementById('publish-filter-status')?.value || '';
  const params = new URLSearchParams();
  if (q) params.set('q', q);
  if (platform) params.set('platform', platform);
  if (status) params.set('status', status);

  const refreshButtons = [document.getElementById('publish-refresh'), document.getElementById('publish-status-refresh')];
  const refreshLabels = refreshButtons.map(button => button?.textContent || '');
  refreshButtons.forEach((button, index) => { if (!button) return; button.disabled = true; button.setAttribute('aria-busy', 'true'); button.textContent = index === 0 ? 'Wird geladen …' : '…'; });
  if (!publishState.listings.length) listContainer.innerHTML = '<div class="publish-loading-state"><span></span><strong>Artikel werden geladen…</strong></div>';

  try {
    const query = params.toString();
    const [listings, queue, scheduled, workflow, ebayStatus, etsyStatus] = await Promise.all([
      publishSafeGet(`/api/listings${query ? `?${query}` : ''}`, PUBLISH_REQUIRED, 3),
      publishSafeGet('/api/ebay/queue', [], 2),
      publishSafeGet('/api/schedule', [], 2),
      publishSafeGet('/api/dashboard/workflow?limit=4', null, 2),
      publishSafeGet('/api/ebay/status', { connected: false, hasCredentials: false }, 2),
      publishSafeGet('/api/etsy/status', { connected: false, hasApiKey: false }, 2),
    ]);
    if (renderToken !== publishState.renderToken) return;

    publishState.allFiltered = Array.isArray(listings) ? listings : [];
    publishState.listings = [...publishState.allFiltered];
    publishState.queue = Array.isArray(queue) ? queue : [];
    publishState.scheduled = Array.isArray(scheduled) ? scheduled : [];
    publishState.workflow = workflow;
    publishState.integrations = { ebay: ebayStatus, etsy: etsyStatus };

    const ids = publishState.listings.map(listing => listing.id);
    publishState.publishStatus = ids.length
      ? await publishSafeGet(`/api/publish/status?ids=${ids.join(',')}`, {}, 2)
      : {};
    if (renderToken !== publishState.renderToken) return;

    sortPublishListings();
    const visibleIds = new Set(publishState.listings.map(listing => listing.id));
    if (!publishState.selectedId || !visibleIds.has(publishState.selectedId)) {
      const selectedVisible = [...publishState.selected].find(id => visibleIds.has(id));
      publishState.selectedId = selectedVisible || publishState.listings[0]?.id || null;
    }

    renderPublishList();
    renderPublishWorkspace();
    renderPublishStatusRail();
    updatePublishToolbar();
  } catch (error) {
    if (renderToken !== publishState.renderToken) return;
    toast(`Publish-Center konnte nicht geladen werden: ${error.message}`);
    listContainer.innerHTML = `<div class="publish-empty-state"><strong>Artikel konnten nicht geladen werden</strong><span>${escapeHtml(error.message)}</span><button type="button" class="vd-button compact" data-publish-retry>Erneut laden</button></div>`;
    listContainer.querySelector('[data-publish-retry]')?.addEventListener('click', loadPublishList);
  } finally {
    if (renderToken === publishState.renderToken) refreshButtons.forEach((button, index) => { if (!button) return; button.disabled = false; button.removeAttribute('aria-busy'); button.textContent = refreshLabels[index]; });
  }
}

function sortPublishListings() {
  const sort = document.getElementById('publish-sort')?.value || 'newest';
  const byDate = value => new Date(value || 0).getTime() || 0;
  publishState.listings.sort((a, b) => {
    if (sort === 'oldest') return byDate(a.created_at) - byDate(b.created_at);
    if (sort === 'price-desc') return (Number(b.price) || 0) - (Number(a.price) || 0);
    if (sort === 'price-asc') return (Number(a.price) || 0) - (Number(b.price) || 0);
    if (sort === 'title') return String(a.title || '').localeCompare(String(b.title || ''), 'de');
    return byDate(b.created_at) - byDate(a.created_at);
  });
}

function publishLatestStatus(listing) {
  const statuses = publishState.publishStatus[listing.id] || {};
  const entries = Object.entries(statuses);
  if (!entries.length) return { label: 'Vorbereitet', status: 'prepared', platform: listing.platform };
  const priority = { failed: 0, uploading: 1, pending: 2, copied: 3, published: 4 };
  entries.sort((a, b) => (priority[a[1]] ?? 9) - (priority[b[1]] ?? 9));
  const [platform, status] = entries[0];
  const labels = { failed: 'Fehlgeschlagen', uploading: 'In Bearbeitung', pending: 'Ausstehend', copied: 'Kopiert', published: 'Live' };
  return { label: labels[status] || status, status, platform };
}

function renderPublishList() {
  const container = document.getElementById('publish-list');
  if (!container) return;
  document.getElementById('publish-list-count').textContent = publishState.listings.length;

  if (!publishState.listings.length) {
    container.innerHTML = '<div class="publish-empty-state"><strong>Keine Artikel gefunden</strong><span>Ändere Suche oder Filter.</span></div>';
    updatePublishToolbar();
    return;
  }

  container.innerHTML = publishState.listings.map(listing => {
    const photo = listing.photos?.[0];
    const status = publishLatestStatus(listing);
    const selected = listing.id === publishState.selectedId;
    const checked = publishState.selected.has(listing.id);
    return `<article class="publish-list-card${selected ? ' is-active' : ''}" data-publish-listing-id="${listing.id}">
      <label class="publish-card-check" title="Auswählen"><input type="checkbox" data-publish-check="${listing.id}" ${checked ? 'checked' : ''}></label>
      <div class="publish-card-media">${photo ? `<img src="${photoUrl(photo)}" alt="">` : '<span>Kein Bild</span>'}</div>
      <div class="publish-card-copy">
        <strong>${escapeHtml(listing.title || 'Ohne Titel')}</strong>
        <span class="publish-card-sku">${escapeHtml(listing.sku || `VD-${String(listing.id).padStart(4, '0')}`)}</span>
        <div class="publish-card-meta"><span>${escapeHtml(platformLabel(listing.platform))}</span><b>${formatCurrency(listing.price) || 'Preis fehlt'}</b></div>
      </div>
      <span class="publish-card-status status-${escapeHtml(status.status)}">${escapeHtml(status.label)}</span>
    </article>`;
  }).join('');

  container.querySelectorAll('[data-publish-listing-id]').forEach(card => {
    card.addEventListener('click', event => {
      if (event.target.closest('input,button,a,label')) return;
      publishState.selectedId = Number(card.dataset.publishListingId);
      renderPublishList();
      renderPublishWorkspace();
    });
  });

  container.querySelectorAll('[data-publish-check]').forEach(checkbox => {
    checkbox.addEventListener('change', event => {
      const id = Number(event.target.dataset.publishCheck);
      if (event.target.checked) publishState.selected.add(id);
      else publishState.selected.delete(id);
      if (!publishState.selectedId) publishState.selectedId = id;
      updatePublishToolbar();
    });
  });
}

function updatePublishToolbar() {
  const count = publishState.selected.size;
  const selectedLabel = document.getElementById('publish-selected-count');
  if (selectedLabel) selectedLabel.textContent = `${count} ausgewählt`;
  ['publish-batch-copy', 'publish-batch-etsy', 'publish-batch-ebay', 'publish-batch-schedule', 'publish-download-photos', 'publish-add-selected-ebay']
    .forEach(id => { const button = document.getElementById(id); if (button) button.disabled = count === 0; });

  const visible = publishState.listings;
  const selectedVisible = visible.filter(listing => publishState.selected.has(listing.id)).length;
  const selectAll = document.getElementById('publish-select-all');
  if (selectAll) {
    selectAll.checked = visible.length > 0 && selectedVisible === visible.length;
    selectAll.indeterminate = selectedVisible > 0 && selectedVisible < visible.length;
  }
}

function setPublishViewLegacy(view) {
  publishState.view = ['smart', 'direct', 'queue', 'scheduled'].includes(view) ? view : 'smart';
  document.querySelectorAll('[data-publish-view]').forEach(button => {
    const active = button.dataset.publishView === publishState.view;
    button.classList.toggle('active', active);
    button.setAttribute('aria-selected', String(active));
  });
  renderPublishWorkspace();
}

function selectedPublishListing() {
  return publishState.listings.find(listing => listing.id === publishState.selectedId)
    || publishState.allFiltered.find(listing => listing.id === publishState.selectedId)
    || null;
}

function renderPublishWorkspaceLegacy() {
  if (publishState.view === 'queue') { renderQueueWorkspace(); return; }
  if (publishState.view === 'scheduled') { renderScheduledWorkspace(); return; }

  const listing = selectedPublishListing();
  const workspace = document.getElementById('publish-workspace');
  if (!workspace) return;
  if (!listing) {
    workspace.innerHTML = `<div class="publish-workspace-empty"><span>↗</span><h3>Artikel auswählen</h3><p>Wähle links einen Artikel, um ihn für die Veröffentlichung vorzubereiten.</p></div>`;
    return;
  }
  if (publishState.view === 'direct') renderDirectPublishWorkspace(listing);
  else renderSmartCopyLauncher(listing);
}


function renderSmartCopyLauncher(listing) {
  const workspace = document.getElementById('publish-workspace');
  const target = publishPlatformTarget(listing.platform);
  const score = publishReadinessScore(listing);
  const tags = Array.isArray(listing.tags) ? listing.tags : String(listing.tags || '').split(',').map(value => value.trim()).filter(Boolean);
  workspace.innerHTML = `${publishWorkspaceHeader(listing, 'Smart Copy', 'Bereit')}
    <div class="smart-copy-center">
      <section class="smart-copy-center-main">
        <div class="smart-copy-center-heading"><div><span class="publish-panel-kicker">Verkaufsdaten</span><h3>${escapeHtml(listing.title || 'Ohne Titel')}</h3></div><button type="button" class="vd-button primary" data-open-smart-copy-modal>${window.VendooIcons?.svg('external-link', { size: 17 }) || ''} Smart Copy öffnen</button></div>
        <div class="smart-copy-center-description">${escapeHtml(listing.description || 'Noch keine Beschreibung vorhanden.').replace(/\n/g, '<br>')}</div>
        <div class="smart-copy-center-tags">${tags.length ? tags.slice(0, 12).map(tag => `<span>${escapeHtml(tag)}</span>`).join('') : '<span>Keine Tags</span>'}</div>
        <div class="smart-copy-center-meta">
          <div><span>Plattform</span><strong>${escapeHtml(target.name)}</strong></div><div><span>Preis</span><strong>${escapeHtml(formatCurrency(listing.price) || 'Nicht gesetzt')}</strong></div>
          <div><span>Zustand</span><strong>${escapeHtml(listing.condition || 'Nicht gesetzt')}</strong></div><div><span>Marke</span><strong>${escapeHtml(listing.brand || 'Nicht gesetzt')}</strong></div>
          <div><span>Kategorie</span><strong>${escapeHtml(listing.category || 'Nicht gesetzt')}</strong></div><div><span>Größe</span><strong>${escapeHtml(listing.size || 'Nicht gesetzt')}</strong></div>
        </div>
      </section>
      <aside class="smart-copy-center-side">
        <div class="smart-copy-launcher-score"><strong>${score}%</strong><span>Artikel vollständig</span></div>
        <section class="publish-photo-section"><div class="publish-field-head"><label>Fotos</label><span>${listing.photos?.length || 0}</span></div>${publishPhotoGrid(listing.photos || [], 8)}</section>
        <p>Der Smart-Copy-Link öffnet das vollständige Popup mit allen Kopierfeldern, Fotos und Plattformaktionen.</p>
      </aside>
    </div>`;
  bindPublishWorkspaceNavigation(workspace);
  workspace.querySelector('[data-open-smart-copy-modal]')?.addEventListener('click', () => openSmartCopyModal(listing));
  window.VendooIcons?.render(workspace);
}

function isSmartCopyModalOpen() {
  return !document.getElementById('smart-copy-modal')?.classList.contains('hidden');
}

function closeSmartCopyModal() {
  const modal = document.getElementById('smart-copy-modal');
  modal?.classList.add('hidden');
  modal?.setAttribute('aria-hidden', 'true');
  document.getElementById('smart-copy-modal-content')?.replaceChildren();
  if (document.getElementById('flux-prompt-lab')?.classList.contains('hidden')) document.body.classList.remove('modal-open');
}

function openSmartCopyModal(listing = selectedPublishListing()) {
  if (!listing) return toast('Bitte zuerst ein Artikel auswählen.');
  const modal = document.getElementById('smart-copy-modal');
  modal?.classList.remove('hidden');
  modal?.setAttribute('aria-hidden', 'false');
  document.body.classList.add('modal-open');
  renderSmartCopyWorkspace(listing);
}

function publishWorkspaceHeader(listing, kicker, stateLabel) {
  const photo = listing.photos?.[0];
  return `<header class="publish-workspace-header">
    <div class="publish-workspace-product">
      <div class="publish-workspace-thumb">${photo ? `<img src="${photoUrl(photo)}" alt="">` : '<span>Kein Bild</span>'}</div>
      <div><span class="publish-panel-kicker">${escapeHtml(kicker)}</span><h3>${escapeHtml(listing.title || 'Ohne Titel')}</h3><p>${escapeHtml(listing.sku || `VD-${String(listing.id).padStart(4, '0')}`)} · ${escapeHtml(platformLabel(listing.platform))}</p></div>
    </div>
    <div class="publish-workspace-header-actions">
      <span class="publish-workspace-state">${escapeHtml(stateLabel)}</span>
      <button class="icon-button" type="button" data-publish-previous title="Vorheriger Artikel">‹</button>
      <button class="icon-button" type="button" data-publish-next title="Nächster Artikel">›</button>
    </div>
  </header>`;
}

function bindPublishWorkspaceNavigation(scope = document) {
  const currentIndex = publishState.listings.findIndex(listing => listing.id === publishState.selectedId);
  scope.querySelector('[data-publish-previous]')?.addEventListener('click', () => {
    if (!publishState.listings.length) return;
    const nextIndex = currentIndex <= 0 ? publishState.listings.length - 1 : currentIndex - 1;
    publishState.selectedId = publishState.listings[nextIndex].id;
    renderPublishList();
    if (isSmartCopyModalOpen()) renderSmartCopyWorkspace(selectedPublishListing()); else renderPublishWorkspace();
  });
  scope.querySelector('[data-publish-next]')?.addEventListener('click', () => {
    if (!publishState.listings.length) return;
    const nextIndex = currentIndex >= publishState.listings.length - 1 ? 0 : currentIndex + 1;
    publishState.selectedId = publishState.listings[nextIndex].id;
    renderPublishList();
    if (isSmartCopyModalOpen()) renderSmartCopyWorkspace(selectedPublishListing()); else renderPublishWorkspace();
  });
}

function publishPlatformTarget(platform) {
  return ({
    vinted: { name: 'Vinted', url: 'https://www.vinted.de/items/new', direct: false },
    'ebay-ka': { name: 'Kleinanzeigen', url: 'https://www.kleinanzeigen.de/p-anzeige-aufgeben.html', direct: false },
    'ebay-de': { name: 'eBay.de', url: 'https://www.ebay.de/sell/create', direct: true },
    etsy: { name: 'Etsy', url: 'https://www.etsy.com/your/shops/me/tools/listings/create', direct: true },
  })[platform] || { name: platformLabel(platform), url: null, direct: false };
}

function publishPhotoGrid(photos, limit = 6) {
  if (!photos?.length) return '<div class="publish-no-photos">Keine Fotos hinterlegt</div>';
  const visible = photos.slice(0, limit);
  return `<div class="publish-photo-grid">${visible.map(photo => `<img src="${photoUrl(photo)}" alt="">`).join('')}${photos.length > limit ? `<span>+${photos.length - limit} weitere</span>` : ''}</div>`;
}

async function renderSmartCopyWorkspace(listing) {
  const workspace = document.getElementById('smart-copy-modal-content');
  if (!workspace) return;
  const token = ++publishState.smartCopyToken;
  workspace.innerHTML = `${publishWorkspaceHeader(listing, 'Smart Copy', 'Wird vorbereitet')}<div class="publish-workspace-loading"><span></span><strong>Daten werden vorbereitet …</strong></div>`;
  bindPublishWorkspaceNavigation(workspace);

  try {
    const data = await api('POST', '/api/publish/smart-copy', { listing_id: listing.id });
    if (token !== publishState.smartCopyToken || !isSmartCopyModalOpen()) return;
    const fields = data.fields || {};
    const target = publishPlatformTarget(listing.platform);
    const tags = Array.isArray(fields.tags) ? fields.tags.join(', ') : (fields.tags || '');

    workspace.innerHTML = `${publishWorkspaceHeader(listing, 'Smart Copy', 'Bereit zum Kopieren')}
      <div class="publish-smart-grid">
        <div class="publish-editor-column">
          <section class="publish-editor-section">
            <div class="publish-field-head"><label>Titel</label><span>${String(fields.title || '').length} Zeichen</span></div>
            <div class="publish-copy-field"><input id="publish-smart-title" value="${escapeHtml(fields.title || '')}" readonly><button type="button" data-copy-publish-field="publish-smart-title">Kopieren</button></div>
          </section>
          <section class="publish-editor-section">
            <div class="publish-field-head"><label>Beschreibung</label><div><button type="button" data-copy-publish-description>Text kopieren</button><button type="button" data-copy-publish-html>HTML kopieren</button></div></div>
            <textarea id="publish-smart-description" readonly>${escapeHtml(fields.description || '')}</textarea>
          </section>
          <section class="publish-editor-section">
            <div class="publish-field-head"><label>Tags</label><span>${tags ? tags.split(',').filter(Boolean).length : 0}</span></div>
            <div class="publish-copy-field"><input id="publish-smart-tags" value="${escapeHtml(tags)}" readonly><button type="button" data-copy-publish-field="publish-smart-tags">Kopieren</button></div>
          </section>
          <div class="publish-meta-form compact">
            ${publishReadonlyMeta('Preis', fields.price ? formatCurrency(fields.price) : 'Nicht gesetzt', 'publish-smart-price')}
            ${publishReadonlyMeta('Marke', fields.brand || '—', 'publish-smart-brand')}
            ${publishReadonlyMeta('Kategorie', fields.category || '—', 'publish-smart-category')}
            ${publishReadonlyMeta('Größe', fields.size || '—', 'publish-smart-size')}
            ${publishReadonlyMeta('Zustand', fields.condition || '—', 'publish-smart-condition')}
          </div>
        </div>
        <aside class="publish-readiness-column">
          <section class="publish-readiness-card">
            <div class="publish-readiness-head"><div><span class="publish-panel-kicker">Zielplattform</span><h4>${escapeHtml(target.name)}</h4></div><span class="publish-platform-wordmark">${escapeHtml(platformLabel(listing.platform))}</span></div>
            <ul>${publishReadinessItems(listing).map(item => `<li class="${item.ok ? 'is-ready' : 'is-missing'}"><span>${item.ok ? '✓' : '!'}</span>${escapeHtml(item.label)}</li>`).join('')}</ul>
            <div class="publish-ready-message ${publishReadinessScore(listing) === 100 ? 'is-ready' : ''}"><strong>${publishReadinessScore(listing)}% vollständig</strong><small>${publishReadinessScore(listing) === 100 ? 'Alle Kerndaten sind vorhanden.' : 'Fehlende Angaben vor Veröffentlichung ergänzen.'}</small></div>
          </section>
          <section class="publish-photo-section"><div class="publish-field-head"><label>Fotos</label><span>${data.photos?.length || 0}</span></div>${publishPhotoGrid(data.photos || [])}<button type="button" class="vd-button compact full" data-download-smart-photos>Fotos speichern</button></section>
        </aside>
      </div>
      <footer class="publish-workspace-footer">
        <div><button type="button" class="vd-button" data-publish-edit-listing>Artikel bearbeiten</button><button type="button" class="vd-button" data-copy-publish-all>Alles kopieren</button></div>
        <div>${target.url ? `<button type="button" class="vd-button" data-open-publish-platform>Plattform öffnen</button>` : ''}<button type="button" class="vd-button primary" data-mark-publish-live>Als veröffentlicht markieren</button></div>
      </footer>`;

    bindPublishWorkspaceNavigation(workspace);
    bindSmartCopyActions(workspace, listing, data, target, fields, tags);
  } catch (error) {
    if (token !== publishState.smartCopyToken || !isSmartCopyModalOpen()) return;
    workspace.innerHTML = `${publishWorkspaceHeader(listing, 'Smart Copy', 'Fehler')}<div class="publish-workspace-error"><strong>Smart Copy konnte nicht vorbereitet werden</strong><p>${escapeHtml(error.message)}</p><button class="vd-button" type="button" data-retry-smart-copy>Erneut versuchen</button></div>`;
    bindPublishWorkspaceNavigation(workspace);
    workspace.querySelector('[data-retry-smart-copy]')?.addEventListener('click', () => renderSmartCopyWorkspace(listing));
  }
}

function publishReadonlyMeta(label, value, id) {
  return `<label><span>${escapeHtml(label)}</span><div class="publish-copy-field"><input id="${id}" value="${escapeHtml(String(value))}" readonly><button type="button" data-copy-publish-field="${id}">Kopieren</button></div></label>`;
}

function bindSmartCopyActions(scope, listing, data, target, fields, tags) {
  scope.querySelectorAll('[data-copy-publish-field]').forEach(button => {
    button.addEventListener('click', () => copyPublishValue(document.getElementById(button.dataset.copyPublishField)?.value || '', button));
  });
  scope.querySelector('[data-copy-publish-description]')?.addEventListener('click', event => copyPublishValue(fields.description || '', event.currentTarget));
  scope.querySelector('[data-copy-publish-html]')?.addEventListener('click', async event => {
    try {
      const html = await api('POST', '/api/html-wrap', { listing_id: listing.id, platform: listing.platform });
      copyPublishValue(html.html || fields.description || '', event.currentTarget);
    } catch (error) { toast(`HTML konnte nicht erstellt werden: ${error.message}`); }
  });
  scope.querySelector('[data-copy-publish-all]')?.addEventListener('click', event => copyPublishValue(`${fields.title || ''}\n\n${fields.description || ''}\n\n${tags}`, event.currentTarget));
  scope.querySelector('[data-open-publish-platform]')?.addEventListener('click', () => { if (target.url) window.open(target.url, '_blank', 'noopener'); });
  scope.querySelector('[data-mark-publish-live]')?.addEventListener('click', async event => {
    const button = event.currentTarget;
    button.disabled = true;
    try {
      await api('POST', '/api/publish/mark', { listing_id: listing.id, platform: listing.platform, status: 'published' });
      toast('Artikel als veröffentlicht markiert');
      closeSmartCopyModal();
      await loadPublishList();
    } catch (error) { toast(`Fehler: ${error.message}`); }
    finally { button.disabled = false; }
  });
  scope.querySelector('[data-download-smart-photos]')?.addEventListener('click', () => downloadPublishPhotos(data.photos || []));
  scope.querySelector('[data-publish-edit-listing]')?.addEventListener('click', () => { closeSmartCopyModal(); switchToTab('history'); showDetail(listing.id); });
}

function copyPublishValue(value, button) {
  navigator.clipboard.writeText(String(value || '')).then(() => {
    const old = button?.textContent;
    if (button) button.textContent = 'Kopiert';
    toast('In die Zwischenablage kopiert');
    if (button) setTimeout(() => { button.textContent = old; }, 1200);
  }).catch(() => toast('Kopieren nicht möglich'));
}

function publishReadinessItems(listing) {
  return [
    { label: 'Titel', ok: Boolean(listing.title?.trim()) },
    { label: 'Beschreibung', ok: Boolean(listing.description?.trim()) },
    { label: 'Preis', ok: Number(listing.price) > 0 },
    { label: 'Bilder', ok: Boolean(listing.photos?.length) },
    { label: 'Kategorie', ok: Boolean(listing.category?.trim()) },
    { label: 'Zustand', ok: Boolean(listing.condition?.trim()) },
  ];
}

function publishReadinessScore(listing) {
  const items = publishReadinessItems(listing);
  return Math.round((items.filter(item => item.ok).length / items.length) * 100);
}

function renderDirectPublishWorkspace(listing) {
  const workspace = document.getElementById('publish-workspace');
  const target = publishPlatformTarget(listing.platform);
  const connected = listing.platform === 'ebay-de'
    ? Boolean(publishState.integrations.ebay?.connected)
    : listing.platform === 'etsy'
      ? Boolean(publishState.integrations.etsy?.connected)
      : false;
  const tags = Array.isArray(listing.tags) ? listing.tags.join(', ') : (listing.tags || '');

  workspace.innerHTML = `${publishWorkspaceHeader(listing, 'Direkt veröffentlichen', target.direct ? (connected ? 'Verbindung bereit' : 'Nicht verbunden') : 'Smart Copy erforderlich')}
    <div class="publish-direct-grid">
      <form id="publish-direct-form" class="publish-editor-column" autocomplete="off">
        <section class="publish-editor-section"><div class="publish-field-head"><label for="publish-direct-title">Titel</label><span id="publish-direct-title-count">${String(listing.title || '').length}/80</span></div><input id="publish-direct-title" value="${escapeHtml(listing.title || '')}" maxlength="80"></section>
        <section class="publish-editor-section"><div class="publish-field-head"><label for="publish-direct-description">Beschreibung</label><span id="publish-direct-description-count">${String(listing.description || '').length}/5000</span></div><textarea id="publish-direct-description" maxlength="5000">${escapeHtml(listing.description || '')}</textarea></section>
        <section class="publish-editor-section"><div class="publish-field-head"><label for="publish-direct-tags">Tags</label><span>${Array.isArray(listing.tags) ? listing.tags.length : 0}</span></div><input id="publish-direct-tags" value="${escapeHtml(tags)}"></section>
        <div class="publish-meta-form">
          <label><span>Preis</span><input id="publish-direct-price" type="number" min="0" step="0.01" value="${listing.price || ''}"></label>
          <label><span>Zustand</span><input id="publish-direct-condition" value="${escapeHtml(listing.condition || '')}"></label>
          <label><span>Marke</span><input id="publish-direct-brand" value="${escapeHtml(listing.brand || '')}"></label>
          <label><span>Modell / Größe</span><input id="publish-direct-size" value="${escapeHtml(listing.size || '')}"></label>
          <label><span>Farbe</span><input id="publish-direct-color" value="${escapeHtml(listing.color || '')}"></label>
          <label><span>Kategorie</span><input id="publish-direct-category" value="${escapeHtml(listing.category || '')}"></label>
        </div>
      </form>
      <aside class="publish-readiness-column">
        <section class="publish-readiness-card">
          <div class="publish-readiness-head"><div><span class="publish-panel-kicker">Plattform</span><h4>${escapeHtml(target.name)}</h4></div><span class="integration-dot ${connected ? 'is-connected' : ''}"></span></div>
          ${target.direct ? `<p class="publish-integration-message">${connected ? 'Die API-Verbindung ist aktiv.' : 'Verbinde das Konto zuerst in den Einstellungen.'}</p>` : '<p class="publish-integration-message">Diese Plattform unterstützt derzeit Smart Copy über Vendoo.</p>'}
          <ul>${publishReadinessItems(listing).map(item => `<li class="${item.ok ? 'is-ready' : 'is-missing'}"><span>${item.ok ? '✓' : '!'}</span>${escapeHtml(item.label)}</li>`).join('')}</ul>
          <div class="publish-ready-message ${publishReadinessScore(listing) === 100 ? 'is-ready' : ''}"><strong>${publishReadinessScore(listing)}% vollständig</strong><small>Änderungen vor dem Publishing speichern.</small></div>
        </section>
        <section class="publish-photo-section"><div class="publish-field-head"><label>Fotos</label><span>${listing.photos?.length || 0}</span></div>${publishPhotoGrid(listing.photos || [])}</section>
      </aside>
    </div>
    <footer class="publish-workspace-footer">
      <div><button type="button" class="vd-button" data-publish-edit-listing>Detailansicht</button><button type="button" class="vd-button" data-publish-save-direct>Änderungen speichern</button></div>
      <div>${listing.platform === 'ebay-de' ? '<button type="button" class="vd-button" data-add-current-ebay>Zur Queue</button>' : ''}<button type="button" class="vd-button primary" data-publish-direct>${target.direct ? 'Jetzt veröffentlichen' : 'Smart Copy öffnen'}</button></div>
    </footer>`;

  bindPublishWorkspaceNavigation(workspace);
  const title = document.getElementById('publish-direct-title');
  const description = document.getElementById('publish-direct-description');
  title?.addEventListener('input', () => { document.getElementById('publish-direct-title-count').textContent = `${title.value.length}/80`; });
  description?.addEventListener('input', () => { document.getElementById('publish-direct-description-count').textContent = `${description.value.length}/5000`; });
  workspace.querySelector('[data-publish-edit-listing]')?.addEventListener('click', () => { closeSmartCopyModal(); switchToTab('history'); showDetail(listing.id); });
  workspace.querySelector('[data-publish-save-direct]')?.addEventListener('click', () => saveDirectPublishDraft(listing));
  workspace.querySelector('[data-add-current-ebay]')?.addEventListener('click', async () => {
    publishState.selected.add(listing.id); updatePublishToolbar(); await batchEbayAdd();
  });
  workspace.querySelector('[data-publish-direct]')?.addEventListener('click', async event => {
    if (!target.direct) { setPublishView('smart'); openSmartCopyModal(listing); return; }
    if (!connected) { toast(`${target.name} ist nicht verbunden`); switchToTab('settings'); return; }
    const saved = await saveDirectPublishDraft(listing, false);
    if (!saved) return;
    const button = event.currentTarget;
    button.disabled = true; button.textContent = 'Wird eingereiht …';
    try {
      if (listing.platform === 'etsy') await api('POST', '/api/publish/etsy', { listing_id: listing.id });
      else await api('POST', `/api/ebay/publish/${listing.id}`);
      toast(`Auftrag für ${target.name} wurde sicher eingereiht`);
      notify('Vendoo Publish', `${listing.title || listing.sku} wurde zur Publishing-Queue hinzugefügt.`);
      await loadPublishList();
    } catch (error) { toast(`Veröffentlichung fehlgeschlagen: ${error.message}`); }
    finally { button.disabled = false; button.textContent = 'Jetzt veröffentlichen'; }
  });
}

async function saveDirectPublishDraft(listing, showSuccess = true) {
  const payload = {
    title: document.getElementById('publish-direct-title')?.value.trim() || '',
    description: document.getElementById('publish-direct-description')?.value.trim() || '',
    tags: (document.getElementById('publish-direct-tags')?.value || '').split(',').map(tag => tag.trim()).filter(Boolean),
    price: Number.parseFloat(document.getElementById('publish-direct-price')?.value) || null,
    condition: document.getElementById('publish-direct-condition')?.value.trim() || null,
    brand: document.getElementById('publish-direct-brand')?.value.trim() || null,
    size: document.getElementById('publish-direct-size')?.value.trim() || null,
    color: document.getElementById('publish-direct-color')?.value.trim() || null,
    category: document.getElementById('publish-direct-category')?.value.trim() || null,
  };
  try {
    const updated = await api('PUT', `/api/listings/${listing.id}`, payload);
    const replace = items => items.map(item => item.id === updated.id ? updated : item);
    publishState.listings = replace(publishState.listings);
    publishState.allFiltered = replace(publishState.allFiltered);
    if (showSuccess) toast('Artikel gespeichert');
    return updated;
  } catch (error) {
    toast(`Speichern fehlgeschlagen: ${error.message}`);
    return null;
  }
}

function renderQueueWorkspaceLegacy() {
  const workspace = document.getElementById('publish-workspace');
  const queue = publishState.queue;
  const counts = publishQueueCounts();
  workspace.innerHTML = `<header class="publish-operation-header"><div><span class="publish-panel-kicker">eBay Automatisierung</span><h3>eBay Queue</h3><p>${counts.pending} ausstehend · ${counts.processing} in Bearbeitung · ${counts.failed} fehlgeschlagen</p></div><div><button class="vd-button" type="button" data-queue-clear>Queue leeren</button><button class="vd-button" type="button" data-queue-retry>Fehler wiederholen</button><button class="vd-button primary" type="button" data-queue-process>Alle verarbeiten</button></div></header>
    <div class="publish-operation-list">${queue.length ? queue.map(item => publishQueueRow(item)).join('') : '<div class="publish-workspace-empty compact"><span>✓</span><h3>Queue ist leer</h3><p>Wähle links Artikel aus und füge sie zur eBay Queue hinzu.</p></div>'}</div>
    <footer class="publish-operation-footer"><button class="vd-button" type="button" data-queue-add-selected ${publishState.selected.size ? '' : 'disabled'}>Ausgewählte Artikel hinzufügen</button><span>eBay-Verbindung: <strong>${publishState.integrations.ebay?.connected ? 'Verbunden' : 'Nicht verbunden'}</strong></span></footer>`;

  workspace.querySelector('[data-queue-process]')?.addEventListener('click', processEbayQueue);
  workspace.querySelector('[data-queue-retry]')?.addEventListener('click', retryEbayQueue);
  workspace.querySelector('[data-queue-clear]')?.addEventListener('click', clearEbayQueueUI);
  workspace.querySelector('[data-queue-add-selected]')?.addEventListener('click', batchEbayAdd);
  workspace.querySelectorAll('[data-queue-remove]').forEach(button => button.addEventListener('click', async () => {
    try { await api('DELETE', `/api/ebay/queue/${button.dataset.queueRemove}`); await loadPublishList(); }
    catch (error) { toast(`Fehler: ${error.message}`); }
  }));
}

function publishQueueRow(item) {
  const labels = { pending: 'Ausstehend', uploading: 'In Bearbeitung', published: 'Veröffentlicht', drafted: 'Entwurf erstellt', failed: 'Fehlgeschlagen' };
  return `<article class="publish-operation-row">
    <div class="publish-operation-product"><span class="publish-operation-mark">${escapeHtml(String(item.sku || 'VD').slice(-2))}</span><div><strong>${escapeHtml(item.title || 'Ohne Titel')}</strong><small>${escapeHtml(item.sku || '')}</small></div></div>
    <div class="publish-operation-meta"><span>Status</span><b class="operation-status status-${escapeHtml(item.status)}">${escapeHtml(labels[item.status] || item.status)}</b></div>
    <div class="publish-operation-meta"><span>Versuche</span><b>${Number(item.retries) || 0}</b></div>
    <div class="publish-operation-error">${item.error ? `<span title="${escapeHtml(item.error)}">${escapeHtml(item.error)}</span>` : '<span>Keine Fehler</span>'}</div>
    <div class="publish-operation-actions">${item.ebay_url ? `<a class="vd-button compact" href="${escapeHtml(item.ebay_url)}" target="_blank" rel="noopener">Ansehen</a>` : ''}<button class="icon-button danger" type="button" data-queue-remove="${item.id}" title="Entfernen">×</button></div>
  </article>`;
}

function renderScheduledWorkspace() {
  const workspace = document.getElementById('publish-workspace');
  const items = publishState.scheduled;
  workspace.innerHTML = `<header class="publish-operation-header"><div><span class="publish-panel-kicker">Zeitsteuerung</span><h3>Geplante Veröffentlichungen</h3><p>${items.length} Planung${items.length === 1 ? '' : 'en'} im System</p></div><button class="vd-button primary" type="button" data-schedule-selected ${publishState.selected.size || publishState.selectedId ? '' : 'disabled'}>Auswahl planen</button></header>
    <div class="publish-schedule-list">${items.length ? items.map(item => publishScheduleRow(item)).join('') : '<div class="publish-workspace-empty compact"><span>◷</span><h3>Nichts geplant</h3><p>Plane ausgewählte Artikel für einen späteren Zeitpunkt.</p></div>'}</div>`;

  workspace.querySelector('[data-schedule-selected]')?.addEventListener('click', openBatchSchedule);
  workspace.querySelectorAll('[data-schedule-remove]').forEach(button => button.addEventListener('click', async () => {
    try { await api('DELETE', `/api/schedule/${button.dataset.scheduleRemove}`); toast('Planung entfernt'); await loadPublishList(); }
    catch (error) { toast(`Fehler: ${error.message}`); }
  }));
}

function publishScheduleRow(item) {
  const statusLabels = { scheduled: 'Geplant', publishing: 'In Bearbeitung', queued: 'An Queue übergeben', published: 'Veröffentlicht', drafted: 'Entwurf erstellt', failed: 'Fehlgeschlagen' };
  const date = new Date(String(item.scheduled_at || '').replace(' ', 'T') + (String(item.scheduled_at || '').includes('Z') ? '' : 'Z'));
  const dateText = Number.isNaN(date.getTime()) ? item.scheduled_at : date.toLocaleString('de-DE', { dateStyle: 'medium', timeStyle: 'short' });
  return `<article class="publish-schedule-row">
    <div class="publish-schedule-date"><strong>${escapeHtml(dateText || '—')}</strong><span>${escapeHtml(platformLabel(item.platform))}</span></div>
    <div class="publish-operation-product"><span class="publish-operation-mark">${escapeHtml(String(item.sku || 'VD').slice(-2))}</span><div><strong>${escapeHtml(item.title || 'Ohne Titel')}</strong><small>${escapeHtml(item.sku || '')}</small></div></div>
    <b class="operation-status status-${escapeHtml(item.status)}">${escapeHtml(statusLabels[item.status] || item.status)}</b>
    ${item.error ? `<span class="publish-schedule-error">${escapeHtml(item.error)}</span>` : '<span></span>'}
    ${item.status === 'scheduled' ? `<button class="icon-button danger" type="button" data-schedule-remove="${item.id}" title="Planung entfernen">×</button>` : '<span></span>'}
  </article>`;
}

function publishQueueCountsLegacy() {
  return publishState.queue.reduce((counts, item) => {
    if (item.status === 'pending') counts.pending++;
    else if (item.status === 'uploading') counts.processing++;
    else if (item.status === 'published') counts.published++;
    else if (item.status === 'failed') counts.failed++;
    return counts;
  }, { pending: 0, processing: 0, published: 0, failed: 0 });
}

function renderPublishStatusRailLegacy() {
  const counts = publishQueueCounts();
  const workflowPublished = publishState.workflow?.stages?.published?.count || 0;
  const directEligible = publishState.listings.filter(listing => ['ebay-de', 'etsy'].includes(listing.platform)).length;
  const setText = (id, value) => { const node = document.getElementById(id); if (node) node.textContent = value; };
  setText('publish-status-pending', counts.pending);
  setText('publish-status-processing', counts.processing);
  setText('publish-status-published', Math.max(counts.published, workflowPublished));
  setText('publish-status-failed', counts.failed);
  setText('publish-status-scheduled', publishState.scheduled.length);
  setText('publish-direct-count', directEligible);
  setText('publish-queue-count', publishState.queue.length);
  setText('publish-scheduled-count', publishState.scheduled.length);
  setText('publish-retry-caption', counts.failed ? `${counts.failed} Fehler warten` : 'Keine Fehler');
  setText('publish-schedule-caption', publishState.scheduled.length ? `${publishState.scheduled.length} Planung${publishState.scheduled.length === 1 ? '' : 'en'}` : 'Keine Planungen');

  const activity = publishState.workflow?.activity || [];
  const container = document.getElementById('publish-activity');
  if (container) {
    container.innerHTML = activity.length ? activity.slice(0, 6).map(item => `<div class="publish-activity-row"><span class="publish-activity-icon type-${escapeHtml(item.type || 'pending')}">${activityIcon(item.type)}</span><div><strong>${escapeHtml(item.text || '')}</strong><small>${relativeTime(item.at)}</small></div></div>`).join('') : '<div class="publish-rail-empty">Noch keine Publish-Aktivität</div>';
  }
}

async function openSmartCopy(listingId) {
  publishState.selectedId = Number(listingId);
  setPublishView('smart');
  renderPublishList();
  openSmartCopyModal(selectedPublishListing());
}

async function batchSmartCopy() {
  const id = [...publishState.selected][0] || publishState.selectedId;
  if (!id) return;
  openSmartCopy(id);
}

async function batchEtsyPublish() {
  const ids = [...publishState.selected];
  const eligible = publishState.listings.filter(listing => ids.includes(listing.id) && listing.platform === 'etsy');
  if (!eligible.length) { toast('Keine ausgewählten Etsy-Artikel'); return; }
  let succeeded = 0;
  let failed = 0;
  for (const listing of eligible) {
    try { await api('POST', '/api/publish/etsy', { listing_id: listing.id }); succeeded++; }
    catch { failed++; }
  }
  toast(`Etsy: ${succeeded} Auftrag${succeeded === 1 ? '' : 'e'} eingereiht${failed ? `, ${failed} fehlgeschlagen` : ''}`);
  await loadPublishList();
}

async function batchEbayAddLegacy() {
  const ids = [...publishState.selected];
  if (!ids.length && publishState.selectedId) ids.push(publishState.selectedId);
  if (!ids.length) return;
  try {
    const response = await api('POST', '/api/ebay/queue', { listing_ids: ids });
    toast(`${response.added} Listing${response.added === 1 ? '' : 's'} zur eBay Queue hinzugefügt`);
    publishState.view = 'queue';
    await loadPublishList();
    setPublishView('queue');
  } catch (error) { toast(`Fehler: ${error.message}`); }
}

async function openEbayQueueLegacy() { setPublishView('queue'); }

async function processEbayQueueLegacy() {
  if (!publishState.integrations.ebay?.connected) { toast('eBay ist nicht verbunden'); switchToTab('settings'); return; }
  const buttons = document.querySelectorAll('[data-queue-process], #publish-ebay-queue-process');
  buttons.forEach(button => { button.disabled = true; });
  try {
    const response = await api('POST', '/api/ebay/queue/process');
    const succeeded = response.results?.filter(item => item.status === 'published').length || 0;
    const failed = response.results?.filter(item => item.status === 'failed').length || 0;
    toast(`${succeeded} veröffentlicht${failed ? `, ${failed} fehlgeschlagen` : ''}`);
    notify('Vendoo — eBay Queue', `${succeeded} veröffentlicht${failed ? `, ${failed} fehlgeschlagen` : ''}`);
    await loadPublishList();
    setPublishView('queue');
  } catch (error) { toast(`Queue-Fehler: ${error.message}`); }
  finally { buttons.forEach(button => { button.disabled = false; }); }
}

async function retryEbayQueueLegacy() {
  try {
    const response = await api('POST', '/api/ebay/queue/retry');
    toast(`${response.retried} Einträge erneut eingeplant`);
    await loadPublishList();
    setPublishView('queue');
  } catch (error) { toast(`Fehler: ${error.message}`); }
}

async function clearEbayQueueUI() {
  if (!confirm('eBay Queue wirklich vollständig leeren?')) return;
  try { await api('DELETE', '/api/ebay/queue'); toast('eBay Queue geleert'); await loadPublishList(); setPublishView('queue'); }
  catch (error) { toast(`Fehler: ${error.message}`); }
}

let scheduleListingIds = [];

function openBatchSchedule() {
  scheduleListingIds = [...publishState.selected];
  if (!scheduleListingIds.length && publishState.selectedId) scheduleListingIds = [publishState.selectedId];
  if (!scheduleListingIds.length) { toast('Bitte mindestens ein Artikel auswählen'); return; }
  openPublishScheduleDialog(scheduleListingIds);
}

function openSingleSchedule(listingId) {
  scheduleListingIds = [Number(listingId)];
  openPublishScheduleDialog(scheduleListingIds);
}

function openPublishScheduleDialog(ids) {
  const now = new Date(Date.now() + 30 * 60 * 1000);
  const local = new Date(now.getTime() - now.getTimezoneOffset() * 60000).toISOString().slice(0, 16);
  const input = document.getElementById('schedule-datetime');
  input.value = local;
  input.min = new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString().slice(0, 16);
  document.getElementById('schedule-modal-title').textContent = ids.length === 1 ? 'Veröffentlichung planen' : `${ids.length} Veröffentlichungen planen`;
  const selected = publishState.listings.filter(listing => ids.includes(listing.id));
  document.getElementById('schedule-listing-summary').innerHTML = selected.slice(0, 4).map(listing => `<span>${escapeHtml(listing.sku || `VD-${listing.id}`)} · ${escapeHtml(listing.title || 'Ohne Titel')}</span>`).join('') + (selected.length > 4 ? `<b>+${selected.length - 4} weitere</b>` : '');
  document.getElementById('schedule-modal').classList.remove('hidden');

  document.getElementById('schedule-confirm').onclick = async () => {
    const dateTime = input.value;
    const platform = document.getElementById('schedule-platform').value;
    if (!dateTime) { toast('Bitte Datum und Uhrzeit auswählen'); return; }
    const utc = new Date(dateTime).toISOString().replace('T', ' ').slice(0, 19);
    let succeeded = 0;
    let failed = 0;
    for (const id of ids) {
      try { await api('POST', '/api/schedule', { listing_id: id, platform, scheduled_at: utc }); succeeded++; }
      catch { failed++; }
    }
    toast(`${succeeded} Veröffentlichung${succeeded === 1 ? '' : 'en'} geplant${failed ? `, ${failed} fehlgeschlagen` : ''}`);
    closePublishSchedule();
    publishState.view = 'scheduled';
    await loadPublishList();
    setPublishView('scheduled');
  };
}

function closePublishSchedule() {
  document.getElementById('schedule-modal')?.classList.add('hidden');
}

async function openScheduleOverview() { setPublishView('scheduled'); }

async function batchDownloadPhotos() {
  const ids = [...publishState.selected];
  if (!ids.length && publishState.selectedId) ids.push(publishState.selectedId);
  if (!ids.length) return;
  try {
    const data = await api('POST', '/api/publish/batch-photos', { listing_ids: ids });
    await downloadPublishPhotos(data.photos || []);
  } catch (error) { toast(`Fehler: ${error.message}`); }
}

async function downloadPublishPhotos(photos) {
  const unique = [...new Set((photos || []).filter(Boolean))];
  if (!unique.length) { toast('Keine Fotos vorhanden'); return; }
  if (unique.length === 1) {
    const photo = unique[0];
    const link = document.createElement('a');
    link.href = photoUrl(photo);
    link.download = String(photo).split('/').pop() || 'vendoo-foto.jpg';
    document.body.appendChild(link);
    link.click();
    link.remove();
    toast('Foto wird gespeichert');
    return;
  }

  try {
    const response = await fetch('/api/photos/archive', {
      method: 'POST',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf() },
      body: JSON.stringify({ photos: unique }),
    });
    if (!response.ok) {
      const error = await response.json().catch(() => ({ error: response.statusText }));
      throw new Error(error.error || 'ZIP konnte nicht erstellt werden');
    }
    const blob = await response.blob();
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = `vendoo-fotos-${new Date().toISOString().slice(0, 10)}.zip`;
    document.body.appendChild(link);
    link.click();
    link.remove();
    setTimeout(() => URL.revokeObjectURL(url), 1500);
    toast(`${unique.length} Fotos wurden als ZIP gepackt`);
  } catch (error) {
    toast(`Foto-ZIP fehlgeschlagen: ${error.message}`);
  }
}



// --- Central image gallery / media library ---
const MEDIA_SOURCE_LABELS = { flux: 'FLUX', edited: 'Bearbeitet', batch: 'Produktion', ai: 'AI-Bild', listing: 'Artikel' };

function formatMediaFileSize(bytes) {
  const size = Number(bytes || 0);
  if (!size) return 'Größe unbekannt';
  if (size < 1024 * 1024) return `${Math.max(1, Math.round(size / 1024))} KB`;
  return `${(size / 1024 / 1024).toFixed(1).replace('.', ',')} MB`;
}

function renderMediaSummary() {
  const summary = state.mediaSummary || { total: 0, sources: {} };
  const sources = summary.sources || {};
  const values = { total: summary.total || 0, flux: sources.flux || 0, edited: sources.edited || 0, batch: sources.batch || 0, ai: sources.ai || 0, listing: sources.listing || 0 };
  for (const [key, value] of Object.entries(values)) document.getElementById(`media-count-${key}`)?.replaceChildren(document.createTextNode(String(value)));
  const current = document.getElementById('media-source')?.value || 'all';
  document.querySelectorAll('[data-media-source]').forEach(button => button.classList.toggle('active', button.dataset.mediaSource === current));
}

function updateMediaToolbar() {
  const count = state.mediaSelected.size;
  document.getElementById('media-selected-count')?.replaceChildren(document.createTextNode(`${count} ausgewählt`));
  const download = document.getElementById('media-download-selected');
  const remove = document.getElementById('media-delete-selected');
  const factory = document.getElementById('media-send-to-factory');
  if (download) download.disabled = count === 0;
  if (remove) remove.disabled = count === 0;
  if (factory) factory.disabled = count === 0;
  const selectPage = document.getElementById('media-select-page');
  if (selectPage) {
    const selectedOnPage = state.mediaItems.filter(item => state.mediaSelected.has(item.image_path)).length;
    selectPage.checked = state.mediaItems.length > 0 && selectedOnPage === state.mediaItems.length;
    selectPage.indeterminate = selectedOnPage > 0 && selectedOnPage < state.mediaItems.length;
  }
}

function renderMediaPagination() {
  const pagination = state.mediaPagination || {};
  const page = Math.max(1, Number(pagination.page || 1));
  const pages = Math.max(1, Number(pagination.total_pages || 1));
  const total = Math.max(0, Number(pagination.total || 0));
  const pageSize = Math.max(1, Number(state.mediaPageSize || 24));
  const first = total ? ((page - 1) * pageSize) + 1 : 0;
  const last = total ? Math.min(total, first + state.mediaItems.length - 1) : 0;
  document.querySelectorAll('[data-media-pagination]').forEach(paginationRoot => {
    paginationRoot.querySelector('[data-media-page-range]')?.replaceChildren(document.createTextNode(total ? `${first}–${last} von ${total} Bildern` : '0 Bilder'));
    paginationRoot.querySelector('[data-media-page-info]')?.replaceChildren(document.createTextNode(`von ${pages}`));
    const select = paginationRoot.querySelector('[data-media-page-jump]');
    if (select) {
      const signature = `${pages}:${page}`;
      if (select.dataset.signature !== signature) {
        select.innerHTML = Array.from({ length: pages }, (_, index) => `<option value="${index + 1}"${index + 1 === page ? ' selected' : ''}>${index + 1}</option>`).join('');
        select.dataset.signature = signature;
      } else select.value = String(page);
    }
    const prev = paginationRoot.querySelector('[data-media-page-action="prev"]');
    const next = paginationRoot.querySelector('[data-media-page-action="next"]');
    if (prev) prev.disabled = page <= 1;
    if (next) next.disabled = page >= pages;
  });
}

function mediaSourceBadges(item) {
  return (item.sources || [item.source]).map(source => `<span class="media-source-chip is-${escapeHtml(source)}">${escapeHtml(MEDIA_SOURCE_LABELS[source] || source)}</span>`).join('');
}

function mediaActionIcon(name, size = 18) {
  const map = { open: 'zoom-in', edit: 'pencil', use: 'plus', download: 'download', delete: 'trash-2' };
  return window.VendooIcons?.svg(map[name] || name, { size, className: 'media-action-icon' }) || '';
}

function renderMediaLibrary() {
  const container = document.getElementById('media-grid');
  if (!container) return;
  container.classList.toggle('is-list', state.mediaView === 'list');
  if (!state.mediaItems.length) {
    container.innerHTML = '<div class="media-empty"><strong>Keine Bilder gefunden</strong><span>Filter anpassen oder im FLUX Studio ein neues Bild erzeugen.</span><button class="vd-button primary" type="button" data-tab-target="flux-studio">FLUX Studio öffnen</button></div>';
    container.querySelector('[data-tab-target]')?.addEventListener('click', () => switchToTab('flux-studio'));
    updateMediaToolbar();
    return;
  }
  container.innerHTML = state.mediaItems.map(item => {
    const selected = state.mediaSelected.has(item.image_path);
    const title = item.title || item.prompt || item.image_path.split('/').pop();
    const dimensions = item.width && item.height ? `${item.width} × ${item.height}` : 'Abmessungen unbekannt';
    const canFavorite = (item.sources || []).includes('flux');
    return `<article class="media-card${selected ? ' is-selected' : ''}" data-media-path="${escapeHtml(item.image_path)}">
      <div class="media-card-image">
        <button type="button" class="media-card-open" data-media-preview="${escapeHtml(item.image_path)}" aria-label="${escapeHtml(title)} in großer Vorschau öffnen">
          <img src="${escapeHtml(item.public_url || photoUrl(item.image_path))}" alt="${escapeHtml(title)}" loading="lazy">
          <span class="media-open-indicator" aria-hidden="true">${mediaActionIcon('open')}</span>
        </button>
        <label class="media-card-select" title="Bild auswählen"><input type="checkbox" data-media-select="${escapeHtml(item.image_path)}" ${selected ? 'checked' : ''} aria-label="Bild auswählen"><span aria-hidden="true">${window.VendooIcons?.svg('check', { size: 13 }) || ''}</span></label>
        ${canFavorite ? `<button type="button" class="media-card-favorite-toggle${item.favorite ? ' is-active' : ''}" data-media-favorite="${escapeHtml(item.image_path)}" aria-label="${item.favorite ? 'Favorit entfernen' : 'Als Favorit markieren'}" title="${item.favorite ? 'Favorit entfernen' : 'Als Favorit markieren'}">${window.VendooIcons?.svg(item.favorite ? 'heart-filled' : 'heart', { size: 16 }) || ''}</button>` : ''}
      </div>
      <div class="media-card-content">
        <div class="media-card-sources">${mediaSourceBadges(item)}</div>
        <strong title="${escapeHtml(title)}">${escapeHtml(title)}</strong>
        <span>${escapeHtml(dimensions)} · ${escapeHtml(formatMediaFileSize(item.file_size))}</span>
        <small title="${escapeHtml(item.image_path.split('/').pop())}">${escapeHtml(formatFluxDate(item.created_at))} · ${escapeHtml(item.image_path.split('/').pop())}</small>
      </div>
      <div class="media-card-actions" aria-label="Bildaktionen">
        <button type="button" class="media-action" data-media-edit="${escapeHtml(item.image_path)}" aria-label="Bild bearbeiten" title="Bearbeiten">${mediaActionIcon('edit')}<span class="media-action-label">Bearbeiten</span></button>
        <button type="button" class="media-action" data-media-use="${escapeHtml(item.image_path)}" aria-label="Bild verwenden" title="Verwenden">${mediaActionIcon('use')}<span class="media-action-label">Verwenden</span></button>
        <a class="media-action" href="${escapeHtml(item.public_url || photoUrl(item.image_path))}" download aria-label="Bild herunterladen" title="Download">${mediaActionIcon('download')}<span class="media-action-label">Download</span></a>
        <button type="button" class="media-action danger" data-media-delete="${escapeHtml(item.image_path)}" aria-label="Bild löschen" title="Löschen">${mediaActionIcon('delete')}<span class="media-action-label">Löschen</span></button>
      </div>
    </article>`;
  }).join('');
  container.querySelectorAll('[data-media-select]').forEach(input => input.addEventListener('change', () => {
    if (input.checked) state.mediaSelected.add(input.dataset.mediaSelect); else state.mediaSelected.delete(input.dataset.mediaSelect);
    input.closest('.media-card')?.classList.toggle('is-selected', input.checked);
    updateMediaToolbar();
  }));
  container.querySelectorAll('[data-media-preview]').forEach(button => button.addEventListener('click', () => openMediaLightbox(findMediaItem(button.dataset.mediaPreview))));
  container.querySelectorAll('[data-media-edit]').forEach(button => button.addEventListener('click', () => openMediaEditor(findMediaItem(button.dataset.mediaEdit))));
  container.querySelectorAll('[data-media-use]').forEach(button => button.addEventListener('click', () => useMediaInGenerator(findMediaItem(button.dataset.mediaUse))));
  container.querySelectorAll('[data-media-favorite]').forEach(button => button.addEventListener('click', () => toggleMediaFavorite(findMediaItem(button.dataset.mediaFavorite))));
  container.querySelectorAll('[data-media-delete]').forEach(button => button.addEventListener('click', () => deleteMediaItems([button.dataset.mediaDelete])));
  updateMediaToolbar();
  window.VendooIcons?.render(container);
}

function findMediaItem(imagePath) {
  return state.mediaItems.find(item => item.image_path === imagePath) || (state.mediaCurrent?.image_path === imagePath ? state.mediaCurrent : null);
}

async function loadMediaLibrary({ silent = false } = {}) {
  const container = document.getElementById('media-grid');
  if (!silent && container && !state.mediaItems.length) container.innerHTML = '<div class="empty-state">Bildergalerie wird geladen …</div>';
  try {
    const params = new URLSearchParams({
      page: String(state.mediaPage), page_size: String(state.mediaPageSize),
      source: document.getElementById('media-source')?.value || 'all',
      sort: document.getElementById('media-sort')?.value || 'newest',
      favorites: document.getElementById('media-favorites-only')?.checked ? '1' : '0',
    });
    const search = document.getElementById('media-search')?.value.trim();
    if (search) params.set('q', search);
    const result = await api('GET', `/api/media-library?${params}`);
    state.mediaItems = result.items || [];
    state.mediaPagination = result.pagination || { page: 1, total_pages: 1, total: state.mediaItems.length };
    state.mediaSummary = result.summary || null;
    state.mediaPage = Number(state.mediaPagination.page || 1);
    state.mediaSelected = new Set([...state.mediaSelected].filter(path => state.mediaItems.some(item => item.image_path === path)));
    renderMediaSummary(); renderMediaLibrary(); renderMediaPagination();
  } catch (error) {
    if (container) container.innerHTML = `<div class="empty-state">Bildergalerie konnte nicht geladen werden: ${escapeHtml(error.message)}</div>`;
  }
}

async function toggleMediaFavorite(item) {
  if (!item) return;
  try {
    const result = await api('PATCH', '/api/media-library/favorite', { image_path: item.image_path, favorite: !item.favorite });
    item.favorite = result.favorite;
    if (state.mediaCurrent?.image_path === item.image_path) state.mediaCurrent.favorite = result.favorite;
    renderMediaLibrary(); renderMediaLightbox();
  } catch (error) { toast(error.message); }
}

async function deleteMediaItems(imagePaths) {
  const paths = [...new Set((imagePaths || []).filter(Boolean))];
  if (!paths.length) return;
  if (!window.confirm(`${paths.length} Bild${paths.length === 1 ? '' : 'er'} endgültig aus der Medienbibliothek und vom Datenträger löschen? In Artikeln verwendete Bilder werden geschützt.`)) return;
  try {
    const result = await api('DELETE', '/api/media-library/items', { image_paths: paths });
    state.mediaSelected.clear();
    if (state.mediaCurrent && paths.includes(state.mediaCurrent.image_path)) closeMediaLightbox();
    const protectedCount = result.protected?.length || 0;
    toast(`${result.deleted || 0} Bild${result.deleted === 1 ? '' : 'er'} gelöscht${protectedCount ? ` · ${protectedCount} Listing-Bild${protectedCount === 1 ? '' : 'er'} geschützt` : ''}.`);
    await loadMediaLibrary();
  } catch (error) { toast(`Löschen fehlgeschlagen: ${error.message}`); }
}

function useMediaInGenerator(item) {
  if (!item?.image_path) return;
  if (!state.photos.includes(item.image_path)) state.photos.push(item.image_path);
  renderPhotoPreview();
  switchToTab('generator');
  toast('Bild wurde dem neuen Artikel hinzugefügt.');
}

function openMediaEditor(item) {
  if (!item) return;
  state.mediaCurrent = item;
  if ((item.sources || []).includes('flux') && item.flux_id) {
    state.fluxStudioCurrent = { ...item, id: item.flux_id, image_path: item.image_path, public_url: item.public_url };
    openPhotoEditor(0, 'flux');
  } else openPhotoEditor(0, 'media');
}

const mediaZoomState = { zoom: 1, fitZoom: 1, naturalWidth: 0, naturalHeight: 0, dragging: false, startX: 0, startY: 0, scrollLeft: 0, scrollTop: 0 };

function updateMediaZoom(zoom, { center = false } = {}) {
  const stage = document.getElementById('media-lightbox-stage');
  const canvas = document.getElementById('media-lightbox-canvas');
  const image = document.getElementById('media-lightbox-image');
  if (!stage || !canvas || !image || !mediaZoomState.naturalWidth || !mediaZoomState.naturalHeight) return;
  const previousCenterX = stage.scrollLeft + stage.clientWidth / 2;
  const previousCenterY = stage.scrollTop + stage.clientHeight / 2;
  const previousWidth = Math.max(1, mediaZoomState.naturalWidth * mediaZoomState.zoom);
  const previousHeight = Math.max(1, mediaZoomState.naturalHeight * mediaZoomState.zoom);
  mediaZoomState.zoom = Math.min(4, Math.max(.1, Number(zoom) || 1));
  const width = Math.max(1, Math.round(mediaZoomState.naturalWidth * mediaZoomState.zoom));
  const height = Math.max(1, Math.round(mediaZoomState.naturalHeight * mediaZoomState.zoom));
  canvas.style.width = `${width}px`;
  canvas.style.height = `${height}px`;
  image.style.width = '100%';
  image.style.height = '100%';
  const range = document.getElementById('media-zoom-range');
  const output = document.getElementById('media-zoom-value');
  if (range) range.value = String(Math.round(mediaZoomState.zoom * 100));
  if (output) output.value = output.textContent = `${Math.round(mediaZoomState.zoom * 100)} %`;
  if (center) {
    const ratioX = previousCenterX / previousWidth;
    const ratioY = previousCenterY / previousHeight;
    requestAnimationFrame(() => {
      stage.scrollLeft = Math.max(0, ratioX * width - stage.clientWidth / 2);
      stage.scrollTop = Math.max(0, ratioY * height - stage.clientHeight / 2);
    });
  }
}

function fitMediaLightbox() {
  const stage = document.getElementById('media-lightbox-stage');
  if (!stage || !mediaZoomState.naturalWidth || !mediaZoomState.naturalHeight) return;
  const availableWidth = Math.max(120, stage.clientWidth - 28);
  const availableHeight = Math.max(120, stage.clientHeight - 28);
  mediaZoomState.fitZoom = Math.min(1, availableWidth / mediaZoomState.naturalWidth, availableHeight / mediaZoomState.naturalHeight);
  updateMediaZoom(mediaZoomState.fitZoom);
  requestAnimationFrame(() => { stage.scrollLeft = 0; stage.scrollTop = 0; });
}

function renderMediaLightbox() {
  const item = state.mediaCurrent;
  const dialog = document.getElementById('media-lightbox');
  if (!dialog || dialog.classList.contains('hidden') || !item) return;
  document.getElementById('media-lightbox-title').textContent = item.title || item.prompt || item.image_path.split('/').pop();
  const image = document.getElementById('media-lightbox-image');
  image.onload = () => {
    mediaZoomState.naturalWidth = image.naturalWidth || Number(item.width) || 1;
    mediaZoomState.naturalHeight = image.naturalHeight || Number(item.height) || 1;
    fitMediaLightbox();
  };
  image.src = item.public_url || photoUrl(item.image_path);
  document.getElementById('media-lightbox-download').href = item.public_url || photoUrl(item.image_path);
  document.getElementById('media-lightbox-download').download = item.image_path.split('/').pop();
  const favorite = document.getElementById('media-lightbox-favorite');
  const canFavorite = (item.sources || []).includes('flux');
  favorite?.classList.toggle('hidden', !canFavorite);
  if (favorite) favorite.innerHTML = `${window.VendooIcons?.svg(item.favorite ? 'heart-filled' : 'heart', { size: 17 }) || ''} ${item.favorite ? 'Favorit entfernen' : 'Favorit'}`;
  document.getElementById('media-lightbox-meta').innerHTML = `
    <div class="media-card-sources">${mediaSourceBadges(item)}</div>
    <dl><dt>Datei</dt><dd>${escapeHtml(item.image_path)}</dd><dt>Abmessungen</dt><dd>${item.width && item.height ? `${item.width} × ${item.height}` : 'Unbekannt'}</dd><dt>Dateigröße</dt><dd>${escapeHtml(formatMediaFileSize(item.file_size))}</dd><dt>Format</dt><dd>${escapeHtml(String(item.format || '—').toUpperCase())}</dd><dt>Erstellt</dt><dd>${escapeHtml(formatFluxDate(item.created_at))}</dd>${item.seed != null ? `<dt>Seed</dt><dd>${item.seed}</dd>` : ''}</dl>
    ${item.prompt ? `<div class="media-lightbox-prompt"><strong>Prompt</strong><p>${escapeHtml(item.prompt)}</p></div>` : ''}`;
  window.VendooIcons?.render(dialog);
}

function openMediaLightbox(item) {
  if (!item) return;
  state.mediaCurrent = item;
  const dialog = document.getElementById('media-lightbox');
  dialog?.classList.remove('hidden');
  dialog?.setAttribute('aria-hidden', 'false');
  document.body.classList.add('modal-open');
  mediaZoomState.zoom = 1;
  renderMediaLightbox();
}

function closeMediaLightbox() {
  const dialog = document.getElementById('media-lightbox');
  dialog?.classList.add('hidden');
  dialog?.setAttribute('aria-hidden', 'true');
  document.body.classList.remove('modal-open');
  const image = document.getElementById('media-lightbox-image');
  if (image) image.onload = null;
}

function setupMediaLibrary() {
  const root = document.getElementById('media-library');
  if (!root) return;
  const resetAndLoad = () => { state.mediaPage = 1; state.mediaSelected.clear(); loadMediaLibrary(); };
  document.getElementById('media-refresh')?.addEventListener('click', () => loadMediaLibrary());
  document.getElementById('media-source')?.addEventListener('change', resetAndLoad);
  document.getElementById('media-sort')?.addEventListener('change', resetAndLoad);
  document.getElementById('media-favorites-only')?.addEventListener('change', resetAndLoad);
  document.getElementById('media-page-size')?.addEventListener('change', event => { state.mediaPageSize = Number(event.target.value) || 24; resetAndLoad(); });
  document.getElementById('media-search')?.addEventListener('input', debounce(resetAndLoad, 280));
  document.querySelectorAll('[data-media-source]').forEach(button => button.addEventListener('click', () => {
    const select = document.getElementById('media-source'); if (select) select.value = button.dataset.mediaSource; resetAndLoad();
  }));
  document.getElementById('media-view-grid')?.addEventListener('click', () => { state.mediaView = 'grid'; document.getElementById('media-view-grid').classList.add('active'); document.getElementById('media-view-list').classList.remove('active'); renderMediaLibrary(); });
  document.getElementById('media-view-list')?.addEventListener('click', () => { state.mediaView = 'list'; document.getElementById('media-view-list').classList.add('active'); document.getElementById('media-view-grid').classList.remove('active'); renderMediaLibrary(); });
  document.getElementById('media-select-page')?.addEventListener('change', event => { for (const item of state.mediaItems) { if (event.target.checked) state.mediaSelected.add(item.image_path); else state.mediaSelected.delete(item.image_path); } renderMediaLibrary(); });
  document.getElementById('media-download-selected')?.addEventListener('click', () => downloadPublishPhotos([...state.mediaSelected]));
  document.getElementById('media-delete-selected')?.addEventListener('click', () => deleteMediaItems([...state.mediaSelected]));
  root.querySelectorAll('[data-media-page-action]').forEach(button => button.addEventListener('click', () => {
    const pages = Math.max(1, Number(state.mediaPagination.total_pages || 1));
    const target = button.dataset.mediaPageAction === 'prev' ? state.mediaPage - 1 : state.mediaPage + 1;
    if (target >= 1 && target <= pages && target !== state.mediaPage) { state.mediaPage = target; loadMediaLibrary(); }
  }));
  root.querySelectorAll('[data-media-page-jump]').forEach(select => select.addEventListener('change', () => {
    const pages = Math.max(1, Number(state.mediaPagination.total_pages || 1));
    const target = Math.min(pages, Math.max(1, Number(select.value || 1)));
    if (target !== state.mediaPage) { state.mediaPage = target; loadMediaLibrary(); }
  }));
  document.querySelectorAll('[data-media-lightbox-close]').forEach(button => button.addEventListener('click', closeMediaLightbox));
  document.getElementById('media-lightbox-edit')?.addEventListener('click', () => { closeMediaLightbox(); openMediaEditor(state.mediaCurrent); });
  document.getElementById('media-lightbox-favorite')?.addEventListener('click', () => toggleMediaFavorite(state.mediaCurrent));
  document.getElementById('media-lightbox-delete')?.addEventListener('click', () => deleteMediaItems(state.mediaCurrent ? [state.mediaCurrent.image_path] : []));
  document.getElementById('media-zoom-in')?.addEventListener('click', () => updateMediaZoom(mediaZoomState.zoom + .15, { center: true }));
  document.getElementById('media-zoom-out')?.addEventListener('click', () => updateMediaZoom(mediaZoomState.zoom - .15, { center: true }));
  document.getElementById('media-zoom-fit')?.addEventListener('click', fitMediaLightbox);
  document.getElementById('media-zoom-original')?.addEventListener('click', () => updateMediaZoom(1, { center: true }));
  document.getElementById('media-zoom-range')?.addEventListener('input', event => updateMediaZoom(Number(event.target.value) / 100, { center: true }));
  const zoomStage = document.getElementById('media-lightbox-stage');
  zoomStage?.addEventListener('wheel', event => {
    if (!event.ctrlKey && !event.metaKey) return;
    event.preventDefault();
    updateMediaZoom(mediaZoomState.zoom + (event.deltaY < 0 ? .1 : -.1), { center: true });
  }, { passive: false });
  zoomStage?.addEventListener('pointerdown', event => {
    if (event.button !== 0 || mediaZoomState.zoom <= mediaZoomState.fitZoom + .01) return;
    mediaZoomState.dragging = true; mediaZoomState.startX = event.clientX; mediaZoomState.startY = event.clientY;
    mediaZoomState.scrollLeft = zoomStage.scrollLeft; mediaZoomState.scrollTop = zoomStage.scrollTop;
    zoomStage.setPointerCapture(event.pointerId); zoomStage.classList.add('is-dragging');
  });
  zoomStage?.addEventListener('pointermove', event => {
    if (!mediaZoomState.dragging) return;
    zoomStage.scrollLeft = mediaZoomState.scrollLeft - (event.clientX - mediaZoomState.startX);
    zoomStage.scrollTop = mediaZoomState.scrollTop - (event.clientY - mediaZoomState.startY);
  });
  const stopZoomDrag = event => { mediaZoomState.dragging = false; zoomStage?.classList.remove('is-dragging'); try { zoomStage?.releasePointerCapture(event.pointerId); } catch {} };
  zoomStage?.addEventListener('pointerup', stopZoomDrag); zoomStage?.addEventListener('pointercancel', stopZoomDrag);
  document.addEventListener('keydown', event => {
    if (document.getElementById('media-lightbox')?.classList.contains('hidden')) return;
    if (event.key === 'Escape') closeMediaLightbox();
    if (event.key === '+' || event.key === '=') updateMediaZoom(mediaZoomState.zoom + .15, { center: true });
    if (event.key === '-') updateMediaZoom(mediaZoomState.zoom - .15, { center: true });
    if (event.key === '0') fitMediaLightbox();
  });
}

// --- Browser extensions ---
const EXTENSION_BROWSERS = {
  chrome: { name: 'Google Chrome', icon: 'chrome', managementUrl: 'chrome://extensions', action: 'Entpackte Erweiterung laden', note: 'Entwicklermodus aktivieren und den vorbereiteten Ordner auswählen.' },
  edge: { name: 'Microsoft Edge', icon: 'edge', managementUrl: 'edge://extensions', action: 'Entpackte Erweiterung laden', note: 'Entwicklermodus aktivieren und den vorbereiteten Ordner auswählen.' },
  firefox: { name: 'Mozilla Firefox', icon: 'firefox', managementUrl: 'about:debugging#/runtime/this-firefox', action: 'Temporäres Add-on laden', note: 'Für eine dauerhaft signierte Installation muss das Add-on über Mozilla signiert werden.' },
  safari: { name: 'Apple Safari', icon: 'compass', managementUrl: 'Safari → Einstellungen → Erweiterungen', action: 'Safari Web Extension erstellen', note: 'Benötigt macOS und Xcode. Vendoo liefert WebExtension-Quelle und Konvertierungsskript mit.' },
};

function detectActiveBrowser() {
  const ua = navigator.userAgent || '';
  if (/Edg\//.test(ua)) return 'edge';
  if (/Firefox\//.test(ua)) return 'firefox';
  if (/Safari\//.test(ua) && !/Chrome\//.test(ua) && !/Chromium\//.test(ua)) return 'safari';
  if (/Chrome\//.test(ua) || /Chromium\//.test(ua)) return 'chrome';
  return 'chrome';
}

async function copyTextSafe(value) {
  try { await navigator.clipboard.writeText(String(value || '')); return true; }
  catch {
    const area = document.createElement('textarea'); area.value = String(value || ''); area.style.position = 'fixed'; area.style.opacity = '0';
    document.body.appendChild(area); area.select(); const ok = document.execCommand('copy'); area.remove(); return ok;
  }
}

async function prepareBrowserExtension(browser, data = {}) {
  const config = EXTENSION_BROWSERS[browser];
  if (!config) return;
  try {
    const result = await api('POST', '/api/extensions/prepare', { browser });
    await copyTextSafe(result.path || '');
    const card = document.querySelector(`[data-extension-browser="${browser}"]`);
    card?.querySelector('.extension-path code')?.replaceChildren(document.createTextNode(result.path || ''));
    toast(result.opened
      ? `Installationsordner geöffnet und Pfad kopiert. Öffne jetzt ${config.managementUrl}.`
      : `Ordnerpfad kopiert: ${result.path}`);
    return result;
  } catch (error) {
    toast(`Extension konnte nicht vorbereitet werden: ${error.message}`);
    throw error;
  }
}

function renderExtensions(data = {}) {
  const grid = document.getElementById('extensions-grid');
  if (!grid) return;
  const active = detectActiveBrowser();
  const activeBrowser = EXTENSION_BROWSERS[active];
  const activeBox = document.getElementById('extensions-active-browser');
  if (activeBox) activeBox.innerHTML = `<span>Aktiver Browser</span><strong>${window.VendooIcons?.svg(activeBrowser.icon, { size: 19 }) || ''} ${escapeHtml(activeBrowser.name)}</strong>`;
  const version = data.version || '1.9.0';
  grid.innerHTML = Object.entries(EXTENSION_BROWSERS).map(([id, browser]) => {
    const info = data.browsers?.[id] || {};
    return `<article class="extension-card${id === active ? ' is-active' : ''}" data-extension-browser="${id}">
      <header><div class="extension-browser-icon">${window.VendooIcons?.svg(browser.icon, { size: 27 }) || ''}</div><div><span>${id === active ? 'Aktiver Browser' : 'Verfügbar'}</span><h3>${escapeHtml(browser.name)}</h3></div><b>v${escapeHtml(version)}</b></header>
      <p>${escapeHtml(browser.note)}</p>
      <div class="extension-path"><span>Installationsordner</span><code>${escapeHtml(info.path || 'Wird vom Server ermittelt …')}</code></div>
      <ol><li>Installation vorbereiten</li><li><code>${escapeHtml(browser.managementUrl)}</code> öffnen</li><li>${escapeHtml(browser.action)}</li></ol>
      <div class="extension-card-actions">
        <button type="button" class="vd-button primary" data-extension-prepare="${id}">${window.VendooIcons?.svg('folder-open', { size: 17 }) || ''} Installation vorbereiten</button>
        <button type="button" class="icon-button" data-extension-copy-path="${id}" title="Ordnerpfad kopieren" aria-label="Ordnerpfad kopieren">${window.VendooIcons?.svg('copy', { size: 17 }) || ''}</button>
        <a class="icon-button" href="/api/extensions/download/${id}" title="Extension-Paket herunterladen" aria-label="Extension-Paket herunterladen">${window.VendooIcons?.svg('download', { size: 17 }) || ''}</a>
      </div>
      ${id === 'safari' ? '<small>Safari lässt Erweiterungen nur über eine signierte macOS-App zu. Das Paket enthält dafür ein Xcode-Konvertierungsskript.</small>' : '<small>Die finale Bestätigung erfolgt im Browser; eine stille automatische Installation ist aus Sicherheitsgründen gesperrt.</small>'}
    </article>`;
  }).join('');
  grid.querySelectorAll('[data-extension-prepare]').forEach(button => button.addEventListener('click', async () => {
    const browser = button.dataset.extensionPrepare;
    const old = button.innerHTML; button.disabled = true; button.textContent = 'Wird vorbereitet …';
    try { await prepareBrowserExtension(browser, data); } catch {}
    finally { button.disabled = false; button.innerHTML = old; }
  }));
  grid.querySelectorAll('[data-extension-copy-path]').forEach(button => button.addEventListener('click', async () => {
    const path = data.browsers?.[button.dataset.extensionCopyPath]?.path || button.closest('.extension-card')?.querySelector('.extension-path code')?.textContent || '';
    await copyTextSafe(path); toast('Ordnerpfad kopiert');
  }));
  window.VendooIcons?.render(document.getElementById('extensions'));
}

async function loadExtensions() {
  const grid = document.getElementById('extensions-grid');
  if (grid && !grid.children.length) grid.innerHTML = '<div class="empty-state">Extensions werden geprüft …</div>';
  try { renderExtensions(await api('GET', '/api/extensions/info')); }
  catch (error) { if (grid) grid.innerHTML = `<div class="empty-state">Extension-Informationen konnten nicht geladen werden: ${escapeHtml(error.message)}</div>`; }
}

function setupExtensions() {
  window.VendooIcons?.render(document.getElementById('extensions') || document);
  document.getElementById('extensions-prepare-active')?.addEventListener('click', async event => {
    const button = event.currentTarget;
    const browser = detectActiveBrowser();
    const old = button.innerHTML;
    button.disabled = true;
    button.textContent = 'Wird vorbereitet …';
    try { await prepareBrowserExtension(browser); } catch {}
    finally { button.disabled = false; button.innerHTML = old; window.VendooIcons?.render(button); }
  });
}

// --- Settings ---
async function detectPublicBaseUrl({ apply = true } = {}) {
  const input = document.getElementById('setting-public-base-url');
  const status = document.getElementById('public-base-url-status');
  const button = document.getElementById('detect-public-base-url-btn');
  if (button) button.disabled = true;
  if (status) status.textContent = 'LAN-Adresse wird erkannt …';
  try {
    const info = await api('GET', '/api/network-info');
    const preferred = info.public_base_url || info.preferred_url || '';
    if (apply && input && preferred) input.value = preferred;
    const adapter = info.addresses?.[0];
    if (status) {
      status.textContent = adapter
        ? `Erkannt: ${preferred} · Adapter: ${adapter.name}`
        : `Keine LAN-IP erkannt. Aktuelle Adresse: ${preferred || 'localhost'}`;
    }
    return info;
  } catch (err) {
    if (status) status.textContent = `LAN-Adresse konnte nicht erkannt werden: ${err.message}`;
    return null;
  } finally {
    if (button) button.disabled = false;
  }
}


function renderLocalFluxInstallState(install = {}) {
  const wrap = document.getElementById('local-flux-progress-wrap');
  const label = document.getElementById('local-flux-progress-label');
  const value = document.getElementById('local-flux-progress-value');
  const bar = document.getElementById('local-flux-progress-bar');
  const phase = document.getElementById('local-flux-phase-label');
  const updated = document.getElementById('local-flux-updated-at');
  const checksWrap = document.getElementById('local-flux-checks');
  const errorWrap = document.getElementById('local-flux-error');
  const logTail = document.getElementById('local-flux-log-tail');
  const sevenZipTail = document.getElementById('local-flux-7zip-tail');
  const installButton = document.getElementById('install-local-image-provider-btn');
  const installerState = document.getElementById('local-flux-installer-state');
  const elapsed = document.getElementById('local-flux-elapsed');
  const extractFiles = document.getElementById('local-flux-extract-files');
  const extractSize = document.getElementById('local-flux-extract-size');
  const heartbeat = document.getElementById('local-flux-heartbeat');

  const progress = Math.max(0, Math.min(100, Number(install.progress ?? install.readiness ?? 0)));
  if (wrap) wrap.classList.remove('hidden');
  if (label) label.textContent = install.message || install.phase_label || 'Lokale FLUX-Installation';
  if (value) value.textContent = `${progress}%`;
  if (bar) {
    bar.style.width = `${progress}%`;
    bar.classList.toggle('is-stalled', install.status === 'stalled');
    bar.classList.toggle('is-failed', install.status === 'failed');
  }
  if (phase) {
    const phaseText = install.installation_complete ? 'Vollständig installiert' : (install.phase_label || install.phase || 'Nicht installiert');
    phase.textContent = `${phaseText} · ${Number(install.readiness || 0)}% geprüft`;
  }
  if (updated) {
    const date = install.updated_at ? new Date(install.updated_at) : null;
    updated.textContent = date && !Number.isNaN(date.getTime()) ? `Stand: ${date.toLocaleString('de-DE')}` : 'Stand: –';
  }

  const metrics = install.extract_metrics || {};
  if (installerState) {
    const labels = { running: 'Läuft', completed: 'Fertig', failed: 'Fehler', stalled: 'Hängt', 'not-started': 'Nicht gestartet', unknown: 'Unbekannt' };
    installerState.textContent = labels[install.status] || install.status || 'Bereit';
    installerState.className = `local-flux-state-value is-${escapeHtml(install.status || 'idle')}`;
  }
  if (elapsed) elapsed.textContent = `${Number(install.elapsed_seconds || metrics.elapsed_seconds || 0)} s`;
  if (extractFiles) extractFiles.textContent = `${Number(metrics.file_count || 0)} Dateien`;
  if (extractSize) extractSize.textContent = `${Number(metrics.size_mb || 0)} MB`;
  if (heartbeat) {
    heartbeat.textContent = install.heartbeat_age_seconds === null || install.heartbeat_age_seconds === undefined
      ? '–'
      : `${Number(install.heartbeat_age_seconds)} s`;
  }

  const checkLabels = {
    seven_zip: '7-Zip',
    archive: 'ComfyUI-Paket',
    embedded_python: 'Python Runtime',
    comfyui: 'ComfyUI',
    model: 'FLUX-Modell',
    workflow: 'Workflow',
    start_script: 'Startskript',
    stop_script: 'Stoppskript',
  };
  if (checksWrap) {
    const checks = install.checks || {};
    checksWrap.innerHTML = Object.entries(checkLabels).map(([key, title]) => {
      const check = checks[key] || {};
      let detail = check.ok ? 'Vorhanden' : 'Fehlt';
      if (check.size_gb) detail += ` · ${check.size_gb} GB`;
      else if (check.size_mb) detail += ` · ${check.size_mb} MB`;
      return `<div class="local-flux-check ${check.ok ? 'is-ok' : 'is-missing'}"><strong>${escapeHtml(title)}</strong><span>${escapeHtml(detail)}</span></div>`;
    }).join('');
  }

  if (errorWrap) {
    const failed = install.status === 'failed' || install.status === 'stalled';
    errorWrap.classList.toggle('hidden', !failed);
    errorWrap.textContent = failed
      ? `${install.message || 'Installation fehlgeschlagen.'}${install.error_position ? `
${install.error_position}` : ''}
Die vorhandenen Downloads bleiben erhalten. „Installation fortsetzen“ startet nur den fehlenden Schritt erneut.`
      : '';
  }
  if (logTail) {
    const lines = Array.isArray(install.log_tail) ? install.log_tail : [];
    logTail.textContent = lines.length ? lines.join('\n') : 'Noch kein Installationsprotokoll vorhanden.';
  }
  if (sevenZipTail) {
    const lines = [
      ...(Array.isArray(install.seven_zip_error_tail) ? install.seven_zip_error_tail : []),
      ...(Array.isArray(install.seven_zip_output_tail) ? install.seven_zip_output_tail : []),
    ];
    sevenZipTail.textContent = lines.length ? lines.slice(-40).join('\n') : 'Noch keine 7-Zip-Ausgabe vorhanden.';
  }

  const healthInstall = document.getElementById('local-flux-health-install');
  const healthModel = document.getElementById('local-flux-health-model');
  if (healthInstall) healthInstall.textContent = install.installation_complete ? 'Vollständig' : install.checks?.comfyui?.ok ? 'Unvollständig' : 'Nicht installiert';
  if (healthModel) healthModel.textContent = install.checks?.model?.ok ? 'Vorhanden' : 'Fehlt';

  if (installButton) {
    if (install.status === 'failed' || install.status === 'stalled') installButton.textContent = 'Installation fortsetzen';
    else if (install.checks?.comfyui?.ok && !install.checks?.model?.ok) installButton.textContent = 'FLUX-Modell nachinstallieren';
    else if (install.can_resume) installButton.textContent = 'Installation fortsetzen';
    else if (install.status === 'completed') installButton.textContent = 'Installation prüfen / ergänzen';
    else installButton.textContent = 'Lokales FLUX installieren';
  }
}

async function getDetailedLocalFluxInstallStatus() {
  try {
    const install = await api('GET', '/api/local-image/install-status');
    renderLocalFluxInstallState(install);
    return install;
  } catch (err) {
    const status = document.getElementById('local-image-provider-status');
    if (status) status.textContent = `Installationsstatus konnte nicht gelesen werden: ${err.message}`;
    return null;
  }
}

let localFluxStatusTimer = null;
let localFluxStatusWatchActive = false;

async function watchLocalFluxInstallStatus() {
  if (localFluxStatusWatchActive) return;
  localFluxStatusWatchActive = true;
  const run = async () => {
    try {
      const install = await getDetailedLocalFluxInstallStatus();
      const delay = install && ['running', 'stalled'].includes(install.status) ? 2000 : 10000;
      localFluxStatusTimer = setTimeout(run, delay);
    } catch {
      localFluxStatusTimer = setTimeout(run, 10000);
    }
  };
  run();
}

async function pollLocalFluxInstall(startedAt = Date.now()) {
  const status = document.getElementById('local-image-provider-status');
  const pollStarted = Date.now();
  while (Date.now() - pollStarted < 60 * 60 * 1000) {
    const install = await getDetailedLocalFluxInstallStatus();
    if (!install) return null;
    if (status) status.textContent = install.message || 'Lokale FLUX-Installation läuft ...';

    const updatedAt = Date.parse(install.updated_at || '') || 0;
    const isFreshTerminal = ['completed', 'failed'].includes(install.status) && (updatedAt >= startedAt - 2000 || Date.now() - pollStarted > 8000);
    if (isFreshTerminal) {
      if (install.status === 'completed') await testLocalImageProvider({ skipInstallFetch: true });
      else if (status) status.textContent = `Installation fehlgeschlagen: ${install.message || 'Unbekannter Fehler'}`;
      return install;
    }
    await new Promise(resolve => setTimeout(resolve, 2000));
  }
  if (status) status.textContent = 'Die Installation läuft möglicherweise weiter. Nutze „Status prüfen“, um den aktuellen Stand zu laden.';
  return null;
}


function updateLocalFluxCommandCenter(result = {}, install = {}) {
  const stateName = result.engine_state || (!result.enabled ? 'disabled' : result.reachable ? 'running' : install.installation_complete ? 'stopped' : 'not-installed');
  const labels = {
    running: ['Aktiv und einsatzbereit', 'ComfyUI läuft. Vendoo kann lokale AI-Bilder erzeugen.'],
    stopped: ['Installiert, aber gestoppt', 'Ein Klick auf „Server starten“ aktiviert die lokale Bild-Engine.'],
    disabled: ['Deaktiviert', 'Vendoo nutzt lokales FLUX aktuell nicht. Installation und Dateien bleiben erhalten.'],
    'not-installed': ['Nicht installiert', 'Installiere ComfyUI und FLUX direkt hier – Vendoo führt dich automatisch durch den Vorgang.'],
  };
  const [title, detail] = labels[stateName] || labels['not-installed'];
  const orb = document.getElementById('local-flux-orb');
  const titleEl = document.getElementById('local-flux-engine-title');
  const detailEl = document.getElementById('local-flux-engine-detail');
  const badge = document.getElementById('local-flux-engine-badge');
  const enableLabel = document.getElementById('local-flux-enable-label');
  const serverHealth = document.getElementById('local-flux-health-server');
  const referenceHealth = document.getElementById('local-flux-health-reference');
  const heroText = document.getElementById('settings-flux-status-text');
  const heroChip = document.getElementById('settings-flux-status-chip');
  if (titleEl) titleEl.textContent = title;
  if (detailEl) detailEl.textContent = detail;
  if (badge) { badge.textContent = title; badge.className = `flux-state-badge is-${stateName}`; }
  if (orb) orb.className = `flux-orb is-${stateName}`;
  if (enableLabel) enableLabel.textContent = result.enabled ? 'Aktiviert' : 'Deaktiviert';
  if (serverHealth) serverHealth.textContent = result.reachable ? 'Online' : install.installation_complete ? 'Gestoppt' : 'Nicht verfügbar';
  if (referenceHealth) referenceHealth.textContent = document.getElementById('setting-ai-model-photo-use-source-reference')?.checked === false ? 'Aus' : 'Aktiv';
  if (heroText) heroText.textContent = title;
  if (heroChip) heroChip.className = `settings-status-chip is-${stateName}`;

  const startBtn = document.getElementById('start-local-image-provider-btn');
  const stopBtn = document.getElementById('stop-local-image-provider-btn');
  const uninstallBtn = document.getElementById('uninstall-local-image-provider-btn');
  if (startBtn) startBtn.disabled = !install.installation_complete || result.reachable || !result.enabled;
  if (stopBtn) stopBtn.disabled = !result.reachable;
  if (uninstallBtn) uninstallBtn.disabled = !install.checks?.comfyui?.ok && !install.checks?.model?.ok;
}

async function setLocalFluxEnabled(enabled) {
  const toggle = document.getElementById('setting-ai-model-photo-local-enabled');
  const status = document.getElementById('local-image-provider-status');
  if (toggle) toggle.disabled = true;
  try {
    await api('PUT', '/api/settings', { ai_model_photo_local_enabled: enabled ? '1' : '0' });
    if (status) status.textContent = enabled ? 'Lokales FLUX wurde aktiviert.' : 'Lokales FLUX wurde deaktiviert.';
    if (!enabled) {
      try { await api('POST', '/api/local-image/stop', {}); } catch {}
    } else {
      const current = await api('GET', '/api/local-image/status');
      if (current.install?.installation_complete && !current.reachable) {
        try { await api('POST', '/api/local-image/start', { profile: document.getElementById('setting-flux-runtime-profile')?.value || 'auto' }); } catch {}
        await new Promise(resolve => setTimeout(resolve, 3500));
      }
    }
    await testLocalImageProvider();
  } catch (err) {
    if (toggle) toggle.checked = !enabled;
    if (status) status.textContent = `Aktivierung konnte nicht geändert werden: ${err.message}`;
  } finally {
    if (toggle) toggle.disabled = false;
  }
}

async function uninstallLocalImageProvider() {
  const button = document.getElementById('uninstall-local-image-provider-btn');
  const status = document.getElementById('local-image-provider-status');
  const confirmed = window.confirm('Lokales FLUX wirklich deinstallieren? ComfyUI und das große FLUX-Modell werden entfernt. Deine Vendoo-Daten und heruntergeladenen Installationsarchive bleiben erhalten.');
  if (!confirmed) return;
  if (button) button.disabled = true;
  try {
    const result = await api('POST', '/api/local-image/uninstall', { remove_downloads: false });
    if (status) status.textContent = result.message || 'Deinstallation wurde gestartet.';
    await new Promise(resolve => setTimeout(resolve, 2500));
    const started = Date.now();
    while (Date.now() - started < 120000) {
      const install = await getDetailedLocalFluxInstallStatus();
      if (install && !install.checks?.comfyui?.ok && !install.checks?.model?.ok) break;
      await new Promise(resolve => setTimeout(resolve, 2000));
    }
    const toggle = document.getElementById('setting-ai-model-photo-local-enabled');
    if (toggle) toggle.checked = false;
    await testLocalImageProvider();
    toast('Lokales FLUX wurde deinstalliert.');
  } catch (err) {
    if (status) status.textContent = `Deinstallation fehlgeschlagen: ${err.message}`;
  } finally {
    if (button) button.disabled = false;
  }
}

function setupSettingsNavigation() {
  const buttons = [...document.querySelectorAll('[data-settings-panel]')];
  const panels = [...document.querySelectorAll('[data-settings-panel-content]')];
  const activate = (name) => {
    buttons.forEach(button => button.classList.toggle('active', button.dataset.settingsPanel === name));
    panels.forEach(panel => panel.classList.toggle('active', panel.dataset.settingsPanelContent === name));
    try { localStorage.setItem('vendoo-settings-panel', name); } catch {}
  };
  buttons.forEach(button => button.addEventListener('click', () => activate(button.dataset.settingsPanel)));
  let initial = 'general';
  try { initial = localStorage.getItem('vendoo-settings-panel') || initial; } catch {}
  if (!panels.some(panel => panel.dataset.settingsPanelContent === initial)) initial = 'general';
  activate(initial);
}

function updateReferenceStrengthUi() {
  const range = document.getElementById('setting-ai-model-photo-reference-strength');
  const output = document.getElementById('setting-ai-model-photo-reference-strength-value');
  if (range && output) output.textContent = `${range.value}%`;
  const referenceHealth = document.getElementById('local-flux-health-reference');
  if (referenceHealth) referenceHealth.textContent = document.getElementById('setting-ai-model-photo-use-source-reference')?.checked === false ? 'Aus' : `${range?.value || 70}%`;
}

async function testLocalImageProvider(options = {}) {
  const status = document.getElementById('local-image-provider-status');
  const button = document.getElementById('test-local-image-provider-btn');
  if (button) button.disabled = true;
  if (status) status.textContent = 'Lokaler FLUX/ComfyUI-Status wird geprüft ...';
  try {
    const result = await api('GET', '/api/local-image/status');
    const install = options.skipInstallFetch ? (result.install || {}) : (await getDetailedLocalFluxInstallStatus() || result.install || {});
    if (options.skipInstallFetch) renderLocalFluxInstallState(install);
    updateLocalFluxCommandCenter(result, install);
    if (status) {
      if (install.status === 'failed') {
        status.textContent = `Installation fehlgeschlagen · ${install.message || 'Details im Installationsstatus'}`;
      } else if (result.reachable) {
        status.textContent = `Aktiv · ${result.url} erreichbar · Modell und Workflow bereit${result.reference_image_supported ? ' · Produktreferenz unterstützt' : ''}`;
      } else {
        status.textContent = install.installation_complete ? 'Installiert, aber der Bildserver ist gestoppt.' : `Noch nicht vollständig installiert · ${install.phase_label || 'Installation erforderlich'}`;
      }
    }
    return { ...result, install };
  } catch (err) {
    if (status) status.textContent = `Lokaler FLUX-Test fehlgeschlagen: ${err.message}`;
    return null;
  } finally {
    if (button) button.disabled = false;
  }
}

async function installLocalImageProvider() {
  const status = document.getElementById('local-image-provider-status');
  const button = document.getElementById('install-local-image-provider-btn');
  const installModel = document.getElementById('local-flux-install-model')?.checked !== false;
  const enableAutostart = document.getElementById('local-flux-enable-autostart')?.checked !== false;
  const startNow = document.getElementById('local-flux-start-now')?.checked !== false;
  const warning = installModel
    ? 'ComfyUI und FLUX.1-schnell FP8 werden heruntergeladen. Das benötigt viel Speicherplatz und kann lange dauern. Installation starten?'
    : 'ComfyUI wird installiert, das FLUX-Modell jedoch nicht. Installation starten?';
  if (!window.confirm(warning)) return null;
  if (button) button.disabled = true;
  if (status) status.textContent = 'Lokale FLUX-Installation wird gestartet ...';
  try {
    const startedAt = Date.now();
    const result = await api('POST', '/api/local-image/install', {
      install_model: installModel,
      enable_autostart: enableAutostart,
      start_now: startNow,
      gpu: 'auto',
    });
    if (status) status.textContent = result.message || 'Lokale FLUX-Installation wurde gestartet.';
    await new Promise(resolve => setTimeout(resolve, 900));
    await pollLocalFluxInstall(startedAt);
    return result;
  } catch (err) {
    if (status) status.textContent = `Lokale FLUX-Installation fehlgeschlagen: ${err.message}`;
    return null;
  } finally {
    if (button) button.disabled = false;
  }
}

async function updateLocalImageProvider() {
  const status = document.getElementById('local-image-provider-status');
  const button = document.getElementById('update-local-image-provider-btn');
  if (!window.confirm('Lokales ComfyUI jetzt aktualisieren? Bestehende Modelle bleiben erhalten.')) return null;
  if (button) button.disabled = true;
  try {
    const startedAt = Date.now();
    const result = await api('POST', '/api/local-image/update', {});
    if (status) status.textContent = result.message || 'Lokales FLUX-Update wurde gestartet.';
    await new Promise(resolve => setTimeout(resolve, 900));
    await pollLocalFluxInstall(startedAt);
    return result;
  } catch (err) {
    if (status) status.textContent = `Update fehlgeschlagen: ${err.message}`;
    return null;
  } finally {
    if (button) button.disabled = false;
  }
}

async function startLocalImageProvider() {
  const status = document.getElementById('local-image-provider-status');
  const button = document.getElementById('start-local-image-provider-btn');
  if (button) button.disabled = true;
  try {
    const result = await api('POST', '/api/local-image/start', { profile: document.getElementById('setting-flux-runtime-profile')?.value || 'auto' });
    if (status) status.textContent = result.message || 'Lokaler FLUX-Server wird gestartet.';
    await waitForFluxEngineReady({ statusElement: status });
    return testLocalImageProvider();
  } catch (err) {
    if (status) status.textContent = `Start fehlgeschlagen: ${err.message}`;
    return null;
  } finally {
    if (button) button.disabled = false;
  }
}

async function stopLocalImageProvider() {
  const status = document.getElementById('local-image-provider-status');
  const button = document.getElementById('stop-local-image-provider-btn');
  if (button) button.disabled = true;
  try {
    const result = await api('POST', '/api/local-image/stop', {});
    if (status) status.textContent = result.message || 'Lokaler FLUX-Server wird beendet.';
    await new Promise(resolve => setTimeout(resolve, 1800));
    return testLocalImageProvider();
  } catch (err) {
    if (status) status.textContent = `Stoppen fehlgeschlagen: ${err.message}`;
    return null;
  } finally {
    if (button) button.disabled = false;
  }
}

async function regenerateLocalImageToken() {
  const field = document.getElementById('setting-ai-model-photo-local-token');
  const status = document.getElementById('local-image-provider-status');
  const button = document.getElementById('regenerate-local-image-token-btn');
  if (button) button.disabled = true;
  try {
    const result = await api('POST', '/api/local-image/regenerate-token', {});
    if (field) field.value = result.token || '';
    if (status) status.textContent = 'Neuer lokaler Zugriffstoken erzeugt. Bitte Einstellungen speichern.';
    return result;
  } catch (err) {
    if (status) status.textContent = `Token konnte nicht erneuert werden: ${err.message}`;
    return null;
  } finally {
    if (button) button.disabled = false;
  }
}

function updateAiPhotoProviderSettingsUi() {
  const provider = document.getElementById('setting-ai-model-photo-provider')?.value || 'auto';
  const openrouterSelect = document.getElementById('setting-ai-model-photo-openrouter-model');
  const refreshBtn = document.getElementById('refresh-openrouter-models-btn');
  const freeOnly = document.getElementById('setting-ai-model-photo-free-only');
  const openAiField = document.getElementById('setting-ai-model-photo-openai-model');
  const localUrlField = document.getElementById('setting-ai-model-photo-local-url');
  const localTokenField = document.getElementById('setting-ai-model-photo-local-token');
  const localWorkflowField = document.getElementById('setting-ai-model-photo-local-workflow-path');
  const localInstallField = document.getElementById('setting-ai-model-photo-local-install-path');
  const localEnabled = document.getElementById('setting-ai-model-photo-local-enabled');
  const localRequireToken = document.getElementById('setting-ai-model-photo-local-require-token');
  const testBtn = document.getElementById('test-local-image-provider-btn');
  const installBtn = document.getElementById('install-local-image-provider-btn');
  const updateBtn = document.getElementById('update-local-image-provider-btn');
  const startBtn = document.getElementById('start-local-image-provider-btn');
  const stopBtn = document.getElementById('stop-local-image-provider-btn');
  const regenBtn = document.getElementById('regenerate-local-image-token-btn');
  const useLocal = provider === 'auto' || provider === 'local';
  const useOpenRouter = provider !== 'openai' && provider !== 'local';
  const useOpenAi = provider !== 'openrouter' && provider !== 'local';
  if (openrouterSelect) openrouterSelect.disabled = !useOpenRouter;
  if (refreshBtn) refreshBtn.disabled = !useOpenRouter;
  if (freeOnly) freeOnly.disabled = !useOpenRouter;
  if (openAiField) openAiField.disabled = !useOpenAi;
  if (localUrlField) localUrlField.disabled = !useLocal;
  if (localTokenField) localTokenField.disabled = !useLocal;
  if (localWorkflowField) localWorkflowField.disabled = !useLocal;
  if (localInstallField) localInstallField.disabled = !useLocal;
  if (localEnabled) localEnabled.disabled = false;
  if (localRequireToken) localRequireToken.disabled = !useLocal;
  if (testBtn) testBtn.disabled = false;
  if (installBtn) installBtn.disabled = false;
  if (updateBtn) updateBtn.disabled = false;
  // Start/Stop werden ausschließlich vom realen Runtime-Status gesteuert.
  if (regenBtn) regenBtn.disabled = false;
}

function renderOpenRouterModelPreview(models = [], selectedId = '', freeOnly = true) {
  const wrap = document.getElementById('openrouter-models-preview');
  const status = document.getElementById('openrouter-models-status');
  const select = document.getElementById('setting-ai-model-photo-openrouter-model');
  if (!wrap || !status || !select) return;

  const normalized = Array.isArray(models) ? models : [];
  const options = ['<option value="">Automatisch bestes Modell wählen</option>'];
  normalized.forEach(model => {
    const label = `${model.name || model.id}${model.is_free ? ' · Free' : ''}`;
    options.push(`<option value="${escapeHtml(model.id || '')}">${escapeHtml(label)}</option>`);
  });
  select.innerHTML = options.join('');
  if (selectedId) select.value = selectedId;
  if (!select.value) select.value = '';

  if (!normalized.length) {
    wrap.classList.add('hidden');
    wrap.innerHTML = '';
    status.textContent = freeOnly
      ? 'Keine kostenlosen OpenRouter-Bildmodelle gefunden. In Auto nutzt Vendoo dann OpenAI als Fallback.'
      : 'Keine OpenRouter-Bildmodelle gefunden.';
    return;
  }

  const top = normalized.slice(0, 4);
  wrap.classList.remove('hidden');
  wrap.innerHTML = top.map(model => `
    <div class="openrouter-model-card">
      <div class="openrouter-model-top">
        <div>
          <strong>${escapeHtml(model.name || model.id || 'OpenRouter Modell')}</strong>
          <span>${escapeHtml(model.id || '')}</span>
        </div>
        <div class="openrouter-model-badges">
          <span class="openrouter-badge ${model.is_free ? 'is-free' : ''}">${model.is_free ? 'Free' : 'Paid'}</span>
          <span class="openrouter-badge">Image</span>
        </div>
      </div>
      <p class="openrouter-model-description">${escapeHtml((model.description || 'Kein Beschreibungstext verfügbar.').slice(0, 220))}</p>
    </div>
  `).join('');
  status.textContent = `${normalized.length} OpenRouter-Bildmodell(e) verfügbar${freeOnly ? ' (Free gefiltert)' : ''}.`;
}

async function loadOpenRouterImageModels(selectedId = '') {
  const status = document.getElementById('openrouter-models-status');
  const freeOnly = document.getElementById('setting-ai-model-photo-free-only')?.checked !== false;
  if (status) status.textContent = 'OpenRouter-Bildmodelle werden geladen ...';
  try {
    const result = await api('GET', `/api/ai-model-photos/providers/openrouter/models?free_only=${freeOnly ? '1' : '0'}`);
    state.openRouterImageModels = result.models || [];
    renderOpenRouterModelPreview(state.openRouterImageModels, selectedId, !!result.free_only);
    if (freeOnly && state.openRouterImageModels.length === 0) {
      try {
        const diagnostics = await api('GET', '/api/ai-model-photos/providers/openrouter/diagnostics');
        if (status) {
          status.textContent = diagnostics.free_image_model_count > 0
            ? `${diagnostics.free_image_model_count} Free-Bildmodell(e) erkannt, aber aktuell nicht nutzbar.`
            : `Aktuell 0 kostenlose Bildmodelle bei OpenRouter; ${diagnostics.image_model_count || 0} kostenpflichtige Bildmodelle erkannt. Auto nutzt OpenAI als Fallback, sofern ein OpenAI-Key hinterlegt ist.`;
        }
      } catch {}
    }
  } catch (err) {
    state.openRouterImageModels = [];
    renderOpenRouterModelPreview([], selectedId, freeOnly);
    if (status) status.textContent = `OpenRouter-Modelle konnten nicht geladen werden: ${err.message}`;
  }
}

function populateSettingsForm(settings, platforms, providers) {
  const ps = document.getElementById('setting-default-platform');
  const fs = document.getElementById('filter-platform');
  const as = document.getElementById('setting-default-ai');

  ps.innerHTML = ''; fs.innerHTML = '<option value="">Alle Plattformen</option>';
  for (const p of platforms) {
    ps.innerHTML += `<option value="${p.id}">${p.name}</option>`;
    fs.innerHTML += `<option value="${p.id}">${p.name}</option>`;
  }
  as.innerHTML = providers.map(p => `<option value="${p.id}">${p.name}</option>`).join('');

  ps.value = settings.default_platform || 'vinted';
  as.value = settings.default_ai || 'claude';
  document.getElementById('setting-default-language').value = settings.default_language || 'de';
  document.getElementById('setting-seller-notes').value = settings.seller_notes || '';
  document.getElementById('setting-anthropic-key').placeholder = settings.has_anthropic_key ? 'Key gesetzt (leer lassen um beizubehalten)' : 'sk-ant-...';
  document.getElementById('setting-openai-key').placeholder = settings.has_openai_key ? 'Key gesetzt (leer lassen um beizubehalten)' : 'sk-...';
  document.getElementById('setting-openrouter-key').placeholder = settings.has_openrouter_key ? 'Key gesetzt (leer lassen um beizubehalten)' : 'sk-or-...';
  document.getElementById('setting-etsy-key').placeholder = settings.has_etsy_key ? 'Key gesetzt (leer lassen um beizubehalten)' : 'xxxxxxxx';
  document.getElementById('setting-ebay-client-id').placeholder = settings.has_ebay_credentials ? 'Key gesetzt (leer lassen um beizubehalten)' : 'eBay-App-...';
  document.getElementById('setting-ebay-client-secret').placeholder = settings.has_ebay_credentials ? 'Key gesetzt (leer lassen um beizubehalten)' : 'eBay-Cert-...';
  document.getElementById('setting-ebay-sandbox').checked = !!settings.ebay_sandbox;
  document.getElementById('setting-ollama-url').value = settings.ollama_url || 'http://localhost:11434';
  document.getElementById('setting-public-base-url').value = settings.public_base_url || '';
  detectPublicBaseUrl({ apply: !settings.public_base_url });
  document.getElementById('setting-ai-model-photo-provider').value = settings.ai_model_photo_provider || 'auto';
  const savedPhotoMode = ['ghost_mannequin', 'flatlay'].includes(settings.ai_model_photo_default_mode) ? settings.ai_model_photo_default_mode : 'flatlay';
  document.getElementById('setting-ai-model-photo-mode').value = savedPhotoMode;
  document.getElementById('setting-ai-model-photo-variants').value = String(settings.ai_model_photo_default_variants || '1');
  document.getElementById('setting-ai-model-photo-preset').value = settings.ai_model_photo_default_preset || 'mixed';
  document.getElementById('setting-ai-model-photo-enabled').checked = String(settings.ai_model_photos_enabled || '1') === '1';
  document.getElementById('setting-ai-model-photo-free-only').checked = String(settings.ai_model_photo_openrouter_free_only || '1') === '1';
  document.getElementById('setting-ai-model-photo-block-underwear').checked = String(settings.ai_model_photo_block_underwear || '1') === '1';
  document.getElementById('setting-ai-model-photo-block-kids').checked = String(settings.ai_model_photo_block_kids || '1') === '1';
  document.getElementById('setting-ai-model-photo-preserve-logos').checked = String(settings.ai_model_photo_preserve_logos || '1') === '1';
  getAiPromptDraft('generator').preserveLogos = String(settings.ai_model_photo_preserve_logos || '1') === '1';
  getAiPromptDraft('detail').preserveLogos = String(settings.ai_model_photo_preserve_logos || '1') === '1';
  document.getElementById('setting-ai-model-photo-openai-model').value = settings.ai_model_photo_openai_model || 'gpt-image-1';
  document.getElementById('setting-ai-model-photo-local-url').value = settings.ai_model_photo_local_url || 'http://127.0.0.1:8188';
  document.getElementById('setting-ai-model-photo-local-token').placeholder = settings.local_image_token_set ? 'Token gesetzt (leer lassen um beizubehalten)' : 'Lokaler Token (optional)';
  document.getElementById('setting-ai-model-photo-local-workflow-path').value = settings.ai_model_photo_local_workflow_path || 'local-ai/workflows/vendoo_flux_schnell_api.json';
  document.getElementById('setting-ai-model-photo-local-install-path').value = settings.ai_model_photo_local_install_path || 'local-ai/comfyui';
  document.getElementById('setting-ai-model-photo-local-enabled').checked = String(settings.ai_model_photo_local_enabled || '1') === '1';
  document.getElementById('setting-ai-model-photo-local-require-token').checked = String(settings.ai_model_photo_local_require_token || '1') === '1';
  document.getElementById('setting-ai-model-photo-use-source-reference').checked = String(settings.ai_model_photo_use_source_reference || '1') === '1';
  document.getElementById('setting-ai-model-photo-reference-strength').value = String(settings.ai_model_photo_reference_strength || '70');
  document.getElementById('setting-ai-model-photo-local-resolution').value = String(settings.ai_model_photo_local_resolution || '768');
  document.getElementById('setting-flux-runtime-profile').value = settings.flux_runtime_profile || 'auto';
  document.getElementById('setting-flux-default-profile').value = settings.flux_default_profile || 'balanced';
  document.getElementById('setting-flux-default-style').value = settings.flux_default_style || 'photorealistic';
  document.getElementById('setting-flux-default-format').value = settings.flux_default_format || '768x768';
  document.getElementById('setting-flux-default-variants').value = settings.flux_default_variants || '1';
  document.getElementById('setting-flux-max-parallel-jobs').value = settings.flux_max_parallel_jobs || '1';
  document.getElementById('setting-flux-prompt-assistant').checked = String(settings.flux_prompt_assistant || '1') === '1';
  document.getElementById('setting-flux-seed-lock').checked = String(settings.flux_seed_lock || '0') === '1';
  const studioStyle = document.getElementById('flux-studio-style'); if (studioStyle) studioStyle.value = settings.flux_default_style || 'photorealistic';
  const studioFormat = document.getElementById('flux-studio-format'); if (studioFormat) studioFormat.value = settings.flux_default_format || '768x768';
  const studioLock = document.getElementById('flux-studio-seed-lock'); if (studioLock) studioLock.checked = String(settings.flux_seed_lock || '0') === '1';
  applyFluxProfile(settings.flux_default_profile || 'balanced', { persist: false });
  setFluxVariantCount(settings.flux_default_variants || '1', { persist: false });
  updateReferenceStrengthUi();
  const aiStatusText = document.getElementById('settings-ai-status-text');
  const aiStatusChip = document.getElementById('settings-ai-status-chip');
  if (aiStatusText) aiStatusText.textContent = String(settings.ai_model_photos_enabled || '1') === '1' ? 'Aktiv' : 'Deaktiviert';
  if (aiStatusChip) aiStatusChip.className = `settings-status-chip ${String(settings.ai_model_photos_enabled || '1') === '1' ? 'is-running' : 'is-disabled'}`;
  updateAiPhotoProviderSettingsUi();
  loadOpenRouterImageModels(settings.ai_model_photo_openrouter_model || '');
  testLocalImageProvider();

  const etsyWrap = document.getElementById('etsy-connect-wrap');
  const etsyBadge = document.getElementById('etsy-status-badge');
  if (settings.has_etsy_key) {
    etsyWrap.classList.remove('hidden');
    if (settings.etsy_connected) {
      etsyBadge.textContent = 'Verbunden';
      etsyBadge.className = 'etsy-status connected';
    } else {
      etsyBadge.textContent = 'Nicht verbunden';
      etsyBadge.className = 'etsy-status disconnected';
    }
  } else {
    etsyWrap.classList.add('hidden');
  }

  const ebayWrap = document.getElementById('ebay-connect-wrap');
  const ebayBadge = document.getElementById('ebay-status-badge');
  if (settings.has_ebay_credentials) {
    ebayWrap.classList.remove('hidden');
    if (settings.ebay_connected) {
      ebayBadge.textContent = settings.ebay_sandbox ? 'Verbunden (Sandbox)' : 'Verbunden';
      ebayBadge.className = 'etsy-status connected';
    } else {
      ebayBadge.textContent = 'Nicht verbunden';
      ebayBadge.className = 'etsy-status disconnected';
    }
  } else {
    ebayWrap.classList.add('hidden');
  }
}

function setupSettings() {
  setupSettingsNavigation();
  updateReferenceStrengthUi();
  watchLocalFluxInstallStatus();
  document.getElementById('detect-public-base-url-btn')?.addEventListener('click', () => detectPublicBaseUrl({ apply: true }));
  document.getElementById('setting-ai-model-photo-provider')?.addEventListener('change', updateAiPhotoProviderSettingsUi);
  document.getElementById('setting-ai-model-photo-free-only')?.addEventListener('change', () => loadOpenRouterImageModels(document.getElementById('setting-ai-model-photo-openrouter-model')?.value || ''));
  document.getElementById('refresh-openrouter-models-btn')?.addEventListener('click', () => loadOpenRouterImageModels(document.getElementById('setting-ai-model-photo-openrouter-model')?.value || ''));
  document.getElementById('test-local-image-provider-btn')?.addEventListener('click', testLocalImageProvider);
  document.getElementById('install-local-image-provider-btn')?.addEventListener('click', installLocalImageProvider);
  document.getElementById('update-local-image-provider-btn')?.addEventListener('click', updateLocalImageProvider);
  document.getElementById('start-local-image-provider-btn')?.addEventListener('click', startLocalImageProvider);
  document.getElementById('stop-local-image-provider-btn')?.addEventListener('click', stopLocalImageProvider);
  document.getElementById('regenerate-local-image-token-btn')?.addEventListener('click', regenerateLocalImageToken);
  document.getElementById('uninstall-local-image-provider-btn')?.addEventListener('click', uninstallLocalImageProvider);
  document.getElementById('setting-ai-model-photo-local-enabled')?.addEventListener('change', (event) => setLocalFluxEnabled(event.target.checked));
  document.getElementById('setting-ai-model-photo-reference-strength')?.addEventListener('input', updateReferenceStrengthUi);
  document.getElementById('setting-ai-model-photo-use-source-reference')?.addEventListener('change', updateReferenceStrengthUi);
  document.getElementById('setting-ai-model-photo-enabled')?.addEventListener('change', (event) => {
    const text = document.getElementById('settings-ai-status-text');
    const chip = document.getElementById('settings-ai-status-chip');
    if (text) text.textContent = event.target.checked ? 'Aktiv' : 'Deaktiviert';
    if (chip) chip.className = `settings-status-chip ${event.target.checked ? 'is-running' : 'is-disabled'}`;
  });
  document.querySelectorAll('#settings input, #settings select, #settings textarea').forEach(field => {
    if (field.closest('#theme-manager-root')) return;
    field.addEventListener('change', () => {
      const title = document.getElementById('settings-save-title');
      const copy = document.getElementById('settings-save-copy');
      if (title) title.textContent = 'Ungespeicherte Änderungen';
      if (copy) copy.textContent = 'Mit „Einstellungen speichern“ übernehmen.';
    });
  });

  document.getElementById('save-settings-btn').addEventListener('click', async () => {
    try {
      const settings = {
        default_platform: document.getElementById('setting-default-platform').value,
        default_ai: document.getElementById('setting-default-ai').value,
        default_language: document.getElementById('setting-default-language').value,
        seller_notes: document.getElementById('setting-seller-notes').value,
        ollama_url: document.getElementById('setting-ollama-url').value,
        public_base_url: document.getElementById('setting-public-base-url').value.trim(),
        ai_model_photos_enabled: document.getElementById('setting-ai-model-photo-enabled').checked ? '1' : '0',
        ai_model_photo_provider: document.getElementById('setting-ai-model-photo-provider').value,
        ai_model_photo_default_mode: document.getElementById('setting-ai-model-photo-mode').value,
        ai_model_photo_default_variants: document.getElementById('setting-ai-model-photo-variants').value,
        ai_model_photo_default_preset: document.getElementById('setting-ai-model-photo-preset').value,
        ai_model_photo_openrouter_free_only: document.getElementById('setting-ai-model-photo-free-only').checked ? '1' : '0',
        ai_model_photo_block_underwear: document.getElementById('setting-ai-model-photo-block-underwear').checked ? '1' : '0',
        ai_model_photo_block_kids: document.getElementById('setting-ai-model-photo-block-kids').checked ? '1' : '0',
        ai_model_photo_preserve_logos: document.getElementById('setting-ai-model-photo-preserve-logos').checked ? '1' : '0',
        ai_model_photo_openrouter_model: document.getElementById('setting-ai-model-photo-openrouter-model').value,
        ai_model_photo_openai_model: document.getElementById('setting-ai-model-photo-openai-model').value.trim() || 'gpt-image-1',
        ai_model_photo_local_enabled: document.getElementById('setting-ai-model-photo-local-enabled').checked ? '1' : '0',
        ai_model_photo_local_url: document.getElementById('setting-ai-model-photo-local-url').value.trim() || 'http://127.0.0.1:8188',
        ai_model_photo_local_workflow_path: document.getElementById('setting-ai-model-photo-local-workflow-path').value.trim() || 'local-ai/workflows/vendoo_flux_schnell_api.json',
        ai_model_photo_local_install_path: document.getElementById('setting-ai-model-photo-local-install-path').value.trim() || 'local-ai/comfyui',
        ai_model_photo_local_require_token: document.getElementById('setting-ai-model-photo-local-require-token').checked ? '1' : '0',
        ai_model_photo_use_source_reference: document.getElementById('setting-ai-model-photo-use-source-reference').checked ? '1' : '0',
        ai_model_photo_reference_strength: document.getElementById('setting-ai-model-photo-reference-strength').value || '70',
        ai_model_photo_local_resolution: document.getElementById('setting-ai-model-photo-local-resolution').value || '768',
        flux_runtime_profile: document.getElementById('setting-flux-runtime-profile').value || 'auto',
        flux_default_profile: document.getElementById('setting-flux-default-profile').value || 'balanced',
        flux_prompt_assistant: document.getElementById('setting-flux-prompt-assistant').checked ? '1' : '0',
        flux_default_style: document.getElementById('setting-flux-default-style').value || 'photorealistic',
        flux_default_format: document.getElementById('setting-flux-default-format').value || '768x768',
        flux_default_variants: document.getElementById('setting-flux-default-variants').value || '1',
        flux_seed_lock: document.getElementById('setting-flux-seed-lock').checked ? '1' : '0',
        flux_max_parallel_jobs: document.getElementById('setting-flux-max-parallel-jobs').value || '1',
      };
      const ak = document.getElementById('setting-anthropic-key').value;
      const ok = document.getElementById('setting-openai-key').value;
      const ork = document.getElementById('setting-openrouter-key').value;
      const ek = document.getElementById('setting-etsy-key').value;
      const localToken = document.getElementById('setting-ai-model-photo-local-token').value.trim();
      const ebayId = document.getElementById('setting-ebay-client-id').value;
      const ebaySec = document.getElementById('setting-ebay-client-secret').value;
      const ebayRu = document.getElementById('setting-ebay-ru-name').value;
      const ebaySandbox = document.getElementById('setting-ebay-sandbox').checked;
      if (ak) settings.anthropic_key = ak;
      if (ok) settings.openai_key = ok;
      if (ork) settings.openrouter_key = ork;
      if (ek) settings.etsy_key = ek;
      if (localToken) settings.ai_model_photo_local_token = localToken;
      if (ebayId) settings.ebay_client_id = ebayId;
      if (ebaySec) settings.ebay_client_secret = ebaySec;
      if (ebayRu) settings.ebay_ru_name = ebayRu;
      settings.ebay_sandbox = ebaySandbox ? 'true' : 'false';

      const saved = await api('PUT', '/api/settings', settings);
      state.platform = settings.default_platform;
      state.aiProvider = settings.default_ai;
      state.language = settings.default_language;
      renderPlatformButtons();
      renderProviderButtons();
      document.getElementById('language-select').value = state.language;

      document.getElementById('setting-anthropic-key').value = '';
      document.getElementById('setting-openai-key').value = '';
      document.getElementById('setting-openrouter-key').value = '';
      document.getElementById('setting-etsy-key').value = '';
      document.getElementById('setting-ai-model-photo-local-token').value = '';
      document.getElementById('setting-ebay-client-id').value = '';
      document.getElementById('setting-ebay-client-secret').value = '';
      document.getElementById('setting-anthropic-key').placeholder = saved.has_anthropic_key ? 'Key gesetzt (leer lassen um beizubehalten)' : 'sk-ant-...';
      document.getElementById('setting-openai-key').placeholder = saved.has_openai_key ? 'Key gesetzt (leer lassen um beizubehalten)' : 'sk-...';
      document.getElementById('setting-openrouter-key').placeholder = saved.has_openrouter_key ? 'Key gesetzt (leer lassen um beizubehalten)' : 'sk-or-...';
      document.getElementById('setting-etsy-key').placeholder = saved.has_etsy_key ? 'Key gesetzt (leer lassen um beizubehalten)' : 'xxxxxxxx';
      document.getElementById('setting-ai-model-photo-local-token').placeholder = saved.local_image_token_set ? 'Token gesetzt (leer lassen um beizubehalten)' : 'Lokaler Token (optional)';
      document.getElementById('setting-ebay-client-id').placeholder = saved.has_ebay_credentials ? 'Key gesetzt (leer lassen um beizubehalten)' : 'eBay-App-...';
      document.getElementById('setting-ebay-client-secret').placeholder = saved.has_ebay_credentials ? 'Key gesetzt (leer lassen um beizubehalten)' : 'eBay-Cert-...';

      const etsyWrap = document.getElementById('etsy-connect-wrap');
      if (saved.has_etsy_key) {
        etsyWrap.classList.remove('hidden');
        const badge = document.getElementById('etsy-status-badge');
        if (saved.etsy_connected) { badge.textContent = 'Verbunden'; badge.className = 'etsy-status connected'; }
        else { badge.textContent = 'Nicht verbunden'; badge.className = 'etsy-status disconnected'; }
      }

      updateAiPhotoProviderSettingsUi();
      if (saved.public_base_url !== undefined) document.getElementById('setting-public-base-url').value = saved.public_base_url || '';
      detectPublicBaseUrl({ apply: false });
      loadOpenRouterImageModels(saved.ai_model_photo_openrouter_model || document.getElementById('setting-ai-model-photo-openrouter-model').value || '');
      testLocalImageProvider();

      const ebayWrap = document.getElementById('ebay-connect-wrap');
      if (saved.has_ebay_credentials) {
        ebayWrap.classList.remove('hidden');
        const ebBadge = document.getElementById('ebay-status-badge');
        if (saved.ebay_connected) {
          ebBadge.textContent = saved.ebay_sandbox ? 'Verbunden (Sandbox)' : 'Verbunden';
          ebBadge.className = 'etsy-status connected';
        } else {
          ebBadge.textContent = 'Nicht verbunden';
          ebBadge.className = 'etsy-status disconnected';
        }
      }
      const saveTitle = document.getElementById('settings-save-title');
      const saveCopy = document.getElementById('settings-save-copy');
      if (saveTitle) saveTitle.textContent = 'Einstellungen gespeichert';
      if (saveCopy) saveCopy.textContent = `Zuletzt gespeichert: ${new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}`;
      toast('Einstellungen gespeichert!');
    } catch (err) { toast('Fehler: ' + err.message); }
  });

  document.getElementById('etsy-connect-btn').addEventListener('click', () => {
    window.open('/auth/etsy', 'etsy-auth', 'width=600,height=700');
  });
  document.getElementById('ebay-connect-btn').addEventListener('click', () => {
    window.open('/auth/ebay', 'ebay-auth', 'width=600,height=700');
  });
  window.addEventListener('message', async (e) => {
    if (e.data === 'etsy-connected') {
      const badge = document.getElementById('etsy-status-badge');
      badge.textContent = 'Verbunden';
      badge.className = 'etsy-status connected';
      toast('Etsy erfolgreich verbunden!');
    }
    if (e.data === 'ebay-connected') {
      const badge = document.getElementById('ebay-status-badge');
      badge.textContent = 'Verbunden';
      badge.className = 'etsy-status connected';
      toast('eBay erfolgreich verbunden!');
    }
  });

  document.querySelectorAll('.toggle-visibility').forEach(btn => {
    btn.addEventListener('click', () => {
      const inp = btn.previousElementSibling;
      const isPw = inp.type === 'password';
      inp.type = isPw ? 'text' : 'password';
      btn.textContent = isPw ? 'Verbergen' : 'Zeigen';
    });
  });
}

// --- Category Picker ---
const catCache = {};

async function loadCategories(platformId) {
  if (catCache[platformId]) return catCache[platformId];
  try {
    const data = await api('GET', `/api/categories/${platformId}`);
    catCache[platformId] = data;
    return data;
  } catch { return { tree: [], flat: [] }; }
}

function createCategoryPicker(container, platformId, initialValue) {
  const wrap = typeof container === 'string' ? document.getElementById(container) : container;
  if (!wrap) return { getValue: () => null, setValue: () => {} };

  let selected = null;
  let dropdownOpen = false;

  wrap.innerHTML = `
    <div class="cat-picker-wrap">
      <input type="text" class="cat-search meta-input" placeholder="Kategorie suchen...">
      <div class="cat-dropdown"></div>
      <div class="cat-selected-display" style="display:none"></div>
    </div>`;

  const input = wrap.querySelector('.cat-search');
  const dropdown = wrap.querySelector('.cat-dropdown');
  const selectedDisplay = wrap.querySelector('.cat-selected-display');

  async function doSearch(q) {
    const data = await loadCategories(platformId);
    if (!q || q.length < 2) {
      renderTree(data.tree);
      return;
    }
    try {
      const results = await api('GET', `/api/categories/${platformId}/match?q=${encodeURIComponent(q)}`);
      renderResults(results);
    } catch { renderResults([]); }
  }

  function renderResults(results) {
    if (!results.length) {
      dropdown.innerHTML = '<div class="cat-empty">Keine Kategorien gefunden</div>';
      openDropdown();
      return;
    }
    dropdown.innerHTML = results.map(r => `
      <div class="cat-result-item" data-path="${escapeHtml(r.pathStr)}" data-name="${escapeHtml(r.name)}">
        <div class="cat-result-name">${escapeHtml(r.name)}</div>
        <div class="cat-result-path">${escapeHtml(r.pathStr)}</div>
      </div>`).join('');
    dropdown.querySelectorAll('.cat-result-item').forEach(el => {
      el.addEventListener('click', () => selectCategory(el.dataset.path, el.dataset.name));
    });
    openDropdown();
  }

  function renderTree(tree) {
    if (!tree || !tree.length) {
      dropdown.innerHTML = '<div class="cat-empty">Keine Kategorien verfügbar</div>';
      openDropdown();
      return;
    }
    dropdown.innerHTML = tree.map(group => {
      const children = (group.children || []).map(sub => {
        if (sub.children) {
          const leaves = sub.children.map(leaf =>
            `<div class="cat-leaf" data-path="${escapeHtml([group.name, sub.name, leaf.name].join(' > '))}" data-name="${escapeHtml(leaf.name)}">${escapeHtml(leaf.name)}</div>`
          ).join('');
          return `<div class="cat-sub">
            <div class="cat-sub-hd">${escapeHtml(sub.name)}</div>
            <div class="cat-sub-children">${leaves}</div>
          </div>`;
        }
        return `<div class="cat-leaf" data-path="${escapeHtml([group.name, sub.name].join(' > '))}" data-name="${escapeHtml(sub.name)}">${escapeHtml(sub.name)}</div>`;
      }).join('');
      return `<div class="cat-group">
        <div class="cat-group-hd" data-expanded="false">${escapeHtml(group.name)}</div>
        <div class="cat-group-body" style="display:none">${children}</div>
      </div>`;
    }).join('');

    dropdown.querySelectorAll('.cat-group-hd').forEach(hd => {
      hd.addEventListener('click', () => {
        const body = hd.nextElementSibling;
        const expanded = hd.dataset.expanded === 'true';
        hd.dataset.expanded = expanded ? 'false' : 'true';
        body.style.display = expanded ? 'none' : 'block';
        hd.classList.toggle('expanded', !expanded);
      });
    });

    dropdown.querySelectorAll('.cat-leaf').forEach(el => {
      el.addEventListener('click', () => selectCategory(el.dataset.path, el.dataset.name));
    });
    openDropdown();
  }

  function selectCategory(path, name) {
    selected = path;
    input.value = '';
    input.style.display = 'none';
    selectedDisplay.style.display = 'flex';
    selectedDisplay.innerHTML = `
      <span class="cat-sel-text">${escapeHtml(path)}</span>
      <button class="cat-sel-clear" type="button">&times;</button>`;
    selectedDisplay.querySelector('.cat-sel-clear').addEventListener('click', clearSelection);
    closeDropdown();
  }

  function clearSelection() {
    selected = null;
    input.style.display = '';
    selectedDisplay.style.display = 'none';
    input.value = '';
    input.focus();
  }

  function openDropdown() { dropdown.style.display = 'block'; dropdownOpen = true; }
  function closeDropdown() { dropdown.style.display = 'none'; dropdownOpen = false; }

  input.addEventListener('focus', () => doSearch(input.value));
  input.addEventListener('input', debounce(() => doSearch(input.value), 200));

  document.addEventListener('click', e => {
    if (dropdownOpen && !wrap.contains(e.target)) closeDropdown();
  });

  if (initialValue) {
    selectCategory(initialValue, initialValue.split(' > ').pop());
  }

  return {
    getValue: () => selected,
    setValue: (val) => {
      if (val) selectCategory(val, val.split(' > ').pop());
      else clearSelection();
    },
  };
}

let metaCategoryPicker = null;

function addQuillTooltips() {
  const tooltips = {
    'ql-bold': 'Fett', 'ql-italic': 'Kursiv', 'ql-underline': 'Unterstrichen',
    'ql-strike': 'Durchgestrichen', 'ql-blockquote': 'Zitat', 'ql-link': 'Link einfügen',
    'ql-clean': 'Formatierung entfernen', 'ql-list[value="ordered"]': 'Nummerierte Liste',
    'ql-list[value="bullet"]': 'Aufzählung', 'ql-color': 'Textfarbe',
    'ql-background': 'Hintergrundfarbe', 'ql-align': 'Ausrichtung', 'ql-header': 'Überschrift',
  };
  for (const [sel, tip] of Object.entries(tooltips)) {
    document.querySelectorAll(`.ql-${sel}, .${sel}, button.${sel.split('[')[0]}`).forEach(el => { el.title = tip; });
  }
  document.querySelectorAll('.ql-bold').forEach(el => { el.title = 'Fett'; });
  document.querySelectorAll('.ql-italic').forEach(el => { el.title = 'Kursiv'; });
  document.querySelectorAll('.ql-underline').forEach(el => { el.title = 'Unterstrichen'; });
  document.querySelectorAll('.ql-strike').forEach(el => { el.title = 'Durchgestrichen'; });
  document.querySelectorAll('.ql-blockquote').forEach(el => { el.title = 'Zitat'; });
  document.querySelectorAll('.ql-link').forEach(el => { el.title = 'Link einfügen'; });
  document.querySelectorAll('.ql-clean').forEach(el => { el.title = 'Formatierung entfernen'; });
  document.querySelectorAll('.ql-list[value="ordered"]').forEach(el => { el.title = 'Nummerierte Liste'; });
  document.querySelectorAll('.ql-list[value="bullet"]').forEach(el => { el.title = 'Aufzählung'; });
  document.querySelectorAll('.ql-color .ql-picker-label').forEach(el => { el.title = 'Textfarbe'; });
  document.querySelectorAll('.ql-background .ql-picker-label').forEach(el => { el.title = 'Hintergrundfarbe'; });
  document.querySelectorAll('.ql-align .ql-picker-label').forEach(el => { el.title = 'Ausrichtung'; });
  document.querySelectorAll('.ql-header .ql-picker-label').forEach(el => { el.title = 'Überschrift'; });
}

function generateQRDataUrl(text, size) {
  if (typeof QRious === 'undefined') return null;
  try {
    const qr = new QRious({ value: text, size: size || 120, level: 'M' });
    return qr.toDataURL();
  } catch { return null; }
}

let printStudioState = {
  mode: 'labels',
  listings: [],
  listing: null,
  copies: 1,
  format: '90x50',
  showPrice: true,
};

function ensurePrintStudio() {
  let studio = document.getElementById('print-studio');
  if (studio) return studio;

  studio = document.createElement('div');
  studio.id = 'print-studio';
  studio.className = 'print-studio hidden';
  studio.innerHTML = `
    <div class="print-studio-backdrop" data-print-close></div>
    <section class="print-studio-shell" role="dialog" aria-modal="true" aria-labelledby="print-studio-title">
      <header class="print-studio-header">
        <div>
          <span class="print-studio-kicker">Vendoo Print Studio</span>
          <h2 id="print-studio-title">Druckvorschau</h2>
        </div>
        <button type="button" class="print-studio-close" data-print-close aria-label="Druckvorschau schließen">×</button>
      </header>
      <div class="print-studio-layout">
        <aside class="print-studio-controls">
          <div class="print-control-group" id="print-mode-summary"></div>
          <label class="print-control-field" id="print-format-field">
            <span>Etikettenformat</span>
            <select id="print-format-select">
              <option value="90x50">90 × 50 mm · 10 pro A4</option>
              <option value="62x29">62 × 29 mm · 27 pro A4</option>
            </select>
          </label>
          <label class="print-control-field" id="print-copies-field">
            <span>Exemplare je Artikel</span>
            <input id="print-copies-input" type="number" min="1" max="20" value="1">
          </label>
          <label class="print-control-check" id="print-price-field">
            <input id="print-show-price" type="checkbox" checked>
            <span>Preis auf Etikett anzeigen</span>
          </label>
          <div class="print-hint">
            <strong>Ohne neues Fenster</strong>
            <span>Die Vorschau bleibt in Vendoo. Erst „Drucken“ öffnet den normalen Systemdruckdialog.</span>
          </div>
        </aside>
        <div class="print-preview-pane">
          <div class="print-preview-toolbar">
            <span id="print-preview-count"></span>
            <span>Maßstabsgetreue A4-Vorschau</span>
          </div>
          <div id="print-preview-pages" class="print-preview-pages"></div>
        </div>
      </div>
      <footer class="print-studio-footer">
        <button type="button" class="vd-button" data-print-close>Abbrechen</button>
        <button type="button" class="vd-button primary" id="print-now-btn">Drucken</button>
      </footer>
    </section>
  `;
  document.body.appendChild(studio);

  studio.querySelectorAll('[data-print-close]').forEach(button => {
    button.addEventListener('click', closePrintStudio);
  });
  studio.querySelector('#print-format-select').addEventListener('change', event => {
    printStudioState.format = event.target.value;
    renderPrintStudio();
  });
  studio.querySelector('#print-copies-input').addEventListener('input', event => {
    printStudioState.copies = Math.max(1, Math.min(20, Number(event.target.value) || 1));
    renderPrintStudio();
  });
  studio.querySelector('#print-show-price').addEventListener('change', event => {
    printStudioState.showPrice = event.target.checked;
    renderPrintStudio();
  });
  studio.querySelector('#print-now-btn').addEventListener('click', () => {
    document.body.classList.add('print-studio-active');
    window.print();
  });
  window.addEventListener('afterprint', () => document.body.classList.remove('print-studio-active'));
  document.addEventListener('keydown', event => {
    if (event.key === 'Escape' && !studio.classList.contains('hidden')) closePrintStudio();
  });
  return studio;
}

function closePrintStudio() {
  const studio = document.getElementById('print-studio');
  if (!studio) return;
  studio.classList.add('hidden');
  document.body.classList.remove('print-studio-open', 'print-studio-active');
}

function openPrintStudio(mode, payload) {
  printStudioState = {
    mode,
    listings: payload.listings || [],
    listing: payload.listing || null,
    copies: 1,
    format: '90x50',
    showPrice: true,
  };
  const studio = ensurePrintStudio();
  studio.classList.remove('hidden');
  document.body.classList.add('print-studio-open');
  studio.querySelector('#print-format-select').value = printStudioState.format;
  studio.querySelector('#print-copies-input').value = '1';
  studio.querySelector('#print-show-price').checked = true;
  renderPrintStudio();
}

function printLabels(listings) {
  if (!Array.isArray(listings) || !listings.length) return;
  openPrintStudio('labels', { listings });
}

function printDatasheet(listing) {
  if (!listing) return;
  openPrintStudio('datasheet', { listing });
}

function getPrintPlatformName(platform) {
  return state.platforms.find(item => item.id === platform)?.name || platformLabel(platform);
}

function buildPrintLabel(listing) {
  const sku = listing.sku || `VD-${String(listing.id).padStart(4, '0')}`;
  const title = (listing.title || 'Ohne Titel').substring(0, 60);
  const qr = generateQRDataUrl(JSON.stringify({ sku, id: listing.id }), 170);
  const price = listing.price ? formatCurrency(listing.price) : '';
  const platform = getPrintPlatformName(listing.platform);
  return `
    <article class="print-label-card">
      <div class="print-label-accent"></div>
      <div class="print-label-qr">${qr ? `<img src="${qr}" alt="QR-Code ${escapeHtml(sku)}">` : ''}</div>
      <div class="print-label-copy">
        <div class="print-label-brand">Vendoo</div>
        <div class="print-label-sku">${escapeHtml(sku)}</div>
        <div class="print-label-title">${escapeHtml(title)}</div>
        <div class="print-label-meta">
          <span>${escapeHtml(platform)}</span>
          ${printStudioState.showPrice && price ? `<strong>${escapeHtml(price)}</strong>` : ''}
        </div>
      </div>
    </article>
  `;
}

function buildPrintDatasheet(listing) {
  const sku = listing.sku || `VD-${String(listing.id).padStart(4, '0')}`;
  const qr = generateQRDataUrl(JSON.stringify({ sku, id: listing.id }), 190);
  const photos = (listing.photos || []).slice(0, 4);
  const dateValue = listing.created_at ? new Date(`${listing.created_at}Z`) : new Date();
  const date = Number.isNaN(dateValue.getTime()) ? '' : dateValue.toLocaleDateString('de-DE');
  const meta = [
    ['Marke', listing.brand],
    ['Kategorie', listing.category],
    ['Größe', listing.size],
    ['Farbe', listing.color],
    ['Zustand', listing.condition],
    ['Preis', listing.price ? formatCurrency(listing.price) : ''],
    ['Plattform', getPrintPlatformName(listing.platform)],
    ['Lagerort', listing.storage_location],
    ['Erstellt', date],
  ].filter(([, value]) => value);

  return `
    <article class="print-datasheet">
      <header class="print-datasheet-header">
        <div>
          <span class="print-datasheet-brand">Vendoo · Artikeldatenblatt</span>
          <h1>${escapeHtml(listing.title || 'Ohne Titel')}</h1>
          <div class="print-datasheet-sku">${escapeHtml(sku)}</div>
        </div>
        ${qr ? `<img class="print-datasheet-qr" src="${qr}" alt="QR-Code ${escapeHtml(sku)}">` : ''}
      </header>
      ${photos.length ? `<div class="print-datasheet-photos">${photos.map(photo => `<div><img src="${escapeHtml(photoUrl(photo))}" alt=""></div>`).join('')}</div>` : ''}
      ${listing.description ? `<section class="print-datasheet-description"><h2>Beschreibung</h2><p>${escapeHtml(String(listing.description).replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim())}</p></section>` : ''}
      <section class="print-datasheet-meta">
        ${meta.map(([label, value]) => `<div><span>${escapeHtml(label)}</span><strong>${escapeHtml(String(value))}</strong></div>`).join('')}
      </section>
    </article>
  `;
}

function chunkPrintItems(items, size) {
  const chunks = [];
  for (let index = 0; index < items.length; index += size) chunks.push(items.slice(index, index + size));
  return chunks;
}

function renderPrintStudio() {
  const studio = ensurePrintStudio();
  const pages = studio.querySelector('#print-preview-pages');
  const summary = studio.querySelector('#print-mode-summary');
  const count = studio.querySelector('#print-preview-count');
  const formatField = studio.querySelector('#print-format-field');
  const copiesField = studio.querySelector('#print-copies-field');
  const priceField = studio.querySelector('#print-price-field');

  const labelsMode = printStudioState.mode === 'labels';
  formatField.classList.toggle('hidden', !labelsMode);
  copiesField.classList.toggle('hidden', !labelsMode);
  priceField.classList.toggle('hidden', !labelsMode);

  if (!labelsMode) {
    const listing = printStudioState.listing;
    summary.innerHTML = `<span class="print-summary-icon">▤</span><div><strong>Datenblatt</strong><span>${escapeHtml(listing?.sku || listing?.title || 'Artikel')}</span></div>`;
    count.textContent = '1 Seite';
    pages.innerHTML = `<div class="print-page datasheet-page">${buildPrintDatasheet(listing)}</div>`;
    return;
  }

  const expanded = [];
  printStudioState.listings.forEach(listing => {
    for (let copy = 0; copy < printStudioState.copies; copy += 1) expanded.push(listing);
  });
  const compact = printStudioState.format === '62x29';
  const pageSize = compact ? 27 : 10;
  const pageChunks = chunkPrintItems(expanded, pageSize);
  summary.innerHTML = `<span class="print-summary-icon">⌗</span><div><strong>Etiketten</strong><span>${printStudioState.listings.length} Artikel · ${expanded.length} Etiketten</span></div>`;
  count.textContent = `${pageChunks.length} ${pageChunks.length === 1 ? 'Seite' : 'Seiten'}`;
  pages.innerHTML = pageChunks.map(chunk => `
    <div class="print-page label-page ${compact ? 'compact-labels' : 'standard-labels'}">
      <div class="print-label-grid">${chunk.map(buildPrintLabel).join('')}</div>
    </div>
  `).join('');
}

// --- Trash / Papierkorb ---
function setupTrash() {
  document.getElementById('empty-trash-btn').addEventListener('click', async () => {
    if (!confirm('Papierkorb endgültig leeren? Dies kann nicht rückgängig gemacht werden!')) return;
    await api('DELETE', '/api/trash');
    toast('Papierkorb geleert');
    loadTrash();
    updateTrashBadge();
  });
}

async function loadTrash() {
  try {
    const listings = await api('GET', '/api/trash');
    const c = document.getElementById('trash-list');
    if (!listings.length) {
      c.innerHTML = '<div class="empty-state">Papierkorb ist leer</div>';
      return;
    }

    c.innerHTML = listings.map(l => {
      const photo = l.photos?.[0] ? `<img class="thumb-small" src="/uploads/${l.photos[0]}" alt="">` : '<div class="thumb-small"></div>';
      const pName = state.platforms.find(p => p.id === l.platform)?.name || l.platform;
      const deletedDate = l.deleted_at ? new Date(l.deleted_at + 'Z').toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: '2-digit', hour: '2-digit', minute: '2-digit' }) : '';
      const priceStr = l.price ? `${l.price}€` : '';

      return `<div class="trash-item" data-id="${l.id}">
        ${photo}
        <div class="item-info">
          <div class="item-title">${escapeHtml(l.title || 'Ohne Titel')}</div>
          <div class="item-meta">${pName} · ${priceStr} · Gelöscht: ${deletedDate}</div>
        </div>
        <div class="trash-actions">
          <button class="restore-btn" data-id="${l.id}">Wiederherstellen</button>
          <button class="perm-delete-btn" data-id="${l.id}">Endgültig löschen</button>
        </div>
      </div>`;
    }).join('');

    c.querySelectorAll('.restore-btn').forEach(btn => {
      btn.addEventListener('click', async () => {
        await api('POST', `/api/trash/${btn.dataset.id}/restore`);
        toast('Listing wiederhergestellt');
        loadTrash();
        updateTrashBadge();
      });
    });

    c.querySelectorAll('.perm-delete-btn').forEach(btn => {
      btn.addEventListener('click', async () => {
        if (!confirm('Listing endgültig löschen? Dies kann nicht rückgängig gemacht werden!')) return;
        await api('DELETE', `/api/trash/${btn.dataset.id}`);
        toast('Endgültig gelöscht');
        loadTrash();
        updateTrashBadge();
      });
    });
  } catch (err) { toast('Fehler: ' + err.message); }
}

async function updateTrashBadge() {
  try {
    const data = await api('GET', '/api/trash/count');
    const badge = document.getElementById('trash-badge');
    if (data.count > 0) {
      badge.textContent = data.count;
      badge.classList.remove('hidden');
    } else {
      badge.classList.add('hidden');
    }
  } catch {}
}

// --- Utils ---
function toast(msg) {
  const el = document.getElementById('toast');
  el.textContent = msg;
  el.classList.add('show');
  setTimeout(() => el.classList.remove('show'), 2500);
}

function debounce(fn, ms) {
  let t; return (...a) => { clearTimeout(t); t = setTimeout(() => fn(...a), ms); };
}

function escapeHtml(str) {
  const d = document.createElement('div');
  d.textContent = str || '';
  return d.innerHTML;
}

// --- Dark Mode ---
function setupDarkMode() {
  const saved = localStorage.getItem('vendoo-theme');
  if (window.VendooTheme) {
    window.VendooTheme.setTheme(saved === 'light' || saved === 'dark' || saved === 'contrast' ? saved : 'system', { persist: false });
  } else if (saved === 'dark' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
    document.documentElement.setAttribute('data-theme', 'dark');
  }
  updateThemeIcon();

  document.getElementById('theme-toggle').addEventListener('click', () => {
    const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
    const nextTheme = isDark ? 'light' : 'dark';
    if (window.VendooTheme) window.VendooTheme.setTheme(nextTheme);
    else {
      document.documentElement.setAttribute('data-theme', nextTheme);
      localStorage.setItem('vendoo-theme', nextTheme);
    }
    updateThemeIcon();
  });
}

function updateThemeIcon() {
  const btn = document.getElementById('theme-toggle');
  const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
  btn.title = isDark ? 'Light Mode' : 'Dark Mode';
  btn.innerHTML = isDark
    ? '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>'
    : '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';
}

// --- Dashboard & Studio Flow ---
const STUDIO_STAGES = [
  { key: 'ready', label: 'Bereit', state: 'Prüfbereit', tone: 'success' },
  { key: 'scheduled', label: 'Geplant', state: 'Geplant', tone: 'warning' },
  { key: 'published', label: 'Veröffentlicht', state: 'Live', tone: 'success' },
  { key: 'sold', label: 'Verkauft', state: 'Verkauft', tone: 'success' },
];

let studioWorkflowData = null;
let selectedStudioListingId = null;
let todayFilter = 'all';
let dashboardAnalyticsData = null;
let dashboardResizeObserver = null;
let dashboardResizeTimer = null;
let dashboardLastChartWidth = 0;

function formatCurrency(value) {
  if (value === null || value === undefined || value === '') return '';
  return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(Number(value) || 0);
}

function platformLabel(platform) {
  return ({ vinted: 'Vinted', 'ebay-ka': 'Kleinanzeigen', 'ebay-de': 'eBay', etsy: 'Etsy', klekt: 'KLEKT' })[platform] || platform || '—';
}

function photoUrl(photo) {
  if (!photo) return '';
  return `/uploads/${String(photo).split('/').map(encodeURIComponent).join('/')}`;
}

function relativeTime(value) {
  if (!value) return '';
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return '';
  const diff = Math.max(0, Date.now() - date.getTime());
  const minutes = Math.floor(diff / 60000);
  if (minutes < 1) return 'Gerade eben';
  if (minutes < 60) return `vor ${minutes} Min.`;
  const hours = Math.floor(minutes / 60);
  if (hours < 24) return `vor ${hours} Std.`;
  const days = Math.floor(hours / 24);
  if (days < 7) return `vor ${days} Tag${days === 1 ? '' : 'en'}`;
  return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' });
}

function activityGlyph(type) {
  return ({ sold: '✓', published: '↗', failed: '!', scheduled: '◷', copied: '⧉', pending: '·', created: '+' })[type] || '·';
}

function setupAnalyticsDashboard() {
  document.getElementById('dashboard-period')?.addEventListener('change', loadAnalyticsDashboard);
  document.getElementById('dashboard-refresh')?.addEventListener('click', loadAnalyticsDashboard);

  const dashboardPage = document.getElementById('dashboard');
  const redrawDashboard = () => {
    if (!dashboardAnalyticsData || !dashboardPage?.classList.contains('active')) return;
    clearTimeout(dashboardResizeTimer);
    dashboardResizeTimer = setTimeout(() => {
      const width = document.getElementById('dashboard-line-chart')?.clientWidth || 0;
      if (Math.abs(width - dashboardLastChartWidth) < 12) return;
      dashboardLastChartWidth = width;
      renderDashboardLineChart(dashboardAnalyticsData.timeline || []);
      renderDashboardBars(dashboardAnalyticsData.platformRevenue || []);
      renderDashboardCategories(dashboardAnalyticsData.categoryRevenue || []);
    }, 90);
  };
  if ('ResizeObserver' in window && dashboardPage) {
    dashboardResizeObserver?.disconnect();
    dashboardResizeObserver = new ResizeObserver(redrawDashboard);
    dashboardResizeObserver.observe(dashboardPage);
  } else {
    window.addEventListener('resize', redrawDashboard, { passive: true });
  }

  document.querySelectorAll('[data-dashboard-action]').forEach(button => button.addEventListener('click', async () => {
    const action = button.dataset.dashboardAction;
    if (action === 'new') return switchToTab('generator');
    if (action === 'qr') {
      switchToTab('generator');
      setTimeout(() => document.getElementById('generator-mobile-upload')?.click(), 160);
      return;
    }
    if (action === 'publish') return switchToTab('publish');
    if (action === 'template') return switchToTab('templates');
    if (action === 'photos') {
      try {
        const listings = await api('GET', '/api/listings');
        const photos = listings.flatMap(item => item.photos || []).slice(0, 60);
        await downloadPublishPhotos(photos);
      } catch (error) { toast(`Fotos konnten nicht geladen werden: ${error.message}`); }
    }
  }));
}

function setupTodayFlow() {
  document.querySelectorAll('[data-today-filter]').forEach(button => button.addEventListener('click', () => {
    todayFilter = button.dataset.todayFilter || 'all';
    document.querySelectorAll('.today-filter-tabs [data-today-filter]').forEach(tab => tab.classList.toggle('active', tab.dataset.todayFilter === todayFilter));
    renderTodayReady(studioWorkflowData);
    if (todayFilter === 'attention') document.querySelector('.attention-panel')?.scrollIntoView({ behavior: 'smooth', block: 'center' });
  }));
  document.getElementById('today-sort')?.addEventListener('change', () => renderTodayReady(studioWorkflowData));
}

function dashboardSetText(id, value) {
  const element = document.getElementById(id);
  if (element) element.textContent = value;
}

function renderSparkline(id, values, tone = 'brand') {
  const svg = document.getElementById(id);
  if (!svg) return;
  const raw = (values || []).map(Number).filter(Number.isFinite);
  const nums = raw.length > 1 ? raw : [raw[0] || 0, raw[0] || 0];
  const max = Math.max(...nums, 1);
  const min = Math.min(...nums, 0);
  const range = Math.max(1, max - min);
  const points = nums.map((value, index) => `${(index / Math.max(1, nums.length - 1)) * 96 + 2},${24 - ((value - min) / range) * 18}`).join(' ');
  const last = points.split(' ').at(-1)?.split(',') || ['98', '14'];
  svg.setAttribute('viewBox', '0 0 100 28');
  svg.setAttribute('preserveAspectRatio', 'none');
  svg.innerHTML = `<polyline points="${points}" fill="none" stroke="currentColor" stroke-width="2" vector-effect="non-scaling-stroke" stroke-linecap="round" stroke-linejoin="round"/><circle cx="${last[0]}" cy="${last[1]}" r="2.2" fill="currentColor"/>`;
  svg.dataset.tone = tone;
}

function renderDashboardLineChart(timeline) {
  const container = document.getElementById('dashboard-line-chart');
  if (!container) return;
  const values = timeline.map(item => Number(item.revenue) || 0);
  const max = Math.max(...values, 1);
  const measuredWidth = Math.round(container.getBoundingClientRect().width || container.clientWidth || 720);
  const width = Math.max(320, measuredWidth);
  const height = width < 520 ? 220 : 250;
  const left = width < 520 ? 38 : 48;
  const right = 14;
  const top = 16;
  const bottom = 34;
  const chartW = width - left - right;
  const chartH = height - top - bottom;
  const points = values.map((value, index) => {
    const x = left + (index / Math.max(1, values.length - 1)) * chartW;
    const y = top + chartH - (value / max) * chartH;
    return { x, y, value, item: timeline[index] };
  });
  const line = points.map(point => `${point.x.toFixed(1)},${point.y.toFixed(1)}`).join(' ');
  const area = `${left},${top + chartH} ${line} ${left + chartW},${top + chartH}`;
  const yGrid = [0, .25, .5, .75, 1].map(part => {
    const y = top + chartH - part * chartH;
    return `<line x1="${left}" y1="${y}" x2="${left + chartW}" y2="${y}"/><text x="${left - 7}" y="${y + 4}">${Math.round(max * part).toLocaleString('de-DE')} €</text>`;
  }).join('');
  const step = Math.max(1, Math.floor(timeline.length / 5));
  const labels = timeline.map((item, index) => index % step === 0 || index === timeline.length - 1
    ? `<text x="${points[index].x}" y="${height - 8}" text-anchor="middle">${new Date(item.date).toLocaleDateString('de-DE', { day: '2-digit', month: 'short' })}</text>` : '').join('');
  container.innerHTML = `<svg viewBox="0 0 ${width} ${height}" preserveAspectRatio="xMidYMid meet"><g class="chart-grid">${yGrid}${labels}</g><polygon class="chart-area" points="${area}"/><polyline class="chart-line" points="${line}"/>${points.map(point => `<circle class="chart-point" cx="${point.x}" cy="${point.y}" r="4" tabindex="0"><title>${new Date(point.item.date).toLocaleDateString('de-DE')}: ${formatCurrency(point.value)}</title></circle>`).join('')}</svg>`;
  dashboardLastChartWidth = measuredWidth;
}

function renderDashboardBars(items) {
  const container = document.getElementById('dashboard-platform-chart');
  if (!container) return;
  const data = items.length ? items.slice(0, 5) : [{ platform: 'Keine Verkäufe', revenue: 0 }];
  const max = Math.max(...data.map(item => Number(item.revenue) || 0), 1);
  container.innerHTML = data.map(item => `<div class="dashboard-bar-item" title="${escapeHtml(platformLabel(item.platform))}: ${escapeHtml(formatCurrency(item.revenue))}"><strong>${escapeHtml(formatCurrency(item.revenue))}</strong><div><i style="height:${Math.max(5, (Number(item.revenue) / max) * 100)}%"></i></div><span>${escapeHtml(platformLabel(item.platform))}</span></div>`).join('');
}

function renderDashboardCategories(items) {
  const container = document.getElementById('dashboard-category-chart');
  if (!container) return;
  const data = items.length ? items : [{ category: 'Keine Verkäufe', revenue: 1 }];
  const total = data.reduce((sum, item) => sum + (Number(item.revenue) || 0), 0) || 1;
  let cursor = 0;
  const colors = ['#49a66f', '#3b82dc', '#8054b3', '#e87334', '#e4ab2f', '#b9b6b0'];
  const stops = data.map((item, index) => {
    const start = cursor;
    cursor += ((Number(item.revenue) || 0) / total) * 100;
    return `${colors[index % colors.length]} ${start}% ${cursor}%`;
  }).join(',');
  container.innerHTML = `<div class="dashboard-donut" style="background:conic-gradient(${stops})"><div><strong>${escapeHtml(formatCurrency(total))}</strong><span>Gesamt</span></div></div><div class="dashboard-category-legend">${data.map((item, index) => `<div><i style="background:${colors[index % colors.length]}"></i><span>${escapeHtml(item.category)}</span><strong>${Math.round((Number(item.revenue) || 0) / total * 1000) / 10}%</strong></div>`).join('')}</div>`;
}

function renderDashboardFocus(focus) {
  const container = document.getElementById('dashboard-focus');
  if (!container) return;
  const items = [
    ['ready', focus.ready, 'Artikel bereit zum Veröffentlichen', 'history'],
    ['price', focus.priceChecks, 'Preisaktualisierungen empfohlen', 'history'],
    ['failed', focus.failedPublishes, 'fehlgeschlagene Publish-Versuche', 'publish'],
    ['uploads', focus.mobileUploads, 'neue Handy-Foto-Uploads', 'generator'],
    ['locations', focus.activeLocations, 'aktive Lagerorte', 'inventory'],
  ];
  container.innerHTML = items.map(([tone, count, label, tab]) => `<button class="dashboard-focus-item tone-${tone}" type="button" data-focus-tab="${tab}"><strong>${Number(count) || 0}</strong><span>${escapeHtml(label)}</span><em>Anzeigen</em></button>`).join('');
  container.querySelectorAll('[data-focus-tab]').forEach(button => button.addEventListener('click', () => switchToTab(button.dataset.focusTab)));
}

function renderDashboardTopSellers(items) {
  const body = document.getElementById('dashboard-top-sellers');
  if (!body) return;
  body.innerHTML = items.length ? items.map(item => `<tr data-dashboard-listing="${item.id}"><td><div class="dashboard-product">${item.photo ? `<img src="${escapeHtml(photoUrl(item.photo))}" alt="">` : '<span></span>'}<div><strong>${escapeHtml(item.title || 'Ohne Titel')}</strong><small>${escapeHtml([item.color, item.size].filter(Boolean).join(' / '))}</small></div></div></td><td>${escapeHtml(item.sku || '')}</td><td>${escapeHtml(platformLabel(item.platform))}</td><td>${escapeHtml(formatCurrency(item.revenue))}</td><td>${item.sales || 1}</td><td><span class="dashboard-status">Verkauft</span></td></tr>`).join('') : '<tr><td colspan="6" class="dashboard-empty">Noch keine Verkäufe vorhanden.</td></tr>';
  body.querySelectorAll('[data-dashboard-listing]').forEach(row => row.addEventListener('click', () => showDetail(Number(row.dataset.dashboardListing))));
}

function renderDashboardFeed(id, items, isNotification = false) {
  const container = document.getElementById(id);
  if (!container) return;
  if (!items?.length) { container.innerHTML = '<div class="dashboard-empty">Keine Einträge</div>'; return; }
  container.innerHTML = items.slice(0, 6).map(item => `<button type="button" data-feed-listing="${item.listingId || ''}"><span class="dashboard-feed-icon tone-${item.severity || item.type || 'info'}">${isNotification ? '!' : activityGlyph(item.type)}</span><span><strong>${escapeHtml(item.text || item.message || item.title || 'Aktivität')}</strong><small>${escapeHtml(relativeTime(item.at || item.createdAt))}</small></span></button>`).join('');
  container.querySelectorAll('[data-feed-listing]').forEach(button => button.addEventListener('click', () => { const idValue = Number(button.dataset.feedListing); if (idValue) showDetail(idValue); }));
}

async function loadAnalyticsDashboard() {
  const period = Number(document.getElementById('dashboard-period')?.value || 30);
  const refresh = document.getElementById('dashboard-refresh');
  refresh?.classList.add('is-loading');
  try {
    const data = await api('GET', `/api/dashboard/analytics?days=${period}`);
    dashboardAnalyticsData = data;
    dashboardSetText('dash-revenue-today', formatCurrency(data.kpis.revenueToday));
    dashboardSetText('dash-sales-week', String(data.kpis.sales7Days));
    dashboardSetText('dash-active-listings', String(data.kpis.activeListings));
    dashboardSetText('dash-drafts', String(data.kpis.drafts));
    dashboardSetText('dash-inventory-value', formatCurrency(data.kpis.inventoryValue));
    dashboardSetText('dash-publish-rate', `${data.kpis.publishSuccessRate.toLocaleString('de-DE')}%`);
    dashboardSetText('dash-period-revenue', formatCurrency(data.timeline.reduce((sum, item) => sum + Number(item.revenue || 0), 0)));
    dashboardSetText('dash-period-label', `Letzte ${data.periodDays} Tage`);
    dashboardSetText('dash-platform-total', formatCurrency(data.platformRevenue.reduce((sum, item) => sum + Number(item.revenue || 0), 0)));
    const timelineValues = data.timeline.map(item => item.revenue);
    renderSparkline('spark-revenue', timelineValues, 'green');
    renderSparkline('spark-sales', data.timeline.map(item => item.sales), 'blue');
    renderSparkline('spark-active', timelineValues.map((_, index) => Math.max(0, data.kpis.activeListings - data.timeline.length + index)), 'purple');
    renderSparkline('spark-drafts', timelineValues.map((_, index) => Math.max(0, data.kpis.drafts + Math.sin(index) * 2)), 'orange');
    renderSparkline('spark-inventory', timelineValues.map((value, index) => data.kpis.inventoryValue * .9 + index * (data.kpis.inventoryValue * .1 / Math.max(1, timelineValues.length))), 'green');
    renderSparkline('spark-publish', timelineValues.map((_, index) => Math.max(0, data.kpis.publishSuccessRate - 4 + index / 3)), 'blue');
    renderDashboardLineChart(data.timeline);
    renderDashboardBars(data.platformRevenue);
    renderDashboardCategories(data.categoryRevenue);
    renderDashboardFocus(data.focus);
    renderDashboardTopSellers(data.topSellers);
    renderDashboardFeed('dashboard-activity', data.activity);
    renderDashboardFeed('dashboard-notifications', data.notifications, true);
    dashboardSetText('dashboard-quality-value', `${data.kpis.quality}%`);
    dashboardSetText('dashboard-quality-label', data.kpis.quality >= 85 ? 'Sehr hoch' : data.kpis.quality >= 65 ? 'Gut, mit Potenzial' : 'Optimierung empfohlen');
    document.getElementById('dashboard-quality-ring')?.style.setProperty('--confidence', `${data.kpis.quality}%`);
    dashboardSetText('dashboard-system-state', data.system.healthy ? 'Alle Systeme sind betriebsbereit' : 'Prüfung erforderlich');
    dashboardSetText('dashboard-system-copy', `${data.system.queuePending} Queue-Einträge ausstehend · ${data.system.scheduled} Veröffentlichungen geplant.`);
  } catch (error) {
    toast(`Dashboard konnte nicht geladen werden: ${error.message}`);
  } finally { refresh?.classList.remove('is-loading'); }
}

function getStudioItem(id) {
  if (!studioWorkflowData?.stages) return null;
  for (const stage of Object.values(studioWorkflowData.stages)) {
    const found = stage.items?.find(item => item.id === id);
    if (found) return found;
  }
  return null;
}

function currentTodayItems(data) {
  if (!data?.stages) return [];
  const key = todayFilter === 'all' ? 'ready' : todayFilter;
  if (key === 'attention') return [...(data.stages.captured?.items || []), ...(data.stages.ai_review?.items || [])];
  return data.stages[key]?.items || [];
}

function renderTodaySummary(data) {
  const set = (id, value) => { const element = document.getElementById(id); if (element) element.textContent = String(value || 0); };
  set('today-count-ready', data.stages?.ready?.count);
  set('today-count-scheduled', data.stages?.scheduled?.count);
  set('today-count-published', data.stages?.published?.count);
  set('today-count-sold', data.stages?.sold?.count);
  set('today-count-attention', data.attentionCount);
  set('today-ready-total', data.stages?.ready?.count);
  set('today-scheduled-total', data.stages?.scheduled?.count);
}

function renderTodayReady(data) {
  const container = document.getElementById('today-ready-grid');
  if (!container || !data) return;
  const labels = { all: 'Bereit für den nächsten Schritt', ready: 'Bereit für den nächsten Schritt', scheduled: 'Geplante Artikel', published: 'Veröffentlichte Artikel', sold: 'Verkaufte Artikel', attention: 'Artikel mit Prüfbedarf' };
  const heading = container.closest('.work-panel')?.querySelector('h3');
  if (heading) heading.textContent = labels[todayFilter] || labels.all;
  let items = [...currentTodayItems(data)];
  const sort = document.getElementById('today-sort')?.value || 'priority';
  if (sort === 'newest') items.sort((a, b) => String(b.updatedAt || b.createdAt).localeCompare(String(a.updatedAt || a.createdAt)));
  if (sort === 'price') items.sort((a, b) => (Number(b.price) || 0) - (Number(a.price) || 0));
  items = items.slice(0, 4);
  if (!items.length) { container.innerHTML = '<div class="empty-state">Für diesen Filter sind keine Artikel vorhanden.</div>'; return; }
  container.innerHTML = items.map(item => `<button class="today-product-card${selectedStudioListingId === item.id ? ' selected' : ''}" type="button" data-studio-listing="${item.id}"><div class="today-product-media">${item.photos?.[0] ? `<img src="${escapeHtml(photoUrl(item.photos[0]))}" alt="${escapeHtml(item.title || 'Produktbild')}" decoding="async" draggable="false">` : '<span class="today-image-placeholder">Kein Bild</span>'}<b>${escapeHtml(platformLabel(item.platform))}</b></div><div class="today-product-copy"><strong>${escapeHtml(item.title)}</strong><span>${escapeHtml(item.size || item.color || item.sku || '')}</span><div><em>${escapeHtml(formatCurrency(item.price) || 'Preis fehlt')}</em><small>${Math.max(0, item.completeness || 0)}%</small></div></div></button>`).join('');
  container.querySelectorAll('.today-product-media img').forEach(image => image.addEventListener('error', () => {
    image.hidden = true;
    const placeholder = document.createElement('span');
    placeholder.className = 'today-image-placeholder';
    placeholder.textContent = 'Bild nicht verfügbar';
    image.parentElement?.prepend(placeholder);
  }, { once: true }));
  container.querySelectorAll('[data-studio-listing]').forEach(card => card.addEventListener('click', () => selectStudioListing(Number(card.dataset.studioListing))));
}

function renderTodayScheduled(data) {
  const body = document.getElementById('today-scheduled-list');
  if (!body) return;
  const items = data.stages?.scheduled?.items?.slice(0, 4) || [];
  body.innerHTML = items.length ? items.map(item => `<tr><td><button type="button" data-scheduled-id="${item.id}"><span>${item.photos?.[0] ? `<img src="${escapeHtml(photoUrl(item.photos[0]))}" alt="">` : ''}</span><div><strong>${escapeHtml(item.title)}</strong><small>${escapeHtml(item.size || item.sku || '')}</small></div></button></td><td>${escapeHtml(platformLabel(item.platform))}</td><td>${item.scheduledAt ? new Date(item.scheduledAt.replace(' ', 'T')).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : '—'}</td><td><span class="today-status">Geplant</span></td><td><button class="vd-button compact" type="button" data-scheduled-id="${item.id}">Vorschau</button></td></tr>`).join('') : '<tr><td colspan="5" class="dashboard-empty">Keine geplanten Veröffentlichungen.</td></tr>';
  body.querySelectorAll('[data-scheduled-id]').forEach(button => button.addEventListener('click', () => selectStudioListing(Number(button.dataset.scheduledId))));
}

function renderStudioAttention(data) {
  const list = document.getElementById('studio-attention');
  const count = document.getElementById('attention-count');
  if (count) count.textContent = String(data.attentionCount || 0);
  if (!list) return;
  if (!data.attention?.length) { list.innerHTML = '<div class="empty-state">Alles erledigt – aktuell ist keine Aktion nötig.</div>'; return; }
  const grouped = new Map();
  for (const item of data.attention) {
    const current = grouped.get(item.type) || { ...item, count: 0 };
    current.count += 1;
    grouped.set(item.type, current);
  }
  list.innerHTML = [...grouped.values()].slice(0, 5).map(item => `<button class="attention-item" type="button" data-attention-id="${item.listingId}" data-attention-type="${escapeHtml(item.type)}"><span class="attention-symbol">${item.severity === 'danger' ? '!' : '○'}</span><span class="attention-copy"><strong>${escapeHtml(item.message)}</strong><small>${escapeHtml(item.action)}</small></span><b>${item.count}</b></button>`).join('');
  list.querySelectorAll('[data-attention-id]').forEach(button => button.addEventListener('click', () => {
    const id = Number(button.dataset.attentionId);
    if (button.dataset.attentionType === 'publish_failed') switchToTab('publish');
    else showDetail(id);
  }));
}

function renderStudioActivity(data) {
  const recent = document.getElementById('studio-activity');
  const completed = document.getElementById('studio-completed');
  const render = (container, items) => {
    if (!container) return;
    container.innerHTML = items.length ? items.map(item => `<button class="activity-item" type="button" data-activity-listing="${item.listingId || ''}"><span class="activity-icon">${activityGlyph(item.type)}</span><span class="activity-copy">${escapeHtml(item.text)}</span><span class="activity-time">${escapeHtml(relativeTime(item.at))}</span></button>`).join('') : '<div class="empty-state">Noch keine Aktivität</div>';
    container.querySelectorAll('[data-activity-listing]').forEach(button => button.addEventListener('click', () => { const id = Number(button.dataset.activityListing); if (id) selectStudioListing(id); }));
  };
  const completeTypes = new Set(['published', 'sold']);
  render(recent, data.activity.filter(item => !completeTypes.has(item.type)).slice(0, 4));
  render(completed, data.activity.filter(item => completeTypes.has(item.type)).slice(0, 4));
}

function selectStudioListing(id) {
  const item = getStudioItem(id);
  if (!item) return;
  selectedStudioListingId = id;
  document.querySelectorAll('[data-studio-listing]').forEach(card => card.classList.toggle('selected', Number(card.dataset.studioListing) === id));
  renderStudioInspector(item);
}

function renderStudioInspector(item) {
  const empty = document.getElementById('studio-inspector-empty');
  const content = document.getElementById('studio-inspector-content');
  if (!content || !empty) return;
  empty.classList.add('hidden');
  content.classList.remove('hidden');
  const photos = item.photos || [];
  const hero = photos[0] ? `<img src="${escapeHtml(photoUrl(photos[0]))}" alt="${escapeHtml(item.title)}">` : '<div class="flow-card-placeholder">Kein Bild</div>';
  const confidence = Math.max(0, Math.min(100, item.completeness || 0));
  const low = Math.max(0, (Number(item.price) || 0) * .92);
  const high = Math.max(low, (Number(item.price) || 0) * 1.08);
  content.innerHTML = `<div class="inspector-title-row"><div><h3>${escapeHtml(item.title)}</h3><div class="inspector-sku">SKU&nbsp;&nbsp;${escapeHtml(item.sku || '')}</div></div><span class="flow-state success">${item.status === 'sold' ? 'Verkauft' : 'Bereit'}</span></div><div class="inspector-hero">${hero}<span class="inspector-photo-count">▧ ${photos.length}</span></div>${photos.length > 1 ? `<div class="inspector-thumbs">${photos.slice(0, 5).map(photo => `<img src="${escapeHtml(photoUrl(photo))}" alt="">`).join('')}${photos.length > 5 ? `<span>+${photos.length - 5}</span>` : ''}</div>` : ''}<div class="inspector-price-row"><span>Preis</span><strong>${escapeHtml(formatCurrency(item.price) || 'Preis fehlt')}</strong></div><section class="inspector-card"><h4>AI-Konfidenz</h4><div class="confidence-row"><div class="confidence-ring" style="--confidence:${confidence}%"><span>${confidence}%</span></div><div class="confidence-copy"><strong>${confidence >= 85 ? 'Sehr hoch' : confidence >= 60 ? 'Prüfung empfohlen' : 'Unvollständig'}</strong><span>${confidence >= 85 ? 'Alle wichtigen Attribute wurden erkannt.' : 'Einige Angaben sollten ergänzt werden.'}</span></div></div></section><section class="inspector-card"><h4>Wichtige Attribute</h4><dl class="attribute-list"><dt>Marke</dt><dd>${escapeHtml(item.brand || '—')}</dd><dt>Kategorie</dt><dd>${escapeHtml(item.category || '—')}</dd><dt>Farbe</dt><dd>${escapeHtml(item.color || '—')}</dd><dt>Größe</dt><dd>${escapeHtml(item.size || '—')}</dd><dt>Zustand</dt><dd>${escapeHtml(item.condition || '—')}</dd><dt>Lagerort</dt><dd>${escapeHtml(item.storageLocation || 'Nicht zugewiesen')}</dd></dl></section>${item.price ? `<section class="inspector-card"><h4>Preisempfehlung</h4><div class="price-range">${escapeHtml(formatCurrency(low))} – ${escapeHtml(formatCurrency(high))}</div><div class="recommended-price"><span>Empfohlener Preis</span><strong>${escapeHtml(formatCurrency(item.price))}</strong></div></section>` : ''}<div class="inspector-actions"><button class="inspector-action primary" type="button" data-inspector-action="review">Prüfen</button><button class="inspector-action" type="button" data-inspector-action="publish">Veröffentlichen</button><button class="inspector-action" type="button" data-inspector-action="inventory">Lagerort setzen</button></div>`;
  content.querySelector('[data-inspector-action="review"]')?.addEventListener('click', () => showDetail(item.id));
  content.querySelector('[data-inspector-action="publish"]')?.addEventListener('click', () => { publishState.selectedId = item.id; switchToTab('publish'); });
  content.querySelector('[data-inspector-action="inventory"]')?.addEventListener('click', () => switchToTab('inventory'));
}

async function loadToday() {
  const ready = document.getElementById('today-ready-grid');
  if (ready) ready.innerHTML = '<div class="empty-state">Studio Flow wird geladen…</div>';
  try {
    const data = await api('GET', '/api/dashboard/workflow?limit=12');
    studioWorkflowData = data;
    renderTodaySummary(data);
    renderTodayReady(data);
    renderTodayScheduled(data);
    renderStudioAttention(data);
    renderStudioActivity(data);
    const first = data.stages?.ready?.items?.[0] || data.stages?.scheduled?.items?.[0] || data.stages?.published?.items?.[0] || data.stages?.sold?.items?.[0];
    if (first && !selectedStudioListingId) selectStudioListing(first.id);
    else if (selectedStudioListingId) { const selected = getStudioItem(selectedStudioListingId); if (selected) renderStudioInspector(selected); }
  } catch (error) {
    if (ready) ready.innerHTML = `<div class="empty-state">Studio Flow konnte nicht geladen werden: ${escapeHtml(error.message)}</div>`;
  }
}


// --- Advanced Image Editor ---
function createPhotoEditorOperations() {
  return {
    rotation: 0,
    straighten: 0,
    flip_h: false,
    flip_v: false,
    crop: null,
    exposure: 0,
    brightness: 0,
    contrast: 0,
    highlights: 0,
    shadows: 0,
    temperature: 0,
    saturation: 0,
    sharpness: 0,
    remove_background: false,
    background_sensitivity: 45,
    watermark: { enabled: false, text: 'Vendoo', position: 'bottom-right', opacity: 40 },
    resize: { width: 0, height: 0 },
    format: 'jpeg',
    quality: 90,
  };
}

let photoEditorState = {
  context: 'generator', platform: '', index: -1,
  currentPath: '', rootPath: '', sourceImage: null, rootImage: null,
  operations: createPhotoEditorOperations(), history: [], historyIndex: -1,
  versions: [], compare: false, cropping: false, cropRect: null,
  cropStart: null, cropEnd: null, _dragOrigin: null, _dragging: false,
};

function cloneEditorOperations(value = photoEditorState.operations) {
  return JSON.parse(JSON.stringify(value));
}

function getPhotoEditorPhotos() {
  if (photoEditorState.context === 'flux') return state.fluxStudioCurrent?.image_path ? [state.fluxStudioCurrent.image_path] : [];
  if (photoEditorState.context === 'media') return state.mediaCurrent?.image_path ? [state.mediaCurrent.image_path] : [];
  return photoEditorState.context === 'detail' ? (detailEditorState?.photos || []) : state.photos;
}

async function commitPhotoEditorResult(filename) {
  if (!filename) return;
  if (photoEditorState.context === 'flux') {
    const current = state.fluxStudioCurrent || {};
    const item = await api('POST', '/api/flux-studio/images', {
      image_path: filename,
      prompt: current.prompt || 'Nachbearbeitete FLUX-Ausgabe',
      width: current.width,
      height: current.height,
      seed: current.seed,
      steps: current.steps,
      style: current.style,
      final_prompt: current.final_prompt,
      profile: current.profile,
      duration_ms: current.duration_ms,
      edited_from: current.id || null,
      favorite: current.favorite,
      job_id: current.job_id,
      variant_index: current.variant_index,
      prompt_mode: current.prompt_mode,
      parameters: current.parameters,
    });
    renderFluxStudioPreview(item);
    await loadFluxStudioHistory();
    return;
  }
  if (photoEditorState.context === 'media') {
    state.mediaCurrent = { ...(state.mediaCurrent || {}), image_path: filename, public_url: photoUrl(filename), source: 'edited', sources: ['edited'] };
    await loadMediaLibrary();
    return;
  }
  const photos = getPhotoEditorPhotos();
  if (!photos[photoEditorState.index]) return;
  photos[photoEditorState.index] = filename;
  if (photoEditorState.context === 'detail') renderDetailPhotos();
  else renderPhotoPreview();
}

function getPhotoEditorPlatform() {
  if (photoEditorState.context === 'flux') return 'ai-generated';
  if (photoEditorState.context === 'media') return state.mediaCurrent?.source || 'media-library';
  if (photoEditorState.context === 'detail') {
    return document.getElementById('detail-platform')?.value || detailEditorState?.listing?.platform || 'allgemein';
  }
  return state.platform || 'allgemein';
}

function setupPhotoEditor() {
  document.getElementById('photo-editor-close').addEventListener('click', closePhotoEditor);
  document.getElementById('pe-cancel').addEventListener('click', closePhotoEditor);
  document.getElementById('photo-editor-overlay').addEventListener('click', event => {
    if (event.target === event.currentTarget) closePhotoEditor();
  });

  document.getElementById('pe-undo').addEventListener('click', () => stepPhotoEditorHistory(-1));
  document.getElementById('pe-redo').addEventListener('click', () => stepPhotoEditorHistory(1));
  document.getElementById('pe-compare').addEventListener('click', togglePhotoEditorCompare);
  document.getElementById('pe-restore-original').addEventListener('click', restorePhotoEditorOriginal);

  const sliders = {
    straighten: { digits: 1, suffix: '°' },
    exposure: { digits: 1, suffix: '' },
    brightness: { digits: 0, suffix: '' },
    contrast: { digits: 0, suffix: '' },
    highlights: { digits: 0, suffix: '' },
    shadows: { digits: 0, suffix: '' },
    temperature: { digits: 0, suffix: '' },
    saturation: { digits: 0, suffix: '' },
    sharpness: { digits: 0, suffix: '' },
  };
  for (const [property, format] of Object.entries(sliders)) {
    const slider = document.getElementById(`pe-${property}`);
    slider.addEventListener('input', () => {
      photoEditorState.operations[property] = Number(slider.value);
      updatePhotoEditorRangeValue(property, format.digits, format.suffix);
      renderPhotoEditorCanvas();
    });
    slider.addEventListener('change', pushPhotoEditorHistory);
  }

  document.getElementById('pe-rotate-left').addEventListener('click', () => mutatePhotoEditor(() => {
    photoEditorState.operations.rotation = (photoEditorState.operations.rotation + 270) % 360;
  }));
  document.getElementById('pe-rotate-right').addEventListener('click', () => mutatePhotoEditor(() => {
    photoEditorState.operations.rotation = (photoEditorState.operations.rotation + 90) % 360;
  }));
  document.getElementById('pe-flip-h').addEventListener('click', () => mutatePhotoEditor(() => {
    photoEditorState.operations.flip_h = !photoEditorState.operations.flip_h;
  }));
  document.getElementById('pe-flip-v').addEventListener('click', () => mutatePhotoEditor(() => {
    photoEditorState.operations.flip_v = !photoEditorState.operations.flip_v;
  }));

  document.getElementById('pe-reset-adjustments').addEventListener('click', () => mutatePhotoEditor(() => {
    for (const property of ['exposure', 'brightness', 'contrast', 'highlights', 'shadows', 'temperature', 'saturation', 'sharpness']) {
      photoEditorState.operations[property] = 0;
    }
  }));

  document.getElementById('pe-crop-toggle').addEventListener('click', toggleCropMode);
  document.getElementById('pe-crop-apply').addEventListener('click', applyCropSelection);
  document.getElementById('pe-crop-cancel').addEventListener('click', cancelCropMode);
  document.getElementById('pe-crop-ratio').addEventListener('change', setDefaultCropSelection);

  document.getElementById('pe-remove-background').addEventListener('change', event => mutatePhotoEditor(() => {
    photoEditorState.operations.remove_background = event.target.checked;
    if (event.target.checked && photoEditorState.operations.format === 'jpeg') photoEditorState.operations.format = 'png';
  }));
  document.getElementById('pe-background-sensitivity').addEventListener('input', event => {
    photoEditorState.operations.background_sensitivity = Number(event.target.value);
    document.getElementById('pe-background-sensitivity-val').textContent = `${event.target.value}%`;
    renderPhotoEditorCanvas();
  });
  document.getElementById('pe-background-sensitivity').addEventListener('change', pushPhotoEditorHistory);
  document.getElementById('pe-watermark-enabled').addEventListener('change', event => mutatePhotoEditor(() => {
    photoEditorState.operations.watermark.enabled = event.target.checked;
  }));
  document.getElementById('pe-watermark-text').addEventListener('input', event => {
    photoEditorState.operations.watermark.text = event.target.value.slice(0, 80);
    renderPhotoEditorCanvas();
  });
  document.getElementById('pe-watermark-text').addEventListener('change', pushPhotoEditorHistory);
  document.getElementById('pe-watermark-position').addEventListener('change', event => mutatePhotoEditor(() => {
    photoEditorState.operations.watermark.position = event.target.value;
  }));
  document.getElementById('pe-watermark-opacity').addEventListener('input', event => {
    photoEditorState.operations.watermark.opacity = Number(event.target.value);
    document.getElementById('pe-watermark-opacity-val').textContent = `${event.target.value}%`;
    renderPhotoEditorCanvas();
  });
  document.getElementById('pe-watermark-opacity').addEventListener('change', pushPhotoEditorHistory);

  const cropOverlay = document.getElementById('photo-editor-crop-overlay');
  cropOverlay.addEventListener('mousedown', cropMouseDown);
  cropOverlay.addEventListener('mousemove', cropMouseMove);
  cropOverlay.addEventListener('mouseup', cropMouseUp);
  cropOverlay.addEventListener('mouseleave', cropMouseUp);
  cropOverlay.addEventListener('touchstart', event => { event.preventDefault(); cropMouseDown(touchToMouse(event)); }, { passive: false });
  cropOverlay.addEventListener('touchmove', event => { event.preventDefault(); cropMouseMove(touchToMouse(event)); }, { passive: false });
  cropOverlay.addEventListener('touchend', cropMouseUp);

  document.getElementById('pe-format').addEventListener('change', event => mutatePhotoEditor(() => {
    photoEditorState.operations.format = event.target.value;
  }, false));
  document.getElementById('pe-quality').addEventListener('input', event => {
    photoEditorState.operations.quality = Number(event.target.value);
    document.getElementById('pe-quality-val').textContent = `${event.target.value}%`;
  });
  document.getElementById('pe-quality').addEventListener('change', pushPhotoEditorHistory);
  document.getElementById('pe-target-size').addEventListener('change', updatePhotoEditorResize);
  document.getElementById('pe-target-width').addEventListener('change', updatePhotoEditorResize);
  document.getElementById('pe-target-height').addEventListener('change', updatePhotoEditorResize);
  document.getElementById('pe-apply').addEventListener('click', applyPhotoEdit);

  window.addEventListener('resize', () => {
    if (!document.getElementById('photo-editor-overlay').classList.contains('hidden')) syncCropOverlay();
  });
  document.addEventListener('keydown', event => {
    const overlay = document.getElementById('photo-editor-overlay');
    if (!overlay || overlay.classList.contains('hidden')) return;
    if (event.key === 'Escape') {
      event.preventDefault();
      closePhotoEditor();
      return;
    }
    if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== 'z') return;
    event.preventDefault();
    stepPhotoEditorHistory(event.shiftKey ? 1 : -1);
  });
}

function updatePhotoEditorRangeValue(property, digits = 0, suffix = '') {
  const value = Number(photoEditorState.operations[property] || 0);
  const text = digits ? value.toFixed(digits).replace('.', ',') : String(Math.round(value));
  document.getElementById(`pe-${property}-val`).textContent = `${text}${suffix}`;
}

function updatePhotoEditorControls() {
  for (const property of ['straighten', 'exposure', 'brightness', 'contrast', 'highlights', 'shadows', 'temperature', 'saturation', 'sharpness']) {
    const input = document.getElementById(`pe-${property}`);
    input.value = photoEditorState.operations[property];
  }
  updatePhotoEditorRangeValue('straighten', 1, '°');
  updatePhotoEditorRangeValue('exposure', 1);
  for (const property of ['brightness', 'contrast', 'highlights', 'shadows', 'temperature', 'saturation', 'sharpness']) updatePhotoEditorRangeValue(property);
  document.getElementById('pe-format').value = photoEditorState.operations.format;
  document.getElementById('pe-quality').value = photoEditorState.operations.quality;
  document.getElementById('pe-quality-val').textContent = `${photoEditorState.operations.quality}%`;
  document.getElementById('pe-remove-background').checked = Boolean(photoEditorState.operations.remove_background);
  document.getElementById('pe-background-sensitivity').value = Number(photoEditorState.operations.background_sensitivity ?? 45);
  document.getElementById('pe-background-sensitivity-val').textContent = `${Number(photoEditorState.operations.background_sensitivity ?? 45)}%`;
  const watermark = photoEditorState.operations.watermark || { enabled: false, text: 'Vendoo', position: 'bottom-right', opacity: 40 };
  document.getElementById('pe-watermark-enabled').checked = Boolean(watermark.enabled);
  document.getElementById('pe-watermark-text').value = watermark.text || 'Vendoo';
  document.getElementById('pe-watermark-position').value = watermark.position || 'bottom-right';
  document.getElementById('pe-watermark-opacity').value = Number(watermark.opacity || 40);
  document.getElementById('pe-watermark-opacity-val').textContent = `${Number(watermark.opacity || 40)}%`;

  const { width, height } = photoEditorState.operations.resize || {};
  const target = width && height && width === height && [1200, 1600, 2048].includes(width) ? String(width) : width || height ? 'custom' : 'original';
  document.getElementById('pe-target-size').value = target;
  document.getElementById('pe-custom-size').classList.toggle('hidden', target !== 'custom');
  document.getElementById('pe-target-width').value = target === 'custom' && width ? width : '';
  document.getElementById('pe-target-height').value = target === 'custom' && height ? height : '';
}

function mutatePhotoEditor(mutator, render = true) {
  mutator();
  updatePhotoEditorControls();
  pushPhotoEditorHistory();
  if (render) renderPhotoEditorCanvas();
}

function pushPhotoEditorHistory() {
  const snapshot = cloneEditorOperations();
  const current = photoEditorState.history[photoEditorState.historyIndex];
  if (current && JSON.stringify(current) === JSON.stringify(snapshot)) return;
  photoEditorState.history = photoEditorState.history.slice(0, photoEditorState.historyIndex + 1);
  photoEditorState.history.push(snapshot);
  photoEditorState.historyIndex = photoEditorState.history.length - 1;
  updatePhotoEditorHistoryControls();
}

function stepPhotoEditorHistory(direction) {
  const next = photoEditorState.historyIndex + direction;
  if (next < 0 || next >= photoEditorState.history.length) return;
  photoEditorState.historyIndex = next;
  photoEditorState.operations = cloneEditorOperations(photoEditorState.history[next]);
  cancelCropMode(false);
  updatePhotoEditorControls();
  updatePhotoEditorHistoryControls();
  renderPhotoEditorCanvas();
}

function updatePhotoEditorHistoryControls() {
  document.getElementById('pe-undo').disabled = photoEditorState.historyIndex <= 0;
  document.getElementById('pe-redo').disabled = photoEditorState.historyIndex >= photoEditorState.history.length - 1;
  document.getElementById('pe-history-status').textContent = `Schritt ${Math.max(1, photoEditorState.historyIndex + 1)} von ${Math.max(1, photoEditorState.history.length)}`;
}

function updatePhotoEditorResize() {
  const target = document.getElementById('pe-target-size').value;
  const custom = document.getElementById('pe-custom-size');
  custom.classList.toggle('hidden', target !== 'custom');
  if (target === 'original') photoEditorState.operations.resize = { width: 0, height: 0 };
  else if (target === 'custom') {
    photoEditorState.operations.resize = {
      width: Math.max(0, Number(document.getElementById('pe-target-width').value) || 0),
      height: Math.max(0, Number(document.getElementById('pe-target-height').value) || 0),
    };
  } else {
    const size = Number(target);
    photoEditorState.operations.resize = { width: size, height: size };
  }
  pushPhotoEditorHistory();
  renderPhotoEditorCanvas();
}

function loadEditorImage(path) {
  return new Promise((resolve, reject) => {
    const image = new Image();
    image.crossOrigin = 'anonymous';
    image.onload = () => resolve(image);
    image.onerror = () => reject(new Error('Bildvorschau konnte nicht geladen werden.'));
    image.src = `${photoUrl(path)}${String(photoUrl(path)).includes('?') ? '&' : '?'}editor=${Date.now()}`;
  });
}

async function openPhotoEditor(index, context = 'generator') {
  const photos = context === 'flux' ? (state.fluxStudioCurrent?.image_path ? [state.fluxStudioCurrent.image_path] : []) : context === 'media' ? (state.mediaCurrent?.image_path ? [state.mediaCurrent.image_path] : []) : context === 'detail' ? (detailEditorState?.photos || []) : state.photos;
  const photo = photos[index];
  if (!photo) return;

  photoEditorState = {
    context,
    platform: context === 'flux' ? 'ai-generated' : context === 'media' ? (state.mediaCurrent?.source || 'media-library') : context === 'detail' ? (detailEditorState?.listing?.platform || '') : state.platform,
    index,
    currentPath: photo,
    rootPath: photo,
    sourceImage: null,
    rootImage: null,
    operations: createPhotoEditorOperations(),
    history: [], historyIndex: -1, versions: [], compare: false,
    cropping: false, cropRect: null, cropStart: null, cropEnd: null,
    _dragOrigin: null, _dragging: false,
  };

  const overlay = document.getElementById('photo-editor-overlay');
  overlay.classList.remove('hidden');
  overlay.setAttribute('aria-hidden', 'false');
  document.getElementById('pe-image-meta').textContent = 'Versionen werden geladen …';
  document.getElementById('pe-version-list').innerHTML = '<div class="empty-state">Versionen werden geladen …</div>';
  updatePhotoEditorControls();
  updatePhotoEditorHistoryControls();
  setPhotoEditorCompare(false);

  try {
    const history = await api('GET', `/api/image-editor/versions?path=${encodeURIComponent(photo)}`);
    photoEditorState.rootPath = history.root_path || photo;
    photoEditorState.versions = [history.original, ...(history.versions || [])].filter(Boolean);
    const [sourceImage, rootImage] = await Promise.all([
      loadEditorImage(photo),
      history.root_path && history.root_path !== photo ? loadEditorImage(history.root_path) : loadEditorImage(photo),
    ]);
    photoEditorState.sourceImage = sourceImage;
    photoEditorState.rootImage = rootImage;
    resetPhotoEditorSession();
    renderPhotoEditorVersions();
    renderPhotoEditorCanvas();
  } catch (error) {
    toast(error.message || 'Bildeditor konnte nicht geöffnet werden.');
    closePhotoEditor();
  }
}

function resetPhotoEditorSession() {
  const extension = String(photoEditorState.currentPath || '').split('.').pop()?.toLowerCase();
  photoEditorState.operations = createPhotoEditorOperations();
  if (extension === 'png') photoEditorState.operations.format = 'png';
  else if (extension === 'webp') photoEditorState.operations.format = 'webp';
  photoEditorState.history = [cloneEditorOperations()];
  photoEditorState.historyIndex = 0;
  cancelCropMode(false);
  updatePhotoEditorControls();
  updatePhotoEditorHistoryControls();
}

async function loadPhotoEditorVersion(path) {
  if (!path || path === photoEditorState.currentPath) return;
  try {
    document.getElementById('pe-image-meta').textContent = 'Version wird geladen …';
    photoEditorState.sourceImage = await loadEditorImage(path);
    photoEditorState.currentPath = path;
    resetPhotoEditorSession();
    renderPhotoEditorVersions();
    renderPhotoEditorCanvas();
  } catch (error) { toast(error.message); }
}

function formatEditorFileSize(bytes) {
  const size = Number(bytes) || 0;
  if (size < 1024) return `${size} B`;
  if (size < 1024 * 1024) return `${(size / 1024).toFixed(0)} KB`;
  return `${(size / 1024 / 1024).toFixed(1).replace('.', ',')} MB`;
}

function renderPhotoEditorVersions() {
  const list = document.getElementById('pe-version-list');
  const versions = photoEditorState.versions || [];
  document.getElementById('pe-version-count').textContent = String(Math.max(0, versions.length - 1));
  if (!versions.length) {
    list.innerHTML = '<div class="empty-state">Noch keine Bearbeitungsversion.</div>';
    return;
  }
  list.innerHTML = versions.map((version, index) => {
    const path = version.output_path || version.root_path;
    const isOriginal = version.id === 'original';
    const date = version.created_at ? new Date(version.created_at).toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' }) : '';
    const dimensions = version.width && version.height ? `${version.width} × ${version.height}` : '';
    return `<button class="pe-version-item${path === photoEditorState.currentPath ? ' active' : ''}" type="button" data-version-path="${escapeHtml(path)}"><span>${isOriginal ? 'ORIG' : `V${versions.length - index}`}</span><div><strong>${isOriginal ? 'Unverändertes Original' : 'Bearbeitungsversion'}</strong><small>${escapeHtml([dimensions, formatEditorFileSize(version.file_size), date].filter(Boolean).join(' · '))}</small></div></button>`;
  }).join('');
  list.querySelectorAll('[data-version-path]').forEach(button => button.addEventListener('click', () => loadPhotoEditorVersion(button.dataset.versionPath)));
}

function createPreviewCanvas(image, operations, { applyCrop = true, maxDimension = 1100 } = {}) {
  const scale = Math.min(1, maxDimension / Math.max(image.naturalWidth || image.width, image.naturalHeight || image.height));
  const sourceWidth = Math.max(1, Math.round((image.naturalWidth || image.width) * scale));
  const sourceHeight = Math.max(1, Math.round((image.naturalHeight || image.height) * scale));
  const angle = (operations.rotation + operations.straighten) * Math.PI / 180;
  const cos = Math.abs(Math.cos(angle));
  const sin = Math.abs(Math.sin(angle));
  const outputWidth = Math.max(1, Math.ceil(sourceWidth * cos + sourceHeight * sin));
  const outputHeight = Math.max(1, Math.ceil(sourceWidth * sin + sourceHeight * cos));
  const geometry = document.createElement('canvas');
  geometry.width = outputWidth;
  geometry.height = outputHeight;
  const geometryContext = geometry.getContext('2d', { willReadFrequently: true });
  geometryContext.fillStyle = '#ffffff';
  geometryContext.fillRect(0, 0, outputWidth, outputHeight);
  geometryContext.save();
  geometryContext.translate(outputWidth / 2, outputHeight / 2);
  geometryContext.rotate(angle);
  geometryContext.scale(operations.flip_h ? -1 : 1, operations.flip_v ? -1 : 1);
  geometryContext.drawImage(image, -sourceWidth / 2, -sourceHeight / 2, sourceWidth, sourceHeight);
  geometryContext.restore();

  let result = geometry;
  if (applyCrop && operations.crop) {
    const left = Math.max(0, Math.min(geometry.width - 1, Math.round(operations.crop.x * geometry.width)));
    const top = Math.max(0, Math.min(geometry.height - 1, Math.round(operations.crop.y * geometry.height)));
    const width = Math.max(1, Math.min(geometry.width - left, Math.round(operations.crop.width * geometry.width)));
    const height = Math.max(1, Math.min(geometry.height - top, Math.round(operations.crop.height * geometry.height)));
    result = document.createElement('canvas');
    result.width = width;
    result.height = height;
    result.getContext('2d', { willReadFrequently: true }).drawImage(geometry, left, top, width, height, 0, 0, width, height);
  }

  applyPreviewToneAdjustments(result, operations);
  applyPreviewBackgroundRemoval(result, operations);
  applyPreviewWatermark(result, operations);
  return result;
}

function applyPreviewBackgroundRemoval(canvas, operations) {
  if (!operations.remove_background) return;
  const context = canvas.getContext('2d', { willReadFrequently: true });
  const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
  const data = imageData.data;
  const cornerIndexes = [0, (canvas.width - 1) * 4, ((canvas.height - 1) * canvas.width) * 4, (canvas.width * canvas.height - 1) * 4];
  const background = cornerIndexes.reduce((sum, index) => ({ r: sum.r + data[index], g: sum.g + data[index + 1], b: sum.b + data[index + 2] }), { r: 0, g: 0, b: 0 });
  background.r /= 4; background.g /= 4; background.b /= 4;
  const level = Math.max(0, Math.min(100, Number(operations.background_sensitivity ?? 45)));
  const solidThreshold = 24 + level * 2.1;
  const featherWidth = 45 + level * 1.2;
  const featherEnd = solidThreshold + featherWidth;
  for (let index = 0; index < data.length; index += 4) {
    const distance = Math.abs(data[index] - background.r) + Math.abs(data[index + 1] - background.g) + Math.abs(data[index + 2] - background.b);
    if (distance <= solidThreshold) data[index + 3] = 0;
    else if (distance < featherEnd) data[index + 3] = Math.round(data[index + 3] * (distance - solidThreshold) / featherWidth);
  }
  context.putImageData(imageData, 0, 0);
}

function applyPreviewWatermark(canvas, operations) {
  const watermark = operations.watermark || {};
  const text = String(watermark.text || '').trim();
  if (!watermark.enabled || !text) return;
  const context = canvas.getContext('2d');
  const margin = Math.max(10, Math.round(Math.min(canvas.width, canvas.height) * 0.025));
  const fontSize = Math.max(16, Math.round(canvas.width * 0.04));
  const position = watermark.position || 'bottom-right';
  let x = canvas.width - margin; let y = canvas.height - margin; let align = 'right'; let baseline = 'bottom';
  if (position.includes('left')) { x = margin; align = 'left'; }
  if (position.includes('top')) { y = margin; baseline = 'top'; }
  if (position === 'center') { x = canvas.width / 2; y = canvas.height / 2; align = 'center'; baseline = 'middle'; }
  context.save();
  context.globalAlpha = Math.max(0.1, Math.min(1, Number(watermark.opacity || 40) / 100));
  context.font = `700 ${fontSize}px system-ui, sans-serif`;
  context.textAlign = align;
  context.textBaseline = baseline;
  context.lineWidth = Math.max(2, fontSize * 0.08);
  context.strokeStyle = 'rgba(0,0,0,.42)';
  context.fillStyle = '#ffffff';
  context.strokeText(text, x, y);
  context.fillText(text, x, y);
  context.restore();
}

function applyPreviewToneAdjustments(canvas, operations) {
  const context = canvas.getContext('2d', { willReadFrequently: true });
  const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
  const data = imageData.data;
  const exposure = 2 ** operations.exposure;
  const brightness = operations.brightness / 100 * 0.22;
  const contrastValue = Math.max(-0.95, Math.min(0.95, operations.contrast / 100));
  const contrast = (1 + contrastValue) / (1 - contrastValue);
  const highlights = operations.highlights / 100;
  const shadows = operations.shadows / 100;
  const temperature = operations.temperature / 100;
  const saturation = 1 + operations.saturation / 100;

  for (let index = 0; index < data.length; index += 4) {
    let red = data[index] / 255;
    let green = data[index + 1] / 255;
    let blue = data[index + 2] / 255;
    red = (red * exposure + brightness - 0.5) * contrast + 0.5;
    green = (green * exposure + brightness - 0.5) * contrast + 0.5;
    blue = (blue * exposure + brightness - 0.5) * contrast + 0.5;
    const lumBefore = Math.max(0, Math.min(1, red * 0.2126 + green * 0.7152 + blue * 0.0722));
    const shadowWeight = (1 - lumBefore) ** 2;
    const highlightWeight = lumBefore ** 2;
    const shadowDelta = shadows >= 0 ? shadows * shadowWeight * 0.36 : shadows * shadowWeight * 0.28;
    const highlightDelta = highlights >= 0 ? highlights * highlightWeight * 0.28 : highlights * highlightWeight * 0.38;
    red += shadowDelta + highlightDelta + temperature * 0.12;
    green += shadowDelta + highlightDelta + temperature * 0.015;
    blue += shadowDelta + highlightDelta - temperature * 0.12;
    const luminance = red * 0.2126 + green * 0.7152 + blue * 0.0722;
    red = luminance + (red - luminance) * saturation;
    green = luminance + (green - luminance) * saturation;
    blue = luminance + (blue - luminance) * saturation;
    data[index] = Math.round(Math.max(0, Math.min(1, red)) * 255);
    data[index + 1] = Math.round(Math.max(0, Math.min(1, green)) * 255);
    data[index + 2] = Math.round(Math.max(0, Math.min(1, blue)) * 255);
  }
  context.putImageData(imageData, 0, 0);
  if (operations.sharpness > 0) applyPreviewSharpen(canvas, operations.sharpness / 100);
}

function applyPreviewSharpen(canvas, strength) {
  if (canvas.width * canvas.height > 1_500_000) return;
  const context = canvas.getContext('2d', { willReadFrequently: true });
  const source = context.getImageData(0, 0, canvas.width, canvas.height);
  const output = context.createImageData(canvas.width, canvas.height);
  output.data.set(source.data);
  const amount = Math.min(1, Math.max(0, strength)) * 0.75;
  const width = canvas.width;
  for (let y = 1; y < canvas.height - 1; y += 1) {
    for (let x = 1; x < width - 1; x += 1) {
      const index = (y * width + x) * 4;
      for (let channel = 0; channel < 3; channel += 1) {
        const center = source.data[index + channel];
        const average = (source.data[index - 4 + channel] + source.data[index + 4 + channel] + source.data[index - width * 4 + channel] + source.data[index + width * 4 + channel]) / 4;
        output.data[index + channel] = Math.max(0, Math.min(255, center + (center - average) * amount));
      }
    }
  }
  context.putImageData(output, 0, 0);
}

function calculatePhotoEditorOutputDimensions() {
  const image = photoEditorState.sourceImage;
  if (!image) return { width: 0, height: 0 };
  const angle = (photoEditorState.operations.rotation + photoEditorState.operations.straighten) * Math.PI / 180;
  const cos = Math.abs(Math.cos(angle));
  const sin = Math.abs(Math.sin(angle));
  let width = Math.ceil(image.naturalWidth * cos + image.naturalHeight * sin);
  let height = Math.ceil(image.naturalWidth * sin + image.naturalHeight * cos);
  if (photoEditorState.operations.crop) {
    width = Math.max(1, Math.round(width * photoEditorState.operations.crop.width));
    height = Math.max(1, Math.round(height * photoEditorState.operations.crop.height));
  }
  const target = photoEditorState.operations.resize || {};
  if (target.width || target.height) {
    const scale = Math.min(target.width ? target.width / width : Infinity, target.height ? target.height / height : Infinity, 1);
    width = Math.max(1, Math.round(width * scale));
    height = Math.max(1, Math.round(height * scale));
  }
  return { width, height };
}

function renderPhotoEditorCanvas() {
  const image = photoEditorState.sourceImage;
  if (!image) return;
  const preview = createPreviewCanvas(image, photoEditorState.operations, { applyCrop: !photoEditorState.cropping });
  const canvas = document.getElementById('photo-editor-canvas');
  canvas.width = preview.width;
  canvas.height = preview.height;
  canvas.getContext('2d').drawImage(preview, 0, 0);

  if (photoEditorState.rootImage) {
    const before = createPreviewCanvas(photoEditorState.rootImage, createPhotoEditorOperations(), { maxDimension: 850 });
    const beforeCanvas = document.getElementById('photo-editor-before-canvas');
    beforeCanvas.width = before.width;
    beforeCanvas.height = before.height;
    beforeCanvas.getContext('2d').drawImage(before, 0, 0);
  }

  const dimensions = calculatePhotoEditorOutputDimensions();
  document.getElementById('pe-output-dimensions').textContent = dimensions.width && dimensions.height ? `Ausgabe: ${dimensions.width} × ${dimensions.height} px` : 'Ausgabegröße wird ermittelt';
  document.getElementById('pe-image-meta').textContent = `${photoEditorState.currentPath === photoEditorState.rootPath ? 'Original' : 'Version'} · ${image.naturalWidth} × ${image.naturalHeight} px · Original bleibt erhalten`;
  requestAnimationFrame(syncCropOverlay);
}

function setPhotoEditorCompare(enabled) {
  photoEditorState.compare = Boolean(enabled);
  document.getElementById('pe-before-pane').classList.toggle('hidden', !photoEditorState.compare);
  const button = document.getElementById('pe-compare');
  button.setAttribute('aria-pressed', String(photoEditorState.compare));
  button.classList.toggle('active', photoEditorState.compare);
}

function togglePhotoEditorCompare() {
  setPhotoEditorCompare(!photoEditorState.compare);
  requestAnimationFrame(syncCropOverlay);
}

function touchToMouse(event) {
  const touch = event.touches?.[0] || event.changedTouches?.[0];
  return { clientX: touch?.clientX || 0, clientY: touch?.clientY || 0, target: event.target };
}

function toggleCropMode() {
  if (photoEditorState.cropping) return cancelCropMode();
  photoEditorState.cropping = true;
  document.getElementById('pe-crop-toggle').classList.add('active');
  document.getElementById('pe-crop-toggle').textContent = 'Auswahl aktiv';
  document.getElementById('pe-crop-apply').classList.remove('hidden');
  document.getElementById('pe-crop-cancel').classList.remove('hidden');
  document.getElementById('pe-crop-hint').textContent = 'Rahmen aufziehen oder verschieben; anschließend anwenden.';
  renderPhotoEditorCanvas();
  requestAnimationFrame(() => {
    syncCropOverlay();
    if (photoEditorState.operations.crop) {
      const overlay = document.getElementById('photo-editor-crop-overlay');
      photoEditorState.cropRect = {
        x: photoEditorState.operations.crop.x * overlay.width,
        y: photoEditorState.operations.crop.y * overlay.height,
        w: photoEditorState.operations.crop.width * overlay.width,
        h: photoEditorState.operations.crop.height * overlay.height,
      };
      drawCropOverlay();
    } else setDefaultCropSelection();
  });
}

function cancelCropMode(render = true) {
  photoEditorState.cropping = false;
  photoEditorState.cropRect = null;
  photoEditorState.cropStart = null;
  photoEditorState.cropEnd = null;
  photoEditorState._dragOrigin = null;
  photoEditorState._dragging = false;
  const overlay = document.getElementById('photo-editor-crop-overlay');
  overlay.classList.add('hidden');
  clearCropOverlay();
  const toggle = document.getElementById('pe-crop-toggle');
  toggle.classList.remove('active');
  toggle.textContent = '✂ Auswahl starten';
  document.getElementById('pe-crop-apply').classList.add('hidden');
  document.getElementById('pe-crop-cancel').classList.add('hidden');
  document.getElementById('pe-crop-hint').textContent = photoEditorState.operations.crop ? 'Ein nicht destruktiver Zuschnitt ist aktiv.' : 'Zuschnitt ist nicht aktiv.';
  if (render) renderPhotoEditorCanvas();
}

function syncCropOverlay() {
  const overlay = document.getElementById('photo-editor-crop-overlay');
  const canvas = document.getElementById('photo-editor-canvas');
  const wrap = document.getElementById('photo-editor-canvas-wrap');
  if (!photoEditorState.cropping || !canvas.offsetWidth || !canvas.offsetHeight) {
    overlay.classList.add('hidden');
    return;
  }
  const canvasRect = canvas.getBoundingClientRect();
  const wrapRect = wrap.getBoundingClientRect();
  overlay.width = Math.max(1, Math.round(canvasRect.width));
  overlay.height = Math.max(1, Math.round(canvasRect.height));
  overlay.style.width = `${canvasRect.width}px`;
  overlay.style.height = `${canvasRect.height}px`;
  overlay.style.left = `${canvasRect.left - wrapRect.left}px`;
  overlay.style.top = `${canvasRect.top - wrapRect.top}px`;
  overlay.classList.remove('hidden');
  drawCropOverlay();
}

function getCropRatio() {
  const value = document.getElementById('pe-crop-ratio').value;
  if (value === 'free') return null;
  if (value === 'original') {
    const canvas = document.getElementById('photo-editor-canvas');
    return canvas.width / Math.max(1, canvas.height);
  }
  const [width, height] = value.split(':').map(Number);
  return width && height ? width / height : null;
}

function setDefaultCropSelection() {
  if (!photoEditorState.cropping) return;
  const overlay = document.getElementById('photo-editor-crop-overlay');
  const ratio = getCropRatio();
  const margin = Math.min(overlay.width, overlay.height) * 0.08;
  let width = Math.max(20, overlay.width - margin * 2);
  let height = Math.max(20, overlay.height - margin * 2);
  if (ratio) {
    if (width / height > ratio) width = height * ratio;
    else height = width / ratio;
  }
  photoEditorState.cropRect = { x: (overlay.width - width) / 2, y: (overlay.height - height) / 2, w: width, h: height };
  drawCropOverlay();
}

function getCropCanvasCoords(event) {
  const overlay = document.getElementById('photo-editor-crop-overlay');
  const rect = overlay.getBoundingClientRect();
  return {
    x: Math.max(0, Math.min(overlay.width, event.clientX - rect.left)),
    y: Math.max(0, Math.min(overlay.height, event.clientY - rect.top)),
  };
}

function cropMouseDown(event) {
  if (!photoEditorState.cropping) return;
  photoEditorState._dragOrigin = getCropCanvasCoords(event);
  photoEditorState._dragging = true;
  photoEditorState.cropStart = photoEditorState._dragOrigin;
  photoEditorState.cropEnd = photoEditorState._dragOrigin;
}

function cropMouseMove(event) {
  if (!photoEditorState.cropping || !photoEditorState._dragging || !photoEditorState._dragOrigin) return;
  const overlay = document.getElementById('photo-editor-crop-overlay');
  const origin = photoEditorState._dragOrigin;
  let point = getCropCanvasCoords(event);
  const ratio = getCropRatio();
  if (ratio) {
    const directionX = point.x >= origin.x ? 1 : -1;
    const directionY = point.y >= origin.y ? 1 : -1;
    let width = Math.abs(point.x - origin.x);
    let height = Math.abs(point.y - origin.y);
    if (width / Math.max(1, height) > ratio) height = width / ratio;
    else width = height * ratio;
    width = Math.min(width, directionX > 0 ? overlay.width - origin.x : origin.x);
    height = Math.min(height, directionY > 0 ? overlay.height - origin.y : origin.y);
    if (width / Math.max(1, height) > ratio) width = height * ratio;
    else height = width / ratio;
    point = { x: origin.x + width * directionX, y: origin.y + height * directionY };
  }
  photoEditorState.cropEnd = point;
  const x = Math.min(origin.x, point.x);
  const y = Math.min(origin.y, point.y);
  photoEditorState.cropRect = { x, y, w: Math.abs(point.x - origin.x), h: Math.abs(point.y - origin.y) };
  drawCropOverlay();
}

function cropMouseUp() {
  if (!photoEditorState.cropping) return;
  photoEditorState._dragging = false;
  photoEditorState._dragOrigin = null;
}

function drawCropOverlay() {
  const overlay = document.getElementById('photo-editor-crop-overlay');
  const context = overlay.getContext('2d');
  context.clearRect(0, 0, overlay.width, overlay.height);
  if (!photoEditorState.cropping) return;
  context.fillStyle = 'rgba(7, 11, 17, 0.58)';
  context.fillRect(0, 0, overlay.width, overlay.height);
  const rect = photoEditorState.cropRect;
  if (!rect || rect.w < 2 || rect.h < 2) return;
  context.clearRect(rect.x, rect.y, rect.w, rect.h);
  context.strokeStyle = '#ffffff';
  context.lineWidth = 2;
  context.setLineDash([]);
  context.strokeRect(rect.x, rect.y, rect.w, rect.h);
  context.strokeStyle = 'rgba(255,255,255,.42)';
  context.lineWidth = 1;
  for (let index = 1; index < 3; index += 1) {
    context.beginPath(); context.moveTo(rect.x + rect.w * index / 3, rect.y); context.lineTo(rect.x + rect.w * index / 3, rect.y + rect.h); context.stroke();
    context.beginPath(); context.moveTo(rect.x, rect.y + rect.h * index / 3); context.lineTo(rect.x + rect.w, rect.y + rect.h * index / 3); context.stroke();
  }
  const canvas = document.getElementById('photo-editor-canvas');
  const realWidth = Math.round(rect.w * canvas.width / overlay.width);
  const realHeight = Math.round(rect.h * canvas.height / overlay.height);
  const label = `${realWidth} × ${realHeight}`;
  context.font = '12px ui-monospace, monospace';
  const textWidth = context.measureText(label).width;
  context.fillStyle = 'rgba(0,0,0,.74)';
  context.fillRect(rect.x + rect.w / 2 - textWidth / 2 - 7, Math.max(rect.y + 5, rect.y + rect.h - 25), textWidth + 14, 20);
  context.fillStyle = '#ffffff';
  context.textAlign = 'center';
  context.fillText(label, rect.x + rect.w / 2, Math.max(rect.y + 19, rect.y + rect.h - 11));
}

function clearCropOverlay() {
  const overlay = document.getElementById('photo-editor-crop-overlay');
  overlay.getContext('2d').clearRect(0, 0, overlay.width, overlay.height);
}

function applyCropSelection() {
  const rect = photoEditorState.cropRect;
  const overlay = document.getElementById('photo-editor-crop-overlay');
  if (!rect || rect.w < 10 || rect.h < 10) return toast('Bitte zuerst einen gültigen Zuschnitt wählen.');
  photoEditorState.operations.crop = {
    x: Math.max(0, Math.min(1, rect.x / overlay.width)),
    y: Math.max(0, Math.min(1, rect.y / overlay.height)),
    width: Math.max(0.01, Math.min(1, rect.w / overlay.width)),
    height: Math.max(0.01, Math.min(1, rect.h / overlay.height)),
  };
  cancelCropMode(false);
  pushPhotoEditorHistory();
  renderPhotoEditorCanvas();
}

async function restorePhotoEditorOriginal() {
  if (!photoEditorState.rootPath) return;
  try {
    await commitPhotoEditorResult(photoEditorState.rootPath);
    toast('Unverändertes Original wiederhergestellt.');
    closePhotoEditor();
  } catch (error) { toast(error.message || 'Original konnte nicht wiederhergestellt werden.'); }
}

async function applyPhotoEdit() {
  const button = document.getElementById('pe-apply');
  const originalLabel = button.textContent;
  button.disabled = true;
  button.textContent = 'Neue Version wird gerendert …';
  try {
    const result = await api('POST', '/api/image-editor/render', {
      source_path: photoEditorState.currentPath,
      context: photoEditorState.context,
      platform: getPhotoEditorPlatform(),
      operations: photoEditorState.operations,
    });
    await commitPhotoEditorResult(result.filename);
    toast(`Neue Bildversion gespeichert (${result.metadata?.width || '?'} × ${result.metadata?.height || '?'} px).`);
    closePhotoEditor();
  } catch (error) {
    toast(error.message || 'Bildversion konnte nicht gespeichert werden.');
  } finally {
    button.disabled = false;
    button.textContent = originalLabel;
  }
}

function closePhotoEditor() {
  const overlay = document.getElementById('photo-editor-overlay');
  overlay.classList.add('hidden');
  overlay.setAttribute('aria-hidden', 'true');
  cancelCropMode(false);
  photoEditorState.sourceImage = null;
  photoEditorState.rootImage = null;
  photoEditorState.versions = [];
}


// --- Photo Drag & Drop Sorting ---
function enablePhotoDragSort() {
  const container = document.getElementById('photo-preview');
  let dragIdx = -1;

  container.addEventListener('dragstart', e => {
    const thumb = e.target.closest('.thumb');
    if (!thumb) return;
    dragIdx = [...container.children].indexOf(thumb);
    thumb.classList.add('dragging');
    e.dataTransfer.effectAllowed = 'move';
  });

  container.addEventListener('dragover', e => {
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
    const thumb = e.target.closest('.thumb');
    if (thumb) {
      container.querySelectorAll('.thumb').forEach(t => t.classList.remove('drag-over'));
      thumb.classList.add('drag-over');
    }
  });

  container.addEventListener('drop', e => {
    e.preventDefault();
    container.querySelectorAll('.thumb').forEach(t => { t.classList.remove('dragging', 'drag-over'); });
    const thumb = e.target.closest('.thumb');
    if (!thumb) return;
    const dropIdx = [...container.children].indexOf(thumb);
    if (dragIdx >= 0 && dropIdx >= 0 && dragIdx !== dropIdx) {
      const moved = state.photos.splice(dragIdx, 1)[0];
      state.photos.splice(dropIdx, 0, moved);
      renderPhotoPreview();
    }
    dragIdx = -1;
  });

  container.addEventListener('dragend', () => {
    container.querySelectorAll('.thumb').forEach(t => { t.classList.remove('dragging', 'drag-over'); });
    dragIdx = -1;
  });
}

// --- Price Suggestion ---
async function fetchPriceSuggestion() {
  const title = document.getElementById('result-title')?.value;
  const category = document.getElementById('meta-category-picker')?.textContent;
  if (!title) return;
  try {
    const data = await api('GET', `/api/price-suggest?title=${encodeURIComponent(title)}&category=${encodeURIComponent(category || '')}`);
    if (data.suggestion) {
      const priceInput = document.getElementById('meta-price');
      const currentPrice = parseFloat(priceInput.value);
      const hint = document.getElementById('price-suggestion-hint');
      const html = `Preisvorschlag: <strong>${data.suggestion}€</strong> (${data.min}–${data.max}€, ${data.count} ähnliche)`;
      if (hint) {
        hint.innerHTML = html;
        hint.classList.remove('hidden');
      } else {
        const el = document.createElement('div');
        el.id = 'price-suggestion-hint';
        el.style.cssText = 'font-size:12px;color:var(--text-muted);margin-top:4px;cursor:pointer';
        el.innerHTML = html;
        el.addEventListener('click', () => { priceInput.value = data.suggestion; });
        priceInput.parentElement.appendChild(el);
      }
    }
  } catch {}
}

// --- Backup & Restore (now handled in setupAdmin for system tab) ---
function setupBackup() {
  // Legacy backup buttons in settings - no longer exist, backup is in admin > system tab
}

// --- Browser Notifications ---
function setupNotifications() {
  if (!('Notification' in window)) return;
  if (Notification.permission === 'default') {
    Notification.requestPermission();
  }
}

function notify(title, body) {
  if (!('Notification' in window) || Notification.permission !== 'granted') return;
  new Notification(title, { body, icon: '/favicon.ico' });
}

// --- Permissions & collaborative editing ---
function userCan(permission) {
  return Boolean(currentUser?.role === 'admin' || currentUser?.permissions?.includes(permission));
}

function applyPermissionUi() {
  const rules = [
    ['generator', 'listings.edit'], ['image-factory', 'batch.execute'], ['flux-studio', 'generation.execute'],
    ['publish', 'publishing.view'], ['quality-center', 'listings.view'], ['inventory', 'listings.view'],
  ];
  for (const [tab, permission] of rules) {
    const button = document.querySelector(`.nav-item[data-tab="${tab}"]`);
    if (button) button.classList.toggle('hidden', !userCan(permission));
  }
  document.querySelectorAll('[data-requires-permission]').forEach(element => {
    const permission = element.dataset.requiresPermission;
    element.classList.toggle('hidden', !userCan(permission));
    if ('disabled' in element) element.disabled = !userCan(permission);
  });
  document.body.dataset.userRole = currentUser?.role || 'viewer';
}

function clearListingLockTimer() {
  if (listingEditLock.timer) window.clearInterval(listingEditLock.timer);
  listingEditLock.timer = null;
}

async function releaseListingLock({ quiet = true, keepalive = false } = {}) {
  clearListingLockTimer();
  if (!listingEditLock.listingId || !listingEditLock.token) {
    listingEditLock.listingId = null; listingEditLock.token = null; listingEditLock.lock = null;
    return;
  }
  const id = listingEditLock.listingId;
  const token = listingEditLock.token;
  listingEditLock.listingId = null; listingEditLock.token = null; listingEditLock.lock = null;
  try {
    if (keepalive) {
      await fetch(`/api/locks/listing/${id}`, {
        method: 'DELETE', credentials: 'same-origin', keepalive: true,
        headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf() },
        body: JSON.stringify({ token }),
      });
    } else await api('DELETE', `/api/locks/listing/${id}`, { token }, { retry: false, timeoutMs: 8000 });
  } catch (error) { if (!quiet) toast(error.message || 'Bearbeitungssperre konnte nicht gelöst werden.'); }
}

function showListingLockBanner(lock, owned = false) {
  document.getElementById('listing-lock-banner')?.remove();
  const detail = document.querySelector('.listing-editor-shell');
  if (!detail || !lock) return;
  const banner = document.createElement('div');
  banner.id = 'listing-lock-banner';
  banner.className = `listing-lock-banner ${owned ? 'is-mine' : 'is-foreign'}`;
  banner.innerHTML = owned
    ? `<span data-vd-icon="lock-keyhole"></span><div><strong>Bearbeitung für dich reserviert</strong><p>Andere Benutzer können diesen Artikel ansehen, aber nicht gleichzeitig speichern.</p></div>`
    : `<span data-vd-icon="lock"></span><div><strong>${escapeHtml(lock.user_name || lock.user_email || 'Ein anderer Benutzer')} bearbeitet diesen Artikel</strong><p>Du kannst den Artikel ansehen. Speichern ist bis zum Ablauf der Sperre deaktiviert.</p></div>`;
  detail.prepend(banner);
  const save = document.getElementById('detail-save');
  if (save && !owned) { save.disabled = true; save.title = 'Artikel ist durch einen anderen Benutzer gesperrt'; }
}

async function acquireListingLock(listingId) {
  await releaseListingLock({ quiet: true });
  if (!listingId || !userCan('listings.edit')) return { acquired: false, readOnly: true };
  try {
    const result = await api('POST', `/api/locks/listing/${listingId}/acquire`, { client_label: `${navigator.platform || 'Browser'} · Artikel-Editor`, ttl_seconds: 120 });
    listingEditLock.listingId = Number(listingId);
    listingEditLock.token = result.token;
    listingEditLock.lock = result.lock;
    showListingLockBanner(result.lock, true);
    listingEditLock.timer = window.setInterval(async () => {
      if (!listingEditLock.listingId || !listingEditLock.token) return;
      try {
        const renewed = await api('POST', `/api/locks/listing/${listingEditLock.listingId}/renew`, { token: listingEditLock.token, ttl_seconds: 120 }, { timeoutMs: 10000, retry: false });
        listingEditLock.lock = renewed.lock;
      } catch (error) {
        clearListingLockTimer();
        const save = document.getElementById('detail-save');
        if (save) save.disabled = true;
        toast(`Bearbeitungssperre verloren: ${error.message}`);
      }
    }, 45000);
    return { acquired: true, lock: result.lock };
  } catch (error) {
    if (error.status === 409) {
      showListingLockBanner(error.lock || { user_name: 'Ein anderer Benutzer' }, false);
      return { acquired: false, readOnly: true, lock: error.lock };
    }
    toast(`Bearbeitungssperre konnte nicht erstellt werden: ${error.message}`);
    return { acquired: false, readOnly: true };
  }
}

window.addEventListener('beforeunload', () => { releaseListingLock({ quiet: true, keepalive: true }); });

// --- User Menu ---
let currentUser = null;

async function setupUserMenu() {
  try {
    currentUser = await api('GET', '/api/me');
    if (!currentUser || currentUser.setupMode) {
      document.getElementById('user-menu').classList.add('hidden');
      return;
    }
    _csrfToken = currentUser.csrf || getCsrf();

    // Populate sidebar user area
    const initials = currentUser.initials || currentUser.name?.[0]?.toUpperCase() || '?';
    const sidebarAvatar = document.getElementById('sidebar-avatar');
    if (sidebarAvatar) {
      sidebarAvatar.textContent = initials;
      sidebarAvatar.style.background = currentUser.avatar_color || 'var(--accent)';
    }
    const sidebarName = document.getElementById('sidebar-name');
    if (sidebarName) sidebarName.textContent = currentUser.name || currentUser.email || '';
    const sidebarRole = document.getElementById('sidebar-role');
    if (sidebarRole) sidebarRole.textContent = currentUser.role || '';
    const topbarAvatar = document.getElementById('topbar-avatar');
    if (topbarAvatar) {
      topbarAvatar.textContent = initials;
      topbarAvatar.style.background = currentUser.avatar_color || 'var(--vd-ink)';
      topbarAvatar.title = currentUser.name || currentUser.email || '';
    }

    const adminNav = document.getElementById('nav-admin-main');
    if (adminNav) adminNav.classList.toggle('hidden', !['admin', 'manager'].includes(currentUser.role));
    if (currentUser.role === 'manager') {
      document.querySelectorAll('.admin-tab:not([data-admin-tab="audit"])').forEach(tab => tab.classList.add('hidden'));
      document.getElementById('invite-user-btn')?.classList.add('hidden');
      adminNav?.setAttribute('data-admin-tab', 'audit');
    }
    applyPermissionUi();

    // Logout
    document.getElementById('sidebar-logout-btn')?.addEventListener('click', async () => {
      await releaseListingLock({ quiet: true });
      await fetch('/auth/logout', { method: 'POST', credentials: 'same-origin', headers: { 'X-CSRF-Token': _csrfToken || getCsrf() } });
      window.location.href = '/login.html';
    });
  } catch {
    document.getElementById('sidebar-user')?.classList.add('hidden');
  }
}


// --- Operations Center 1.30.0 ---
const operationsState = { status: null, backups: [], extensions: null, restoreFile: null, updateFile: null };

function operationFormatBytes(value) {
  const bytes = Number(value || 0);
  if (!bytes) return '0 B';
  const units = ['B', 'KB', 'MB', 'GB'];
  const index = Math.min(units.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)));
  return `${(bytes / (1024 ** index)).toLocaleString('de-DE', { maximumFractionDigits: index ? 1 : 0 })} ${units[index]}`;
}

function operationFormatDate(value) {
  if (!value) return '—';
  try { return new Date(String(value).replace(' ', 'T') + (String(value).includes('Z') ? '' : 'Z')).toLocaleString('de-DE', { dateStyle: 'medium', timeStyle: 'short' }); }
  catch { return String(value); }
}

async function operationsUploadRequest(url, field, file) {
  const form = new FormData();
  form.append(field, file);
  const response = await fetch(url, { method: 'POST', body: form, headers: { 'X-CSRF-Token': getCsrf() }, credentials: 'same-origin' });
  const data = await response.json().catch(() => ({}));
  if (!response.ok) throw new Error(data.error || `HTTP ${response.status}`);
  return data;
}

function renderOperationsStatus() {
  const status = operationsState.status || {};
  const pending = status.pending;
  const banner = document.getElementById('operations-pending-banner');
  if (banner) {
    banner.classList.toggle('hidden', !pending);
    if (pending) banner.innerHTML = `<span data-vd-icon="alert-triangle"></span><div><strong>${pending.type === 'update' ? `Update auf ${escapeHtml(pending.version_to || 'neue Version')}` : 'Wiederherstellung'} ist vorbereitet</strong><p>Vendoo beenden und <code>setup.bat</code> → <b>11 – Vorbereitete Operation anwenden</b> ausführen.</p></div><button type="button" class="secondary-btn" id="operations-pending-cancel">Vorbereitung verwerfen</button>`;
  }
  document.getElementById('operations-pending-cancel')?.addEventListener('click', async () => {
    if (!confirm('Vorbereitete Operation wirklich verwerfen?')) return;
    try { await api('POST', '/api/operations/pending/cancel'); toast('Vorbereitung verworfen.'); await loadOperationsCenter(); }
    catch (error) { toast(error.message || 'Vorbereitung konnte nicht verworfen werden.'); }
  });

  const version = document.getElementById('operations-version-card');
  if (version) {
    const last = status.last;
    version.innerHTML = `<strong>Vendoo ${escapeHtml(status.version || VENDOO_UI_BUILD)}</strong><span>${last ? `Letzte Operation: ${escapeHtml(last.type || 'System')} · ${escapeHtml(last.status || 'unbekannt')}` : 'Noch keine Setup-Operation protokolliert.'}</span>`;
  }
  const migrations = document.getElementById('operations-migration-list');
  if (migrations) {
    const rows = status.migrations || [];
    migrations.innerHTML = rows.length ? rows.slice(0, 8).map(row => `<div><span data-vd-icon="check-circle-2"></span><p><strong>${escapeHtml(row.app_version)}</strong><small>${escapeHtml(row.description || row.migration_key)} · ${operationFormatDate(row.applied_at)}</small></p></div>`).join('') : '<div class="empty-state">Keine Migrationsinformationen vorhanden.</div>';
  }

  const settings = status.settings || {};
  const enabled = document.getElementById('operations-auto-enabled'); if (enabled) enabled.checked = !!settings.auto_backup_enabled;
  const hour = document.getElementById('operations-auto-hour'); if (hour) hour.value = String(settings.auto_backup_hour ?? 3);
  const retention = document.getElementById('operations-auto-retention'); if (retention) retention.value = String(settings.auto_backup_retention ?? 7);
  const uploads = document.getElementById('operations-auto-uploads'); if (uploads) uploads.checked = settings.include_uploads_automatic !== false;
  const updateUrl = document.getElementById('operations-update-url'); if (updateUrl && document.activeElement !== updateUrl) updateUrl.value = settings.update_manifest_url || '';
  window.VendooIcons?.render(document.getElementById('admin-system-panel'));
}

function renderOperationsBackups() {
  const list = document.getElementById('operations-backup-list');
  const summary = document.getElementById('operations-backup-summary');
  if (!list) return;
  const backups = operationsState.backups || [];
  const total = backups.reduce((sum, item) => sum + Number(item.file_size || 0), 0);
  if (summary) summary.textContent = `${backups.length} Sicherung(en) · ${operationFormatBytes(total)}`;
  if (!backups.length) {
    list.innerHTML = '<div class="empty-state">Noch kein Backup vorhanden.</div>';
    return;
  }
  list.innerHTML = backups.map(item => {
    const verified = item.status === 'verified';
    const includesUploads = item.options?.includeUploads;
    return `<article class="operations-backup-row${item.exists ? '' : ' is-missing'}">
      <div class="operation-backup-icon"><span data-vd-icon="${verified ? 'shield-check' : 'archive'}"></span></div>
      <div class="operation-backup-main"><strong>${escapeHtml(item.label || item.file_name)}</strong><span>${escapeHtml(item.kind === 'automatic' ? 'Automatisch' : item.kind === 'database' ? 'Nur Datenbank' : 'Manuell')} · ${operationFormatDate(item.created_at)}</span><small>${operationFormatBytes(item.file_size)} · ${includesUploads ? 'mit Uploads' : 'ohne Uploads'}${item.options?.includeConfig ? ' · enthält .env' : ''} · ${verified ? 'verifiziert' : escapeHtml(item.status)}</small></div>
      <code class="operation-backup-hash" title="SHA-256">${escapeHtml(String(item.sha256 || '').slice(0, 16))}…</code>
      <div class="operation-backup-actions">
        <a class="icon-button" href="/api/operations/backups/${encodeURIComponent(item.id)}/download" title="Herunterladen"><span data-vd-icon="download"></span></a>
        <button type="button" class="icon-button" data-operation-backup-verify="${escapeHtml(item.id)}" title="Prüfen"><span data-vd-icon="scan-check"></span></button>
        <button type="button" class="icon-button danger" data-operation-backup-delete="${escapeHtml(item.id)}" title="Löschen"><span data-vd-icon="trash-2"></span></button>
      </div>
    </article>`;
  }).join('');
  list.querySelectorAll('[data-operation-backup-verify]').forEach(button => button.addEventListener('click', async () => {
    button.disabled = true;
    try {
      const result = await api('POST', `/api/operations/backups/${encodeURIComponent(button.dataset.operationBackupVerify)}/verify`);
      toast(result.ok ? 'Backup vollständig verifiziert.' : `Backup fehlerhaft: ${(result.errors || []).join(', ')}`);
      await loadOperationsBackups();
    } catch (error) { toast(error.message); }
    finally { button.disabled = false; }
  }));
  list.querySelectorAll('[data-operation-backup-delete]').forEach(button => button.addEventListener('click', async () => {
    if (!confirm('Dieses Backup endgültig löschen?')) return;
    try { await api('DELETE', `/api/operations/backups/${encodeURIComponent(button.dataset.operationBackupDelete)}`); toast('Backup gelöscht.'); await loadOperationsBackups(); }
    catch (error) { toast(error.message); }
  }));
  window.VendooIcons?.render(list);
}

function renderOperationsExtensions() {
  const data = operationsState.extensions || {};
  const summary = document.getElementById('operations-extension-summary');
  const packages = document.getElementById('operations-extension-packages');
  const clients = document.getElementById('operations-extension-clients');
  if (summary) summary.innerHTML = `<article><span data-vd-icon="plug-zap"></span><div><strong>${Number(data.connected || 0)} Browser live verbunden</strong><p>Erwartete Extension-Version: ${escapeHtml(data.expected_version || '—')}</p></div></article>`;
  if (packages) packages.innerHTML = Object.entries(data.packages || {}).map(([browser, item]) => `<article class="${item.exists && item.version === data.expected_version ? 'is-ok' : 'is-warning'}"><span data-vd-icon="${item.exists ? 'package-check' : 'package-x'}"></span><div><strong>${escapeHtml(browser[0].toUpperCase() + browser.slice(1))}</strong><small>${item.exists ? `Paket v${escapeHtml(item.version || '?')}` : 'Paket fehlt'}${item.version && item.version !== data.expected_version ? ` · erwartet ${escapeHtml(data.expected_version)}` : ''}</small></div></article>`).join('');
  if (clients) {
    const rows = data.clients || [];
    clients.innerHTML = rows.length ? rows.map(client => {
      const live = Number(client.age_seconds || 999999) <= 180;
      return `<article class="operations-extension-client${live ? ' is-live' : ''}"><span class="operation-live-dot"></span><div><strong>${escapeHtml(client.browser || 'Browser')} · v${escapeHtml(client.version || '?')}</strong><span>${escapeHtml(client.platform || 'Extension')} · zuletzt ${Number(client.age_seconds || 0) < 60 ? 'gerade eben' : `vor ${Math.max(1, Math.round(Number(client.age_seconds || 0) / 60))} Min.`}</span><small>${escapeHtml(client.server_url || '')}</small></div><b>${live ? 'Live' : 'Offline'}</b></article>`;
    }).join('') : '<div class="empty-state">Noch keine Extension-Verbindung gemeldet. Öffne nach dem Update die Extension oder eine unterstützte Vinted-Seite.</div>';
  }
  window.VendooIcons?.render(document.getElementById('operations-extensions-panel'));
}

async function loadOperationsBackups() {
  try { operationsState.backups = (await api('GET', '/api/operations/backups')).backups || []; renderOperationsBackups(); }
  catch (error) { const list = document.getElementById('operations-backup-list'); if (list) list.innerHTML = `<div class="empty-state">${escapeHtml(error.message)}</div>`; }
}

async function loadOperationsExtensions() {
  try { operationsState.extensions = await api('GET', '/api/extensions/diagnostics'); renderOperationsExtensions(); }
  catch (error) { const box = document.getElementById('operations-extension-clients'); if (box) box.innerHTML = `<div class="empty-state">${escapeHtml(error.message)}</div>`; }
}

function renderDeploymentStatus() {
  const data = operationsState.deployment || {};
  const setValue = (id, value) => { const field = document.getElementById(id); if (field && document.activeElement !== field) field.value = value ?? ''; };
  setValue('deployment-provider', data.provider || 'none');
  setValue('deployment-coolify-url', data.coolify_url || '');
  setValue('deployment-resource-uuid', data.resource_uuid || '');
  setValue('deployment-trigger-mode', data.trigger_mode || 'webhook');
  setValue('deployment-github-repository', data.github_repository || 'Masterluke77/vendoo');
  setValue('deployment-github-branch', data.github_branch || 'main');
  setValue('deployment-update-channel', data.update_channel || 'stable');
  const backup = document.getElementById('deployment-backup-before'); if (backup) backup.checked = data.backup_before_deploy !== false;
  const uploads = document.getElementById('deployment-backup-uploads'); if (uploads) uploads.checked = data.backup_uploads === true;
  const webhook = document.getElementById('deployment-webhook-url'); if (webhook && document.activeElement !== webhook) { webhook.value = ''; webhook.placeholder = data.deploy_webhook?.configured ? `Gespeichert: ${data.deploy_webhook.masked || '••••••••'}` : 'Coolify Deploy-Webhook eintragen'; }
  const token = document.getElementById('deployment-api-token'); if (token && document.activeElement !== token) { token.value = ''; token.placeholder = data.api_token?.configured ? `Gespeichert: ${data.api_token.masked || '••••••••'}` : 'Nur für API-Modus'; }
  const status = document.getElementById('deployment-status');
  if (status) status.innerHTML = `<strong>${data.configured ? 'Coolify ist konfiguriert' : 'Coolify noch nicht vollständig konfiguriert'}</strong><span>Provider: ${escapeHtml(data.provider || 'none')} · Trigger: ${escapeHtml(data.trigger_mode || 'webhook')} · Docker-Socket: ${data.docker_socket_access ? 'vorhanden' : 'nicht freigegeben'}</span>`;
  const action = document.getElementById('deployment-action-result');
  if (action && data.last_deployment) action.innerHTML = `<strong>${data.last_deployment.accepted ? 'Letzter Deployment-Auftrag angenommen' : 'Letzter Deployment-Auftrag fehlgeschlagen'}</strong><span>${operationFormatDate(data.last_deployment.requested_at)} · Branch ${escapeHtml(data.last_deployment.github_branch || 'main')} · HTTP ${escapeHtml(String(data.last_deployment.http_status || '—'))}</span>`;
}

async function loadDeploymentStatus() {
  try { operationsState.deployment = await api('GET', '/api/admin/deployment'); renderDeploymentStatus(); }
  catch (error) { const box = document.getElementById('deployment-status'); if (box) box.innerHTML = `<strong>Deploymentstatus konnte nicht geladen werden</strong><span>${escapeHtml(error.message)}</span>`; }
}

function deploymentSettingsPayload() {
  return {
    provider: document.getElementById('deployment-provider')?.value || 'none',
    coolify_url: document.getElementById('deployment-coolify-url')?.value || '',
    resource_uuid: document.getElementById('deployment-resource-uuid')?.value || '',
    trigger_mode: document.getElementById('deployment-trigger-mode')?.value || 'webhook',
    deploy_method: 'GET',
    deploy_endpoint: '/api/v1/deploy?uuid={uuid}&force={force}',
    github_repository: document.getElementById('deployment-github-repository')?.value || 'Masterluke77/vendoo',
    github_branch: document.getElementById('deployment-github-branch')?.value || 'main',
    ghcr_image: 'ghcr.io/masterluke77/vendoo',
    update_channel: document.getElementById('deployment-update-channel')?.value || 'stable',
    backup_before_deploy: document.getElementById('deployment-backup-before')?.checked !== false,
    backup_uploads: document.getElementById('deployment-backup-uploads')?.checked === true,
    api_token: document.getElementById('deployment-api-token')?.value || '',
    deploy_webhook_url: document.getElementById('deployment-webhook-url')?.value || '',
  };
}

async function loadOperationsCenter() {
  if (!document.getElementById('operations-center-title')) return;
  try { operationsState.status = await api('GET', '/api/operations/status'); renderOperationsStatus(); } catch (error) { toast(error.message); }
  await Promise.all([loadOperationsBackups(), loadOperationsExtensions(), loadDeploymentStatus()]);
}

function setOperationsTab(target) {
  document.querySelectorAll('.operations-tab').forEach(button => button.classList.toggle('active', button.dataset.operationsTab === target));
  document.querySelectorAll('.operations-panel').forEach(panel => panel.classList.toggle('active', panel.id === `operations-${target}-panel`));
}

function setupOperationsCenter() {
  const hour = document.getElementById('operations-auto-hour');
  if (hour && !hour.options.length) hour.innerHTML = Array.from({ length: 24 }, (_, index) => `<option value="${index}">${String(index).padStart(2, '0')}:00 Uhr</option>`).join('');
  document.querySelectorAll('.operations-tab').forEach(button => button.addEventListener('click', () => setOperationsTab(button.dataset.operationsTab)));
  document.getElementById('operations-refresh-btn')?.addEventListener('click', loadOperationsCenter);
  document.getElementById('operations-extension-refresh')?.addEventListener('click', loadOperationsExtensions);

  document.getElementById('operations-backup-create')?.addEventListener('click', async event => {
    const button = event.currentTarget;
    button.disabled = true; const old = button.innerHTML; button.textContent = 'Backup wird erstellt und geprüft …';
    try {
      const result = await api('POST', '/api/operations/backups', {
        label: document.getElementById('operations-backup-label')?.value || '',
        include_uploads: document.getElementById('operations-backup-uploads')?.checked !== false,
        include_config: document.getElementById('operations-backup-config')?.checked === true,
      });
      toast(`Backup verifiziert: ${result.filename}`);
      document.getElementById('operations-backup-label').value = '';
      await loadOperationsBackups();
    } catch (error) { toast(error.message || 'Backup fehlgeschlagen.'); }
    finally { button.disabled = false; button.innerHTML = old; window.VendooIcons?.render(button); }
  });

  document.getElementById('operations-settings-save')?.addEventListener('click', async () => {
    try {
      operationsState.status.settings = await api('PUT', '/api/operations/settings', {
        auto_backup_enabled: document.getElementById('operations-auto-enabled')?.checked === true,
        auto_backup_hour: Number(document.getElementById('operations-auto-hour')?.value || 3),
        auto_backup_retention: Number(document.getElementById('operations-auto-retention')?.value || 7),
        include_uploads_automatic: document.getElementById('operations-auto-uploads')?.checked !== false,
      });
      renderOperationsStatus(); toast('Backup-Zeitplan gespeichert.');
    } catch (error) { toast(error.message); }
  });

  const restoreInput = document.getElementById('operations-restore-input');
  restoreInput?.addEventListener('change', () => {
    operationsState.restoreFile = restoreInput.files?.[0] || null;
    document.getElementById('operations-restore-file').textContent = operationsState.restoreFile ? `${operationsState.restoreFile.name} · ${operationFormatBytes(operationsState.restoreFile.size)}` : 'Keine Datei ausgewählt.';
    document.getElementById('operations-restore-stage').disabled = !operationsState.restoreFile;
  });
  document.getElementById('operations-restore-stage')?.addEventListener('click', async event => {
    if (!operationsState.restoreFile || !confirm('Backup vollständig prüfen und Wiederherstellung vorbereiten? Es wird automatisch ein Sicherheitsbackup erstellt.')) return;
    const button = event.currentTarget; button.disabled = true; button.textContent = 'Backup wird geprüft …';
    try { const result = await operationsUploadRequest('/api/operations/restore/stage', 'backup', operationsState.restoreFile); toast(result.message); operationsState.restoreFile = null; restoreInput.value = ''; await loadOperationsCenter(); }
    catch (error) { toast(error.message); }
    finally { button.disabled = !operationsState.restoreFile; button.textContent = 'Wiederherstellung prüfen und vorbereiten'; }
  });

  document.getElementById('operations-update-url-save')?.addEventListener('click', async () => {
    try { operationsState.status.settings = await api('PUT', '/api/operations/settings', { update_manifest_url: document.getElementById('operations-update-url')?.value || '' }); renderOperationsStatus(); toast('Update-Manifest-URL gespeichert.'); }
    catch (error) { toast(error.message); }
  });
  document.getElementById('operations-update-check')?.addEventListener('click', async event => {
    const resultBox = document.getElementById('operations-update-check-result'); const button = event.currentTarget; button.disabled = true; if (resultBox) resultBox.textContent = 'Update wird geprüft …';
    try {
      const result = await api('POST', '/api/operations/updates/check');
      if (resultBox) resultBox.innerHTML = result.configured ? `<strong>${result.update_available ? `Version ${escapeHtml(result.latest_version)} verfügbar` : 'Vendoo ist aktuell'}</strong><span>Installiert: ${escapeHtml(result.current_version)}${result.latest_version ? ` · Verfügbar: ${escapeHtml(result.latest_version)}` : ''}</span>` : `<strong>Keine Online-Prüfung konfiguriert</strong><span>${escapeHtml(result.message || '')}</span>`;
    } catch (error) { if (resultBox) resultBox.innerHTML = `<strong>Prüfung fehlgeschlagen</strong><span>${escapeHtml(error.message)}</span>`; }
    finally { button.disabled = false; }
  });

  const updateInput = document.getElementById('operations-update-input');
  updateInput?.addEventListener('change', () => {
    operationsState.updateFile = updateInput.files?.[0] || null;
    document.getElementById('operations-update-file').textContent = operationsState.updateFile ? `${operationsState.updateFile.name} · ${operationFormatBytes(operationsState.updateFile.size)}` : 'Keine Datei ausgewählt.';
    document.getElementById('operations-update-stage').disabled = !operationsState.updateFile;
  });
  document.getElementById('operations-update-stage')?.addEventListener('click', async event => {
    if (!operationsState.updateFile || !confirm('Update prüfen, Sicherheitsbackup erzeugen und für den nächsten Setup-Lauf vorbereiten?')) return;
    const button = event.currentTarget; button.disabled = true; button.textContent = 'Update wird geprüft …';
    try { const result = await operationsUploadRequest('/api/operations/updates/stage', 'update', operationsState.updateFile); toast(result.message); operationsState.updateFile = null; updateInput.value = ''; await loadOperationsCenter(); }
    catch (error) { toast(error.message); }
    finally { button.disabled = !operationsState.updateFile; button.textContent = 'Update prüfen und vorbereiten'; }
  });

  document.getElementById('deployment-save')?.addEventListener('click', async event => {
    const button = event.currentTarget; button.disabled = true;
    try { operationsState.deployment = await api('PUT', '/api/admin/deployment', deploymentSettingsPayload()); renderDeploymentStatus(); toast('Coolify- und GitHub-Konfiguration gespeichert.'); }
    catch (error) { toast(error.message); }
    finally { button.disabled = false; }
  });
  document.getElementById('deployment-test')?.addEventListener('click', async event => {
    const button = event.currentTarget; const resultBox = document.getElementById('deployment-status'); button.disabled = true;
    try { const result = await api('POST', '/api/admin/deployment/test'); if (resultBox) resultBox.innerHTML = `<strong>Coolify erreichbar</strong><span>Healthcheck HTTP ${escapeHtml(String(result.status || 200))}</span>`; }
    catch (error) { if (resultBox) resultBox.innerHTML = `<strong>Coolify nicht erreichbar</strong><span>${escapeHtml(error.message)}</span>`; }
    finally { button.disabled = false; }
  });
  document.getElementById('deployment-update-check')?.addEventListener('click', async event => {
    const button = event.currentTarget; const resultBox = document.getElementById('deployment-action-result'); button.disabled = true;
    try { const result = await api('POST', '/api/admin/deployment/updates/check'); if (resultBox) resultBox.innerHTML = `<strong>${result.update_available ? `Version ${escapeHtml(result.latest_version)} verfügbar` : result.configured ? 'Vendoo ist aktuell' : 'Keine Manifest-URL konfiguriert'}</strong><span>Installiert: ${escapeHtml(result.current_version || VENDOO_UI_BUILD)}${result.message ? ` · ${escapeHtml(result.message)}` : ''}</span>`; }
    catch (error) { if (resultBox) resultBox.innerHTML = `<strong>Updateprüfung fehlgeschlagen</strong><span>${escapeHtml(error.message)}</span>`; }
    finally { button.disabled = false; }
  });
  document.getElementById('deployment-trigger')?.addEventListener('click', async event => {
    const confirmation = prompt('Vor dem Deployment wird – sofern aktiviert – ein Backup erzeugt. Coolify zieht danach den konfigurierten GitHub-Branch. Zum Fortfahren exakt DEPLOY eingeben:');
    if (confirmation !== 'DEPLOY') return;
    const button = event.currentTarget; const resultBox = document.getElementById('deployment-action-result'); button.disabled = true; button.textContent = 'Deployment wird angefordert …';
    try {
      const result = await api('POST', '/api/admin/deployment/trigger', { confirmation, force: false, reason: 'update-center' });
      if (resultBox) resultBox.innerHTML = `<strong>Coolify-Deployment angenommen</strong><span>${escapeHtml(result.message || '')}${result.backup_file ? ` · Backup: ${escapeHtml(result.backup_file)}` : ''}</span>`;
      toast('Coolify-Deployment wurde angefordert.');
      await loadDeploymentStatus();
    } catch (error) { if (resultBox) resultBox.innerHTML = `<strong>Deployment fehlgeschlagen</strong><span>${escapeHtml(error.message)}</span>`; toast(error.message); }
    finally { button.disabled = false; button.textContent = 'Coolify-Deployment starten'; }
  });
}

// --- Admin ---
function setupAdmin() {
  // Admin tab switching
  document.querySelectorAll('.admin-tab').forEach(tab => {
    tab.addEventListener('click', () => {
      const target = tab.dataset.adminTab;
      switchAdminTab(target);
      if (target === 'users') loadAdminUsers();
      if (target === 'roles') loadRoleMatrix();
      if (target === 'sessions') loadAdminSessions();
      if (target === 'smtp') loadSmtpSettings();
      if (target === 'system') { loadSystemInfo(); loadOperationsCenter(); }
      if (target === 'modules') globalThis.VendooModuleManager?.load?.();
      if (target === 'security') loadSecurityCenter();
      if (target === 'audit') loadAdminAudit();
    });
  });

  document.getElementById('invite-user-btn')?.addEventListener('click', () => {
    document.getElementById('invite-email').value = '';
    document.getElementById('invite-name').value = '';
    document.getElementById('invite-role').value = 'editor';
    document.getElementById('invite-result-wrap').classList.add('hidden');
    document.getElementById('invite-result-password').value = '';
    const mlRadio = document.querySelector('input[name="invite-method"][value="magic_link"]');
    if (mlRadio) mlRadio.checked = true;
    document.getElementById('invite-send-btn').textContent = 'Einladung senden';
    document.getElementById('invite-send-btn').onclick = sendInvite;
    document.getElementById('invite-modal').classList.remove('hidden');
  });

  document.getElementById('invite-copy-pw')?.addEventListener('click', () => {
    const pw = document.getElementById('invite-result-password').value;
    if (pw) { navigator.clipboard.writeText(pw); toast('Passwort kopiert!'); }
  });

  document.getElementById('invite-send-btn')?.addEventListener('click', sendInvite);
  document.getElementById('edit-user-save-btn')?.addEventListener('click', saveUserEdit);

  // SMTP
  document.getElementById('smtp-save-btn')?.addEventListener('click', saveSmtpSettings);
  document.getElementById('smtp-test-btn')?.addEventListener('click', testSmtpConnection);
  document.getElementById('smtp-pass-toggle')?.addEventListener('click', () => {
    const inp = document.getElementById('smtp-pass');
    const isPw = inp.type === 'password';
    inp.type = isPw ? 'text' : 'password';
    document.getElementById('smtp-pass-toggle').textContent = isPw ? 'Verbergen' : 'Zeigen';
  });

  // Edit user password toggle
  document.getElementById('edit-user-pw-toggle')?.addEventListener('click', () => {
    const inp = document.getElementById('edit-user-password');
    const isPw = inp.type === 'password';
    inp.type = isPw ? 'text' : 'password';
    document.getElementById('edit-user-pw-toggle').textContent = isPw ? 'Verbergen' : 'Zeigen';
  });

  // System tab
  document.getElementById('system-cleanup-btn')?.addEventListener('click', async () => {
    try {
      const res = await api('POST', '/api/admin/sessions/cleanup');
      toast(res.message || 'Sessions bereinigt');
      loadSystemInfo();
    } catch (err) { toast('Fehler: ' + err.message); }
  });

  document.getElementById('system-diagnostics-btn')?.addEventListener('click', runSystemDiagnostics);
  document.getElementById('system-diagnostics-copy')?.addEventListener('click', copySystemDiagnostics);
  document.getElementById('security-refresh-btn')?.addEventListener('click', loadSecurityCenter);
  document.getElementById('audit-filter-btn')?.addEventListener('click', () => { auditState.page = 1; loadAdminAudit(); });
  document.getElementById('audit-search')?.addEventListener('keydown', event => { if (event.key === 'Enter') { auditState.page = 1; loadAdminAudit(); } });
  document.getElementById('audit-prev')?.addEventListener('click', () => { if (auditState.page > 1) { auditState.page -= 1; loadAdminAudit(); } });
  document.getElementById('audit-next')?.addEventListener('click', () => { if (auditState.page < auditState.pages) { auditState.page += 1; loadAdminAudit(); } });
  document.getElementById('audit-export-btn')?.addEventListener('click', exportAdminAudit);

  setupOperationsCenter();
}

function switchAdminTab(target) {
  document.querySelectorAll('.admin-tab').forEach(t => t.classList.toggle('active', t.dataset.adminTab === target));
  document.querySelectorAll('.admin-panel').forEach(p => p.classList.remove('active'));
  document.getElementById(`admin-${target}-panel`)?.classList.add('active');
}

function timeAgo(dateStr) {
  if (!dateStr) return 'Nie';
  const diff = Date.now() - new Date(dateStr + 'Z').getTime();
  const min = Math.floor(diff / 60000);
  if (min < 1) return 'Gerade eben';
  if (min < 60) return `Vor ${min}m`;
  const hrs = Math.floor(min / 60);
  if (hrs < 24) return `Vor ${hrs}h`;
  const days = Math.floor(hrs / 24);
  return `Vor ${days}d`;
}

function getInitials(name, email) {
  const src = name || email || '?';
  return src.split(/[\s@]/).map(w => w[0]?.toUpperCase()).filter(Boolean).slice(0, 2).join('');
}

const ROLE_LABELS = { admin: 'Administrator', manager: 'Manager', editor: 'Editor', publisher: 'Publisher', viewer: 'Leser' };
let adminUsersCache = [];

async function loadRoleMatrix() {
  const cards = document.getElementById('role-cards');
  const matrix = document.getElementById('role-permission-matrix');
  if (!cards || !matrix) return;
  try {
    const data = await api('GET', '/api/security/roles');
    cards.innerHTML = data.roles.map(role => `<article class="role-card role-${escapeHtml(role.key)}"><header><span class="role-badge ${escapeHtml(role.key)}">${escapeHtml(role.label)}</span><strong>${role.permissions.length} Rechte</strong></header><p>${escapeHtml(role.description)}</p></article>`).join('');
    const groups = [...new Set(data.permissions.map(permission => permission.group))];
    matrix.innerHTML = groups.map(group => {
      const permissions = data.permissions.filter(permission => permission.group === group);
      return `<section class="role-matrix-group"><h4>${escapeHtml(group)}</h4><div class="role-matrix-table"><div class="role-matrix-head"><span>Berechtigung</span>${data.roles.map(role => `<strong>${escapeHtml(role.label)}</strong>`).join('')}</div>${permissions.map(permission => `<div class="role-matrix-row"><span>${escapeHtml(permission.label)}</span>${data.roles.map(role => `<i class="${role.permissions.includes(permission.key) ? 'is-allowed' : 'is-denied'}" title="${role.permissions.includes(permission.key) ? 'Erlaubt' : 'Nicht erlaubt'}">${role.permissions.includes(permission.key) ? '✓' : '—'}</i>`).join('')}</div>`).join('')}</div></section>`;
    }).join('');
  } catch (error) {
    cards.innerHTML = `<div class="empty-state">${escapeHtml(error.message)}</div>`;
    matrix.innerHTML = '';
  }
}

async function forceReleaseSecurityLock(type, id) {
  if (!confirm('Bearbeitungssperre wirklich administrativ aufheben?')) return;
  try { await api('DELETE', `/api/security/locks/${encodeURIComponent(type)}/${encodeURIComponent(id)}`); toast('Sperre aufgehoben'); loadSecurityCenter(); }
  catch (error) { toast(error.message); }
}

async function loadSecurityCenter() {
  const summary = document.getElementById('security-status-summary');
  if (!summary) return;
  summary.innerHTML = '<div class="empty-state">Sicherheitsstatus wird geladen …</div>';
  try {
    const status = await api('GET', '/api/security/status');
    const secure = !status.remote_binding || status.secure_public_url;
    summary.innerHTML = `<div class="security-score ${secure && !status.warnings.length ? 'is-good' : 'is-warning'}"><span data-vd-icon="${secure ? 'shield-check' : 'shield-alert'}"></span><div><strong>${secure && !status.warnings.length ? 'Sicherheitsbasis aktiv' : 'Konfiguration braucht Aufmerksamkeit'}</strong><p>Vendoo ${escapeHtml(status.version)} · Bind ${escapeHtml(status.bind_host)}:${status.port} · ${status.trust_proxy ? 'Reverse-Proxy vertraut' : 'Direkte Verbindung'}</p></div></div>`;
    const warnings = document.getElementById('security-warning-list');
    warnings.innerHTML = status.warnings.length ? status.warnings.map(message => `<div class="security-warning"><span data-vd-icon="triangle-alert"></span><p>${escapeHtml(message)}</p></div>`).join('') : '<div class="security-ok"><span data-vd-icon="badge-check"></span> Keine kritischen Konfigurationswarnungen erkannt.</div>';
    document.getElementById('security-network-info').innerHTML = `<dl><dt>Bind-Adresse</dt><dd><code>${escapeHtml(status.bind_host)}</code></dd><dt>Öffentliche URL</dt><dd>${status.public_base_url ? `<code>${escapeHtml(status.public_base_url)}</code>` : 'Nicht gesetzt'}</dd><dt>HTTPS</dt><dd>${status.secure_public_url ? 'Aktiv' : 'Nicht erkannt'}</dd><dt>Erlaubte Hosts</dt><dd>${status.allowed_hosts.length ? status.allowed_hosts.map(escapeHtml).join(', ') : 'Nur lokale/private Hosts'}</dd><dt>Erlaubte Origins</dt><dd>${status.allowed_origins.length ? status.allowed_origins.map(escapeHtml).join(', ') : 'Nur Same-Origin und Extensions'}</dd></dl>`;
    document.getElementById('security-session-info').innerHTML = `<dl><dt>Absolute Sessiondauer</dt><dd>${status.session_days} Tage</dd><dt>Inaktivitätslimit</dt><dd>${status.session_idle_minutes} Minuten</dd><dt>Rate-Limit erlaubt</dt><dd>${status.rate_limits.allowed}</dd><dt>Rate-Limit blockiert</dt><dd>${status.rate_limits.blocked}</dd><dt>Aktive Sperren</dt><dd>${status.active_locks.length}</dd></dl>`;
    const secretInfo = document.getElementById('security-secret-info');
    if (secretInfo) secretInfo.innerHTML = `<dl><dt>Provider</dt><dd>${escapeHtml(status.secrets.provider)}</dd><dt>Verschlüsselt</dt><dd>${status.secrets.encrypted}</dd><dt>Umgebungsvariablen</dt><dd>${status.secrets.environment}</dd><dt>Konfiguriert</dt><dd>${status.secrets.configured}</dd></dl><div class="security-secret-list">${status.secrets.secrets.filter(item => item.configured).map(item => `<span class="status-badge">${escapeHtml(item.label)} · ${escapeHtml(item.provider)}</span>`).join('') || '<span class="muted">Keine Secrets konfiguriert.</span>'}</div>`;
    const requestInfo = document.getElementById('security-request-info');
    if (requestInfo) requestInfo.innerHTML = `<dl><dt>Requests seit Start</dt><dd>${status.requests.total}</dd><dt>Aktiv</dt><dd>${status.requests.active}</dd><dt>Serverfehler</dt><dd>${status.requests.errors}</dd><dt>Sicherheitsblockaden</dt><dd>${status.requests.blocked}</dd><dt>Inline-Skripte</dt><dd>${status.csp.script_inline_allowed ? 'Erlaubt' : 'Blockiert'}</dd><dt>CSP</dt><dd>${status.csp.enforced ? 'Erzwungen' : 'Nur Bericht'}</dd></dl>`;
    const locks = document.getElementById('security-lock-list');
    locks.innerHTML = status.active_locks.length ? status.active_locks.map(lock => `<article class="security-lock-row"><div><strong>${escapeHtml(lock.resource_type)} #${escapeHtml(lock.resource_id)}</strong><p>${escapeHtml(lock.user_name || lock.user_email)} · bis ${new Date(lock.expires_at).toLocaleTimeString('de-DE')}</p></div><button type="button" class="small-btn danger-btn" data-force-lock-type="${escapeHtml(lock.resource_type)}" data-force-lock-id="${escapeHtml(lock.resource_id)}">Aufheben</button></article>`).join('') : '<div class="empty-state">Keine aktiven Bearbeitungssperren.</div>';
    locks.querySelectorAll('[data-force-lock-type]').forEach(button => button.addEventListener('click', () => forceReleaseSecurityLock(button.dataset.forceLockType, button.dataset.forceLockId)));
    globalThis.VendooIcons?.render?.();
  } catch (error) { summary.innerHTML = `<div class="empty-state">${escapeHtml(error.message)}</div>`; }
}

const auditState = { page: 1, pages: 1, total: 0 };

function buildAuditQuery() {
  const size = Number(document.getElementById('audit-page-size')?.value || 100);
  const params = new URLSearchParams({ limit: String(size), offset: String((auditState.page - 1) * size) });
  const values = {
    q: document.getElementById('audit-search')?.value.trim(),
    action: document.getElementById('audit-action-filter')?.value.trim(),
    from: document.getElementById('audit-from')?.value,
    to: document.getElementById('audit-to')?.value ? `${document.getElementById('audit-to').value} 23:59:59` : '',
  };
  for (const [key, value] of Object.entries(values)) if (value) params.set(key, value);
  return params;
}

function exportAdminAudit() {
  const params = buildAuditQuery();
  params.delete('limit'); params.delete('offset');
  window.location.href = `/api/admin/audit/export.csv?${params.toString()}`;
}

async function loadAdminUsers() {
  try {
    const users = await api('GET', '/api/admin/users');
    adminUsersCache = Array.isArray(users) ? users : [];
    const container = document.getElementById('users-list');
    if (!container) return;
    if (!adminUsersCache.length) { container.innerHTML = '<div class="empty-state">Keine User vorhanden</div>'; return; }

    container.innerHTML = adminUsersCache.map(u => {
      const initials = getInitials(u.name, u.email);
      const isOnline = u.last_active_at && (Date.now() - new Date(`${u.last_active_at}Z`).getTime()) < 5 * 60 * 1000;
      const dotClass = !u.active ? 'inactive' : isOnline ? 'online' : 'offline';
      const lockedBadge = u.locked_until && Date.parse(u.locked_until) > Date.now() ? '<span class="pw-indicator no-pw">Vorübergehend gesperrt</span>' : '';
      const pwBadge = u.has_password
        ? '<span class="pw-indicator has-pw">Passwort gesetzt</span>'
        : '<span class="pw-indicator no-pw">Kein Passwort</span>';
      return `<div class="user-card" data-user-card="${u.id}">
        <div class="user-card-avatar" style="background:${escapeHtml(u.avatar_color || 'var(--accent)')}">${escapeHtml(initials)}</div>
        <div class="user-card-info">
          <div class="name">${escapeHtml(u.name || u.email)} <span class="role-badge ${escapeHtml(u.role)}">${escapeHtml(ROLE_LABELS[u.role] || u.role)}</span> ${pwBadge} ${lockedBadge}</div>
          <div class="email">${escapeHtml(u.email)}</div>
          <div class="meta"><span class="status-dot ${dotClass}"></span> ${u.active ? timeAgo(u.last_active_at) : 'Deaktiviert'} · Erstellt: ${new Date(`${u.created_at}Z`).toLocaleDateString('de-DE')}</div>
        </div>
        <div class="user-card-actions">
          <button type="button" class="small-btn" data-user-edit="${u.id}" title="Bearbeiten" aria-label="${escapeHtml(u.name || u.email)} bearbeiten"><span data-vd-icon="pencil"></span></button>
          <button type="button" class="small-btn" data-user-resend="${u.id}" title="Einladung erneut senden" aria-label="Einladung erneut senden"><span data-vd-icon="mail"></span></button>
          ${u.id !== currentUser?.id ? `<button type="button" class="small-btn danger-btn" data-user-delete="${u.id}" title="Löschen" aria-label="${escapeHtml(u.name || u.email)} löschen"><span data-vd-icon="trash-2"></span></button>` : ''}
        </div>
      </div>`;
    }).join('');

    container.querySelectorAll('[data-user-edit]').forEach(button => button.addEventListener('click', () => openEditUser(Number(button.dataset.userEdit))));
    container.querySelectorAll('[data-user-resend]').forEach(button => button.addEventListener('click', () => resendInvite(Number(button.dataset.userResend))));
    container.querySelectorAll('[data-user-delete]').forEach(button => button.addEventListener('click', () => {
      const user = adminUsersCache.find(entry => Number(entry.id) === Number(button.dataset.userDelete));
      if (user) confirmDeleteUser(user.id, user.email);
    }));
    globalThis.VendooIcons?.render?.(container);
  } catch (err) { toast('Fehler: ' + err.message); }
}

async function sendInvite() {
  const email = document.getElementById('invite-email').value.trim();
  const name = document.getElementById('invite-name').value.trim();
  const role = document.getElementById('invite-role').value;
  const method = document.querySelector('input[name="invite-method"]:checked')?.value || 'magic_link';
  if (!email) return toast('E-Mail erforderlich');

  try {
    const result = await api('POST', '/api/admin/users/invite', { email, name, role, method });

    if (method === 'auto_password' && result.password) {
      // Show generated password
      const resultWrap = document.getElementById('invite-result-wrap');
      const pwInput = document.getElementById('invite-result-password');
      pwInput.value = result.password;
      resultWrap.classList.remove('hidden');
      document.getElementById('invite-send-btn').textContent = 'Fertig';
      document.getElementById('invite-send-btn').onclick = () => {
        document.getElementById('invite-modal').classList.add('hidden');
        resultWrap.classList.add('hidden');
        document.getElementById('invite-send-btn').textContent = 'Einladung senden';
        document.getElementById('invite-send-btn').onclick = sendInvite;
      };
      toast(result.consoleFallback ? 'Account erstellt — Zugangsdaten in Konsole' : 'Account erstellt & Zugangsdaten gesendet');
    } else {
      document.getElementById('invite-modal').classList.add('hidden');
      if (result.consoleFallback) {
        toast('Einladung erstellt — Link in Server-Konsole');
      } else {
        toast('Einladung gesendet an ' + email);
      }
    }
    loadAdminUsers();
  } catch (err) { toast('Fehler: ' + err.message); }
}

async function resendInvite(userId) {
  try {
    const result = await api('POST', '/api/admin/users/resend-invite', { user_id: userId });
    if (result.consoleFallback) {
      toast('Link in Server-Konsole ausgegeben');
    } else {
      toast('Einladung erneut gesendet');
    }
  } catch (err) { toast('Fehler: ' + err.message); }
}

async function openEditUser(userId) {
  try {
    let user = adminUsersCache.find(entry => Number(entry.id) === Number(userId));
    if (!user) {
      const users = await api('GET', '/api/admin/users');
      adminUsersCache = Array.isArray(users) ? users : [];
      user = adminUsersCache.find(entry => Number(entry.id) === Number(userId));
    }
    if (!user) throw new Error('Benutzer wurde nicht gefunden.');
    document.getElementById('edit-user-id').value = user.id;
    const emailField = document.getElementById('edit-user-email');
    if (emailField) emailField.value = user.email || '';
    document.getElementById('edit-user-name').value = user.name || '';
    document.getElementById('edit-user-role').value = user.role;
    document.getElementById('edit-user-active').value = user.active ? '1' : '0';
    document.getElementById('edit-user-password').value = '';
    document.getElementById('edit-user-password').type = 'password';
    document.getElementById('edit-user-pw-toggle').textContent = 'Zeigen';

    // Password status
    const pwStatus = document.getElementById('edit-user-pw-status');
    if (user.has_password) {
      pwStatus.textContent = 'Passwort gesetzt';
      pwStatus.className = 'pw-status-badge has-pw';
    } else {
      pwStatus.textContent = 'Kein Passwort';
      pwStatus.className = 'pw-status-badge no-pw';
    }

    // Login history
    const historyEl = document.getElementById('edit-user-login-history');
    historyEl.classList.add('hidden');
    historyEl.innerHTML = '';

    const showHistBtn = document.getElementById('edit-user-show-history');
    showHistBtn.onclick = async () => {
      if (!historyEl.classList.contains('hidden')) {
        historyEl.classList.add('hidden');
        showHistBtn.textContent = 'Login-Verlauf anzeigen';
        return;
      }
      try {
        const history = await api('GET', `/api/admin/users/${userId}/login-history`);
        if (!history.length) {
          historyEl.innerHTML = '<div style="padding:10px;font-size:12px;color:var(--text-muted);text-align:center">Keine Login-Einträge</div>';
        } else {
          historyEl.innerHTML = history.map(h => {
            const dt = new Date(h.created_at + 'Z').toLocaleString('de-DE');
            const statusClass = h.success ? 'success' : 'failed';
            const statusText = h.success ? 'Erfolgreich' : 'Fehlgeschlagen';
            return `<div class="login-history-item">
              <span class="lh-time">${dt}</span>
              <span class="lh-ip">${h.ip || '—'}</span>
              <span class="lh-status ${statusClass}">${statusText}</span>
            </div>`;
          }).join('');
        }
        historyEl.classList.remove('hidden');
        showHistBtn.textContent = 'Verlauf ausblenden';
      } catch (err) { toast('Fehler: ' + err.message); }
    };

    document.getElementById('edit-user-modal').classList.remove('hidden');
  } catch (err) { toast('Fehler: ' + err.message); }
}

async function saveUserEdit() {
  const id = Number(document.getElementById('edit-user-id').value);
  const name = document.getElementById('edit-user-name').value.trim();
  const role = document.getElementById('edit-user-role').value;
  const active = Number(document.getElementById('edit-user-active').value);
  const password = document.getElementById('edit-user-password').value;
  const button = document.getElementById('edit-user-save-btn');

  if (!Number.isInteger(id) || id <= 0) return toast('Ungültiger Benutzer. Bitte die Liste neu laden.');
  if (!name) return toast('Bitte einen Namen eintragen.');
  if (password && (password.length < 10 || !/[a-z]/.test(password) || !/[A-Z]/.test(password) || !/\d/.test(password) || !/[^A-Za-z0-9]/.test(password))) {
    return toast('Passwort braucht mindestens 10 Zeichen, Groß-/Kleinbuchstaben, Zahl und Sonderzeichen');
  }

  const oldLabel = button?.textContent || 'Speichern';
  if (button) { button.disabled = true; button.textContent = 'Wird gespeichert …'; }
  try {
    const updated = await api('PUT', `/api/admin/users/${id}`, { name, role, active });
    if (password) await api('POST', `/api/admin/users/${id}/reset-password`, { password });
    adminUsersCache = adminUsersCache.map(entry => Number(entry.id) === id ? { ...entry, ...updated, has_password: password ? 1 : entry.has_password } : entry);
    toast(password ? 'Benutzer aktualisiert und Passwort zurückgesetzt.' : 'Benutzer aktualisiert.');
    document.getElementById('edit-user-modal').classList.add('hidden');
    await loadAdminUsers();
  } catch (err) { toast('Fehler: ' + err.message); }
  finally { if (button) { button.disabled = false; button.textContent = oldLabel; } }
}

async function confirmDeleteUser(userId, email) {
  if (!confirm(`User "${email}" wirklich löschen? Alle Sessions werden beendet.`)) return;
  try {
    await api('DELETE', `/api/admin/users/${userId}`);
    toast('User gelöscht');
    loadAdminUsers();
  } catch (err) { toast('Fehler: ' + err.message); }
}

async function loadAdminSessions() {
  try {
    const sessions = await api('GET', '/api/admin/sessions');
    const container = document.getElementById('sessions-list');
    if (!sessions.length) { container.innerHTML = '<div class="empty-state">Keine aktiven Sessions</div>'; return; }

    container.innerHTML = sessions.map(s => {
      const ua = parseUA(s.user_agent);
      return `<div class="session-row">
        <div class="session-info">
          <strong>${s.name || s.email}</strong> <span class="role-badge ${s.role}">${s.role}</span>
          <div class="session-ua">${ua}</div>
          <div class="session-ip">IP: ${s.ip || 'unbekannt'}</div>
        </div>
        <div class="session-meta">
          ${timeAgo(s.created_at)}<br>
          <small>Läuft ab: ${new Date(s.expires_at.endsWith('Z') ? s.expires_at : s.expires_at + 'Z').toLocaleDateString('de-DE')}</small>
        </div>
        <button class="small-btn" data-kill-session-id="${s.id}" style="color:var(--danger)" title="Session beenden">✕</button>
      </div>`;
    }).join('');
  } catch (err) { toast('Fehler: ' + err.message); }
}

async function killSession(sessionId) {
  try {
    await api('DELETE', `/api/admin/sessions/${sessionId}`);
    toast('Session beendet');
    loadAdminSessions();
  } catch (err) { toast('Fehler: ' + err.message); }
}

async function loadAdminAudit() {
  try {
    const params = buildAuditQuery();
    const result = await api('GET', `/api/admin/audit?${params.toString()}`);
    const logs = result.rows || [];
    const size = Number(result.limit || 100);
    auditState.total = Number(result.total || 0);
    auditState.pages = Math.max(1, Math.ceil(auditState.total / size));
    if (auditState.page > auditState.pages) auditState.page = auditState.pages;
    const container = document.getElementById('audit-list');
    document.getElementById('audit-summary').textContent = `${auditState.total.toLocaleString('de-DE')} protokollierte Ereignisse`;
    document.getElementById('audit-page-info').textContent = `Seite ${auditState.page} von ${auditState.pages}`;
    document.getElementById('audit-prev').disabled = auditState.page <= 1;
    document.getElementById('audit-next').disabled = auditState.page >= auditState.pages;
    if (!logs.length) { container.innerHTML = '<div class="empty-state">Keine passenden Audit-Einträge</div>'; return; }
    container.innerHTML = logs.map(l => {
      const actionParts = (l.action || '').split('.');
      const category = actionParts[0] || 'other';
      return `<div class="audit-row"><div class="audit-time">${new Date(String(l.created_at).replace(' ', 'T') + 'Z').toLocaleString('de-DE')}</div><div class="audit-user"><strong>${escapeHtml(l.user_email || 'System')}</strong><small>${escapeHtml(l.ip || '')}</small></div><div class="audit-action"><span class="audit-action-badge ${escapeHtml(category)}">${escapeHtml(l.action)}</span>${l.target_type ? `<small>${escapeHtml(l.target_type)} ${escapeHtml(l.target_id || '')}</small>` : ''}${l.details ? `<p>${escapeHtml(l.details)}</p>` : ''}</div></div>`;
    }).join('');
  } catch (err) { toast('Fehler: ' + err.message); }
}

// --- SMTP Settings ---
async function loadSmtpSettings() {
  try {
    const smtp = await api('GET', '/api/admin/smtp');
    document.getElementById('smtp-host').value = smtp.smtp_host || '';
    document.getElementById('smtp-port').value = smtp.smtp_port || '587';
    document.getElementById('smtp-user').value = smtp.smtp_user || '';
    document.getElementById('smtp-pass').value = '';
    document.getElementById('smtp-pass').placeholder = smtp.smtp_pass ? 'Gesetzt (leer = beibehalten)' : 'Passwort';
    document.getElementById('smtp-from').value = smtp.smtp_from || '';

    const dot = document.getElementById('smtp-status-dot');
    const text = document.getElementById('smtp-status-text');
    if (smtp.configured) {
      dot.className = 'smtp-status-dot configured';
      text.textContent = `SMTP konfiguriert (Quelle: ${smtp.source === 'db' ? 'Datenbank' : smtp.source === 'env' ? '.env Datei' : 'Nicht konfiguriert'})`;
    } else {
      dot.className = 'smtp-status-dot not-configured';
      text.textContent = 'SMTP nicht konfiguriert — E-Mails werden in der Konsole ausgegeben';
    }
  } catch (err) { toast('Fehler: ' + err.message); }
}

async function saveSmtpSettings() {
  const data = {
    smtp_host: document.getElementById('smtp-host').value.trim(),
    smtp_port: document.getElementById('smtp-port').value.trim(),
    smtp_user: document.getElementById('smtp-user').value.trim(),
    smtp_from: document.getElementById('smtp-from').value.trim(),
  };
  const pass = document.getElementById('smtp-pass').value;
  if (pass) data.smtp_pass = pass;

  try {
    const btn = document.getElementById('smtp-save-btn');
    btn.disabled = true; btn.textContent = 'Speichere...';
    await api('PUT', '/api/admin/smtp', data);
    toast('SMTP-Einstellungen gespeichert');
    loadSmtpSettings();
  } catch (err) { toast('Fehler: ' + err.message); }
  finally {
    const btn = document.getElementById('smtp-save-btn');
    btn.disabled = false; btn.textContent = 'SMTP speichern';
  }
}

async function testSmtpConnection() {
  const btn = document.getElementById('smtp-test-btn');
  btn.disabled = true; btn.textContent = 'Sende Testmail...';
  try {
    const res = await api('POST', '/api/admin/smtp/test');
    toast(res.message || 'Testmail gesendet!');
  } catch (err) { toast('Fehler: ' + err.message); }
  finally { btn.disabled = false; btn.textContent = 'Verbindung testen'; }
}


// --- Batch Image Factory ---
let imageFactoryPollTimer = null;
let imageFactoryEditingProfileId = null;

function imageFactoryStatusMeta(status) {
  return ({
    queued: ['Wartend', 'queued'], running: ['Läuft', 'running'], paused: ['Pausiert', 'paused'],
    completed: ['Fertig', 'completed'], partial: ['Teilweise fertig', 'partial'], failed: ['Fehlgeschlagen', 'failed'],
    cancelled: ['Abgebrochen', 'cancelled'],
  })[status] || [status || 'Unbekannt', 'queued'];
}

function updateImageFactorySelection() {
  const sources = state.imageFactorySelectedSources.size;
  const profiles = state.imageFactorySelectedProfiles.size;
  const outputs = sources * profiles;
  document.getElementById('image-factory-selection-count')?.replaceChildren(document.createTextNode(`${sources} Bilder · ${profiles} Profile`));
  document.getElementById('image-factory-output-estimate')?.replaceChildren(document.createTextNode(outputs ? `${outputs} neue Bilddateien werden erzeugt` : 'Noch keine Ausgabe geplant'));
  const submit = document.getElementById('image-factory-submit');
  if (submit) submit.disabled = !sources || !profiles;
}

function renderImageFactorySources() {
  const container = document.getElementById('image-factory-source-grid');
  if (!container) return;
  if (!state.imageFactorySources.length) {
    container.innerHTML = '<div class="empty-state">Keine passenden Bilder gefunden. Passe Suche oder Quellenfilter an.</div>';
    return;
  }
  container.innerHTML = state.imageFactorySources.map(item => {
    const selected = state.imageFactorySelectedSources.has(item.image_path);
    const title = item.title || item.prompt || item.image_path.split('/').pop();
    return `<label class="image-factory-source-card${selected ? ' is-selected' : ''}">
      <input type="checkbox" data-image-factory-source="${escapeHtml(item.image_path)}" ${selected ? 'checked' : ''}>
      <span class="image-factory-source-check">${window.VendooIcons?.svg('check', { size: 13 }) || '✓'}</span>
      <img src="${escapeHtml(item.public_url || photoUrl(item.image_path))}" alt="${escapeHtml(title)}" loading="lazy">
      <span class="image-factory-source-caption"><strong>${escapeHtml(title)}</strong><small>${escapeHtml(MEDIA_SOURCE_LABELS[item.source] || item.source || 'Bild')}</small></span>
    </label>`;
  }).join('');
  container.querySelectorAll('[data-image-factory-source]').forEach(input => input.addEventListener('change', () => {
    if (input.checked) state.imageFactorySelectedSources.add(input.dataset.imageFactorySource);
    else state.imageFactorySelectedSources.delete(input.dataset.imageFactorySource);
    input.closest('.image-factory-source-card')?.classList.toggle('is-selected', input.checked);
    updateImageFactorySelection();
  }));
}

function profileDimensionLabel(profile) {
  const resize = profile.operations?.resize || {};
  const dimensions = resize.width || resize.height ? `${resize.width || 'auto'} × ${resize.height || 'auto'}` : 'Originalgröße';
  return `${dimensions} · ${(profile.operations?.format || 'jpeg').toUpperCase()} · Q${profile.operations?.quality || 90}`;
}

function renderImageFactoryProfiles() {
  const container = document.getElementById('image-factory-profile-grid');
  if (!container) return;
  if (!state.imageFactoryProfiles.length) {
    container.innerHTML = '<div class="empty-state">Keine Produktionsprofile vorhanden.</div>';
    return;
  }
  container.innerHTML = state.imageFactoryProfiles.map(profile => {
    const selected = state.imageFactorySelectedProfiles.has(Number(profile.id));
    return `<article class="image-factory-profile-card${selected ? ' is-selected' : ''}" data-image-factory-profile-card="${profile.id}">
      <label class="image-factory-profile-select">
        <input type="checkbox" data-image-factory-profile="${profile.id}" ${selected ? 'checked' : ''}>
        <span>${window.VendooIcons?.svg('check', { size: 13 }) || '✓'}</span>
      </label>
      <div class="image-factory-profile-icon">${profile.is_builtin ? 'P' : 'E'}</div>
      <div class="image-factory-profile-copy"><strong>${escapeHtml(profile.name)}</strong><p>${escapeHtml(profile.description || 'Eigenes Produktionsprofil')}</p><small>${escapeHtml(profileDimensionLabel(profile))}</small></div>
      ${profile.is_builtin ? '<span class="image-factory-profile-badge">Standard</span>' : `<div class="image-factory-profile-actions"><button type="button" data-image-factory-profile-edit="${profile.id}" title="Profil bearbeiten">Bearbeiten</button><button type="button" data-image-factory-profile-delete="${profile.id}" title="Profil löschen">Löschen</button></div>`}
    </article>`;
  }).join('');
  container.querySelectorAll('[data-image-factory-profile]').forEach(input => input.addEventListener('change', () => {
    const id = Number(input.dataset.imageFactoryProfile);
    if (input.checked) state.imageFactorySelectedProfiles.add(id); else state.imageFactorySelectedProfiles.delete(id);
    input.closest('.image-factory-profile-card')?.classList.toggle('is-selected', input.checked);
    updateImageFactorySelection();
  }));
  container.querySelectorAll('[data-image-factory-profile-edit]').forEach(button => button.addEventListener('click', () => openImageFactoryProfileModal(Number(button.dataset.imageFactoryProfileEdit))));
  container.querySelectorAll('[data-image-factory-profile-delete]').forEach(button => button.addEventListener('click', async () => {
    const id = Number(button.dataset.imageFactoryProfileDelete);
    if (!confirm('Dieses eigene Produktionsprofil löschen? Bestehende Aufträge und Bilder bleiben erhalten.')) return;
    try { await api('DELETE', `/api/image-batch/profiles/${id}`); state.imageFactorySelectedProfiles.delete(id); await loadImageFactoryProfiles(); toast('Produktionsprofil gelöscht.'); }
    catch (error) { toast(`Profil konnte nicht gelöscht werden: ${error.message}`); }
  }));
}

async function loadImageFactorySources({ silent = false } = {}) {
  const container = document.getElementById('image-factory-source-grid');
  if (!silent && container) container.innerHTML = '<div class="empty-state">Bilder werden geladen …</div>';
  try {
    const params = new URLSearchParams({ page: '1', page_size: '96', source: document.getElementById('image-factory-source-filter')?.value || 'all', sort: 'newest' });
    const search = document.getElementById('image-factory-source-search')?.value.trim();
    if (search) params.set('q', search);
    const result = await api('GET', `/api/media-library?${params}`);
    state.imageFactorySources = result.items || [];
    renderImageFactorySources();
  } catch (error) {
    if (container) container.innerHTML = `<div class="empty-state">Bilder konnten nicht geladen werden: ${escapeHtml(error.message)}</div>`;
  }
}

async function loadImageFactoryProfiles() {
  try {
    const result = await api('GET', '/api/image-batch/profiles');
    state.imageFactoryProfiles = result.items || [];
    if (!state.imageFactorySelectedProfiles.size && state.imageFactoryProfiles.length) state.imageFactorySelectedProfiles.add(Number(state.imageFactoryProfiles[0].id));
    state.imageFactorySelectedProfiles = new Set([...state.imageFactorySelectedProfiles].filter(id => state.imageFactoryProfiles.some(profile => Number(profile.id) === Number(id))));
    renderImageFactoryProfiles();
    updateImageFactorySelection();
  } catch (error) {
    const container = document.getElementById('image-factory-profile-grid');
    if (container) container.innerHTML = `<div class="empty-state">Profile konnten nicht geladen werden: ${escapeHtml(error.message)}</div>`;
  }
}

function renderImageFactorySummary() {
  const summary = state.imageFactorySummary || {};
  document.querySelectorAll('[data-batch-summary]').forEach(node => { node.textContent = String(summary[node.dataset.batchSummary] || 0); });
}

function batchItemStatusLabel(status) {
  return ({ queued: 'Wartend', running: 'Wird erzeugt', completed: 'Fertig', failed: 'Fehler', cancelled: 'Abgebrochen' })[status] || status;
}

function renderImageFactoryJobItems(job) {
  if (!job.items) return '<div class="image-factory-job-loading">Details werden geladen …</div>';
  return `<div class="image-factory-job-items">${job.items.map(item => `<article class="image-factory-job-item is-${escapeHtml(item.status)}">
    <img src="${escapeHtml(photoUrl(item.output_path || item.source_path))}" alt="" loading="lazy">
    <div><strong>${escapeHtml(item.profile_name)}</strong><span>${escapeHtml(item.source_path.split('/').pop())}</span>${item.error_message ? `<small>${escapeHtml(item.error_message)}</small>` : ''}</div>
    <span class="image-factory-item-status">${escapeHtml(batchItemStatusLabel(item.status))}</span>
    ${['failed','cancelled'].includes(item.status) ? `<button type="button" class="vd-button compact" data-image-factory-retry-item="${escapeHtml(item.id)}" data-job-id="${escapeHtml(job.id)}">Erneut</button>` : ''}
    ${item.output_path ? `<a class="vd-button compact" href="${escapeHtml(photoUrl(item.output_path))}" download>Download</a>` : ''}
  </article>`).join('')}</div>`;
}

function renderImageFactoryJobs() {
  const container = document.getElementById('image-factory-jobs');
  if (!container) return;
  if (!state.imageFactoryJobs.length) {
    container.innerHTML = '<div class="empty-state">Noch keine Produktionsaufträge. Wähle Bilder und Profile und starte die erste Produktion.</div>';
  } else {
    container.innerHTML = state.imageFactoryJobs.map(job => {
      const [label, tone] = imageFactoryStatusMeta(job.status);
      const terminal = ['completed','partial','failed','cancelled'].includes(job.status);
      const expanded = state.imageFactoryExpanded.has(job.id);
      const done = Number(job.completed_items || 0) + Number(job.failed_items || 0) + Number(job.cancelled_items || 0);
      const total = Math.max(1, Number(job.total_items || 0));
      const percent = Math.min(100, Math.round(done / total * 100));
      return `<article class="image-factory-job is-${tone}${expanded ? ' is-expanded' : ''}" data-image-factory-job="${escapeHtml(job.id)}">
        <button type="button" class="image-factory-job-head" data-image-factory-job-toggle="${escapeHtml(job.id)}" aria-expanded="${expanded}">
          <span class="image-factory-job-chevron">›</span><span class="image-factory-job-title"><strong>${escapeHtml(job.name)}</strong><small>${job.source_count} Bilder · ${job.profile_count} Profile · ${formatFluxDate(job.created_at)}</small></span>
          <span class="image-factory-job-count">${job.completed_items || 0}/${job.total_items || 0}</span><span class="image-factory-job-status">${label}</span>
        </button>
        <div class="image-factory-job-progress"><span style="width:${percent}%"></span></div>
        <div class="image-factory-job-actions">
          ${['queued','running'].includes(job.status) ? `<button type="button" class="vd-button compact" data-image-factory-action="pause" data-job-id="${job.id}">Pausieren</button><button type="button" class="vd-button compact danger" data-image-factory-action="cancel" data-job-id="${job.id}">Abbrechen</button>` : ''}
          ${job.status === 'paused' ? `<button type="button" class="vd-button compact primary" data-image-factory-action="resume" data-job-id="${job.id}">Fortsetzen</button><button type="button" class="vd-button compact danger" data-image-factory-action="cancel" data-job-id="${job.id}">Abbrechen</button>` : ''}
          ${['failed','partial','cancelled'].includes(job.status) ? `<button type="button" class="vd-button compact" data-image-factory-action="retry" data-job-id="${job.id}">Fehler wiederholen</button>` : ''}
          ${Number(job.completed_items || 0) > 0 ? `<a class="vd-button compact" href="/api/image-batch/jobs/${encodeURIComponent(job.id)}/archive">Ergebnisse als ZIP</a>` : ''}
          ${terminal ? `<button type="button" class="vd-button compact danger" data-image-factory-action="delete" data-job-id="${job.id}">Auftrag löschen</button>` : ''}
        </div>
        <div class="image-factory-job-detail${expanded ? '' : ' hidden'}">${expanded ? renderImageFactoryJobItems(job) : ''}</div>
      </article>`;
    }).join('');
  }
  container.querySelectorAll('[data-image-factory-job-toggle]').forEach(button => button.addEventListener('click', async () => {
    const id = button.dataset.imageFactoryJobToggle;
    if (state.imageFactoryExpanded.has(id)) state.imageFactoryExpanded.delete(id); else state.imageFactoryExpanded.add(id);
    const index = state.imageFactoryJobs.findIndex(job => job.id === id);
    if (index >= 0 && state.imageFactoryExpanded.has(id) && !state.imageFactoryJobs[index].items) {
      try { state.imageFactoryJobs[index] = await api('GET', `/api/image-batch/jobs/${encodeURIComponent(id)}`); } catch (error) { toast(error.message); }
    }
    renderImageFactoryJobs();
  }));
  container.querySelectorAll('[data-image-factory-action]').forEach(button => button.addEventListener('click', () => handleImageFactoryJobAction(button.dataset.imageFactoryAction, button.dataset.jobId)));
  container.querySelectorAll('[data-image-factory-retry-item]').forEach(button => button.addEventListener('click', async () => {
    try { await api('POST', `/api/image-batch/jobs/${encodeURIComponent(button.dataset.jobId)}/items/${encodeURIComponent(button.dataset.imageFactoryRetryItem)}/retry`); toast('Bildposition wurde erneut eingereiht.'); await loadImageFactoryJobs({ silent: true }); }
    catch (error) { toast(`Wiederholung fehlgeschlagen: ${error.message}`); }
  }));
  const pagination = state.imageFactoryJobPagination || {};
  document.getElementById('image-factory-job-page-info')?.replaceChildren(document.createTextNode(`Seite ${pagination.page || 1} von ${pagination.total_pages || 1}`));
  const prev = document.getElementById('image-factory-job-prev'); const next = document.getElementById('image-factory-job-next');
  if (prev) prev.disabled = Number(pagination.page || 1) <= 1;
  if (next) next.disabled = Number(pagination.page || 1) >= Number(pagination.total_pages || 1);
}

async function loadImageFactoryJobs({ silent = false } = {}) {
  const container = document.getElementById('image-factory-jobs');
  if (!silent && container && !state.imageFactoryJobs.length) container.innerHTML = '<div class="empty-state">Produktionsaufträge werden geladen …</div>';
  try {
    const result = await api('GET', `/api/image-batch/jobs?page=${state.imageFactoryJobPage}&page_size=${state.imageFactoryJobPageSize}`);
    const previous = new Map(state.imageFactoryJobs.map(job => [job.id, job]));
    state.imageFactoryJobs = (result.items || []).map(job => state.imageFactoryExpanded.has(job.id) && previous.get(job.id)?.items ? { ...job, items: previous.get(job.id).items } : job);
    const expandedJobs = state.imageFactoryJobs.filter(job => state.imageFactoryExpanded.has(job.id));
    if (expandedJobs.length) {
      const details = await Promise.all(expandedJobs.map(job => api('GET', `/api/image-batch/jobs/${encodeURIComponent(job.id)}`).catch(() => null)));
      const detailMap = new Map(details.filter(Boolean).map(job => [job.id, job]));
      state.imageFactoryJobs = state.imageFactoryJobs.map(job => detailMap.get(job.id) || job);
    }
    state.imageFactoryJobPagination = result.pagination || { page: 1, total_pages: 1, total: 0 };
    state.imageFactoryJobPage = Number(state.imageFactoryJobPagination.page || 1);
    state.imageFactorySummary = result.summary || null;
    renderImageFactorySummary();
    renderImageFactoryJobs();
    const active = state.imageFactoryJobs.some(job => ['queued','running'].includes(job.status));
    if (imageFactoryPollTimer) clearTimeout(imageFactoryPollTimer);
    if (active && document.getElementById('image-factory')?.classList.contains('active')) imageFactoryPollTimer = setTimeout(() => loadImageFactoryJobs({ silent: true }), 1600);
  } catch (error) {
    if (container) container.innerHTML = `<div class="empty-state">Aufträge konnten nicht geladen werden: ${escapeHtml(error.message)}</div>`;
  }
}

async function loadImageFactory() {
  await Promise.all([loadImageFactoryProfiles(), loadImageFactorySources(), loadImageFactoryJobs()]);
}

async function submitImageFactoryJob() {
  const button = document.getElementById('image-factory-submit');
  const sourcePaths = [...state.imageFactorySelectedSources];
  const profileIds = [...state.imageFactorySelectedProfiles];
  if (!sourcePaths.length || !profileIds.length) return toast('Bitte mindestens ein Bild und ein Produktionsprofil auswählen.');
  const original = button?.textContent || 'Bildproduktion starten';
  if (button) { button.disabled = true; button.textContent = 'Auftrag wird angelegt …'; }
  const watermarkEnabled = document.getElementById('image-factory-watermark')?.checked;
  const overrides = {
    remove_background: document.getElementById('image-factory-remove-bg')?.checked || false,
    background_sensitivity: Number(document.getElementById('image-factory-bg-sensitivity')?.value || 45),
    watermark: watermarkEnabled ? {
      enabled: true,
      text: document.getElementById('image-factory-watermark-text')?.value || 'Vendoo',
      position: document.getElementById('image-factory-watermark-position')?.value || 'bottom-right',
      opacity: Number(document.getElementById('image-factory-watermark-opacity')?.value || 40),
    } : { enabled: false },
  };
  try {
    const job = await api('POST', '/api/image-batch/jobs', { name: document.getElementById('image-factory-name')?.value.trim(), source_paths: sourcePaths, profile_ids: profileIds, overrides });
    toast(`${job.total_items} Bildausgaben wurden in die Produktion eingereiht.`);
    state.imageFactoryJobPage = 1;
    state.imageFactoryExpanded.add(job.id);
    await loadImageFactoryJobs({ silent: true });
    document.getElementById('image-factory-jobs')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
  } catch (error) { toast(`Bildproduktion konnte nicht gestartet werden: ${error.message}`); }
  finally { if (button) { button.disabled = false; button.textContent = original; } updateImageFactorySelection(); }
}

async function handleImageFactoryJobAction(action, jobId) {
  if (action === 'delete' && !confirm('Diesen abgeschlossenen Auftrag aus dem Job-Center löschen? Die erzeugten Bilder bleiben erhalten.')) return;
  try {
    if (action === 'delete') await api('DELETE', `/api/image-batch/jobs/${encodeURIComponent(jobId)}`);
    else await api('POST', `/api/image-batch/jobs/${encodeURIComponent(jobId)}/${action}`);
    toast(({ pause: 'Auftrag wird pausiert.', resume: 'Auftrag wird fortgesetzt.', cancel: 'Auftrag wird abgebrochen.', retry: 'Fehler wurden erneut eingereiht.', delete: 'Auftrag gelöscht.' })[action] || 'Auftrag aktualisiert.');
    await loadImageFactoryJobs({ silent: true });
  } catch (error) { toast(`Aktion fehlgeschlagen: ${error.message}`); }
}

function openImageFactoryProfileModal(profileId = null) {
  imageFactoryEditingProfileId = profileId;
  const profile = state.imageFactoryProfiles.find(item => Number(item.id) === Number(profileId));
  const operations = profile?.operations || {};
  const resize = operations.resize || {};
  document.getElementById('image-factory-profile-title').textContent = profile ? 'Produktionsprofil bearbeiten' : 'Produktionsprofil anlegen';
  document.getElementById('image-factory-profile-name').value = profile?.name || '';
  document.getElementById('image-factory-profile-description').value = profile?.description || '';
  document.getElementById('image-factory-profile-width').value = resize.width || 1600;
  document.getElementById('image-factory-profile-height').value = resize.height || 1600;
  document.getElementById('image-factory-profile-fit').value = resize.fit || 'contain';
  document.getElementById('image-factory-profile-background').value = resize.background || '#ffffff';
  document.getElementById('image-factory-profile-format').value = operations.format || 'jpeg';
  document.getElementById('image-factory-profile-quality').value = operations.quality || 90;
  document.getElementById('image-factory-profile-pattern').value = profile?.filename_pattern || '{name}-{profile}-{index}';
  const modal = document.getElementById('image-factory-profile-modal');
  modal?.classList.remove('hidden'); modal?.setAttribute('aria-hidden', 'false');
}

function closeImageFactoryProfileModal() {
  const modal = document.getElementById('image-factory-profile-modal');
  modal?.classList.add('hidden'); modal?.setAttribute('aria-hidden', 'true'); imageFactoryEditingProfileId = null;
}

async function saveImageFactoryProfile() {
  const data = {
    name: document.getElementById('image-factory-profile-name')?.value.trim(),
    description: document.getElementById('image-factory-profile-description')?.value.trim(),
    filename_pattern: document.getElementById('image-factory-profile-pattern')?.value.trim(),
    operations: {
      resize: {
        width: Number(document.getElementById('image-factory-profile-width')?.value || 0),
        height: Number(document.getElementById('image-factory-profile-height')?.value || 0),
        fit: document.getElementById('image-factory-profile-fit')?.value || 'contain',
        background: document.getElementById('image-factory-profile-background')?.value || '#ffffff',
      },
      format: document.getElementById('image-factory-profile-format')?.value || 'jpeg',
      quality: Number(document.getElementById('image-factory-profile-quality')?.value || 90),
      sharpness: 12,
    },
  };
  if (!data.name) return toast('Bitte einen Namen für das Produktionsprofil eingeben.');
  const button = document.getElementById('image-factory-profile-save');
  if (button) button.disabled = true;
  try {
    const profile = imageFactoryEditingProfileId
      ? await api('PATCH', `/api/image-batch/profiles/${imageFactoryEditingProfileId}`, data)
      : await api('POST', '/api/image-batch/profiles', data);
    state.imageFactorySelectedProfiles.add(Number(profile.id));
    closeImageFactoryProfileModal(); await loadImageFactoryProfiles(); toast('Produktionsprofil gespeichert.');
  } catch (error) { toast(`Profil konnte nicht gespeichert werden: ${error.message}`); }
  finally { if (button) button.disabled = false; }
}

function setupImageFactory() {
  document.getElementById('image-factory-refresh')?.addEventListener('click', loadImageFactory);
  document.getElementById('image-factory-jobs-refresh')?.addEventListener('click', () => loadImageFactoryJobs());
  document.getElementById('image-factory-start')?.addEventListener('click', () => document.getElementById('image-factory-source-grid')?.scrollIntoView({ behavior: 'smooth', block: 'center' }));
  document.getElementById('image-factory-submit')?.addEventListener('click', submitImageFactoryJob);
  document.getElementById('image-factory-source-filter')?.addEventListener('change', () => loadImageFactorySources());
  document.getElementById('image-factory-source-search')?.addEventListener('input', debounce(() => loadImageFactorySources({ silent: true }), 350));
  document.getElementById('image-factory-select-visible')?.addEventListener('click', () => {
    const visible = state.imageFactorySources.map(item => item.image_path);
    const allSelected = visible.length && visible.every(path => state.imageFactorySelectedSources.has(path));
    visible.forEach(path => allSelected ? state.imageFactorySelectedSources.delete(path) : state.imageFactorySelectedSources.add(path));
    renderImageFactorySources(); updateImageFactorySelection();
  });
  document.getElementById('image-factory-clear-sources')?.addEventListener('click', () => { state.imageFactorySelectedSources.clear(); renderImageFactorySources(); updateImageFactorySelection(); });
  document.getElementById('image-factory-bg-sensitivity')?.addEventListener('input', event => { document.getElementById('image-factory-bg-value').textContent = event.target.value; });
  document.getElementById('image-factory-watermark-opacity')?.addEventListener('input', event => { document.getElementById('image-factory-watermark-value').textContent = `${event.target.value} %`; });
  document.getElementById('image-factory-new-profile')?.addEventListener('click', () => openImageFactoryProfileModal());
  document.getElementById('image-factory-profile-close')?.addEventListener('click', closeImageFactoryProfileModal);
  document.getElementById('image-factory-profile-cancel')?.addEventListener('click', closeImageFactoryProfileModal);
  document.getElementById('image-factory-profile-save')?.addEventListener('click', saveImageFactoryProfile);
  document.getElementById('image-factory-profile-modal')?.addEventListener('click', event => { if (event.target.id === 'image-factory-profile-modal') closeImageFactoryProfileModal(); });
  document.getElementById('image-factory-job-page-size')?.addEventListener('change', event => { state.imageFactoryJobPageSize = Number(event.target.value || 10); state.imageFactoryJobPage = 1; loadImageFactoryJobs(); });
  document.getElementById('image-factory-job-prev')?.addEventListener('click', () => { if (state.imageFactoryJobPage > 1) { state.imageFactoryJobPage -= 1; loadImageFactoryJobs(); } });
  document.getElementById('image-factory-job-next')?.addEventListener('click', () => { if (state.imageFactoryJobPage < Number(state.imageFactoryJobPagination.total_pages || 1)) { state.imageFactoryJobPage += 1; loadImageFactoryJobs(); } });
  document.getElementById('media-send-to-factory')?.addEventListener('click', () => {
    for (const imagePath of state.mediaSelected) state.imageFactorySelectedSources.add(imagePath);
    switchToTab('image-factory');
    toast(`${state.mediaSelected.size} Galeriebild(er) für die Bildproduktion übernommen.`);
  });
}


// --- Article Quality Center ---
function qualityTone(report) {
  if (report.blockers > 0) return 'blocked';
  if (report.ready) return 'ready';
  return 'review';
}

function qualityStatusLabel(report) {
  if (report.blockers > 0) return `${report.blockers} Blocker`;
  if (report.ready) return 'Bereit';
  return 'Prüfen';
}

function qualityIssueIcon(severity) {
  const names = { error: 'x', warning: 'triangle-alert', recommendation: 'sparkles' };
  return window.VendooIcons?.svg(names[severity] || 'circle-help', { size: 18, strokeWidth: 2 }) || (severity === 'error' ? '!' : '·');
}

function renderQualityList() {
  const container = document.getElementById('quality-list');
  if (!container) return;
  if (!state.qualityItems.length) {
    container.innerHTML = '<div class="empty-state"><p>Keine Artikel entsprechen den gewählten Filtern.</p></div>';
    return;
  }
  container.innerHTML = state.qualityItems.map(report => {
    const item = report.listing || {};
    const active = Number(state.qualityCurrent?.listing_id) === Number(report.listing_id);
    const tone = qualityTone(report);
    return `<button type="button" class="quality-list-item is-${tone}${active ? ' is-active' : ''}" data-quality-listing-id="${report.listing_id}">
      <span class="quality-list-thumb">${item.photo ? `<img src="${escapeHtml(photoUrl(item.photo))}" alt="">` : '<span>□</span>'}</span>
      <span class="quality-list-copy"><strong>${escapeHtml(item.title || 'Unbenannter Artikel')}</strong><small>${escapeHtml(item.sku || 'Ohne SKU')} · ${escapeHtml(platformLabel(item.platform))}</small><span>${report.issues?.[0] ? escapeHtml(report.issues[0].title) : 'Keine offenen Hinweise'}</span></span>
      <span class="quality-list-score is-${String(report.grade || 'E').toLowerCase()}"><b>${report.score}</b><em>${escapeHtml(report.grade || 'E')}</em></span>
      <span class="quality-list-state">${escapeHtml(qualityStatusLabel(report))}</span>
    </button>`;
  }).join('');
  container.querySelectorAll('[data-quality-listing-id]').forEach(button => button.addEventListener('click', () => loadQualityDetail(button.dataset.qualityListingId)));
}

function renderQualitySummary() {
  const summary = state.qualitySummary || {};
  const values = {
    'quality-summary-average': summary.average_score ?? 0,
    'quality-summary-ready': summary.ready ?? 0,
    'quality-summary-blocked': summary.blocked ?? 0,
    'quality-summary-total': summary.total ?? 0,
  };
  Object.entries(values).forEach(([id, value]) => { const el = document.getElementById(id); if (el) el.textContent = value; });
}

function renderQualityPagination() {
  const pagination = state.qualityPagination || {};
  const page = Number(pagination.page || 1); const totalPages = Number(pagination.total_pages || 1); const total = Number(pagination.total || 0);
  const start = total ? (page - 1) * Number(pagination.page_size || state.qualityPageSize) + 1 : 0;
  const end = Math.min(total, page * Number(pagination.page_size || state.qualityPageSize));
  const info = document.getElementById('quality-page-info'); if (info) info.textContent = `Seite ${page} von ${totalPages}`;
  const range = document.getElementById('quality-list-range'); if (range) range.textContent = total ? `${start}–${end} von ${total}` : '0 Artikel';
  const prev = document.getElementById('quality-prev'); const next = document.getElementById('quality-next');
  if (prev) prev.disabled = page <= 1; if (next) next.disabled = page >= totalPages;
}

async function loadQualityCenter({ silent = false } = {}) {
  const container = document.getElementById('quality-list');
  if (!silent && container) container.innerHTML = '<div class="quality-loading"><span></span><span></span><span></span><p>Artikelqualität wird geladen …</p></div>';
  const params = new URLSearchParams({
    q: document.getElementById('quality-search')?.value.trim() || '',
    status: document.getElementById('quality-status')?.value || 'all',
    grade: document.getElementById('quality-grade')?.value || 'all',
    page: String(state.qualityPage),
    page_size: String(state.qualityPageSize),
  });
  try {
    const result = await api('GET', `/api/quality-center?${params}`);
    state.qualityItems = result.items || [];
    state.qualityPagination = result.pagination || { page: 1, total_pages: 1, total: 0 };
    state.qualitySummary = result.summary || {};
    renderQualitySummary(); renderQualityPagination(); renderQualityList();
    if (!state.qualityCurrent && state.qualityItems[0]) loadQualityDetail(state.qualityItems[0].listing_id, { quiet: true });
  } catch (error) {
    if (container) container.innerHTML = `<div class="empty-state"><p>Quality Center konnte nicht geladen werden: ${escapeHtml(error.message)}</p></div>`;
  }
}

function renderQualityIssues(report) {
  const container = document.getElementById('quality-issues');
  if (!container) return;
  const filter = state.qualityIssueFilter;
  const issues = (report.issues || []).filter(item => filter === 'all' || item.severity === filter);
  if (!issues.length) {
    container.innerHTML = '<div class="quality-all-clear"><strong>Keine Punkte in diesem Bereich</strong><span>Der Artikel erfüllt die aktuell geprüften Qualitätsregeln.</span></div>';
    return;
  }
  container.innerHTML = issues.map(item => `<article class="quality-issue is-${escapeHtml(item.severity)}">
    <span class="quality-issue-icon">${qualityIssueIcon(item.severity)}</span>
    <div><strong>${escapeHtml(item.title)}</strong><p>${escapeHtml(item.detail || '')}</p><small>${escapeHtml(item.category || '')}${item.field ? ` · Feld: ${escapeHtml(item.field)}` : ''}</small></div>
    <span class="quality-issue-points">−${Number(item.points || 0)}</span>
  </article>`).join('');
}

function renderQualityDetail(report) {
  state.qualityCurrent = report;
  document.getElementById('quality-detail-empty')?.classList.add('hidden');
  document.getElementById('quality-detail-content')?.classList.remove('hidden');
  const title = document.getElementById('quality-detail-title'); if (title) title.textContent = report.listing?.title || 'Unbenannter Artikel';
  const platform = document.getElementById('quality-detail-platform'); if (platform) platform.textContent = `${report.platform?.name || platformLabel(report.listing?.platform)} · Note ${report.grade}`;
  const meta = document.getElementById('quality-detail-meta'); if (meta) meta.textContent = `${report.listing?.sku || 'Ohne SKU'} · ${report.facts?.photo_count || 0} Bilder · ${report.facts?.title_length || 0} Titelzeichen`;
  const score = document.getElementById('quality-detail-score'); if (score) score.textContent = report.score;
  const ring = document.getElementById('quality-score-ring'); if (ring) { ring.dataset.grade = report.grade || 'E'; ring.style.setProperty('--quality-score', `${report.score * 3.6}deg`); }
  const readiness = document.getElementById('quality-readiness');
  if (readiness) readiness.innerHTML = `<strong>${report.ready ? 'Bereit zur Veröffentlichung' : report.blockers ? 'Veröffentlichung blockiert' : 'Überarbeitung empfohlen'}</strong><span>${report.blockers || 0} Blocker · ${report.warnings || 0} Warnungen · ${report.recommendations || 0} Empfehlungen</span>`;
  const finance = report.financials || {};
  const financials = document.getElementById('quality-financials');
  if (financials) financials.innerHTML = `<div><span>Preis</span><strong>${formatCurrency(finance.price || report.listing?.price || 0)}</strong></div><div><span>Gebühren</span><strong>${formatCurrency(finance.fees || 0)}</strong></div><div><span>Geschätzter Erlös</span><strong>${formatCurrency(finance.estimated_net || 0)}</strong></div>`;
  renderQualityIssues(report);
  const duplicates = document.getElementById('quality-duplicates');
  if (duplicates) duplicates.innerHTML = report.duplicates?.length ? report.duplicates.map(item => `<button type="button" data-quality-duplicate-id="${item.id}"><strong>${escapeHtml(item.title || 'Artikel')}</strong><span>${escapeHtml(item.sku || 'Ohne SKU')} · ${item.same_sku ? 'gleiche SKU' : `${item.similarity}% ähnlich`}</span></button>`).join('') : '<p class="quality-empty-note">Keine auffälligen Duplikate gefunden.</p>';
  duplicates?.querySelectorAll('[data-quality-duplicate-id]').forEach(button => button.addEventListener('click', () => showDetail(button.dataset.qualityDuplicateId)));
  const images = document.getElementById('quality-images');
  if (images) images.innerHTML = report.images?.length ? report.images.map(item => `<article class="quality-image-check"><span>${item.missing || item.unreadable ? '!' : '✓'}</span><div><strong>${escapeHtml(item.path || 'Bild')}</strong><small>${item.width ? `${item.width} × ${item.height} px · ${item.megapixels} MP · ${escapeHtml(item.format || '')}` : item.missing ? 'Datei fehlt' : 'Nicht lesbar'}</small></div></article>`).join('') : '<p class="quality-empty-note">Für eine vollständige Bildprüfung bitte „Neu analysieren“ verwenden.</p>';
  const history = document.getElementById('quality-history');
  if (history) history.innerHTML = report.history?.length ? report.history.map(item => `<div class="quality-history-item"><span>${new Date(String(item.analyzed_at || '').replace(' ', 'T') + 'Z').toLocaleString('de-DE')}</span><strong>${item.score}</strong><em>Note ${item.grade}</em></div>`).join('') : '<p class="quality-empty-note">Noch kein gespeicherter Verlauf.</p>';
  const delta = document.getElementById('quality-score-delta');
  if (delta) { const value = Number(report.comparison?.delta || 0); delta.textContent = value ? `${value > 0 ? '+' : ''}${value} seit letzter Prüfung` : 'Noch kein Vergleich'; delta.dataset.tone = value > 0 ? 'up' : value < 0 ? 'down' : 'neutral'; }
  renderQualityList();
}

async function loadQualityDetail(listingId, { quiet = false } = {}) {
  const panel = document.getElementById('quality-detail-content');
  if (!quiet && panel) panel.classList.add('is-loading');
  try {
    const report = await api('GET', `/api/quality-center/${listingId}`, null, { timeoutMs: 20000 });
    renderQualityDetail(report);
  } catch (error) { toast(`Qualitätsprüfung fehlgeschlagen: ${error.message}`); }
  finally { panel?.classList.remove('is-loading'); }
}

async function runQualityAnalysis(listingId = state.qualityCurrent?.listing_id) {
  if (!listingId) return;
  const button = document.getElementById('quality-run-analysis'); const original = button?.textContent;
  if (button) { button.disabled = true; button.textContent = 'Analyse läuft …'; }
  try {
    const report = await api('POST', `/api/quality-center/${listingId}/analyze`, {}, { timeoutMs: 30000 });
    renderQualityDetail(report); await loadQualityCenter({ silent: true }); toast(`Qualitätsanalyse abgeschlossen: ${report.score}/100`);
  } catch (error) { toast(`Analyse fehlgeschlagen: ${error.message}`); }
  finally { if (button) { button.disabled = false; button.textContent = original || 'Neu analysieren'; } }
}

async function analyzeVisibleQualityItems() {
  const ids = state.qualityItems.map(item => item.listing_id).slice(0, 25);
  if (!ids.length) return toast('Keine sichtbaren Artikel vorhanden.');
  const button = document.getElementById('quality-analyze-visible'); const original = button?.textContent;
  if (button) button.disabled = true;
  let done = 0; let failed = 0;
  for (const id of ids) {
    if (button) button.textContent = `${done + 1} / ${ids.length} prüfen`;
    try { await api('POST', `/api/quality-center/${id}/analyze`, {}, { timeoutMs: 30000 }); done += 1; } catch { failed += 1; }
  }
  if (button) { button.disabled = false; button.textContent = original || 'Sichtbare prüfen'; }
  await loadQualityCenter({ silent: true });
  if (state.qualityCurrent?.listing_id) await loadQualityDetail(state.qualityCurrent.listing_id, { quiet: true });
  toast(`${done} Artikel geprüft${failed ? ` · ${failed} fehlgeschlagen` : ''}.`);
}

function openQualityAiModal() {
  if (!state.qualityCurrent?.listing_id) return;
  state.qualityAiDraft = null;
  const modal = document.getElementById('quality-ai-modal'); modal?.classList.remove('hidden');
  document.getElementById('quality-ai-compare')?.classList.add('hidden');
  document.getElementById('quality-ai-apply')?.classList.add('hidden');
  const status = document.getElementById('quality-ai-status'); if (status) status.textContent = 'Wähle die Felder und erstelle einen Vorschlag.';
  const reasons = document.getElementById('quality-ai-reasons'); if (reasons) reasons.innerHTML = '';
}

function closeQualityAiModal() { document.getElementById('quality-ai-modal')?.classList.add('hidden'); state.qualityAiDraft = null; }

async function generateQualityAiSuggestion() {
  const fields = [['title','quality-ai-field-title'],['description','quality-ai-field-description'],['tags','quality-ai-field-tags']].filter(([, id]) => document.getElementById(id)?.checked).map(([field]) => field);
  if (!fields.length) return toast('Bitte mindestens ein Feld auswählen.');
  const button = document.getElementById('quality-ai-generate'); const status = document.getElementById('quality-ai-status');
  if (button) button.disabled = true; if (status) status.textContent = 'Der aktive AI-Provider erstellt sichere Vorschläge …';
  try {
    const result = await api('POST', `/api/quality-center/${state.qualityCurrent.listing_id}/ai-suggestions`, { fields, mode: document.getElementById('quality-ai-mode')?.value || 'balanced' }, { timeoutMs: 60000 });
    state.qualityAiDraft = result;
    const original = result.original || {}; const suggestions = result.suggestions || {};
    document.getElementById('quality-ai-original-title').value = original.title || '';
    document.getElementById('quality-ai-original-description').value = original.description || '';
    document.getElementById('quality-ai-original-tags').value = (original.tags || []).join(', ');
    document.getElementById('quality-ai-suggested-title').value = suggestions.title ?? original.title ?? '';
    document.getElementById('quality-ai-suggested-description').value = suggestions.description ?? original.description ?? '';
    document.getElementById('quality-ai-suggested-tags').value = (suggestions.tags ?? original.tags ?? []).join(', ');
    document.getElementById('quality-ai-compare')?.classList.remove('hidden'); document.getElementById('quality-ai-apply')?.classList.remove('hidden');
    const reasons = document.getElementById('quality-ai-reasons'); if (reasons) reasons.innerHTML = (suggestions.reasons || []).map(reason => `<span>${escapeHtml(reason)}</span>`).join('');
    if (status) status.textContent = `Vorschlag über ${result.provider} erstellt. Prüfe und bearbeite ihn vor dem Übernehmen.`;
  } catch (error) { if (status) status.textContent = `AI-Vorschlag fehlgeschlagen: ${error.message}`; }
  finally { if (button) button.disabled = false; }
}

async function applyQualityAiSuggestion() {
  if (!state.qualityAiDraft || !state.qualityCurrent?.listing_id) return;
  const payload = {};
  if (document.getElementById('quality-ai-field-title')?.checked) payload.title = document.getElementById('quality-ai-suggested-title').value.trim();
  if (document.getElementById('quality-ai-field-description')?.checked) payload.description = document.getElementById('quality-ai-suggested-description').value.trim();
  if (document.getElementById('quality-ai-field-tags')?.checked) payload.tags = document.getElementById('quality-ai-suggested-tags').value.split(',').map(value => value.trim()).filter(Boolean);
  const button = document.getElementById('quality-ai-apply'); if (button) button.disabled = true;
  try {
    const result = await api('POST', `/api/quality-center/${state.qualityCurrent.listing_id}/apply-suggestions`, payload, { timeoutMs: 30000 });
    closeQualityAiModal(); renderQualityDetail(result.report); await loadQualityCenter({ silent: true }); toast('Bestätigte Verbesserungen wurden gespeichert.');
  } catch (error) { toast(`Verbesserungen konnten nicht gespeichert werden: ${error.message}`); }
  finally { if (button) button.disabled = false; }
}

function setupQualityCenter() {
  document.getElementById('quality-refresh')?.addEventListener('click', () => loadQualityCenter());
  document.getElementById('quality-analyze-visible')?.addEventListener('click', analyzeVisibleQualityItems);
  document.getElementById('quality-search')?.addEventListener('input', debounce(() => { state.qualityPage = 1; loadQualityCenter({ silent: true }); }, 300));
  document.getElementById('quality-status')?.addEventListener('change', () => { state.qualityPage = 1; loadQualityCenter(); });
  document.getElementById('quality-grade')?.addEventListener('change', () => { state.qualityPage = 1; loadQualityCenter(); });
  document.getElementById('quality-page-size')?.addEventListener('change', event => { state.qualityPageSize = Number(event.target.value || 10); state.qualityPage = 1; loadQualityCenter(); });
  document.getElementById('quality-prev')?.addEventListener('click', () => { if (state.qualityPage > 1) { state.qualityPage -= 1; loadQualityCenter(); } });
  document.getElementById('quality-next')?.addEventListener('click', () => { if (state.qualityPage < Number(state.qualityPagination.total_pages || 1)) { state.qualityPage += 1; loadQualityCenter(); } });
  document.getElementById('quality-run-analysis')?.addEventListener('click', () => runQualityAnalysis());
  document.getElementById('quality-open-article')?.addEventListener('click', () => state.qualityCurrent?.listing_id && showDetail(state.qualityCurrent.listing_id));
  document.getElementById('quality-open-ai')?.addEventListener('click', openQualityAiModal);
  document.querySelectorAll('[data-quality-issue-filter]').forEach(button => button.addEventListener('click', () => {
    state.qualityIssueFilter = button.dataset.qualityIssueFilter || 'all';
    document.querySelectorAll('[data-quality-issue-filter]').forEach(item => item.classList.toggle('active', item === button));
    if (state.qualityCurrent) renderQualityIssues(state.qualityCurrent);
  }));
  document.getElementById('quality-ai-close')?.addEventListener('click', closeQualityAiModal);
  document.getElementById('quality-ai-cancel')?.addEventListener('click', closeQualityAiModal);
  document.getElementById('quality-ai-generate')?.addEventListener('click', generateQualityAiSuggestion);
  document.getElementById('quality-ai-apply')?.addEventListener('click', applyQualityAiSuggestion);
  document.getElementById('quality-ai-modal')?.addEventListener('click', event => { if (event.target.id === 'quality-ai-modal') closeQualityAiModal(); });
}

// --- System Diagnostics ---
function diagnosticTone(status) {
  return status === 'ok' ? 'ok' : status === 'error' ? 'error' : status === 'warning' ? 'warning' : 'optional';
}

function diagnosticIcon(status) {
  const name = status === 'ok' ? 'shield-check' : status === 'error' ? 'x' : 'circle-help';
  return window.VendooIcons?.svg(name, { size: 18, strokeWidth: 2 }) || '';
}

function renderSystemDiagnostics(report) {
  const summary = document.getElementById('system-diagnostics-summary');
  const grid = document.getElementById('system-diagnostics-grid');
  const copy = document.getElementById('system-diagnostics-copy');
  if (!summary || !grid) return;
  const errors = report.checks?.filter(item => item.status === 'error').length || 0;
  const warnings = report.checks?.filter(item => ['warning', 'optional'].includes(item.status)).length || 0;
  summary.className = `diagnostics-summary ${errors ? 'is-error' : warnings ? 'is-warning' : 'is-ok'}`;
  summary.innerHTML = `<strong>${errors ? `${errors} kritische Prüfung(en) fehlgeschlagen` : 'Vendoo-Kernsystem ist betriebsbereit'}</strong><span>${warnings ? `${warnings} Hinweis(e) · ` : ''}${report.checks?.length || 0} Prüfungen in ${report.duration_ms || 0} ms · Build ${escapeHtml(report.version || VENDOO_UI_BUILD)}</span>`;
  grid.innerHTML = (report.checks || []).map(check => `
    <article class="diagnostic-card is-${diagnosticTone(check.status)}">
      <span class="diagnostic-card-icon">${diagnosticIcon(check.status)}</span>
      <div><strong>${escapeHtml(check.label)}</strong><p>${escapeHtml(check.detail || '')}</p></div>
      <span class="diagnostic-card-status">${check.status === 'ok' ? 'OK' : check.status === 'error' ? 'Fehler' : check.status === 'warning' ? 'Hinweis' : 'Optional'}</span>
    </article>`).join('');
  if (copy) copy.disabled = false;
}

async function runSystemDiagnostics() {
  const button = document.getElementById('system-diagnostics-btn');
  const summary = document.getElementById('system-diagnostics-summary');
  const grid = document.getElementById('system-diagnostics-grid');
  const original = button?.textContent || 'Systemprüfung starten';
  if (button) { button.disabled = true; button.textContent = 'Prüfung läuft …'; }
  if (summary) { summary.className = 'diagnostics-summary is-running'; summary.textContent = 'Datenbank, Dateisystem, UI, Extensions und optionale Dienste werden geprüft …'; }
  if (grid) grid.innerHTML = '<div class="diagnostics-loading"><span></span><span></span><span></span></div>';
  try {
    const report = await api('GET', '/api/admin/diagnostics', null, { retry: false, timeoutMs: 15000 });
    clientDiagnostics.lastSystemReport = report;
    renderSystemDiagnostics(report);
    toast(report.ok ? 'Systemprüfung erfolgreich.' : 'Systemprüfung hat kritische Fehler gefunden.');
  } catch (error) {
    if (summary) { summary.className = 'diagnostics-summary is-error'; summary.textContent = `Systemprüfung fehlgeschlagen: ${error.message}`; }
    if (grid) grid.innerHTML = '';
    toast(`Systemprüfung fehlgeschlagen: ${error.message}`);
  } finally {
    if (button) { button.disabled = false; button.textContent = original; }
  }
}

async function copySystemDiagnostics() {
  const report = clientDiagnostics.lastSystemReport;
  if (!report) return toast('Bitte zuerst eine Systemprüfung starten.');
  const text = [
    `Vendoo Diagnose ${report.version || VENDOO_UI_BUILD}`,
    `Erstellt: ${report.generated_at || new Date().toISOString()}`,
    `Runtime: ${report.runtime?.node || '?'} · ${report.runtime?.platform || '?'} ${report.runtime?.arch || ''}`,
    '',
    ...(report.checks || []).map(check => `[${String(check.status).toUpperCase()}] ${check.label}: ${check.detail}`),
    '',
    `Client-Fehler (letzte ${clientDiagnostics.incidents.length}):`,
    ...clientDiagnostics.incidents.slice(0, 10).map(item => `${item.at} · ${item.type} · ${item.view} · ${item.message} · ${item.requestId || ''}`),
  ].join('\n');
  try {
    await navigator.clipboard.writeText(text);
    toast('Diagnose in die Zwischenablage kopiert.');
  } catch {
    const area = document.createElement('textarea');
    area.value = text; document.body.append(area); area.select(); document.execCommand('copy'); area.remove();
    toast('Diagnose kopiert.');
  }
}

// --- System Info ---
async function loadSystemInfo() {
  try {
    const sys = await api('GET', '/api/admin/system');
    document.getElementById('system-info-cards').innerHTML = `
      <div class="system-info-card">
        <div class="sys-label">Server Uptime</div>
        <div class="sys-value">${sys.uptime}</div>
      </div>
      <div class="system-info-card">
        <div class="sys-label">Benutzer</div>
        <div class="sys-value accent">${sys.total_users}</div>
      </div>
      <div class="system-info-card">
        <div class="sys-label">Artikel</div>
        <div class="sys-value accent">${sys.total_listings}</div>
      </div>
      <div class="system-info-card">
        <div class="sys-label">Aktive Sessions</div>
        <div class="sys-value">${sys.active_sessions}</div>
      </div>
      <div class="system-info-card">
        <div class="sys-label">Datenbankgröße</div>
        <div class="sys-value">${sys.db_size}</div>
      </div>
      <div class="system-info-card">
        <div class="sys-label">Node.js</div>
        <div class="sys-value" style="font-size:16px">${sys.node_version}</div>
      </div>
      <div class="system-info-card">
        <div class="sys-label">Speicher (RAM)</div>
        <div class="sys-value">${sys.memory_mb} MB</div>
      </div>
      <div class="system-info-card">
        <div class="sys-label">Plattform</div>
        <div class="sys-value" style="font-size:16px">${sys.platform}</div>
      </div>
    `;
  } catch (err) { toast('Fehler: ' + err.message); }
}

async function loadMySessions() {
  try {
    const sessions = await api('GET', '/api/my/sessions');
    const container = document.getElementById('my-sessions-list');
    container.innerHTML = sessions.length ? sessions.map(s => {
      const ua = parseUA(s.user_agent);
      return `<div class="session-row">
        <div class="session-info">
          <div class="session-ua">${ua}</div>
          <div class="session-ip">IP: ${s.ip || 'unbekannt'}</div>
        </div>
        <div class="session-meta">${timeAgo(s.created_at)}</div>
        <button class="small-btn" data-end-my-session-id="${s.id}" style="color:var(--danger)">Beenden</button>
      </div>`;
    }).join('') : '<div class="empty-state">Keine aktiven Sessions</div>';
    document.getElementById('my-sessions-modal').classList.remove('hidden');
  } catch (err) { toast('Fehler: ' + err.message); }
}

async function endMySession(sessionId) {
  try {
    await api('DELETE', `/api/my/sessions/${sessionId}`);
    toast('Session beendet');
    loadMySessions();
  } catch (err) { toast('Fehler: ' + err.message); }
}

function parseUA(ua) {
  if (!ua) return 'Unbekannt';
  if (ua.includes('Chrome')) return 'Chrome';
  if (ua.includes('Firefox')) return 'Firefox';
  if (ua.includes('Safari')) return 'Safari';
  if (ua.includes('Edge')) return 'Edge';
  return ua.substring(0, 40);
}

init().catch(error => {
  recordClientIncident('startup', error?.message || String(error));
  setConnectionState(false, `Vendoo konnte nicht vollständig gestartet werden: ${error?.message || 'Unbekannter Fehler'}`);
  console.error('[Vendoo] Initialisierung fehlgeschlagen', error);
});

// --- Vendoo 1.29.0 Publishing Hardening UI ---
publishState.jobsData = { items: [], pagination: { page: 1, page_size: 10, total: 0, total_pages: 1 }, summary: {} };
publishState.jobPage = 1;
publishState.jobPageSize = 10;
publishState.jobStatus = '';
publishState.jobPlatform = '';
publishState.jobSearch = '';
publishState.jobSelected = new Set();
publishState.publishEvents = [];
publishState.capabilities = null;

async function loadPublishingQueueData({ render = true } = {}) {
  const params = new URLSearchParams({ page: String(publishState.jobPage), page_size: String(publishState.jobPageSize) });
  if (publishState.jobStatus) params.set('status', publishState.jobStatus);
  if (publishState.jobPlatform) params.set('platform', publishState.jobPlatform);
  if (publishState.jobSearch) params.set('q', publishState.jobSearch);
  const [jobs, events, capabilities] = await Promise.all([
    publishSafeGet(`/api/publishing/jobs?${params}`, { items: [], pagination: { page: 1, page_size: publishState.jobPageSize, total: 0, total_pages: 1 }, summary: {} }, 2),
    publishSafeGet('/api/publishing/events?limit=120', [], 2),
    publishSafeGet('/api/publishing/capabilities', null, 2),
  ]);
  publishState.jobsData = jobs;
  publishState.publishEvents = Array.isArray(events) ? events : [];
  publishState.capabilities = capabilities;
  const visible = new Set((jobs.items || []).map(item => item.id));
  publishState.jobSelected = new Set([...publishState.jobSelected].filter(id => visible.has(id)));
  if (render) {
    renderPublishWorkspace();
    renderPublishStatusRail();
  }
}

async function loadPublishList() {
  await loadPublishListLegacy();
  try { await loadPublishingQueueData(); }
  catch (error) { console.warn('[Vendoo] Publishing-Queue konnte nicht geladen werden', error); }
}

function setPublishView(view) {
  publishState.view = ['smart', 'direct', 'queue', 'errors', 'scheduled'].includes(view) ? view : 'smart';
  document.querySelectorAll('[data-publish-view]').forEach(button => {
    const active = button.dataset.publishView === publishState.view;
    button.classList.toggle('active', active);
    button.setAttribute('aria-selected', String(active));
  });
  renderPublishWorkspace();
}

function renderPublishWorkspace() {
  if (publishState.view === 'queue') { renderQueueWorkspace(); return; }
  if (publishState.view === 'errors') { renderPublishingErrorsWorkspace(); return; }
  if (publishState.view === 'scheduled') { renderScheduledWorkspace(); return; }
  const listing = selectedPublishListing();
  const workspace = document.getElementById('publish-workspace');
  if (!workspace) return;
  if (!listing) {
    workspace.innerHTML = `<div class="publish-workspace-empty"><span>↗</span><h3>Artikel auswählen</h3><p>Wähle links einen Artikel, um ihn für die Veröffentlichung vorzubereiten.</p></div>`;
    return;
  }
  if (publishState.view === 'direct') renderDirectPublishWorkspace(listing);
  else renderSmartCopyLauncher(listing);
}

function publishingStatusLabel(status) {
  return ({ queued: 'Wartend', running: 'In Bearbeitung', awaiting_user: 'Wartet auf Extension', retry_wait: 'Wiederholung geplant', published: 'Veröffentlicht', drafted: 'Entwurf erstellt', failed: 'Fehlgeschlagen', cancelled: 'Abgebrochen', blocked: 'Blockiert', ended: 'Beendet' })[status] || status || 'Unbekannt';
}
function publishingActionLabel(action) {
  return ({ publish: 'Veröffentlichen', update: 'Aktualisieren', end: 'Beenden', sync: 'Status abgleichen' })[action] || action;
}
function publishingModeLabel(mode) { return mode === 'api' ? 'Offizielle API' : 'Browser-Extension'; }
function publishingCan(job, action) {
  return Boolean(publishState.capabilities?.platforms?.[job.platform]?.[action]);
}
function publishingDate(value) {
  if (!value) return '—';
  const date = new Date(String(value).replace(' ', 'T') + (String(value).includes('Z') ? '' : 'Z'));
  return Number.isNaN(date.getTime()) ? value : date.toLocaleString('de-DE', { dateStyle: 'short', timeStyle: 'short' });
}

function renderQueueWorkspace() {
  const workspace = document.getElementById('publish-workspace');
  const data = publishState.jobsData || { items: [], pagination: {}, summary: {} };
  const items = data.items || [];
  const page = data.pagination?.page || 1;
  const pages = data.pagination?.total_pages || 1;
  const total = data.pagination?.total || 0;
  const selectedCount = publishState.jobSelected.size;
  const platformOptions = ['', 'ebay-de', 'etsy', 'vinted', 'ebay-ka'].map(value => `<option value="${value}" ${publishState.jobPlatform === value ? 'selected' : ''}>${value ? escapeHtml(platformLabel(value)) : 'Alle Plattformen'}</option>`).join('');
  const statusOptions = ['', 'queued', 'running', 'awaiting_user', 'retry_wait', 'published', 'drafted', 'failed', 'cancelled', 'ended'].map(value => `<option value="${value}" ${publishState.jobStatus === value ? 'selected' : ''}>${value ? escapeHtml(publishingStatusLabel(value)) : 'Alle Status'}</option>`).join('');

  workspace.innerHTML = `<header class="publish-operation-header publishing-hardened-header">
      <div><span class="publish-panel-kicker">Zuverlässige Verarbeitung</span><h3>Publishing-Queue</h3><p>${total} Aufträge · Schutz vor Doppelveröffentlichung · automatische Wiederholungen</p></div>
      <div><button class="vd-button" type="button" data-publishing-refresh>Aktualisieren</button><button class="vd-button primary" type="button" data-publishing-add ${publishState.selected.size || publishState.selectedId ? '' : 'disabled'}>Auswahl einreihen</button></div>
    </header>
    <div class="publishing-toolbar">
      <label class="publish-search-field"><span aria-hidden="true">⌕</span><input type="search" data-publishing-search value="${escapeHtml(publishState.jobSearch)}" placeholder="Titel, SKU, externe ID oder Fehler …"></label>
      <select data-publishing-platform>${platformOptions}</select>
      <select data-publishing-status>${statusOptions}</select>
      <select data-publishing-size><option value="5" ${publishState.jobPageSize === 5 ? 'selected' : ''}>5 / Seite</option><option value="10" ${publishState.jobPageSize === 10 ? 'selected' : ''}>10 / Seite</option><option value="15" ${publishState.jobPageSize === 15 ? 'selected' : ''}>15 / Seite</option></select>
    </div>
    <div class="publishing-selection-bar"><label><input type="checkbox" data-publishing-select-page ${items.length && items.every(item => publishState.jobSelected.has(item.id)) ? 'checked' : ''}> Seite auswählen</label><span>${selectedCount} ausgewählt</span><button class="vd-button compact danger" type="button" data-publishing-delete-selected ${selectedCount ? '' : 'disabled'}>Ausgewählte löschen</button></div>
    <div class="publish-operation-list publishing-job-list">${items.length ? items.map(renderPublishingJobRow).join('') : '<div class="publish-workspace-empty compact"><span>✓</span><h3>Keine Aufträge</h3><p>Wähle Artikel aus und füge sie zur Publishing-Queue hinzu.</p></div>'}</div>
    <footer class="publishing-pagination"><span>Seite ${page} von ${pages}</span><div><button class="vd-button compact" data-publishing-prev ${page <= 1 ? 'disabled' : ''}>Zurück</button><button class="vd-button compact" data-publishing-next ${page >= pages ? 'disabled' : ''}>Weiter</button></div></footer>`;

  workspace.querySelector('[data-publishing-refresh]')?.addEventListener('click', () => loadPublishingQueueData());
  workspace.querySelector('[data-publishing-add]')?.addEventListener('click', batchEbayAdd);
  workspace.querySelector('[data-publishing-search]')?.addEventListener('input', debounce(event => { publishState.jobSearch = event.target.value.trim(); publishState.jobPage = 1; loadPublishingQueueData(); }, 280));
  workspace.querySelector('[data-publishing-platform]')?.addEventListener('change', event => { publishState.jobPlatform = event.target.value; publishState.jobPage = 1; loadPublishingQueueData(); });
  workspace.querySelector('[data-publishing-status]')?.addEventListener('change', event => { publishState.jobStatus = event.target.value; publishState.jobPage = 1; loadPublishingQueueData(); });
  workspace.querySelector('[data-publishing-size]')?.addEventListener('change', event => { publishState.jobPageSize = Number(event.target.value); publishState.jobPage = 1; loadPublishingQueueData(); });
  workspace.querySelector('[data-publishing-prev]')?.addEventListener('click', () => { publishState.jobPage = Math.max(1, page - 1); loadPublishingQueueData(); });
  workspace.querySelector('[data-publishing-next]')?.addEventListener('click', () => { publishState.jobPage = Math.min(pages, page + 1); loadPublishingQueueData(); });
  workspace.querySelector('[data-publishing-select-page]')?.addEventListener('change', event => {
    for (const item of items) event.target.checked ? publishState.jobSelected.add(item.id) : publishState.jobSelected.delete(item.id);
    renderQueueWorkspace();
  });
  workspace.querySelectorAll('[data-publishing-check]').forEach(input => input.addEventListener('change', event => {
    event.target.checked ? publishState.jobSelected.add(event.target.dataset.publishingCheck) : publishState.jobSelected.delete(event.target.dataset.publishingCheck);
    renderQueueWorkspace();
  }));
  workspace.querySelector('[data-publishing-delete-selected]')?.addEventListener('click', deleteSelectedPublishingJobs);
  workspace.querySelectorAll('[data-publishing-action]').forEach(button => button.addEventListener('click', () => runPublishingJobAction(button.dataset.publishingId, button.dataset.publishingAction)));
}

function renderPublishingJobRow(job) {
  const canCancel = ['queued', 'running', 'awaiting_user', 'retry_wait'].includes(job.status);
  const canRetry = ['failed', 'blocked', 'cancelled'].includes(job.status);
  const canDelete = ['published', 'drafted', 'failed', 'cancelled', 'ended', 'blocked'].includes(job.status);
  const isLive = job.status === 'published';
  return `<article class="publish-operation-row publishing-job-row status-${escapeHtml(job.status)}">
    <label class="publishing-job-check"><input type="checkbox" data-publishing-check="${job.id}" ${publishState.jobSelected.has(job.id) ? 'checked' : ''}></label>
    <div class="publish-operation-product"><span class="publish-operation-mark">${escapeHtml(String(job.sku || 'VD').slice(-2))}</span><div><strong>${escapeHtml(job.title || 'Ohne Titel')}</strong><small>${escapeHtml(job.sku || '')} · ${escapeHtml(platformLabel(job.platform))}</small></div></div>
    <div class="publish-operation-meta"><span>Aktion</span><b>${escapeHtml(publishingActionLabel(job.action))}</b><small>${escapeHtml(publishingModeLabel(job.mode))}</small></div>
    <div class="publish-operation-meta"><span>Status</span><b class="operation-status status-${escapeHtml(job.status)}">${escapeHtml(publishingStatusLabel(job.status))}</b><small>${escapeHtml(publishingDate(job.updated_at))}</small></div>
    <div class="publish-operation-meta"><span>Versuche</span><b>${Number(job.retries)}/${Number(job.max_retries)}</b><small>${job.next_attempt_at ? `Nächster: ${escapeHtml(publishingDate(job.next_attempt_at))}` : ''}</small></div>
    <div class="publish-operation-error">${job.error_message ? `<span title="${escapeHtml(job.error_message)}">${escapeHtml(job.error_message)}</span>` : job.external_id ? `<span>Externe ID: ${escapeHtml(job.external_id)}</span>` : '<span>Keine Fehler</span>'}</div>
    <div class="publish-operation-actions publishing-job-actions">
      ${job.external_url ? `<a class="icon-button" href="${escapeHtml(job.external_url)}" target="_blank" rel="noopener" title="Extern öffnen">↗</a>` : ''}
      ${isLive && publishingCan(job, 'sync') ? `<button class="icon-button" data-publishing-action="sync" data-publishing-id="${job.id}" title="Status abgleichen">⟳</button>` : ''}
      ${isLive && publishingCan(job, 'update') ? `<button class="icon-button" data-publishing-action="update" data-publishing-id="${job.id}" title="Angebot aktualisieren">✎</button>` : ''}
      ${isLive && publishingCan(job, 'end') ? `<button class="icon-button danger" data-publishing-action="end" data-publishing-id="${job.id}" title="Angebot beenden">■</button>` : ''}
      ${canRetry ? `<button class="icon-button" data-publishing-action="retry" data-publishing-id="${job.id}" title="Wiederholen">↻</button>` : ''}
      ${canCancel ? `<button class="icon-button danger" data-publishing-action="cancel" data-publishing-id="${job.id}" title="Abbrechen">×</button>` : ''}
      ${canDelete ? `<button class="icon-button danger" data-publishing-action="delete" data-publishing-id="${job.id}" title="Protokoll löschen">⌫</button>` : ''}
    </div>
  </article>`;
}

async function runPublishingJobAction(id, action) {
  try {
    if (action === 'retry') await api('POST', `/api/publishing/jobs/${id}/retry`);
    else if (action === 'cancel') await api('POST', `/api/publishing/jobs/${id}/cancel`);
    else if (action === 'delete') {
      if (!confirm('Diesen abgeschlossenen Publishing-Auftrag aus dem Protokoll löschen? Das externe Angebot bleibt unberührt.')) return;
      await api('DELETE', '/api/publishing/jobs', { ids: [id] });
    } else if (['sync', 'update', 'end'].includes(action)) {
      if (action === 'end' && !confirm('Das externe Angebot wirklich beenden?')) return;
      await api('POST', `/api/publishing/jobs/${id}/action`, { action });
    }
    toast(action === 'sync' ? 'Statusabgleich eingereiht' : action === 'update' ? 'Aktualisierung eingereiht' : action === 'end' ? 'Beenden eingereiht' : 'Auftrag aktualisiert');
    await loadPublishingQueueData();
  } catch (error) { toast(`Publishing-Aktion fehlgeschlagen: ${error.message}`); }
}

async function deleteSelectedPublishingJobs() {
  const ids = [...publishState.jobSelected];
  if (!ids.length || !confirm(`${ids.length} abgeschlossene Publishing-Aufträge aus dem lokalen Protokoll löschen?`)) return;
  try {
    const result = await api('DELETE', '/api/publishing/jobs', { ids });
    toast(`${result.deleted} Aufträge gelöscht`);
    publishState.jobSelected.clear();
    await loadPublishingQueueData();
  } catch (error) { toast(`Löschen fehlgeschlagen: ${error.message}`); }
}

function renderPublishingErrorsWorkspace() {
  const workspace = document.getElementById('publish-workspace');
  const events = publishState.publishEvents || [];
  const errors = events.filter(event => event.level === 'error' || ['failed', 'blocked', 'retry_wait'].includes(event.event_type));
  workspace.innerHTML = `<header class="publish-operation-header"><div><span class="publish-panel-kicker">Diagnose & Audit</span><h3>Fehlerzentrale und Veröffentlichungsprotokoll</h3><p>${errors.length} relevante Fehler · ${events.length} Ereignisse geladen</p></div><button class="vd-button" type="button" data-publishing-events-refresh>Aktualisieren</button></header>
    <div class="publishing-error-summary"><article><strong>${errors.length}</strong><span>Fehlerereignisse</span></article><article><strong>${events.filter(item => item.event_type === 'completed').length}</strong><span>Erfolgreiche Abschlüsse</span></article><article><strong>${events.filter(item => item.event_type === 'retry_wait').length}</strong><span>Automatische Wiederholungen</span></article></div>
    <div class="publishing-event-list">${events.length ? events.map(renderPublishingEvent).join('') : '<div class="publish-workspace-empty compact"><span>✓</span><h3>Keine Ereignisse</h3><p>Neue Veröffentlichungsaktivitäten erscheinen hier.</p></div>'}</div>`;
  workspace.querySelector('[data-publishing-events-refresh]')?.addEventListener('click', () => loadPublishingQueueData());
  workspace.querySelectorAll('[data-event-open-job]').forEach(button => button.addEventListener('click', () => {
    publishState.jobSearch = button.dataset.eventOpenJob;
    publishState.jobStatus = '';
    publishState.jobPage = 1;
    setPublishView('queue');
    loadPublishingQueueData();
  }));
}

function renderPublishingEvent(event) {
  return `<article class="publishing-event-row level-${escapeHtml(event.level)}">
    <span class="publishing-event-dot"></span>
    <div><strong>${escapeHtml(event.message)}</strong><small>${escapeHtml(event.title || event.sku || '')} · ${escapeHtml(platformLabel(event.platform))} · ${escapeHtml(publishingDate(event.created_at))}</small></div>
    <span class="publishing-event-type">${escapeHtml(event.event_type)}</span>
    ${event.job_id ? `<button class="vd-button compact" type="button" data-event-open-job="${escapeHtml(event.job_id)}">Auftrag</button>` : '<span></span>'}
  </article>`;
}

async function batchEbayAdd() {
  const ids = [...publishState.selected];
  if (!ids.length && publishState.selectedId) ids.push(publishState.selectedId);
  if (!ids.length) return;
  let created = 0;
  let duplicates = 0;
  let failed = 0;
  for (const id of ids) {
    const listing = publishState.listings.find(item => item.id === id) || publishState.allFiltered.find(item => item.id === id);
    if (!listing) continue;
    try {
      const result = await api('POST', '/api/publishing/jobs', { listing_id: id, platform: listing.platform, action: 'publish' });
      created += result.jobs?.length || 0;
      duplicates += result.duplicates || 0;
    } catch (error) {
      if (error.code === 'DUPLICATE_PUBLISH' || /bereits.*veröffentlicht/i.test(error.message)) duplicates++;
      else failed++;
    }
  }
  toast(`${created} Auftrag${created === 1 ? '' : 'e'} eingereiht${duplicates ? ` · ${duplicates} bereits vorhanden/live` : ''}${failed ? ` · ${failed} fehlgeschlagen` : ''}`);
  publishState.view = 'queue';
  await loadPublishList();
  setPublishView('queue');
}

async function processEbayQueue() {
  setPublishView('queue');
  await loadPublishingQueueData();
}
async function retryEbayQueue() {
  setPublishView('errors');
  await loadPublishingQueueData();
}
async function openEbayQueue() {
  setPublishView('queue');
  await loadPublishingQueueData();
}

function publishQueueCounts() {
  const summary = publishState.jobsData?.summary || {};
  return {
    pending: Number(summary.queued || 0) + Number(summary.awaiting_user || 0) + Number(summary.retry_wait || 0),
    processing: Number(summary.running || 0),
    published: Number(summary.published || 0),
    failed: Number(summary.failed || 0) + Number(summary.blocked || 0),
  };
}

function renderPublishStatusRail() {
  const counts = publishQueueCounts();
  const summary = publishState.jobsData?.summary || {};
  const workflowPublished = publishState.workflow?.stages?.published?.count || 0;
  const directEligible = publishState.listings.filter(listing => ['ebay-de', 'etsy'].includes(listing.platform)).length;
  const setText = (id, value) => { const node = document.getElementById(id); if (node) node.textContent = value; };
  setText('publish-status-pending', counts.pending);
  setText('publish-status-processing', counts.processing);
  setText('publish-status-published', Math.max(counts.published, workflowPublished));
  setText('publish-status-failed', counts.failed);
  setText('publish-status-scheduled', publishState.scheduled.length);
  setText('publish-direct-count', directEligible);
  setText('publish-queue-count', publishState.jobsData?.pagination?.total || 0);
  setText('publish-error-count', counts.failed);
  setText('publish-scheduled-count', publishState.scheduled.length);
  setText('publish-retry-caption', counts.failed ? `${counts.failed} Fehler prüfen` : 'Keine Fehler');
  setText('publish-schedule-caption', publishState.scheduled.length ? `${publishState.scheduled.length} Planung${publishState.scheduled.length === 1 ? '' : 'en'}` : 'Keine Planungen');
  const container = document.getElementById('publish-activity');
  const events = publishState.publishEvents || [];
  if (container) container.innerHTML = events.length ? events.slice(0, 6).map(event => `<div class="publish-activity-row"><span class="publish-activity-icon type-${escapeHtml(event.level || 'info')}">${event.level === 'error' ? '!' : event.event_type === 'completed' ? '✓' : '•'}</span><div><strong>${escapeHtml(event.message || '')}</strong><small>${relativeTime(event.created_at)}</small></div></div>`).join('') : '<div class="publish-rail-empty">Noch keine Publishing-Aktivität</div>';
}


// 1.39.0: Inline-Eventhandler entfernt; zentrale CSP-kompatible Delegation.
document.addEventListener('click', event => {
  const close = event.target.closest?.('[data-close-target]');
  if (close) document.getElementById(close.dataset.closeTarget)?.classList.add('hidden');
  const kill = event.target.closest?.('[data-kill-session-id]');
  if (kill) killSession(Number(kill.dataset.killSessionId));
  const own = event.target.closest?.('[data-end-my-session-id]');
  if (own) endMySession(Number(own.dataset.endMySessionId));
});
