@namespace Lumeo
@implements IAsyncDisposable

@if (_extensionLoaded)
{
    <Chart OptionJson="@_optionJson" Width="@Width" Height="@Height" Theme="@Theme"
           EChartsSource="@EChartsSource" Class="@Class" IsLoading="@IsLoading" ShowLoadingSkeleton="@ShowLoadingSkeleton" SkeletonKind="@SkeletonKind" SkeletonStyle="@SkeletonStyle" ShowLoadingLabel="@ShowLoadingLabel" LoadingText="@LoadingText" PhantomCycleMs="@PhantomCycleMs" @attributes="AdditionalAttributes" />
}
else
{
    <div style="width: @Width; height: @Height;" class="@Class"></div>
}

@inject ComponentInteropService Interop

@code {
    [Parameter] public List<WordCloudItem>? Words { get; set; }
    [Parameter] public string Shape { get; set; } = "circle";
    [Parameter] public int[] SizeRange { get; set; } = new[] { 12, 60 };
    [Parameter] public int[] RotationRange { get; set; } = new[] { -90, 90 };
    [Parameter] public string? EChartsSource { get; set; }
    [Parameter] public bool ShowTooltip { get; set; } = true;
    [Parameter] public string Width { get; set; } = "100%";
    [Parameter] public string Height { get; set; } = "350px";
    [Parameter] public string? Theme { get; set; }
    [Parameter] public string? Class { get; set; }
    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }
    /// <summary>When true, overlays a shape-aware loading skeleton on top of the chart (consumer is fetching data). Flips back to the real chart once set to false.</summary>
    [Parameter] public bool IsLoading { get; set; }
    /// <summary>When true (default), renders a shape-aware skeleton while the chart has not yet mounted and whenever <see cref="IsLoading"/> is true. Set to <c>false</c> to opt out.</summary>
    [Parameter] public bool ShowLoadingSkeleton { get; set; } = true;
    /// <summary>Shape family for the loading skeleton. Defaults to <c>ChartSkeletonKind.Generic</c> for this chart type; override if you want a different silhouette.</summary>
    [Parameter] public ChartSkeletonKind SkeletonKind { get; set; } = ChartSkeletonKind.Generic;
    /// <summary>Phantom (default) renders the real chart with placeholder data and morphs to real data via ECharts' option-change animation. Silhouette falls back to the legacy SVG skeleton overlay.</summary>
    [Parameter] public ChartSkeletonStyle SkeletonStyle { get; set; } = ChartSkeletonStyle.Phantom;
    /// <summary>Phantom-mode refresh interval in milliseconds. Lower = faster data cycling while loading. Defaults to 1400.</summary>
    [Parameter] public int PhantomCycleMs { get; set; } = 1400;
    /// <summary>When true (default), shows a small "Loading…" pill in the top-right corner whenever <see cref="IsLoading"/> is active. Works on top of any <see cref="SkeletonStyle"/>.</summary>
    [Parameter] public bool ShowLoadingLabel { get; set; } = true;
    /// <summary>Label rendered next to the spinner inside the loading pill. Set to empty to hide. Defaults to "Loading…".</summary>
    [Parameter] public string? LoadingText { get; set; } = "Loading…";

    [Parameter] public bool ShowDataLabels { get; set; }
    [Parameter] public string LabelPosition { get; set; } = "center";
    [Parameter] public string? LabelFormat { get; set; }
    [Parameter] public bool Toolbox { get; set; }
    [Parameter] public List<string>? ColorPalette { get; set; }
    [Parameter] public int? AnimationDuration { get; set; }
    [Parameter] public string? AnimationEasing { get; set; }
    [Parameter] public Dictionary<string, object>? OptionOverride { get; set; }

    private string _optionJson = "{}";
    private IJSObjectReference? _module;
    private bool _extensionLoaded;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && !_extensionLoaded)
        {
            _module = await Interop.ImportModuleAsync("./_content/Lumeo.Charts/js/echarts-interop.js");
            await _module.InvokeVoidAsync("loadExtension",
                "https://cdn.jsdelivr.net/npm/echarts-wordcloud@2.1.0/dist/echarts-wordcloud.min.js");
            // Wait for the extension to fully register its series type and renderers
            await Task.Delay(200);
            _extensionLoaded = true;
            BuildOptionJson();
            StateHasChanged();
        }
    }

    protected override void OnParametersSet()
    {
        if (_extensionLoaded)
            BuildOptionJson();
    }

    private void BuildOptionJson()
    {
        var option = new Dictionary<string, object?>();

        var wordData = Words?.Select(w => new Dictionary<string, object?>
        {
            ["name"] = w.Text,
            ["value"] = w.Weight
        }).ToList() ?? new List<Dictionary<string, object?>>();

        var series = new Dictionary<string, object?>
        {
            ["type"] = "wordCloud",
            ["shape"] = Shape,
            ["sizeRange"] = SizeRange,
            ["rotationRange"] = RotationRange,
            ["rotationStep"] = 45,
            ["gridSize"] = 8,
            ["drawOutOfBound"] = false,
            ["data"] = wordData,
            ["textStyle"] = new Dictionary<string, object?>
            {
                ["fontFamily"] = "sans-serif",
                ["fontWeight"] = "bold"
            }
        };

        option["series"] = new[] { series };

        if (ShowTooltip)
            option["tooltip"] = new Dictionary<string, object?> { ["trigger"] = "item" };

        if (ColorPalette is { Count: > 0 })
            option["color"] = ColorPalette;

        if (AnimationDuration.HasValue)
            option["animationDuration"] = AnimationDuration;
        if (!string.IsNullOrEmpty(AnimationEasing))
            option["animationEasing"] = AnimationEasing;

        if (Toolbox)
            option["toolbox"] = new Dictionary<string, object?>
            {
                ["show"] = true,
                ["feature"] = new Dictionary<string, object?>
                {
                    ["saveAsImage"] = new Dictionary<string, object?> { ["show"] = true }
                }
            };

        if (OptionOverride is { Count: > 0 })
            foreach (var kvp in OptionOverride)
                option[kvp.Key] = kvp.Value;

        _optionJson = System.Text.Json.JsonSerializer.Serialize(option);
    }

    public async ValueTask DisposeAsync()
    {
        if (_module is not null)
        {
            try { await _module.DisposeAsync(); }
            catch (JSDisconnectedException) { }
        }
    }

    public class WordCloudItem
    {
        public string Text { get; set; } = "";
        public double Weight { get; set; }
    }
}
