import { join } from 'path';
import { UPLOADS_DIR } from './runtime-paths.mjs';
import { mkdirSync, writeFileSync } from 'fs';
import { randomUUID } from 'crypto';
import {
  generateWithLocalFlux,
  waitForLocalFluxPrompt,
  cancelLocalFluxPrompt,
} from './ai-local-images.mjs';
import { db, createFluxImage, getSettings } from './db.mjs';

const outputDir = join(UPLOADS_DIR, 'ai-generated');
mkdirSync(outputDir, { recursive: true });

const activeVariants = new Map();
let loopTimer = null;
let loopBusy = false;

const STYLE_SUFFIXES = {
  none: '',
  photorealistic: 'photorealistic image, natural materials, physically plausible lighting, realistic proportions, fine surface detail, clean coherent composition',
  product: 'professional commercial product photography, clear hero subject, controlled studio lighting, crisp edges, realistic materials, premium catalog composition',
  editorial: 'premium editorial photography, refined magazine composition, intentional styling, controlled light, elegant color harmony, realistic detail',
  cinematic: 'cinematic still, purposeful framing, atmospheric but realistic lighting, natural depth, controlled contrast, coherent visual storytelling',
  illustration: 'polished high-quality digital illustration, coherent shapes, intentional line and color treatment, refined detail, professional composition',
  social: 'modern social media visual, immediately readable focal point, clean hierarchy, polished lighting, balanced composition, premium finish',
};

const PROFILE_CONFIG = {
  fast: { maxEdge: 768, steps: 4 },
  balanced: { maxEdge: 1024, steps: 4 },
  quality: { maxEdge: 1024, steps: 6 },
  custom: { maxEdge: 1536, steps: null },
};

const MODE_SUFFIXES = {
  raw: '',
  precise: 'The main subject must be unambiguous and visually dominant. Keep all requested objects, positions, materials, colors, perspective and environment consistent.',
  creative: 'Create a visually distinctive but coherent interpretation while preserving the requested subject, purpose and essential attributes.',
};

const TERMINAL_VARIANT_STATUSES = new Set(['completed', 'failed', 'cancelled']);
const ACTIVE_JOB_STATUSES = new Set(['queued', 'running', 'cancelling']);

function normalizeWhitespace(value) {
  return String(value || '').replace(/\s+/g, ' ').replace(/\s*,\s*/g, ', ').trim();
}

function removeNegativePromptPatterns(value) {
  return normalizeWhitespace(value)
    .replace(/\bwithout\s+([\w-]+)/gi, 'avoiding visible $1')
    .replace(/\bohne\s+([\wäöüß-]+)/gi, 'mit einer Gestaltung, in der $1 nicht im Fokus steht');
}

export function buildFluxPrompt(prompt, { style = 'none', mode = 'precise', assistant = true } = {}) {
  const cleaned = removeNegativePromptPatterns(prompt);
  if (!assistant) return [cleaned, STYLE_SUFFIXES[style] || ''].filter(Boolean).join('. ');
  const sections = [cleaned];
  if (MODE_SUFFIXES[mode]) sections.push(MODE_SUFFIXES[mode]);
  if (STYLE_SUFFIXES[style]) sections.push(STYLE_SUFFIXES[style]);
  sections.push('single coherent scene, accurate anatomy and geometry where applicable, no unintended duplicate subjects, no watermark, no interface elements');
  return sections.map(normalizeWhitespace).filter(Boolean).join('. ').replace(/\.{2,}/g, '.');
}

function clampDimension(value, fallback) {
  const number = Number(value);
  if (!Number.isFinite(number)) return fallback;
  return Math.max(256, Math.min(1536, Math.round(number / 8) * 8));
}

function cleanStyle(value) {
  const style = String(value || 'none').toLowerCase();
  return Object.hasOwn(STYLE_SUFFIXES, style) ? style : 'none';
}

function cleanProfile(value) {
  const profile = String(value || 'balanced').toLowerCase();
  return Object.hasOwn(PROFILE_CONFIG, profile) ? profile : 'balanced';
}

function cleanMode(value) {
  const mode = String(value || 'precise').toLowerCase();
  return Object.hasOwn(MODE_SUFFIXES, mode) ? mode : 'precise';
}

function cleanVariantCount(value) {
  const count = Number(value);
  return [1, 2, 4].includes(count) ? count : 1;
}

function normalizeSeed(value) {
  return Number.isFinite(Number(value))
    ? Math.max(0, Math.min(2147483647, Math.round(Number(value))))
    : Math.floor(Math.random() * 2147483647);
}

function randomSeed(excluded = new Set()) {
  let seed = Math.floor(Math.random() * 2147483647);
  while (excluded.has(seed)) seed = Math.floor(Math.random() * 2147483647);
  return seed;
}

function buildSeeds(baseSeed, variants, seedLock, explicitSeeds = null) {
  if (Array.isArray(explicitSeeds) && explicitSeeds.length === variants) {
    return explicitSeeds.map(normalizeSeed);
  }
  const used = new Set();
  const seeds = [];
  const base = normalizeSeed(baseSeed);
  for (let index = 0; index < variants; index += 1) {
    let seed;
    if (seedLock) seed = (base + index) % 2147483648;
    else seed = index === 0 ? base : randomSeed(used);
    while (used.has(seed)) seed = (seed + 1) % 2147483648;
    used.add(seed);
    seeds.push(seed);
  }
  return seeds;
}

function applyProfile({ profile, width, height, steps }) {
  const cfg = PROFILE_CONFIG[profile];
  let outWidth = clampDimension(width, 768);
  let outHeight = clampDimension(height, 768);
  if (profile !== 'custom') {
    const edge = Math.max(outWidth, outHeight);
    if (edge > cfg.maxEdge) {
      const scale = cfg.maxEdge / edge;
      outWidth = Math.max(256, Math.round((outWidth * scale) / 8) * 8);
      outHeight = Math.max(256, Math.round((outHeight * scale) / 8) * 8);
    }
  }
  return {
    width: outWidth,
    height: outHeight,
    steps: cfg.steps ?? Math.max(4, Math.min(12, Math.round(Number(steps) || 4))),
  };
}

function parseDate(value) {
  if (!value) return null;
  const iso = String(value).includes('T') ? String(value) : `${String(value).replace(' ', 'T')}Z`;
  const time = Date.parse(iso);
  return Number.isFinite(time) ? time : null;
}

function getVariantRows(jobId) {
  return db.prepare(`SELECT v.*, i.image_path, i.favorite, i.prompt, i.final_prompt, i.width, i.height, i.steps, i.style, i.profile, i.prompt_mode
    FROM flux_prompt_job_variants v
    LEFT JOIN flux_images i ON i.id = v.image_id
    WHERE v.job_id = ? ORDER BY v.variant_index`).all(String(jobId));
}

function publicVariant(row) {
  return {
    id: row.id,
    job_id: row.job_id,
    variant_index: Number(row.variant_index),
    seed: Number(row.seed),
    status: row.status,
    phase: row.phase,
    message: row.message || '',
    prompt_id: row.prompt_id || null,
    image_id: row.image_id || null,
    image_path: row.image_path || null,
    public_url: row.image_path ? `/uploads/${row.image_path}` : null,
    favorite: Number(row.favorite || 0) === 1,
    prompt: row.prompt || null,
    final_prompt: row.final_prompt || null,
    width: row.width ? Number(row.width) : null,
    height: row.height ? Number(row.height) : null,
    steps: row.steps ? Number(row.steps) : null,
    style: row.style || null,
    profile: row.profile || null,
    prompt_mode: row.prompt_mode || null,
    error: row.error_message || null,
    duration_ms: row.duration_ms || null,
    cancel_requested: Number(row.cancel_requested || 0) === 1,
    created_at: row.created_at,
    started_at: row.started_at,
    completed_at: row.completed_at,
  };
}

function publicJob(row, { includeVariants = true } = {}) {
  if (!row) return null;
  const variants = includeVariants ? getVariantRows(row.id).map(publicVariant) : undefined;
  const createdMs = parseDate(row.created_at);
  const startedMs = parseDate(row.started_at);
  const completedMs = parseDate(row.completed_at);
  return {
    id: row.id,
    prompt: row.prompt,
    final_prompt: row.final_prompt,
    width: Number(row.width),
    height: Number(row.height),
    steps: Number(row.steps),
    style: row.style,
    profile: row.profile,
    prompt_mode: row.prompt_mode,
    assistant: Number(row.assistant || 0) === 1,
    variants_count: Number(row.variants),
    seed_base: Number(row.seed_base),
    seed_lock: Number(row.seed_lock || 0) === 1,
    status: row.status,
    phase: row.phase,
    message: row.message || '',
    error: row.error_message || null,
    completed_variants: Number(row.completed_variants || 0),
    source_job_id: row.source_job_id || null,
    created_at: row.created_at,
    started_at: row.started_at,
    completed_at: row.completed_at,
    archived_at: row.archived_at || null,
    archived: Boolean(row.archived_at),
    elapsed_ms: completedMs && startedMs ? completedMs - startedMs : startedMs ? Date.now() - startedMs : createdMs ? Date.now() - createdMs : null,
    variants,
  };
}

function fetchJobRow(id) {
  return db.prepare('SELECT * FROM flux_prompt_jobs WHERE id = ?').get(String(id || ''));
}

function updateParentJob(jobId) {
  const job = fetchJobRow(jobId);
  if (!job) return null;
  const variants = db.prepare('SELECT status, error_message FROM flux_prompt_job_variants WHERE job_id = ? ORDER BY variant_index').all(jobId);
  const counts = variants.reduce((acc, item) => {
    acc[item.status] = (acc[item.status] || 0) + 1;
    return acc;
  }, {});
  const completed = counts.completed || 0;
  const terminal = variants.length > 0 && variants.every(item => TERMINAL_VARIANT_STATUSES.has(item.status));
  let status = job.status;
  let phase = job.phase;
  let message = job.message;
  let errorMessage = null;
  let completedAt = null;
  if (terminal) {
    completedAt = new Date().toISOString();
    if (completed === variants.length) {
      status = 'completed';
      phase = 'completed';
      message = `${completed} Bildvariante${completed === 1 ? '' : 'n'} fertig.`;
    } else if ((counts.cancelled || 0) === variants.length) {
      status = 'cancelled';
      phase = 'cancelled';
      message = 'Auftrag wurde abgebrochen.';
    } else if (completed > 0) {
      status = 'completed_with_errors';
      phase = 'completed_with_errors';
      message = `${completed} von ${variants.length} Varianten fertig.`;
      errorMessage = variants.find(item => item.error_message)?.error_message || null;
    } else {
      status = 'failed';
      phase = 'failed';
      message = 'Alle Bildvarianten sind fehlgeschlagen.';
      errorMessage = variants.find(item => item.error_message)?.error_message || null;
    }
  } else if ((counts.running || 0) > 0) {
    status = job.status === 'cancelling' ? 'cancelling' : 'running';
    phase = job.status === 'cancelling' ? 'cancelling' : 'rendering';
    message = job.status === 'cancelling' ? 'Abbruch wird an ComfyUI übergeben …' : `${counts.running} Variante${counts.running === 1 ? '' : 'n'} läuft, ${counts.queued || 0} wartet.`;
  } else if ((counts.queued || 0) > 0) {
    status = job.status === 'cancelling' ? 'cancelling' : 'queued';
    phase = job.status === 'cancelling' ? 'cancelling' : 'queued';
    message = job.status === 'cancelling' ? 'Auftrag wird abgebrochen …' : `${counts.queued} Variante${counts.queued === 1 ? '' : 'n'} wartet.`;
  }
  db.prepare(`UPDATE flux_prompt_jobs SET status = ?, phase = ?, message = ?, error_message = ?, completed_variants = ?,
    completed_at = COALESCE(?, completed_at), updated_at = datetime('now') WHERE id = ?`)
    .run(status, phase, message, errorMessage, completed, completedAt, jobId);
  return getFluxPromptJob(jobId);
}

function saveResultImage(job, variant, result, startedAt) {
  const relative = `ai-generated/${randomUUID()}.png`;
  writeFileSync(join(UPLOADS_DIR, relative), Buffer.from(result.base64, 'base64'));
  const durationMs = Math.max(0, Date.now() - startedAt);
  const image = createFluxImage({
    prompt: job.prompt,
    final_prompt: job.final_prompt,
    image_path: relative,
    width: job.width,
    height: job.height,
    seed: variant.seed,
    steps: job.steps,
    style: job.style,
    profile: job.profile,
    duration_ms: durationMs,
    job_id: job.id,
    variant_index: variant.variant_index,
    prompt_mode: job.prompt_mode,
    parameters: {
      width: Number(job.width),
      height: Number(job.height),
      steps: Number(job.steps),
      style: job.style,
      profile: job.profile,
      prompt_mode: job.prompt_mode,
      seed_lock: Number(job.seed_lock || 0) === 1,
      variants: Number(job.variants),
    },
  });
  db.prepare(`UPDATE flux_prompt_job_variants SET status = 'completed', phase = 'completed', message = ?, image_id = ?,
    duration_ms = ?, completed_at = datetime('now'), updated_at = datetime('now') WHERE id = ?`)
    .run(`Variante ${variant.variant_index} ist fertig.`, image.id, durationMs, variant.id);
  return image;
}

async function variantCancelled(variantId) {
  const row = db.prepare('SELECT cancel_requested FROM flux_prompt_job_variants WHERE id = ?').get(variantId);
  return Number(row?.cancel_requested || 0) === 1;
}

async function runVariant(variantId, { resume = false } = {}) {
  if (activeVariants.has(variantId)) return;
  const variant = db.prepare('SELECT * FROM flux_prompt_job_variants WHERE id = ?').get(variantId);
  if (!variant) return;
  const job = fetchJobRow(variant.job_id);
  if (!job || !ACTIVE_JOB_STATUSES.has(job.status)) return;
  activeVariants.set(variantId, true);
  const startedAt = parseDate(variant.started_at) || Date.now();
  db.prepare(`UPDATE flux_prompt_job_variants SET status = 'running', phase = ?, message = ?, started_at = COALESCE(started_at, datetime('now')), updated_at = datetime('now') WHERE id = ?`)
    .run(resume ? 'resuming' : 'preparing', resume ? 'Vorhandener ComfyUI-Auftrag wird wieder aufgenommen …' : 'FLUX-Workflow wird vorbereitet …', variantId);
  db.prepare(`UPDATE flux_prompt_jobs SET status = CASE WHEN status = 'cancelling' THEN status ELSE 'running' END,
    phase = CASE WHEN status = 'cancelling' THEN 'cancelling' ELSE 'rendering' END,
    started_at = COALESCE(started_at, datetime('now')), updated_at = datetime('now') WHERE id = ?`).run(job.id);
  try {
    const settings = getSettings();
    const onPhase = (phase, message) => {
      db.prepare(`UPDATE flux_prompt_job_variants SET phase = ?, message = ?, updated_at = datetime('now') WHERE id = ?`)
        .run(String(phase || 'running'), String(message || 'FLUX arbeitet …').slice(0, 500), variantId);
    };
    let result;
    if (resume && variant.prompt_id) {
      result = await waitForLocalFluxPrompt({
        promptId: variant.prompt_id,
        settings,
        timeoutMs: 300000,
        onPhase,
        isCancelled: () => variantCancelled(variantId),
      });
    } else {
      result = await generateWithLocalFlux({
        prompt: job.final_prompt,
        settings: { ...settings, ai_model_photo_use_source_reference: '0', ai_model_photo_local_resolution: String(Math.max(job.width, job.height) >= 1024 ? 1024 : 768) },
        mode: 'prompt',
        variantIndex: Math.max(0, Number(variant.variant_index) - 1),
        sourceDataUrl: '',
        width: Number(job.width),
        height: Number(job.height),
        seed: Number(variant.seed),
        steps: Number(job.steps),
        onPhase,
        onSubmitted: async (promptId) => {
          db.prepare(`UPDATE flux_prompt_job_variants SET prompt_id = ?, phase = 'submitted', message = 'An ComfyUI übergeben …', updated_at = datetime('now') WHERE id = ?`)
            .run(String(promptId), variantId);
        },
        isCancelled: () => variantCancelled(variantId),
      });
    }
    if (await variantCancelled(variantId)) {
      const error = new Error('FLUX-Auftrag wurde abgebrochen.');
      error.code = 'FLUX_CANCELLED';
      throw error;
    }
    saveResultImage(job, variant, result, startedAt);
  } catch (error) {
    const cancelled = error?.code === 'FLUX_CANCELLED' || await variantCancelled(variantId);
    db.prepare(`UPDATE flux_prompt_job_variants SET status = ?, phase = ?, message = ?, error_message = ?,
      completed_at = datetime('now'), updated_at = datetime('now') WHERE id = ?`)
      .run(
        cancelled ? 'cancelled' : 'failed',
        cancelled ? 'cancelled' : 'failed',
        cancelled ? 'Variante wurde abgebrochen.' : 'Bildgenerierung fehlgeschlagen.',
        cancelled ? null : String(error?.message || error).slice(0, 2000),
        variantId,
      );
  } finally {
    activeVariants.delete(variantId);
    updateParentJob(job.id);
    queueMicrotask(() => processQueue().catch(() => {}));
  }
}

function getMaxParallelJobs() {
  const settings = getSettings();
  return Math.max(1, Math.min(4, Math.round(Number(settings.flux_max_parallel_jobs) || 1)));
}

async function processQueue() {
  if (loopBusy) return;
  loopBusy = true;
  try {
    const maxParallel = getMaxParallelJobs();
    while (activeVariants.size < maxParallel) {
      const next = db.prepare(`SELECT v.id FROM flux_prompt_job_variants v
        JOIN flux_prompt_jobs j ON j.id = v.job_id
        WHERE v.status = 'queued' AND v.cancel_requested = 0 AND j.archived_at IS NULL AND j.status IN ('queued','running')
        ORDER BY j.created_at, v.variant_index LIMIT 1`).get();
      if (!next) break;
      runVariant(next.id).catch(() => {});
      await new Promise(resolve => setTimeout(resolve, 10));
    }
  } finally {
    loopBusy = false;
  }
}

function createJob({
  prompt,
  width = 768,
  height = 768,
  seed = null,
  steps = 4,
  style = 'none',
  profile = 'balanced',
  promptMode = 'precise',
  assistant = true,
  variants = 1,
  seedLock = false,
  sourceJobId = null,
  explicitSeeds = null,
} = {}) {
  const cleanPrompt = normalizeWhitespace(prompt);
  if (cleanPrompt.length < 3) throw new Error('Bitte einen aussagekräftigen Prompt eingeben.');
  if (cleanPrompt.length > 2000) throw new Error('Der Prompt darf höchstens 2.000 Zeichen lang sein.');
  const normalizedProfile = cleanProfile(profile);
  const dimensions = applyProfile({ profile: normalizedProfile, width, height, steps });
  const normalizedStyle = cleanStyle(style);
  const normalizedMode = cleanMode(promptMode);
  const variantCount = cleanVariantCount(variants);
  const baseSeed = normalizeSeed(seed);
  const seeds = buildSeeds(baseSeed, variantCount, seedLock === true, explicitSeeds);
  const id = randomUUID();
  const finalPrompt = buildFluxPrompt(cleanPrompt, { style: normalizedStyle, mode: normalizedMode, assistant: assistant !== false });
  const insert = db.transaction(() => {
    db.prepare(`INSERT INTO flux_prompt_jobs
      (id, prompt, final_prompt, width, height, steps, style, profile, prompt_mode, assistant, variants, seed_base, seed_lock,
       status, phase, message, source_job_id, settings_json)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'queued', 'queued', ?, ?, '{}')`)
      .run(id, cleanPrompt, finalPrompt, dimensions.width, dimensions.height, dimensions.steps, normalizedStyle, normalizedProfile,
        normalizedMode, assistant !== false ? 1 : 0, variantCount, seeds[0], seedLock === true ? 1 : 0,
        `${variantCount} Variante${variantCount === 1 ? '' : 'n'} wartet${variantCount === 1 ? '' : 'en'} auf FLUX.`, sourceJobId || null);
    const stmt = db.prepare(`INSERT INTO flux_prompt_job_variants
      (id, job_id, variant_index, seed, status, phase, message) VALUES (?, ?, ?, ?, 'queued', 'queued', ?)`);
    seeds.forEach((variantSeed, index) => stmt.run(randomUUID(), id, index + 1, variantSeed, `Variante ${index + 1} wartet.`));
  });
  insert();
  processQueue().catch(() => {});
  return getFluxPromptJob(id);
}

export function enqueueFluxPromptJob(payload = {}) {
  return createJob(payload);
}

export function getFluxPromptJob(id) {
  return publicJob(fetchJobRow(id));
}

function fluxStatusWhere(status = '') {
  const normalized = String(status || '').trim();
  if (normalized === 'active') return { sql: "status IN ('queued','running','cancelling')", args: [] };
  if (normalized === 'completed') return { sql: "status IN ('completed','completed_with_errors')", args: [] };
  if (['failed', 'cancelled', 'queued', 'running', 'cancelling', 'completed', 'completed_with_errors'].includes(normalized)) {
    return { sql: 'status = ?', args: [normalized] };
  }
  return { sql: '', args: [] };
}

export function listFluxPromptJobs({ limit = 50, status = '', archived = false } = {}) {
  const safeLimit = Math.max(1, Math.min(200, Number(limit) || 50));
  const statusClause = fluxStatusWhere(status);
  const where = [archived ? 'archived_at IS NOT NULL' : 'archived_at IS NULL'];
  if (statusClause.sql) where.push(statusClause.sql);
  const rows = db.prepare(`SELECT * FROM flux_prompt_jobs WHERE ${where.join(' AND ')} ORDER BY created_at DESC LIMIT ?`)
    .all(...statusClause.args, safeLimit);
  return rows.map(row => publicJob(row));
}

export function paginateFluxPromptJobs({ page = 1, pageSize = 10, status = '', archived = false } = {}) {
  const safePageSize = Math.max(5, Math.min(15, Number(pageSize) || 10));
  const requestedPage = Math.max(1, Number(page) || 1);
  const statusClause = fluxStatusWhere(status);
  const where = [archived ? 'archived_at IS NOT NULL' : 'archived_at IS NULL'];
  if (statusClause.sql) where.push(statusClause.sql);
  const whereSql = where.join(' AND ');
  const total = Number(db.prepare(`SELECT COUNT(*) AS count FROM flux_prompt_jobs WHERE ${whereSql}`).get(...statusClause.args)?.count || 0);
  const totalPages = Math.max(1, Math.ceil(total / safePageSize));
  const safePage = Math.min(requestedPage, totalPages);
  const rows = db.prepare(`SELECT * FROM flux_prompt_jobs WHERE ${whereSql} ORDER BY created_at DESC LIMIT ? OFFSET ?`)
    .all(...statusClause.args, safePageSize, (safePage - 1) * safePageSize);
  return {
    jobs: rows.map(row => publicJob(row)),
    pagination: { page: safePage, page_size: safePageSize, total, total_pages: totalPages },
  };
}

function normalizeJobIds(ids = []) {
  return [...new Set((Array.isArray(ids) ? ids : [ids]).map(value => String(value || '').trim()).filter(Boolean))].slice(0, 500);
}

export function archiveFluxPromptJobs(ids = []) {
  const jobIds = normalizeJobIds(ids);
  if (!jobIds.length) return { ok: true, archived: 0, skipped: 0 };
  const placeholders = jobIds.map(() => '?').join(',');
  const result = db.prepare(`UPDATE flux_prompt_jobs SET archived_at = datetime('now'), updated_at = datetime('now')
    WHERE id IN (${placeholders}) AND archived_at IS NULL AND status NOT IN ('queued','running','cancelling')`).run(...jobIds);
  return { ok: true, archived: Number(result.changes || 0), skipped: jobIds.length - Number(result.changes || 0) };
}

export function restoreFluxPromptJobs(ids = []) {
  const jobIds = normalizeJobIds(ids);
  if (!jobIds.length) return { ok: true, restored: 0 };
  const placeholders = jobIds.map(() => '?').join(',');
  const result = db.prepare(`UPDATE flux_prompt_jobs SET archived_at = NULL, updated_at = datetime('now') WHERE id IN (${placeholders}) AND archived_at IS NOT NULL`).run(...jobIds);
  return { ok: true, restored: Number(result.changes || 0) };
}

export function deleteArchivedFluxPromptJobs(ids = [], { all = false } = {}) {
  if (all) {
    const result = db.prepare('DELETE FROM flux_prompt_jobs WHERE archived_at IS NOT NULL').run();
    return { ok: true, deleted: Number(result.changes || 0) };
  }
  const jobIds = normalizeJobIds(ids);
  if (!jobIds.length) return { ok: true, deleted: 0 };
  const placeholders = jobIds.map(() => '?').join(',');
  const result = db.prepare(`DELETE FROM flux_prompt_jobs WHERE archived_at IS NOT NULL AND id IN (${placeholders})`).run(...jobIds);
  return { ok: true, deleted: Number(result.changes || 0) };
}

export function getFluxPromptJobSummary() {
  const rows = db.prepare('SELECT status, COUNT(*) AS count FROM flux_prompt_jobs WHERE archived_at IS NULL GROUP BY status').all();
  const jobs = Object.fromEntries(rows.map(row => [row.status, Number(row.count)]));
  const archivedTotal = Number(db.prepare('SELECT COUNT(*) AS count FROM flux_prompt_jobs WHERE archived_at IS NOT NULL').get()?.count || 0);
  const variants = db.prepare(`SELECT
    SUM(CASE WHEN v.status = 'running' THEN 1 ELSE 0 END) AS running,
    SUM(CASE WHEN v.status = 'queued' THEN 1 ELSE 0 END) AS queued
    FROM flux_prompt_job_variants v JOIN flux_prompt_jobs j ON j.id = v.job_id WHERE j.archived_at IS NULL`).get();
  return {
    jobs,
    archived_total: archivedTotal,
    variants: { running: Number(variants?.running || 0), queued: Number(variants?.queued || 0) },
    active_workers: activeVariants.size,
    max_parallel: getMaxParallelJobs(),
  };
}

export async function cancelFluxPromptJob(id) {
  const job = fetchJobRow(id);
  if (!job) return null;
  if (!ACTIVE_JOB_STATUSES.has(job.status)) return getFluxPromptJob(id);
  const runningVariants = db.prepare("SELECT id, prompt_id FROM flux_prompt_job_variants WHERE job_id = ? AND status = 'running'").all(id);
  db.transaction(() => {
    db.prepare("UPDATE flux_prompt_jobs SET status = 'cancelling', phase = 'cancelling', message = 'Abbruch wird ausgeführt …', updated_at = datetime('now') WHERE id = ?").run(id);
    db.prepare("UPDATE flux_prompt_job_variants SET status = 'cancelled', phase = 'cancelled', message = 'Vor Start abgebrochen.', completed_at = datetime('now'), updated_at = datetime('now') WHERE job_id = ? AND status = 'queued'").run(id);
    db.prepare("UPDATE flux_prompt_job_variants SET cancel_requested = 1, phase = 'cancelling', message = 'Abbruch angefordert …', updated_at = datetime('now') WHERE job_id = ? AND status = 'running'").run(id);
  })();
  if (runningVariants.length > 0) {
    const settings = getSettings();
    for (const variant of runningVariants) {
      if (!variant.prompt_id) continue;
      try { await cancelLocalFluxPrompt(variant.prompt_id, settings); } catch {}
    }
  }
  updateParentJob(id);
  return getFluxPromptJob(id);
}

export function retryFluxPromptJob(id) {
  const source = fetchJobRow(id);
  if (!source) return null;
  const variants = getVariantRows(id);
  return createJob({
    prompt: source.prompt,
    width: source.width,
    height: source.height,
    seed: source.seed_base,
    steps: source.steps,
    style: source.style,
    profile: source.profile,
    promptMode: source.prompt_mode,
    assistant: Number(source.assistant || 0) === 1,
    variants: source.variants,
    seedLock: Number(source.seed_lock || 0) === 1,
    sourceJobId: source.id,
    explicitSeeds: variants.map(item => Number(item.seed)),
  });
}

export function clearFluxPromptQueue() {
  const jobIds = db.prepare(`SELECT DISTINCT job_id FROM flux_prompt_job_variants WHERE status = 'queued'`).all().map(row => row.job_id);
  const result = db.prepare(`UPDATE flux_prompt_job_variants SET status = 'cancelled', phase = 'cancelled', message = 'Aus Warteschlange entfernt.',
    completed_at = datetime('now'), updated_at = datetime('now') WHERE status = 'queued'`).run();
  jobIds.forEach(updateParentJob);
  return { ok: true, cleared_variants: Number(result.changes || 0), affected_jobs: jobIds.length };
}

export function stopFluxPromptJobLoop() {
  if (loopTimer) clearInterval(loopTimer);
  loopTimer = null;
}

export function startFluxPromptJobLoop() {
  if (loopTimer) return;
  const running = db.prepare("SELECT id, prompt_id FROM flux_prompt_job_variants WHERE status = 'running'").all();
  for (const variant of running) {
    if (variant.prompt_id) runVariant(variant.id, { resume: true }).catch(() => {});
    else db.prepare("UPDATE flux_prompt_job_variants SET status = 'queued', phase = 'queued', message = 'Nach Vendoo-Neustart erneut eingereiht.', updated_at = datetime('now') WHERE id = ?").run(variant.id);
  }
  db.prepare("UPDATE flux_prompt_jobs SET status = 'queued', phase = 'queued', message = 'Nach Vendoo-Neustart wieder aufgenommen.', updated_at = datetime('now') WHERE status = 'running' AND id NOT IN (SELECT job_id FROM flux_prompt_job_variants WHERE status = 'running')").run();
  loopTimer = setInterval(() => processQueue().catch(() => {}), 1000);
  loopTimer.unref?.();
  processQueue().catch(() => {});
}
