<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/components/AppOfflineOverlay.vue
| Data: 2026
| Local: São Carlos/SP — Brasil
|--------------------------------------------------------------------------
| © 2026 — Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';

// ── Estado ────────────────────────────────────────────────────
// Começa otimista (true) — só marca offline com confirmação dupla.
const isOnline = ref(true);
const wasOffline = ref(false);
const showReconnected = ref(false);

let pollTimer = null;
let reconnectedTimer = null;
let consecutiveFailures = 0;

// Em DEV, ignora completamente o polling: Vite HMR + dev server podem
// disparar falhas pontuais que geram falso positivo constante. Em DEV,
// só confia em navigator.onLine + eventos nativos (mais conservador).
const IS_DEV = import.meta.env?.DEV === true;

// Tolerância: precisa N falhas seguidas pra considerar offline. Evita
// falso positivo de slow request / HMR rebuild / network blip.
const FAILURE_THRESHOLD = 2;
const POLL_INTERVAL = IS_DEV ? 60_000 : 30_000;
const FETCH_TIMEOUT = 8_000;

// ── Detecção: navigator.onLine primeiro, fetch como confirmação ──
//
// navigator.onLine é a fonte autoritativa do browser. Se for true,
// quase certo que tem rede física. Se for false, com certeza offline.
// O fetch só serve pra detectar "rede funciona mas servidor parado".
async function checkConnectivity() {
    // 1) Browser offline = confia direto, sem fetch
    if (typeof navigator !== 'undefined' && navigator.onLine === false) {
        consecutiveFailures = FAILURE_THRESHOLD;
        setOffline();
        return;
    }

    // 2) Browser online — confirma com HEAD no favicon (rápido, cacheável)
    try {
        await fetch('/favicon.ico?_t=' + Date.now(), {
            method: 'HEAD',
            cache: 'no-store',
            signal: AbortSignal.timeout(FETCH_TIMEOUT)
        });
        consecutiveFailures = 0;
        setOnline();
    } catch {
        consecutiveFailures++;
        // Só marca offline após N falhas consecutivas — evita falso positivo
        // de slow request, HMR rebuild, transient blip.
        if (consecutiveFailures >= FAILURE_THRESHOLD) {
            setOffline();
        }
    }
}

function setOnline() {
    if (!isOnline.value && wasOffline.value) {
        showReconnected.value = true;
        if (reconnectedTimer) clearTimeout(reconnectedTimer);
        reconnectedTimer = setTimeout(() => {
            showReconnected.value = false;
        }, 4000);
    }
    isOnline.value = true;
}

function setOffline() {
    if (isOnline.value) wasOffline.value = true;
    showReconnected.value = false;
    if (reconnectedTimer) clearTimeout(reconnectedTimer);
    isOnline.value = false;
}

// ── Eventos nativos do browser ────────────────────────────────
// navigator.onLine + offline/online events são SUPER confiáveis pra
// estado real (sem rede física, wifi caiu, etc). Outros falsos
// positivos vinham só do fetch agressivo.
function onBrowserOffline() {
    consecutiveFailures = FAILURE_THRESHOLD;
    setOffline();
}
function onBrowserOnline() {
    consecutiveFailures = 0;
    checkConnectivity();
}

onMounted(() => {
    window.addEventListener('offline', onBrowserOffline);
    window.addEventListener('online', onBrowserOnline);

    // Polling defensivo — captura quedas que não disparam evento
    // (raras, ex: DNS travado em wifi público).
    pollTimer = setInterval(checkConnectivity, POLL_INTERVAL);

    // Verifica estado atual ao montar (útil se já começou offline)
    checkConnectivity();
});

onBeforeUnmount(() => {
    window.removeEventListener('offline', onBrowserOffline);
    window.removeEventListener('online', onBrowserOnline);
    clearInterval(pollTimer);
    clearTimeout(reconnectedTimer);
});
</script>

<template>
    <Teleport to="body">
        <!-- ── Overlay: sem internet ───────────────────────────── -->
        <Transition name="offline-fade">
            <div v-if="!isOnline" class="offline-overlay" role="alertdialog" aria-live="assertive" aria-label="Sem conexão com a internet">
                <div class="offline-backdrop" />

                <div class="offline-card">
                    <div class="offline-icon-wrap">
                        <span class="offline-icon-ring" />
                        <i class="pi pi-wifi offline-icon" />
                    </div>

                    <h2 class="offline-title">Sem conexão</h2>
                    <p class="offline-desc">Verifique sua internet.<br />Tentando reconectar automaticamente…</p>

                    <div class="offline-pulse-bar">
                        <span class="offline-pulse-dot" />
                        <span class="offline-pulse-dot" style="animation-delay: 0.2s" />
                        <span class="offline-pulse-dot" style="animation-delay: 0.4s" />
                    </div>
                </div>
            </div>
        </Transition>

        <!-- ── Toast: reconectou ──────────────────────────────── -->
        <Transition name="reconnect-toast">
            <div v-if="showReconnected" class="reconnect-toast" role="status">
                <i class="pi pi-check-circle" />
                <span>Conexão restabelecida</span>
            </div>
        </Transition>
    </Teleport>
</template>

<style scoped>
/* ── Overlay ─────────────────────────────────────────────── */
.offline-overlay {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}

.offline-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}

/* ── Card ────────────────────────────────────────────────── */
.offline-card {
    position: relative;
    z-index: 1;
    background: var(--surface-card, #fff);
    border: 1px solid var(--surface-border, #e0e0e0);
    border-radius: 20px;
    padding: 40px 48px;
    max-width: 380px;
    width: 100%;
    text-align: center;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.25);
}

/* ── Ícone ───────────────────────────────────────────────── */
.offline-icon-wrap {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 80px;
    height: 80px;
    margin-bottom: 24px;
}

.offline-icon-ring {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 2px solid var(--primary-color, #6366f1);
    opacity: 0.25;
    animation: ring-pulse 2s ease-in-out infinite;
}

.offline-icon {
    font-size: 2.2rem;
    color: var(--primary-color, #6366f1);
    opacity: 0.85;
    position: relative;
}

@keyframes ring-pulse {
    0%,
    100% {
        transform: scale(1);
        opacity: 0.25;
    }
    50% {
        transform: scale(1.18);
        opacity: 0.1;
    }
}

/* ── Texto ───────────────────────────────────────────────── */
.offline-title {
    font-size: 1.3rem;
    font-weight: 700;
    color: var(--text-color, #1a1a2e);
    margin: 0 0 10px;
}

.offline-desc {
    font-size: 0.88rem;
    color: var(--text-color-secondary, #666);
    margin: 0 0 28px;
    line-height: 1.6;
}

/* ── Dots de pulso ───────────────────────────────────────── */
.offline-pulse-bar {
    display: flex;
    justify-content: center;
    gap: 7px;
}

.offline-pulse-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--primary-color, #6366f1);
    opacity: 0.7;
    animation: dot-bounce 1.2s ease-in-out infinite;
}

@keyframes dot-bounce {
    0%,
    80%,
    100% {
        transform: scale(0.7);
        opacity: 0.4;
    }
    40% {
        transform: scale(1);
        opacity: 1;
    }
}

/* ── Transição do overlay ────────────────────────────────── */
.offline-fade-enter-active {
    transition: opacity 0.3s ease;
}
.offline-fade-leave-active {
    transition: opacity 0.4s ease;
}
.offline-fade-enter-from,
.offline-fade-leave-to {
    opacity: 0;
}

/* ── Toast de reconexão ──────────────────────────────────── */
.reconnect-toast {
    position: fixed;
    bottom: 28px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 9999;
    display: flex;
    align-items: center;
    gap: 8px;
    background: #16a34a;
    color: #fff;
    font-size: 0.875rem;
    font-weight: 600;
    padding: 10px 20px;
    border-radius: 999px;
    box-shadow: 0 4px 20px rgba(22, 163, 74, 0.4);
    white-space: nowrap;
}

.reconnect-toast .pi {
    font-size: 1rem;
}

.reconnect-toast-enter-active {
    transition:
        opacity 0.3s ease,
        transform 0.3s ease;
}
.reconnect-toast-leave-active {
    transition:
        opacity 0.4s ease,
        transform 0.4s ease;
}
.reconnect-toast-enter-from {
    opacity: 0;
    transform: translateX(-50%) translateY(12px);
}
.reconnect-toast-leave-to {
    opacity: 0;
    transform: translateX(-50%) translateY(12px);
}
</style>
