@page "/components/rich-text-editor"

@using Lumeo.Services
@inject ToastService Toast

<PageTitle>Rich Text Editor — Lumeo</PageTitle>

<PageHeader Title="Rich Text Editor"
            Description="A fully-featured WYSIWYG editor built on TipTap/ProseMirror. Supports mentions, slash commands, tables, code blocks, task lists, AI actions, image upload, and Word import — all wired through Blazor parameters." />

<Alert Variant="Alert.AlertVariant.Warning" ShowIcon="true" Class="mt-6">
    <ChildContent>
        <strong>In active development.</strong> The RichTextEditor is functional but still being polished —
        expect rough edges, occasional input lag, and ongoing improvements to the slash/mention dropdowns,
        Word import fidelity, and AI action wiring. Please report issues on
        <a href="https://github.com/Brain2k-0005/Lumeo/issues" class="underline hover:text-foreground">GitHub</a>.
    </ChildContent>
</Alert>

<Alert Variant="Alert.AlertVariant.Info" ShowIcon="true" Class="mt-3">
    <ChildContent>
        <strong>Dependencies &amp; licensing — fully MIT, no paid extensions.</strong>
        Built on TipTap's free MIT extensions (<code>@@tiptap/core</code>, <code>starter-kit</code>, <code>extension-mention</code>,
        <code>extension-table</code>, <code>extension-code-block-lowlight</code>, <code>extension-image</code>, etc.) plus
        <code>lowlight</code> (MIT) and <code>Mammoth</code> 1.11.0 (BSD-2-Clause) for Word import.
        <strong>No <code>@@tiptap-pro/*</code> packages</strong> — the AI actions, drag handle, and bubble menu are all
        custom Lumeo code using the free MIT primitives. The AI actions invoke <strong>your own LLM</strong> via the
        <code>OnAiAction</code> callback (OpenAI, Anthropic, Mistral, Ollama — your choice, your bill). Lumeo doesn't
        ship credentials and doesn't take a cut.
    </ChildContent>
</Alert>

<div class="mt-10 space-y-3">
    <Heading Id="when-to-use" Level="2" Class="scroll-mt-20">When to Use</Heading>
    <ul class="list-disc list-inside space-y-1 text-muted-foreground text-sm">
        <li>Content authoring surfaces — blog posts, knowledge-base articles, product descriptions</li>
        <li>Long-form fields that benefit from headings, lists, quotes, links, and tables</li>
        <li>Comment or reply surfaces where users expect bold, italic, mentions, and emoji</li>
        <li>Document editors that need Word import, AI writing assistance, or collaborative slash commands</li>
        <li>Anywhere you currently use a Textarea but want structured HTML output</li>
    </ul>
</div>

<Stack Gap="8" Class="mt-8">

    <!-- 1. Basic -->
    <ComponentDemo Title="Basic" Code="@_basicCode">
        <RichTextEditor @bind-Value="_basicHtml" Placeholder="Start writing…" />
    </ComponentDemo>

    <!-- 2. Toolbar presets -->
    <ComponentDemo Title="Toolbar presets" Code="@_presetsCode">
        <Stack Gap="6">
            <div>
                <Lumeo.Text Size="sm" Color="muted" Class="mb-2 font-medium">Minimal — Bold, Italic, Link</Lumeo.Text>
                <RichTextEditor @bind-Value="_minimalHtml" Toolbar="EditorToolbarPreset.Minimal" MinHeight="120" />
            </div>
            <div>
                <Lumeo.Text Size="sm" Color="muted" Class="mb-2 font-medium">Standard (default) — Headings, marks, lists, link, undo/redo</Lumeo.Text>
                <RichTextEditor @bind-Value="_standardHtml" Toolbar="EditorToolbarPreset.Standard" MinHeight="120" />
            </div>
            <div>
                <Lumeo.Text Size="sm" Color="muted" Class="mb-2 font-medium">Full — Standard + tables, image, code block, AI menu, Word import</Lumeo.Text>
                <RichTextEditor @bind-Value="_fullHtml" Toolbar="EditorToolbarPreset.Full" MinHeight="120" />
            </div>
            <div>
                <Lumeo.Text Size="sm" Color="muted" Class="mb-2 font-medium">None — Toolbar hidden; bubble menu and keyboard shortcuts still active</Lumeo.Text>
                <RichTextEditor @bind-Value="_noneHtml" Toolbar="EditorToolbarPreset.None" MinHeight="100"
                                Placeholder="No toolbar — select text to see the bubble menu." />
            </div>
        </Stack>
    </ComponentDemo>

    <!-- 3. mentions -->
    <ComponentDemo Title="@("@-mentions")" Code="@_mentionsCode">
        <RichTextEditor @bind-Value="_mentionsHtml"
                        Triggers="_mentionTriggers"
                        Placeholder="@("Type @ to mention a team member…")" />
    </ComponentDemo>

    <!-- 4. Multiple triggers -->
    <ComponentDemo Title="@("Multiple triggers (@, #, $)")" Code="@_multiTriggerCode">
        <Stack Gap="2">
            <Alert Variant="Alert.AlertVariant.Info" ShowIcon="true">
                <ChildContent>Type <code class="font-mono text-xs">@@</code> for users, <code class="font-mono text-xs">#</code> for tags, <code class="font-mono text-xs">$</code> for template variables.</ChildContent>
            </Alert>
            <RichTextEditor @bind-Value="_multiHtml" Triggers="_multiTriggers" />
        </Stack>
    </ComponentDemo>

    <!-- 5. Slash command -->
    <ComponentDemo Title="Slash command" Code="@_slashCode">
        <Stack Gap="2">
            <Alert Variant="Alert.AlertVariant.Info" ShowIcon="true">
                <ChildContent>Type <code class="font-mono text-xs">/</code> on a new line to open the command palette. Built-in commands: Heading 1-3, Bullet list, Numbered list, Task list, Quote, Code block, Table, Image, Divider.</ChildContent>
            </Alert>
            <RichTextEditor @bind-Value="_slashHtml" EnableSlashCommand="true" />
        </Stack>
    </ComponentDemo>

    <!-- 6. Tables, code blocks, task lists -->
    <ComponentDemo Title="Tables, code, and task lists" Code="@_richBlocksCode">
        <RichTextEditor @bind-Value="_blocksHtml"
                        Toolbar="EditorToolbarPreset.Full"
                        EnableTables="true"
                        EnableCodeBlock="true"
                        EnableTaskList="true"
                        Placeholder="Use the Full toolbar or slash commands to insert a table, code block, or task list." />
    </ComponentDemo>

    <!-- 7. Image upload -->
    <ComponentDemo Title="Image upload" Code="@_imagesCode">
        <Stack Gap="2">
            <Alert Variant="Alert.AlertVariant.Info" ShowIcon="true">
                <ChildContent>Click the image button in the toolbar (or type <code class="font-mono text-xs">/image</code>). The <code class="font-mono text-xs">OnImageRequested</code> callback returns the final URL; the demo returns a placeholder after a simulated upload delay.</ChildContent>
            </Alert>
            <RichTextEditor @bind-Value="_imageHtml"
                            Toolbar="EditorToolbarPreset.Full"
                            OnImageRequested="HandleImageUpload" />
        </Stack>
    </ComponentDemo>

    <!-- 8. AI actions -->
    <ComponentDemo Title="AI actions" Code="@_aiCode">
        <Stack Gap="2">
            <Alert Variant="Alert.AlertVariant.Info" ShowIcon="true">
                <ChildContent>Select some text in the editor below to see the bubble menu's AI button. Actions: Improve, Shorten, Expand, Fix Grammar, Summarize, Translate.</ChildContent>
            </Alert>
            <RichTextEditor @bind-Value="_aiHtml"
                            EnableAiActions="true"
                            OnAiAction="HandleAiAction"
                            Placeholder="Write something here, select it, and try an AI action…" />
        </Stack>
    </ComponentDemo>

    <!-- 9. Word (.docx) import -->
    <ComponentDemo Title="Word (.docx) import" Code="@_wordCode">
        <Stack Gap="2">
            <Alert Variant="Alert.AlertVariant.Info" ShowIcon="true">
                <ChildContent>
                    Click the <em>Import</em> button in the toolbar and pick any <code class="font-mono text-xs">.docx</code> file.
                    Conversion runs entirely in your browser via <code class="font-mono text-xs">WordImporter</code> (Mammoth ships with the
                    Lumeo.Editor WASM bundle), so this demo works on static hosting with no server. The default style map handles
                    English and German Word default styles (<em>Title/Titel, Heading 1-6/Überschrift 1-6, Body Text/Textkörper,
                    List Paragraph/Listenabsatz</em>, etc.). For server-side conversion with custom callbacks (e.g. uploading
                    embedded images), set <code class="font-mono text-xs">OnWordImportRequested</code>.
                </ChildContent>
            </Alert>
            <RichTextEditor @bind-Value="_wordHtml"
                            Toolbar="EditorToolbarPreset.Full"
                            AllowWordImport="true" />
        </Stack>
    </ComponentDemo>

    <!-- 10. FormField validation -->
    <ComponentDemo Title="With FormField validation" Code="@_formFieldCode">
        <Stack Gap="3" Class="max-w-2xl">
            <FormField Label="Description"
                       Required="true"
                       HelpText="Markdown shortcuts work too — try '**bold**' or '# Heading'."
                       Error="@_formError">
                <RichTextEditor @bind-Value="_formHtml"
                                Required="true"
                                Invalid="@(_formError is not null)"
                                MinHeight="160" />
            </FormField>
            <Flex Gap="2">
                <Button OnClick="ValidateForm">Validate</Button>
                <Button Variant="Button.ButtonVariant.Ghost" OnClick="ResetForm">Reset</Button>
            </Flex>
        </Stack>
    </ComponentDemo>

    <!-- 11. Read-only & disabled -->
    <ComponentDemo Title="Read-only and disabled" Code="@_statesCode">
        <Stack Gap="4">
            <div>
                <Lumeo.Text Size="sm" Color="muted" Class="mb-2 font-medium">Read-only — content is selectable but not editable</Lumeo.Text>
                <RichTextEditor Value="@_readOnlySample"
                                ReadOnly="true"
                                Toolbar="EditorToolbarPreset.None"
                                MinHeight="100" />
            </div>
            <div>
                <Lumeo.Text Size="sm" Color="muted" Class="mb-2 font-medium">Disabled — dimmed and non-interactive</Lumeo.Text>
                <RichTextEditor Value="@_disabledSample"
                                Disabled="true"
                                MinHeight="100" />
            </div>
        </Stack>
    </ComponentDemo>

    <!-- 12. Max-length & constrained height -->
    <ComponentDemo Title="Max length + bounded height" Code="@_constraintsCode">
        <RichTextEditor @bind-Value="_constrainedHtml"
                        MaxLength="500"
                        MinHeight="140"
                        MaxHeight="260"
                        HelperText="Plain-text character limit: 500. The editor scrolls internally when content overflows 260 px." />
    </ComponentDemo>

</Stack>

<!-- Keyboard shortcuts -->
<div class="mt-16">
    <Heading Id="keyboard" Level="2" Class="scroll-mt-20">Keyboard Shortcuts</Heading>
    <div class="overflow-x-auto mt-4">
        <Stack Class="rounded-lg border border-border/40 overflow-x-auto">
            <table class="w-full min-w-[500px] text-sm">
                <thead>
                    <tr class="border-b border-border/40 bg-muted/40">
                        <th class="text-left p-3 font-medium text-foreground w-[220px]">Shortcut</th>
                        <th class="text-left p-3 font-medium text-foreground">Action</th>
                    </tr>
                </thead>
                <tbody class="divide-y divide-border/40">
                    <tr><td class="p-3 font-mono text-xs text-muted-foreground">Ctrl / ⌘ + B</td><td class="p-3 text-muted-foreground">Bold</td></tr>
                    <tr><td class="p-3 font-mono text-xs text-muted-foreground">Ctrl / ⌘ + I</td><td class="p-3 text-muted-foreground">Italic</td></tr>
                    <tr><td class="p-3 font-mono text-xs text-muted-foreground">Ctrl / ⌘ + U</td><td class="p-3 text-muted-foreground">Underline</td></tr>
                    <tr><td class="p-3 font-mono text-xs text-muted-foreground">Ctrl / ⌘ + Shift + X</td><td class="p-3 text-muted-foreground">Strikethrough</td></tr>
                    <tr><td class="p-3 font-mono text-xs text-muted-foreground">Ctrl / ⌘ + Z</td><td class="p-3 text-muted-foreground">Undo</td></tr>
                    <tr><td class="p-3 font-mono text-xs text-muted-foreground">Ctrl / ⌘ + Shift + Z</td><td class="p-3 text-muted-foreground">Redo</td></tr>
                    <tr><td class="p-3 font-mono text-xs text-muted-foreground">Ctrl / ⌘ + Alt + 1 – 3</td><td class="p-3 text-muted-foreground">Heading 1 / 2 / 3</td></tr>
                    <tr><td class="p-3 font-mono text-xs text-muted-foreground">/ (on a new line)</td><td class="p-3 text-muted-foreground">Open slash-command palette</td></tr>
                    <tr><td class="p-3 font-mono text-xs text-muted-foreground">@@ # $</td><td class="p-3 text-muted-foreground">Open the corresponding trigger dropdown</td></tr>
                    <tr><td class="p-3 font-mono text-xs text-muted-foreground">**text**</td><td class="p-3 text-muted-foreground">Bold (markdown shortcut)</td></tr>
                    <tr><td class="p-3 font-mono text-xs text-muted-foreground"># text (space)</td><td class="p-3 text-muted-foreground">Heading 1 (markdown shortcut)</td></tr>
                    <tr><td class="p-3 font-mono text-xs text-muted-foreground">- or * (space)</td><td class="p-3 text-muted-foreground">Bullet list (markdown shortcut)</td></tr>
                    <tr><td class="p-3 font-mono text-xs text-muted-foreground">[ ] (space)</td><td class="p-3 text-muted-foreground">Task list item (markdown shortcut)</td></tr>
                </tbody>
            </table>
        </Stack>
    </div>
</div>

<!-- API Reference -->
<Stack Gap="6" Class="mt-16">
    <Heading Id="api-reference" Level="2" Class="scroll-mt-20">API Reference</Heading>

    <Stack Gap="8" Class="mt-4">

        <!-- RichTextEditor parameters -->
        <Stack Gap="3">
            <Heading Level="3">RichTextEditor</Heading>
            <Stack Class="rounded-lg border border-border/40 overflow-x-auto">
                <table class="w-full min-w-[700px] text-sm">
                    <thead>
                        <tr class="border-b border-border/40 bg-muted/40">
                            <th class="text-left p-3 font-medium text-foreground w-[200px]">Property</th>
                            <th class="text-left p-3 font-medium text-foreground w-[230px]">Type</th>
                            <th class="text-left p-3 font-medium text-foreground w-[130px]">Default</th>
                            <th class="text-left p-3 font-medium text-foreground">Description</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-border/40">
                        <tr><td class="p-3 font-mono text-xs text-primary">Value</td><td class="p-3 font-mono text-xs text-muted-foreground">string?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Current HTML content. Two-way bindable via <code class="font-mono text-xs">@@bind-Value</code>.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">ValueChanged</td><td class="p-3 font-mono text-xs text-muted-foreground">EventCallback&lt;string?&gt;</td><td class="p-3 font-mono text-xs text-muted-foreground">—</td><td class="p-3 text-muted-foreground">Fired on every document change. Carries the serialised HTML.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Placeholder</td><td class="p-3 font-mono text-xs text-muted-foreground">string</td><td class="p-3 font-mono text-xs text-muted-foreground">"Start writing…"</td><td class="p-3 text-muted-foreground">Placeholder shown when the editor is empty.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Toolbar</td><td class="p-3 font-mono text-xs text-muted-foreground">EditorToolbarPreset</td><td class="p-3 font-mono text-xs text-muted-foreground">Standard</td><td class="p-3 text-muted-foreground">Toolbar density. <code class="font-mono text-xs">None</code> hides it; <code class="font-mono text-xs">Minimal</code> = Bold/Italic/Link; <code class="font-mono text-xs">Standard</code> = headings + marks + lists + undo/redo; <code class="font-mono text-xs">Full</code> adds tables, image, code block, AI menu, Word import.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">MinHeight</td><td class="p-3 font-mono text-xs text-muted-foreground">int</td><td class="p-3 font-mono text-xs text-muted-foreground">180</td><td class="p-3 text-muted-foreground">Minimum editor height in pixels.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">MaxHeight</td><td class="p-3 font-mono text-xs text-muted-foreground">int?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Optional maximum height in pixels. When set, the content area scrolls internally beyond this height.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Disabled</td><td class="p-3 font-mono text-xs text-muted-foreground">bool</td><td class="p-3 font-mono text-xs text-muted-foreground">false</td><td class="p-3 text-muted-foreground">Disables editing and dims the toolbar.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">ReadOnly</td><td class="p-3 font-mono text-xs text-muted-foreground">bool</td><td class="p-3 font-mono text-xs text-muted-foreground">false</td><td class="p-3 text-muted-foreground">Prevents edits but keeps content selectable (no dimming).</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Required</td><td class="p-3 font-mono text-xs text-muted-foreground">bool</td><td class="p-3 font-mono text-xs text-muted-foreground">false</td><td class="p-3 text-muted-foreground">Marks the field as required; sets <code class="font-mono text-xs">aria-required</code> on the root element.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Invalid</td><td class="p-3 font-mono text-xs text-muted-foreground">bool</td><td class="p-3 font-mono text-xs text-muted-foreground">false</td><td class="p-3 text-muted-foreground">Shows a destructive ring and sets <code class="font-mono text-xs">aria-invalid</code>. Also driven automatically when nested inside a <code class="font-mono text-xs">&lt;FormField&gt;</code> with an error.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">ErrorText</td><td class="p-3 font-mono text-xs text-muted-foreground">string?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Error message rendered below the editor when <code class="font-mono text-xs">Invalid</code> is true (only when not inside a FormField).</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">HelperText</td><td class="p-3 font-mono text-xs text-muted-foreground">string?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Helper text rendered below the editor when there is no error (only when not inside a FormField).</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Label</td><td class="p-3 font-mono text-xs text-muted-foreground">string?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Inline label rendered above the editor (only when not inside a FormField).</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Name</td><td class="p-3 font-mono text-xs text-muted-foreground">string?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">HTML name attribute, useful for form identification.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Triggers</td><td class="p-3 font-mono text-xs text-muted-foreground">IReadOnlyList&lt;EditorTrigger&gt;?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">One or more trigger characters (e.g. <code class="font-mono text-xs">@@</code>, <code class="font-mono text-xs">#</code>, <code class="font-mono text-xs">$</code>). Each trigger provides an async <code class="font-mono text-xs">ItemSource</code> callback.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">EnableSlashCommand</td><td class="p-3 font-mono text-xs text-muted-foreground">bool</td><td class="p-3 font-mono text-xs text-muted-foreground">true</td><td class="p-3 text-muted-foreground">Enables the built-in <code class="font-mono text-xs">/</code> command palette (Notion-style). Supply a custom <code class="font-mono text-xs">/</code> trigger to override the default commands.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">EnableTables</td><td class="p-3 font-mono text-xs text-muted-foreground">bool</td><td class="p-3 font-mono text-xs text-muted-foreground">true</td><td class="p-3 text-muted-foreground">Enables the TipTap Table extension.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">EnableCodeBlock</td><td class="p-3 font-mono text-xs text-muted-foreground">bool</td><td class="p-3 font-mono text-xs text-muted-foreground">true</td><td class="p-3 text-muted-foreground">Enables syntax-highlighted code blocks.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">EnableImages</td><td class="p-3 font-mono text-xs text-muted-foreground">bool</td><td class="p-3 font-mono text-xs text-muted-foreground">true</td><td class="p-3 text-muted-foreground">Enables image upload/paste. Falls back to base64 when <code class="font-mono text-xs">OnImageRequested</code> is null.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">EnableTaskList</td><td class="p-3 font-mono text-xs text-muted-foreground">bool</td><td class="p-3 font-mono text-xs text-muted-foreground">true</td><td class="p-3 text-muted-foreground">Enables checkable task-list items.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">EnableMarkdownShortcuts</td><td class="p-3 font-mono text-xs text-muted-foreground">bool</td><td class="p-3 font-mono text-xs text-muted-foreground">true</td><td class="p-3 text-muted-foreground">Enables Markdown input shortcuts (e.g. <code class="font-mono text-xs">**bold**</code>, <code class="font-mono text-xs"># Heading</code>).</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">EnableAiActions</td><td class="p-3 font-mono text-xs text-muted-foreground">bool</td><td class="p-3 font-mono text-xs text-muted-foreground">false</td><td class="p-3 text-muted-foreground">Shows an AI button in the bubble menu when text is selected. Requires <code class="font-mono text-xs">OnAiAction</code> to be wired.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">EnableBubbleMenu</td><td class="p-3 font-mono text-xs text-muted-foreground">bool</td><td class="p-3 font-mono text-xs text-muted-foreground">true</td><td class="p-3 text-muted-foreground">Shows the floating bubble menu (Bold, Italic, Link, etc.) when text is selected.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">EnableDragHandle</td><td class="p-3 font-mono text-xs text-muted-foreground">bool</td><td class="p-3 font-mono text-xs text-muted-foreground">true</td><td class="p-3 text-muted-foreground">Shows the drag handle on the left of each block node for drag-and-drop reordering.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">MaxLength</td><td class="p-3 font-mono text-xs text-muted-foreground">int?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Optional plain-text character limit enforced by the editor.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">AllowWordImport</td><td class="p-3 font-mono text-xs text-muted-foreground">bool</td><td class="p-3 font-mono text-xs text-muted-foreground">false</td><td class="p-3 text-muted-foreground">Adds an <em>Import .docx</em> button to the Full toolbar. By default, conversion runs in-browser via <code class="font-mono text-xs">WordImporter</code>. Override with <code class="font-mono text-xs">OnWordImportRequested</code> for server-side conversion.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">OnImageRequested</td><td class="p-3 font-mono text-xs text-muted-foreground">Func&lt;EditorImageRequest, ValueTask&lt;string?&gt;&gt;?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Async callback that receives file metadata and returns the final image URL. When null, images are inlined as base64.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">OnAiAction</td><td class="p-3 font-mono text-xs text-muted-foreground">Func&lt;string, string, ValueTask&lt;string?&gt;&gt;?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Async callback receiving <code class="font-mono text-xs">(actionId, selectedText)</code>; returns replacement HTML. Action IDs: <code class="font-mono text-xs">improve</code>, <code class="font-mono text-xs">shorten</code>, <code class="font-mono text-xs">expand</code>, <code class="font-mono text-xs">fix-grammar</code>, <code class="font-mono text-xs">summarize</code>, <code class="font-mono text-xs">translate</code>.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">OnWordImportRequested</td><td class="p-3 font-mono text-xs text-muted-foreground">Func&lt;WordImportPayload, ValueTask&lt;string&gt;&gt;?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Optional override. Receives the picked file (filename + bytes) and returns the HTML to insert. When not set, the editor calls <code class="font-mono text-xs">WordImporter.ToHtmlAsync</code> in-browser and inserts the result.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">ToolbarMode</td><td class="p-3 font-mono text-xs text-muted-foreground">RichTextToolbarMode?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Legacy alias for <code class="font-mono text-xs">Toolbar</code>. Values: None, Basic, Full. Use <code class="font-mono text-xs">Toolbar</code> (EditorToolbarPreset) for new code.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">MaxWordImportBytes</td><td class="p-3 font-mono text-xs text-muted-foreground">long</td><td class="p-3 font-mono text-xs text-muted-foreground">52428800</td><td class="p-3 text-muted-foreground">Maximum file size (bytes) allowed for Word import. Default is 50 MB.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">FormField</td><td class="p-3 font-mono text-xs text-muted-foreground">FormFieldContext?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Cascaded context from a parent FormField; drives Invalid, Required, and error display automatically.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Class</td><td class="p-3 font-mono text-xs text-muted-foreground">string?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Additional CSS classes applied to the root wrapper element.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">AdditionalAttributes</td><td class="p-3 font-mono text-xs text-muted-foreground">Dictionary&lt;string, object&gt;?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Splatted onto the root element. Useful for <code class="font-mono text-xs">data-*</code> or ARIA attributes.</td></tr>
                    </tbody>
                </table>
            </Stack>
        </Stack>

        <!-- EditorTrigger -->
        <Stack Gap="3">
            <Heading Level="3">EditorTrigger</Heading>
            <Stack Class="rounded-lg border border-border/40 overflow-x-auto">
                <table class="w-full min-w-[700px] text-sm">
                    <thead>
                        <tr class="border-b border-border/40 bg-muted/40">
                            <th class="text-left p-3 font-medium text-foreground w-[160px]">Property</th>
                            <th class="text-left p-3 font-medium text-foreground w-[260px]">Type</th>
                            <th class="text-left p-3 font-medium text-foreground w-[100px]">Default</th>
                            <th class="text-left p-3 font-medium text-foreground">Description</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-border/40">
                        <tr><td class="p-3 font-mono text-xs text-primary">Char</td><td class="p-3 font-mono text-xs text-muted-foreground">char</td><td class="p-3 font-mono text-xs text-muted-foreground">—</td><td class="p-3 text-muted-foreground">The character that activates this trigger (e.g. <code class="font-mono text-xs">'@@'</code>).</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">ItemSource</td><td class="p-3 font-mono text-xs text-muted-foreground">Func&lt;string, ValueTask&lt;IReadOnlyList&lt;TriggerItem&gt;&gt;&gt;</td><td class="p-3 font-mono text-xs text-muted-foreground">—</td><td class="p-3 text-muted-foreground">Async callback called on each keystroke with the partial query; returns matching items.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">ItemTemplate</td><td class="p-3 font-mono text-xs text-muted-foreground">RenderFragment&lt;TriggerItem&gt;?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Optional custom row renderer for the dropdown list.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">ChipClass</td><td class="p-3 font-mono text-xs text-muted-foreground">string?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">CSS classes applied to the chip/pill inserted into the document when an item is selected.</td></tr>
                    </tbody>
                </table>
            </Stack>
        </Stack>

        <!-- TriggerItem -->
        <Stack Gap="3">
            <Heading Level="3">TriggerItem</Heading>
            <Stack Class="rounded-lg border border-border/40 overflow-x-auto">
                <table class="w-full min-w-[700px] text-sm">
                    <thead>
                        <tr class="border-b border-border/40 bg-muted/40">
                            <th class="text-left p-3 font-medium text-foreground w-[140px]">Property</th>
                            <th class="text-left p-3 font-medium text-foreground w-[160px]">Type</th>
                            <th class="text-left p-3 font-medium text-foreground w-[100px]">Default</th>
                            <th class="text-left p-3 font-medium text-foreground">Description</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-border/40">
                        <tr><td class="p-3 font-mono text-xs text-primary">Id</td><td class="p-3 font-mono text-xs text-muted-foreground">string</td><td class="p-3 font-mono text-xs text-muted-foreground">—</td><td class="p-3 text-muted-foreground">Unique identifier stored in the document node's data attribute.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Label</td><td class="p-3 font-mono text-xs text-muted-foreground">string</td><td class="p-3 font-mono text-xs text-muted-foreground">—</td><td class="p-3 text-muted-foreground">Display name shown in the dropdown and rendered in the chip.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Subtitle</td><td class="p-3 font-mono text-xs text-muted-foreground">string?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Secondary text shown below the label in the dropdown row.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">IconName</td><td class="p-3 font-mono text-xs text-muted-foreground">string?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Lucide icon name (e.g. <code class="font-mono text-xs">"User"</code>) displayed alongside the label.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Payload</td><td class="p-3 font-mono text-xs text-muted-foreground">object?</td><td class="p-3 font-mono text-xs text-muted-foreground">null</td><td class="p-3 text-muted-foreground">Arbitrary data passed back to the consumer for custom rendering or server look-ups.</td></tr>
                    </tbody>
                </table>
            </Stack>
        </Stack>

        <!-- EditorImageRequest -->
        <Stack Gap="3">
            <Heading Level="3">EditorImageRequest</Heading>
            <Stack Class="rounded-lg border border-border/40 overflow-x-auto">
                <table class="w-full min-w-[700px] text-sm">
                    <thead>
                        <tr class="border-b border-border/40 bg-muted/40">
                            <th class="text-left p-3 font-medium text-foreground w-[140px]">Property</th>
                            <th class="text-left p-3 font-medium text-foreground w-[140px]">Type</th>
                            <th class="text-left p-3 font-medium text-foreground">Description</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-border/40">
                        <tr><td class="p-3 font-mono text-xs text-primary">FileName</td><td class="p-3 font-mono text-xs text-muted-foreground">string</td><td class="p-3 text-muted-foreground">Original file name chosen by the user.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">MimeType</td><td class="p-3 font-mono text-xs text-muted-foreground">string</td><td class="p-3 text-muted-foreground">MIME type of the selected file (e.g. <code class="font-mono text-xs">image/png</code>).</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Size</td><td class="p-3 font-mono text-xs text-muted-foreground">long</td><td class="p-3 text-muted-foreground">File size in bytes.</td></tr>
                    </tbody>
                </table>
            </Stack>
        </Stack>

        <!-- EditorToolbarPreset -->
        <Stack Gap="3">
            <Heading Level="3">EditorToolbarPreset</Heading>
            <Stack Class="rounded-lg border border-border/40 overflow-x-auto">
                <table class="w-full min-w-[500px] text-sm">
                    <thead>
                        <tr class="border-b border-border/40 bg-muted/40">
                            <th class="text-left p-3 font-medium text-foreground w-[140px]">Value</th>
                            <th class="text-left p-3 font-medium text-foreground">Description</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-border/40">
                        <tr><td class="p-3 font-mono text-xs text-primary">None</td><td class="p-3 text-muted-foreground">No toolbar. Bubble menu and keyboard shortcuts remain available.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Minimal</td><td class="p-3 text-muted-foreground">Bold, Italic, Link only.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Standard</td><td class="p-3 text-muted-foreground">Headings, basic marks (bold, italic, underline, strikethrough), lists, link, undo/redo. Default value.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">Full</td><td class="p-3 text-muted-foreground">Standard plus tables, image, code block, AI action menu, and Word import button.</td></tr>
                    </tbody>
                </table>
            </Stack>
        </Stack>

        <!-- WordImporter -->
        <Stack Gap="3">
            <Heading Level="3">WordImporter (server-side)</Heading>
            <p class="text-sm text-muted-foreground">
                <code class="font-mono text-xs">WordImporter</code> is a static server-side helper in <code class="font-mono text-xs">Lumeo.Editor</code>
                that converts a <code class="font-mono text-xs">.docx</code> stream to clean HTML using Mammoth.NET.
                Call it from an API endpoint and return the result to the editor via
                <code class="font-mono text-xs">SetHtmlAsync</code>.
            </p>
            <Stack Class="rounded-lg border border-border/40 overflow-x-auto">
                <table class="w-full min-w-[600px] text-sm">
                    <thead>
                        <tr class="border-b border-border/40 bg-muted/40">
                            <th class="text-left p-3 font-medium text-foreground w-[200px]">Member</th>
                            <th class="text-left p-3 font-medium text-foreground">Description</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-border/40">
                        <tr><td class="p-3 font-mono text-xs text-primary">ToHtmlAsync(stream, options?)</td><td class="p-3 text-muted-foreground">Converts a .docx stream to <code class="font-mono text-xs">WordImportResult</code> containing <code class="font-mono text-xs">Html</code> and <code class="font-mono text-xs">Warnings</code>.</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">DefaultStyleMap</td><td class="p-3 text-muted-foreground">Built-in Mammoth style map covering English and German Word styles (Title, Heading 1-6, Body Text, Quote, etc.).</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">WordImportOptions.StyleMap</td><td class="p-3 text-muted-foreground">Custom style map appended after the default map (so custom rules win).</td></tr>
                        <tr><td class="p-3 font-mono text-xs text-primary">WordImportOptions.ConvertImage</td><td class="p-3 text-muted-foreground">Optional async callback <code class="font-mono text-xs">(contentType, stream) => url</code> to upload embedded images rather than base64-inlining them.</td></tr>
                    </tbody>
                </table>
            </Stack>

            <!-- Server-side snippet -->
            <div class="mt-4">
                <Lumeo.Text Size="sm" Color="muted" Class="mb-2">ASP.NET Core endpoint example</Lumeo.Text>
                <pre class="rounded-lg border border-border/40 bg-muted/40 px-4 py-3 text-xs text-muted-foreground overflow-x-auto leading-relaxed">@_wordImporterSnippet</pre>
            </div>
        </Stack>

    </Stack>
</Stack>

<!-- Related -->
<div class="mt-16">
    <Heading Id="related" Level="2" Class="scroll-mt-20">Related Components</Heading>
    <ul class="mt-4 space-y-2 text-sm">
        <li><a href="/components/textarea" class="text-primary hover:underline">Textarea</a> — Plain multi-line text input without formatting</li>
        <li><a href="/components/form-field" class="text-primary hover:underline">FormField</a> — Wraps any input with a label, help text, and validation error</li>
        <li><a href="/components/form" class="text-primary hover:underline">Form</a> — Full form wrapper with model validation</li>
        <li><a href="/components/input" class="text-primary hover:underline">Input</a> — Single-line text input</li>
    </ul>
</div>

@code {
    // ── bind values ──────────────────────────────────────────────────────────
    private string? _basicHtml;
    private string? _minimalHtml;
    private string? _standardHtml;
    private string? _fullHtml;
    private string? _noneHtml;
    private string? _mentionsHtml;
    private string? _multiHtml;
    private string? _slashHtml;
    private string? _blocksHtml;
    private string? _imageHtml;
    private string? _aiHtml = "<p>Select this paragraph and click the AI button in the bubble menu to see the available actions.</p>";
    private string? _wordHtml;
    private string? _formHtml;
    private string? _constrainedHtml;
    private string? _formError;

    private string _readOnlySample = "<h3>Lumeo Rich Text Editor</h3><p>This is a <strong>read-only</strong> view. The content is selectable but cannot be edited. The toolbar is hidden via <code>Toolbar=\"EditorToolbarPreset.None\"</code>.</p>";
    private string _disabledSample = "<p>This editor is <strong>disabled</strong>. Editing and text selection are both blocked.</p>";

    // ── mention triggers ─────────────────────────────────────────────────────

    private static readonly TriggerItem[] _users = new[]
    {
        new TriggerItem("alice",   "Alice Schmidt",  "Product",      "User"),
        new TriggerItem("bob",     "Bob Hoffmann",   "Engineering",  "User"),
        new TriggerItem("carla",   "Carla Meier",    "Design",       "User"),
        new TriggerItem("david",   "David Fischer",  "Marketing",    "User"),
        new TriggerItem("emma",    "Emma Wagner",    "Customer Success", "User"),
        new TriggerItem("florian", "Florian Braun",  "Engineering",  "User"),
        new TriggerItem("grace",   "Grace Müller",   "Finance",      "User"),
        new TriggerItem("hans",    "Hans Becker",    "Legal",        "User"),
    };

    private static readonly TriggerItem[] _tags = new[]
    {
        new TriggerItem("bug",       "bug",       null, "Tag"),
        new TriggerItem("feature",   "feature",   null, "Tag"),
        new TriggerItem("urgent",    "urgent",    null, "Tag"),
        new TriggerItem("backlog",   "backlog",   null, "Tag"),
        new TriggerItem("in-review", "in-review", null, "Tag"),
    };

    private static readonly TriggerItem[] _variables = new[]
    {
        new TriggerItem("customer_name",  "customer_name",  "Contact name",   "Variable"),
        new TriggerItem("company_name",   "company_name",   "Account name",   "Variable"),
        new TriggerItem("invoice_number", "invoice_number", "Invoice #",      "Variable"),
        new TriggerItem("due_date",       "due_date",       "Payment due",    "Variable"),
        new TriggerItem("total_amount",   "total_amount",   "Invoice total",  "Variable"),
    };

    private IReadOnlyList<EditorTrigger> _mentionTriggers = default!;
    private IReadOnlyList<EditorTrigger> _multiTriggers   = default!;

    protected override void OnInitialized()
    {
        _mentionTriggers = new[]
        {
            new EditorTrigger('@', query =>
            {
                var q = query.Trim();
                IReadOnlyList<TriggerItem> results = string.IsNullOrEmpty(q)
                    ? _users
                    : _users.Where(u => u.Label.Contains(q, StringComparison.OrdinalIgnoreCase)).ToArray();
                return ValueTask.FromResult(results);
            }),
        };

        _multiTriggers = new[]
        {
            new EditorTrigger('@', query =>
            {
                var q = query.Trim();
                IReadOnlyList<TriggerItem> r = string.IsNullOrEmpty(q)
                    ? _users
                    : _users.Where(u => u.Label.Contains(q, StringComparison.OrdinalIgnoreCase)).ToArray();
                return ValueTask.FromResult(r);
            }),
            new EditorTrigger('#', query =>
            {
                var q = query.Trim();
                IReadOnlyList<TriggerItem> r = string.IsNullOrEmpty(q)
                    ? _tags
                    : _tags.Where(t => t.Label.Contains(q, StringComparison.OrdinalIgnoreCase)).ToArray();
                return ValueTask.FromResult(r);
            }),
            new EditorTrigger('$', query =>
            {
                var q = query.Trim();
                IReadOnlyList<TriggerItem> r = string.IsNullOrEmpty(q)
                    ? _variables
                    : _variables.Where(v => v.Label.Contains(q, StringComparison.OrdinalIgnoreCase)).ToArray();
                return ValueTask.FromResult(r);
            }),
        };
    }

    // ── image upload mock ─────────────────────────────────────────────────────
    private async ValueTask<string?> HandleImageUpload(EditorImageRequest req)
    {
        // Simulate a 500 ms upload delay
        await Task.Delay(500);
        return $"https://placehold.co/600x400/png?text={Uri.EscapeDataString(req.FileName)}";
    }

    // ── AI action mock ────────────────────────────────────────────────────────
    private async ValueTask<string?> HandleAiAction(string actionId, string selectedText)
    {
        await Task.Delay(200);
        return actionId switch
        {
            "improve"    => $"<p><em>[improved]</em> {selectedText}</p>",
            "shorten"    => $"<p>{selectedText[..(selectedText.Length / 2)]}…</p>",
            "expand"     => $"<p>{selectedText} Furthermore, this topic encompasses a wide range of nuances worth exploring.</p>",
            "fix-grammar"=> $"<p>{selectedText}</p>",
            "summarize"  => $"<p><strong>TL;DR:</strong> {selectedText[..(Math.Min(60, selectedText.Length))]}…</p>",
            "translate"  => $"<p>[DE] {selectedText}</p>",
            _            => null,
        };
    }

    // Word import: no callback wired → editor uses the built-in browser-side WordImporter.
    // For server-side conversion, see the snippet at the bottom of this page.

    // ── form validation ───────────────────────────────────────────────────────
    private void ValidateForm()
    {
        var plain = System.Text.RegularExpressions.Regex.Replace(_formHtml ?? "", "<.*?>", "").Trim();
        _formError = string.IsNullOrEmpty(plain) ? "Description is required." : null;
    }

    private void ResetForm()
    {
        _formHtml  = null;
        _formError = null;
    }

    // ── code snippets ─────────────────────────────────────────────────────────

    private const string _basicCode =
@"<RichTextEditor @bind-Value=""_html"" Placeholder=""Start writing…"" />

@code {
    private string? _html;
}";

    private const string _presetsCode =
@"<RichTextEditor @bind-Value=""_html"" Toolbar=""EditorToolbarPreset.Minimal"" MinHeight=""120"" />
<RichTextEditor @bind-Value=""_html"" Toolbar=""EditorToolbarPreset.Standard"" MinHeight=""120"" />
<RichTextEditor @bind-Value=""_html"" Toolbar=""EditorToolbarPreset.Full""     MinHeight=""120"" />
<RichTextEditor @bind-Value=""_html"" Toolbar=""EditorToolbarPreset.None""     MinHeight=""100""
                Placeholder=""No toolbar — select text to see the bubble menu."" />";

    private const string _mentionsCode =
@"<RichTextEditor @bind-Value=""_html""
                Triggers=""_triggers""
                Placeholder=""Type @ to mention a team member…"" />

@code {
    private string? _html;

    private IReadOnlyList<EditorTrigger> _triggers = new[]
    {
        new EditorTrigger('@', async query =>
        {
            var users = await FetchUsersAsync(query);
            return users.Select(u => new TriggerItem(u.Id, u.Name, u.Role)).ToArray();
        }),
    };
}";

    private const string _multiTriggerCode =
@"<RichTextEditor @bind-Value=""_html"" Triggers=""_triggers"" />

@code {
    private string? _html;

    private IReadOnlyList<EditorTrigger> _triggers = new[]
    {
        // @ → users
        new EditorTrigger('@', query => ValueTask.FromResult<IReadOnlyList<TriggerItem>>(
            _users.Where(u => u.Label.Contains(query, StringComparison.OrdinalIgnoreCase)).ToArray())),

        // # → tags
        new EditorTrigger('#', query => ValueTask.FromResult<IReadOnlyList<TriggerItem>>(
            _tags.Where(t => t.Label.Contains(query, StringComparison.OrdinalIgnoreCase)).ToArray())),

        // $ → template variables
        new EditorTrigger('$', query => ValueTask.FromResult<IReadOnlyList<TriggerItem>>(
            _vars.Where(v => v.Label.Contains(query, StringComparison.OrdinalIgnoreCase)).ToArray())),
    };
}";

    private const string _slashCode =
@"<RichTextEditor @bind-Value=""_html"" EnableSlashCommand=""true"" />

@code { private string? _html; }";

    private const string _richBlocksCode =
@"<RichTextEditor @bind-Value=""_html""
                Toolbar=""EditorToolbarPreset.Full""
                EnableTables=""true""
                EnableCodeBlock=""true""
                EnableTaskList=""true"" />

@code { private string? _html; }";

    private const string _imagesCode =
@"<RichTextEditor @bind-Value=""_html""
                Toolbar=""EditorToolbarPreset.Full""
                OnImageRequested=""HandleImageUpload"" />

@code {
    private string? _html;

    private async ValueTask<string?> HandleImageUpload(EditorImageRequest req)
    {
        // Upload req.FileName / req.MimeType / req.Size to your storage
        var url = await MyStorageService.UploadAsync(req.FileName);
        return url;
    }
}";

    private const string _aiCode =
@"<RichTextEditor @bind-Value=""_html""
                EnableAiActions=""true""
                OnAiAction=""HandleAiAction"" />

@code {
    private string? _html;

    // actionId values: improve | shorten | expand | fix-grammar | summarize | translate
    private async ValueTask<string?> HandleAiAction(string actionId, string selectedText)
    {
        var result = await MyAiService.TransformAsync(actionId, selectedText);
        return result.Html; // replacement HTML injected in place of the selection
    }
}";

    private const string _wordCode =
@"<!-- Default: browser-side conversion via WordImporter (Mammoth in WASM bundle). -->
<RichTextEditor @bind-Value=""_html""
                Toolbar=""EditorToolbarPreset.Full""
                AllowWordImport=""true"" />

<!-- Optional: server-side conversion with custom processing. -->
<RichTextEditor @bind-Value=""_html2""
                Toolbar=""EditorToolbarPreset.Full""
                AllowWordImport=""true""
                OnWordImportRequested=""HandleWordImport"" />

@code {
    private string? _html;
    private string? _html2;

    private async ValueTask<string> HandleWordImport(WordImportPayload payload)
    {
        // Post the file to your server endpoint; return the converted HTML.
        var content = new ByteArrayContent(payload.Content);
        var response = await Http.PostAsync(""/api/upload-docx"", content);
        var result   = await response.Content.ReadFromJsonAsync<WordImportResponse>();
        return result!.Html;
    }
}";

    private const string _formFieldCode =
@"<FormField Label=""Description""
            Required=""true""
            HelpText=""Markdown shortcuts work too — try '**bold**' or '# Heading'."">
    <RichTextEditor @bind-Value=""_html""
                    Required=""true""
                    Invalid=""@(_error is not null)"" />
</FormField>

@code {
    private string? _html;
    private string? _error;

    private void Validate()
    {
        var plain = Regex.Replace(_html ?? """", ""<.*?>"", """").Trim();
        _error = string.IsNullOrEmpty(plain) ? ""Description is required."" : null;
    }
}";

    private const string _statesCode =
@"<!-- Read-only: content selectable, not editable -->
<RichTextEditor Value=""@_html"" ReadOnly=""true"" Toolbar=""EditorToolbarPreset.None"" />

<!-- Disabled: fully non-interactive, toolbar dimmed -->
<RichTextEditor Value=""@_html"" Disabled=""true"" />";

    private const string _constraintsCode =
@"<RichTextEditor @bind-Value=""_html""
                MaxLength=""500""
                MinHeight=""140""
                MaxHeight=""260""
                HelperText=""Plain-text character limit: 500. Scrolls internally after 260 px."" />

@code { private string? _html; }";

    private const string _wordImporterSnippet =
@"// ASP.NET Core controller endpoint
[HttpPost(""/api/upload-docx"")]
public async Task<IActionResult> ImportDocx(IFormFile file)
{
    using var stream = file.OpenReadStream();
    var result = await WordImporter.ToHtmlAsync(stream);
    return Ok(new { html = result.Html, warnings = result.Warnings });
}

// Optional: custom image upload during import
var options = new WordImportOptions
{
    ConvertImage = async (contentType, imageStream) =>
    {
        var url = await _storage.UploadAsync(contentType, imageStream);
        return url;
    }
};
var result = await WordImporter.ToHtmlAsync(stream, options);";
}
