@namespace Lumeo
@implements IAsyncDisposable
@inject ComponentInteropService Interop

@* The SVG is absolutely positioned inside the container via Blazor's wrapper.
   ContainerId MUST be the id of the position:relative parent that holds both
   the from/to nodes and this component. *@
<svg id="@_elementId"
     class="@CssClass"
     aria-hidden="true"
     style="position:absolute;top:0;left:0;pointer-events:none;overflow:visible;"
     @attributes="AdditionalAttributes">
    <defs>
        <linearGradient id="@_gradientId" gradientUnits="userSpaceOnUse"
                        x1="0" y1="0" x2="100" y2="0">
            <stop offset="0%" stop-color="@GradientStartColor" stop-opacity="0" />
            <stop offset="40%" stop-color="@GradientStartColor" />
            <stop offset="70%" stop-color="@GradientStopColor" />
            <stop offset="100%" stop-color="@GradientStopColor" stop-opacity="0" />
        </linearGradient>
    </defs>
    @* Guide path — always visible as a faint track *@
    <path id="@(_elementId + "-track")"
          stroke="@PathColor"
          stroke-width="@PathWidth"
          stroke-opacity="@(PathOpacity.ToString("F2", System.Globalization.CultureInfo.InvariantCulture))"
          stroke-linecap="round"
          fill="none" />
    @* Animated beam path — gradient travels along the track *@
    <path id="@(_elementId + "-beam")"
          stroke="url(#@_gradientId)"
          stroke-width="@BeamWidth"
          stroke-linecap="round"
          fill="none" />
</svg>

@code {
    /// <summary>
    /// The <c>id</c> of the container element that holds both the from/to nodes
    /// and this AnimatedBeam. Coordinates are measured relative to this element
    /// (matching Magic UI's containerRef pattern). If null, the SVG's own
    /// bounding rect is used as fallback.
    /// </summary>
    [Parameter] public string? ContainerId { get; set; }
    /// <summary>The element id of the source node.</summary>
    [Parameter] public string? FromId { get; set; }
    /// <summary>The element id of the destination node.</summary>
    [Parameter] public string? ToId { get; set; }
    /// <summary>Duration of one full beam cycle in milliseconds.</summary>
    [Parameter] public int DurationMs { get; set; } = 3000;
    /// <summary>Delay before the first animation cycle starts.</summary>
    [Parameter] public int DelayMs { get; set; } = 0;
    /// <summary>Curvature of the bezier path. 0 = straight. Positive = bows up.</summary>
    [Parameter] public double Curvature { get; set; } = 0;
    /// <summary>Reverses the direction of travel.</summary>
    [Parameter] public bool Reverse { get; set; } = false;
    /// <summary>Color at the start of the gradient.</summary>
    [Parameter] public string GradientStartColor { get; set; } = "#ffaa40";
    /// <summary>Color at the end of the gradient.</summary>
    [Parameter] public string GradientStopColor { get; set; } = "#9c40ff";
    /// <summary>CSS color of the guide track path.</summary>
    [Parameter] public string PathColor { get; set; } = "var(--color-border)";
    /// <summary>Width of the guide track in px.</summary>
    [Parameter] public double PathWidth { get; set; } = 1;
    /// <summary>Opacity of the guide track (0–1).</summary>
    [Parameter] public double PathOpacity { get; set; } = 0.3;
    /// <summary>Width of the animated beam stroke in px.</summary>
    [Parameter] public double BeamWidth { get; set; } = 2;
    [Parameter] public string? Class { get; set; }
    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }

    private readonly string _elementId = $"lumeo-animated-beam-{Guid.NewGuid():N}";
    private readonly string _gradientId = $"lumeo-beam-grad-{Guid.NewGuid():N}";
    private bool _registered;

    private string CssClass => string.Join(" ", new[] { "lumeo-animated-beam", Class }.Where(s => !string.IsNullOrWhiteSpace(s)));

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && !_registered && !string.IsNullOrEmpty(FromId) && !string.IsNullOrEmpty(ToId))
        {
            _registered = true;
            try
            {
                await Interop.MotionAnimatedBeam(_elementId, FromId, ToId, new
                {
                    containerId = ContainerId,
                    durationMs = DurationMs,
                    delayMs = DelayMs,
                    curvature = Curvature,
                    reverse = Reverse
                });
            }
            catch (JSDisconnectedException) { }
        }
    }

    public async ValueTask DisposeAsync()
    {
        if (_registered)
        {
            try { await Interop.MotionDisposeAnimatedBeam(_elementId); }
            catch (JSDisconnectedException) { }
        }
    }
}
