@namespace Lumeo
@implements IAsyncDisposable
@inject ComponentInteropService Interop
@inject Lumeo.Services.Localization.ILumeoLocalizer L

@if (Open && !_minimized && !_maximized)
{
    <div id="@_windowId"
         class="@CssClass"
         style="@StyleString"
         role="dialog"
         aria-modal="false"
         aria-labelledby="@_titleId"
         @attributes="AdditionalAttributes">
        @* Title bar *@
        <div id="@_titleBarId"
             class="flex items-center gap-2 px-4 py-2 border-b border-border/40 bg-card rounded-t-lg cursor-default select-none"
             @onpointerdown="HandleTitleBarPointerDown"
             @onpointermove="HandlePointerMove"
             @onpointerup="HandlePointerUp"
             @onpointercancel="HandlePointerUp">
            <span id="@_titleId" class="flex-1 text-sm font-medium text-foreground truncate">
                @if (TitleContent is not null)
                {
                    @TitleContent
                }
                else
                {
                    @Title
                }
            </span>
            <div class="flex items-center gap-1">
                @if (ShowMinimize)
                {
                    <button type="button"
                            class="flex h-11 w-11 sm:h-7 sm:w-7 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1"
                            @onclick="ToggleMinimize"
                            aria-label="@L["Window.Minimize"]">
                        <Blazicon Svg="Lucide.Minus" class="h-3 w-3" />
                    </button>
                }
                @if (ShowMaximize)
                {
                    <button type="button"
                            class="flex h-11 w-11 sm:h-7 sm:w-7 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1"
                            @onclick="ToggleMaximize"
                            aria-label="@L["Window.Maximize"]">
                        <Blazicon Svg="Lucide.Square" class="h-3 w-3" />
                    </button>
                }
                @if (ShowClose)
                {
                    <button type="button"
                            class="flex h-11 w-11 sm:h-7 sm:w-7 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1"
                            @onclick="Close"
                            aria-label="@L["Window.Close"]">
                        <Blazicon Svg="Lucide.X" class="h-3 w-3" />
                    </button>
                }
            </div>
        </div>

        @* Body *@
        <div class="overflow-auto p-4 flex-1">
            @ChildContent
        </div>

        @* Footer *@
        @if (FooterContent is not null)
        {
            <div class="px-4 py-3 border-t border-border/40 bg-card rounded-b-lg">
                @FooterContent
            </div>
        }

        @* Resize handle *@
        @if (Resizable)
        {
            @* Touch hit target is 44×44 per WCAG 2.5.5; on desktop (sm and up)
               the handle compresses to a 16×16 corner so it doesn't intrude on
               the window's content area. Inner SVG stays a fixed 16×16 visual
               regardless of hit-area size so the chevron glyph doesn't scale. *@
            <div class="absolute bottom-0 right-0 flex items-end justify-end w-11 h-11 sm:w-4 sm:h-4 cursor-se-resize opacity-40 hover:opacity-80"
                 @onpointerdown="HandleResizePointerDown"
                 @onpointermove="HandlePointerMove"
                 @onpointerup="HandlePointerUp"
                 @onpointercancel="HandlePointerUp">
                <svg viewBox="0 0 10 10" class="w-4 h-4 text-muted-foreground fill-current">
                    <path d="M0 10L10 0M4 10L10 4M8 10L10 8" stroke="currentColor" stroke-width="1" fill="none"/>
                </svg>
            </div>
        }
    </div>
}
else if (Open && _minimized)
{
    @* Minimized title bar only *@
    <div id="@_windowId"
         class="fixed z-40 flex items-center gap-2 px-4 py-2 bg-card border border-border/40 rounded-lg shadow-md"
         style="@MinimizedStyleString"
         @attributes="AdditionalAttributes">
        <span class="flex-1 text-sm font-medium text-foreground truncate max-w-[200px]">@Title</span>
        <button type="button"
                class="flex h-11 w-11 sm:h-7 sm:w-7 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1"
                @onclick="ToggleMinimize"
                aria-label="@L["Window.Restore"]">
            <Blazicon Svg="Lucide.Square" class="h-3 w-3" />
        </button>
        @if (ShowClose)
        {
            <button type="button"
                    class="flex h-11 w-11 sm:h-7 sm:w-7 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1"
                    @onclick="Close"
                    aria-label="@L["Window.Close"]">
                <Blazicon Svg="Lucide.X" class="h-3 w-3" />
            </button>
        }
    </div>
}
else if (Open && _maximized)
{
    @* Maximized — fills most of viewport *@
    <div id="@_windowId"
         class="fixed inset-4 z-40 flex flex-col bg-card border border-border/40 rounded-lg shadow-xl overflow-hidden"
         role="dialog"
         aria-modal="false"
         aria-labelledby="@_titleId"
         @attributes="AdditionalAttributes">
        <div class="flex items-center gap-2 px-4 py-2 border-b border-border/40 bg-card select-none">
            <span id="@_titleId" class="flex-1 text-sm font-medium text-foreground truncate">@Title</span>
            <div class="flex items-center gap-1">
                @if (ShowMinimize)
                {
                    <button type="button"
                            class="flex h-11 w-11 sm:h-7 sm:w-7 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1"
                            @onclick="ToggleMinimize"
                            aria-label="@L["Window.Minimize"]">
                        <Blazicon Svg="Lucide.Minus" class="h-3 w-3" />
                    </button>
                }
                <button type="button"
                        class="flex h-11 w-11 sm:h-7 sm:w-7 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1"
                        @onclick="ToggleMaximize"
                        aria-label="@L["Window.Restore"]">
                    <Blazicon Svg="Lucide.Minimize2" class="h-3 w-3" />
                </button>
                @if (ShowClose)
                {
                    <button type="button"
                            class="flex h-11 w-11 sm:h-7 sm:w-7 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring focus-visible:ring-offset-1"
                            @onclick="Close"
                            aria-label="@L["Window.Close"]">
                        <Blazicon Svg="Lucide.X" class="h-3 w-3" />
                    </button>
                }
            </div>
        </div>
        <div class="overflow-auto p-4 flex-1">
            @ChildContent
        </div>
        @if (FooterContent is not null)
        {
            <div class="px-4 py-3 border-t border-border/40 bg-card">
                @FooterContent
            </div>
        }
    </div>
}

@code {
    [Parameter] public bool Open { get; set; }
    [Parameter] public EventCallback<bool> OpenChanged { get; set; }
    [Parameter] public string Title { get; set; } = "";
    [Parameter] public RenderFragment? TitleContent { get; set; }
    [Parameter] public RenderFragment? ChildContent { get; set; }
    [Parameter] public RenderFragment? FooterContent { get; set; }
    [Parameter] public string Width { get; set; } = "480px";
    [Parameter] public string Height { get; set; } = "360px";
    [Parameter] public bool Resizable { get; set; } = true;
    [Parameter] public bool Draggable { get; set; } = true;
    [Parameter] public bool ShowClose { get; set; } = true;
    [Parameter] public bool ShowMinimize { get; set; } = true;
    [Parameter] public bool ShowMaximize { get; set; } = true;
    [Parameter] public string? InitialX { get; set; }
    [Parameter] public string? InitialY { get; set; }
    [Parameter] public string? Class { get; set; }
    [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }

    private readonly string _windowId = $"window-{Guid.NewGuid():N}";
    private readonly string _titleBarId = $"window-titlebar-{Guid.NewGuid():N}";
    private readonly string _titleId = $"window-title-{Guid.NewGuid():N}";

    private bool _minimized;
    private bool _maximized;

    // Drag state (pointer-events-based; viewport dimensions fetched via JS once per drag start)
    private bool _isDragging;
    private bool _isResizing;
    private double _posX;
    private double _posY;
    private double _width;
    private double _height;
    private double _dragStartX;
    private double _dragStartY;
    private double _dragStartPosX;
    private double _dragStartPosY;
    private bool _initialized;

    // Viewport bounds captured at drag-start (and on first render) so the clamp
    // reflects the size at the moment the user grabbed the window rather than
    // a potentially stale value from a previous drag or resize event.
    private double _viewportWidth;
    private double _viewportHeight;

    protected override void OnParametersSet()
    {
        if (!_initialized && Open)
        {
            _posX = InitialX is not null ? ParsePx(InitialX) : 80;
            _posY = InitialY is not null ? ParsePx(InitialY) : 80;
            _width = ParsePx(Width);
            _height = ParsePx(Height);
            _initialized = true;
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && Open)
        {
            // Fetch viewport once on first render so a consumer-provided
            // InitialX/InitialY that exceeds the viewport (e.g. 9999px) is
            // clamped before the user ever touches the window.
            await FetchAndClampToViewportAsync();
            StateHasChanged();
        }
    }

    private async Task FetchAndClampToViewportAsync()
    {
        try
        {
            var vp = await Interop.GetViewportSize();
            // bUnit's JS layer returns null for unhandled invocations; real
            // browsers always return a populated ViewportSize. Guard so tests
            // (and any future SSR/prerender path) don't NRE.
            if (vp is null) return;
            _viewportWidth = vp.Width;
            _viewportHeight = vp.Height;
            ClampPosition();
        }
        catch (Microsoft.JSInterop.JSDisconnectedException) { }
        catch (Microsoft.JSInterop.JSException) { }
        catch (ObjectDisposedException) { }
    }

    // Keeps at least MIN_VISIBLE px of the window inside the viewport on all
    // four edges.  The title bar (≈40px) is specifically kept inside the bottom
    // edge so it never slides off and becomes unreachable.
    private const double MinVisible = 50;
    private const double TitleBarHeight = 40;

    private void ClampPosition()
    {
        if (_viewportWidth <= 0 || _viewportHeight <= 0) return;

        // Horizontal: allow the window to go almost completely off the left
        // edge (so only MIN_VISIBLE of it remains visible on the right), but
        // never let the right side go past the right viewport edge minus MIN_VISIBLE.
        _posX = Math.Clamp(_posX, MinVisible - _width, _viewportWidth - MinVisible);

        // Vertical: never let the title bar go above 0 or below the bottom
        // of the viewport (so the close/restore buttons are always reachable).
        _posY = Math.Clamp(_posY, 0, _viewportHeight - TitleBarHeight);
    }

    private static double ParsePx(string value)
    {
        if (string.IsNullOrEmpty(value)) return 0;
        if (value.EndsWith("px", StringComparison.OrdinalIgnoreCase) &&
            double.TryParse(value[..^2], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var d))
            return d;
        if (double.TryParse(value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var d2))
            return d2;
        return 0;
    }

    private string StyleString =>
        $"left:{_posX.ToString(System.Globalization.CultureInfo.InvariantCulture)}px;" +
        $"top:{_posY.ToString(System.Globalization.CultureInfo.InvariantCulture)}px;" +
        $"width:{_width.ToString(System.Globalization.CultureInfo.InvariantCulture)}px;" +
        $"height:{_height.ToString(System.Globalization.CultureInfo.InvariantCulture)}px;";

    private string MinimizedStyleString =>
        $"left:{_posX.ToString(System.Globalization.CultureInfo.InvariantCulture)}px;" +
        $"bottom:16px;";

    private string CssClass =>
        $"fixed z-40 flex flex-col bg-card border border-border/40 rounded-lg shadow-xl overflow-hidden {Class}".Trim();

    private async Task HandleTitleBarPointerDown(PointerEventArgs e)
    {
        if (!Draggable) return;
        _isDragging = true;
        _isResizing = false;
        _dragStartX = e.ClientX;
        _dragStartY = e.ClientY;
        _dragStartPosX = _posX;
        _dragStartPosY = _posY;
        // Fetch current viewport size at drag-start so the clamp in HandlePointerMove
        // always reflects the live viewport (handles viewport resize between drags).
        await FetchAndClampToViewportAsync();
    }

    private void HandleResizePointerDown(PointerEventArgs e)
    {
        if (!Resizable) return;
        _isResizing = true;
        _isDragging = false;
        _dragStartX = e.ClientX;
        _dragStartY = e.ClientY;
        _dragStartPosX = _width;
        _dragStartPosY = _height;
    }

    private void HandlePointerMove(PointerEventArgs e)
    {
        if (_isDragging)
        {
            var dx = e.ClientX - _dragStartX;
            var dy = e.ClientY - _dragStartY;

            // rc.44 hotfix: clamp ONLY when viewport dimensions are known.
            // Without this guard, `Math.Clamp(value, 0, -40)` throws when the
            // viewport fetch hasn't completed yet (or returned null in tests/SSR).
            // Mirror the ClampPosition() guard so drag-during-init doesn't crash.
            //
            // Clamp meaning when valid:
            //   X: allow almost-fully-left (right edge stays visible) but not past right edge.
            //   Y: never above 0 (title bar stays in viewport top) and never below
            //      (viewport height − title bar height) so the controls are always reachable.
            if (_viewportWidth > 0 && _viewportHeight > TitleBarHeight)
            {
                _posX = Math.Clamp(_dragStartPosX + dx, MinVisible - _width, _viewportWidth - MinVisible);
                _posY = Math.Clamp(_dragStartPosY + dy, 0, _viewportHeight - TitleBarHeight);
            }
            else
            {
                // Pre-viewport-fetch fallback: lower-bound only (matches pre-rc.44 behavior).
                _posX = Math.Max(0, _dragStartPosX + dx);
                _posY = Math.Max(0, _dragStartPosY + dy);
            }
            StateHasChanged();
        }
        else if (_isResizing)
        {
            var dx = e.ClientX - _dragStartX;
            var dy = e.ClientY - _dragStartY;
            _width = Math.Max(200, _dragStartPosX + dx);
            _height = Math.Max(120, _dragStartPosY + dy);
            StateHasChanged();
        }
    }

    private void HandlePointerUp(PointerEventArgs e)
    {
        _isDragging = false;
        _isResizing = false;
    }

    private void ToggleMinimize()
    {
        _minimized = !_minimized;
        if (_minimized) _maximized = false;
    }

    private void ToggleMaximize()
    {
        _maximized = !_maximized;
        if (_maximized) _minimized = false;
    }

    private async Task Close()
    {
        Open = false;
        await OpenChanged.InvokeAsync(false);
    }

    public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}
