import React from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { ArrowLeft, RefreshCcw, Plus } from 'lucide-react';
import { YStack, XStack } from '@tamagui/stacks';
import { SizableText as Text } from '@tamagui/text';
import { Pressable } from '@tamagui/react-native-web-lite';
import { Input, TextArea } from 'tamagui';
import type { fabric as FabricNS } from 'fabric';
import { MobileShell } from './components/MobileShell';
import { MobileCard, CTAButton, PillBadge } from './components/Primitives';
import {
  TenantEvent,
  EventQrInvite,
  EventQrInviteLayout,
  getEvent,
  getEventQrInvites,
  updateEventQrInvite,
} from '../api';
import { isAuthError } from '../auth/tokens';
import { getApiErrorMessage } from '../lib/apiError';
import toast from 'react-hot-toast';
import {
  generatePdfBytes,
  generatePngDataUrl,
  triggerDownloadFromBlob,
  triggerDownloadFromDataUrl,
} from './invite-layout/export-utils';
import { LayoutElement, CANVAS_WIDTH, CANVAS_HEIGHT } from './invite-layout/schema';
import { buildInitialTextFields } from './qr/utils';

type Step = 'background' | 'text' | 'preview';

const BACKGROUND_PRESETS = [
  { id: 'bg-blue-floral', src: '/storage/layouts/backgrounds-portrait/bg-blue-floral.png', label: 'Blue Floral' },
  { id: 'bg-goldframe', src: '/storage/layouts/backgrounds-portrait/bg-goldframe.png', label: 'Gold Frame' },
  { id: 'gr-green-floral', src: '/storage/layouts/backgrounds-portrait/gr-green-floral.png', label: 'Green Floral' },
];

export function buildInitialTextFields({
  customization,
  layoutDefaults,
  eventName,
}: {
  customization: Record<string, unknown> | null | undefined;
  layoutDefaults: EventQrInviteLayout | null | undefined;
  eventName?: string | null;
}): { headline: string; subtitle: string; description: string; instructions: string[] } {
  const initialInstructions =
    Array.isArray(customization?.instructions) && customization.instructions.length
      ? customization.instructions
          .map((item: unknown) => String(item ?? ''))
          .filter((item: string) => item.length > 0)
      : [
          'QR-Code scannen\nKamera-App öffnen und auf den Code richten.',
          'Webseite öffnen\nDer Link öffnet direkt das gemeinsame Jubiläumsalbum.',
          'Fotos hochladen\nZeigt eure Lieblingsmomente oder erfüllt kleine Fotoaufgaben, um besondere Erinnerungen beizusteuern.',
        ];

  return {
    headline:
      (typeof customization?.headline === 'string' && customization.headline.length ? customization.headline : null) ??
      (typeof eventName === 'string' && eventName.length ? eventName : null) ??
      (typeof layoutDefaults?.name === 'string' ? layoutDefaults.name : '') ??
      '',
    subtitle: typeof customization?.subtitle === 'string' ? customization.subtitle : '',
    description:
      (typeof customization?.description === 'string' && customization.description.length ? customization.description : null) ??
      (typeof layoutDefaults?.description === 'string' ? layoutDefaults.description : null) ??
      'Helft uns, diesen besonderen Tag mit euren schönen Momenten festzuhalten.',
    instructions: initialInstructions,
  };
}

export default function MobileQrLayoutCustomizePage() {
  const { slug: slugParam, tokenId: tokenParam } = useParams<{ slug?: string; tokenId?: string }>();
  const [searchParams] = useSearchParams();
  const slug = slugParam ?? null;
  const tokenId = tokenParam ? Number(tokenParam) : null;
  const layoutParam = searchParams.get('layout');
  const navigate = useNavigate();
  const { t } = useTranslation('management');

  const [event, setEvent] = React.useState<TenantEvent | null>(null);
  const [invite, setInvite] = React.useState<EventQrInvite | null>(null);
  const [selectedLayoutId, setSelectedLayoutId] = React.useState<string | null>(layoutParam);
  const [backgroundPreset, setBackgroundPreset] = React.useState<string | null>(null);
  const [backgroundGradient, setBackgroundGradient] = React.useState<{ angle: number; stops: string[] } | null>(null);
  const [backgroundSolid, setBackgroundSolid] = React.useState<string | null>(null);
  const [textFields, setTextFields] = React.useState({
    headline: '',
    subtitle: '',
    description: '',
    instructions: [''],
  });
  const [qrPngUrl, setQrPngUrl] = React.useState<string | null>(null);
  const [step, setStep] = React.useState<Step>('background');
  const [saving, setSaving] = React.useState(false);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState<string | null>(null);

  const handleSelectPreset = React.useCallback((id: string | null) => {
    setBackgroundPreset(id);
    if (id) {
      setBackgroundGradient(null);
      setBackgroundSolid(null);
    }
  }, []);

  const handleSelectGradient = React.useCallback((gradient: { angle: number; stops: string[] } | null) => {
    setBackgroundGradient(gradient);
    if (gradient) {
      setBackgroundPreset(null);
      setBackgroundSolid(null);
    }
  }, []);

  const handleSelectSolid = React.useCallback((color: string | null) => {
    setBackgroundSolid(color);
    if (color) {
      setBackgroundPreset(null);
      setBackgroundGradient(null);
    }
  }, []);

  React.useEffect(() => {
    if (!slug) return;
    (async () => {
      setLoading(true);
      try {
        const eventData = await getEvent(slug);
        const invites = await getEventQrInvites(slug);
        const matchedInvite = tokenId ? invites.find((item) => item.id === tokenId) : invites.find((i) => i.is_active) ?? invites[0] ?? null;
        setEvent(eventData);
        setInvite(matchedInvite ?? null);
        setQrPngUrl(matchedInvite?.qr_code_data_url ?? null);
        const customization = (matchedInvite?.metadata as any)?.layout_customization ?? {};
        const initialLayoutId = layoutParam ?? customization.layout_id ?? matchedInvite?.layouts?.[0]?.id ?? null;
        const layoutDefaults = matchedInvite?.layouts?.find((l) => l.id === initialLayoutId) ?? matchedInvite?.layouts?.[0] ?? null;
        setBackgroundPreset(typeof customization.background_preset === 'string' ? customization.background_preset : null);
        setSelectedLayoutId(initialLayoutId);
        setBackgroundGradient(customization.background_gradient ?? null);
        setBackgroundSolid(customization.background_color ?? null);
        setTextFields(
          buildInitialTextFields({
            customization,
            layoutDefaults,
            eventName: eventData?.name ?? null,
          })
        );
        setError(null);
      } catch (err) {
        if (!isAuthError(err)) {
          setError(getApiErrorMessage(err, t('events.errors.loadFailed', 'QR-Daten konnten nicht geladen werden.')));
        }
      } finally {
        setLoading(false);
      }
    })();
  }, [slug, tokenId, layoutParam, t]);

  const selectedLayout = invite?.layouts.find((l) => l.id === selectedLayoutId) ?? invite?.layouts?.[0] ?? null;
  const isFoldable = (selectedLayout?.panel_mode ?? '').toLowerCase() === 'double-mirror';

  const handleSave = async () => {
    if (!slug || !invite || !selectedLayout) {
      toast.error(t('events.qr.missing', 'Kein QR-Link vorhanden'));
      return;
    }
    setSaving(true);
    try {
      const payload = {
        metadata: {
          layout_customization: {
            layout_id: selectedLayout.id,
            background_preset: backgroundPreset,
            background_gradient: backgroundGradient ?? undefined,
            background_color: backgroundSolid ?? undefined,
            headline: textFields.headline || null,
            subtitle: textFields.subtitle || null,
            description: textFields.description || null,
            instructions: textFields.instructions.filter((item) => item.trim().length > 0),
          },
        },
      };
      const updated = await updateEventQrInvite(slug, invite.id, payload);
      setInvite(updated);
      toast.success(t('common.saved', 'Gespeichert'));
    } catch (err) {
      toast.error(getApiErrorMessage(err, t('events.errors.saveFailed', 'Speichern fehlgeschlagen.')));
    } finally {
      setSaving(false);
    }
  };

  return (
    <MobileShell
      activeTab="home"
      title={t('events.qr.customize', 'Layout anpassen')}
      onBack={() => navigate(-1)}
      headerActions={
        <Pressable onPress={() => window.location.reload()}>
          <RefreshCcw size={18} color="#0f172a" />
        </Pressable>
      }
    >
      {error ? (
        <MobileCard>
          <Text fontWeight="700" color="#b91c1c">
            {error}
          </Text>
        </MobileCard>
      ) : null}

      <Stepper current={step} onStepChange={setStep} />

      <MobileCard space="$2" marginTop="$2">
        {step === 'background' && (
          <BackgroundStep
            onBack={() => navigate(-1)}
            presets={BACKGROUND_PRESETS}
            selectedPreset={backgroundPreset}
            onSelectPreset={handleSelectPreset}
            selectedLayout={selectedLayout}
            layouts={invite?.layouts ?? []}
            onSelectGradient={handleSelectGradient}
            onSelectSolid={handleSelectSolid}
            selectedGradient={backgroundGradient}
            selectedSolid={backgroundSolid}
            onSave={() => {
              handleSave();
              setStep('text');
            }}
            saving={saving || loading}
          />
        )}

        {step === 'text' && (
          <TextStep
            onBack={() => setStep('background')}
            textFields={textFields}
            onChange={(fields) => setTextFields(fields)}
            onSave={() => {
              handleSave();
              setStep('preview');
            }}
            saving={saving || loading}
          />
        )}

        {step === 'preview' && (
        <PreviewStep
          onBack={() => setStep('text')}
          layout={selectedLayout}
          backgroundPreset={backgroundPreset}
          backgroundGradient={backgroundGradient}
          backgroundSolid={backgroundSolid}
          presets={BACKGROUND_PRESETS}
          textFields={textFields}
          qrUrl={qrPngUrl ?? invite?.url ?? ''}
          isFoldable={isFoldable}
          onExport={(format) => {
            const layout = selectedLayout;
            const url = layout?.download_urls?.[format];
            if (!url) {
                toast.error(t('events.qr.missing', 'Kein QR-Link vorhanden'));
                return;
              }
              window.open(url, '_blank', 'noopener');
            }}
          />
        )}
      </MobileCard>
    </MobileShell>
  );
}

function Stepper({ current, onStepChange }: { current: Step; onStepChange: (step: Step) => void }) {
  const { t } = useTranslation('management');
  const steps: { key: Step; label: string }[] = [
    { key: 'background', label: t('events.qr.background', 'Hintergrund') },
    { key: 'text', label: t('events.qr.text', 'Text') },
    { key: 'preview', label: t('events.qr.preview', 'Vorschau') },
  ];
  const currentIndex = steps.findIndex((s) => s.key === current);
  const progress = ((currentIndex + 1) / steps.length) * 100;

  return (
    <YStack space="$2" marginTop="$2">
      <XStack space="$2" alignItems="center">
        {steps.map((step, idx) => {
          const active = step.key === current;
          const completed = idx < currentIndex;
          return (
            <Pressable key={step.key} onPress={() => onStepChange(step.key)} style={{ flex: 1 }}>
              <YStack
                borderRadius={12}
                borderWidth={1}
                borderColor={active ? '#2563EB' : completed ? '#16a34a' : '#e5e7eb'}
                backgroundColor={active ? '#eff6ff' : '#fff'}
                padding="$2"
                alignItems="center"
                justifyContent="center"
              >
                <Text fontSize="$sm" fontWeight="700" color="#111827">
                  {step.label}
                </Text>
                <Text fontSize="$xs" color="#6b7280">
                  {t('events.qr.step', 'Schritt')} {idx + 1}
                </Text>
              </YStack>
            </Pressable>
          );
        })}
      </XStack>
      <YStack height={8} borderRadius={999} backgroundColor="#e5e7eb" overflow="hidden">
        <YStack height="100%" width={`${progress}%`} backgroundColor="#2563EB" />
      </YStack>
    </YStack>
  );
}

type SlotDefinition = {
  x: number;
  y: number;
  w: number;
  h?: number;
  fontSize?: number;
  fontWeight?: string | number;
  lineHeight?: number;
  align?: 'left' | 'center' | 'right';
};

function getDefaultSlots(): Record<string, SlotDefinition> {
  return {
    headline: { x: 0.08, y: 0.08, w: 0.84, fontSize: 30, fontWeight: 800, align: 'center' },
    subtitle: { x: 0.1, y: 0.16, w: 0.8, fontSize: 18, fontWeight: 600, align: 'center' },
    description: { x: 0.1, y: 0.22, w: 0.8, fontSize: 16, lineHeight: 1.4, align: 'center' },
    instructions: { x: 0.1, y: 0.34, w: 0.8, fontSize: 14, lineHeight: 1.35 },
    qr: { x: 0.35, y: 0.55, w: 0.3 },
  };
}

function resolveSlots(layout: EventQrInviteLayout | null, isFoldable: boolean): Record<string, SlotDefinition> {
  const fromLayout = (layout as any)?.slots;
  if (fromLayout && typeof fromLayout === 'object') {
    return Object.entries(fromLayout as Record<string, unknown>).reduce<Record<string, SlotDefinition>>((acc, [key, value]) => {
      if (value && typeof value === 'object') {
        const slot = value as Record<string, unknown>;
        acc[key] = {
          x: Number(slot.x ?? 0),
          y: Number(slot.y ?? 0),
          w: Number(slot.w ?? 0.8),
          h: typeof slot.h === 'number' ? slot.h : undefined,
          fontSize: typeof slot.fontSize === 'number' ? slot.fontSize : undefined,
          fontWeight: typeof slot.fontWeight === 'string' || typeof slot.fontWeight === 'number' ? slot.fontWeight : undefined,
          lineHeight: typeof slot.lineHeight === 'number' ? slot.lineHeight : undefined,
          align: typeof slot.align === 'string' ? (slot.align as SlotDefinition['align']) : undefined,
        };
      }
      return acc;
    }, {});
  }

  if (isFoldable) {
    return {
      headline: { x: 0.1, y: 0.1, w: 0.8, fontSize: 28, fontWeight: 800, align: 'center' },
      subtitle: { x: 0.12, y: 0.18, w: 0.76, fontSize: 18, fontWeight: 600, align: 'center' },
      description: { x: 0.12, y: 0.24, w: 0.76, fontSize: 15, lineHeight: 1.4, align: 'center' },
      instructions: { x: 0.12, y: 0.36, w: 0.76, fontSize: 13, lineHeight: 1.3 },
      qr: { x: 0.36, y: 0.56, w: 0.28 },
    };
  }

  return getDefaultSlots();
}

function getCanvasSize(layout: EventQrInviteLayout | null, isFoldable: boolean): { width: number; height: number } {
  const svgWidth = (layout as any)?.preview?.svg?.width;
  const svgHeight = (layout as any)?.preview?.svg?.height;
  if (svgWidth && svgHeight) {
    return { width: svgWidth, height: svgHeight };
  }
  if ((layout?.orientation || '').toLowerCase() === 'landscape' || isFoldable) {
    return { width: 1754, height: 1240 }; // A4 landscape fallback
  }
  return { width: 1240, height: 1754 }; // A4 portrait fallback
}

function FabricCanvasPreview({
  layout,
  isFoldable,
  backgroundPresetSrc,
  backgroundGradient,
  backgroundSolid,
  textFields,
  qrUrl,
}: {
  layout: EventQrInviteLayout | null;
  isFoldable: boolean;
  backgroundPresetSrc: string | null;
  backgroundGradient: { angle: number; stops: string[] } | null;
  backgroundSolid: string | null;
  textFields: { headline: string; subtitle: string; description: string; instructions: string[] };
  qrUrl: string;
}) {
  const canvasRef = React.useRef<HTMLCanvasElement | null>(null);
  const slots = React.useMemo(() => resolveSlots(layout, isFoldable), [layout, isFoldable]);
  const containerRef = React.useRef<HTMLDivElement | null>(null);
  const [frame, setFrame] = React.useState<{ width: number; height: number } | null>(null);

  React.useLayoutEffect(() => {
    const measure = () => {
      if (!containerRef.current) return;
      const rect = containerRef.current.getBoundingClientRect();
      const width = rect.width || 320;
      const aspect =
        layout?.preview?.aspect_ratio ??
        ((layout?.orientation || '').toLowerCase() === 'landscape' || isFoldable ? 297 / 210 : 210 / 297);
      const height = width / aspect;
      setFrame({ width, height });
    };
    measure();
    window.addEventListener('resize', measure);
    return () => window.removeEventListener('resize', measure);
  }, [layout?.orientation, layout?.preview?.aspect_ratio, isFoldable]);

  React.useEffect(() => {
    if (!frame) return;
    const baseWidth = frame.width;
    const baseHeight = frame.height;
    let canvas: FabricNS.Canvas | null = null;
    let disposed = false;

    async function render() {
      const mod = await import('fabric');
      const fabricLib: typeof FabricNS | undefined = (mod as any)?.fabric ?? (mod as any)?.default ?? (mod as any);
      const canvasEl = canvasRef.current;
      const fallbackColor = backgroundSolid ?? '#f8fafc';
      if (canvasEl) {
        const ctx = canvasEl.getContext('2d');
        if (ctx) {
          ctx.fillStyle = fallbackColor;
          ctx.fillRect(0, 0, baseWidth, baseHeight);
        }
      }
      if (!fabricLib || !fabricLib.Canvas || !canvasEl) {
        return;
      }
      if (disposed || !canvasRef.current) return;
      const fabric: typeof FabricNS = fabricLib as unknown as typeof FabricNS;

      canvasEl.width = baseWidth;
      canvasEl.height = baseHeight;

      canvas = new fabric.Canvas(canvasRef.current, {
        selection: false,
        hoverCursor: 'default',
      });
      canvas.setWidth(baseWidth);
      canvas.setHeight(baseHeight);
      canvas.clear();
      const lower = (canvas as any).lowerCanvasEl as HTMLCanvasElement | undefined;
      const upper = (canvas as any).upperCanvasEl as HTMLCanvasElement | undefined;
      [lower, upper].forEach((el, idx) => {
        if (!el) return;
        el.style.position = 'absolute';
        el.style.left = '0';
        el.style.top = '0';
        el.style.width = '100%';
        el.style.height = '100%';
        el.style.setProperty('background-color', 'transparent', 'important');
        el.style.pointerEvents = 'none';
        el.style.zIndex = idx === 0 ? '1' : '2';
      });
      canvas.set('backgroundColor', 'transparent');
      canvas.renderAll();

      const textColor = layout?.preview?.text ?? '#0f172a';
      const panelWidth = isFoldable ? baseWidth / 2 : baseWidth;
      const panelHeight = baseHeight;

      const drawPanel = async (offset: number, mirrored: boolean) => {
        const placeX = (slotX: number, slotW: number) =>
          mirrored ? offset + (panelWidth - (slotX + slotW) * panelWidth) : offset + slotX * panelWidth;

        // Headline
        if (textFields.headline) {
          const slot = slots.headline;
          const txt = new fabric.Textbox(textFields.headline, {
            left: placeX(slot.x, slot.w),
            top: slot.y * panelHeight,
            width: slot.w * panelWidth,
            fontSize: slot.fontSize ?? 28,
            fontWeight: slot.fontWeight ?? '800',
            fill: textColor,
            textAlign: slot.align ?? 'center',
            selectable: false,
            evented: false,
          });
          canvas!.add(txt);
        }

        if (textFields.subtitle) {
          const slot = slots.subtitle;
          const txt = new fabric.Textbox(textFields.subtitle, {
            left: placeX(slot.x, slot.w),
            top: slot.y * panelHeight,
            width: slot.w * panelWidth,
            fontSize: slot.fontSize ?? 18,
            fontWeight: slot.fontWeight ?? '600',
            fill: textColor,
            textAlign: slot.align ?? 'center',
            selectable: false,
            evented: false,
          });
          canvas!.add(txt);
        }

        if (textFields.description) {
          const slot = slots.description;
          const txt = new fabric.Textbox(textFields.description, {
            left: placeX(slot.x, slot.w),
            top: slot.y * panelHeight,
            width: slot.w * panelWidth,
            fontSize: slot.fontSize ?? 16,
            lineHeight: slot.lineHeight ?? 1.35,
            fill: textColor,
            textAlign: slot.align ?? 'center',
            selectable: false,
            evented: false,
          });
          canvas!.add(txt);
        }

        const instructions = textFields.instructions.filter((i) => i.trim().length > 0);
        if (instructions.length) {
          const slot = slots.instructions;
          const txt = new fabric.Textbox(
            instructions
              .map((line, idx) => `${idx + 1}. ${line}`)
              .join('\n'),
            {
              left: placeX(slot.x, slot.w),
              top: slot.y * panelHeight,
              width: slot.w * panelWidth,
              fontSize: slot.fontSize ?? 14,
              lineHeight: slot.lineHeight ?? 1.3,
              fill: textColor,
              textAlign: slot.align ?? 'left',
              selectable: false,
              evented: false,
            }
          );
          canvas!.add(txt);
        }

        if (qrUrl) {
          const slot = slots.qr;
          const size = slot.w * panelWidth;
          const left = placeX(slot.x, slot.w);
          const top = slot.y * panelHeight;
          await new Promise<void>((resolve) => {
            fabric.Image.fromURL(
              qrUrl,
              (img) => {
                if (!img) return resolve();
                img.set({
                  left,
                  top,
                  selectable: false,
                  evented: false,
                  objectCaching: false,
                });
                img.scaleToWidth(size);
                img.scaleToHeight(size);
                canvas!.add(img);
                resolve();
              },
              { crossOrigin: qrUrl.startsWith(window.location.origin) ? undefined : 'anonymous' }
            );
          });
        }
      };

      await drawPanel(0, false);
      if (isFoldable) {
        await drawPanel(panelWidth, true);
      }

      canvas.renderAll();
    }

    render();

    return () => {
      disposed = true;
      if (canvas) {
        canvas.dispose();
      }
    };
  }, [
    backgroundGradient,
    backgroundPresetSrc,
    backgroundSolid,
    frame,
    isFoldable,
    layout?.preview?.text,
    qrUrl,
    slots,
    textFields.description,
    textFields.headline,
    textFields.instructions,
    textFields.subtitle,
  ]);

  return (
    <YStack
      ref={containerRef}
      position="relative"
      width="100%"
      height="100%"
      borderRadius={16}
      overflow="hidden"
      backgroundColor="transparent"
      borderWidth={1}
      borderColor="#e5e7eb"
      minHeight={frame?.height ?? undefined}
    >
      <canvas
        ref={canvasRef}
        style={{
          width: frame ? `${frame.width}px` : '100%',
          height: frame ? `${frame.height}px` : '100%',
          display: 'block',
          backgroundColor: 'transparent',
          position: 'absolute',
          inset: 0,
          pointerEvents: 'none',
        }}
      />
    </YStack>
  );
}

function buildFabricOptions({
  layout,
  isFoldable,
  backgroundGradient,
  backgroundSolid,
  backgroundImageUrl,
  textFields,
  qrUrl,
  qrPngUrl,
  baseWidth = CANVAS_WIDTH,
  baseHeight = CANVAS_HEIGHT,
}: {
  layout: EventQrInviteLayout | null;
  isFoldable: boolean;
  backgroundGradient: { angle: number; stops: string[] } | null;
  backgroundSolid: string | null;
  backgroundImageUrl: string | null;
  textFields: { headline: string; subtitle: string; description: string; instructions: string[] };
  qrUrl: string;
  qrPngUrl?: string | null;
  baseWidth?: number;
  baseHeight?: number;
}) {
  const scaleX = CANVAS_WIDTH / baseWidth;
  const scaleY = CANVAS_HEIGHT / baseHeight;
  const panelWidth = isFoldable ? baseWidth / 2 : baseWidth;
  const panelHeight = baseHeight;
  const slots = resolveSlots(layout, isFoldable);

  const elements: LayoutElement[] = [];
  const textColor = layout?.preview?.text ?? '#0f172a';
  const accentColor = layout?.preview?.accent ?? '#2563EB';
  const secondaryColor = layout?.preview?.secondary ?? '#1f2937';
  const badgeColor = layout?.preview?.badge ?? accentColor;

  const addTextElement = (
    type: LayoutElement['type'],
    content: string,
    slot: SlotDefinition,
    opts: { panelOffset: number; mirrored: boolean }
  ) => {
    if (!content) return;
    const widthPx = slot.w * panelWidth * scaleX;
    const heightPx = (slot.h ?? 0.12) * panelHeight * scaleY;
    const xBase = opts.mirrored
      ? opts.panelOffset + (panelWidth - (slot.x + slot.w) * panelWidth)
      : opts.panelOffset + slot.x * panelWidth;
    const xPx = xBase * scaleX;
    const yPx = slot.y * panelHeight * scaleY;

    elements.push({
      id: `${type}-${opts.panelOffset}-${opts.mirrored ? 'm' : 'n'}`,
      type,
      x: xPx,
      y: yPx,
      width: widthPx,
      height: heightPx,
      fontSize: slot.fontSize ?? (type === 'headline' ? 30 : type === 'subtitle' ? 18 : 16),
      align: slot.align ?? 'center',
      lineHeight: slot.lineHeight ?? 1.35,
      content,
      fill: textColor,
    });
  };

  const addQr = (slot: SlotDefinition, opts: { panelOffset: number; mirrored: boolean }) => {
    if (!qrUrl) return;
    const sizePx = slot.w * panelWidth * scaleX;
    const xBase = opts.mirrored
      ? opts.panelOffset + (panelWidth - (slot.x + slot.w) * panelWidth)
      : opts.panelOffset + slot.x * panelWidth;
    const xPx = xBase * scaleX;
    const yPx = slot.y * panelHeight * scaleY;
    elements.push({
      id: `qr-${opts.panelOffset}-${opts.mirrored ? 'm' : 'n'}`,
      type: 'qr',
      x: xPx,
      y: yPx,
      width: sizePx,
      height: sizePx,
    });
  };

  const instructions = textFields.instructions.filter((i) => i.trim().length > 0);
  const instructionContent = instructions.map((line, idx) => `${idx + 1}. ${line}`).join('\n');

  const drawPanelElements = (panelOffset: number, mirrored: boolean) => {
    addTextElement('headline', textFields.headline, slots.headline, { panelOffset, mirrored });
    addTextElement('subtitle', textFields.subtitle, slots.subtitle, { panelOffset, mirrored });
    addTextElement('description', textFields.description, slots.description, { panelOffset, mirrored });
    if (instructionContent) {
      addTextElement('description', instructionContent, slots.instructions, { panelOffset, mirrored });
    }
    addQr(slots.qr, { panelOffset, mirrored });
  };

  drawPanelElements(0, false);
  if (isFoldable) {
    drawPanelElements(panelWidth, true);
  }

  return {
    elements,
    accentColor,
    textColor,
    secondaryColor,
    badgeColor,
    qrCodeDataUrl: qrPngUrl ?? (qrUrl ? `https://api.qrserver.com/v1/create-qr-code/?size=400x400&data=${encodeURIComponent(qrUrl)}` : null),
    logoDataUrl: null,
    backgroundColor: backgroundSolid ?? layout?.preview?.background ?? '#ffffff',
    backgroundGradient,
    backgroundImageUrl,
    readOnly: true,
  } as const;
}

function BackgroundStep({
  onBack,
  presets,
  selectedPreset,
  onSelectPreset,
  selectedLayout,
  layouts,
  onSelectGradient,
  onSelectSolid,
  selectedGradient,
  selectedSolid,
  onSave,
  saving,
}: {
  onBack: () => void;
  presets: { id: string; src: string; label: string }[];
  selectedPreset: string | null;
  onSelectPreset: (id: string) => void;
  selectedLayout: EventQrInviteLayout | null;
  layouts: EventQrInviteLayout[];
  onSelectGradient: (gradient: { angle: number; stops: string[] } | null) => void;
  onSelectSolid: (color: string | null) => void;
  selectedGradient: { angle: number; stops: string[] } | null;
  selectedSolid: string | null;
  onSave: () => void;
  saving: boolean;
}) {
  const { t } = useTranslation('management');
  const resolvedLayout =
    selectedLayout ??
    layouts.find((layout) => {
      const panel = (layout.panel_mode ?? '').toLowerCase();
      const orientation = (layout.orientation ?? '').toLowerCase();
      return panel === 'double-mirror' || orientation === 'landscape';
    }) ??
    layouts[0] ??
    null;

  const isFoldable = (resolvedLayout?.panel_mode ?? '').toLowerCase() === 'double-mirror';
  const formatLabel = isFoldable ? 'A4 Landscape (Double/Mirror)' : 'A4 Portrait';
  const disablePresets = isFoldable;
  const gradientPresets = [
    { angle: 180, stops: ['#F8FAFC', '#EEF2FF', '#F8FAFC'], label: 'Soft Lilac' },
    { angle: 135, stops: ['#FEE2E2', '#EFF6FF', '#ECFDF3'], label: 'Pastell' },
    { angle: 210, stops: ['#0B132B', '#1C2541', '#274690'], label: 'Midnight' },
  ];
  const solidPresets = ['#FFFFFF', '#F8FAFC', '#0F172A', '#2563EB', '#F59E0B'];

  return (
    <YStack space="$3" marginTop="$2">
      <XStack alignItems="center" space="$2">
        <Pressable onPress={onBack}>
          <XStack alignItems="center" space="$1">
            <ArrowLeft size={16} color="#111827" />
            <Text fontSize="$sm" color="#111827">
              {t('common.back', 'Zurück')}
            </Text>
          </XStack>
        </Pressable>
        <PillBadge tone="muted">{formatLabel}</PillBadge>
      </XStack>

      <YStack space="$2">
        <Text fontSize="$sm" fontWeight="700" color="#111827">
          {disablePresets
            ? t('events.qr.backgroundPicker', 'Hintergrund für A5 (Gradient/Farbe)')
            : t('events.qr.backgroundPicker', `Hintergrund auswählen (${formatLabel})`)}
        </Text>
        {!disablePresets ? (
          <>
            <XStack flexWrap="wrap" gap="$2">
              {presets.map((preset) => {
                const isSelected = selectedPreset === preset.id;
                return (
                  <Pressable key={preset.id} onPress={() => onSelectPreset(preset.id)} style={{ width: '48%' }}>
                    <YStack
                      aspectRatio={210 / 297}
                      maxHeight={220}
                      borderRadius={14}
                      overflow="hidden"
                      borderWidth={isSelected ? 2 : 1}
                      borderColor={isSelected ? '#2563EB' : '#e5e7eb'}
                      backgroundColor="#f8fafc"
                    >
                      <YStack
                        flex={1}
                        backgroundImage={`url(${preset.src})`}
                        backgroundSize="cover"
                        backgroundPosition="center"
                      />
                      <XStack padding="$2" justifyContent="space-between" alignItems="center" backgroundColor="rgba(255,255,255,0.92)">
                        <Text fontSize="$xs" color="#111827">
                          {preset.label}
                        </Text>
                        {isSelected ? <PillBadge tone="muted">{t('common.selected', 'Ausgewählt')}</PillBadge> : null}
                      </XStack>
                    </YStack>
                  </Pressable>
                );
              })}
            </XStack>
            <Text fontSize="$xs" color="#6b7280">
              {t('events.qr.backgroundNote', 'Diese Presets sind für A4 Hochformat. Spiegelung erfolgt automatisch bei Tischkarten.')}
            </Text>
          </>
        ) : (
          <Text fontSize="$xs" color="#6b7280">
            {t('events.qr.foldableBackgroundNote', 'Für A5 Tischkarten bitte Gradient oder Vollfarbe wählen.')}
          </Text>
        )}

        <YStack space="$2" marginTop="$2">
          <Text fontSize="$sm" fontWeight="700" color="#111827">
            {t('events.qr.gradients', 'Gradienten')}
          </Text>
          <XStack flexWrap="wrap" gap="$2">
            {gradientPresets.map((gradient, idx) => {
              const isSelected =
                selectedGradient &&
                selectedGradient.angle === gradient.angle &&
                JSON.stringify(selectedGradient.stops) === JSON.stringify(gradient.stops);
              const style = {
                backgroundImage: `linear-gradient(${gradient.angle}deg, ${gradient.stops.join(',')})`,
              } as React.CSSProperties;
              return (
                <Pressable key={idx} onPress={() => onSelectGradient(gradient)} style={{ width: '30%' }}>
                  <YStack
                    height={70}
                    borderRadius={12}
                    overflow="hidden"
                    borderWidth={isSelected ? 2 : 1}
                    borderColor={isSelected ? '#2563EB' : '#e5e7eb'}
                    style={style}
                  />
                </Pressable>
              );
            })}
          </XStack>
        </YStack>

        <YStack space="$2" marginTop="$2">
          <Text fontSize="$sm" fontWeight="700" color="#111827">
            {t('events.qr.colors', 'Vollfarbe')}
          </Text>
          <XStack flexWrap="wrap" gap="$2">
            {solidPresets.map((color) => {
              const isSelected = selectedSolid === color;
              return (
                <Pressable key={color} onPress={() => onSelectSolid(color)} style={{ width: '20%' }}>
                  <YStack
                    height={50}
                    borderRadius={12}
                    overflow="hidden"
                    borderWidth={isSelected ? 2 : 1}
                    borderColor={isSelected ? '#2563EB' : '#e5e7eb'}
                    backgroundColor={color}
                  />
                </Pressable>
              );
            })}
          </XStack>
        </YStack>
      </YStack>

      <CTAButton
        label={saving ? t('common.saving', 'Speichern …') : t('common.next', 'Weiter')}
        disabled={saving || !resolvedLayout}
        onPress={onSave}
      />
    </YStack>
  );
}

function TextStep({
  onBack,
  textFields,
  onChange,
  onSave,
  saving,
}: {
  onBack: () => void;
  textFields: { headline: string; subtitle: string; description: string; instructions: string[] };
  onChange: (fields: { headline: string; subtitle: string; description: string; instructions: string[] }) => void;
  onSave: () => void;
  saving: boolean;
}) {
  const { t } = useTranslation('management');

  const updateField = (key: 'headline' | 'subtitle' | 'description', value: string) => {
    onChange({ ...textFields, [key]: value });
  };

  const updateInstruction = (idx: number, value: string) => {
    const next = [...textFields.instructions];
    next[idx] = value;
    onChange({ ...textFields, instructions: next });
  };

  const addInstruction = () => {
    onChange({ ...textFields, instructions: [...textFields.instructions, ''] });
  };

  const removeInstruction = (idx: number) => {
    const next = textFields.instructions.filter((_, i) => i !== idx);
    onChange({ ...textFields, instructions: next.length ? next : [''] });
  };

  return (
    <YStack space="$3" marginTop="$2">
      <XStack alignItems="center" space="$2">
        <Pressable onPress={onBack}>
          <XStack alignItems="center" space="$1">
            <ArrowLeft size={16} color="#111827" />
            <Text fontSize="$sm" color="#111827">
              {t('common.back', 'Zurück')}
            </Text>
          </XStack>
        </Pressable>
        <PillBadge tone="muted">{t('events.qr.textStep', 'Texte & Hinweise')}</PillBadge>
      </XStack>

      <YStack space="$2">
        <Text fontSize="$sm" fontWeight="700" color="#111827">
          {t('events.qr.textFields', 'Texte')}
        </Text>
        <Input
          placeholder={t('events.qr.headline', 'Headline')}
          value={textFields.headline}
          onChangeText={(val) => updateField('headline', val)}
          size="$4"
        />
        <Input
          placeholder={t('events.qr.subtitle', 'Subtitle')}
          value={textFields.subtitle}
          onChangeText={(val) => updateField('subtitle', val)}
          size="$4"
        />
        <TextArea
          placeholder={t('events.qr.description', 'Beschreibung')}
          value={textFields.description}
          onChangeText={(val) => updateField('description', val)}
          size="$4"
          numberOfLines={3}
        />
      </YStack>

      <YStack space="$2">
          <Text fontSize="$sm" fontWeight="700" color="#111827">
            {t('events.qr.instructions', 'Anleitung')}
          </Text>
        {textFields.instructions.map((item, idx) => (
          <XStack key={idx} alignItems="center" space="$2">
            <TextArea
              value={item}
              onChangeText={(val) => updateInstruction(idx, val)}
              placeholder={t('events.qr.instructionsPlaceholder', 'Schritt hinzufügen')}
              numberOfLines={2}
              flex={1}
              size="$4"
            />
            <Pressable onPress={() => removeInstruction(idx)} disabled={textFields.instructions.length === 1}>
              <XStack
                width={44}
                height={44}
                borderRadius={12}
                alignItems="center"
                justifyContent="center"
                borderWidth={1}
                borderColor="#e5e7eb"
                backgroundColor="#fff"
              >
                <Text fontSize="$md" fontWeight="800" color="#111827">
                  –
                </Text>
              </XStack>
            </Pressable>
            {idx === textFields.instructions.length - 1 ? (
              <Pressable onPress={addInstruction}>
                <XStack
                  width={44}
                  height={44}
                  borderRadius={12}
                  alignItems="center"
                  justifyContent="center"
                  borderWidth={1}
                  borderColor="#e5e7eb"
                  backgroundColor="#fff"
                >
                  <Plus size={18} color="#111827" />
                </XStack>
              </Pressable>
            ) : null}
          </XStack>
        ))}
      </YStack>

      <CTAButton
        label={saving ? t('common.saving', 'Speichern …') : t('events.qr.preview', 'Vorschau')}
        disabled={saving}
        onPress={onSave}
      />
    </YStack>
  );
}

function PreviewStep({
  onBack,
  layout,
  backgroundPreset,
  backgroundGradient,
  backgroundSolid,
  presets,
  textFields,
  qrUrl,
  qrPngUrl,
  isFoldable,
  onExport,
}: {
  onBack: () => void;
  layout: EventQrInviteLayout | null;
  backgroundPreset: string | null;
  backgroundGradient: { angle: number; stops: string[] } | null;
  backgroundSolid: string | null;
  presets: { id: string; src: string; label: string }[];
  textFields: { headline: string; subtitle: string; description: string; instructions: string[] };
  qrUrl: string;
  qrPngUrl?: string | null;
  isFoldable: boolean;
  onExport: (format: 'pdf' | 'png') => void;
}) {
  const { t } = useTranslation('management');
  const presetSrc = backgroundPreset ? presets.find((p) => p.id === backgroundPreset)?.src ?? null : null;
  const resolvedBgGradient =
    backgroundGradient ??
    layout?.preview?.background_gradient ??
    (layout?.preview?.background
      ? { angle: 180, stops: [layout.preview.background, layout.preview.background, layout.preview.background] }
      : null);
  const resolvedBgSolid = backgroundSolid ?? layout?.preview?.background ?? null;
  const aspectRatio =
    layout?.preview?.aspect_ratio ??
    ((layout?.orientation || '').toLowerCase() === 'landscape' ? 297 / 210 : 210 / 297);
  const backgroundImageUrl = presetSrc ?? null;
  const exportOptions = React.useMemo(() => {
    const svgWidth = (layout as any)?.preview?.svg?.width ?? CANVAS_WIDTH;
    const svgHeight = (layout as any)?.preview?.svg?.height ?? CANVAS_HEIGHT;
    return buildFabricOptions({
      layout,
      isFoldable,
      backgroundGradient: resolvedBgGradient,
      backgroundSolid: resolvedBgSolid,
      backgroundImageUrl,
      textFields,
      qrUrl,
      qrPngUrl,
      baseWidth: svgWidth,
      baseHeight: svgHeight,
    });
  }, [backgroundImageUrl, isFoldable, layout, qrUrl, qrPngUrl, resolvedBgGradient, resolvedBgSolid, textFields]);

  const cssBg = React.useMemo(() => {
    if (presetSrc) {
      const url = presetSrc.startsWith('http') || presetSrc.startsWith('data:') ? presetSrc : `${window.location.origin}${presetSrc}`;
      return { backgroundImage: `url(${url})`, backgroundSize: 'cover', backgroundPosition: 'center' } as React.CSSProperties;
    }
    if (resolvedBgGradient) {
      return {
        backgroundImage: `linear-gradient(${resolvedBgGradient.angle}deg, ${resolvedBgGradient.stops.join(',')})`,
      } as React.CSSProperties;
    }
    return { backgroundColor: resolvedBgSolid ?? '#f8fafc' } as React.CSSProperties;
  }, [presetSrc, resolvedBgGradient, resolvedBgSolid]);

  const qrImageSrc = qrPngUrl ?? qrUrl ?? '';
  const qrSlot = React.useMemo(() => resolveSlots(layout, isFoldable).qr, [layout, isFoldable]);

  const Panel = () => (
    <YStack width="100%" maxWidth={380} aspectRatio={aspectRatio} borderRadius={16} overflow="hidden" position="relative" style={cssBg}>
      {presetSrc ? (
        <img
          src={presetSrc.startsWith('http') || presetSrc.startsWith('data:') ? presetSrc : `${window.location.origin}${presetSrc}`}
          alt=""
          aria-hidden
          style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', zIndex: 0 }}
        />
      ) : null}
      {qrImageSrc ? (
        <>
          <img
            src={qrImageSrc}
            alt="QR"
            style={{
              position: 'absolute',
              left: `${qrSlot.x * 100}%`,
              top: `${qrSlot.y * 100}%`,
              width: `${qrSlot.w * 100}%`,
              height: 'auto',
              objectFit: 'contain',
              zIndex: 5,
            }}
          />
          {isFoldable ? (
            <img
              src={qrImageSrc}
              alt="QR"
              style={{
                position: 'absolute',
                left: `${50 + qrSlot.x * 50}%`,
                top: `${qrSlot.y * 100}%`,
                width: `${qrSlot.w * 50}%`,
                height: 'auto',
                objectFit: 'contain',
                transform: 'scaleX(-1)',
                zIndex: 5,
              }}
            />
          ) : null}
        </>
      ) : null}
      <FabricCanvasPreview
        layout={layout}
        isFoldable={isFoldable}
        backgroundPresetSrc={presetSrc}
        backgroundGradient={resolvedBgGradient}
        backgroundSolid={resolvedBgSolid}
        textFields={textFields}
        qrUrl={qrPngUrl ?? qrUrl}
      />
    </YStack>
  );

  return (
    <YStack space="$3" marginTop="$2">
      <XStack alignItems="center" space="$2">
        <Pressable onPress={onBack}>
          <XStack alignItems="center" space="$1">
            <ArrowLeft size={16} color="#111827" />
            <Text fontSize="$sm" color="#111827">
              {t('common.back', 'Zurück')}
            </Text>
          </XStack>
        </Pressable>
        <PillBadge tone="muted">{isFoldable ? 'A5 Foldable' : 'A4 Poster'}</PillBadge>
      </XStack>

      <YStack space="$2">
        <Text fontSize="$sm" fontWeight="700" color="#111827">
          {t('events.qr.preview', 'Vorschau')}
        </Text>
        {isFoldable ? (
          <Panel />
        ) : (
          <Panel />
        )}
      </YStack>

      <XStack space="$2">
        <CTAButton
          label={t('events.qr.exportPdf', 'Export PDF')}
          onPress={async () => {
            try {
              const pdfBytes = await generatePdfBytes(exportOptions, 'a4', isFoldable ? 'landscape' : 'portrait');
              const blob = new Blob([pdfBytes], { type: 'application/pdf' });
              triggerDownloadFromBlob(blob, 'qr-layout.pdf');
            } catch (err) {
              toast.error(t('events.qr.exportFailed', 'Export fehlgeschlagen'));
              console.error(err);
            }
          }}
        />
        <CTAButton
          label={t('events.qr.exportPng', 'Export PNG')}
          onPress={async () => {
            try {
              const dataUrl = await generatePngDataUrl(exportOptions);
              await triggerDownloadFromDataUrl(dataUrl, 'qr-layout.png');
            } catch (err) {
              toast.error(t('events.qr.exportFailed', 'Export fehlgeschlagen'));
              console.error(err);
            }
          }}
        />
      </XStack>
    </YStack>
  );
}
