import { Head, Link, useForm, usePage } from '@inertiajs/react';
import type { ClipboardEvent, DragEvent, FormEvent, MouseEvent } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import '../../../css/invitation-editor.css';

type InvitationTemplate = {
    id: string;
    name: string;
    description: string | null;
    eyebrow: string;
    headline: string;
    bodyHtml: string;
    closingText: string | null;
};

type PlaceholderDefinition = {
    token: string;
    label: string;
    description: string;
    example: string;
};

type PreviewRecipient = {
    id: string;
    label: string;
    salutation: string;
    primaryFirstName: string | null;
};

type MediaType = 'none' | 'image' | 'video';

type InvitationDesign = {
    eyebrow: string;
    headline: string;
    bodyHtml: string;
    closingText: string | null;
    mediaType: MediaType;
    mediaUrl: string | null;
    mediaName: string | null;
    updatedAt: string | null;
};

type PageProps = {
    wedding: { name: string; timezone: string };
    event: { id: string; title: string; startsAt: string; venueName: string | null };
    design: InvitationDesign;
    templates: InvitationTemplate[];
    placeholders: PlaceholderDefinition[];
    previewRecipients: PreviewRecipient[];
    flash: { success: string | null };
    errors: Record<string, string>;
};

type InvitationForm = {
    eyebrow: string;
    headline: string;
    body_html: string;
    closing_text: string;
    media_type: MediaType;
    media_file: File | null;
    remove_existing_media: boolean;
};

type SavedSnapshot = Omit<InvitationForm, 'media_file' | 'remove_existing_media'>;

const escapeHtml = (value: string) => value
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;')
    .replaceAll("'", '&#039;');

const isEditorEmpty = (html: string) => {
    const element = document.createElement('div');
    element.innerHTML = html;
    return (element.textContent ?? '').replaceAll('\u00a0', ' ').trim().length === 0;
};

const formatEventDate = (value: string, timeZone: string) => {
    const date = new Date(value);
    if (Number.isNaN(date.getTime())) return 'Termin noch nicht verfügbar';
    try {
        return new Intl.DateTimeFormat('de-DE', {
            timeZone,
            weekday: 'long',
            day: '2-digit',
            month: 'long',
            year: 'numeric',
            hour: '2-digit',
            minute: '2-digit',
        }).format(date);
    } catch {
        return new Intl.DateTimeFormat('de-DE', { dateStyle: 'long', timeStyle: 'short' }).format(date);
    }
};

export default function InvitationEditor() {
    const { wedding, event, design, templates, placeholders, previewRecipients, flash, errors } = usePage<PageProps>().props;
    const form = useForm<InvitationForm>({
        eyebrow: design.eyebrow,
        headline: design.headline,
        body_html: design.bodyHtml,
        closing_text: design.closingText ?? '',
        media_type: design.mediaType,
        media_file: null,
        remove_existing_media: false,
    });
    const [savedSnapshot, setSavedSnapshot] = useState<SavedSnapshot>({
        eyebrow: design.eyebrow,
        headline: design.headline,
        body_html: design.bodyHtml,
        closing_text: design.closingText ?? '',
        media_type: design.mediaType,
    });
    const [selectedRecipientId, setSelectedRecipientId] = useState(previewRecipients[0]?.id ?? '');
    const [localMediaUrl, setLocalMediaUrl] = useState<string | null>(null);
    const [mediaInputKey, setMediaInputKey] = useState(0);
    const [editorError, setEditorError] = useState<string | null>(null);
    const [editorNotice, setEditorNotice] = useState<string | null>(null);
    const [copiedToken, setCopiedToken] = useState<string | null>(null);
    const editorRef = useRef<HTMLDivElement>(null);
    const savedRange = useRef<Range | null>(null);

    const selectedRecipient = previewRecipients.find((recipient) => recipient.id === selectedRecipientId) ?? null;
    const isDirty = form.data.eyebrow !== savedSnapshot.eyebrow
        || form.data.headline !== savedSnapshot.headline
        || form.data.body_html !== savedSnapshot.body_html
        || form.data.closing_text !== savedSnapshot.closing_text
        || form.data.media_type !== savedSnapshot.media_type
        || form.data.media_file !== null
        || form.data.remove_existing_media;
    const visibleExistingMedia = !form.data.remove_existing_media
        && design.mediaUrl
        && design.mediaType === form.data.media_type;
    const mediaPreviewUrl = localMediaUrl ?? (visibleExistingMedia ? design.mediaUrl : null);
    const generalError = errors.invitation ?? errors.design ?? errors.general;

    useEffect(() => {
        if (!isDirty) return;

        const warnBeforeLeaving = (event: BeforeUnloadEvent) => {
            event.preventDefault();
        };
        window.addEventListener('beforeunload', warnBeforeLeaving);

        return () => window.removeEventListener('beforeunload', warnBeforeLeaving);
    }, [isDirty]);

    useEffect(() => {
        if (!form.data.media_file) {
            setLocalMediaUrl(null);
            return;
        }
        const objectUrl = URL.createObjectURL(form.data.media_file);
        setLocalMediaUrl(objectUrl);
        return () => URL.revokeObjectURL(objectUrl);
    }, [form.data.media_file]);

    const placeholderValues = useMemo(() => {
        const values = new Map(placeholders.map((placeholder) => [placeholder.token, placeholder.example]));
        placeholders.forEach((placeholder) => {
            if (placeholder.token === '{{wedding.name}}') values.set(placeholder.token, wedding.name);
            if (placeholder.token === '{{event.title}}') values.set(placeholder.token, event.title);
            if (placeholder.token === '{{group.label}}' && selectedRecipient) values.set(placeholder.token, selectedRecipient.label);
            if (placeholder.token === '{{guest.salutation}}' && selectedRecipient) values.set(placeholder.token, selectedRecipient.salutation);
            if (placeholder.token === '{{guest.first_name}}' && selectedRecipient?.primaryFirstName) values.set(placeholder.token, selectedRecipient.primaryFirstName);
        });
        return values;
    }, [event.title, placeholders, selectedRecipient, wedding.name]);

    const replacePlaceholders = (value: string, htmlContext = false) => {
        let result = value;
        placeholderValues.forEach((replacement, token) => {
            result = result.split(token).join(htmlContext ? escapeHtml(replacement) : replacement);
        });
        return result;
    };

    const previewDocument = useMemo(() => {
        const media = mediaPreviewUrl
            ? form.data.media_type === 'video'
                ? `<video controls preload="metadata" src="${escapeHtml(mediaPreviewUrl)}"></video>`
                : form.data.media_type === 'image'
                    ? `<img src="${escapeHtml(mediaPreviewUrl)}" alt="" />`
                    : ''
            : '';
        return `<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><style>
            *{box-sizing:border-box}body{margin:0;padding:24px;color:#3f4740;background:#fbf7ef;font-family:Arial,sans-serif}.card{max-width:620px;margin:auto;padding:clamp(24px,7vw,52px);border:1px solid #d4d7d3;border-radius:24px;background:#fffdf9;box-shadow:0 20px 55px rgba(63,71,64,.1);text-align:center}.eyebrow{color:#586c56;font-size:11px;font-weight:700;letter-spacing:.2em;text-transform:uppercase}h1{margin:12px 0 18px;color:#586c56;font:400 clamp(36px,9vw,62px)/1 Georgia,serif;letter-spacing:-.04em}.ornament{margin:15px 0;color:#7999a1}.body{color:#606b63;font:17px/1.7 Georgia,serif;text-align:left}.body a{color:#586c56}.closing{margin:28px 0 0;color:#586c56;font:20px Georgia,serif}img,video{display:block;width:100%;max-height:300px;margin:0 0 25px;border-radius:16px;object-fit:cover}ul,ol{padding-left:24px}</style></head><body><article class="card">${media}<div class="eyebrow">${escapeHtml(replacePlaceholders(form.data.eyebrow))}</div><h1>${escapeHtml(replacePlaceholders(form.data.headline))}</h1><div class="ornament">◇</div><div class="body">${replacePlaceholders(form.data.body_html, true)}</div>${form.data.closing_text ? `<p class="closing">${escapeHtml(replacePlaceholders(form.data.closing_text))}</p>` : ''}</article></body></html>`;
    }, [form.data.body_html, form.data.closing_text, form.data.eyebrow, form.data.headline, form.data.media_type, mediaPreviewUrl, placeholderValues]);

    const syncEditor = () => {
        const html = editorRef.current?.innerHTML ?? '';
        form.setData('body_html', html);
        setEditorError(null);
    };

    const rememberSelection = () => {
        const selection = window.getSelection();
        if (!selection || selection.rangeCount === 0 || !editorRef.current) return;
        const range = selection.getRangeAt(0);
        if (editorRef.current.contains(range.commonAncestorContainer)) savedRange.current = range.cloneRange();
    };

    const restoreSelection = () => {
        editorRef.current?.focus();
        const selection = window.getSelection();
        if (!selection) return;
        selection.removeAllRanges();
        if (savedRange.current && editorRef.current?.contains(savedRange.current.commonAncestorContainer)) {
            selection.addRange(savedRange.current);
        } else if (editorRef.current) {
            const range = document.createRange();
            range.selectNodeContents(editorRef.current);
            range.collapse(false);
            selection.addRange(range);
        }
    };

    const runEditorCommand = (command: string, value?: string) => {
        restoreSelection();
        document.execCommand(command, false, value);
        rememberSelection();
        syncEditor();
    };

    const createLink = () => {
        const url = window.prompt('Linkadresse eingeben (https://… oder mailto:…)');
        if (url === null) return;
        if (!/^(https?:\/\/|mailto:)/i.test(url.trim())) {
            setEditorNotice('Bitte verwende eine vollständige https://-, http://- oder mailto:-Adresse.');
            return;
        }
        setEditorNotice(null);
        runEditorCommand('createLink', url.trim());
        editorRef.current?.querySelectorAll('a').forEach((link) => link.setAttribute('rel', 'noopener noreferrer'));
        syncEditor();
    };

    const insertPlainText = (text: string) => {
        restoreSelection();
        document.execCommand('insertText', false, text);
        rememberSelection();
        syncEditor();
    };

    const handlePaste = (pasteEvent: ClipboardEvent<HTMLDivElement>) => {
        pasteEvent.preventDefault();
        insertPlainText(pasteEvent.clipboardData.getData('text/plain'));
    };

    const handleDrop = (dropEvent: DragEvent<HTMLDivElement>) => {
        dropEvent.preventDefault();
        const text = dropEvent.dataTransfer.getData('text/plain');
        if (text) insertPlainText(text);
    };

    const insertPlaceholder = (token: string) => {
        insertPlainText(token);
        setEditorNotice(`Platzhalter ${token} wurde eingefügt.`);
    };

    const copyPlaceholder = async (token: string) => {
        try {
            if (navigator.clipboard?.writeText) {
                await navigator.clipboard.writeText(token);
            } else {
                const textarea = document.createElement('textarea');
                textarea.value = token;
                textarea.style.position = 'fixed';
                textarea.style.opacity = '0';
                document.body.appendChild(textarea);
                textarea.select();
                document.execCommand('copy');
                textarea.remove();
            }
            setCopiedToken(token);
            window.setTimeout(() => setCopiedToken(null), 1800);
        } catch {
            setEditorNotice('Der Platzhalter konnte nicht kopiert werden. Bitte markiere das Token manuell.');
        }
    };

    const applyTemplate = (template: InvitationTemplate) => {
        if (!window.confirm(`Vorlage „${template.name}“ anwenden? Aktuelle ungespeicherte Texte werden überschrieben.`)) return;
        form.setData({
            ...form.data,
            eyebrow: template.eyebrow,
            headline: template.headline,
            body_html: template.bodyHtml,
            closing_text: template.closingText ?? '',
        });
        if (editorRef.current) editorRef.current.innerHTML = template.bodyHtml;
        setEditorError(null);
        setEditorNotice(`Vorlage „${template.name}“ wurde lokal angewendet. Speichere, um sie zu übernehmen.`);
    };

    const changeMediaType = (mediaType: MediaType) => {
        const removesExisting = design.mediaType !== 'none' && mediaType !== design.mediaType;
        form.setData({ ...form.data, media_type: mediaType, media_file: null, remove_existing_media: removesExisting });
        setMediaInputKey((key) => key + 1);
    };

    const discardNewMedia = () => {
        form.setData('media_file', null);
        setMediaInputKey((key) => key + 1);
    };

    const submit = (submitEvent: FormEvent<HTMLFormElement>) => {
        submitEvent.preventDefault();
        if (form.processing) return;
        const html = editorRef.current?.innerHTML ?? form.data.body_html;
        if (isEditorEmpty(html)) {
            setEditorError('Bitte füge einen Einladungstext hinzu.');
            editorRef.current?.focus();
            return;
        }
        setEditorError(null);
        form.setData('body_html', html);
        form.transform((data) => ({ ...data, body_html: html }));
        form.put(`/veranstaltungen/${event.id}/einladung`, {
            forceFormData: true,
            preserveScroll: true,
            onSuccess: () => {
                setSavedSnapshot({
                    eyebrow: form.data.eyebrow,
                    headline: form.data.headline,
                    body_html: html,
                    closing_text: form.data.closing_text,
                    media_type: form.data.media_type,
                });
                form.setData({ ...form.data, body_html: html, media_file: null, remove_existing_media: false });
                setMediaInputKey((key) => key + 1);
            },
        });
    };

    const handleBack = (clickEvent: MouseEvent<Element>) => {
        if (isDirty && !window.confirm('Es gibt ungespeicherte Änderungen. Möchtest du die Seite wirklich verlassen?')) clickEvent.preventDefault();
    };

    return (
        <>
            <Head title={`Einladung gestalten · ${event.title}`} />
            <main className="invitation-editor-shell">
                <nav className="invitation-editor-nav" aria-label="Bereichsnavigation">
                    <div><span className="invitation-editor-eyebrow">{wedding.name}</span><strong>Einladungseditor</strong></div>
                    <Link className="invitation-editor-secondary" href="/veranstaltungen" onClick={handleBack}>Zurück zu Veranstaltungen</Link>
                </nav>

                <header className="invitation-editor-header">
                    <div><span className="invitation-editor-eyebrow">Veranstaltungseinladung</span><h1>{event.title}</h1><p>{formatEventDate(event.startsAt, wedding.timezone)}{event.venueName ? ` · ${event.venueName}` : ''}</p></div>
                    <p>Diese Einladung wird ausschließlich Gästen angezeigt, die dieser Veranstaltung zugeordnet sind.</p>
                </header>

                {flash.success && <div className="alert invitation-editor-success" role="status">{flash.success}</div>}
                {generalError && <div className="alert alert-danger invitation-editor-page-alert" role="alert">{generalError}</div>}

                <form aria-busy={form.processing} noValidate onSubmit={submit}>
                    <div className="invitation-editor-layout">
                        <div className="invitation-editor-workspace">
                            <section className="invitation-editor-panel" aria-labelledby="invitation-content-title">
                                <div className="invitation-editor-section-heading"><div><span className="invitation-editor-eyebrow">Inhalt</span><h2 id="invitation-content-title">Einladung bearbeiten</h2></div>{isDirty && <span className="invitation-editor-unsaved">Ungespeichert</span>}</div>
                                <div className="mb-3"><label className="form-label" htmlFor="invitation-eyebrow">Kurze Oberzeile</label><input className={`form-control${form.errors.eyebrow ? ' is-invalid' : ''}`} id="invitation-eyebrow" maxLength={120} onChange={(e) => form.setData('eyebrow', e.target.value)} value={form.data.eyebrow} /><div className="invitation-editor-field-meta"><span>Kurzer Einstieg über der Überschrift</span><span>{form.data.eyebrow.length}/120</span></div>{form.errors.eyebrow && <div className="invalid-feedback">{form.errors.eyebrow}</div>}</div>
                                <div className="mb-3"><label className="form-label" htmlFor="invitation-headline">Hauptüberschrift</label><input className={`form-control${form.errors.headline ? ' is-invalid' : ''}`} id="invitation-headline" maxLength={180} onChange={(e) => form.setData('headline', e.target.value)} value={form.data.headline} /><div className="invitation-editor-field-meta"><span>Der zentrale Titel eurer Einladung</span><span>{form.data.headline.length}/180</span></div>{form.errors.headline && <div className="invalid-feedback">{form.errors.headline}</div>}</div>

                                <div className="invitation-editor-rich-field">
                                    <label id="invitation-body-label">Einladungstext</label>
                                    <div className="invitation-editor-toolbar" aria-label="Text formatieren" role="toolbar">
                                        <button aria-label="Fett formatieren" onClick={() => runEditorCommand('bold')} onMouseDown={(e) => e.preventDefault()} type="button"><strong>F</strong></button>
                                        <button aria-label="Kursiv formatieren" onClick={() => runEditorCommand('italic')} onMouseDown={(e) => e.preventDefault()} type="button"><em>K</em></button>
                                        <button aria-label="Unterstrichen formatieren" onClick={() => runEditorCommand('underline')} onMouseDown={(e) => e.preventDefault()} type="button"><u>U</u></button>
                                        <button aria-label="Aufzählung einfügen" onClick={() => runEditorCommand('insertUnorderedList')} onMouseDown={(e) => e.preventDefault()} type="button">• Liste</button>
                                        <button aria-label="Nummerierte Liste einfügen" onClick={() => runEditorCommand('insertOrderedList')} onMouseDown={(e) => e.preventDefault()} type="button">1. Liste</button>
                                        <button aria-label="Link einfügen" onClick={createLink} onMouseDown={(e) => e.preventDefault()} type="button">Link</button>
                                        <button aria-label="Formatierung entfernen" onClick={() => runEditorCommand('removeFormat')} onMouseDown={(e) => e.preventDefault()} type="button">Format löschen</button>
                                    </div>
                                    <div
                                        aria-describedby="invitation-body-help"
                                        aria-invalid={Boolean(editorError || form.errors.body_html)}
                                        aria-labelledby="invitation-body-label"
                                        className={`invitation-editor-contenteditable${editorError || form.errors.body_html ? ' is-invalid' : ''}`}
                                        contentEditable={!form.processing}
                                        dangerouslySetInnerHTML={{ __html: design.bodyHtml }}
                                        onBlur={() => { rememberSelection(); syncEditor(); }}
                                        onDrop={handleDrop}
                                        onInput={syncEditor}
                                        onKeyUp={rememberSelection}
                                        onMouseUp={rememberSelection}
                                        onPaste={handlePaste}
                                        ref={editorRef}
                                        role="textbox"
                                        suppressContentEditableWarning
                                    />
                                    <div className="invitation-editor-field-meta" id="invitation-body-help"><span>Einfügen übernimmt nur Text und Zeilenumbrüche.</span><span>{(editorRef.current?.textContent ?? '').trim().length} Zeichen</span></div>
                                    {(editorError || form.errors.body_html) && <div className="invitation-editor-field-error" role="alert">{editorError || form.errors.body_html}</div>}
                                    {editorNotice && <p className="invitation-editor-notice" role="status">{editorNotice}</p>}
                                </div>

                                <div className="mt-3"><label className="form-label" htmlFor="invitation-closing">Abschlusstext <span>optional</span></label><textarea className={`form-control${form.errors.closing_text ? ' is-invalid' : ''}`} id="invitation-closing" maxLength={300} onChange={(e) => form.setData('closing_text', e.target.value)} rows={3} value={form.data.closing_text} /><div className="invitation-editor-field-meta"><span>Zum Beispiel eure Namen oder ein kurzer Gruß</span><span>{form.data.closing_text.length}/300</span></div>{form.errors.closing_text && <div className="invalid-feedback">{form.errors.closing_text}</div>}</div>
                            </section>

                            <section className="invitation-editor-panel" aria-labelledby="invitation-media-title">
                                <div className="invitation-editor-section-heading"><div><span className="invitation-editor-eyebrow">Titelmedium</span><h2 id="invitation-media-title">Bild oder Video</h2></div></div>
                                <fieldset className="invitation-editor-media-types"><legend>Medientyp wählen</legend>{([{ value: 'none', label: 'Kein Medium' }, { value: 'image', label: 'Titelbild' }, { value: 'video', label: 'Titelvideo' }] as const).map((choice) => <label className={form.data.media_type === choice.value ? 'is-selected' : ''} key={choice.value}><input checked={form.data.media_type === choice.value} name="media-type" onChange={() => changeMediaType(choice.value)} type="radio" /><span>{choice.label}</span></label>)}</fieldset>
                                {form.data.media_type !== 'none' && <div className="invitation-editor-file-field"><label className="form-label" htmlFor="invitation-media-file">{form.data.media_type === 'image' ? 'Bilddatei auswählen' : 'Videodatei auswählen'}</label><input accept={form.data.media_type === 'image' ? 'image/*' : 'video/*'} className={`form-control${form.errors.media_file ? ' is-invalid' : ''}`} id="invitation-media-file" key={mediaInputKey} onChange={(e) => form.setData('media_file', e.target.files?.[0] ?? null)} type="file" />{form.errors.media_file && <div className="invalid-feedback">{form.errors.media_file}</div>}</div>}
                                {mediaPreviewUrl ? <div className="invitation-editor-media-preview">{form.data.media_type === 'video' ? <video controls src={mediaPreviewUrl} /> : <img alt="Vorschau des Titelbildes" src={mediaPreviewUrl} />}<div><span>{form.data.media_file?.name ?? design.mediaName ?? 'Gespeichertes Medium'}</span>{form.data.media_file ? <button className="invitation-editor-text-button" onClick={discardNewMedia} type="button">Neue Datei verwerfen</button> : <button className="invitation-editor-text-button is-danger" onClick={() => form.setData('remove_existing_media', true)} type="button">Bestehendes Medium entfernen</button>}</div></div> : <div className="invitation-editor-no-media"><span aria-hidden="true">◇</span><p>{form.data.media_type === 'none' ? 'Die Einladung wird ohne Titelmedium angezeigt.' : 'Noch keine Datei ausgewählt.'}</p></div>}
                                {form.errors.media_type && <div className="invitation-editor-field-error">{form.errors.media_type}</div>}{form.errors.remove_existing_media && <div className="invitation-editor-field-error">{form.errors.remove_existing_media}</div>}
                            </section>

                            <section className="invitation-editor-panel" aria-labelledby="invitation-tools-title">
                                <div className="invitation-editor-section-heading"><div><span className="invitation-editor-eyebrow">Bausteine</span><h2 id="invitation-tools-title">Vorlagen &amp; Platzhalter</h2></div></div>
                                <div className="invitation-editor-resource-section"><h3>Textvorlagen</h3>{templates.length === 0 ? <p className="invitation-editor-empty-copy">Es stehen noch keine Vorlagen zur Verfügung.</p> : <div className="invitation-editor-template-list">{templates.map((template) => <article key={template.id}><div><h4>{template.name}</h4>{template.description && <p>{template.description}</p>}</div><button className="invitation-editor-secondary" onClick={() => applyTemplate(template)} type="button">Vorlage anwenden</button></article>)}</div>}</div>
                                <div className="invitation-editor-resource-section"><h3>Personalisierte Platzhalter</h3>{placeholders.length === 0 ? <p className="invitation-editor-empty-copy">Keine Platzhalter vorhanden.</p> : <div className="invitation-editor-placeholder-list">{placeholders.map((placeholder) => <article key={placeholder.token}><code>{placeholder.token}</code><div><h4>{placeholder.label}</h4><p>{placeholder.description}</p><small>Beispiel: {placeholder.example}</small></div><div><button className="invitation-editor-text-button" onClick={() => insertPlaceholder(placeholder.token)} type="button">Einfügen</button><button className="invitation-editor-text-button" onClick={() => copyPlaceholder(placeholder.token)} type="button">{copiedToken === placeholder.token ? 'Kopiert' : 'Kopieren'}</button></div></article>)}</div>}</div>
                            </section>
                        </div>

                        <aside className="invitation-editor-preview-panel" aria-labelledby="invitation-preview-title">
                            <div className="invitation-editor-section-heading"><div><span className="invitation-editor-eyebrow">Live-Vorschau</span><h2 id="invitation-preview-title">Ansicht für Gäste</h2></div><span className="invitation-editor-preview-badge">Vorschau</span></div>
                            {previewRecipients.length > 0 ? <div className="invitation-editor-recipient"><label htmlFor="preview-recipient">Empfängergruppe</label><select className="form-select" id="preview-recipient" onChange={(e) => setSelectedRecipientId(e.target.value)} value={selectedRecipientId}>{previewRecipients.map((recipient) => <option key={recipient.id} value={recipient.id}>{recipient.label}</option>)}</select></div> : <p className="invitation-editor-preview-hint">Noch keine Empfänger vorhanden. Die Vorschau verwendet die gelieferten Beispielwerte.</p>}
                            <iframe sandbox="allow-same-origin" srcDoc={previewDocument} title={`Vorschau der Einladung für ${selectedRecipient?.label ?? 'allgemeine Beispieldaten'}`} />
                            <p className="invitation-editor-preview-hint">Die Vorschau ist isoliert und führt keine Skripte aus.</p>
                            <button className="invitation-editor-primary" disabled={form.processing} type="submit">{form.processing ? <><span className="spinner-border spinner-border-sm" aria-hidden="true" /> Einladung wird gespeichert …</> : 'Einladung speichern'}</button>
                            {form.processing && <p className="invitation-editor-saving" role="status">Texte und Medium werden übertragen.</p>}
                        </aside>
                    </div>
                </form>
            </main>
        </>
    );
}
