@page "/components/icon"
@inject Lumeo.Docs.Services.IconService IconService
@inject IJSRuntime JS
@implements IDisposable
@using Lumeo.Docs.Shared
@using Blazicons
@using System.Reflection

<PageTitle>Icon — Lumeo</PageTitle>

@* ────────────────────────────────────────────────────────────────────── *@
@* Intro — compact header so the browser dominates the page               *@
@* ────────────────────────────────────────────────────────────────────── *@
<div class="mb-6">
    <Heading Id="top" Level="1" Size="3xl" Class="tracking-tight">Icon</Heading>
    <Lumeo.Text Size="base" Color="muted" Class="mt-2 max-w-2xl">
        Lumeo-native icon wrapper. Search, pick, copy. Uses <span class="text-foreground font-medium">Lucide</span> by default
        — switch libraries via the selector to preview alternatives.
    </Lumeo.Text>
</div>

@* ────────────────────────────────────────────────────────────────────── *@
@* Icon browser — the primary content of this page                        *@
@* ────────────────────────────────────────────────────────────────────── *@
<section id="browse" data-toc-entry data-toc-title="Browse icons" class="scroll-mt-24">
    <div class="rounded-[var(--radius-xl)] border border-border/40 bg-card overflow-hidden">

        @* Toolbar: search + library + size. Non-sticky — the grid below has
           its own internal scroll, so the toolbar stays visible while you
           browse icons. Making it sticky here caused it to overlap the grid's
           first row whenever the page was even slightly scrolled. *@
        <div class="border-b border-border/40 bg-card px-4 py-3">
            <Flex Gap="2" Align="center" Class="flex-wrap">
                <div class="flex-1 min-w-[240px] relative">
                    <div class="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground">
                        <DynamicIcon Name="Search" Class="h-4 w-4" />
                    </div>
                    <Input @ref="_searchInput"
                           Value="@_query"
                           ValueChanged="@((string v) => { _query = v; _selected = null; _displayLimit = _initialLimit; })"
                           Placeholder="Search icons — chevron, heart, chart, user…"
                           Class="pl-9" />
                    @if (!string.IsNullOrEmpty(_query))
                    {
                        <button type="button"
                                @onclick='@(() => { _query = ""; _selected = null; })'
                                class="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground p-1 rounded">
                            <DynamicIcon Name="X" Class="h-3.5 w-3.5" />
                        </button>
                    }
                </div>
                <Select Value="@IconService.ActiveLibrary" ValueChanged="@((string v) => { IconService.SetLibrary(v); _selected = null; _displayLimit = _initialLimit; })">
                    <SelectTrigger Class="h-9" Style="width: 200px">
                        <span class="text-sm whitespace-nowrap">@LibraryDisplayName(IconService.ActiveLibrary)</span>
                    </SelectTrigger>
                    <SelectContent Class="whitespace-nowrap" Style="min-width: 200px">
                        <SelectItem Value="lucide">Lucide</SelectItem>
                        <SelectItem Value="bootstrap">Bootstrap Icons</SelectItem>
                        <SelectItem Value="fluentui">Fluent UI</SelectItem>
                        <SelectItem Value="google-material">Google Material</SelectItem>
                        <SelectItem Value="font-awesome">Font Awesome</SelectItem>
                        <SelectItem Value="ionicons">Ionicons</SelectItem>
                        <SelectItem Value="material-design">Material Design</SelectItem>
                    </SelectContent>
                </Select>
                <Select Value="@_previewSize" ValueChanged="@((string v) => _previewSize = v)">
                    <SelectTrigger Class="h-9" Style="width: 140px">
                        <span class="text-sm whitespace-nowrap uppercase">@_previewSize</span>
                    </SelectTrigger>
                    <SelectContent Class="whitespace-nowrap" Style="min-width: 160px">
                        <SelectItem Value="xs"><span>Xs</span><span class="ml-auto text-xs text-muted-foreground">10 px</span></SelectItem>
                        <SelectItem Value="sm"><span>Sm</span><span class="ml-auto text-xs text-muted-foreground">14 px</span></SelectItem>
                        <SelectItem Value="md"><span>Md</span><span class="ml-auto text-xs text-muted-foreground">18 px</span></SelectItem>
                        <SelectItem Value="lg"><span>Lg</span><span class="ml-auto text-xs text-muted-foreground">24 px</span></SelectItem>
                        <SelectItem Value="xl"><span>Xl</span><span class="ml-auto text-xs text-muted-foreground">32 px</span></SelectItem>
                    </SelectContent>
                </Select>
            </Flex>
            <div class="mt-2 flex items-center justify-between text-xs text-muted-foreground">
                <span><span class="font-medium text-foreground">@Filtered.Count</span> of @ActiveCatalog.Count @LibraryDisplayName(IconService.ActiveLibrary) icons</span>
                @if (IconService.ActiveLibrary != "lucide")
                {
                    <span>Non-Lucide icons use <code class="font-mono text-[11px] px-1 rounded bg-muted">Svg=</code></span>
                }
            </div>
        </div>

        @* Grid OR selected-icon detail — side-by-side on desktop, stacked on mobile.
           `relative` + explicit `bg-card` on the aside prevents background bleed-through. *@
        <div class="flex flex-col lg:flex-row">

            @* Icon grid — inline style for the height cap because the `max-h-[Npx]`
               Tailwind arbitrary class wasn't being emitted in the docs' pre-built
               CSS, so the grid blew out to its full intrinsic height (~7000 icons
               tall for Material Design). Inline style is bulletproof — no Tailwind
               JIT dependency.
               @key on the library+query so the scroll container remounts fresh
               (scrollTop 0) whenever you switch library or change the search —
               avoids the first-row-cut-off look from a preserved scrollTop. *@
            <div @key="@GridKey"
                 id="@_gridId"
                 class="flex-1 p-3 min-w-0 overflow-y-auto"
                 style="max-height: 600px">
                @if (Filtered.Count == 0)
                {
                    <div class="flex flex-col items-center gap-2 py-16 text-muted-foreground">
                        <DynamicIcon Name="SearchX" Class="h-10 w-10 text-muted-foreground/40" />
                        <Lumeo.Text Size="sm">No icons match "<span class="text-foreground">@_query</span>"</Lumeo.Text>
                        <button type="button"
                                class="text-xs text-primary hover:underline"
                                @onclick='@(() => _query = "")'>
                            Clear search
                        </button>
                    </div>
                }
                else
                {
                    @* Tile size is fixed — the size picker only affects the selected-icon code snippet,
                       so changing to Xs doesn't squish every tile in the grid. *@
                    <div class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-6 xl:grid-cols-8 gap-2">
                        @foreach (var entry in Filtered.Take(_displayLimit))
                        {
                            var e = entry;
                            var isActive = _selected?.Name == e.Name;
                            <button type="button"
                                    @onclick="@(() => _selected = e)"
                                    title="@e.Name"
                                    class="@($"flex flex-col items-center justify-center gap-2 h-20 rounded-md border text-center transition-colors {(isActive ? "border-primary bg-primary/10 text-foreground" : "border-transparent hover:border-border/60 hover:bg-accent/50 text-muted-foreground hover:text-foreground")}")">
                                <Blazicon Svg="@e.Svg" class="shrink-0" style="@PreviewIconStyle" />
                                <span class="text-[10px] leading-tight truncate w-full px-1">@e.Name</span>
                            </button>
                        }
                    </div>
                    @if (Filtered.Count > _displayLimit)
                    {
                        <div class="mt-4 flex justify-center">
                            <button type="button"
                                    @onclick="() => _displayLimit += _pageStep"
                                    class="text-sm text-primary hover:underline">
                                Load @Math.Min(_pageStep, Filtered.Count - _displayLimit) more — showing @_displayLimit of @Filtered.Count
                            </button>
                        </div>
                    }
                }
            </div>

            @* Detail sidebar — appears when an icon is selected. Solid bg so the grid doesn't bleed through. *@
            @if (_selected is not null)
            {
                <aside class="w-full lg:w-[340px] shrink-0 border-t lg:border-t-0 lg:border-l border-border/40 bg-background p-5">
                    <div class="flex items-center justify-between mb-4">
                        <Lumeo.Text Size="xs" Color="muted" Class="uppercase tracking-wider font-medium">Selected</Lumeo.Text>
                        <button type="button"
                                @onclick="() => _selected = null"
                                class="text-muted-foreground hover:text-foreground p-1 rounded">
                            <DynamicIcon Name="X" Class="h-3.5 w-3.5" />
                        </button>
                    </div>

                    @* Big preview — uses the selected size from the toolbar picker *@
                    <div class="flex items-center justify-center h-32 rounded-md border border-border/40 bg-background mb-4">
                        <Blazicon Svg="@_selected.Svg" class="@BigPreviewClass" />
                    </div>

                    @* Name + library *@
                    <Stack Gap="0" Class="mb-4">
                        <Lumeo.Text Weight="medium" Class="text-base">@_selected.Name</Lumeo.Text>
                        <Lumeo.Text Size="xs" Color="muted">via @LibraryDisplayName(IconService.ActiveLibrary)</Lumeo.Text>
                    </Stack>

                    @* Size preview row — all five presets side-by-side *@
                    <div class="mb-4">
                        <Lumeo.Text Size="xs" Color="muted" Class="mb-2 block">All sizes</Lumeo.Text>
                        <div class="flex items-center justify-around rounded-md border border-border/40 bg-background px-3 py-3">
                            <Blazicon Svg="@_selected.Svg" class="h-3 w-3" />
                            <Blazicon Svg="@_selected.Svg" class="h-3.5 w-3.5" />
                            <Blazicon Svg="@_selected.Svg" class="h-4 w-4" />
                            <Blazicon Svg="@_selected.Svg" class="h-5 w-5" />
                            <Blazicon Svg="@_selected.Svg" class="h-6 w-6" />
                        </div>
                    </div>

                    @* Code snippet + copy *@
                    <div class="mb-2 flex items-center justify-between">
                        <Lumeo.Text Size="xs" Color="muted">Code</Lumeo.Text>
                        <button type="button"
                                @onclick="CopySnippet"
                                class="inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors">
                            @if (_copied)
                            {
                                <DynamicIcon Name="Check" Class="h-3.5 w-3.5 text-success" />
                                <span class="text-success">Copied</span>
                            }
                            else
                            {
                                <DynamicIcon Name="Copy" Class="h-3.5 w-3.5" />
                                <span>Copy</span>
                            }
                        </button>
                    </div>
                    <pre class="rounded-md border border-border/40 bg-background p-3 text-xs font-mono overflow-x-auto whitespace-pre">@SnippetForSelected</pre>
                </aside>
            }
        </div>
    </div>
</section>

@* ────────────────────────────────────────────────────────────────────── *@
@* Usage examples — tabs keep the page compact                            *@
@* ────────────────────────────────────────────────────────────────────── *@
<section id="usage" data-toc-entry data-toc-title="Usage" class="scroll-mt-24 mt-10">
    <Heading Level="2" Class="mb-4">Usage</Heading>

    <Tabs @bind-Value="_usageTab">
        <TabsList>
            <TabsTrigger Value="basic">Basic</TabsTrigger>
            <TabsTrigger Value="sizes">Sizes</TabsTrigger>
            <TabsTrigger Value="colors">Colors</TabsTrigger>
            <TabsTrigger Value="buttons">In buttons</TabsTrigger>
            <TabsTrigger Value="svg">Other libraries</TabsTrigger>
        </TabsList>

        <TabsContent Value="basic">
            <div class="rounded-md border border-border/40 p-6 bg-muted/20">
                <Flex Gap="4" Align="center">
                    <Icon Name="Search" />
                    <Icon Name="Heart" />
                    <Icon Name="Star" />
                    <Icon Name="Bell" />
                    <Icon Name="Settings" />
                    <Icon Name="ChevronDown" />
                </Flex>
            </div>
            <pre class="mt-3 rounded-md border border-border/40 bg-background p-3 text-xs font-mono overflow-x-auto">@_basicCode</pre>
        </TabsContent>

        <TabsContent Value="sizes">
            <div class="rounded-md border border-border/40 p-6 bg-muted/20">
                <Flex Gap="5" Align="center">
                    <Icon Name="Heart" Size="Icon.IconSize.Xs" />
                    <Icon Name="Heart" Size="Icon.IconSize.Sm" />
                    <Icon Name="Heart" />
                    <Icon Name="Heart" Size="Icon.IconSize.Lg" />
                    <Icon Name="Heart" Size="Icon.IconSize.Xl" />
                </Flex>
            </div>
            <pre class="mt-3 rounded-md border border-border/40 bg-background p-3 text-xs font-mono overflow-x-auto">@_sizesCode</pre>
        </TabsContent>

        <TabsContent Value="colors">
            <div class="rounded-md border border-border/40 p-6 bg-muted/20">
                <Flex Gap="4" Align="center">
                    <Icon Name="CircleCheck" Class="text-success" />
                    <Icon Name="CircleAlert" Class="text-destructive" />
                    <Icon Name="TriangleAlert" Class="text-warning" />
                    <Icon Name="Info" Class="text-info" />
                    <Icon Name="Star" Class="text-primary" />
                    <Icon Name="BadgeCheck" Class="text-muted-foreground" />
                </Flex>
            </div>
            <pre class="mt-3 rounded-md border border-border/40 bg-background p-3 text-xs font-mono overflow-x-auto">@_colorCode</pre>
        </TabsContent>

        <TabsContent Value="buttons">
            <div class="rounded-md border border-border/40 p-6 bg-muted/20">
                <Flex Gap="3" Align="center" Class="flex-wrap">
                    <Button>
                        <Icon Name="Plus" Class="mr-2" />
                        New project
                    </Button>
                    <Button Variant="Button.ButtonVariant.Outline">
                        <Icon Name="Download" Class="mr-2" />
                        Download
                    </Button>
                    <Button Variant="Button.ButtonVariant.Ghost" Size="Button.ButtonSize.Icon">
                        <Icon Name="Settings" />
                    </Button>
                    <Button Variant="Button.ButtonVariant.Destructive">
                        <Icon Name="Trash" Class="mr-2" />
                        Delete
                    </Button>
                </Flex>
            </div>
            <pre class="mt-3 rounded-md border border-border/40 bg-background p-3 text-xs font-mono overflow-x-auto">@_buttonCode</pre>
        </TabsContent>

        <TabsContent Value="svg">
            <div class="rounded-md border border-border/40 p-6 bg-muted/20">
                <Flex Gap="5" Align="center">
                    <Icon Svg="Blazicons.Lucide.Search" />
                    <Icon Svg="Blazicons.BootstrapIcon.Archive" />
                    <Icon Svg="Blazicons.FluentUiIcon.Alert" />
                    <Icon Svg="Blazicons.GoogleMaterialOutlinedIcon.Home" />
                    <Icon Svg="Blazicons.FontAwesomeSolidIcon.Heart" />
                </Flex>
            </div>
            <pre class="mt-3 rounded-md border border-border/40 bg-background p-3 text-xs font-mono overflow-x-auto">@_svgCode</pre>
        </TabsContent>
    </Tabs>
</section>

@* ────────────────────────────────────────────────────────────────────── *@
@* API reference                                                          *@
@* ────────────────────────────────────────────────────────────────────── *@
<section id="api-reference" data-toc-entry data-toc-title="API reference" class="scroll-mt-24 mt-10">
    <Heading Level="2" Class="mb-4">API reference</Heading>
    <div class="rounded-lg border border-border/40 overflow-hidden">
        <table class="w-full text-sm">
            <thead>
                <tr class="border-b border-border/40 bg-muted/40">
                    <th class="text-left p-3 font-medium text-foreground">Property</th>
                    <th class="text-left p-3 font-medium text-foreground">Type</th>
                    <th class="text-left p-3 font-medium text-foreground">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">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">PascalCase Lucide icon name. Unknown names fall back to <code class="font-mono text-xs">Circle</code>.</td>
                </tr>
                <tr>
                    <td class="p-3 font-mono text-xs text-primary">Svg</td>
                    <td class="p-3 font-mono text-xs text-muted-foreground">SvgIcon?</td>
                    <td class="p-3 font-mono text-xs text-muted-foreground">null</td>
                    <td class="p-3 text-muted-foreground">Direct Blazicons icon. Takes precedence over <code class="font-mono text-xs">Name</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">Icon.IconSize</td>
                    <td class="p-3 font-mono text-xs text-muted-foreground">Md</td>
                    <td class="p-3 text-muted-foreground">Xs (h-3) · Sm (h-3.5) · Md (h-4) · Lg (h-5) · Xl (h-6).</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">Extra CSS classes appended after the size class.</td>
                </tr>
            </tbody>
        </table>
    </div>
</section>

@code {
    // ── Browser state ─────────────────────────────────────────────────────────
    private string _query = "";
    private IconEntry? _selected;
    private string _previewSize = "md";
    private bool _copied;
    private const int _initialLimit = 120;
    private const int _pageStep = 240;
    private int _displayLimit = _initialLimit;
    private string _usageTab = "basic";
    private Lumeo.Input? _searchInput;
    private readonly string _gridId = $"icon-grid-{Guid.NewGuid():N}";
    private string _lastGridKey = "";

    // An icon entry the grid can render and the snippet can target.
    // Name is the Blazicons property name (e.g. "Search", "ChevronDown").
    private record IconEntry(string Name, SvgIcon Svg);

    // Full catalog per library — enumerated via reflection over the Blazicons
    // static classes so every available icon is shown automatically. No manual
    // list to keep in sync when Blazicons adds more.
    private static readonly Dictionary<string, IReadOnlyList<IconEntry>> _catalogsByLibrary = BuildCatalogs();

    // Key that re-mounts the grid scroll container whenever the filter/library
    // changes, so scrollTop resets to 0 and the first row is never cut off.
    private string GridKey => $"grid-{IconService.ActiveLibrary}-{_query}";

    private IReadOnlyList<IconEntry> ActiveCatalog =>
        _catalogsByLibrary.TryGetValue(IconService.ActiveLibrary, out var list)
            ? list
            : _catalogsByLibrary["lucide"];

    private List<IconEntry> Filtered => string.IsNullOrWhiteSpace(_query)
        ? ActiveCatalog.ToList()
        : ActiveCatalog.Where(e => e.Name.Contains(_query, StringComparison.OrdinalIgnoreCase)).ToList();

    private static Dictionary<string, IReadOnlyList<IconEntry>> BuildCatalogs() => new()
    {
        ["lucide"]          = EnumerateIcons(typeof(Lucide)),
        ["bootstrap"]       = EnumerateIcons(typeof(BootstrapIcon)),
        ["fluentui"]        = EnumerateIcons(typeof(FluentUiIcon)),
        ["google-material"] = EnumerateIcons(typeof(GoogleMaterialOutlinedIcon)),
        ["font-awesome"]    = EnumerateIcons(typeof(FontAwesomeSolidIcon)),
        ["ionicons"]        = EnumerateIcons(typeof(Ionicon)),
        ["material-design"] = EnumerateIcons(typeof(MdiIcon)),
    };

    private static IReadOnlyList<IconEntry> EnumerateIcons(Type svgIconContainer)
    {
        return svgIconContainer
            .GetProperties(BindingFlags.Public | BindingFlags.Static)
            .Where(p => p.PropertyType == typeof(SvgIcon))
            .Select(p => new IconEntry(p.Name, (SvgIcon)p.GetValue(null)!))
            .OrderBy(e => e.Name, StringComparer.Ordinal)
            .ToList();
    }

    // Tile dimensions stay fixed (h-20) regardless of size. The picker scales
    // only the icon glyph inside each tile, so the layout never collapses and
    // users can still preview how Xs vs Xl feels in the grid. Deltas are
    // deliberately wide (Xs=10px → Xl=32px) so the change is unmissable;
    // classes like h-3.5 get dropped from some pre-built Tailwind outputs if
    // nothing else references them, so the picker uses inline style instead.
    private string PreviewIconStyle => _previewSize switch
    {
        "xs" => "width: 10px; height: 10px;",
        "sm" => "width: 14px; height: 14px;",
        "lg" => "width: 24px; height: 24px;",
        "xl" => "width: 32px; height: 32px;",
        _ => "width: 18px; height: 18px;",
    };

    // Selected-icon big preview uses the picked size scaled up (Lg/Xl are
    // unmissable, Xs is still big enough to inspect).
    private string BigPreviewClass => _previewSize switch
    {
        "xs" => "h-6 w-6",
        "sm" => "h-8 w-8",
        "lg" => "h-16 w-16",
        "xl" => "h-20 w-20",
        _ => "h-12 w-12",
    };

    private string SnippetForSelected
    {
        get
        {
            if (_selected is null) return "";
            var n = _selected.Name;
            var sizeAttr = _previewSize == "md" ? "" : $" Size=\"Icon.IconSize.{char.ToUpper(_previewSize[0])}{_previewSize[1..]}\"";
            return IconService.ActiveLibrary switch
            {
                // For Lucide, always use the Svg= form — it's the universal
                // path that resolves every one of the 1000+ Lucide icons.
                // (The short Name= form only works for the ~100 icons that
                // Icon.razor's MapName hardcodes.)
                "lucide"          => $"<Icon Svg=\"Lucide.{n}\"{sizeAttr} />",
                "bootstrap"       => $"<Icon Svg=\"BootstrapIcon.{n}\"{sizeAttr} />",
                "fluentui"        => $"<Icon Svg=\"FluentUiIcon.{n}\"{sizeAttr} />",
                "google-material" => $"<Icon Svg=\"GoogleMaterialOutlinedIcon.{n}\"{sizeAttr} />",
                "font-awesome"    => $"<Icon Svg=\"FontAwesomeSolidIcon.{n}\"{sizeAttr} />",
                "ionicons"        => $"<Icon Svg=\"Ionicon.{n}\"{sizeAttr} />",
                "material-design" => $"<Icon Svg=\"MdiIcon.{n}\"{sizeAttr} />",
                _ => $"<Icon Svg=\"Lucide.{n}\"{sizeAttr} />",
            };
        }
    }

    private static string LibraryDisplayName(string key) => key switch
    {
        "lucide"          => "Lucide",
        "bootstrap"       => "Bootstrap Icons",
        "fluentui"        => "Fluent UI",
        "google-material" => "Google Material",
        "font-awesome"    => "Font Awesome",
        "ionicons"        => "Ionicons",
        "material-design" => "Material Design",
        _ => key,
    };

    private async Task CopySnippet()
    {
        if (_selected is null) return;
        try { await JS.InvokeVoidAsync("navigator.clipboard.writeText", SnippetForSelected); } catch { }
        _copied = true;
        StateHasChanged();
        await Task.Delay(1500);
        _copied = false;
        StateHasChanged();
    }

    protected override void OnInitialized()
    {
        IconService.OnChange += OnLibChanged;
    }

    private void OnLibChanged() => InvokeAsync(StateHasChanged);

    // Browsers restore scrollTop of inner scroll containers across reloads and
    // re-renders. @key alone doesn't reliably clear it. Resolve the element by
    // id in JS (not via ElementReference — Blazor's marshalled form of an
    // ElementReference isn't the live HTMLElement, so writes to it no-op).
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        var currentKey = GridKey;
        if (firstRender || currentKey != _lastGridKey)
        {
            _lastGridKey = currentKey;
            try
            {
                await JS.InvokeVoidAsync("lumeo.resetScrollTopById", _gridId);
            }
            catch (JSException) { /* transient — next render will catch it */ }
        }
    }

    public void Dispose()
    {
        IconService.OnChange -= OnLibChanged;
    }

    // Cross-library set — icons supported across all 7 Blazicons libraries.
    // Used when a non-Lucide provider is active so every tile renders something
    // meaningful (DynamicIcon only maps this set across providers).
    private static readonly string[] _crossLibraryCatalog =
    {
        "Accessibility", "AlignCenter", "AlignLeft", "AlignRight", "Archive",
        "ArrowDown", "ArrowLeft", "ArrowRight", "ArrowUp", "BarChart3", "Bell",
        "BellOff", "Blocks", "Bold", "Box", "Calendar", "CalendarDays", "Camera",
        "ChartBar", "Check", "CheckCheck", "CheckSquare", "ChevronDown",
        "ChevronLeft", "ChevronRight", "ChevronUp", "ChevronsUpDown", "Chrome",
        "CircleAlert", "CircleCheck", "CircleDot", "Clipboard", "ClipboardList",
        "Code", "Columns3", "Copy", "Disc", "Download", "Ellipsis", "ExternalLink",
        "Eye", "FileText", "Flag", "FolderOpen", "Github", "Globe", "Heart",
        "Home", "House", "Image", "Inbox", "Info", "Italic", "LayoutDashboard",
        "LayoutGrid", "LayoutTemplate", "List", "ListMusic", "Loader",
        "LoaderCircle", "Lock", "LogIn", "LogOut", "Mail", "Menu", "MessageCircle",
        "MessageSquare", "Mic", "Minus", "Moon", "MousePointerClick", "Music",
        "Package", "Paintbrush", "Palette", "PanelRightClose", "PanelRightOpen",
        "Pause", "Pencil", "Phone", "Pin", "Play", "Plus", "Radio", "RefreshCw",
        "RotateCcw", "Search", "SearchX", "Send", "Settings", "Share", "Share2",
        "Shield", "ShieldCheck", "ShoppingCart", "SkipBack", "SkipForward",
        "Slash", "SlidersHorizontal", "Smartphone", "Star", "Sun", "Tag", "Trash",
        "Trash2", "TrendingUp", "TriangleAlert", "Underline", "Upload", "User",
        "UserPlus", "Users", "Volume", "Volume1", "Volume2", "X", "Zap",
    };

    // Extended Lucide-only catalog kept for reference; not used yet. Wiring it
    // up requires reflecting into Blazicons.Lucide at render time (DynamicIcon
    // only maps the cross-library set), and updating Icon.razor's MapName so
    // every name here resolves through `<Icon Name=>`. Tracked as a follow-up.
    private static readonly string[] _lucideCatalogExtended = new[]
    {
        "Accessibility", "Activity", "Airplay", "AlarmClock", "Album",
        "AlignCenter", "AlignEndHorizontal", "AlignJustify", "AlignLeft", "AlignRight",
        "Anchor", "Aperture", "Apple", "Archive", "ArrowBigDown", "ArrowBigLeft",
        "ArrowBigRight", "ArrowBigUp", "ArrowDown", "ArrowDownLeft", "ArrowDownRight",
        "ArrowLeft", "ArrowLeftRight", "ArrowRight", "ArrowUp", "ArrowUpDown",
        "ArrowUpLeft", "ArrowUpRight", "Asterisk", "AtSign", "Award", "Axe",
        "Baby", "Backpack", "Badge", "BadgeCheck", "BadgeDollarSign", "BadgeInfo",
        "BadgePercent", "Banknote", "BarChart3", "Barcode", "Baseline", "Battery",
        "BatteryCharging", "BatteryFull", "BatteryLow", "Beaker", "Bed", "Bell",
        "BellOff", "BellRing", "Binary", "Bitcoin", "Blocks", "Bluetooth",
        "Bold", "Bolt", "Book", "BookOpen", "Bookmark", "BookmarkCheck",
        "BookmarkPlus", "BookmarkX", "Bot", "Box", "Boxes", "Briefcase",
        "Brush", "Bug", "Building", "Building2", "Bus", "Cake", "Calculator",
        "Calendar", "CalendarCheck", "CalendarClock", "CalendarDays", "CalendarHeart",
        "CalendarMinus", "CalendarOff", "CalendarPlus", "CalendarRange",
        "CalendarSearch", "CalendarX", "Camera", "CameraOff", "Car", "Cast",
        "Check", "CheckCheck", "ChefHat", "Chrome",
        "ChartBar", "ChartLine", "ChartPie", "ChartScatter", "ChartArea",
        "ChevronDown", "ChevronLeft", "ChevronRight", "ChevronUp",
        "ChevronsDown", "ChevronsLeft", "ChevronsRight", "ChevronsUp",
        "ChevronsUpDown", "Circle", "CircleAlert", "CircleCheck", "CircleDashed",
        "CircleDollarSign", "CircleDot", "CircleEllipsis", "CircleHelp", "CircleMinus",
        "CircleOff", "CirclePlus", "CircleSlash", "CircleStop", "CircleUser",
        "CircleX", "Clapperboard", "Clipboard", "ClipboardCheck", "ClipboardCopy",
        "ClipboardList", "ClipboardPaste", "Clock", "Cloud", "CloudDownload",
        "CloudOff", "CloudRain", "CloudSnow", "CloudSun", "CloudUpload",
        "Code", "Code2", "Coffee", "Cog", "Coins", "Columns2", "Columns3", "Command",
        "Compass", "Component", "Contact", "Container", "Copy", "Copyright",
        "CreditCard", "Crop", "Cross", "Crosshair", "Crown", "Cup",
        "Database", "Delete", "Diamond", "Dice1", "Dice2", "Dice3", "Dice4", "Dice5", "Dice6",
        "Disc", "Divide", "DollarSign", "Download", "DraftingCompass",
        "Ear", "Eclipse", "Edit", "EggFried", "Ellipsis", "Equal", "Eraser",
        "ExternalLink", "Eye", "EyeOff", "Facebook", "Factory", "Fan",
        "File", "FileArchive", "FileAudio", "FileBadge", "FileBox", "FileChartColumn",
        "FileCheck", "FileClock", "FileCode", "FileDiff", "FileDown", "FileEdit",
        "FileHeart", "FileImage", "FileInput", "FileJson", "FileKey", "FileLock",
        "FileMinus", "FileOutput", "FilePen", "FilePlus", "FileQuestion", "FileScan",
        "FileSearch", "FileSpreadsheet", "FileSymlink", "FileTerminal", "FileText",
        "FileType", "FileUp", "FileVideo", "FileVolume", "FileWarning", "FileX",
        "Film", "Filter", "Fingerprint", "Fish", "Flag", "FlagOff", "FlagTriangleLeft",
        "FlagTriangleRight", "Flame", "Flashlight", "Flask", "FlaskConical",
        "Flower", "Flower2", "Focus", "FoldHorizontal", "FoldVertical", "Folder",
        "FolderArchive", "FolderCheck", "FolderClosed", "FolderDown", "FolderGit",
        "FolderHeart", "FolderInput", "FolderKanban", "FolderKey", "FolderLock",
        "FolderMinus", "FolderOpen", "FolderOutput", "FolderPen", "FolderPlus",
        "FolderRoot", "FolderSearch", "FolderSymlink", "FolderSync", "FolderTree",
        "FolderUp", "FolderX", "Folders", "Footprints", "Forklift", "Forward",
        "Frame", "Framer", "Frown", "Fuel", "Fullscreen", "GalleryHorizontal",
        "GalleryVertical", "GalleryThumbnails", "Gamepad", "Gamepad2", "Gauge",
        "Gavel", "Gem", "Ghost", "Gift", "GitBranch", "GitCommit", "GitCompare",
        "GitFork", "GitGraph", "GitMerge", "GitPullRequest", "Github", "Gitlab",
        "GlassWater", "Glasses", "Globe", "Goal", "Grab", "GraduationCap", "Grape",
        "Grid2x2", "Grid3x3", "Grip", "GripHorizontal", "GripVertical", "Group",
        "Hammer", "Hand", "HandCoins", "HandHeart", "HandMetal", "HardDrive",
        "HardHat", "Hash", "Haze", "Headphones", "Headset", "Heart", "HeartCrack",
        "HeartHandshake", "HeartOff", "HeartPulse", "Heater", "Hexagon", "Highlighter",
        "History", "Home", "Hospital", "Hotel", "Hourglass", "House", "Ice",
        "IceCream", "Image", "ImageDown", "ImageMinus", "ImageOff", "ImagePlay",
        "ImagePlus", "ImageUp", "Images", "Import", "Inbox", "Indent", "IndianRupee",
        "Infinity", "Info", "Inspect", "Instagram", "Italic", "IterationCcw",
        "IterationCw", "JapaneseYen", "Joystick", "Key", "KeyRound", "Keyboard",
        "Lamp", "LampCeiling", "LampDesk", "LampFloor", "LampWallDown", "LampWallUp",
        "Landmark", "Languages", "Laptop", "Lasso", "Laugh", "Layers", "LayoutDashboard",
        "LayoutGrid", "LayoutList", "LayoutPanelLeft", "LayoutPanelTop", "LayoutTemplate",
        "Leaf", "LeafyGreen", "LibraryBig", "Library", "LifeBuoy", "Ligature", "Lightbulb",
        "Link", "Link2", "Linkedin", "List", "ListCheck", "ListChecks", "ListCollapse",
        "ListEnd", "ListFilter", "ListMinus", "ListMusic", "ListOrdered", "ListPlus",
        "ListRestart", "ListStart", "ListTodo", "ListTree", "ListVideo", "ListX",
        "Loader", "LoaderCircle", "LocateFixed", "LocateOff", "Locate", "Lock",
        "LockKeyhole", "LockOpen", "LogIn", "LogOut", "Logs", "Loader2", "Lollipop",
        "Luggage", "Magnet", "Mail", "MailCheck", "MailMinus", "MailOpen", "MailPlus",
        "MailQuestion", "MailSearch", "MailWarning", "MailX", "Mailbox", "Mails",
        "Map", "MapPin", "MapPinOff", "MapPinned", "Martini", "Maximize",
        "Maximize2", "Medal", "Megaphone", "Meh", "MemoryStick", "Menu",
        "MenuSquare", "Merge", "MessageCircle", "MessageCircleCode",
        "MessageCircleHeart", "MessageCirclePlus", "MessageCircleQuestion",
        "MessageCircleReply", "MessageCircleWarning", "MessageCircleX",
        "MessageSquare", "MessageSquareCode", "MessageSquareDashed",
        "MessageSquareDiff", "MessageSquareDot", "MessageSquareHeart",
        "MessageSquareMore", "MessageSquareOff", "MessageSquarePlus",
        "MessageSquareQuote", "MessageSquareReply", "MessageSquareShare",
        "MessageSquareText", "MessageSquareWarning", "MessageSquareX", "MessagesSquare",
        "Mic", "MicOff", "MicVocal", "Microscope", "Microwave", "Milestone",
        "Milk", "Minimize", "Minimize2", "Minus", "Monitor", "MonitorCheck",
        "MonitorDot", "MonitorDown", "MonitorOff", "MonitorPause", "MonitorPlay",
        "MonitorSmartphone", "MonitorSpeaker", "MonitorStop", "MonitorUp", "MonitorX",
        "Moon", "MoonStar", "Mountain", "MountainSnow", "Mouse", "MousePointer",
        "MousePointerClick", "Move", "Move3d", "MoveDiagonal", "MoveDiagonal2",
        "MoveDown", "MoveDownLeft", "MoveDownRight", "MoveHorizontal", "MoveLeft",
        "MoveRight", "MoveUp", "MoveUpLeft", "MoveUpRight", "MoveVertical", "Music",
        "Music2", "Music3", "Music4", "Navigation", "Network", "Newspaper", "Nfc",
        "Notebook", "NotebookPen", "NotebookTabs", "NotebookText", "NotepadText",
        "Nut", "Octagon", "Option", "Orbit", "Package", "Package2",
        "PackageCheck", "PackageMinus", "PackageOpen", "PackagePlus", "PackageSearch",
        "PackageX", "PaintBucket", "Paintbrush", "Paintbrush2", "Palette", "Palmtree",
        "Panda", "PanelBottom", "PanelBottomClose", "PanelBottomOpen", "PanelLeft",
        "PanelLeftClose", "PanelLeftOpen", "PanelRight", "PanelRightClose",
        "PanelRightOpen", "PanelTop", "PanelTopClose", "PanelTopOpen", "Paperclip",
        "Parentheses", "ParkingCircle", "ParkingMeter", "PartyPopper", "Pause",
        "PawPrint", "PcCase", "Pen", "PenLine", "PenTool", "Pencil", "PencilLine",
        "PencilRuler", "Pentagon", "Percent", "PersonStanding", "Phone", "PhoneCall",
        "PhoneForwarded", "PhoneIncoming", "PhoneMissed", "PhoneOff", "PhoneOutgoing",
        "Pi", "PictureInPicture", "PictureInPicture2", "PiggyBank", "Pill", "Pin",
        "PinOff", "Pipette", "Pizza", "Plane", "PlaneLanding", "PlaneTakeoff", "Play",
        "Plug", "Plug2", "PlugZap", "Plus", "Pocket", "PocketKnife", "Podcast",
        "Pointer", "PointerOff", "Popcorn", "Popsicle", "PoundSterling", "Power",
        "PowerOff", "Presentation", "Printer", "Projector", "Puzzle", "Pyramid",
        "Qr", "Quote", "Rabbit", "Radar", "Radiation", "Radio", "RadioReceiver",
        "RadioTower", "Radius", "Rainbow", "Rat", "Ratio", "Receipt", "RectangleHorizontal",
        "RectangleVertical", "Recycle", "Redo", "Redo2", "RefreshCcw", "RefreshCw",
        "Refrigerator", "Regex", "RemoveFormatting", "Repeat", "Repeat1", "Repeat2",
        "Replace", "Reply", "ReplyAll", "Rewind", "Ribbon", "Rocket", "RockingChair",
        "RollerCoaster", "Rotate3d", "RotateCcw", "RotateCw", "Route", "RouteOff",
        "Router", "Rows2", "Rows3", "Rss", "Ruler", "RussianRuble", "Sailboat",
        "Salad", "Sandwich", "Satellite", "Save", "SaveAll", "Scale", "Scale3d",
        "Scaling", "Scan", "ScanBarcode", "ScanEye", "ScanFace", "ScanLine",
        "ScanSearch", "ScanText", "School", "Scissors", "ScreenShare", "ScreenShareOff",
        "Scroll", "ScrollText", "Search", "SearchCheck", "SearchCode", "SearchSlash",
        "SearchX", "Send", "Server", "ServerCog", "ServerCrash", "ServerOff",
        "Settings", "Settings2", "Shapes", "Share", "Share2", "Sheet", "Shell",
        "Shield", "ShieldAlert", "ShieldBan", "ShieldCheck", "ShieldEllipsis",
        "ShieldHalf", "ShieldMinus", "ShieldOff", "ShieldPlus", "ShieldQuestion",
        "ShieldX", "Ship", "ShipWheel", "Shirt", "ShoppingBag", "ShoppingBasket",
        "ShoppingCart", "Shovel", "ShowerHead", "Shrink", "Shrub", "Shuffle", "Sigma",
        "Signal", "Signpost", "Siren", "SkipBack", "SkipForward", "Skull", "Slack",
        "Slash", "Slice", "SlidersHorizontal", "Slice", "Smartphone", "Smile",
        "SmilePlus", "Snail", "Snowflake", "Sofa", "Soup", "Space", "Spade", "Sparkle",
        "Sparkles", "Speaker", "Speech", "SpellCheck", "Spline", "Split", "SprayCan",
        "Sprout", "Square", "SquareActivity", "SquareArrowDown", "SquareArrowLeft",
        "SquareArrowOutDownLeft", "SquareArrowOutDownRight", "SquareArrowOutUpLeft",
        "SquareArrowOutUpRight", "SquareArrowRight", "SquareArrowUp", "SquareAsterisk",
        "SquareCheck", "SquareChevronDown", "SquareChevronLeft", "SquareChevronRight",
        "SquareChevronUp", "SquareCode", "SquareDashedBottomCode", "SquareDivide",
        "SquareDot", "SquareEqual", "SquareKanban", "SquareMousePointer", "SquareParking",
        "SquarePen", "SquarePi", "SquarePower", "SquareRadical", "SquareSigma",
        "SquareSlash", "SquareStack", "SquareTerminal", "SquareUser", "SquareX",
        "Squircle", "Squirrel", "Stamp", "Star", "StarHalf", "StarOff", "StepBack",
        "StepForward", "Stethoscope", "Sticker", "StickyNote", "Store", "StretchHorizontal",
        "StretchVertical", "Strikethrough", "Subscript", "Sun", "Sunrise", "Sunset",
        "Superscript", "SwatchBook", "SwissFranc", "Sword", "Swords", "Syringe",
        "Table", "Table2", "TableCellsMerge", "TableCellsSplit", "TableColumnsSplit",
        "TableOfContents", "TableProperties", "TableRowsSplit", "Tablet", "Tablets",
        "Tag", "Tags", "Tally1", "Tally2", "Tally3", "Tally4", "Tally5", "Tangent",
        "Target", "Telescope", "Tent", "TentTree", "Terminal", "TerminalSquare",
        "TestTube", "Text", "TextCursor", "TextQuote", "TextSelect", "Theater",
        "Thermometer", "ThumbsDown", "ThumbsUp", "Ticket", "TicketCheck", "TicketMinus",
        "TicketPercent", "TicketPlus", "TicketSlash", "TicketX", "Timer", "TimerOff",
        "TimerReset", "ToggleLeft", "ToggleRight", "Toilet", "Tornado", "Torus",
        "Touchpad", "TouchpadOff", "TowerControl", "ToyBrick", "Tractor", "TrafficCone",
        "Train", "TrainFront", "TrainTrack", "TramFront", "Trash", "Trash2", "TreeDeciduous",
        "TreePalm", "TreePine", "Trees", "Trello", "TrendingDown", "TrendingUp",
        "Triangle", "TriangleAlert", "TriangleRight", "Trophy", "Truck", "Turtle", "Tv",
        "Twitch", "Twitter", "Type", "Umbrella", "Underline", "Undo", "Undo2",
        "UnfoldHorizontal", "UnfoldVertical", "Ungroup", "University", "Unlink",
        "Unlink2", "Unlock", "UnlockKeyhole", "Upload", "Usb", "User", "UserCheck",
        "UserCog", "UserMinus", "UserPlus", "UserRound", "UserRoundCheck", "UserRoundCog",
        "UserRoundMinus", "UserRoundPlus", "UserRoundX", "UserSearch", "UserX", "Users",
        "Utensils", "UtensilsCrossed", "UtilityPole", "Variable", "Vault", "Vegan",
        "VenetianMask", "Verified", "Vibrate", "VibrateOff", "Video", "VideoOff",
        "Videotape", "View", "Voicemail", "Volume", "Volume1", "Volume2", "VolumeX",
        "Vote", "Wallet", "Wallet2", "WalletCards", "WalletMinimal", "Wallpaper",
        "Wand", "Wand2", "Warehouse", "WashingMachine", "Watch", "Waves", "Waypoints",
        "Webcam", "Webhook", "Weight", "Wheat", "Wifi", "WifiOff", "Wind", "Wine",
        "WineOff", "Workflow", "WrapText", "Wrench", "X", "Youtube", "Zap", "ZapOff",
        "ZoomIn", "ZoomOut",
    };

    // ── Tab snippets ──────────────────────────────────────────────────────────
    private const string _basicCode = @"<Icon Name=""Search"" />
<Icon Name=""Heart"" />
<Icon Name=""Star"" />
<Icon Name=""Bell"" />
<Icon Name=""Settings"" />
<Icon Name=""ChevronDown"" />";

    private const string _sizesCode = @"<Icon Name=""Heart"" Size=""Icon.IconSize.Xs"" />
<Icon Name=""Heart"" Size=""Icon.IconSize.Sm"" />
<Icon Name=""Heart"" />                   @* Md (default) *@
<Icon Name=""Heart"" Size=""Icon.IconSize.Lg"" />
<Icon Name=""Heart"" Size=""Icon.IconSize.Xl"" />";

    private const string _colorCode = @"<Icon Name=""CircleCheck""   Class=""text-success"" />
<Icon Name=""CircleAlert""   Class=""text-destructive"" />
<Icon Name=""TriangleAlert"" Class=""text-warning"" />
<Icon Name=""Info""          Class=""text-info"" />
<Icon Name=""Star""          Class=""text-primary"" />";

    private const string _buttonCode = @"<Button>
    <Icon Name=""Plus"" Class=""mr-2"" />
    New project
</Button>

<Button Variant=""Button.ButtonVariant.Ghost"" Size=""Button.ButtonSize.Icon"">
    <Icon Name=""Settings"" />
</Button>";

    private const string _svgCode = @"@using Blazicons

<Icon Svg=""Lucide.Search"" />
<Icon Svg=""BootstrapIcon.Archive"" />
<Icon Svg=""FluentUiIcon.Alert"" />
<Icon Svg=""GoogleMaterialOutlinedIcon.Home"" />
<Icon Svg=""FontAwesomeSolidIcon.Heart"" />";
}
