<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/patients/PatientsListPage.vue
| Data: 2026
| Local: São Carlos/SP — Brasil
|--------------------------------------------------------------------------
| © 2026 — Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { supabase } from '@/lib/supabase/client';
import { tenantDb } from '@/lib/supabase/tenantClient';
import {
    listGroupsByPatient,
    listTagsByPatient,
    listGroups as repoListGroups,
    listTags as repoListTags,
    getSessionCounts
} from '@/features/patients/services/patientsRepository';
import Menu from 'primevue/menu';
import MultiSelect from 'primevue/multiselect';
import Popover from 'primevue/popover';
import ProgressSpinner from 'primevue/progressspinner';
import { useConfirm } from 'primevue/useconfirm';
import { useToast } from 'primevue/usetoast';
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';

import ComponentCadastroRapido from '@/components/ComponentCadastroRapido.vue';
import PatientActionMenu from '@/components/patients/PatientActionMenu.vue';
import { usePatientLifecycle } from '@/composables/usePatientLifecycle';
import PatientCadastroDialog from '@/components/ui/PatientCadastroDialog.vue';
import PatientCreatePopover from '@/components/ui/PatientCreatePopover.vue';
import PatientProntuario from '@/features/patients/prontuario/PatientProntuario.vue';
import { useConversationDrawerStore } from '@/stores/conversationDrawerStore';
import { useTenantStore } from '@/stores/tenantStore';
import { logError } from '@/support/supportLogger';
import { getSysGroupColor, getSystemGroupDefaultColor } from '@/utils/systemGroupColors.js';

const tenantStore = useTenantStore();

// ── Descontos por paciente ────────────────────────────────────────
const discountMap = ref({});

// ── Convênio por paciente ─────────────────────────────────────────
const insuranceMap = ref({});

// ── Sessões do paciente ──────────────────────────────────────────
const sessoesOpen = ref(false);
const sessoesPaciente = ref(null);
const sessoesLoading = ref(false);
const sessoesLista = ref([]);
const recorrencias = ref([]);

const MESES_BR = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'];
function fmtDataSessao(iso) {
    if (!iso) return '—';
    const d = new Date(iso);
    return `${String(d.getDate()).padStart(2, '0')} ${MESES_BR[d.getMonth()]} ${d.getFullYear()} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
}
function statusSessaoSev(st) {
    return { agendado: 'info', realizado: 'success', cancelado: 'danger', faltou: 'warn' }[st] || 'secondary';
}

async function abrirSessoes(pat) {
    sessoesPaciente.value = pat;
    sessoesOpen.value = true;
    sessoesLoading.value = true;
    sessoesLista.value = [];
    recorrencias.value = [];
    try {
        const [evts, recs] = await Promise.all([
            tenantDb().from('agenda_eventos')
                .select('id, titulo, tipo, status, inicio_em, fim_em, modalidade, insurance_guide_number, insurance_value, insurance_plans(name)')
                .eq('patient_id', pat.id)
                .order('inicio_em', { ascending: false })
                .limit(100),
            tenantDb().from('recurrence_rules').select('id, type, interval, weekdays, start_date, end_date, start_time, duration_min, status').eq('patient_id', pat.id).order('start_date', { ascending: false })
        ]);
        sessoesLista.value = evts.data || [];
        recorrencias.value = recs.data || [];
    } catch (e) {
        logError('patients', 'abrirSessoes falhou', e);
    } finally {
        sessoesLoading.value = false;
    }
}

const DIAS_SEMANA = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'];
function fmtRecorrencia(r) {
    const dias = (r.weekdays || []).map((d) => DIAS_SEMANA[d]).join(', ');
    const freq = r.type === 'weekly' && r.interval === 2 ? 'Quinzenal' : r.type === 'weekly' ? 'Semanal' : 'Personalizado';
    return `${freq} · ${dias} · ${r.start_time?.slice(0, 5) || '—'} · ${r.duration_min || 50}min`;
}

const router = useRouter();
const conversationDrawerStore = useConversationDrawerStore();
const route = useRoute();
const toast = useToast();
const confirm = useConfirm();
const { reactivatePatient } = usePatientLifecycle();

// Restaurar paciente arquivado — atalho visível direto na linha (em vez
// de exigir abrir o menu de "..."). Compartilha reactivatePatient com
// PatientActionMenu pra manter fonte única de verdade.
function restaurarPaciente(p) {
    if (!p?.id) return;
    confirm.require({
        message: `Restaurar "${p.nome_completo}" pra lista de pacientes ativos?`,
        header: 'Restaurar paciente',
        icon: 'pi pi-undo',
        acceptLabel: 'Restaurar',
        rejectLabel: 'Cancelar',
        accept: async () => {
            try {
                const result = await reactivatePatient(p.id);
                if (!result?.ok) throw result?.error || new Error('Falha ao restaurar.');
                toast.add({ severity: 'success', summary: 'Restaurado', detail: p.nome_completo, life: 2200 });
                await fetchAll();
            } catch (e) {
                toast.add({
                    severity: 'error',
                    summary: 'Falha ao restaurar',
                    detail: e?.message || 'Tente novamente.',
                    life: 4000
                });
            }
        }
    });
}

// ── Hero sticky ───────────────────────────────────────────
const headerEl = ref(null);
const headerSentinelRef = ref(null);
const headerStuck = ref(false);
let _observer = null;

// ── Mobile menu ───────────────────────────────────────────
const patMobileMenuRef = ref(null);

const patMobileMenuItems = [
    { label: 'Cadastro Rápido', icon: 'pi pi-bolt', command: () => openQuickCreate() },
    { label: 'Cadastro Completo', icon: 'pi pi-file-edit', command: () => goCreateFull() },
    { label: 'Link de Cadastro', icon: 'pi pi-link', command: () => router.push('/therapist/patients/link-externo') },
    { separator: true },
    { label: 'Descontos por Paciente', icon: 'pi pi-percentage', command: () => router.push('/configuracoes/descontos') },
    { separator: true },
    { label: 'Atualizar', icon: 'pi pi-refresh', command: () => fetchAll() }
];

const uid = ref(null);

const loading = ref(false);
const hasLoaded = ref(false);
const showAdvanced = ref(false);

const quickDialog = ref(false);
const cadastroFullDialog = ref(false);
const editPatientId = ref(null);
const dialogSaved = ref(false);
const createPopoverRef = ref(null);

const prontuarioOpen = ref(false);
const selectedPatient = ref(null);

const patients = ref([]);
const groups = ref([]);
const tags = ref([]);

// ── Colunas ───────────────────────────────────────────────
const colPopover = ref(null);
const columnCatalogAll = [
    { key: 'paciente', label: 'Paciente', locked: true },
    { key: 'status', label: 'Status', locked: true },
    { key: 'telefone', label: 'Telefone', locked: true },
    { key: 'email', label: 'Email' },
    { key: 'last_attended_at', label: 'Último Atendimento' },
    { key: 'created_at', label: 'Cadastrado em' },
    { key: 'grupos', label: 'Grupos' },
    { key: 'tags', label: 'Tags' },
    { key: 'acoes', label: 'Ações', locked: true }
];

const selectedColumns = ref(['paciente', 'status', 'telefone', 'acoes']);

const lockedKeys = computed(() => columnCatalogAll.filter((c) => c.locked).map((c) => c.key));

const visibleKeys = computed(() => {
    const set = new Set(selectedColumns.value || []);
    lockedKeys.value.forEach((k) => set.add(k));
    return Array.from(set);
});

function isColVisible(key) {
    return visibleKeys.value.includes(key);
}
function toggleColumnsPopover(event) {
    colPopover.value?.toggle(event);
}
function setDefaultColumns() {
    selectedColumns.value = ['paciente', 'status', 'telefone', 'acoes'];
}
function setAllColumns() {
    selectedColumns.value = columnCatalogAll.map((c) => c.key);
}

const sort = reactive({ field: 'created_at', order: -1 });

const kpis = reactive({ total: 0, active: 0, inactive: 0, archived: 0, latestLastAttended: '' });

const filters = reactive({
    status: 'Ativo',
    search: '',
    groupId: null,
    tagId: null,
    createdFrom: null,
    createdTo: null
});

const statusOptions = [
    { label: 'Ativos', value: 'Ativo' },
    { label: 'Inativos', value: 'Inativo' },
    { label: 'Arquivados', value: 'Arquivado' },
    { label: 'Todos', value: 'Todos' }
];

const historySet = ref(new Set());
const sessionCountMap = ref(new Map());

const groupOptions = computed(() => (groups.value || []).map((g) => ({ label: g.name, value: g.id })));
const tagOptions = computed(() => (tags.value || []).map((t) => ({ label: t.name, value: t.id })));

const hasActiveFilters = computed(() => Boolean(String(filters.search || '').trim() || (filters.status && filters.status !== 'Todos') || filters.groupId || filters.tagId || filters.createdFrom || filters.createdTo));

onMounted(async () => {
    const rootMargin = `${document.querySelector('.l2-main') ? '0px' : '-56px'} 0px 0px 0px`;
    _observer = new IntersectionObserver(
        ([entry]) => {
            headerStuck.value = !entry.isIntersecting;
        },
        { threshold: 0, rootMargin }
    );
    if (headerSentinelRef.value) _observer.observe(headerSentinelRef.value);
    await loadUser();
    await fetchAll();
});

onBeforeUnmount(() => {
    _observer?.disconnect();
});

function fmtBRL(v) {
    if (v == null || v === '') return '—';
    return Number(v).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' });
}

function fmtDiscount(d) {
    if (!d) return null;
    const parts = [];
    if (Number(d.discount_pct) > 0) parts.push(`${Number(d.discount_pct)}%`);
    if (Number(d.discount_flat) > 0) parts.push(fmtBRL(d.discount_flat));
    return parts.length ? parts.join(' + ') : null;
}

function fmtPhoneBR(v) {
    const d = String(v ?? '').replace(/\D/g, '');
    if (!d) return '—';
    if (d.length === 11) return `(${d.slice(0, 2)}) ${d.slice(2, 7)}-${d.slice(7)}`;
    if (d.length === 10) return `(${d.slice(0, 2)}) ${d.slice(2, 6)}-${d.slice(6)}`;
    return d;
}

// ── UI actions ────────────────────────────────────────────
function openProntuario(row) {
    if (!row?.id) return;
    selectedPatient.value = { ...row };
    prontuarioOpen.value = true;
}

function closeProntuario() {
    prontuarioOpen.value = false;
    selectedPatient.value = null;
}
function openQuickCreate() {
    quickDialog.value = true;
}

function onPatientCreated() {
    dialogSaved.value = true;
}

watch(cadastroFullDialog, async (isOpen) => {
    if (!isOpen) {
        editPatientId.value = null;
        if (dialogSaved.value) {
            dialogSaved.value = false;
            await fetchAll();
        }
    }
});

function onQuickCreated(row) {
    if (!row) return;
    patients.value = [
        {
            ...row,
            status: normalizeStatus(row.status),
            created_at: (row.created_at || '').slice(0, 10),
            updated_at: (row.updated_at || '').slice(0, 10),
            last_attended_at: (row.last_attended_at || '').slice(0, 10),
            groups: row.groups || [],
            tags: row.tags || []
        },
        ...(patients.value || [])
    ];
    sort.field = 'created_at';
    sort.order = -1;
    updateKpis();
}

// ── Navigation ────────────────────────────────────────────
function getAreaBase() {
    const seg = String(route.path || '').split('/')[1];
    return seg === 'therapist' ? '/therapist' : '/admin';
}

function getAreaKey() {
    const seg = String(route.path || '').split('/')[1];
    return seg === 'therapist' ? 'therapist' : 'admin';
}

function getPatientsRoutes() {
    const area = getAreaKey();
    if (area === 'therapist')
        return {
            groupsPath: '/therapist/patients/grupos',
            createPath: '/therapist/patients/cadastro',
            editPath: (id) => `/therapist/patients/cadastro/${id}`,
            createName: 'therapist-patients-cadastro',
            editName: 'therapist-patients-cadastro-edit',
            groupsName: 'therapist-patients-grupos'
        };
    return {
        groupsPath: '/admin/pacientes/grupos',
        createPath: '/admin/pacientes/cadastro',
        editPath: (id) => `/admin/pacientes/cadastro/${id}`,
        createName: 'admin-pacientes-cadastro',
        editName: 'admin-pacientes-cadastro-edit',
        groupsName: 'admin-pacientes-grupos'
    };
}

function safePush(toObj, fallbackPath) {
    try {
        const r = router.resolve(toObj);
        if (r?.matched?.length) return router.push(toObj);
    } catch (_) {}
    return router.push(fallbackPath);
}

function goGroups() {
    const r = getPatientsRoutes();
    return safePush({ name: r.groupsName }, r.groupsPath);
}
function goCreateFull() {
    cadastroFullDialog.value = true;
}
function goEdit(row) {
    if (!row?.id) return;
    editPatientId.value = String(row.id);
    cadastroFullDialog.value = true;
}

function goConversation(row) {
    if (!row?.id) return;
    conversationDrawerStore.openForPatient(String(row.id));
}

// ── Filters & Sort ────────────────────────────────────────
let searchTimer = null;
function onFilterChangedDebounced() {
    if (searchTimer) clearTimeout(searchTimer);
    searchTimer = setTimeout(() => onFilterChanged(), 250);
}
function onFilterChanged() {
    updateKpis();
}
function setStatus(s) {
    filters.status = s;
    onFilterChanged();
}
function clearAllFilters() {
    filters.status = 'Ativo';
    filters.search = '';
    filters.groupId = null;
    filters.tagId = null;
    filters.createdFrom = null;
    filters.createdTo = null;
    onFilterChanged();
}
function onSort(e) {
    sort.field = e.sortField;
    sort.order = e.sortOrder;
}

// ── Helpers ───────────────────────────────────────────────
function prettyDate(iso) {
    if (!iso) return '—';
    const parts = String(iso).slice(0, 10).split('-');
    if (parts.length !== 3) return String(iso);
    const [yyyy, mm, dd] = parts;
    return `${dd}/${mm}/${yyyy}`;
}

function normalizeStatus(s) {
    const v = String(s || '')
        .toLowerCase()
        .trim();
    if (!v) return 'Ativo';
    if (v === 'active' || v === 'ativo') return 'Ativo';
    if (v === 'inactive' || v === 'inativo') return 'Inativo';
    return v.charAt(0).toUpperCase() + v.slice(1);
}

function statusSeverity(s) {
    if (s === 'Ativo') return 'success';
    if (s === 'Inativo') return 'warn';
    if (s === 'Arquivado') return 'secondary';
    if (s === 'Alta') return 'info';
    if (s === 'Encaminhado') return 'contrast';
    return 'secondary';
}

function initials(name) {
    const parts = String(name || '')
        .trim()
        .split(/\s+/)
        .filter(Boolean);
    if (!parts.length) return '—';
    if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
    return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
}

function shortId(id) {
    const s = String(id || '');
    return s.length > 8 ? s.slice(0, 8) : s;
}

function chipStyle(color) {
    if (!color) return { background: 'rgba(148,163,184,.18)', border: '1px solid rgba(148,163,184,.30)', color: 'var(--text-color)' };
    return { background: hexToRgba(color, 0.16), border: `1px solid ${hexToRgba(color, 0.3)}`, color: 'var(--text-color)' };
}

function hexToRgba(hex, alpha) {
    const h = String(hex || '')
        .replace('#', '')
        .trim();
    if (![3, 6].includes(h.length)) return `rgba(255,255,255,${alpha})`;
    const full =
        h.length === 3
            ? h
                  .split('')
                  .map((ch) => ch + ch)
                  .join('')
            : h;
    const r = parseInt(full.slice(0, 2), 16);
    const g = parseInt(full.slice(2, 4), 16);
    const b = parseInt(full.slice(4, 6), 16);
    return `rgba(${r},${g},${b},${alpha})`;
}

// ── Auth ──────────────────────────────────────────────────
async function loadUser() {
    const { data, error } = await supabase.auth.getUser();
    if (error) throw error;
    uid.value = data?.user?.id || null;
}

function withOwnerFilter(q) {
    return uid.value ? q.eq('owner_id', uid.value) : q;
}

// Schema-per-tenant: isolamento via schema tenant_<slug>; tabela não tem mais coluna tenant_id.
function withTenantFilter(q) {
    return q;
}

// ── Filtered rows ─────────────────────────────────────────
const filteredRows = computed(() => {
    const s = String(filters.search || '')
        .trim()
        .toLowerCase();
    const toISO = (d) => {
        if (!d) return null;
        return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
    };
    const from = toISO(filters.createdFrom);
    const to = toISO(filters.createdTo);

    const inDateRange = (row) => {
        if (!from && !to) return true;
        const rowDate = (row.created_at || '').slice(0, 10);
        if (!rowDate) return true;
        if (from && rowDate < from) return false;
        if (to && rowDate > to) return false;
        return true;
    };

    let rows = (patients.value || []).filter((r) => {
        if (filters.status !== 'Todos' && r.status !== filters.status) return false;
        if (s) {
            const hay = `${r.nome_completo || ''} ${r.email_principal || ''} ${r.telefone || ''}`.toLowerCase();
            if (!hay.includes(s)) return false;
        }
        if (!inDateRange(r)) return false;
        if (filters.groupId && !(r.groups || []).some((g) => g.id === filters.groupId)) return false;
        if (filters.tagId && !(r.tags || []).some((t) => t.id === filters.tagId)) return false;
        return true;
    });

    if (sort.field) {
        rows = rows.slice().sort((a, b) => {
            const av = a[sort.field] ?? '',
                bv = b[sort.field] ?? '';
            if (av === bv) return 0;
            return av > bv ? sort.order : -sort.order;
        });
    }
    return rows;
});

// ── Fetch ─────────────────────────────────────────────────
async function fetchAll() {
    loading.value = true;
    try {
        patients.value = await listPatients();

        discountMap.value = {};
        if (uid.value) {
            const now = new Date().toISOString();
            const { data: discRows } = await tenantDb().from('patient_discounts').select('patient_id, discount_pct, discount_flat').eq('owner_id', uid.value).eq('active', true).or(`active_to.is.null,active_to.gte.${now}`);
            if (discRows) discountMap.value = Object.fromEntries(discRows.map((d) => [d.patient_id, d]));
        }

        insuranceMap.value = {};
        if (uid.value) {
            const { data: insRows } = await tenantDb().from('agenda_eventos').select('patient_id, insurance_plan_id, insurance_plans(name)').eq('owner_id', uid.value).not('insurance_plan_id', 'is', null).order('inicio_em', { ascending: false });
            if (insRows) {
                for (const row of insRows) {
                    if (!insuranceMap.value[row.patient_id]) insuranceMap.value[row.patient_id] = row.insurance_plans?.name ?? null;
                }
            }
        }

        groups.value = await listGroups();
        tags.value = await listTags();
        await hydrateAssociationsSupabase();
        updateKpis();
    } catch (e) {
        toast.add({ severity: 'error', summary: 'Falha', detail: e?.message || 'Não consegui carregar pacientes.', life: 3500 });
    } finally {
        loading.value = false;
        hasLoaded.value = true;
    }
}

async function listPatients() {
    let q = tenantDb().from('patients').select('id, owner_id, nome_completo, email_principal, telefone, avatar_url, status, last_attended_at, created_at, updated_at').order('created_at', { ascending: false });
    q = withTenantFilter(withOwnerFilter(q));
    const { data, error } = await q;
    if (error) throw error;
    return (data || []).map((p) => ({
        ...p,
        status: normalizeStatus(p.status),
        last_attended_at: (p.last_attended_at || '').slice(0, 10),
        created_at: (p.created_at || '').slice(0, 10),
        updated_at: (p.updated_at || '').slice(0, 10),
        groups: [],
        tags: []
    }));
}

async function listGroups() {
    let q = tenantDb().from('patient_groups').select('id, owner_id, nome, cor, is_system, is_active').eq('is_active', true).order('nome', { ascending: true });
    q = withTenantFilter(q);
    if (uid.value) q = q.or(`is_system.eq.true,owner_id.eq.${uid.value}`);
    else q = q.eq('is_system', true);
    const { data, error } = await q;
    if (error) throw error;
    return (data || []).map((g) => ({ ...g, name: g.nome, color: g.cor }));
}

async function listTags() {
    let q = tenantDb().from('patient_tags').select('id, owner_id, nome, cor').order('nome', { ascending: true });
    q = withTenantFilter(q);
    if (uid.value) q = q.eq('owner_id', uid.value);
    const { data, error } = await q;
    if (error) throw error;
    return (data || []).map((t) => ({ ...t, name: t.nome, color: t.cor }));
}

async function hydrateAssociationsSupabase() {
    const ids = (patients.value || []).map((p) => p.id).filter(Boolean);
    if (!ids.length) return;

    const tenantId = tenantStore.activeTenantId;

    // V#3 — Fase 1: vínculos + sessions via repo (paralelo).
    const [pg, pt, sessionCounts] = await Promise.all([
        listGroupsByPatient(ids, { tenantId }),
        listTagsByPatient(ids, { tenantId }),
        getSessionCounts(ids)
    ]);

    // Fase 2: catálogos completos (grupos + tags) também via repo.
    // Lista TUDO do tenant pra deixar mapas prontos (volume baixo); repo já
    // filtra is_system OR owner do uid.
    const [groupCatalog, tagCatalog] = await Promise.all([
        repoListGroups({ tenantId, ownerId: uid.value || null }),
        repoListTags({ tenantId, ownerId: uid.value || null })
    ]);

    // Monta mapas finais
    const gById = new Map(groupCatalog.map((g) => [g.id, g]));
    const groupsByPatient = new Map();
    for (const rel of pg) {
        const arr = groupsByPatient.get(rel.patient_id) || [];
        const g = gById.get(rel.patient_group_id);
        if (g) arr.push(g);
        groupsByPatient.set(rel.patient_id, arr);
    }

    const tById = new Map(tagCatalog.map((t) => [t.id, t]));
    const tagsByPatient = new Map();
    for (const rel of pt) {
        const arr = tagsByPatient.get(rel.patient_id) || [];
        const t = tById.get(rel.tag_id);
        if (t) arr.push(t);
        tagsByPatient.set(rel.patient_id, arr);
    }

    patients.value = (patients.value || []).map((p) => ({
        ...p,
        groups: groupsByPatient.get(p.id) || [],
        tags: tagsByPatient.get(p.id) || []
    }));

    const tempSet = new Set();
    const countMap = new Map();
    for (const r of sessionCounts) {
        if (r.patient_id && r.session_count > 0) {
            tempSet.add(r.patient_id);
            countMap.set(r.patient_id, r.session_count);
        }
    }
    historySet.value = tempSet;
    sessionCountMap.value = countMap;
}

// Delete movido para PatientActionMenu + usePatientLifecycle

// ── KPIs ──────────────────────────────────────────────────
function updateKpis() {
    const all = patients.value || [];
    kpis.total = all.length;
    kpis.active = all.filter((p) => p.status === 'Ativo').length;
    kpis.inactive = all.filter((p) => p.status === 'Inativo').length;
    kpis.archived = all.filter((p) => p.status === 'Arquivado').length;
    const dates = all
        .map((p) => (p.last_attended_at || '').slice(0, 10))
        .filter(Boolean)
        .sort();
    kpis.latestLastAttended = dates.length ? dates[dates.length - 1] : '';
}

// ── Grupos view ───────────────────────────────────────────
const groupedPatientsView = computed(() => {
    const all = patients.value || [];
    const grpMap = new Map();
    for (const g of groups.value || []) {
        const isSystem = !!g.is_system;
        const storedColor = isSystem ? getSysGroupColor(g.id) : null;
        const rawColor = storedColor || g.color || g.cor || null;
        const resolvedColor = rawColor ? (rawColor.startsWith('#') ? rawColor : `#${rawColor}`) : isSystem ? `#${systemDefaultColorForGrp(g.name || g.nome)}` : null;
        grpMap.set(g.id, { id: g.id, name: g.name || g.nome, color: resolvedColor, patients: [], isSystem });
    }
    const ungrouped = { id: '__none__', name: 'Sem grupo', color: null, patients: [], isSystem: false };
    for (const p of all) {
        const gs = p.groups || [];
        if (!gs.length) {
            ungrouped.patients.push(p);
        } else {
            for (const g of gs) {
                if (grpMap.has(g.id)) grpMap.get(g.id).patients.push(p);
            }
        }
    }
    const result = [...grpMap.values()].filter((g) => g.patients.length > 0).sort((a, b) => b.patients.length - a.patients.length);
    if (ungrouped.patients.length > 0) result.push(ungrouped);
    return result;
});

function systemDefaultColorForGrp(nameOrObj) {
    const name = typeof nameOrObj === 'string' ? nameOrObj : nameOrObj?.name || nameOrObj?.nome || '';
    return getSystemGroupDefaultColor(name).replace('#', '');
}

function grpColorStyle(grp) {
    // aceita string (#hex) ou objeto grp com .color já resolvido
    const hex = typeof grp === 'string' ? grp || null : grp?.color || null;
    if (!hex) return { background: 'var(--surface-border)' };
    return { background: hex };
}

function grpChipAvatarStyle(grp) {
    const hex = typeof grp === 'string' ? grp || null : grp?.color || null;
    if (!hex) return {};
    return { background: `${hex}25`, color: hex };
}

// ── Dialog: grupo de pacientes ────────────────────────────
const grpDialog = reactive({ open: false, group: null, search: '' });

const grpDialogFiltered = computed(() => {
    const list = grpDialog.group?.patients || [];
    const s = String(grpDialog.search || '')
        .trim()
        .toLowerCase();
    if (!s) return list;
    return list.filter(
        (p) =>
            String(p.nome_completo || '')
                .toLowerCase()
                .includes(s) ||
            String(p.email_principal || '')
                .toLowerCase()
                .includes(s) ||
            String(p.telefone || '')
                .toLowerCase()
                .includes(s)
    );
});

function openGrpDialog(grp) {
    grpDialog.group = grp;
    grpDialog.search = '';
    grpDialog.open = true;
}

function grpDialogHex() {
    return grpDialog.group?.color || '#6366f1';
}

const HIGHLIGHT_MS = 24 * 60 * 60 * 1000;
function isRecent(row) {
    if (!row?.created_at) return false;
    return Date.now() - new Date(row.created_at).getTime() < HIGHLIGHT_MS;
}
</script>

<template>
    <!-- Sentinel -->
    <div ref="headerSentinelRef" class="h-px" />

    <!-- ══════════════════════════════════════
       Hero sticky
  ═══════════════════════════════════════ -->
    <div
        ref="headerEl"
        class="sticky mx-3 md:mx-4 my-3 z-20 overflow-hidden rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] px-3 py-2.5 transition-[border-radius] duration-200"
        :class="{ 'rounded-tl-none rounded-tr-none': headerStuck }"
        :style="{ top: 'var(--layout-sticky-top, 56px)' }"
    >
        <!-- Blobs decorativos -->
        <div class="absolute inset-0 pointer-events-none overflow-hidden" aria-hidden="true">
            <div class="absolute w-64 h-64 -top-16 -right-8 rounded-full blur-[60px] bg-emerald-400/10" />
            <div class="absolute w-72 h-72 top-0 -left-16 rounded-full blur-[60px] bg-indigo-500/[0.09]" />
        </div>

        <div class="relative z-1 flex items-center gap-3">
            <!-- Brand -->
            <div class="flex items-center gap-2 shrink-0">
                <div class="grid place-items-center w-9 h-9 rounded-md shrink-0 bg-indigo-500/10 text-indigo-500">
                    <i class="pi pi-users text-base" />
                </div>
                <div class="min-w-0 hidden lg:block">
                    <div class="text-[1rem] font-bold tracking-tight text-[var(--text-color)]">Pacientes</div>
                    <div class="text-[1rem] text-[var(--text-color-secondary)]">Gerencie seus pacientes, histórico e status de atendimento</div>
                </div>
            </div>

            <!-- Ações desktop -->
            <div class="hidden xl:flex items-center gap-1 shrink-0 ml-auto">
                <Button icon="pi pi-refresh" severity="secondary" outlined class="h-9 w-9 rounded-full" :loading="loading" @click="fetchAll" />
                <Button icon="pi pi-percentage" severity="secondary" outlined class="h-9 w-9 rounded-full" v-tooltip.top="'Descontos'" @click="router.push('/configuracoes/descontos')" />
                <Button label="Novo" icon="pi pi-user-plus" class="rounded-full" @click="(e) => createPopoverRef?.toggle(e)" />
                <PatientCreatePopover ref="createPopoverRef" @quick-create="openQuickCreate" @go-complete="goCreateFull" />
                <PatientCadastroDialog v-model="cadastroFullDialog" :patient-id="editPatientId" @created="onPatientCreated" />
            </div>

            <!-- Mobile -->
            <div class="flex xl:hidden items-center gap-1 shrink-0 ml-auto">
                <Button icon="pi pi-user-plus" class="h-9 w-9 rounded-full" @click="(e) => createPopoverRef?.toggle(e)" />
                <Button label="Ações" icon="pi pi-ellipsis-v" severity="secondary" size="small" class="rounded-full" @click="(e) => patMobileMenuRef.toggle(e)" />
                <Menu ref="patMobileMenuRef" :model="patMobileMenuItems" :popup="true" />
            </div>
        </div>
    </div>

    <!-- ══════════════════════════════════════
       Stats row
       Mobile (< xl / ≤ 1280px):
         Linha 1 — Agenda + Descontos (flex, 2 cols)
         Linha 2 — Total + Ativos + Inativos + Último atend.
       Desktop (xl+): tudo em uma única linha
  ═══════════════════════════════════════ -->
    <div class="px-3 md:px-4 mb-3 flex flex-col xl:flex-row gap-2">
        <!-- ── Linha 1 (mobile) / parte esquerda (desktop): atalhos ── -->
        <div class="flex gap-2">
            <!-- Minha Agenda -->
            <div
                class="flex flex-col gap-1 px-4 py-2.5 rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] flex-1 cursor-pointer select-none transition-[border-color,box-shadow] duration-150 hover:border-indigo-400/40 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
                @click="router.push(getAreaBase() + '/agenda')"
            >
                <div class="flex items-center gap-1.5">
                    <i class="pi pi-calendar-clock text-[var(--primary-color,#6366f1)]" />
                    <span class="text-[1.1rem] font-bold leading-none text-[var(--primary-color,#6366f1)]">Agenda</span>
                </div>
                <div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 truncate">Ver meus compromissos</div>
            </div>

            <!-- Descontos -->
            <div
                class="flex flex-col gap-1 px-4 py-2.5 rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] flex-1 cursor-pointer select-none transition-[border-color,box-shadow] duration-150 hover:border-amber-400/40 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
                @click="router.push('/configuracoes/descontos')"
            >
                <div class="flex items-center gap-1.5">
                    <i class="pi pi-percentage text-amber-500" />
                    <span class="text-[1.1rem] font-bold leading-none text-amber-500">Descontos</span>
                </div>
                <div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 truncate">Ver descontos aplicados</div>
            </div>
        </div>

        <!-- ── Linha 2 (mobile) / parte direita (desktop): KPIs ── -->
        <div class="flex gap-2 flex-1 flex-wrap xl:flex-nowrap">
            <template v-if="loading">
                <Skeleton v-for="n in 5" :key="n" height="3.5rem" class="flex-1 min-w-[72px] rounded-md" />
            </template>

            <template v-else>
                <!-- Total -->
                <div
                    class="flex flex-col gap-1 px-4 py-2.5 rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] min-w-[72px] flex-1 cursor-pointer select-none transition-[border-color,box-shadow] duration-150 hover:border-indigo-400/40 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
                    :class="{ 'border-[var(--primary-color,#6366f1)] shadow-[0_0_0_3px_rgba(99,102,241,0.15)]': filters.status === 'Todos' }"
                    @click="setStatus('Todos')"
                >
                    <div class="text-[1.35rem] font-bold leading-none text-[var(--text-color)]">{{ kpis.total }}</div>
                    <div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 truncate">Total</div>
                </div>

                <!-- Ativos -->
                <div
                    class="flex flex-col gap-1 px-4 py-2.5 rounded-md border min-w-[72px] flex-1 cursor-pointer select-none transition-[border-color,box-shadow,background] duration-150 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
                    :class="filters.status === 'Ativo' ? 'border-green-500 bg-green-500/5 shadow-[0_0_0_3px_rgba(34,197,94,0.15)]' : 'border-green-500/30 bg-green-500/5 hover:border-green-500/50'"
                    @click="setStatus('Ativo')"
                >
                    <div class="text-[1.35rem] font-bold leading-none text-green-500">{{ kpis.active }}</div>
                    <div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 truncate">Ativos</div>
                </div>

                <!-- Inativos -->
                <div
                    class="flex flex-col gap-1 px-4 py-2.5 rounded-md border min-w-[72px] flex-1 cursor-pointer select-none transition-[border-color,box-shadow,background] duration-150 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
                    :class="filters.status === 'Inativo' ? 'border-red-500 bg-red-500/5 shadow-[0_0_0_3px_rgba(239,68,68,0.15)]' : 'border-red-500/30 bg-red-500/5 hover:border-red-500/50'"
                    @click="setStatus('Inativo')"
                >
                    <div class="text-[1.35rem] font-bold leading-none text-red-500">{{ kpis.inactive }}</div>
                    <div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 truncate">Inativos</div>
                </div>

                <!-- Arquivados -->
                <div
                    class="flex flex-col gap-1 px-4 py-2.5 rounded-md border min-w-[72px] flex-1 cursor-pointer select-none transition-[border-color,box-shadow,background] duration-150 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
                    :class="filters.status === 'Arquivado' ? 'border-slate-500 bg-slate-500/5 shadow-[0_0_0_3px_rgba(100,116,139,0.15)]' : 'border-slate-500/30 bg-slate-500/5 hover:border-slate-500/50'"
                    @click="setStatus('Arquivado')"
                >
                    <div class="text-[1.35rem] font-bold leading-none text-slate-500">{{ kpis.archived }}</div>
                    <div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 truncate">Arquivados</div>
                </div>

                <!-- Último atendimento — não clicável -->
                <div class="flex flex-col gap-1 px-4 py-2.5 rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] min-w-[72px] flex-1">
                    <div class="text-[1.1rem] font-bold leading-none text-[var(--text-color)]">{{ prettyDate(kpis.latestLastAttended) || '—' }}</div>
                    <div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 truncate">Último atend.</div>
                </div>
            </template>
        </div>
    </div>

    <!-- Chips de filtros ativos -->
    <div v-if="hasActiveFilters" class="mx-3 md:mx-4 mb-3 flex flex-wrap items-center gap-2">
        <span class="text-base text-color-secondary">Filtros ativos:</span>
        <Tag v-if="filters.status && filters.status !== 'Todos'" :value="`Status: ${filters.status}`" severity="secondary" />
        <Tag v-if="filters.groupId" value="Grupo selecionado" severity="secondary" />
        <Tag v-if="filters.tagId" value="Tag selecionada" severity="secondary" />
        <Tag v-if="filters.createdFrom" value="Data inicial" severity="secondary" />
        <Tag v-if="filters.createdTo" value="Data final" severity="secondary" />
        <Button label="Limpar" icon="pi pi-filter-slash" severity="danger" outlined size="small" class="!rounded-full" @click="clearAllFilters" />
    </div>

    <!-- TABS -->
    <Tabs value="pacientes" class="px-3 md:px-4 mb-5">
        <TabList>
            <Tab value="pacientes"><i class="pi pi-users mr-2" />Pacientes</Tab>
            <Tab value="espera"><i class="pi pi-hourglass mr-2" />Lista de espera</Tab>
            <Tab value="grupos"><i class="pi pi-sitemap mr-2" />Grupos</Tab>
        </TabList>

        <TabPanels>
            <TabPanel value="pacientes">
                <!-- Filters -->
                <div class="flex flex-col gap-3 mb-4">
                    <div class="flex flex-col md:flex-row md:items-end md:flex-wrap gap-3">
                        <div class="w-full lg:flex-1">
                            <FloatLabel variant="on">
                                <IconField>
                                    <InputIcon class="pi pi-search" />
                                    <InputText v-model="filters.search" class="w-full" placeholder="Procurar..." @input="onFilterChangedDebounced" variant="filled" />
                                </IconField>
                            </FloatLabel>
                        </div>

                        <div class="flex gap-2 justify-end">
                            <Button label="Mais filtros" icon="pi pi-sliders-h" severity="secondary" outlined @click="showAdvanced = !showAdvanced" />

                            <!-- Popover colunas -->
                            <Button icon="pi pi-table" severity="secondary" outlined v-tooltip.top="'Colunas'" @click="toggleColumnsPopover($event)" />

                            <Button label="Limpar filtros" icon="pi pi-filter-slash" severity="danger" outlined :disabled="!hasActiveFilters" @click="clearAllFilters" />
                        </div>

                        <Popover ref="colPopover">
                            <div class="p-3" style="min-width: 280px">
                                <div class="flex items-center justify-between gap-2 mb-2">
                                    <div class="text-base font-semibold">Colunas</div>
                                    <div class="flex gap-2">
                                        <Button label="Padrão" size="small" severity="secondary" text @click="setDefaultColumns" />
                                        <Button label="Todas" size="small" severity="secondary" text @click="setAllColumns" />
                                    </div>
                                </div>
                                <MultiSelect v-model="selectedColumns" :options="columnCatalogAll" optionLabel="label" optionValue="key" :optionDisabled="(opt) => !!opt.locked" class="w-full" placeholder="Selecione colunas">
                                    <template #value="{ value }">
                                        <span class="text-base text-color-secondary">
                                            <template v-if="(value?.length || 0) === 4">Padrão (4 colunas)</template>
                                            <template v-else>{{ value?.length || 0 }} colunas selecionadas</template>
                                        </span>
                                    </template>
                                    <template #option="{ option }">
                                        <div class="flex items-center justify-between w-full">
                                            <div class="flex items-center gap-2">
                                                <span class="font-medium">{{ option.label }}</span>
                                                <span v-if="option.locked" class="text-base text-color-secondary">(fixa)</span>
                                            </div>
                                            <i v-if="option.locked" class="pi pi-lock text-base opacity-70" />
                                        </div>
                                    </template>
                                </MultiSelect>
                                <div class="mt-3 text-base text-color-secondary">Paciente, Status, Telefone e Ações ficam sempre visíveis.</div>
                            </div>
                        </Popover>
                    </div>

                    <Transition name="fade">
                        <div v-if="showAdvanced" class="grid gap-3 mb-4 p-4 bg-gray-100 rounded-md items-end grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5">
                            <div class="min-w-0">
                                <label class="block mb-2">Status</label>
                                <Select v-model="filters.status" :options="statusOptions" optionLabel="label" optionValue="value" placeholder="(todos)" class="w-full" @change="onFilterChanged" />
                            </div>
                            <div class="min-w-0">
                                <label class="block mb-2">Grupo</label>
                                <Select v-model="filters.groupId" :options="groupOptions" optionLabel="label" optionValue="value" placeholder="(todos)" showClear class="w-full" @change="onFilterChanged" />
                            </div>
                            <div class="min-w-0">
                                <label class="block mb-2">Tag</label>
                                <Select v-model="filters.tagId" :options="tagOptions" optionLabel="label" optionValue="value" placeholder="(todas)" showClear class="w-full" @change="onFilterChanged" />
                            </div>
                            <div class="min-w-0">
                                <label class="block mb-2">Cadastrado (de)</label>
                                <DatePicker v-model="filters.createdFrom" dateFormat="dd-mm-yy" placeholder="DD-MM-AAAA" showIcon class="w-full" @update:modelValue="onFilterChanged" />
                            </div>
                            <div class="min-w-0">
                                <label class="block mb-2">Cadastrado (até)</label>
                                <DatePicker v-model="filters.createdTo" dateFormat="dd-mm-yy" placeholder="DD-MM-AAAA" showIcon class="w-full" @update:modelValue="onFilterChanged" />
                            </div>
                        </div>
                    </Transition>
                </div>

                <!-- Table – desktop (md+) -->
                <div class="hidden md:block">
                    <DataTable
                        :value="filteredRows"
                        dataKey="id"
                        :loading="loading"
                        paginator
                        :rows="15"
                        :rowsPerPageOptions="[10, 15, 25, 50]"
                        stripedRows
                        scrollable
                        scrollHeight="400px"
                        sortMode="single"
                        :sortField="sort.field"
                        :sortOrder="sort.order"
                        @sort="onSort"
                        class="pat-datatable"
                        :rowClass="(r) => (isRecent(r) ? 'row-new-highlight' : '')"
                    >
                        <template #empty>
                            <div class="py-10 text-center">
                                <div class="mx-auto mb-3 grid h-12 w-12 place-items-center rounded-[6px] bg-[var(--primary-color)]/10 text-[var(--primary-color)]">
                                    <i class="pi pi-search text-xl" />
                                </div>
                                <div class="font-semibold">Nenhum paciente encontrado</div>
                                <div class="mt-1 text-base text-color-secondary">Tente limpar filtros ou mudar o termo de busca.</div>
                                <div class="mt-4 flex justify-center gap-2">
                                    <Button severity="secondary" outlined icon="pi pi-filter-slash" label="Limpar filtros" @click="clearAllFilters" />
                                    <Button icon="pi pi-user-plus" label="Cadastrar paciente" @click="goCreateFull" />
                                </div>
                            </div>
                        </template>

                        <Column :key="'col-paciente'" field="nome_completo" header="Paciente" v-if="isColVisible('paciente')" sortable>
                            <template #body="{ data }">
                                <div class="flex items-center gap-3">
                                    <Avatar v-if="data.avatar_url" :image="data.avatar_url" shape="square" size="large" />
                                    <Avatar v-else :label="initials(data.nome_completo)" shape="square" size="large" />
                                    <div class="min-w-0">
                                        <div class="flex items-center gap-1.5">
                                            <span class="font-medium truncate">{{ data.nome_completo }}</span>
                                            <Tag v-if="discountMap[data.id]" :value="fmtDiscount(discountMap[data.id])" severity="success" class="shrink-0" style="font-size: 0.7rem; padding: 1px 6px" />
                                            <Tag v-if="insuranceMap[data.id]" :value="insuranceMap[data.id]" severity="info" class="shrink-0" style="font-size: 0.7rem; padding: 1px 6px" />
                                        </div>
                                        <span class="text-base text-color-secondary">ID: {{ shortId(data.id) }}</span>
                                    </div>
                                </div>
                            </template>
                        </Column>

                        <Column field="status" header="Status" v-if="isColVisible('status')" :key="'col-status'" sortable style="width: 9rem">
                            <template #body="{ data }">
                                <Tag :value="data.status" :severity="statusSeverity(data.status)" />
                            </template>
                        </Column>

                        <Column header="Telefone" style="width: 16rem" v-if="isColVisible('telefone')" :key="'col-telefone'">
                            <template #body="{ data }">
                                <div class="text-base leading-tight">
                                    <div class="font-medium">{{ fmtPhoneBR(data.telefone) }}</div>
                                    <div class="text-base text-color-secondary">{{ data.email_principal || '—' }}</div>
                                </div>
                            </template>
                        </Column>

                        <Column field="email_principal" header="Email" style="width: 18rem" v-if="isColVisible('email')" :key="'col-email'">
                            <template #body="{ data }">
                                <span class="text-color-secondary">{{ data.email_principal || '—' }}</span>
                            </template>
                        </Column>

                        <Column field="last_attended_at" header="Último Atendimento" v-if="isColVisible('last_attended_at')" :key="'col-last_attended_at'" sortable style="width: 12rem">
                            <template #body="{ data }">{{ data.last_attended_at || '—' }}</template>
                        </Column>

                        <Column field="created_at" header="Cadastrado em" v-if="isColVisible('created_at')" :key="'col-created_at'" sortable style="width: 12rem">
                            <template #body="{ data }">{{ data.created_at || '—' }}</template>
                        </Column>

                        <Column header="Grupos" style="min-width: 14rem" v-if="isColVisible('grupos')" :key="'col-grupos'">
                            <template #body="{ data }">
                                <div v-if="!(data.groups || []).length" class="text-color-secondary">—</div>
                                <div v-else class="flex flex-wrap gap-2">
                                    <Tag v-for="g in data.groups" :key="g.id" :value="g.name" :style="chipStyle(g.color)" />
                                </div>
                            </template>
                        </Column>

                        <Column header="Tags" style="min-width: 14rem" v-if="isColVisible('tags')" :key="'col-tags'">
                            <template #body="{ data }">
                                <div v-if="!(data.tags || []).length" class="text-color-secondary">—</div>
                                <div v-else class="flex flex-wrap gap-2">
                                    <Tag v-for="t in data.tags" :key="t.id" :value="t.name" :style="chipStyle(t.color)" />
                                </div>
                            </template>
                        </Column>

                        <Column :key="'col-acoes'" header="Ações" style="width: 22rem" frozen alignFrozen="right">
                            <template #body="{ data }">
                                <div class="flex gap-2 justify-end">
                                    <Button v-if="historySet.has(data.id)" :label="`Sessões × ${sessionCountMap.get(data.id) || 0}`" icon="pi pi-calendar" size="small" severity="info" outlined @click="abrirSessoes(data)" />
                                    <Button label="Prontuário" icon="pi pi-file" size="small" @click="openProntuario(data)" />
                                    <Button icon="pi pi-whatsapp" severity="success" outlined size="small" v-tooltip.top="'Conversar no WhatsApp'" @click="goConversation(data)" />
                                    <Button icon="pi pi-pencil" severity="secondary" outlined size="small" v-tooltip.top="'Editar'" @click="goEdit(data)" />
                                    <Button v-if="data.status === 'Arquivado'" icon="pi pi-undo" label="Restaurar" size="small" severity="primary" outlined v-tooltip.top="'Voltar paciente pra ativos'" @click="restaurarPaciente(data)" />
                                    <PatientActionMenu :patient="data" :hasHistory="historySet.has(data.id)" @updated="fetchAll" />
                                </div>
                            </template>
                        </Column>
                    </DataTable>
                </div>

                <!-- Cards – mobile (<md) -->
                <div class="md:hidden">
                    <div v-if="loading" class="flex flex-col gap-3 pb-4">
                        <div v-for="n in 6" :key="n" class="rounded-[6px] border border-[var(--surface-border)] bg-[var(--surface-card)] p-4 flex flex-col gap-3">
                            <div class="flex items-center gap-3">
                                <Skeleton shape="square" size="3rem" border-radius="6px" />
                                <div class="flex flex-col gap-1.5 flex-1">
                                    <Skeleton width="60%" height="14px" />
                                    <Skeleton width="40%" height="11px" />
                                </div>
                                <Skeleton width="50px" height="22px" border-radius="999px" />
                            </div>
                            <div class="flex gap-2 justify-end">
                                <Skeleton width="90px" height="30px" border-radius="999px" />
                                <Skeleton width="80px" height="30px" border-radius="999px" />
                            </div>
                        </div>
                    </div>

                    <div v-else-if="filteredRows.length === 0" class="py-10 text-center">
                        <div class="mx-auto mb-3 grid h-12 w-12 place-items-center rounded-[6px] bg-[var(--primary-color)]/10 text-[var(--primary-color)]">
                            <i class="pi pi-search text-xl" />
                        </div>
                        <div class="font-semibold">Nenhum paciente encontrado</div>
                        <div class="mt-1 text-base text-color-secondary">Tente limpar filtros ou mudar o termo de busca.</div>
                        <div class="mt-4 flex justify-center gap-2">
                            <Button severity="secondary" outlined icon="pi pi-filter-slash" label="Limpar filtros" @click="clearAllFilters" />
                            <Button icon="pi pi-user-plus" label="Cadastrar" @click="goCreateFull" />
                        </div>
                    </div>

                    <div v-else class="flex flex-col gap-3 pb-4">
                        <div v-for="pat in filteredRows" :key="pat.id" class="rounded-[6px] border border-[var(--surface-border)] bg-[var(--surface-card)] p-4">
                            <!-- Topo: avatar + nome + status -->
                            <div class="flex items-center gap-3">
                                <Avatar v-if="pat.avatar_url" :image="pat.avatar_url" shape="square" size="large" />
                                <Avatar v-else :label="initials(pat.nome_completo)" shape="square" size="large" />
                                <div class="flex-1 min-w-0">
                                    <div class="flex items-center gap-1.5">
                                        <span class="font-semibold truncate">{{ pat.nome_completo }}</span>
                                        <Tag v-if="discountMap[pat.id]" :value="fmtDiscount(discountMap[pat.id])" severity="success" class="shrink-0" style="font-size: 0.7rem; padding: 1px 6px" />
                                        <Tag v-if="insuranceMap[pat.id]" :value="insuranceMap[pat.id]" severity="info" class="shrink-0" style="font-size: 0.7rem; padding: 1px 6px" />
                                    </div>
                                    <div class="text-base text-color-secondary">{{ fmtPhoneBR(pat.telefone) }} · {{ pat.email_principal || '—' }}</div>
                                </div>
                                <Tag :value="pat.status" :severity="statusSeverity(pat.status)" />
                            </div>

                            <!-- Grupos + Tags -->
                            <div v-if="(pat.groups || []).length || (pat.tags || []).length" class="mt-3 flex flex-wrap gap-1.5">
                                <Tag v-for="g in pat.groups" :key="g.id" :value="g.name" :style="chipStyle(g.color)" />
                                <Tag v-for="t in pat.tags" :key="t.id" :value="t.name" :style="chipStyle(t.color)" />
                            </div>

                            <!-- Ações -->
                            <div class="mt-3 flex gap-2 justify-end flex-wrap">
                                <Button v-if="historySet.has(pat.id)" :label="`Sessões × ${sessionCountMap.get(pat.id) || 0}`" icon="pi pi-calendar" size="small" severity="info" outlined @click="abrirSessoes(pat)" />
                                <Button label="Prontuário" icon="pi pi-file" size="small" @click="openProntuario(pat)" />
                                <Button icon="pi pi-whatsapp" severity="success" outlined size="small" v-tooltip.top="'Conversar'" @click="goConversation(pat)" />
                                <Button icon="pi pi-pencil" severity="secondary" outlined size="small" @click="goEdit(pat)" />
                                <Button v-if="pat.status === 'Arquivado'" icon="pi pi-undo" label="Restaurar" size="small" severity="primary" outlined v-tooltip.top="'Voltar paciente pra ativos'" @click="restaurarPaciente(pat)" />
                                <PatientActionMenu :patient="pat" :hasHistory="historySet.has(pat.id)" @updated="fetchAll" />
                            </div>
                        </div>
                    </div>
                </div>

                <div class="mt-3 flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between text-base text-color-secondary">
                    <div>
                        Exibindo <b class="text-[var(--text-color)]">{{ filteredRows.length }}</b> de <b class="text-[var(--text-color)]">{{ patients.length }}</b> pacientes.
                        <span v-if="hasActiveFilters"> (filtrado)</span>
                    </div>
                    <div class="hidden md:block">Dica: clique em "Ativos/Inativos" no topo para filtrar rápido.</div>
                </div>
            </TabPanel>

            <TabPanel value="espera">
                <Card>
                    <template #content>
                        <div class="text-color-secondary">Placeholder. Quando você quiser, podemos ligar isso a uma tabela (ex.: patient_waitlist).</div>
                    </template>
                </Card>
            </TabPanel>

            <TabPanel value="grupos">
                <!-- Cabeçalho da view de grupos -->
                <div class="flex items-center justify-between gap-3 mb-4">
                    <div class="flex items-center gap-2">
                        <i class="pi pi-sitemap text-[var(--primary-color,#6366f1)]" />
                        <span class="font-semibold text-[var(--text-color)]">Pacientes distribuídos por grupo</span>
                        <span v-if="groupedPatientsView.length" class="inline-flex items-center justify-center min-w-[20px] h-5 px-1 rounded-full bg-[var(--primary-color,#6366f1)] text-white text-[0.65rem] font-bold">{{
                            groupedPatientsView.length
                        }}</span>
                    </div>
                    <Button label="Gerenciar grupos" icon="pi pi-external-link" severity="secondary" outlined size="small" @click="goGroups" />
                </div>

                <!-- Loading -->
                <div v-if="loading" class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3">
                    <Skeleton v-for="n in 6" :key="n" height="8rem" class="rounded-xl" />
                </div>

                <!-- Empty -->
                <div v-else-if="groupedPatientsView.length === 0" class="flex flex-col items-center justify-center gap-3 py-12 text-[var(--text-color-secondary)]">
                    <div class="w-14 h-14 rounded-xl bg-indigo-500/10 flex items-center justify-center">
                        <i class="pi pi-sitemap text-2xl text-indigo-500" />
                    </div>
                    <div class="font-semibold text-[var(--text-color)]">Nenhuma associação encontrada</div>
                    <div class="text-sm opacity-70 text-center max-w-xs">Associe pacientes a grupos no cadastro ou na listagem para visualizá-los aqui.</div>
                    <Button label="Gerenciar grupos" icon="pi pi-sitemap" outlined size="small" class="mt-1" @click="goGroups" />
                </div>

                <!-- Grid de grupos -->
                <div v-else class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-3">
                    <div
                        v-for="grp in groupedPatientsView"
                        :key="grp.id"
                        class="rounded-xl border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] overflow-hidden flex flex-col shadow-sm hover:shadow-md transition-shadow duration-200"
                    >
                        <!-- Barra de cor do grupo -->
                        <div class="h-1.5 w-full" :style="grpColorStyle(grp)" />

                        <!-- Header do grupo (clicável) -->
                        <button
                            class="flex items-center gap-3 px-4 py-3 border-b border-[var(--surface-border,#f1f5f9)] w-full text-left bg-transparent border-0 border-b cursor-pointer transition-opacity duration-150 hover:opacity-80"
                            style="border-bottom-width: 1px"
                            :style="{ borderBottomColor: `${grp.color || 'var(--surface-border)'}30` }"
                            @click="openGrpDialog(grp)"
                        >
                            <div class="w-9 h-9 rounded-lg flex items-center justify-center text-white font-bold text-sm shrink-0 shadow-sm" :style="grpColorStyle(grp)">
                                {{ (grp.name || '?')[0].toUpperCase() }}
                            </div>
                            <div class="flex-1 min-w-0">
                                <div class="font-semibold truncate text-sm" :style="{ color: grp.color || 'var(--text-color)' }">{{ grp.name }}</div>
                                <div class="text-[0.72rem] text-[var(--text-color-secondary)] opacity-70">{{ grp.patients.length }} paciente{{ grp.patients.length !== 1 ? 's' : '' }} · clique para ver</div>
                            </div>
                            <span class="inline-flex items-center justify-center min-w-[26px] h-6 px-1.5 rounded-full text-white text-xs font-bold shrink-0" :style="grpColorStyle(grp)">{{ grp.patients.length }}</span>
                        </button>

                        <!-- Chips de pacientes -->
                        <div class="p-3 flex flex-wrap gap-1.5 flex-1">
                            <button
                                v-for="p in grp.patients.slice(0, 12)"
                                :key="p.id"
                                class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full border border-[var(--surface-border)] bg-[var(--surface-ground)] text-xs text-[var(--text-color)] cursor-pointer transition-all duration-150 font-medium group"
                                v-tooltip.top="p.nome_completo"
                                @click="goEdit(p)"
                                @mouseenter="
                                    (e) => {
                                        e.currentTarget.style.background = grp.color || 'var(--primary-color,#6366f1)';
                                        e.currentTarget.style.color = '#fff';
                                        e.currentTarget.style.borderColor = 'transparent';
                                    }
                                "
                                @mouseleave="
                                    (e) => {
                                        e.currentTarget.style.background = '';
                                        e.currentTarget.style.color = '';
                                        e.currentTarget.style.borderColor = '';
                                    }
                                "
                            >
                                <span class="w-5 h-5 rounded-full flex items-center justify-center text-[9px] font-bold shrink-0 transition-colors group-hover:bg-white/20 group-hover:text-white" :style="grpChipAvatarStyle(grp)">
                                    {{ (p.nome_completo || '?').charAt(0).toUpperCase() }}
                                </span>
                                <span class="max-w-[120px] truncate">{{ (p.nome_completo || '—').split(' ').slice(0, 2).join(' ') }}</span>
                            </button>
                            <span v-if="grp.patients.length > 12" class="inline-flex items-center px-2.5 py-1 rounded-full border border-dashed border-[var(--surface-border)] text-xs text-[var(--text-color-secondary)] font-medium"
                                >+{{ grp.patients.length - 12 }} mais</span
                            >
                        </div>
                    </div>
                </div>
            </TabPanel>
        </TabPanels>
    </Tabs>

    <div class="px-3 md:px-4 pb-5">
        <LoadedPhraseBlock v-if="hasLoaded" />
    </div>

    <!-- ══════════════════════════════════════
       Dialog: Pacientes do grupo
  ═══════════════════════════════════════ -->
    <Dialog
        v-model:visible="grpDialog.open"
        modal
        :draggable="false"
        :style="{ width: '780px', maxWidth: '95vw' }"
        :pt="{
            root: { style: `border: 4px solid ${grpDialogHex()}` },
            header: { style: `border-bottom: 1px solid ${grpDialogHex()}30` }
        }"
        pt:mask:class="backdrop-blur-xs"
    >
        <template #header>
            <div class="flex items-center gap-3">
                <div class="w-9 h-9 rounded-lg flex items-center justify-center text-white font-bold text-base shrink-0" :style="grpColorStyle(grpDialog.group)">
                    {{ (grpDialog.group?.name || '?')[0].toUpperCase() }}
                </div>
                <div>
                    <div class="text-[1rem] font-bold" :style="{ color: grpDialogHex() }">Grupo — {{ grpDialog.group?.name }}</div>
                    <div class="text-[0.72rem] text-[var(--text-color-secondary)]">{{ grpDialog.group?.patients?.length || 0 }} paciente{{ (grpDialog.group?.patients?.length || 0) !== 1 ? 's' : '' }}</div>
                </div>
            </div>
        </template>

        <div class="flex flex-col gap-3">
            <!-- Busca + contador -->
            <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
                <IconField class="w-full sm:w-72">
                    <InputIcon><i class="pi pi-search" /></InputIcon>
                    <InputText v-model="grpDialog.search" placeholder="Buscar paciente..." class="w-full" />
                </IconField>
                <span class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold" :style="{ background: `${grpDialogHex()}18`, color: grpDialogHex() }">{{ grpDialogFiltered.length }} paciente(s)</span>
            </div>

            <!-- Empty -->
            <div v-if="grpDialogFiltered.length === 0" class="py-10 text-center">
                <div class="mx-auto mb-3 grid h-12 w-12 place-items-center rounded-md" :style="{ background: `${grpDialogHex()}18`, color: grpDialogHex() }">
                    <i class="pi pi-users text-xl" />
                </div>
                <div class="font-semibold">Nenhum paciente encontrado</div>
                <div class="mt-1 text-sm text-[var(--text-color-secondary)]">
                    {{ grpDialog.search ? 'Nenhum paciente corresponde à busca.' : 'Este grupo não possui pacientes associados.' }}
                </div>
                <Button v-if="grpDialog.search" class="mt-3 rounded-full" severity="secondary" outlined icon="pi pi-filter-slash" label="Limpar" size="small" @click="grpDialog.search = ''" />
            </div>

            <!-- Tabela -->
            <DataTable v-else :value="grpDialogFiltered" dataKey="id" stripedRows responsiveLayout="scroll" paginator :rows="8" :rowsPerPageOptions="[8, 15, 30]">
                <Column header="Paciente" sortable sortField="nome_completo">
                    <template #body="{ data }">
                        <div class="flex items-center gap-3">
                            <Avatar v-if="data.avatar_url" :image="data.avatar_url" shape="circle" />
                            <Avatar v-else :label="(data.nome_completo || '?').charAt(0).toUpperCase()" shape="circle" :style="{ background: `${grpDialogHex()}25`, color: grpDialogHex() }" />
                            <div class="min-w-0">
                                <div class="font-medium truncate">{{ data.nome_completo }}</div>
                                <div class="text-[0.75rem] text-[var(--text-color-secondary)]">{{ data.email_principal || '—' }}</div>
                            </div>
                        </div>
                    </template>
                </Column>

                <Column header="Telefone" style="min-width: 10rem">
                    <template #body="{ data }">
                        <span class="text-[var(--text-color-secondary)]">{{ data.telefone || '—' }}</span>
                    </template>
                </Column>

                <Column header="Ação" style="width: 9rem">
                    <template #body="{ data }">
                        <Button
                            label="Abrir"
                            icon="pi pi-external-link"
                            size="small"
                            outlined
                            :style="{ borderColor: grpDialogHex(), color: grpDialogHex() }"
                            @click="
                                goEdit(data);
                                grpDialog.open = false;
                            "
                        />
                    </template>
                </Column>

                <template #empty>
                    <div class="py-8 text-center">
                        <i class="pi pi-search text-2xl opacity-20 mb-2 block" />
                        <div class="font-semibold text-sm">Nenhum resultado</div>
                    </div>
                </template>
            </DataTable>
        </div>

        <template #footer>
            <Button label="Fechar" icon="pi pi-times" outlined class="rounded-full" :style="{ borderColor: grpDialogHex(), color: grpDialogHex() }" @click="grpDialog.open = false" />
        </template>
    </Dialog>

    <!-- MODAL: CADASTRO RÁPIDO -->
    <ComponentCadastroRapido v-model="quickDialog" title="Cadastro Rápido" table-name="patients" name-field="nome_completo" email-field="email_principal" phone-field="telefone" @created="onQuickCreated" />

    <PatientProntuario
        :key="selectedPatient?.id || 'none'"
        v-model="prontuarioOpen"
        :patient="selectedPatient"
        @close="closeProntuario"
        @edit="
            (id) => {
                closeProntuario();
                goEdit({ id });
            }
        "
    />

    <ConfirmDialog />

    <!-- ── DIALOG SESSÕES DO PACIENTE ─────────────────────────── -->
    <Dialog v-model:visible="sessoesOpen" modal :draggable="false" :style="{ width: '700px', maxWidth: '96vw' }" :header="sessoesPaciente ? `Sessões — ${sessoesPaciente.nome_completo}` : 'Sessões'">
        <div v-if="sessoesLoading" class="flex justify-center py-8">
            <ProgressSpinner />
        </div>

        <div v-else>
            <!-- Recorrências ativas -->
            <div v-if="recorrencias.length" class="mb-5">
                <div class="text-base font-semibold text-color-secondary mb-2 flex items-center gap-2"><i class="pi pi-sync" /> Recorrências</div>
                <div class="flex flex-col gap-2">
                    <div v-for="r in recorrencias" :key="r.id" class="flex items-center gap-2.5 flex-wrap px-3.5 py-2.5 border border-[var(--surface-border)] rounded-md bg-[var(--surface-ground)] text-base">
                        <Tag :value="r.status === 'ativo' ? 'Ativa' : 'Encerrada'" :severity="r.status === 'ativo' ? 'success' : 'secondary'" />
                        <span class="text-base">{{ fmtRecorrencia(r) }}</span>
                        <span class="text-base text-color-secondary ml-auto"> {{ r.start_date }} {{ r.end_date ? `→ ${r.end_date}` : '(em aberto)' }} </span>
                    </div>
                </div>
            </div>

            <!-- Lista de sessões -->
            <div class="text-base font-semibold text-color-secondary mb-2 flex items-center gap-2"><i class="pi pi-calendar" /> Sessões ({{ sessoesLista.length }})</div>

            <div v-if="sessoesLista.length === 0" class="text-center py-6 text-color-secondary text-base">Nenhuma sessão encontrada para este paciente.</div>

            <div v-else class="flex flex-col gap-2 max-h-[55vh] overflow-y-auto pr-1">
                <div v-for="ev in sessoesLista" :key="ev.id" class="px-3.5 py-2.5 border border-[var(--surface-border)] rounded-md bg-[var(--surface-ground)]">
                    <div class="flex items-center gap-3 flex-wrap">
                        <Tag :value="ev.status || 'agendado'" :severity="statusSessaoSev(ev.status)" />
                        <span class="font-semibold text-base">{{ fmtDataSessao(ev.inicio_em) }}</span>
                        <Tag v-if="ev.modalidade" :value="ev.modalidade" severity="secondary" class="ml-auto" />
                        <span v-if="ev.insurance_plans?.name" class="text-base text-color-secondary flex items-center gap-1">
                            <i class="pi pi-id-card opacity-60" />{{ ev.insurance_plans.name }}
                            <span v-if="ev.insurance_guide_number" class="opacity-70">· Guia: {{ ev.insurance_guide_number }}</span>
                            <span v-if="ev.insurance_value" class="opacity-70">· {{ fmtBRL(ev.insurance_value) }}</span>
                        </span>
                    </div>
                    <div v-if="ev.titulo" class="text-base text-color-secondary mt-1">{{ ev.titulo }}</div>
                </div>
            </div>
        </div>
    </Dialog>
</template>

<style scoped>
/* Transição fade (filtros avançados) */
.fade-enter-active,
.fade-leave-active {
    transition: opacity 0.15s ease;
}
.fade-enter-from,
.fade-leave-to {
    opacity: 0;
}

.pat-datatable :deep(tr.row-new-highlight td) {
    background-color: #f0fdf4 !important;
}
:global(.app-dark) .pat-datatable :deep(tr.row-new-highlight td) {
    background-color: rgba(16, 185, 129, 0.08) !important;
}
</style>
