@namespace Lumeo
@using Lumeo.Services
@implements IAsyncDisposable

@foreach (var overlay in _overlays)
{
    @switch (overlay.Type)
    {
        case OverlayType.Dialog:
            <Dialog @key="overlay.Id" Open="true" OpenChanged="(bool val) => OnOverlayClosed(val, overlay.Id)">
                <DialogContent PreventClose="@overlay.Options.PreventClose" Class="@overlay.Options.Class">
                    @if (overlay.Title is not null)
                    {
                        <DialogHeader>
                            <DialogTitle>@overlay.Title</DialogTitle>
                        </DialogHeader>
                    }
                    <CascadingValue Value="@(new OverlayShellMarker(overlay.Id))" IsFixed="true">
                        <CascadingValue Value="@GetOrCreateReference(overlay)" IsFixed="true">
                            <DynamicComponent Type="@GetComponentType(overlay)" Parameters="@GetParameters(overlay)" />
                        </CascadingValue>
                    </CascadingValue>
                </DialogContent>
            </Dialog>
            break;

        case OverlayType.Sheet:
            <Sheet @key="overlay.Id" Open="true" OpenChanged="(bool val) => OnOverlayClosed(val, overlay.Id)">
                <SheetContent Side="@EffectiveSheetSide(overlay.Options)" Size="@EffectiveSheetSize(overlay.Options)" PreventClose="@overlay.Options.PreventClose" SwipeToClose="@EffectiveSwipeToClose(overlay.Options)" Class="@overlay.Options.Class">
                    @if (overlay.Title is not null)
                    {
                        <SheetHeader>
                            <SheetTitle>@overlay.Title</SheetTitle>
                        </SheetHeader>
                    }
                    <CascadingValue Value="@(new OverlayShellMarker(overlay.Id))" IsFixed="true">
                        <CascadingValue Value="@GetOrCreateReference(overlay)" IsFixed="true">
                            <DynamicComponent Type="@GetComponentType(overlay)" Parameters="@GetParameters(overlay)" />
                        </CascadingValue>
                    </CascadingValue>
                </SheetContent>
            </Sheet>
            break;

        case OverlayType.Drawer:
            <Drawer @key="overlay.Id" Open="true" OpenChanged="(bool val) => OnOverlayClosed(val, overlay.Id)">
                <DrawerContent PreventClose="@overlay.Options.PreventClose" Class="@overlay.Options.Class">
                    @if (overlay.Title is not null)
                    {
                        <DrawerHeader>
                            <DrawerTitle>@overlay.Title</DrawerTitle>
                        </DrawerHeader>
                    }
                    <CascadingValue Value="@(new OverlayShellMarker(overlay.Id))" IsFixed="true">
                        <CascadingValue Value="@GetOrCreateReference(overlay)" IsFixed="true">
                            <DynamicComponent Type="@GetComponentType(overlay)" Parameters="@GetParameters(overlay)" />
                        </CascadingValue>
                    </CascadingValue>
                </DrawerContent>
            </Drawer>
            break;

        case OverlayType.AlertDialog:
            <AlertDialog @key="overlay.Id" Open="true">
                <AlertDialogContent>
                    <AlertDialogHeader>
                        <AlertDialogTitle>@(overlay.AlertOptions?.Title ?? "Are you sure?")</AlertDialogTitle>
                        @if (overlay.AlertOptions?.Description is not null)
                        {
                            <AlertDialogDescription>@overlay.AlertOptions.Description</AlertDialogDescription>
                        }
                    </AlertDialogHeader>
                    <AlertDialogFooter>
                        <AlertDialogCancel @onclick="() => HandleCancel(overlay.Id)">@(overlay.AlertOptions?.CancelText ?? "Cancel")</AlertDialogCancel>
                        @if (overlay.AlertOptions?.IsDestructive == true)
                        {
                            <AlertDialogAction OnAction="() => HandleConfirm(overlay.Id)">@(overlay.AlertOptions?.ConfirmText ?? "Continue")</AlertDialogAction>
                        }
                        else
                        {
                            <Button OnClick="() => HandleConfirm(overlay.Id)">@(overlay.AlertOptions?.ConfirmText ?? "Continue")</Button>
                        }
                    </AlertDialogFooter>
                </AlertDialogContent>
            </AlertDialog>
            break;
    }
}

@inject OverlayService OverlayService
@inject IResponsiveService Responsive

@code {
    private readonly List<OverlayInstance> _overlays = new();
    private readonly Dictionary<string, OverlayReference> _referenceCache = new();

    protected override void OnInitialized()
    {
        OverlayService.OnShow += HandleShow;
        OverlayService.OnClose += HandleClose;
        // 2.1.3 — react to viewport changes so overlays with responsive
        // Mobile* overrides swap their effective Side/Size/SwipeToClose when
        // the user rotates a tablet or resizes the window. Cheap StateHasChanged
        // re-render that flips through EffectiveSheet* per overlay.
        Responsive.ViewportChanged += HandleViewportChanged;
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            // Pull the initial viewport size + attach the JS resize listener
            // once, lazily. Subsequent overlays read straight from the cached
            // breakpoint.
            await Responsive.EnsureInitialisedAsync();
        }
    }

    private void HandleViewportChanged(ViewportInfo _)
    {
        // Only re-render if any open overlay actually has responsive Mobile*
        // overrides — common case (no responsive options on any sheet) costs
        // zero render churn.
        if (_overlays.Any(o => HasMobileOverrides(o.Options)))
        {
            InvokeAsync(StateHasChanged);
        }
    }

    private static bool HasMobileOverrides(OverlayOptions opts) =>
        opts.MobileBreakpoint is not null
        && (opts.MobileSheetSide is not null
            || opts.MobileSheetSize is not null
            || opts.MobileSwipeToClose is not null);

    // Effective Side/Size/SwipeToClose helpers — picked at each render so a
    // viewport crossing flips them automatically via HandleViewportChanged.
    private bool IsMobile(OverlayOptions opts)
    {
        if (opts.MobileBreakpoint is not int bp) return false;
        return Responsive.Width > 0 && Responsive.Width < bp;
    }

    private SheetContent.SheetSide EffectiveSheetSide(OverlayOptions opts)
    {
        var side = IsMobile(opts) && opts.MobileSheetSide is { } ms ? ms : opts.SheetSide;
        return MapSheetSide(side);
    }

    private SheetContent.SheetSize EffectiveSheetSize(OverlayOptions opts)
    {
        var size = IsMobile(opts) && opts.MobileSheetSize is { } ms ? ms : opts.SheetSize;
        return MapSheetSize(size);
    }

    private bool EffectiveSwipeToClose(OverlayOptions opts) =>
        IsMobile(opts) && opts.MobileSwipeToClose is bool m ? m : opts.SwipeToClose;

    private void HandleShow(OverlayInstance instance)
    {
        SafeAsyncDispatcher.FireAndForget(InvokeAsync, () => HandleShowAsync(instance), "OverlayProvider.HandleShow");
    }

    private Task HandleShowAsync(OverlayInstance instance)
    {
        _overlays.Add(instance);
        // Pre-cache the reference so the CascadingValue always receives the same
        // object reference for the lifetime of this overlay (prevents subtree re-renders).
        _referenceCache[instance.Id] = new OverlayReference(instance.Id, instance.Parameters, OverlayService);
        StateHasChanged();
        return Task.CompletedTask;
    }

    private void HandleClose(string id, object? result, bool cancelled)
    {
        SafeAsyncDispatcher.FireAndForget(InvokeAsync, () => HandleCloseAsync(id, result, cancelled), "OverlayProvider.HandleClose");
    }

    private Task HandleCloseAsync(string id, object? result, bool cancelled)
    {
        var overlay = _overlays.FirstOrDefault(o => o.Id == id);
        if (overlay is null) return Task.CompletedTask;

        _overlays.Remove(overlay);
        _referenceCache.Remove(id);

        if (cancelled)
            overlay.Tcs.TrySetResult(OverlayResult.CancelResult());
        else
            overlay.Tcs.TrySetResult(OverlayResult.Ok(result));

        StateHasChanged();
        return Task.CompletedTask;
    }

    private void OnOverlayClosed(bool isOpen, string id)
    {
        if (!isOpen) OverlayService.Cancel(id);
    }

    private void HandleConfirm(string id)
    {
        OverlayService.Close(id, true);
    }

    private void HandleCancel(string id)
    {
        OverlayService.Cancel(id);
    }

    private static Type GetComponentType(OverlayInstance overlay) => overlay.ComponentType ?? typeof(Fallback);
    private static Dictionary<string, object>? GetParameters(OverlayInstance overlay) => overlay.Parameters?.ToDictionary();

    private class Fallback : Microsoft.AspNetCore.Components.ComponentBase { }

    private OverlayReference GetOrCreateReference(OverlayInstance overlay)
    {
        if (!_referenceCache.TryGetValue(overlay.Id, out var reference))
        {
            reference = new OverlayReference(overlay.Id, overlay.Parameters, OverlayService);
            _referenceCache[overlay.Id] = reference;
        }
        return reference;
    }

    private static SheetContent.SheetSide MapSheetSide(SheetSide side) => side switch
    {
        SheetSide.Top => SheetContent.SheetSide.Top,
        SheetSide.Bottom => SheetContent.SheetSide.Bottom,
        SheetSide.Left => SheetContent.SheetSide.Left,
        _ => SheetContent.SheetSide.Right
    };

    private static SheetContent.SheetSize MapSheetSize(SheetSize size) => size switch
    {
        SheetSize.Sm => SheetContent.SheetSize.Sm,
        SheetSize.Lg => SheetContent.SheetSize.Lg,
        SheetSize.Xl => SheetContent.SheetSize.Xl,
        SheetSize.Full => SheetContent.SheetSize.Full,
        _ => SheetContent.SheetSize.Default
    };

    public async ValueTask DisposeAsync()
    {
        OverlayService.OnShow -= HandleShow;
        OverlayService.OnClose -= HandleClose;
        Responsive.ViewportChanged -= HandleViewportChanged;

        foreach (var overlay in _overlays)
        {
            overlay.Tcs.TrySetResult(OverlayResult.CancelResult());
        }
        _overlays.Clear();
        _referenceCache.Clear();
        await Task.CompletedTask;
    }
}
