@php($runs = $runs ?? collect())
@php($overflowCount = (int) ($overflowCount ?? 0))
@php($tenant = $tenant ?? null)

{{-- Widget must always be mounted, even when empty, so it can receive Livewire events --}}
<div 
    x-data="opsUxProgressWidgetPoller()" 
    x-init="init()" 
    wire:key="ops-ux-progress-widget"
    @if($runs->isNotEmpty()) wire:poll.5s="refreshRuns" @endif
>
    @if($runs->isNotEmpty())
        <div class="fixed bottom-4 right-4 z-[999999] w-96 space-y-2" style="pointer-events: auto;">
            @foreach ($runs->take(5) as $run)
                <div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl border-2 border-primary-500 dark:border-primary-400 p-4 transition-all animate-in slide-in-from-right duration-300"
                    wire:key="run-{{ $run->id }}">
                    <div class="flex items-start justify-between gap-4">
                        <div class="min-w-0">
                            <h4 class="text-sm font-semibold text-gray-900 dark:text-gray-100">
                                {{ \App\Support\OperationCatalog::label((string) $run->type) }}
                            </h4>
                            <p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
                                @if($run->status === 'queued')
                                    Queued • {{ ($run->started_at ?? $run->created_at)?->diffForHumans(null, true, true) }}
                                @else
                                    Running • {{ ($run->started_at ?? $run->created_at)?->diffForHumans(null, true, true) }}
                                @endif
                            </p>
                        </div>

                        @if ($tenant)
                            <a
                                href="{{ \App\Support\OpsUx\OperationRunUrl::view($run, $tenant) }}"
                                class="shrink-0 text-xs font-medium text-primary-700 hover:text-primary-800 dark:text-primary-300 dark:hover:text-primary-200"
                            >
                                View run
                            </a>
                        @endif
                    </div>
                </div>
            @endforeach

            @if($overflowCount > 0 && $tenant)
                <a
                    href="{{ \App\Support\OpsUx\OperationRunUrl::index($tenant) }}"
                    class="block rounded-lg bg-white/90 dark:bg-gray-800/90 px-4 py-2 text-center text-xs font-medium text-gray-700 hover:text-gray-900 dark:text-gray-200 dark:hover:text-white shadow"
                >
                    +{{ $overflowCount }} more
                </a>
            @endif
        </div>
    @endif
</div>

<script>
    window.opsUxProgressWidgetPoller ??= function opsUxProgressWidgetPoller() {
        return {
            timer: null,
            activeSinceMs: null,
            fastUntilMs: null,
            teardownObserver: null,

            init() {
                this.onVisibilityChange = this.onVisibilityChange.bind(this);
                window.addEventListener('visibilitychange', this.onVisibilityChange);

                this.onNavigated = this.onNavigated.bind(this);
                window.addEventListener('livewire:navigated', this.onNavigated);

                // Ensure we always detach listeners when Livewire/Filament removes this
                // element (for example during modal unmounts or navigation/morph).
                this.teardownObserver = new MutationObserver(() => {
                    if (!this.$el || this.$el.isConnected !== true) {
                        this.destroy();
                    }
                });

                this.teardownObserver.observe(document.body, { childList: true, subtree: true });

                // First sync immediately.
                this.schedule(0);
            },

            destroy() {
                this.stop();
                window.removeEventListener('visibilitychange', this.onVisibilityChange);
                window.removeEventListener('livewire:navigated', this.onNavigated);

                if (this.teardownObserver) {
                    this.teardownObserver.disconnect();
                    this.teardownObserver = null;
                }
            },

            stop() {
                if (this.timer) {
                    clearTimeout(this.timer);
                    this.timer = null;
                }
            },

            isModalOpen() {
                return document.querySelector('[role="dialog"][aria-modal="true"]') !== null;
            },

            isPaused() {
                if (document.hidden === true) {
                    return true;
                }

                if (this.isModalOpen()) {
                    return true;
                }

                if (!this.$el || this.$el.isConnected !== true) {
                    return true;
                }

                return false;
            },

            onVisibilityChange() {
                if (!this.isPaused()) {
                    this.schedule(0);
                }
            },

            onNavigated() {
                if (!this.isPaused()) {
                    this.schedule(0);
                }
            },

            activeAgeSeconds() {
                if (this.activeSinceMs === null) {
                    return 0;
                }

                return Math.floor((Date.now() - this.activeSinceMs) / 1000);
            },

            nextIntervalMs() {
                // Stop polling entirely if server says this component is disabled.
                if (this.$wire?.disabled === true) {
                    return null;
                }

                // Discovery polling.
                if (this.$wire?.hasActiveRuns !== true) {
                    this.activeSinceMs = null;
                    return 30_000;
                }

                // Active polling backoff.
                if (this.activeSinceMs === null) {
                    this.activeSinceMs = Date.now();
                }

                const now = Date.now();
                if (this.fastUntilMs && now < this.fastUntilMs) {
                    return 1_000;
                }

                const age = this.activeAgeSeconds();
                if (age < 10) {
                    return 1_000;
                }

                if (age < 60) {
                    return 5_000;
                }

                return 10_000;
            },

            async tick() {
                if (this.isPaused()) {
                    // Keep it calm: check again later.
                    this.schedule(2_000);
                    return;
                }

                try {
                    await this.$wire.refreshRuns();
                } catch (error) {
                    // Livewire will reject pending action promises when requests are
                    // cancelled (for example during `wire:navigate`). That's expected
                    // for this background poller, so we swallow cancellation rejections.
                    const isCancellation = Boolean(
                        error &&
                            typeof error === 'object' &&
                            error.status === null &&
                            error.body === null &&
                            error.json === null &&
                            error.errors === null,
                    );

                    if (!isCancellation) {
                        console.warn('Ops UX widget refreshRuns failed', error);
                    }
                }

                const next = this.nextIntervalMs();
                if (next === null) {
                    this.stop();
                    return;
                }

                this.schedule(next);
            },

            schedule(delayMs) {
                this.stop();

                const delay = Math.max(0, Number(delayMs ?? 0));

                this.timer = setTimeout(() => {
                    this.tick().catch(() => {});
                }, delay);
            },
        };
    };
</script>
