@namespace Lumeo
@using Lumeo.Services
@implements IAsyncDisposable

@*
    Animation=Slide is now safe (rc.21 fixed the transform trap), but
    Animation=Fade or None still available for consumers who want maximum
    predictability. The Slide keyframes were updated so they end at
    `transform: none` rather than an identity-translate, which used to create
    a CSS containing-block trap that broke `position: fixed` popovers
    (Select/DatePicker/Combobox) rendered inside a Drawer.
*@

@if (Shell is not null)
{
    @* See OverlayShellMarker — host DrawerContent already painted the shell.
       Pass children through to avoid a second backdrop + panel + swipe handler. *@
    @ChildContent
}
else if (Context.IsOpen)
{
    <div class="fixed inset-0 z-50">
        <div class="fixed inset-0 bg-black/80 animate-fade-in" @onclick="HandleBackdropClick"></div>
        <div id="@_contentId" class="@CssClass" role="dialog" aria-modal="true" aria-labelledby="@Context.TitleId" aria-describedby="@Context.DescriptionId" @onkeydown="HandleKeyDown" tabindex="-1" @attributes="AdditionalAttributes">
            @if (ShowDragHandle)
            {
                <div class="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted"></div>
            }
            @ChildContent
        </div>
    </div>
}

@inject ComponentInteropService Interop

@code {
    [CascadingParameter] public Drawer.DrawerContext Context { get; set; } = default!;
    [CascadingParameter] public OverlayShellMarker? Shell { get; set; }
    [Parameter] public RenderFragment? ChildContent { get; set; }
    [Parameter] public DrawerSide Side { get; set; } = DrawerSide.Bottom;
    [Parameter] public DrawerAnimation Animation { get; set; } = DrawerAnimation.Slide;
    [Parameter] public bool PreventClose { get; set; }
    [Parameter] public string? Class { get; set; }
    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }

    private readonly string _contentId = $"drawer-{Guid.NewGuid():N}";
    private bool _wasOpen;
    // Slide-animation transform trap is fixed via JS interop:
    // Interop.AttachOverlaySlideEnd registers a native animationend listener
    // that filters strictly on slide-in-from-* AND on event.target === this
    // element, then sets inline `transform: none`. See SheetContent for the
    // full rationale.

    private bool ShowDragHandle => Side is DrawerSide.Top or DrawerSide.Bottom;

    private string PositionClasses => Side switch
    {
        DrawerSide.Top => "inset-x-0 top-0 rounded-b-[10px] max-h-[96vh]",
        DrawerSide.Right => "inset-y-0 right-0 w-3/4 max-w-sm rounded-l-[10px] h-full max-h-screen",
        DrawerSide.Bottom => "inset-x-0 bottom-0 rounded-t-[10px] max-h-[96vh]",
        DrawerSide.Left => "inset-y-0 left-0 w-3/4 max-w-sm rounded-r-[10px] h-full max-h-screen",
        _ => "inset-x-0 bottom-0 rounded-t-[10px] max-h-[96vh]"
    };

    private string AnimationClass => Animation switch
    {
        DrawerAnimation.None => "",
        DrawerAnimation.Fade => "animate-fade-in",
        DrawerAnimation.Slide => Side switch
        {
            DrawerSide.Top => "animate-slide-in-from-top",
            DrawerSide.Right => "animate-slide-in-from-right",
            DrawerSide.Bottom => "animate-slide-in-from-bottom",
            DrawerSide.Left => "animate-slide-in-from-left",
            _ => "animate-slide-in-from-bottom"
        },
        _ => ""
    };

    private string CssClass =>
        $"fixed z-50 mt-24 flex h-auto flex-col bg-background shadow-lg {PositionClasses} {AnimationClass} {Class}".Trim();

    private string SwipeDirection => Side switch
    {
        DrawerSide.Top => "up",
        DrawerSide.Right => "right",
        DrawerSide.Bottom => "down",
        DrawerSide.Left => "left",
        _ => "down"
    };

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        // Passthrough mode — host DrawerContent owns scroll lock, focus trap, swipe.
        if (Shell is not null) return;

        if (Context.IsOpen && !_wasOpen)
        {
            await Interop.LockScroll();
            await Interop.SetupFocusTrap(_contentId);
            // Same animationend cleanup as SheetContent — bypasses Blazor
            // event roundtrip + filters strictly on slide-in-from-* names.
            if (Animation == DrawerAnimation.Slide)
            {
                await Interop.AttachOverlaySlideEnd(_contentId);
            }
            if (!PreventClose)
            {
                await Interop.RegisterDrawerSwipe(_contentId, SwipeDirection, Close);
            }
            _wasOpen = true;
        }
        else if (!Context.IsOpen && _wasOpen)
        {
            await Cleanup();
            _wasOpen = false;
        }
    }

    private async Task Close() => await Context.SetOpen.InvokeAsync(false);

    private async Task HandleBackdropClick()
    {
        if (!PreventClose) await Close();
    }


    private async Task HandleKeyDown(KeyboardEventArgs e)
    {
        if (e.Key == "Escape" && !PreventClose) await Close();
    }

    private async Task Cleanup()
    {
        try
        {
            await Interop.UnregisterDrawerSwipe(_contentId);
            await Interop.RemoveFocusTrap(_contentId);
            await Interop.UnlockScroll();
        }
        catch (JSDisconnectedException) { }
    }

    public async ValueTask DisposeAsync()
    {
        if (_wasOpen) await Cleanup();
    }

    public enum DrawerSide { Top, Right, Bottom, Left }
    public enum DrawerAnimation { Slide, Fade, None }
}
