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

import AppMenuItem from './AppMenuItem.vue';
import AppMenuFooterPanel from './AppMenuFooterPanel.vue';
import ComponentCadastroRapido from '@/components/ComponentCadastroRapido.vue';

import { useMenuStore } from '@/stores/menuStore';

const route = useRoute();
const router = useRouter();
const { layoutState } = useLayout();

const menuStore = useMenuStore();

// ======================================================
// ✅ Blindagem anti-"menu some"
// - se o menuStore.model piscar como [], mantém o último menu válido
// - evita sumiço ao entrar em /admin/clinic/features (reset momentâneo)
// ======================================================

// raw (pode piscar vazio)
const rawModel = computed(() => menuStore.model || []);

// último menu válido
const lastGoodModel = ref([]);

// debounce curto para aceitar "vazio real" (ex.: logout) sem travar UI
let acceptEmptyT = null;

function setLastGoodIfValid(m) {
    if (Array.isArray(m) && m.length) {
        lastGoodModel.value = m;
    }
}

watch(
    rawModel,
    (m) => {
        // se veio com itens, atualiza na hora
        if (Array.isArray(m) && m.length) {
            if (acceptEmptyT) clearTimeout(acceptEmptyT);
            setLastGoodIfValid(m);
            return;
        }

        // se veio vazio, NÃO derruba o menu imediatamente.
        // Só aceita vazio se continuar vazio por um tempinho.
        if (acceptEmptyT) clearTimeout(acceptEmptyT);
        acceptEmptyT = setTimeout(() => {
            // se ainda estiver vazio, e você quiser realmente limpar, faça aqui.
            // Por padrão, manteremos o último menu válido para evitar UX quebrada.
            // Se quiser limpar em logout, o logout deve limpar lastGoodModel explicitamente.
        }, 250);
    },
    { immediate: true, deep: false }
);

// model final exibido (com fallback)
const model = computed(() => {
    const m = rawModel.value;
    if (Array.isArray(m) && m.length) return m;
    if (Array.isArray(lastGoodModel.value) && lastGoodModel.value.length) return lastGoodModel.value;
    return [];
});

// ✅ rota -> activePath (NÃO fecha menu)
watch(
    () => route.path,
    (p) => {
        layoutState.activePath = p;
    },
    { immediate: true }
);

// ======================================================
// 🔎 Busca no menu (mantive igual)
// ======================================================
const query = ref('');
const showResults = ref(false);
const activeIndex = ref(-1);
const forcedOpen = ref(false);

const searchEl = ref(null);
const searchWrapEl = ref(null);

const RECENT_KEY = 'menu_search_recent';
const recent = ref([]);

function loadRecent() {
    try {
        recent.value = JSON.parse(localStorage.getItem(RECENT_KEY) || '[]');
    } catch {
        recent.value = [];
    }
}
function saveRecent(q) {
    const v = String(q || '').trim();
    if (!v) return;
    const list = [v, ...recent.value.filter((x) => x !== v)].slice(0, 8);
    recent.value = list;
    localStorage.setItem(RECENT_KEY, JSON.stringify(list));
}
function clearRecent() {
    recent.value = [];
    try {
        localStorage.removeItem(RECENT_KEY);
    } catch {}
}
loadRecent();

watch(query, (v) => {
    const hasText = !!v?.trim();
    if (hasText) {
        forcedOpen.value = false;
        showResults.value = true;
        return;
    }
    showResults.value = forcedOpen.value;
});

function clearSearch() {
    query.value = '';
    activeIndex.value = -1;
    showResults.value = false;
    forcedOpen.value = false;
}

function norm(s) {
    return String(s || '')
        .toLowerCase()
        .normalize('NFD')
        .replace(/\p{Diacritic}/gu, '')
        .trim();
}

function isVisibleItem(it) {
    const v = it?.visible;
    if (typeof v === 'function') return !!v();
    if (v === undefined || v === null) return true;
    return v !== false;
}

function flattenMenu(items, trail = []) {
    const out = [];
    for (const it of items || []) {
        if (!isVisibleItem(it)) continue;

        const nextTrail = [...trail, it?.label].filter(Boolean);

        if (it?.to && !it?.items?.length) {
            out.push({
                label: it.label || it.to,
                to: it.to,
                icon: it.icon,
                trail: nextTrail,

                // ✅ usa cálculo dinâmico vindo do navigation (quando existir)
                proBadge: !!(it.__showProBadge ?? it.proBadge),

                feature: it.feature || null
            });
        }

        if (it?.items?.length) {
            out.push(...flattenMenu(it.items, nextTrail));
        }
    }
    return out;
}

const allLinks = computed(() => flattenMenu(model.value));

const results = computed(() => {
    const q = norm(query.value);
    if (!q) return [];

    const wantPro = q === 'pro' || q.startsWith('pro ') || q.includes(' pro');

    return allLinks.value
        .filter((r) => {
            const hay = `${norm(r.label)} ${norm(r.trail.join(' > '))} ${norm(r.to)}`;
            if (hay.includes(q)) return true;
            if (wantPro && (r.proBadge || r.feature)) return true;
            return false;
        })
        .slice(0, 12);
});

watch(results, (list) => {
    activeIndex.value = list.length ? 0 : -1;
});

function escapeHtml(s) {
    return String(s || '')
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
}

function highlight(text, q) {
    const queryNorm = norm(q);
    const raw = String(text || '');
    if (!queryNorm) return escapeHtml(raw);

    const rawNorm = norm(raw);
    const idx = rawNorm.indexOf(queryNorm);
    if (idx < 0) return escapeHtml(raw);

    const before = escapeHtml(raw.slice(0, idx));
    const mid = escapeHtml(raw.slice(idx, idx + queryNorm.length));
    const after = escapeHtml(raw.slice(idx + queryNorm.length));
    return `${before}<mark class="px-1 rounded bg-yellow-200/40">${mid}</mark>${after}`;
}

function onSearchKeydown(e) {
    if (e.key === 'Escape') {
        showResults.value = false;
        forcedOpen.value = false;
        return;
    }

    if (e.key === 'ArrowDown') {
        e.preventDefault();
        if (!results.value.length) return;
        showResults.value = true;
        activeIndex.value = (activeIndex.value + 1) % results.value.length;
        return;
    }

    if (e.key === 'ArrowUp') {
        e.preventDefault();
        if (!results.value.length) return;
        showResults.value = true;
        activeIndex.value = (activeIndex.value - 1 + results.value.length) % results.value.length;
        return;
    }

    if (e.key === 'Enter') {
        if (showResults.value && results.value.length && activeIndex.value >= 0) {
            e.preventDefault();
            goTo(results.value[activeIndex.value]);
        }
    }
}

function isTypingTarget(el) {
    if (!el) return false;
    const tag = (el.tagName || '').toLowerCase();
    return tag === 'input' || tag === 'textarea' || el.isContentEditable;
}

function focusSearch() {
    forcedOpen.value = true;
    showResults.value = true;

    requestAnimationFrame(() => {
        requestAnimationFrame(() => {
            const inst = searchEl.value;
            const input = inst?.$el?.tagName === 'INPUT' ? inst.$el : inst?.$el?.querySelector?.('input');

            input?.focus?.();
        });
    });
}

function onGlobalKeydown(e) {
    if (isTypingTarget(document.activeElement)) return;

    const isK = e.key?.toLowerCase() === 'k';
    const withCmdOrCtrl = e.ctrlKey || e.metaKey;

    if (withCmdOrCtrl && isK) {
        e.preventDefault();
        e.stopPropagation();
        focusSearch();
    }
}

function applyRecent(q) {
    query.value = q;
    forcedOpen.value = true;
    showResults.value = true;
    activeIndex.value = 0;
    nextTick(() => focusSearch());
}

function onDocMouseDown(e) {
    if (!showResults.value) return;
    const root = searchWrapEl.value;
    if (!root) return;
    if (!root.contains(e.target)) {
        showResults.value = false;
        forcedOpen.value = false;
    }
}

onMounted(() => {
    window.addEventListener('keydown', onGlobalKeydown, true);
    document.addEventListener('mousedown', onDocMouseDown);
});
onBeforeUnmount(() => {
    if (acceptEmptyT) clearTimeout(acceptEmptyT);
    window.removeEventListener('keydown', onGlobalKeydown, true);
    document.removeEventListener('mousedown', onDocMouseDown);
});

async function goTo(r) {
    saveRecent(query.value);

    query.value = '';
    showResults.value = false;
    activeIndex.value = -1;
    forcedOpen.value = false;

    await router.push(r.to);
}

// ==============================
// Quick create
// ==============================
const quickDialog = ref(false);
function onQuickCreate() {
    quickDialog.value = true;
}
function onQuickCreated() {
    quickDialog.value = false;
}

function onSearchFocus() {
    if (!query.value?.trim()) {
        forcedOpen.value = true;
        showResults.value = true;
    }
}
</script>

<template>
    <div class="flex flex-col h-full">
        <!-- 🔎 TOPO FIXO (56px, alinhado com topbar) -->
        <div ref="searchWrapEl" class="sticky top-0 z-20 bg-[var(--surface-card)] border-b border-[var(--surface-border)]">
            <div class="relative h-14 px-3 flex items-center">
                <div aria-hidden="true" style="position: absolute; left: -9999px; top: -9999px; width: 1px; height: 1px; overflow: hidden">
                    <input type="email" name="email" autocomplete="username" tabindex="-1" disabled />
                    <input type="password" name="password" autocomplete="current-password" tabindex="-1" disabled />
                </div>

                <FloatLabel variant="on" class="w-full">
                    <IconField class="w-full">
                        <InputIcon class="pi pi-search" />
                        <!-- type="text" (não "search"): o nativo "search" injeta
                             um X de "clear" do navegador que duplicava o botão
                             custom logo abaixo. `inputmode="search"` mantém o
                             teclado correto em mobile sem trazer o X nativo. -->
                        <InputText
                            ref="searchEl"
                            id="menu_search"
                            name="menu_search"
                            type="text"
                            inputmode="search"
                            autocomplete="off"
                            autocapitalize="off"
                            autocorrect="off"
                            spellcheck="false"
                            data-lpignore="true"
                            data-1p-ignore="true"
                            v-model="query"
                            class="w-full pr-10"
                            variant="filled"
                            @focus="onSearchFocus"
                            @keydown="onSearchKeydown"
                        />
                    </IconField>
                    <label for="menu_search">Encontrar menu...</label>
                </FloatLabel>

                <button v-if="query.trim()" type="button" class="absolute right-5 top-1/2 -translate-y-1/2 opacity-70 hover:opacity-100" @mousedown.prevent="clearSearch" aria-label="Limpar busca">
                    <i class="pi pi-times" />
                </button>
            </div>

            <div v-if="showResults && !query.trim() && recent.length" class="mx-3 mb-2 border border-[var(--surface-border)] rounded-xl bg-[var(--surface-card)] shadow-sm overflow-hidden">
                <div class="px-3 py-2 text-xs opacity-70 flex items-center justify-content-between">
                    <span>Recentes</span>
                    <button type="button" class="opacity-70 hover:opacity-100" @mousedown.prevent="clearRecent" aria-label="Limpar recentes">
                        <i class="pi pi-trash" />
                    </button>
                </div>

                <button v-for="q in recent" :key="q" class="w-full text-left px-3 py-2 hover:bg-[var(--surface-hover)] flex items-center gap-2" type="button" @click.stop.prevent="applyRecent(q)">
                    <i class="pi pi-history opacity-70" />
                    <div class="flex-1">{{ q }}</div>
                </button>
            </div>

            <div v-else-if="showResults && results.length" class="mx-3 mb-2 border border-[var(--surface-border)] rounded-xl bg-[var(--surface-card)] shadow-sm overflow-hidden">
                <button
                    v-for="(r, i) in results"
                    :key="String(r.to)"
                    type="button"
                    @mousedown.prevent="goTo(r)"
                    :class="['w-full text-left px-3 py-2 flex items-center gap-2', i === activeIndex ? 'bg-[var(--surface-hover)]' : 'hover:bg-[var(--surface-hover)]']"
                >
                    <i v-if="r.icon" :class="r.icon" class="opacity-80" />
                    <div class="flex flex-col flex-1">
                        <div class="font-medium leading-tight" v-html="highlight(r.label, query)" />
                        <small class="opacity-70">{{ r.trail.join(' > ') }}</small>
                    </div>

                    <span v-if="r.proBadge" class="text-xs px-2 py-0.5 rounded border border-[var(--surface-border)] opacity-80"> PRO </span>
                </button>
            </div>

            <div v-else-if="showResults && query && !results.length" class="mx-3 mb-2 px-3 py-2 text-sm opacity-70">Nenhum item encontrado.</div>
        </div>

        <div class="flex-1 overflow-y-auto">
            <ul class="layout-menu pb-20">
                <template v-for="(item, i) in model" :key="i">
                    <AppMenuItem :item="item" :index="i" :root="true" @quick-create="onQuickCreate" />
                </template>
            </ul>
        </div>

        <AppMenuFooterPanel />

        <ComponentCadastroRapido v-model="quickDialog" title="Cadastro Rápido" table-name="patients" name-field="nome_completo" email-field="email_principal" phone-field="telefone" :extra-payload="{ status: 'Ativo' }" @created="onQuickCreated" />
    </div>
</template>
