<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/patients/cadastro/recebidos/CadastrosRecebidosPage.vue
| Data: 2026
| Local: São Carlos/SP — Brasil
|--------------------------------------------------------------------------
| © 2026 — Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { supabase } from '@/lib/supabase/client';
import { useTenantStore } from '@/stores/tenantStore';
// Fase 2 (Graphify hotspot): convertToPatient duplicado em 2 pages — INSERT/UPDATE
// extraídos pro repository pra remover duplicação.
import { createPatient, markIntakeConverted } from '@/features/patients/services/patientsRepository';
import { logError } from '@/support/supportLogger';
import { maybeShowPlanLimitToast } from '@/utils/planLimit';
import { useConfirm } from 'primevue/useconfirm';
import { useToast } from 'primevue/usetoast';
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';

import Avatar from 'primevue/avatar';
import Menu from 'primevue/menu';
import Textarea from 'primevue/textarea';

import { brToISO, isoToBR } from '@/utils/dateBR';

const toast = useToast();
const confirm = useConfirm();
const tenantStore = useTenantStore();

const converting = ref(false);
const loading = ref(false);
const hasLoaded = ref(false);
const rows = ref([]);
const q = ref('');

const dlg = ref({ open: false, saving: false, mode: 'view', item: null, reject_note: '' });

function statusSeverity(s) {
    if (s === 'new') return 'info';
    if (s === 'converted') return 'success';
    if (s === 'rejected') return 'danger';
    return 'secondary';
}

function statusLabel(s) {
    if (s === 'new') return 'Novo';
    if (s === 'converted') return 'Convertido';
    if (s === 'rejected') return 'Rejeitado';
    return s || '—';
}

// ── Field helpers ─────────────────────────────────────────
function pickField(obj, keys) {
    for (const k of keys) {
        const v = obj?.[k];
        if (v !== undefined && v !== null && String(v).trim() !== '') return v;
    }
    return null;
}

const fNome = (i) => pickField(i, ['nome_completo', 'name']);
const fEmail = (i) => pickField(i, ['email_principal', 'email']);
const fEmailAlt = (i) => pickField(i, ['email_alternativo', 'email_alt']);
const fTel = (i) => pickField(i, ['telefone', 'phone']);
const fTelAlt = (i) => pickField(i, ['telefone_alternativo', 'phone_alt']);
const fNasc = (i) => pickField(i, ['data_nascimento', 'birth_date']);
const fGenero = (i) => pickField(i, ['genero', 'gender']);
const fEstadoCivil = (i) => pickField(i, ['estado_civil', 'marital_status']);
const fProf = (i) => pickField(i, ['profissao', 'profession']);
const fNacionalidade = (i) => pickField(i, ['nacionalidade', 'nationality']);
const fNaturalidade = (i) => pickField(i, ['naturalidade', 'place_of_birth']);
const fEscolaridade = (i) => pickField(i, ['escolaridade', 'education_level']);
const fOndeConheceu = (i) => pickField(i, ['onde_nos_conheceu', 'lead_source']);
const fEncaminhado = (i) => pickField(i, ['encaminhado_por', 'referred_by']);
const fCep = (i) => pickField(i, ['cep']);
const fEndereco = (i) => pickField(i, ['endereco', 'address_street']);
const fNumero = (i) => pickField(i, ['numero', 'address_number']);
const fComplemento = (i) => pickField(i, ['complemento', 'address_complement']);
const fBairro = (i) => pickField(i, ['bairro', 'address_neighborhood']);
const fCidade = (i) => pickField(i, ['cidade', 'address_city']);
const fEstado = (i) => pickField(i, ['estado', 'address_state']);
const fPais = (i) => pickField(i, ['pais', 'country']) || 'Brasil';
const fObs = (i) => pickField(i, ['observacoes', 'notes_short']);
const fNotas = (i) => pickField(i, ['notas_internas', 'notes']);

// ── Filtro ────────────────────────────────────────────────
const statusFilter = ref('');

function toggleStatusFilter(s) {
    statusFilter.value = statusFilter.value === s ? '' : s;
}

const filteredRows = computed(() => {
    const term = String(q.value || '')
        .trim()
        .toLowerCase();
    let list = rows.value || [];
    if (statusFilter.value) list = list.filter((r) => r.status === statusFilter.value);
    if (!term) return list;
    return list.filter((r) => {
        const nome = String(fNome(r) || '').toLowerCase();
        const email = String(fEmail(r) || '').toLowerCase();
        const tel = String(fTel(r) || '').toLowerCase();
        return nome.includes(term) || email.includes(term) || tel.includes(term);
    });
});

// ── Avatar ────────────────────────────────────────────────
const AVATAR_BUCKET = 'avatars';

function firstNonEmpty(...vals) {
    for (const v of vals) {
        const s = String(v ?? '').trim();
        if (s) return s;
    }
    return '';
}
function looksLikeUrl(s) {
    return /^https?:\/\//i.test(String(s || ''));
}

function getAvatarUrlFromItem(i) {
    const p = i?.payload || i?.data || i?.form || null;
    const direct = firstNonEmpty(i?.avatar_url, i?.foto_url, i?.photo_url, p?.avatar_url, p?.foto_url, p?.photo_url);
    if (direct && looksLikeUrl(direct)) return direct;
    const path = firstNonEmpty(i?.avatar_path, i?.photo_path, i?.foto_path, i?.avatar_file_path, p?.avatar_path, p?.photo_path, p?.foto_path, p?.avatar_file_path, direct);
    if (!path) return null;
    if (looksLikeUrl(path)) return path;
    const { data } = supabase.storage.from(AVATAR_BUCKET).getPublicUrl(path);
    return data?.publicUrl || null;
}

const avatarCache = new Map();
function avatarUrl(row) {
    const id = row?.id;
    if (!id) return getAvatarUrlFromItem(row);
    if (avatarCache.has(id)) return avatarCache.get(id);
    const url = getAvatarUrlFromItem(row);
    avatarCache.set(id, url);
    return url;
}

const dlgAvatarUrl = computed(() => {
    const item = dlg.value?.item;
    if (!item) return null;
    return avatarUrl(item);
});

// ── Formatters ────────────────────────────────────────────
function dash(v) {
    const s = String(v ?? '').trim();
    return s ? s : '—';
}
function onlyDigits(v) {
    return String(v ?? '').replace(/\D/g, '');
}

function fmtPhoneBR(v) {
    const d = onlyDigits(v);
    if (!d) return '—';
    if (d.length === 11) return `(${d.slice(0, 2)}) ${d.slice(2, 7)}-${d.slice(7, 11)}`;
    if (d.length === 10) return `(${d.slice(0, 2)}) ${d.slice(2, 6)}-${d.slice(6, 10)}`;
    return d;
}

function fmtCPF(v) {
    const d = onlyDigits(v);
    if (!d) return '—';
    if (d.length !== 11) return d;
    return `${d.slice(0, 3)}.${d.slice(3, 6)}.${d.slice(6, 9)}-${d.slice(9, 11)}`;
}

function fmtRG(v) {
    const s = String(v ?? '').trim();
    return s ? s : '—';
}

function fmtBirth(v) {
    if (!v) return '—';
    const s = String(v).trim();
    if (/^\d{2}-\d{2}-\d{4}$/.test(s)) return s;
    if (/^\d{4}-\d{2}-\d{2}/.test(s)) {
        const iso = s.slice(0, 10);
        return isoToBR(iso) || s;
    }
    return s;
}

function fmtDate(iso) {
    if (!iso) return '—';
    const d = new Date(iso);
    if (Number.isNaN(d.getTime())) return String(iso);
    return d.toLocaleString('pt-BR');
}

function normalizeBirthToISO(v) {
    if (!v) return null;
    const s = String(v).trim();
    if (!s) return null;
    if (/^\d{2}-\d{2}-\d{4}$/.test(s)) return brToISO(s);
    if (/^\d{4}-\d{2}-\d{2}/.test(s)) return s.slice(0, 10);
    const d = new Date(s);
    if (Number.isNaN(d.getTime())) return null;
    return `${String(d.getFullYear()).padStart(4, '0')}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}

// ── Tenant / member ───────────────────────────────────────
async function getTenantIdForConversion() {
    return tenantStore?.activeTenantId || tenantStore?.currentTenantId || tenantStore?.tenantId || tenantStore?.tenant?.id || null;
}

async function getResponsibleMemberId(tenantId, userId) {
    const { data, error } = await supabase.from('tenant_members').select('id').eq('tenant_id', tenantId).eq('user_id', userId).eq('status', 'active').maybeSingle();
    if (error) throw error;
    if (!data?.id) throw new Error('Responsible member not found');
    return data.id;
}

// ── Seções do modal ───────────────────────────────────────
const intakeSections = computed(() => {
    const i = dlg.value.item;
    if (!i) return [];
    const section = (title, rows) => ({ title, rows: (rows || []).filter((r) => r && r.value !== undefined) });
    const row = (label, value, opts = {}) => ({ label, value, pre: !!opts.pre });
    return [
        section('Identificação', [
            row('Nome completo', dash(fNome(i))),
            row('Email principal', dash(fEmail(i))),
            row('Email alternativo', dash(fEmailAlt(i))),
            row('Telefone', fmtPhoneBR(fTel(i))),
            row('Telefone alternativo', fmtPhoneBR(fTelAlt(i)))
        ]),
        section('Informações pessoais', [
            row('Data de nascimento', fmtBirth(fNasc(i))),
            row('Gênero', dash(fGenero(i))),
            row('Estado civil', dash(fEstadoCivil(i))),
            row('Profissão', dash(fProf(i))),
            row('Nacionalidade', dash(fNacionalidade(i))),
            row('Naturalidade', dash(fNaturalidade(i))),
            row('Escolaridade', dash(fEscolaridade(i))),
            row('Onde nos conheceu?', dash(fOndeConheceu(i))),
            row('Encaminhado por', dash(fEncaminhado(i)))
        ]),
        section('Documentos', [row('CPF', fmtCPF(i.cpf)), row('RG', fmtRG(i.rg))]),
        section('Endereço', [
            row('CEP', dash(fCep(i))),
            row('Endereço', dash(fEndereco(i))),
            row('Número', dash(fNumero(i))),
            row('Complemento', dash(fComplemento(i))),
            row('Bairro', dash(fBairro(i))),
            row('Cidade', dash(fCidade(i))),
            row('Estado', dash(fEstado(i))),
            row('País', dash(fPais(i)))
        ]),
        section('Observações', [row('Observações', dash(fObs(i)), { pre: true }), row('Notas internas', dash(fNotas(i)), { pre: true })]),
        section('Administração', [
            row('Status', statusLabel(i.status)),
            row('Consentimento', i.consent ? 'Aceito' : 'Não aceito'),
            row('Motivo da rejeição', dash(i.rejected_reason), { pre: true }),
            row('Paciente convertido (ID)', dash(i.converted_patient_id))
        ]),
        section('Metadados', [row('Owner ID', dash(i.owner_id)), row('Token', dash(i.token)), row('Criado em', fmtDate(i.created_at)), row('Atualizado em', fmtDate(i.updated_at)), row('ID do intake', dash(i.id))])
    ];
});

// ── Fetch ─────────────────────────────────────────────────
async function fetchIntakes() {
    loading.value = true;
    try {
        const { data, error } = await supabase.from('patient_intake_requests').select('*').order('created_at', { ascending: false });
        if (error) throw error;
        const weight = (s) => (s === 'new' ? 0 : s === 'converted' ? 1 : s === 'rejected' ? 2 : 9);
        rows.value = (data || []).slice().sort((a, b) => {
            const wa = weight(a.status),
                wb = weight(b.status);
            if (wa !== wb) return wa - wb;
            return new Date(b.created_at || 0).getTime() - new Date(a.created_at || 0).getTime();
        });
        avatarCache.clear();
    } catch (e) {
        logError('patients.recebidos', 'fetchIntakes falhou', e);
        toast.add({ severity: 'error', summary: 'Erro ao carregar', detail: e.message || String(e), life: 3500 });
    } finally {
        loading.value = false;
        hasLoaded.value = true;
    }
}

// ── Dialog ────────────────────────────────────────────────
function openDetails(row) {
    dlg.value.open = true;
    dlg.value.mode = 'view';
    dlg.value.item = row;
    dlg.value.reject_note = row?.rejected_reason || '';
}
function closeDlg() {
    dlg.value.open = false;
    dlg.value.saving = false;
    dlg.value.item = null;
    dlg.value.reject_note = '';
}

// ── Rejeitar ──────────────────────────────────────────────
async function markRejected() {
    const item = dlg.value.item;
    if (!item) return;
    confirm.require({
        message: 'Marcar este cadastro como rejeitado?',
        header: 'Confirmar rejeição',
        icon: 'pi pi-exclamation-triangle',
        acceptLabel: 'Rejeitar',
        rejectLabel: 'Cancelar',
        accept: async () => {
            dlg.value.saving = true;
            try {
                const reason = String(dlg.value.reject_note || '').trim() || null;
                const { error } = await supabase.from('patient_intake_requests').update({ status: 'rejected', rejected_reason: reason, updated_at: new Date().toISOString() }).eq('id', item.id);
                if (error) throw error;
                toast.add({ severity: 'success', summary: 'Rejeitado', detail: 'Solicitação rejeitada.', life: 2500 });
                await fetchIntakes();
                const updated = rows.value.find((r) => r.id === item.id);
                if (updated) openDetails(updated);
            } catch (e) {
                logError('patients.recebidos', 'saveDetails falhou', e);
                toast.add({ severity: 'error', summary: 'Erro', detail: e.message || String(e), life: 3500 });
            } finally {
                dlg.value.saving = false;
            }
        }
    });
}

// ── Converter ─────────────────────────────────────────────
async function convertToPatient() {
    const item = dlg.value?.item;
    if (!item?.id) return;
    if (converting.value) return;
    if (item.status === 'converted') {
        toast.add({ severity: 'warn', summary: 'Atenção', detail: 'Este cadastro já foi convertido em paciente.', life: 3000 });
        return;
    }
    converting.value = true;
    try {
        const { data: userData, error: userErr } = await supabase.auth.getUser();
        if (userErr) throw userErr;
        const ownerId = userData?.user?.id;
        if (!ownerId) throw new Error('Sessão inválida.');

        const tenantId = await getTenantIdForConversion(item);
        if (!tenantId) throw new Error('tenant_id is required');

        const responsibleMemberId = await getResponsibleMemberId(tenantId, ownerId);

        const cleanStr = (v) => {
            const s = String(v ?? '').trim();
            return s ? s : null;
        };
        const digitsOnly = (v) => {
            const d = String(v ?? '').replace(/\D/g, '');
            return d ? d : null;
        };

        const intakeAvatar = cleanStr(item.avatar_url) || cleanStr(item.foto_url) || cleanStr(item.photo_url) || null;

        const patientPayload = {
            responsible_member_id: responsibleMemberId,
            owner_id: ownerId,
            nome_completo: cleanStr(fNome(item)),
            email_principal: cleanStr(fEmail(item))?.toLowerCase() || null,
            email_alternativo: cleanStr(fEmailAlt(item))?.toLowerCase() || null,
            telefone: digitsOnly(fTel(item)),
            telefone_alternativo: digitsOnly(fTelAlt(item)),
            data_nascimento: normalizeBirthToISO(fNasc(item)),
            naturalidade: cleanStr(fNaturalidade(item)),
            genero: cleanStr(fGenero(item)),
            estado_civil: cleanStr(fEstadoCivil(item)),
            cpf: digitsOnly(item.cpf),
            rg: cleanStr(item.rg),
            pais: cleanStr(fPais(item)) || 'Brasil',
            cep: digitsOnly(fCep(item)),
            cidade: cleanStr(fCidade(item)),
            estado: cleanStr(fEstado(item)) || 'SP',
            endereco: cleanStr(fEndereco(item)),
            numero: cleanStr(fNumero(item)),
            bairro: cleanStr(fBairro(item)),
            complemento: cleanStr(fComplemento(item)),
            escolaridade: cleanStr(fEscolaridade(item)),
            profissao: cleanStr(fProf(item)),
            onde_nos_conheceu: cleanStr(fOndeConheceu(item)),
            encaminhado_por: cleanStr(fEncaminhado(item)),
            observacoes: cleanStr(fObs(item)),
            notas_internas: cleanStr(fNotas(item)),
            avatar_url: intakeAvatar
        };
        Object.keys(patientPayload).forEach((k) => {
            if (patientPayload[k] === undefined) delete patientPayload[k];
        });

        // Repository chamadas (Fase 2 — convertToPatient de-dup).
        // patientsRepository.createPatient strip owner_id do payload + sempre injeta auth.uid().
        const created = await createPatient(patientPayload);
        const patientId = created?.id;
        if (!patientId) throw new Error('Falha ao obter ID do paciente criado.');

        await markIntakeConverted(item.id, patientId);

        toast.add({ severity: 'success', summary: 'Convertido', detail: 'Cadastro convertido em paciente.', life: 2500 });
        dlg.value.open = false;
        await fetchIntakes();
    } catch (err) {
        logError('patients.recebidos', 'converter paciente falhou', err);
        if (!maybeShowPlanLimitToast(toast, err, '/admin/pacientes/recebidos')) {
            toast.add({ severity: 'error', summary: 'Falha ao converter', detail: err?.message || 'Não foi possível converter o cadastro.', life: 4500 });
        }
    } finally {
        converting.value = false;
    }
}

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

const totals = computed(() => {
    const all = rows.value || [];
    return {
        total: all.length,
        nNew: all.filter((r) => r.status === 'new').length,
        nConv: all.filter((r) => r.status === 'converted').length,
        nRej: all.filter((r) => r.status === 'rejected').length
    };
});

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

const recMobileMenuRef = ref(null);
const recSearchDlgOpen = ref(false);

const recMobileMenuItems = computed(() => [
    {
        label: 'Buscar',
        icon: 'pi pi-search',
        command: () => {
            recSearchDlgOpen.value = true;
        }
    },
    { separator: true },
    { label: 'Atualizar', icon: 'pi pi-refresh', command: () => fetchIntakes() }
]);

onMounted(() => {
    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);
    fetchIntakes();
});

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

<template>
    <ConfirmDialog />

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

    <!-- ══════════════════════════════════════════════════════
       HERO sticky
  ═══════════════════════════════════════════════════════ -->
    <section
        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 -->
        <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-inbox text-base" />
                </div>
                <div class="min-w-0 hidden lg:block">
                    <div class="flex items-center gap-2">
                        <div class="text-[1rem] font-bold tracking-tight text-[var(--text-color)]">Cadastros recebidos</div>
                        <Tag :value="`${totals.total}`" severity="secondary" />
                    </div>
                    <div class="text-[1rem] text-[var(--text-color-secondary)]">Pré-cadastros externos para avaliar e converter em pacientes</div>
                </div>
            </div>

            <!-- Filtros de status + busca — desktop -->
            <div class="hidden xl:flex flex-1 min-w-0 mx-2 items-center gap-2 flex-wrap">
                <Button type="button" size="small" class="!rounded-full" :outlined="statusFilter !== 'new'" :severity="statusFilter === 'new' ? 'info' : 'secondary'" @click="toggleStatusFilter('new')">
                    <span class="flex items-center gap-1.5"
                        ><i class="pi pi-sparkles text-[1rem]" /> Novos: <b>{{ totals.nNew }}</b></span
                    >
                </Button>
                <Button type="button" size="small" class="!rounded-full" :outlined="statusFilter !== 'converted'" :severity="statusFilter === 'converted' ? 'success' : 'secondary'" @click="toggleStatusFilter('converted')">
                    <span class="flex items-center gap-1.5"
                        ><i class="pi pi-check text-[1rem]" /> Convertidos: <b>{{ totals.nConv }}</b></span
                    >
                </Button>
                <Button type="button" size="small" class="!rounded-full" :outlined="statusFilter !== 'rejected'" :severity="statusFilter === 'rejected' ? 'danger' : 'secondary'" @click="toggleStatusFilter('rejected')">
                    <span class="flex items-center gap-1.5"
                        ><i class="pi pi-times text-[1rem]" /> Rejeitados: <b>{{ totals.nRej }}</b></span
                    >
                </Button>
                <Button v-if="statusFilter" type="button" size="small" class="!rounded-full" severity="secondary" outlined icon="pi pi-filter-slash" label="Limpar" @click="statusFilter = ''" />

                <InputGroup class="w-64 ml-1">
                    <InputGroupAddon><i class="pi pi-search" /></InputGroupAddon>
                    <InputText v-model="q" placeholder="Nome, e-mail ou telefone…" :disabled="loading" />
                    <Button v-if="q" icon="pi pi-times" severity="secondary" @click="q = ''" />
                </InputGroup>
            </div>

            <!-- Ações desktop -->
            <div class="hidden xl:flex items-center gap-1 shrink-0">
                <Button icon="pi pi-refresh" severity="secondary" outlined class="h-9 w-9 rounded-full" :loading="loading" title="Atualizar" @click="fetchIntakes" />
            </div>

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

    <!-- Dialog busca mobile -->
    <Dialog v-model:visible="recSearchDlgOpen" modal :draggable="false" header="Buscar cadastro" class="w-[94vw] max-w-sm">
        <div class="pt-1">
            <InputGroup>
                <InputGroupAddon><i class="pi pi-search" /></InputGroupAddon>
                <InputText v-model="q" placeholder="Nome, e-mail ou telefone…" autofocus />
                <Button v-if="q" icon="pi pi-times" severity="secondary" @click="q = ''" />
            </InputGroup>
        </div>
        <template #footer>
            <Button label="Fechar" severity="secondary" outlined class="rounded-full" @click="recSearchDlgOpen = false" />
        </template>
    </Dialog>

    <!-- ══════════════════════════════════════════════════════
       QUICK-STATS
  ═══════════════════════════════════════════════════════ -->
    <div class="grid grid-cols-2 md:grid-cols-4 gap-2 px-3 md:px-4 mb-3">
        <template v-if="loading">
            <Skeleton v-for="n in 4" :key="n" height="3.5rem" class="rounded-md" />
        </template>
        <template v-else>
            <div
                class="flex flex-col gap-0.5 px-4 py-2.5 rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] flex-1 min-w-[72px] 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)]': statusFilter === '' }"
                @click="toggleStatusFilter('')"
            >
                <div class="text-[1.35rem] font-bold leading-none text-[var(--text-color)]">{{ totals.total }}</div>
                <div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 truncate">Total</div>
            </div>

            <div
                class="flex flex-col gap-0.5 px-4 py-2.5 rounded-md border flex-1 min-w-[72px] cursor-pointer select-none transition-[border-color,box-shadow,background] duration-150 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
                :class="statusFilter === 'new' ? 'border-sky-500 bg-sky-500/5 shadow-[0_0_0_3px_rgba(14,165,233,0.15)]' : 'border-sky-400/30 bg-sky-500/5 hover:border-sky-400/60'"
                @click="toggleStatusFilter('new')"
            >
                <div class="text-[1.35rem] font-bold leading-none text-sky-500">{{ totals.nNew }}</div>
                <div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 truncate">Novos</div>
            </div>

            <div
                class="flex flex-col gap-0.5 px-4 py-2.5 rounded-md border flex-1 min-w-[72px] cursor-pointer select-none transition-[border-color,box-shadow,background] duration-150 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
                :class="statusFilter === 'converted' ? '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="toggleStatusFilter('converted')"
            >
                <div class="text-[1.35rem] font-bold leading-none text-green-500">{{ totals.nConv }}</div>
                <div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 truncate">Convertidos</div>
            </div>

            <div
                class="flex flex-col gap-0.5 px-4 py-2.5 rounded-md border flex-1 min-w-[72px] cursor-pointer select-none transition-[border-color,box-shadow,background] duration-150 hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]"
                :class="statusFilter === 'rejected' ? '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="toggleStatusFilter('rejected')"
            >
                <div class="text-[1.35rem] font-bold leading-none text-red-500">{{ totals.nRej }}</div>
                <div class="text-[1rem] text-[var(--text-color-secondary)] opacity-75 truncate">Rejeitados</div>
            </div>
        </template>
    </div>

    <!-- ══════════════════════════════════════════════════════
       TABELA — desktop (md+)
  ═══════════════════════════════════════════════════════ -->
    <div class="hidden md:block mx-3 md:mx-4 mb-3">
        <div class="rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] overflow-hidden">
            <!-- Cabeçalho da seção -->
            <div class="flex items-center justify-between px-4 py-3 border-b border-[var(--surface-border,#e2e8f0)]">
                <div class="flex items-center gap-2">
                    <i class="pi pi-table text-[var(--text-color-secondary)] opacity-60" />
                    <span class="font-semibold text-[1rem]">Lista de cadastros</span>
                    <span v-if="statusFilter" class="inline-flex items-center px-1.5 py-0.5 rounded text-[0.65rem] font-semibold bg-indigo-500/10 text-indigo-600"> Filtrado: {{ statusLabel(statusFilter) }} </span>
                </div>
                <span class="inline-flex items-center justify-center min-w-[22px] h-[22px] px-1.5 rounded-full bg-[var(--primary-color,#6366f1)] text-white text-[1rem] font-bold">
                    {{ filteredRows.length }}
                </span>
            </div>

            <DataTable :value="filteredRows" :loading="loading" dataKey="id" paginator :rows="10" :rowsPerPageOptions="[10, 20, 50]" stripedRows class="rec-datatable" :rowClass="(r) => (isRecent(r) ? 'row-new-highlight' : '')">
                <Column header="Status" style="width: 10rem">
                    <template #body="{ data }">
                        <Tag :value="statusLabel(data.status)" :severity="statusSeverity(data.status)" />
                    </template>
                </Column>

                <Column header="Paciente">
                    <template #body="{ data }">
                        <div class="flex items-center gap-3 min-w-0">
                            <Avatar v-if="avatarUrl(data)" :image="avatarUrl(data)" shape="circle" />
                            <Avatar v-else icon="pi pi-user" shape="circle" />
                            <div class="min-w-0">
                                <div class="font-medium truncate">{{ fNome(data) || '—' }}</div>
                                <div class="text-[var(--text-color-secondary)] text-[1rem] truncate">{{ fEmail(data) || '—' }}</div>
                            </div>
                        </div>
                    </template>
                </Column>

                <Column header="Contato" style="width: 13rem">
                    <template #body="{ data }">
                        <div class="text-[1rem]">
                            <div class="font-medium">{{ fmtPhoneBR(fTel(data)) }}</div>
                            <div class="text-[var(--text-color-secondary)]">{{ fTelAlt(data) ? fmtPhoneBR(fTelAlt(data)) : '—' }}</div>
                        </div>
                    </template>
                </Column>

                <Column header="Criado em" style="width: 13rem">
                    <template #body="{ data }">
                        <span class="text-[var(--text-color-secondary)] text-[1rem]">{{ fmtDate(data.created_at) }}</span>
                    </template>
                </Column>

                <Column header="" style="width: 9rem; text-align: right">
                    <template #body="{ data }">
                        <Button icon="pi pi-eye" label="Ver" severity="secondary" outlined @click="openDetails(data)" />
                    </template>
                </Column>

                <template #empty>
                    <div class="py-10 text-center">
                        <div class="mx-auto mb-3 grid h-12 w-12 place-items-center rounded-md bg-indigo-500/10 text-indigo-500">
                            <i class="pi pi-inbox text-xl" />
                        </div>
                        <div class="font-semibold text-[var(--text-color)]">Nenhum cadastro encontrado</div>
                        <div class="mt-1 text-[1rem] text-[var(--text-color-secondary)]">
                            {{ q || statusFilter ? 'Tente limpar os filtros ou mudar o termo de busca.' : 'Ainda não há cadastros recebidos.' }}
                        </div>
                        <div v-if="q || statusFilter" class="mt-4 flex justify-center gap-2">
                            <Button
                                severity="secondary"
                                outlined
                                icon="pi pi-filter-slash"
                                label="Limpar filtros"
                                @click="
                                    q = '';
                                    statusFilter = '';
                                "
                            />
                        </div>
                    </div>
                </template>
            </DataTable>
        </div>
    </div>

    <!-- ══════════════════════════════════════════════════════
       CARDS — mobile (<md)
  ═══════════════════════════════════════════════════════ -->
    <div class="md:hidden mx-3 mb-5">
        <div v-if="loading" class="flex flex-col gap-2.5">
            <div v-for="n in 5" :key="n" class="rounded-md border border-[var(--surface-border)] bg-[var(--surface-card)] p-3.5 flex flex-col gap-3">
                <div class="flex items-center gap-3">
                    <Skeleton shape="circle" size="2.5rem" />
                    <div class="flex flex-col gap-1.5 flex-1">
                        <Skeleton width="55%" height="13px" />
                        <Skeleton width="40%" height="11px" />
                    </div>
                    <Skeleton width="60px" height="22px" border-radius="999px" />
                </div>
                <div class="flex items-center justify-between">
                    <Skeleton width="30%" height="11px" />
                    <Skeleton width="60px" height="28px" border-radius="999px" />
                </div>
            </div>
        </div>

        <div v-else-if="filteredRows.length === 0" class="rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] py-10 text-center">
            <div class="mx-auto mb-3 grid h-12 w-12 place-items-center rounded-md bg-indigo-500/10 text-indigo-500">
                <i class="pi pi-inbox text-xl" />
            </div>
            <div class="font-semibold">Nenhum cadastro encontrado</div>
            <div class="mt-1 text-[1rem] text-[var(--text-color-secondary)]">
                {{ q || statusFilter ? 'Tente limpar os filtros ou mudar o termo de busca.' : 'Ainda não há cadastros recebidos.' }}
            </div>
            <div v-if="q || statusFilter" class="mt-4 flex justify-center gap-2">
                <Button
                    severity="secondary"
                    outlined
                    icon="pi pi-filter-slash"
                    label="Limpar filtros"
                    @click="
                        q = '';
                        statusFilter = '';
                    "
                />
            </div>
        </div>

        <div v-else class="flex flex-col gap-2.5">
            <div
                v-for="row in filteredRows"
                :key="row.id"
                class="rounded-md border p-3.5 transition-colors"
                :class="isRecent(row) ? 'border-emerald-300 bg-emerald-50 dark:bg-emerald-950/30 dark:border-emerald-800' : 'border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)]'"
            >
                <div class="flex items-center gap-3">
                    <Avatar v-if="avatarUrl(row)" :image="avatarUrl(row)" shape="circle" />
                    <Avatar v-else icon="pi pi-user" shape="circle" />
                    <div class="flex-1 min-w-0">
                        <div class="font-semibold truncate">{{ fNome(row) || '—' }}</div>
                        <div class="text-[1rem] text-[var(--text-color-secondary)] truncate">{{ fEmail(row) || '—' }}</div>
                    </div>
                    <Tag :value="statusLabel(row.status)" :severity="statusSeverity(row.status)" />
                </div>
                <div class="mt-2.5 flex items-center justify-between gap-2">
                    <div class="text-[1rem] text-[var(--text-color-secondary)] flex flex-col gap-0.5">
                        <span>{{ fmtPhoneBR(fTel(row)) }}</span>
                        <span>{{ fmtDate(row.created_at) }}</span>
                    </div>
                    <Button icon="pi pi-eye" label="Ver" severity="secondary" outlined size="small" @click="openDetails(row)" />
                </div>
            </div>
        </div>
    </div>

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

    <!-- ══════════════════════════════════════════════════════
       MODAL detalhe
  ═══════════════════════════════════════════════════════ -->
    <Dialog v-model:visible="dlg.open" modal :header="null" :style="{ width: 'min(940px, 96vw)' }" :contentStyle="{ padding: 0 }" :draggable="false" @hide="closeDlg">
        <div v-if="dlg.item" class="relative">
            <div class="max-h-[70vh] overflow-auto p-5 bg-[var(--surface-ground,#f8fafc)]">
                <!-- Topo: avatar + nome + status -->
                <div class="rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] p-4 mb-4">
                    <div class="flex flex-col items-center text-center gap-3">
                        <div class="relative">
                            <div class="absolute inset-0 blur-2xl opacity-30 rounded-full bg-slate-300" />
                            <div class="relative">
                                <Avatar v-if="dlgAvatarUrl" :image="dlgAvatarUrl" alt="avatar" shape="circle" size="xlarge" />
                                <Avatar v-else icon="pi pi-user" shape="circle" size="xlarge" />
                            </div>
                        </div>
                        <div class="min-w-0">
                            <div class="text-xl font-semibold text-[var(--text-color)] truncate">{{ fNome(dlg.item) || '—' }}</div>
                            <div class="text-[var(--text-color-secondary)] text-[1rem] truncate">{{ fEmail(dlg.item) || '—' }} · {{ fmtPhoneBR(fTel(dlg.item)) }}</div>
                        </div>
                        <div class="flex flex-wrap justify-center gap-2">
                            <Tag :value="statusLabel(dlg.item.status)" :severity="statusSeverity(dlg.item.status)" />
                            <Tag :value="dlg.item.consent ? 'Consentimento OK' : 'Sem consentimento'" :severity="dlg.item.consent ? 'success' : 'danger'" />
                            <Tag :value="`Criado: ${fmtDate(dlg.item.created_at)}`" severity="secondary" />
                        </div>
                    </div>
                </div>

                <!-- Seções de dados -->
                <div class="grid grid-cols-1 md:grid-cols-2 gap-3.5">
                    <div v-for="(sec, sidx) in intakeSections" :key="sidx" class="rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] p-4">
                        <div class="font-semibold text-[var(--text-color)] text-[0.88rem] mb-3">{{ sec.title }}</div>
                        <div class="grid grid-cols-1 sm:grid-cols-2 gap-2.5">
                            <div v-for="(r, ridx) in sec.rows" :key="ridx" class="min-w-0">
                                <div class="text-[0.68rem] text-[var(--text-color-secondary)] opacity-60 mb-0.5 uppercase tracking-[0.04em]">{{ r.label }}</div>
                                <div class="text-[1rem] text-[var(--text-color)]" :class="r.pre ? 'whitespace-pre-wrap leading-relaxed' : 'truncate'">{{ r.value }}</div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Rejeição -->
                <div class="mt-4 rounded-md border border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)] p-4">
                    <div class="flex items-center justify-between gap-2 flex-wrap mb-3">
                        <div class="font-semibold text-[var(--text-color)] text-[0.88rem]">Rejeição</div>
                        <Tag :value="dlg.item.status === 'rejected' ? 'Este cadastro já foi rejeitado' : 'Opcional'" :severity="dlg.item.status === 'rejected' ? 'danger' : 'secondary'" />
                    </div>
                    <label class="block text-[1rem] text-[var(--text-color-secondary)] mb-2">Motivo (anotação interna)</label>
                    <Textarea v-model="dlg.reject_note" autoResize rows="2" class="w-full" :disabled="dlg.saving || converting" placeholder="Ex.: dados incompletos, pediu para não seguir, duplicado…" />
                </div>

                <div class="h-24" />
            </div>

            <!-- Ações fixas no rodapé -->
            <div class="sticky bottom-0 z-10 border-t border-[var(--surface-border,#e2e8f0)] bg-[var(--surface-card,#fff)]">
                <div class="px-5 py-4 flex flex-col sm:flex-row gap-3 sm:items-center sm:justify-between">
                    <div class="flex items-center gap-2">
                        <Tag :value="statusLabel(dlg.item.status)" :severity="statusSeverity(dlg.item.status)" />
                    </div>
                    <div class="flex gap-2 justify-end flex-wrap">
                        <Button label="Rejeitar" icon="pi pi-times" severity="danger" outlined :disabled="dlg.saving || dlg.item.status === 'rejected' || converting" @click="markRejected" />
                        <Button label="Converter" icon="pi pi-check" severity="success" :loading="converting" :disabled="dlg.item.status === 'converted' || dlg.saving || converting" @click="convertToPatient" />
                        <Button label="Fechar" icon="pi pi-times-circle" severity="secondary" outlined :disabled="dlg.saving || converting" @click="closeDlg" />
                    </div>
                </div>
            </div>
        </div>
    </Dialog>
</template>
