<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/patients/cadastro/PatientsCadastroPage.vue
| Data: 2026 · São Carlos/SP — Brasil
|--------------------------------------------------------------------------
| Engenharia reversa da tela de detalhe (PatientsDetailPage):
|
| SEÇÃO 0 — Identidade (indigo)
|   nome_completo    → header: "Mariana Lima"
|   nome_social      → card Dados pessoais: "Nome social"         [NOVO]
|   pronomes         → header: "ela/dela" + card Dados pessoais   [NOVO]
|   data_nascimento  → header: "32 anos"
|   genero           → card Dados pessoais: "Gênero"
|   estado_civil     → card Dados pessoais: "Estado civil"
|   cpf              → card Dados pessoais: mascarado ••••456••••90
|   rg               → card Dados pessoais
|   etnia            → card Dados pessoais: "Etnia"               [NOVO]
|   naturalidade     → card Dados pessoais
|   profissao        → card Dados pessoais: "Profissão"
|   escolaridade     → card Dados pessoais: "Escolaridade"
|   telefone         → card Contato: "WhatsApp (16) 99123-4567"
|   email_principal  → card Contato: "mariana@email.com"
|   canal_preferido  → card Contato: "Canal preferido: WhatsApp" [NOVO]
|   horario_contato  → card Contato: "Horário: 08h–18h"          [NOVO]
|
| SEÇÃO 1 — Endereço (teal)
|   cep              → card Contato: "13560-000 · São Carlos"
|   cidade / estado  → header: "São Carlos, SP"
|
| SEÇÃO 2 — Clínico & origem (violet)
|   status           → badge verde "Ativa"                        [NOVO]
|   convenio         → badge azul "Unimed"                        [NOVO]
|   patient_scope    → badge cinza "Clínica"                      [NOVO]
|   tags[]           → chips coloridos "Ansiedade", "TCC"
|   grupo            → define modelo de anamnese
|   onde_nos_conheceu → card Origem: "Como chegou: Indicação"
|   encaminhado_por  → card Origem: "Dr. Roberto (psiq.)"
|   metodo_pagamento → card Origem: "Método de pag.: PIX"         [NOVO]
|   motivo_saida     → card Origem: "Motivo de saída"             [NOVO]
|
| SEÇÃO 3 — Rede de suporte (amber)
|   patient_support_contacts[] → card "Contatos & rede de suporte" [NOVO]
|   cada item: nome, relacao, tipo, telefone, email, is_primario
|   is_primario = true → badge vermelho "emergência"
|
| SEÇÃO 4 — Responsável (sky)
|   nome_responsavel, cpf_responsavel, telefone_responsavel, etc.
|
| SEÇÃO 5 — Anotações internas (rose)
|   notas_internas   → campo interno, não vai ao paciente
|--------------------------------------------------------------------------
-->
<script setup>
import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
import { useRoute, useRouter }  from 'vue-router'
import { useRoleGuard }         from '@/composables/useRoleGuard'
import { useToast }             from 'primevue/usetoast'
import { useConfirm }           from 'primevue/useconfirm'
import { supabase }             from '@/lib/supabase/client'
import { useTenantStore }       from '@/stores/tenantStore'
import { logError }             from '@/support/supportLogger'
import { digitsOnly, fmtCPF, fmtRG, fmtPhone, toISODate, generateCPF } from '@/utils/validators'
import CadastroRapidoConvenio from '@/components/CadastroRapidoConvenio.vue'
import CadastroRapidoMedico   from '@/components/CadastroRapidoMedico.vue'
import ContactPhonesEditor    from '@/components/ui/ContactPhonesEditor.vue'
import ContactEmailsEditor    from '@/components/ui/ContactEmailsEditor.vue'

// V#9 — composables/repo da feature pacientes (extração da página gigante)
import { useCep } from '@/features/patients/composables/useCep'
import { usePatientSupportContacts } from '@/features/patients/composables/usePatientSupportContacts'
// Fase 2b — LGPD export (Art. 18, II)
import { useLgpdExport } from '@/composables/useLgpdExport'
// Fase 5b-7 — drawer global de conversa
import { useConversationDrawerStore } from '@/stores/conversationDrawerStore'
import {
    listGroups as repoListGroups,
    listTags as repoListTags,
    getPatientById as repoGetPatientById,
    createPatient as repoCreatePatient,
    updatePatient as repoUpdatePatient,
    getPatientRelations as repoGetPatientRelations,
    replacePatientGroup as repoReplacePatientGroup,
    replacePatientTags as repoReplacePatientTags
} from '@/features/patients/services/patientsRepository'

// ─────────────────────────────────────────────────────────
// Props / emits
// ─────────────────────────────────────────────────────────
const props = defineProps({
  dialogMode: { type: Boolean, default: false },
  patientId:  { type: String,  default: null  },
})
const emit = defineEmits(['cancel', 'created'])

// ─────────────────────────────────────────────────────────
// Infra
// ─────────────────────────────────────────────────────────
const { canSee }  = useRoleGuard()
const route       = useRoute()
const router      = useRouter()
const toast       = useToast()
const confirm     = useConfirm()
const tenantStore = useTenantStore()

async function getCurrentTenantId () {
  return tenantStore.tenantId || tenantStore.currentTenantId || tenantStore.tenant?.id
}

// Helper sync para blindar queries com .eq('tenant_id', ...) — defesa em profundidade.
function currentTenantId () {
  return tenantStore.activeTenantId || tenantStore.tenantId || tenantStore.tenant?.id || null
}
async function getCurrentMemberId (tenantId) {
  const { data: a, error: ae } = await supabase.auth.getUser(); if (ae) throw ae
  const uid = a?.user?.id; if (!uid) throw new Error('Sessão inválida.')
  const { data, error } = await supabase.from('tenant_members').select('id')
    .eq('tenant_id', tenantId).eq('user_id', uid).eq('status', 'active').single()
  if (error) throw error
  if (!data?.id) throw new Error('Responsible member not found')
  return data.id
}
async function getOwnerId () {
  const { data, error } = await supabase.auth.getUser(); if (error) throw error
  const uid = data?.user?.id; if (!uid) throw new Error('Sessão inválida.'); return uid
}
async function resolveTenantContextOrFail () {
  const { data: a, error: ae } = await supabase.auth.getUser(); if (ae) throw ae
  const uid = a?.user?.id; if (!uid) throw new Error('Sessão inválida.')
  const storeTid = await getCurrentTenantId()
  if (storeTid) {
    try { const mid = await getCurrentMemberId(storeTid); return { tenantId: storeTid, memberId: mid } } catch (_) {}
  }
  const { data, error } = await supabase.from('tenant_members').select('id, tenant_id')
    .eq('user_id', uid).eq('status', 'active').order('created_at', { ascending: false }).limit(1).single()
  if (error) throw error
  if (!data?.tenant_id || !data?.id) throw new Error('Responsible member not found')
  return { tenantId: data.tenant_id, memberId: data.id }
}

// ─────────────────────────────────────────────────────────
// Route helpers
// ─────────────────────────────────────────────────────────
const patientId = computed(() =>
  props.dialogMode
    ? (props.patientId || null)
    : (String(route.params?.id || '').trim() || null)
)
const isEdit = computed(() => !!patientId.value)

// ─────────────────────────────────────────────────────────
// LGPD export (Art. 18, II - portabilidade)
// ─────────────────────────────────────────────────────────
const lgpdDialog = ref(false)
const { loading: lgpdExporting, exportJSON: lgpdExportJSON, exportPDF: lgpdExportPDF } = useLgpdExport()

async function onLgpdExport(format) {
  if (!isEdit.value || !patientId.value) return
  const name = form.nome_completo || 'paciente'
  try {
    if (format === 'json') {
      await lgpdExportJSON(patientId.value, name)
    } else {
      await lgpdExportPDF(patientId.value, name, '')
    }
    toast.add({ severity: 'success', summary: 'Export LGPD gerado', detail: `Arquivo ${format.toUpperCase()} baixado. O evento foi registrado na auditoria.`, life: 5000 })
    lgpdDialog.value = false
  } catch (err) {
    const msg = err?.message || String(err)
    toast.add({ severity: 'error', summary: 'Falha no export LGPD', detail: msg, life: 6000 })
    logError('PatientsCadastroPage.onLgpdExport', msg, err)
  }
}

// Fase 5b-7 — Abre drawer global (não navega)
const conversationDrawer = useConversationDrawerStore()
function goToConversation() {
  if (!isEdit.value || !patientId.value) return
  conversationDrawer.openForPatient(patientId.value)
}

function getAreaKey () {
  const seg = String(route.path || '').split('/').filter(Boolean)[0] || 'admin'
  return seg === 'therapist' ? 'therapist' : 'admin'
}
function getPatientsRoutes () {
  const area = getAreaKey()
  if (area === 'therapist') return { listName: 'therapist-patients', listPath: '/therapist/patients' }
  return { listName: 'admin-pacientes', listPath: '/admin/pacientes' }
}
async function safePush (toNameObj, fallbackPath) {
  try { const r = router.resolve(toNameObj); if (r?.matched?.length) return router.push(toNameObj) } catch (_) {}
  return router.push(fallbackPath)
}
function goBack () {
  if (props.dialogMode) { emit('cancel'); return }
  const { listName, listPath } = getPatientsRoutes()
  if (window.history.length > 1) router.back()
  else safePush({ name: listName }, listPath)
}

// ─────────────────────────────────────────────────────────
// Accordion + nav
// ─────────────────────────────────────────────────────────
const activeValue     = ref('0')
const panelHeaderRefs = ref([])

function setPanelHeaderRef (el, idx) {
  if (!el) return
  panelHeaderRefs.value[idx] = el
}

async function openPanel (i) {
  activeValue.value = String(i)
  await nextTick()
  const headerRef = panelHeaderRefs.value?.[i]
  const el = headerRef?.$el ?? headerRef
  if (!el) return
  const sc = el.closest('.l2-main') || document.querySelector('.l2-main')
  if (sc) {
    const off = el.getBoundingClientRect().top - sc.getBoundingClientRect().top + sc.scrollTop - 16
    sc.scrollTo({ top: Math.max(0, off), behavior: 'smooth' })
  } else if (typeof el.scrollIntoView === 'function') {
    el.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
  }
}

// Definição das seções — accent é a chave para a paleta abaixo
const sections = [
  { value: '0', label: 'Identidade',          sub: 'Nome · pronomes · CPF · contato',          icon: 'pi pi-user',       accent: 'indigo' },
  { value: '1', label: 'Endereço',            sub: 'CEP · cidade/UF no header',                icon: 'pi pi-map-marker', accent: 'teal'   },
  { value: '2', label: 'Clínico & origem',    sub: 'Badges · tags · convênio · como chegou',   icon: 'pi pi-heart',      accent: 'violet' },
  { value: '3', label: 'Rede de suporte',     sub: 'Card "Contatos & rede de suporte"',        icon: 'pi pi-users',      accent: 'amber'  },
  { value: '4', label: 'Responsável',         sub: 'Para menores ou cobrança em terceiro',     icon: 'pi pi-id-card',    accent: 'sky'    },
  { value: '5', label: 'Anotações internas',  sub: 'Visível apenas para a equipe',             icon: 'pi pi-lock',       accent: 'rose'   },
]

// Paleta clara por accent
const pal = {
  indigo: {
    panelBorder: 'border-indigo-100',
    bg:          'bg-indigo-50/60',
    iconBox:     'bg-indigo-100 text-indigo-600',
    activeBtn:   'bg-indigo-500/10 border-indigo-300/50 text-indigo-700',
    divTxt:      'text-indigo-500',
    divLine:     'bg-indigo-200/50',
    hint:        'text-indigo-500/80',
    infoBox:     'bg-indigo-50 border-indigo-200/50 text-indigo-700',
  },
  teal: {
    panelBorder: 'border-teal-100',
    bg:          'bg-teal-50/60',
    iconBox:     'bg-teal-100 text-teal-600',
    activeBtn:   'bg-teal-500/10 border-teal-300/50 text-teal-700',
    divTxt:      'text-teal-500',
    divLine:     'bg-teal-200/50',
    hint:        'text-teal-600/80',
    infoBox:     'bg-teal-50 border-teal-200/50 text-teal-700',
  },
  violet: {
    panelBorder: 'border-violet-100',
    bg:          'bg-violet-50/60',
    iconBox:     'bg-violet-100 text-violet-600',
    activeBtn:   'bg-violet-500/10 border-violet-300/50 text-violet-700',
    divTxt:      'text-violet-500',
    divLine:     'bg-violet-200/50',
    hint:        'text-violet-500/80',
    infoBox:     'bg-violet-50 border-violet-200/50 text-violet-700',
  },
  amber: {
    panelBorder: 'border-amber-100',
    bg:          'bg-amber-50/60',
    iconBox:     'bg-amber-100 text-amber-700',
    activeBtn:   'bg-amber-500/10 border-amber-300/50 text-amber-700',
    divTxt:      'text-amber-500',
    divLine:     'bg-amber-200/50',
    hint:        'text-amber-600/80',
    infoBox:     'bg-amber-50 border-amber-200/50 text-amber-700',
  },
  sky: {
    panelBorder: 'border-sky-100',
    bg:          'bg-sky-50/60',
    iconBox:     'bg-sky-100 text-sky-600',
    activeBtn:   'bg-sky-500/10 border-sky-300/50 text-sky-700',
    divTxt:      'text-sky-500',
    divLine:     'bg-sky-200/50',
    hint:        'text-sky-500/80',
    infoBox:     'bg-sky-50 border-sky-200/50 text-sky-700',
  },
  rose: {
    panelBorder: 'border-rose-100',
    bg:          'bg-rose-50/60',
    iconBox:     'bg-rose-100 text-rose-600',
    activeBtn:   'bg-rose-500/10 border-rose-300/50 text-rose-700',
    divTxt:      'text-rose-500',
    divLine:     'bg-rose-200/50',
    hint:        'text-rose-500/80',
    infoBox:     'bg-rose-50 border-rose-200/50 text-rose-700',
  },
}

// Nav popover + responsivo
const navPopover     = ref(null)
const isCompact      = ref(false)
let mql = null, mqlCb = null

// View mode: 'vertical' (Accordion) | 'horizontal' (Tabs)
const VIEW_MODE_KEY = 'pcd.viewMode.v1'
const viewMode = ref('vertical')
try {
  const saved = localStorage.getItem(VIEW_MODE_KEY)
  if (saved === 'vertical' || saved === 'horizontal') viewMode.value = saved
} catch (_) {}
watch(viewMode, (v) => { try { localStorage.setItem(VIEW_MODE_KEY, v) } catch (_) {} })
function setViewMode (m) { if (m === 'vertical' || m === 'horizontal') viewMode.value = m }

function syncCompact ()  { isCompact.value = !!mql?.matches }
function toggleNav (e)   { navPopover.value?.toggle(e) }
function selectNav (s)   { openPanel(Number(s.value)); navPopover.value?.hide() }
const selectedSection = computed(() => sections.find(s => s.value === activeValue.value) || sections[0])

onMounted(() => {
  mql   = window.matchMedia('(max-width: 1199px)'); syncCompact()
  mqlCb = () => syncCompact()
  if (mql.addEventListener) mql.addEventListener('change', mqlCb)
  else mql.addListener(mqlCb)
})
onBeforeUnmount(() => {
  if (!mql || !mqlCb) return
  if (mql.removeEventListener) mql.removeEventListener('change', mqlCb)
  else mql.removeListener(mqlCb)
})

// ─────────────────────────────────────────────────────────
// Avatar
// ─────────────────────────────────────────────────────────
const avatarFile       = ref(null)
const avatarPreviewUrl = ref('')
const avatarUploading  = ref(false)
const AVATAR_BUCKET    = 'avatars'

function isImg (f) { return !!f && typeof f.type === 'string' && f.type.startsWith('image/') }
function ext  (f) { const n = String(f?.name||''); const e = n.includes('.')?n.split('.').pop():''; return String(e||'').toLowerCase().replace(/[^a-z0-9]/g,'')||'png' }
function revokePreview () {
  if (avatarPreviewUrl.value?.startsWith('blob:')) { try { URL.revokeObjectURL(avatarPreviewUrl.value) } catch (_) {} }
  avatarPreviewUrl.value = ''
}
function onAvatarPicked (ev) {
  const file = ev?.target?.files?.[0]||null; avatarFile.value=null; revokePreview()
  if (!file) return
  if (!isImg(file)) { toast.add({ severity:'warn', summary:'Avatar', detail:'Use PNG/JPG/WebP.', life:3000 }); return }
  avatarFile.value = file; avatarPreviewUrl.value = URL.createObjectURL(file)
}
async function getReadableAvatarUrl (path) {
  try { const { data: p } = supabase.storage.from(AVATAR_BUCKET).getPublicUrl(path); if (p?.publicUrl) return p.publicUrl } catch (_) {}
  const { data, error } = await supabase.storage.from(AVATAR_BUCKET).createSignedUrl(path, 60*60*24*7)
  if (error) throw error; return data.signedUrl
}
async function uploadAvatarToStorage ({ ownerId, patientId: pid, file }) {
  if (file.size > 3*1024*1024) throw new Error('Imagem até 3MB.')
  const path = `owners/${ownerId}/patients/${pid}/avatar.${ext(file)}`
  const { error } = await supabase.storage.from(AVATAR_BUCKET).upload(path, file, { upsert:true, cacheControl:'3600', contentType:file.type||'image/*' })
  if (error) throw error; return { publicUrl: await getReadableAvatarUrl(path) }
}
async function maybeUploadAvatar (ownerId, id) {
  if (!avatarFile.value) return null
  avatarUploading.value = true
  try {
    const { publicUrl } = await uploadAvatarToStorage({ ownerId, patientId: id, file: avatarFile.value })
    form.value.avatar_url = publicUrl; avatarFile.value=null; revokePreview(); avatarPreviewUrl.value=publicUrl
    await updatePatient(id, { avatar_url: publicUrl }); return publicUrl
  } catch (e) { toast.add({ severity:'warn', summary:'Avatar', detail:e?.message||'Falha no upload.', life:4500 }); return null }
  finally { avatarUploading.value=false }
}

// ─────────────────────────────────────────────────────────
// Form state
// ─────────────────────────────────────────────────────────
function resetForm () {
  return {
    // Identidade — alimenta header + card Dados pessoais + card Contato
    nome_completo: '', nome_social: '', pronomes: '',
    data_nascimento: '', genero: '', estado_civil: '',
    cpf: '', rg: '', naturalidade: '', etnia: '',
    profissao: '', escolaridade: '',
    // Contato
    telefone: '', email_principal: '',
    email_alternativo: '', telefone_alternativo: '',
    canal_preferido: '', horario_contato: '',
    // Endereço — cep + cidade → card Contato + header
    cep: '', pais: 'Brasil', cidade: '', estado: 'SP',
    endereco: '', numero: '', bairro: '', complemento: '',
    // Clínico — badges no header
    status: 'Ativo', convenio: '', patient_scope: '',
    // Origem — card Origem
    onde_nos_conheceu: '', encaminhado_por: '',
    motivo_saida: '',
    // Responsável
    nome_responsavel: '', cpf_responsavel: '',
    telefone_responsavel: '', observacao_responsavel: '',
    cobranca_no_responsavel: false,
    // Interno
    observacoes: '', notas_internas: '', avatar_url: '',
  }
}
const form = ref(resetForm())

// ── Helpers ───────────────────────────────────────────────
function parseDDMMYYYY (s) {
  const str = String(s||'').trim()
  const m = /^(\d{2})-(\d{2})-(\d{4})$/.exec(str); if (!m) return null
  const dd=Number(m[1]),mm=Number(m[2]),yyyy=Number(m[3])
  const dt=new Date(yyyy,mm-1,dd)
  if (isNaN(dt.getTime())||dt.getFullYear()!==yyyy||dt.getMonth()!==mm-1||dt.getDate()!==dd) return null
  return dt
}
function isoToDDMMYYYY (v) {
  if (!v) return ''
  const s=String(v).trim()
  if (/^\d{2}-\d{2}-\d{4}$/.test(s)) return s
  const m=/^(\d{4})-(\d{2})-(\d{2})/.exec(s); if (m) return `${m[3]}-${m[2]}-${m[1]}`
  const d=new Date(s); if (isNaN(d.getTime())) return ''
  return `${String(d.getDate()).padStart(2,'0')}-${String(d.getMonth()+1).padStart(2,'0')}-${d.getFullYear()}`
}
const ageLabel = computed(() => {
  const dt=parseDDMMYYYY(form.value?.data_nascimento); if (!dt) return '—'
  const now=new Date(); let age=now.getFullYear()-dt.getFullYear()
  const mo=now.getMonth()-dt.getMonth()
  if (mo<0||(mo===0&&now.getDate()<dt.getDate())) age--
  if (age<0||age>130) return '—'; return `${age} anos`
})
const avatarIniciais = computed(() =>
  (form.value.nome_completo||'').split(' ').filter(Boolean).map(w=>w[0].toUpperCase()).slice(0,2).join('')
)

// ── DB map ─────────────────────────────────────────────────
function mapDbToForm (p) {
  return {
    ...resetForm(),
    nome_completo: p.nome_completo??'', nome_social: p.nome_social??'', pronomes: p.pronomes??'',
    data_nascimento: p.data_nascimento ? isoToDDMMYYYY(p.data_nascimento) : '',
    genero: p.genero??'', estado_civil: p.estado_civil??'',
    cpf: fmtCPF(p.cpf??''), rg: fmtRG(p.rg??''),
    naturalidade: p.naturalidade??'', etnia: p.etnia??'',
    profissao: p.profissao??'', escolaridade: p.escolaridade??'',
    telefone: fmtPhone(p.telefone??''), email_principal: p.email_principal??'',
    email_alternativo: p.email_alternativo??'', telefone_alternativo: fmtPhone(p.telefone_alternativo??''),
    canal_preferido: p.canal_preferido??'', horario_contato: p.horario_contato??'',
    cep: p.cep??'', pais: p.pais??'Brasil', cidade: p.cidade??'', estado: p.estado??'SP',
    endereco: p.endereco??'', numero: p.numero??'', bairro: p.bairro??'', complemento: p.complemento??'',
    status: p.status??'Ativo', convenio: p.convenio??'', patient_scope: p.patient_scope??'',
    onde_nos_conheceu: p.onde_nos_conheceu??'', encaminhado_por: p.encaminhado_por??'',
    motivo_saida: p.motivo_saida??'',
    nome_responsavel: p.nome_responsavel??'', cpf_responsavel: fmtCPF(p.cpf_responsavel??''),
    telefone_responsavel: fmtPhone(p.telefone_responsavel??''),
    observacao_responsavel: p.observacao_responsavel??'',
    cobranca_no_responsavel: !!p.cobranca_no_responsavel,
    observacoes: p.observacoes??'', notas_internas: p.notas_internas??'', avatar_url: p.avatar_url??'',
  }
}

// ── Sanitize ──────────────────────────────────────────────
const ALLOWED = new Set([
  'owner_id','tenant_id','responsible_member_id',
  'nome_completo','nome_social','pronomes',
  'data_nascimento','genero','estado_civil','cpf','rg','naturalidade','etnia',
  'profissao','escolaridade',
  // telefone, telefone_alternativo, email_principal, email_alternativo agora são
  // gerenciados pelos editors polimórficos (contact_phones / contact_emails);
  // triggers sincronizam de volta pra essas colunas legadas.
  'canal_preferido','horario_contato',
  'cep','pais','cidade','estado','endereco','numero','bairro','complemento',
  'status','convenio','convenio_id','patient_scope',
  'onde_nos_conheceu','encaminhado_por','motivo_saida',
  'nome_responsavel','cpf_responsavel','telefone_responsavel',
  'observacao_responsavel','cobranca_no_responsavel',
  'observacoes','notas_internas','avatar_url',
])
function sanitizePayload (raw, ownerId) {
  const p = { owner_id: ownerId }
  Object.keys(raw).forEach(k => { p[k] = raw[k]??null })
  Object.keys(p).forEach(k => {
    if (p[k]==='') p[k]=null
    if (typeof p[k]==='string') { const t=p[k].trim(); p[k]=t===''?null:t }
  })
  p.cpf = p.cpf ? digitsOnly(p.cpf) : null
  p.rg  = p.rg  ? digitsOnly(p.rg)  : null
  p.cpf_responsavel      = p.cpf_responsavel      ? digitsOnly(p.cpf_responsavel)      : null
  p.telefone             = p.telefone             ? digitsOnly(p.telefone)             : null
  p.telefone_alternativo = p.telefone_alternativo ? digitsOnly(p.telefone_alternativo) : null
  p.telefone_responsavel = p.telefone_responsavel ? digitsOnly(p.telefone_responsavel) : null
  p.data_nascimento      = p.data_nascimento      ? (toISODate(p.data_nascimento)||null) : null
  p.cobranca_no_responsavel = !!raw.cobranca_no_responsavel
  // convenio_id vem do ref separado (não do form)
  p.convenio_id = convenioId.value || null
  const out = {}
  Object.keys(p).forEach(k => { if (ALLOWED.has(k)) out[k]=p[k] })
  return out
}

// ─────────────────────────────────────────────────────────
// Rede de suporte — patient_support_contacts
// Entidade separada que alimenta o card "Contatos & rede de suporte" do detalhe
// is_primario = true → badge vermelho "emergência" no detalhe
// ─────────────────────────────────────────────────────────
// V#9 — contatos de suporte agora vêm do composable (script -300 linhas)
const _supportContacts = usePatientSupportContacts()
const contatosSuporte  = _supportContacts.contatos
const addContato       = () => _supportContacts.add()
const removeContato    = (i) => _supportContacts.remove(i)
const iniciaisFor      = (n) => _supportContacts.iniciaisFor(n)
const loadContatosSuporte = (pid) => _supportContacts.load(pid)
const saveContatosSuporte = (pid, tenantId, ownerId) => _supportContacts.save(pid, tenantId, ownerId)

// V#9 — DB calls delegadas ao patientsRepository (V#3 fundação)
async function listGroups () {
  return repoListGroups({ tenantId: currentTenantId() })
}
async function listTags () {
  return repoListTags({ tenantId: currentTenantId() })
}
async function getPatientById (id) {
  return repoGetPatientById(id, { tenantId: currentTenantId() })
}
async function getPatientRelations (id) {
  return repoGetPatientRelations(id)
}
async function createPatient (payload) {
  return repoCreatePatient(payload)
}
async function updatePatient (id, payload) {
  return repoUpdatePatient(id, { ...payload, updated_at: new Date().toISOString() }, { tenantId: currentTenantId() })
}

const groups             = ref([])
const tags               = ref([])
const grupoIdSelecionado = ref(null)
const tagIdsSelecionadas = ref([])

// V#9 — replace de grupos/tags via repo
async function replacePatientGroups (patient_id, groupId) {
  const { tenantId } = await resolveTenantContextOrFail()
  return repoReplacePatientGroup(patient_id, groupId, { tenantId })
}
async function replacePatientTags (patient_id, tagIds) {
  const ownerId = await getOwnerId()
  const { tenantId } = await resolveTenantContextOrFail()
  return repoReplacePatientTags(patient_id, tagIds, { tenantId, ownerId })
}

// V#9 — CEP via composable useCep (reutilizável)
const _cep = useCep()
async function onCepBlur () {
  const result = await _cep.fetchCep(form.value.cep)
  if (!result) return
  form.value.cidade   = result.cidade   || form.value.cidade
  form.value.estado   = result.uf       || form.value.estado
  form.value.bairro   = result.bairro   || form.value.bairro
  form.value.endereco = result.endereco || form.value.endereco
  if (!form.value.complemento) form.value.complemento = result.complemento || ''
  toast.add({ severity:'success', summary:'CEP', detail:`${result.cidade} / ${result.uf}`, life:2000 })
}

// ─────────────────────────────────────────────────────────
// UI state + fetch
// ─────────────────────────────────────────────────────────
const loading  = ref(false)
const saving   = ref(false)
const deleting = ref(false)
// `submitted` é true depois da primeira tentativa de salvar — usado pra
// mostrar borda vermelha + msg "Campo obrigatório" embaixo dos inputs
// sem incomodar o usuário antes da primeira interação.
const submitted = ref(false)
// Counts dos editores polimórficos (telefones/emails) — atualizados via
// @change. Telefone e email são obrigatórios: pelo menos 1 cada.
const phonesCount = ref(0)
const emailsCount = ref(0)
// Refs pros editores — usados pra chamar `flushPending` depois que o
// paciente é criado (telefones/emails inseridos antes do save ficam
// em modo pendente até a entidade existir no DB).
const phonesEditorRef = ref(null)
const emailsEditorRef = ref(null)

async function fetchAll () {
  loading.value = true
  try {
    const [gRes, tRes] = await Promise.allSettled([listGroups(), listTags()])
    groups.value = gRes.status==='fulfilled' ? gRes.value||[] : []
    tags.value   = tRes.status==='fulfilled' ? tRes.value||[] : []
    if (isEdit.value) {
      const p = await getPatientById(patientId.value)
      form.value = mapDbToForm(p)
      avatarPreviewUrl.value = form.value.avatar_url||''
      const rel = await getPatientRelations(patientId.value)
      grupoIdSelecionado.value = rel.groupIds?.[0]||null
      tagIdsSelecionadas.value = rel.tagIds||[]
      await loadContatosSuporte(patientId.value)
      // Restaura convênio selecionado
      convenioId.value   = p.convenio_id || null
      convenioNome.value = p.convenio    || ''
      // Restaura médicos a partir do texto encaminhado_por salvo no banco
      // (sem tabela patient_medicos, mantemos o texto como fonte da verdade)
      medicosSelecionados.value = []
      // encaminhado_por já vem populado via mapDbToForm — não zeramos
    } else {
      grupoIdSelecionado.value=null; tagIdsSelecionadas.value=[]
      avatarFile.value=null; revokePreview(); contatosSuporte.value=[]
      convenioId.value=null; convenioNome.value=''; medicosSelecionados.value=[]
    }
  } catch (err) {
    toast.add({ severity:'error', summary:'Erro', detail:err?.message||'Falha ao carregar.', life:3500 })
  } finally { loading.value=false }
}
watch(patientId, fetchAll, { immediate:true })

// ─────────────────────────────────────────────────────────
// Submit
// ─────────────────────────────────────────────────────────
async function onSubmit () {
  // Marca pra que :invalid + mensagens de erro fiquem visíveis nos inputs
  // exigidos. Reseta no sucesso (logo abaixo) ou na próxima edição válida
  // (não reseta automaticamente — só atrapalharia o feedback visual).
  submitted.value = true
  saving.value = true
  try {
    const ownerId = await getOwnerId()
    const { tenantId, memberId } = await resolveTenantContextOrFail()
    const nome = String(form.value?.nome_completo||'').trim()
    if (!nome) {
      toast.add({ severity:'warn', summary:'Nome obrigatório', detail:'Preencha o nome completo.', life:3500 })
      await openPanel(0); return
    }
    // Telefone e email são obrigatórios: pelo menos 1 cada. Toast aponta
    // pro campo faltando + abre a seção Identidade (onde os editores ficam).
    if (phonesCount.value === 0) {
      toast.add({ severity:'warn', summary:'Telefone obrigatório', detail:'Adicione pelo menos um telefone.', life:3500 })
      await openPanel(0); return
    }
    if (emailsCount.value === 0) {
      toast.add({ severity:'warn', summary:'E-mail obrigatório', detail:'Adicione pelo menos um e-mail.', life:3500 })
      await openPanel(0); return
    }
    const payload = sanitizePayload(form.value, ownerId)
    payload.tenant_id = tenantId; payload.responsible_member_id = memberId
    if (isEdit.value) {
      await updatePatient(patientId.value, payload)
      await maybeUploadAvatar(ownerId, patientId.value)
      await replacePatientGroups(patientId.value, grupoIdSelecionado.value)
      await replacePatientTags(patientId.value, tagIdsSelecionadas.value)
      await saveContatosSuporte(patientId.value, tenantId, ownerId)
      toast.add({ severity:'success', summary:'Salvo', detail:'Paciente atualizado.', life:2500 })
      submitted.value = false
      if (props.dialogMode) { emit('created', { id:patientId.value }); return }
      return
    }
    const created = await createPatient(payload)
    // Telefones/emails podem ter sido adicionados ANTES do paciente existir
    // (modo pendente — id 'pending_*' em memória). Agora que temos `created.id`,
    // gravamos tudo em lote no DB. Roda antes de avatar/grupos/tags pra que
    // qualquer falha aqui aborte o resto do fluxo.
    await phonesEditorRef.value?.flushPending('patient', created.id)
    await emailsEditorRef.value?.flushPending('patient', created.id)
    await maybeUploadAvatar(ownerId, created.id)
    await replacePatientGroups(created.id, grupoIdSelecionado.value)
    await replacePatientTags(created.id, tagIdsSelecionadas.value)
    await saveContatosSuporte(created.id, tenantId, ownerId)
    toast.add({ severity:'success', summary:'Salvo', detail:'Paciente cadastrado.', life:2500 })
    submitted.value = false
    if (props.dialogMode) { emit('created', created); return }
    form.value=resetForm(); grupoIdSelecionado.value=null; tagIdsSelecionadas.value=[]
    contatosSuporte.value=[]; avatarFile.value=null; revokePreview(); avatarPreviewUrl.value=''
    convenioId.value=null; convenioNome.value=''; medicosSelecionados.value=[]
    await openPanel(0)
  } catch (e) {
    logError('patients.cadastro', 'save falhou', e)
    toast.add({ severity:'error', summary:'Erro', detail:e?.message||'Falha ao salvar.', life:4000 })
  } finally { saving.value=false }
}

// ─────────────────────────────────────────────────────────
// Delete
// ─────────────────────────────────────────────────────────
function confirmDelete () {
  if (!isEdit.value) return
  confirm.require({
    header:'Excluir paciente', message:'Esta ação não pode ser desfeita.',
    icon:'pi pi-exclamation-triangle', acceptLabel:'Excluir',
    rejectLabel:'Cancelar', acceptClass:'p-button-danger',
    accept: () => doDelete(),
  })
}
async function doDelete () {
  deleting.value = true
  try {
    const pid = patientId.value
    const tables = [
      ['patient_group_patient',    'patient_id'],
      ['patient_patient_tag',      'patient_id'],
      ['patient_support_contacts', 'patient_id'],
      ['patients',                 'id'],
    ]
    for (const [tbl, col] of tables) {
      const { error } = await supabase.from(tbl).delete().eq(col, pid); if (error) throw error
    }
    toast.add({ severity:'success', summary:'Excluído', detail:'Paciente removido.', life:2500 })
    if (props.dialogMode) { emit('created', null); return }
    goBack()
  } catch (err) {
    toast.add({ severity:'error', summary:'Erro', detail:err?.message||'Falha ao excluir.', life:4000 })
  } finally { deleting.value=false }
}

// ─────────────────────────────────────────────────────────
// Opções
// ─────────────────────────────────────────────────────────
const genderOptions = [
  { label:'Feminino',value:'Feminino' },{ label:'Masculino',value:'Masculino' },
  { label:'Não-binário',value:'Não-binário' },{ label:'Prefere não informar',value:'Prefere não informar' },{ label:'Outro',value:'Outro' },
]
const maritalOpts = [
  { label:'Solteiro(a)',value:'Solteiro(a)' },{ label:'Casado(a)',value:'Casado(a)' },
  { label:'União estável',value:'União estável' },{ label:'Divorciado(a)',value:'Divorciado(a)' },
  { label:'Separado(a)',value:'Separado(a)' },{ label:'Viúvo(a)',value:'Viúvo(a)' },
  { label:'Prefere não informar',value:'Prefere não informar' },
]
const pronounsOpts  = [{ label:'ela/dela',value:'ela/dela' },{ label:'ele/dele',value:'ele/dele' },{ label:'elu/delu',value:'elu/delu' },{ label:'Prefere não informar',value:'Prefere não informar' }]
const etniaOpts     = [{ label:'Amarela',value:'Amarela' },{ label:'Branca',value:'Branca' },{ label:'Indígena',value:'Indígena' },{ label:'Parda',value:'Parda' },{ label:'Preta',value:'Preta' },{ label:'Prefere não informar',value:'Prefere não informar' }]
const escolaridadeOpts = [
  { label:'Não alfabetizado(a)',          value:'Não alfabetizado(a)'          },
  { label:'Ensino fundamental incompleto',value:'Ensino fundamental incompleto'},
  { label:'Ensino fundamental completo',  value:'Ensino fundamental completo'  },
  { label:'Ensino médio incompleto',      value:'Ensino médio incompleto'      },
  { label:'Ensino médio completo',        value:'Ensino médio completo'        },
  { label:'Ensino técnico',               value:'Ensino técnico'               },
  { label:'Superior incompleto',          value:'Superior incompleto'          },
  { label:'Superior completo',            value:'Superior completo'            },
  { label:'Pós-graduação / Especialização',value:'Pós-graduação / Especialização'},
  { label:'Mestrado',                     value:'Mestrado'                     },
  { label:'Doutorado',                    value:'Doutorado'                    },
  { label:'Prefere não informar',         value:'Prefere não informar'         },
]
const canalOpts     = [{ label:'WhatsApp',value:'WhatsApp' },{ label:'Telefone',value:'Telefone' },{ label:'E-mail',value:'E-mail' },{ label:'SMS',value:'SMS' }]
const statusOpts    = [{ label:'Ativo',value:'Ativo' },{ label:'Em espera',value:'Em espera' },{ label:'Inativo',value:'Inativo' },{ label:'Alta',value:'Alta' },{ label:'Arquivado',value:'Arquivado' }]
const scopeOpts     = [{ label:'Clínica',value:'Clínica' },{ label:'Particular',value:'Particular' },{ label:'Online',value:'Online' },{ label:'Híbrido',value:'Híbrido' }]
const tipoContatoOpts = [{ label:'Emergência',value:'emergencia' },{ label:'Familiar',value:'familiar' },{ label:'Profissional de saúde',value:'profissional_saude' },{ label:'Amigo(a)',value:'amigo' },{ label:'Outro',value:'outro' }]

// ─────────────────────────────────────────────────────────
// Dialogs Grupo / Tag
// ─────────────────────────────────────────────────────────
const createGroupDialog=ref(false); const createGroupSaving=ref(false); const createGroupError=ref(''); const newGroup=ref({ name:'',color:'#6366F1' })
const createTagDialog=ref(false);   const createTagSaving=ref(false);   const createTagError=ref('');   const newTag=ref({ name:'',color:'#22C55E' })

function openGroupDlg () { createGroupError.value=''; newGroup.value={ name:'',color:'#6366F1' }; createGroupDialog.value=true }
function openTagDlg   () { createTagError.value='';   newTag.value={ name:'',color:'#22C55E' };   createTagDialog.value=true   }

async function createGroupPersist () {
  if (createGroupSaving.value) return; createGroupError.value=''
  const name=String(newGroup.value?.name||'').trim(); const color=String(newGroup.value?.color||'').trim()||'#6366F1'
  if (!name) { createGroupError.value='Informe um nome.'; return }
  createGroupSaving.value=true
  try {
    const ownerId=await getOwnerId(); const { tenantId }=await resolveTenantContextOrFail()
    const { data, error }=await supabase.from('patient_groups').insert({ owner_id:ownerId, tenant_id:tenantId, nome:name, cor:color, is_system:false, is_active:true }).select('id').single()
    if (error) throw error
    groups.value=await listGroups(); if (data?.id) grupoIdSelecionado.value=data.id
    toast.add({ severity:'success', summary:'Grupo criado.', life:2500 }); createGroupDialog.value=false
  } catch (e) {
    const msg=e?.message||''
    createGroupError.value=(e?.code==='23505'||/duplicate/i.test(msg)) ? 'Já existe esse grupo.' : (msg||'Falha.')
  } finally { createGroupSaving.value=false }
}
async function createTagPersist () {
  if (createTagSaving.value) return; createTagError.value=''
  const name=String(newTag.value?.name||'').trim(); const color=String(newTag.value?.color||'').trim()||'#22C55E'
  if (!name) { createTagError.value='Informe um nome.'; return }
  createTagSaving.value=true
  try {
    const ownerId=await getOwnerId(); const { tenantId }=await resolveTenantContextOrFail()
    const { data, error }=await supabase.from('patient_tags').insert({ owner_id:ownerId, tenant_id:tenantId, nome:name, cor:color }).select('id').single()
    if (error) throw error
    tags.value=await listTags()
    if (data?.id) { const s=new Set([...(tagIdsSelecionadas.value||[]),data.id]); tagIdsSelecionadas.value=Array.from(s) }
    toast.add({ severity:'success', summary:'Tag criada.', life:2500 }); createTagDialog.value=false
  } catch (e) {
    const msg=e?.message||''
    createTagError.value=(e?.code==='23505'||/duplicate/i.test(msg)) ? 'Já existe essa tag.' : (msg||'Falha.')
  } finally { createTagSaving.value=false }
}

// ─────────────────────────────────────────────────────────
// Dialog Convênio — CadastroRapidoConvenio
// Seleciona 1 convênio da tabela insurance_plans
// Armazena: convenioId (uuid) + convenioNome (string para exibição)
// ─────────────────────────────────────────────────────────
const showConvenioDlg = ref(false)
const convenioId      = ref(null)   // insurance_plans.id selecionado
const convenioNome    = ref('')     // nome exibido no campo

function onConvenioSelected (plan) {
  if (!plan) {
    convenioId.value   = null
    convenioNome.value = ''
    form.value.convenio = ''
  } else {
    convenioId.value    = plan.id
    convenioNome.value  = plan.name
    form.value.convenio = plan.name  // salva o nome no campo texto (exibido como badge)
  }
}
function clearConvenio () {
  convenioId.value    = null
  convenioNome.value  = ''
  form.value.convenio = ''
}

// ─────────────────────────────────────────────────────────
// Dialog Médico — CadastroRapidoMedico
// Pode selecionar/adicionar múltiplos médicos encaminhadores
// Armazenados como array de objetos; no save persistimos
// os nomes em encaminhado_por (text) e os IDs em patient_medicos[]
// ─────────────────────────────────────────────────────────
const showMedicoDlg   = ref(false)
const medicosSelecionados = ref([])  // [{ id, nome, crm, especialidade }]
const medicoEditandoId = ref(null)   // id do médico sendo editado no dialog

function onMedicoSelected (medico) {
  if (!medico) return
  // Se veio de uma edição, atualiza o item existente
  const idx = medicosSelecionados.value.findIndex(m => m.id === medico.id)
  if (idx !== -1) {
    medicosSelecionados.value[idx] = { id: medico.id, nome: medico.nome, crm: medico.crm||'', especialidade: medico.especialidade||'' }
    syncEncaminhadoPor(); return
  }
  // Evita duplicata nova
  if (medicosSelecionados.value.some(m => m.id === medico.id)) {
    toast.add({ severity: 'info', summary: 'Médico já adicionado', life: 2000 }); return
  }
  medicosSelecionados.value.push({ id: medico.id, nome: medico.nome, crm: medico.crm||'', especialidade: medico.especialidade||'' })
  syncEncaminhadoPor()
}
function openMedicoEdit (id) {
  medicoEditandoId.value = id
  showMedicoDlg.value    = true
}
function removeMedico (idx) {
  medicosSelecionados.value.splice(idx, 1)
  // Se ainda há médicos no picker, atualiza o campo
  // Se removeu todos, limpa o campo também
  if (medicosSelecionados.value.length > 0) {
    form.value.encaminhado_por = medicosSelecionados.value
      .map(m => m.crm ? `${m.nome} (${m.crm})` : m.nome)
      .join(', ')
  } else {
    form.value.encaminhado_por = ''
  }
}
function syncEncaminhadoPor () {
  // Só sobrescreve se há médicos selecionados via picker
  // Se o array está vazio, preserva o texto que veio do banco
  if (medicosSelecionados.value.length === 0) return
  form.value.encaminhado_por = medicosSelecionados.value
    .map(m => m.crm ? `${m.nome} (${m.crm})` : m.nome)
    .join(', ')
}

// ─────────────────────────────────────────────────────────
// Progresso de preenchimento por seção
// ─────────────────────────────────────────────────────────
const progSec = computed(() => {
  const f = form.value
  return [
    { key:'0', filled:[f.nome_completo,f.telefone,f.email_principal,f.data_nascimento,f.pronomes].filter(Boolean).length, total:5 },
    { key:'1', filled:[f.cep,f.cidade,f.estado,f.endereco].filter(Boolean).length,                                        total:4 },
    { key:'2', filled:[f.status,f.convenio,f.onde_nos_conheceu].filter(Boolean).length,                                   total:3 },
    { key:'3', filled:contatosSuporte.value.filter(c=>c.nome.trim()).length>0 ? 1 : 0,                                    total:1 },
    { key:'4', filled:[f.nome_responsavel,f.telefone_responsavel].filter(Boolean).length,                                 total:2 },
    { key:'5', filled:f.notas_internas ? 1 : 0,                                                                           total:1 },
  ]
})
const progGeral = computed(() => {
  const tot = progSec.value.reduce((a,s)=>a+s.total,0)
  const fil = progSec.value.reduce((a,s)=>a+s.filled,0)
  return Math.round((fil/tot)*100)
})
function p (key) { return progSec.value.find(x=>x.key===key) || { filled:0, total:1 } }

// ─────────────────────────────────────────────────────────
// Fake fill
// ─────────────────────────────────────────────────────────
function ri (a,b)  { return Math.floor(Math.random()*(b-a+1))+a }
function pk (arr)  { return arr[ri(0,arr.length-1)] }
function mb (v=.5) { return Math.random()<v }
function p2 (n)    { return String(n).padStart(2,'0') }

function fillRandomPatient () {
  const first=['Ana','Bruno','Carla','Daniel','Eduarda','Felipe','Giovana','Henrique','Isabela','João','Larissa','Marcos']
  const last=['Silva','Santos','Oliveira','Souza','Pereira','Lima','Ferreira','Almeida','Costa','Gomes']
  const emailIdx = ri(1, 7)  // teste1 até teste7
  const nome=`${pk(first)} ${pk(last)} ${pk(last)}`
  const age=ri(18,75); const y=new Date().getFullYear()
  form.value={ ...resetForm(),
    nome_completo:  nome,
    nome_social:    mb(.3) ? pk(first) : '',
    pronomes:       pk(['ela/dela','ele/dele','elu/delu']),
    data_nascimento:`${p2(ri(1,28))}-${p2(ri(1,12))}-${y-age}`,
    genero:          pk(['Feminino','Masculino','Não-binário']),
    estado_civil:    pk(['Solteiro(a)','Casado(a)','União estável']),
    cpf:             fmtCPF(generateCPF()),
    rg:              fmtRG(String(ri(10000000,999999999))),
    etnia:           pk(['Branca','Parda','Preta','Amarela','Indígena']),
    naturalidade:    'São Carlos',
    profissao:       pk(['Estudante','Professora','Desenvolvedor','Enfermeira','Autônomo']),
    escolaridade:    pk(['Ensino médio completo','Ensino técnico','Superior incompleto','Superior completo','Pós-graduação / Especialização','Mestrado']),
    // Contatos fixos
    telefone:              '(16) 98828-0038',
    telefone_alternativo:  '(16) 99600-5268',
    email_principal:       `teste${emailIdx}@agenciapsi.com.br`,
    email_alternativo:     `teste${emailIdx === 7 ? 1 : emailIdx + 1}@agenciapsi.com.br`,
    canal_preferido:  pk(['WhatsApp','Telefone','E-mail']),
    horario_contato:  pk(['08h–12h','13h–18h','08h–20h','Qualquer horário']),
    // Endereço fixo
    cep:         '13561-260',
    pais:        'Brasil',
    cidade:      'São Carlos',
    estado:      'SP',
    endereco:    'Rua Conde do Pinhal',
    numero:      '457',
    bairro:      'Centro',
    complemento: 'Apartamento',
    observacoes: 'Próximo ao posto de saúde do centro.',
    // Clínico
    status:        'Ativo',
    convenio:      '',   // selecionado via dialog de convênio
    patient_scope: pk(['Clínica','Online','Híbrido']),
    onde_nos_conheceu: pk(['Instagram','Google','Indicação','Site']),
    encaminhado_por:            '',  // preenchido via dialog de médicos
    // Interno
    notas_internas: 'Paciente apresenta discurso organizado. Acompanhar evolução clínica.',
  }
  contatosSuporte.value = [{
    _k: 1,
    nome:       `${pk(first)} ${pk(last)}`,
    relacao:    pk(['mãe','pai','cônjuge','psiquiatra']),
    tipo:       'emergencia',
    telefone:   '(16) 98828-0038',
    email:      `teste${emailIdx}@agenciapsi.com.br`,
    is_primario: true,
  }]
  if (groups.value.length) grupoIdSelecionado.value = pk(groups.value).id
  if (tags.value.length) {
    const sh=[...tags.value].sort(()=>Math.random()-.5)
    tagIdsSelecionadas.value = sh.slice(0,ri(1,Math.min(3,tags.value.length))).map(t=>t.id)
  }
  toast.add({ severity:'info', summary:'Preenchido', detail:'Dados fictícios aplicados.', life:2500 })
}

function openLgpdDialog() { lgpdDialog.value = true }
defineExpose({ fillRandomPatient, onSubmit, confirmDelete, saving, deleting, canSee, isEdit, openLgpdDialog, lgpdExporting, goToConversation })
</script>

<template>
  <ConfirmDialog v-if="!dialogMode" />
  <div class="h-px" />

  <!-- ══════════════════════════════════════════════════════
       HERO STICKY — preview ao vivo do que aparece no detalhe
  ═══════════════════════════════════════════════════════ -->
  <section
    v-if="!dialogMode"
    class="sticky mx-3 md:mx-4 my-3 z-20 overflow-hidden rounded-xl border border-[var(--surface-border)] bg-[var(--surface-card)] px-4 py-2.5 shadow-sm"
    :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-72 h-72 -top-20 -right-10 rounded-full blur-[80px] bg-indigo-400/10" />
      <div class="absolute w-56 h-56 -bottom-16 -left-10 rounded-full blur-[60px] bg-teal-400/[0.07]" />
    </div>
    <div class="relative flex items-center gap-3">

      <!-- Avatar ao vivo com iniciais -->
      <div class="w-9 h-9 rounded-full overflow-hidden border-2 border-[var(--surface-border)] bg-indigo-50 flex items-center justify-center shrink-0">
        <img v-if="avatarPreviewUrl || form.avatar_url" :src="avatarPreviewUrl || form.avatar_url" class="w-full h-full object-cover" alt="" />
        <span v-else-if="avatarIniciais" class="text-indigo-600 font-black text-[0.8rem] select-none">{{ avatarIniciais }}</span>
        <i v-else class="pi pi-user text-indigo-300 text-sm" />
      </div>

      <!-- Nome + pronomes + status — ao vivo -->
      <div class="hidden sm:block min-w-0">
        <div class="font-bold text-[0.92rem] text-[var(--text-color)] truncate leading-tight">
          {{ form.nome_completo || (isEdit ? 'Editar paciente' : 'Novo paciente') }}
        </div>
        <div class="text-[0.7rem] text-[var(--text-color-secondary)] flex items-center gap-1.5 flex-wrap">
          <template v-if="ageLabel !== '—'">{{ ageLabel }}</template>
          <template v-else>Cadastro completo</template>
          <template v-if="form.pronomes">
            · <span class="text-indigo-500 font-medium">{{ form.pronomes }}</span>
          </template>
          <template v-if="form.cidade">
            · {{ form.cidade }}<template v-if="form.estado">, {{ form.estado }}</template>
          </template>
          <span v-if="form.status"
            class="inline-flex items-center px-1.5 py-0 rounded-full text-[0.6rem] font-bold bg-green-100 text-green-700 ml-0.5">
            {{ form.status }}
          </span>
        </div>
      </div>

      <!-- Progresso -->
      <div class="hidden lg:flex flex-1 items-center gap-3 px-3 max-w-xs">
        <div class="flex-1 min-w-0">
          <div class="flex justify-between text-[0.65rem] text-[var(--text-color-secondary)] mb-1">
            <span>Preenchimento</span><span class="font-bold">{{ progGeral }}%</span>
          </div>
          <ProgressBar :value="progGeral" :showValue="false" style="height:4px;border-radius:99px;" />
        </div>
        <div class="flex gap-1 shrink-0">
          <button
            v-for="s in progSec" :key="s.key" type="button"
            class="w-1.5 h-1.5 rounded-full transition-all duration-150 hover:scale-150"
            :class="s.filled===s.total ? 'bg-emerald-500' : s.filled>0 ? 'bg-amber-400' : 'bg-[var(--surface-border)]'"
            :title="sections[Number(s.key)]?.label"
            @click="openPanel(Number(s.key))"
          />
        </div>
      </div>

      <div class="flex-1" />

      <!-- Ações -->
      <div v-if="!dialogMode" class="flex items-center gap-1.5 shrink-0">
        <Button
          v-if="canSee('testMODE')"
          label="Preencher tudo" icon="pi pi-bolt"
          severity="secondary" outlined size="small"
          class="rounded-full hidden xl:flex"
          @click="fillRandomPatient"
        />
        <Button icon="pi pi-arrow-left" severity="secondary" outlined class="h-9 w-9 rounded-full" title="Voltar" @click="goBack" />
        <Button v-if="isEdit" icon="pi pi-whatsapp" severity="success" outlined class="h-9 w-9 rounded-full" title="Conversar no WhatsApp" @click="goToConversation" />
        <Button v-if="isEdit" icon="pi pi-shield" severity="secondary" outlined class="h-9 w-9 rounded-full" title="Exportar dados do paciente (LGPD)" @click="lgpdDialog = true" />
        <Button v-if="isEdit" icon="pi pi-trash" severity="danger" outlined class="h-9 w-9 rounded-full" :loading="deleting" @click="confirmDelete" />
        <Button label="Salvar" icon="pi pi-check" class="rounded-full" :loading="saving" @click="onSubmit" />
      </div>
    </div>
  </section>

  <!-- ══════════════════════════════════════════════════════
       CORPO
  ═══════════════════════════════════════════════════════ -->
  <div class="px-3 md:px-4 pb-8">

    <div v-if="loading" class="flex items-center justify-center py-20 gap-2 text-[var(--text-color-secondary)]">
      <i class="pi pi-spin pi-spinner text-xl" /> Carregando…
    </div>

    <div v-else class="grid grid-cols-1 gap-3 xl:grid-cols-[220px_1fr] xl:items-start max-w-[1040px] mx-auto">

      <!-- ── SIDEBAR ────────────────────────────────────── -->
      <aside
        class="rounded-xl border border-[var(--surface-border)] bg-[var(--surface-card)] p-3 xl:sticky xl:self-start shadow-sm"
        :class="dialogMode ? 'xl:top-4' : 'xl:top-[calc(var(--layout-sticky-top,56px)+3.6rem)]'"
      >
        <!-- Avatar + upload -->
        <div class="flex items-center gap-3 pb-3 mb-3 border-b border-[var(--surface-border)] xl:flex-col xl:items-center xl:text-center">
          <div class="relative shrink-0">
            <div class="w-16 h-16 xl:w-[4.5rem] xl:h-[4.5rem] rounded-full overflow-hidden border-2 border-[var(--surface-border)] bg-[var(--surface-ground)] flex items-center justify-center">
              <img v-if="avatarPreviewUrl||form.avatar_url" :src="avatarPreviewUrl||form.avatar_url" class="w-full h-full object-cover" alt="Avatar" />
              <span v-else-if="avatarIniciais" class="text-2xl font-black text-indigo-500 select-none">{{ avatarIniciais }}</span>
              <i v-else class="pi pi-user text-2xl text-[var(--text-color-secondary)] opacity-25" />
            </div>
          </div>
          <div class="flex-1 xl:w-full">
            <input type="file" accept="image/*"
              class="block w-full text-[0.72rem] text-[var(--text-color-secondary)]
                     file:mr-2 file:rounded-full file:border file:border-[var(--surface-border)]
                     file:bg-[var(--surface-ground)] file:px-3 file:py-1 file:text-[0.68rem]
                     file:cursor-pointer hover:file:bg-indigo-50 hover:file:border-indigo-300"
              @change="onAvatarPicked"
            />
            <div class="mt-1 text-[0.63rem] text-[var(--text-color-secondary)] opacity-60">
              Foto opcional · máx 3 MB<span v-if="avatarUploading" class="text-indigo-500 ml-1">(enviando…)</span>
            </div>
          </div>
        </div>

        <!-- Toggle layout vertical/horizontal -->
        <div class="flex items-center gap-1 mb-3 p-0.5 rounded-lg bg-[var(--surface-ground)] border border-[var(--surface-border)]">
          <button
            type="button"
            class="flex-1 flex items-center justify-center gap-1.5 px-2 py-1 rounded-md text-[0.68rem] font-medium transition-all duration-150"
            :class="viewMode === 'vertical' ? 'bg-[var(--surface-card)] text-[var(--text-color)] shadow-sm' : 'text-[var(--text-color-secondary)] hover:text-[var(--text-color)]'"
            title="Layout vertical (acordeão)"
            @click="setViewMode('vertical')"
          >
            <i class="pi pi-bars text-[0.68rem]" />
            <span>Vertical</span>
          </button>
          <button
            type="button"
            class="flex-1 flex items-center justify-center gap-1.5 px-2 py-1 rounded-md text-[0.68rem] font-medium transition-all duration-150"
            :class="viewMode === 'horizontal' ? 'bg-[var(--surface-card)] text-[var(--text-color)] shadow-sm' : 'text-[var(--text-color-secondary)] hover:text-[var(--text-color)]'"
            title="Layout horizontal (abas)"
            @click="setViewMode('horizontal')"
          >
            <i class="pi pi-th-large text-[0.68rem] rotate-90" />
            <span>Abas</span>
          </button>
        </div>

        <!-- Nav desktop (≥ xl) — só em vertical (em horizontal as tabs ficam acima do form) -->
        <div v-if="!isCompact && viewMode === 'vertical'" class="flex flex-col gap-0.5">
          <div class="text-[0.62rem] font-bold uppercase tracking-widest text-[var(--text-color-secondary)] opacity-40 px-2 mb-1">Seções</div>
          <button
            v-for="s in sections" :key="s.value" type="button"
            class="flex items-center gap-2 rounded-lg px-2.5 py-[7px] text-left text-[0.82rem] border transition-all duration-150"
            :class="activeValue===s.value ? pal[s.accent].activeBtn : 'border-transparent text-[var(--text-color)] hover:bg-[var(--surface-ground)] font-medium'"
            @click="openPanel(Number(s.value))"
          >
            <span
              class="flex items-center justify-center w-[22px] h-[22px] rounded-md shrink-0 text-[0.68rem]"
              :class="activeValue===s.value ? pal[s.accent].iconBox : 'bg-[var(--surface-ground)] text-[var(--text-color-secondary)]'"
            >
              <i :class="s.icon" />
            </span>
            <span class="flex-1 leading-tight">{{ s.label }}</span>
            <!-- dot de progresso -->
            <span class="w-[6px] h-[6px] rounded-full shrink-0"
              :class="p(s.value).filled===p(s.value).total ? 'bg-emerald-500' : p(s.value).filled>0 ? 'bg-amber-400' : 'bg-[var(--surface-border)]'"
            />
          </button>

          <!-- Barra de preenchimento -->
          <div class="mt-3 pt-3 border-t border-[var(--surface-border)]">
            <div class="flex justify-between text-[0.63rem] text-[var(--text-color-secondary)] mb-1.5">
              <span>Preenchimento geral</span><span class="font-bold">{{ progGeral }}%</span>
            </div>
            <ProgressBar :value="progGeral" :showValue="false" style="height:4px;border-radius:99px;" />
            <div class="flex gap-3 mt-2 text-[0.6rem] text-[var(--text-color-secondary)]">
              <span class="flex items-center gap-1"><span class="w-1.5 h-1.5 rounded-full bg-emerald-500 inline-block"/>Completo</span>
              <span class="flex items-center gap-1"><span class="w-1.5 h-1.5 rounded-full bg-amber-400 inline-block"/>Parcial</span>
              <span class="flex items-center gap-1"><span class="w-1.5 h-1.5 rounded-full bg-[var(--surface-border)] inline-block"/>Vazio</span>
            </div>
          </div>
        </div>

        <!-- Nav compacto (< xl) — só em vertical -->
        <div v-if="isCompact && viewMode === 'vertical'">
          <Button
            type="button" class="w-full !rounded-full"
            icon="pi pi-list" iconPos="right"
            :label="selectedSection?.label || 'Selecionar seção'"
            severity="secondary" outlined
            @click="toggleNav($event)"
          />
          <Popover ref="navPopover" :pt="{ root:{ class:'z-[9999999]' } }">
            <div class="flex min-w-[220px] flex-col gap-0.5 p-1">
              <button
                v-for="s in sections" :key="s.value" type="button"
                class="flex items-center gap-2.5 rounded-lg px-3 py-2 text-left text-[0.85rem] border"
                :class="activeValue===s.value ? pal[s.accent].activeBtn : 'border-transparent text-[var(--text-color)] hover:bg-[var(--surface-ground)]'"
                @click="selectNav(s)"
              >
                <span class="flex items-center justify-center w-6 h-6 rounded-md text-[0.7rem]" :class="pal[s.accent].iconBox"><i :class="s.icon"/></span>
                {{ s.label }}
              </button>
            </div>
          </Popover>
        </div>

        <!-- ── Atalhos de cadastro ──────────────────────── -->
        <div class="mt-3 pt-3 border-t border-[var(--surface-border)] flex flex-col gap-1.5">
          <div class="text-[0.62rem] font-bold uppercase tracking-widest text-[var(--text-color-secondary)] opacity-40 px-1 mb-0.5">Cadastros rápidos</div>
          <button
            type="button"
            class="flex items-center gap-2 rounded-lg px-2.5 py-[7px] text-left text-[0.8rem] border border-transparent hover:bg-indigo-50/60 hover:border-indigo-100 text-[var(--text-color)] font-medium transition-all duration-150 group"
            @click="openGroupDlg"
          >
            <span class="flex items-center justify-center w-[22px] h-[22px] rounded-md bg-indigo-100 text-indigo-600 text-[0.68rem] shrink-0 group-hover:bg-indigo-200 transition-colors">
              <i class="pi pi-folder-open"/>
            </span>
            <span class="flex-1">Grupos</span>
            <i class="pi pi-plus text-[0.6rem] text-[var(--text-color-secondary)] opacity-40 group-hover:opacity-80"/>
          </button>
          <button
            type="button"
            class="flex items-center gap-2 rounded-lg px-2.5 py-[7px] text-left text-[0.8rem] border border-transparent hover:bg-violet-50/60 hover:border-violet-100 text-[var(--text-color)] font-medium transition-all duration-150 group"
            @click="openTagDlg"
          >
            <span class="flex items-center justify-center w-[22px] h-[22px] rounded-md bg-violet-100 text-violet-600 text-[0.68rem] shrink-0 group-hover:bg-violet-200 transition-colors">
              <i class="pi pi-tag"/>
            </span>
            <span class="flex-1">Tags clínicas</span>
            <i class="pi pi-plus text-[0.6rem] text-[var(--text-color-secondary)] opacity-40 group-hover:opacity-80"/>
          </button>
          <button
            type="button"
            class="flex items-center gap-2 rounded-lg px-2.5 py-[7px] text-left text-[0.8rem] border border-transparent hover:bg-teal-50/60 hover:border-teal-100 text-[var(--text-color)] font-medium transition-all duration-150 group"
            @click="showMedicoDlg = true"
          >
            <span class="flex items-center justify-center w-[22px] h-[22px] rounded-md bg-teal-100 text-teal-600 text-[0.68rem] shrink-0 group-hover:bg-teal-200 transition-colors">
              <i class="pi pi-user-plus"/>
            </span>
            <span class="flex-1">Médicos</span>
            <i class="pi pi-plus text-[0.6rem] text-[var(--text-color-secondary)] opacity-40 group-hover:opacity-80"/>
          </button>
          <button
            type="button"
            class="flex items-center gap-2 rounded-lg px-2.5 py-[7px] text-left text-[0.8rem] border border-transparent hover:bg-blue-50/60 hover:border-blue-100 text-[var(--text-color)] font-medium transition-all duration-150 group"
            @click="showConvenioDlg = true"
          >
            <span class="flex items-center justify-center w-[22px] h-[22px] rounded-md bg-blue-100 text-blue-600 text-[0.68rem] shrink-0 group-hover:bg-blue-200 transition-colors">
              <i class="pi pi-shield"/>
            </span>
            <span class="flex-1">Convênios</span>
            <i class="pi pi-plus text-[0.6rem] text-[var(--text-color-secondary)] opacity-40 group-hover:opacity-80"/>
          </button>
        </div>
      </aside>

      <!-- ── MAIN ───────────────────────────────────────── -->
      <main
        class="rounded-xl border border-[var(--surface-border)] bg-[var(--surface-card)] overflow-hidden shadow-sm"
        :class="{ 'pcd-horizontal': viewMode === 'horizontal' }"
      >
        <!-- Tab list (só em horizontal) -->
        <div
          v-if="viewMode === 'horizontal'"
          class="flex gap-0.5 overflow-x-auto px-2 pt-2 border-b border-[var(--surface-border)] bg-[var(--surface-ground)]/40"
          role="tablist"
        >
          <button
            v-for="s in sections" :key="s.value"
            type="button" role="tab"
            :aria-selected="activeValue === s.value"
            class="flex items-center gap-1.5 px-3 py-2 rounded-t-lg text-[0.78rem] font-medium border-b-2 transition-all duration-150 shrink-0 whitespace-nowrap"
            :class="activeValue === s.value
              ? `${pal[s.accent].activeBtn} !rounded-b-none`
              : 'text-[var(--text-color-secondary)] border-transparent hover:bg-[var(--surface-card)]/60 hover:text-[var(--text-color)]'"
            @click="activeValue = s.value"
          >
            <span class="flex items-center justify-center w-5 h-5 rounded-md text-[0.62rem] shrink-0" :class="pal[s.accent].iconBox">
              <i :class="s.icon"/>
            </span>
            <span>{{ s.label }}</span>
            <i v-if="p(s.value).filled === p(s.value).total"
              class="pi pi-check-circle text-emerald-500 text-[0.7rem] shrink-0" />
            <span v-else-if="p(s.value).filled > 0"
              class="text-[0.6rem] text-amber-600 font-bold shrink-0">
              {{ p(s.value).filled }}/{{ p(s.value).total }}
            </span>
          </button>
        </div>

        <Accordion :multiple="false" v-model:value="activeValue">

          <!-- ╔═══════════════════════════════════════════╗
               ║  SEÇÃO 0 — IDENTIDADE  (indigo)          ║
               ║  → header: nome, pronomes, idade, cidade ║
               ║  → card Dados pessoais: todos os campos  ║
               ║  → card Contato: tel, email, canal, CEP  ║
               ╚═══════════════════════════════════════════╝ -->
          <AccordionPanel value="0" :class="`border-b ${pal.indigo.panelBorder}`">
            <AccordionHeader :ref="el => setPanelHeaderRef(el, 0)" :pt="{ root: { class: pal.indigo.bg } }">
              <div class="flex items-center gap-3 w-full pr-2">
                <span class="flex items-center justify-center w-7 h-7 rounded-lg shrink-0 text-[0.78rem]" :class="pal.indigo.iconBox"><i class="pi pi-user"/></span>
                <div class="flex-1 min-w-0">
                  <div class="font-semibold text-[0.88rem]">Identidade</div>
                  <div class="text-[0.68rem] text-[var(--text-color-secondary)] font-normal">Nome · pronomes · CPF · etnia · contato · canal</div>
                </div>
                <Tag v-if="p('0').filled===p('0').total" value="Completo" severity="success" class="text-[0.6rem] shrink-0" />
                <span v-else-if="p('0').filled>0" class="text-[0.67rem] text-amber-600 font-bold shrink-0">{{ p('0').filled }}/{{ p('0').total }}</span>
              </div>
            </AccordionHeader>
            <AccordionContent>
              <div class="p-5">

                <!-- Nome & identidade -->
                <div class="flex items-center gap-2 mb-5">
                  <span class="text-[0.7rem] font-bold uppercase tracking-widest" :class="pal.indigo.divTxt">Nome & identidade</span>
                  <div class="flex-1 h-px" :class="pal.indigo.divLine"/>
                </div>
                <div class="grid grid-cols-1 gap-6 xl:grid-cols-2 mb-7">

                  <!-- Nome completo — full width -->
                  <div class="xl:col-span-2">
                    <FloatLabel variant="on">
                      <IconField>
                        <InputIcon class="pi pi-user"/>
                        <InputText
                          id="f_nome"
                          v-model="form.nome_completo"
                          class="w-full"
                          variant="filled"
                          :invalid="submitted && !String(form.nome_completo || '').trim()"
                        />
                      </IconField>
                      <label for="f_nome">Nome completo *</label>
                    </FloatLabel>
                    <small
                      v-if="submitted && !String(form.nome_completo || '').trim()"
                      class="mt-2 text-[0.85rem] text-red-500 flex items-center gap-1.5"
                    >
                      <i class="pi pi-exclamation-circle text-[0.78rem]"/>
                      <span>Campo obrigatório.</span>
                    </small>
                  </div>

                  <!-- Nome social -->
                  <div>
                    <FloatLabel variant="on">
                      <InputText id="f_nome_social" v-model="form.nome_social" class="w-full" variant="filled"/>
                      <label for="f_nome_social">Nome social</label>
                    </FloatLabel>
                  </div>

                  <!-- Pronomes -->
                  <div>
                    <FloatLabel variant="on">
                      <Select id="f_pronomes" v-model="form.pronomes" :options="pronounsOpts" optionLabel="label" optionValue="value" class="w-full" variant="filled"/>
                      <label for="f_pronomes">Pronomes</label>
                    </FloatLabel>
                  </div>

                  <!-- Data de nascimento — InputGroup com idade calculada como addon à direita -->
                  <div>
                    <FloatLabel variant="on">
                      <InputGroup>
                        <InputGroupAddon><i class="pi pi-calendar"/></InputGroupAddon>
                        <InputMask id="f_nasc" v-model="form.data_nascimento" mask="99-99-9999" :unmask="false" variant="filled"/>
                        <InputGroupAddon v-if="ageLabel !== '—'" class="font-semibold text-[var(--primary-color)]">{{ ageLabel }}</InputGroupAddon>
                      </InputGroup>
                      <label for="f_nasc">Data de nascimento</label>
                    </FloatLabel>
                  </div>

                  <!-- Gênero -->
                  <div>
                    <FloatLabel variant="on">
                      <Select id="f_genero" v-model="form.genero" :options="genderOptions" optionLabel="label" optionValue="value" class="w-full" variant="filled"/>
                      <label for="f_genero">Gênero</label>
                    </FloatLabel>
                  </div>

                  <!-- Estado civil -->
                  <div>
                    <FloatLabel variant="on">
                      <Select id="f_ec" v-model="form.estado_civil" :options="maritalOpts" optionLabel="label" optionValue="value" class="w-full" variant="filled"/>
                      <label for="f_ec">Estado civil</label>
                    </FloatLabel>
                  </div>

                  <!-- CPF -->
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-id-card"/><InputMask id="f_cpf" v-model="form.cpf" mask="999.999.999-99" :unmask="false" class="w-full" variant="filled"/></IconField>
                      <label for="f_cpf">CPF</label>
                    </FloatLabel>
                  </div>

                  <!-- RG -->
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-id-card"/><InputText id="f_rg" v-model="form.rg" class="w-full" variant="filled"/></IconField>
                      <label for="f_rg">RG</label>
                    </FloatLabel>
                  </div>

                  <!-- Etnia -->
                  <div>
                    <FloatLabel variant="on">
                      <Select id="f_etnia" v-model="form.etnia" :options="etniaOpts" optionLabel="label" optionValue="value" class="w-full" variant="filled"/>
                      <label for="f_etnia">Etnia / raça</label>
                    </FloatLabel>
                  </div>

                  <!-- Naturalidade -->
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-map"/><InputText id="f_nat" v-model="form.naturalidade" class="w-full" variant="filled"/></IconField>
                      <label for="f_nat">Naturalidade</label>
                    </FloatLabel>
                  </div>

                  <!-- Profissão -->
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-briefcase"/><InputText id="f_prof" v-model="form.profissao" class="w-full" variant="filled"/></IconField>
                      <label for="f_prof">Profissão</label>
                    </FloatLabel>
                  </div>

                  <!-- Escolaridade -->
                  <div>
                    <FloatLabel variant="on">
                      <Select id="f_esc" v-model="form.escolaridade" :options="escolaridadeOpts" optionLabel="label" optionValue="value" class="w-full" variant="filled"/>
                      <label for="f_esc">Escolaridade</label>
                    </FloatLabel>
                  </div>
                </div>

                <!-- Contato — alimenta card "Contato" -->
                <div class="flex items-center gap-2 mb-5">
                  <span class="text-[0.7rem] font-bold uppercase tracking-widest" :class="pal.indigo.divTxt">Contato</span>
                  <div class="flex-1 h-px" :class="pal.indigo.divLine"/>
                </div>
                <!-- Telefones (polimórfico — tipo/número/principal/vinculado) -->
                <div class="col-span-full mb-7">
                  <div
                    class="flex items-start gap-3 p-4 mb-5 rounded-xl border transition-colors"
                    :class="submitted && phonesCount === 0
                      ? 'border-red-300 bg-red-50/60 text-red-700'
                      : pal.indigo.infoBox"
                  >
                    <span
                      class="flex items-center justify-center w-9 h-9 rounded-lg shrink-0"
                      :class="submitted && phonesCount === 0
                        ? 'bg-red-100 text-red-600'
                        : pal.indigo.iconBox"
                    >
                      <i class="pi pi-phone text-[0.95rem]"/>
                    </span>
                    <div class="flex-1 min-w-0">
                      <div class="text-[0.95rem] font-semibold leading-tight">Telefones *</div>
                      <div class="text-[0.85rem] mt-1 leading-snug opacity-90">
                        Marque um telefone como <strong>principal</strong> — ele é usado pra cobranças, lembretes automáticos e contato padrão. Número vindo do CRM WhatsApp recebe a etiqueta <strong>"vinculado"</strong>.
                      </div>
                      <div
                        v-if="submitted && phonesCount === 0"
                        class="mt-2 text-[0.85rem] flex items-center gap-1.5 font-semibold"
                      >
                        <i class="pi pi-exclamation-circle text-[0.78rem]"/>
                        <span>Adicione pelo menos um telefone.</span>
                      </div>
                    </div>
                  </div>
                  <ContactPhonesEditor
                    ref="phonesEditorRef"
                    entity-type="patient"
                    :entity-id="patientId || null"
                    @change="(arr) => phonesCount = (arr || []).length"
                  />
                </div>

                <!-- Emails (polimórfico — tipo/endereço/principal) -->
                <div class="col-span-full mb-7">
                  <div
                    class="flex items-start gap-3 p-4 mb-5 rounded-xl border transition-colors"
                    :class="submitted && emailsCount === 0
                      ? 'border-red-300 bg-red-50/60 text-red-700'
                      : pal.indigo.infoBox"
                  >
                    <span
                      class="flex items-center justify-center w-9 h-9 rounded-lg shrink-0"
                      :class="submitted && emailsCount === 0
                        ? 'bg-red-100 text-red-600'
                        : pal.indigo.iconBox"
                    >
                      <i class="pi pi-envelope text-[0.95rem]"/>
                    </span>
                    <div class="flex-1 min-w-0">
                      <div class="text-[0.95rem] font-semibold leading-tight">E-mails *</div>
                      <div class="text-[0.85rem] mt-1 leading-snug opacity-90">
                        Marque um e-mail como <strong>principal</strong> — ele é usado pra envio de recibos, comprovantes e comunicações oficiais.
                      </div>
                      <div
                        v-if="submitted && emailsCount === 0"
                        class="mt-2 text-[0.85rem] flex items-center gap-1.5 font-semibold"
                      >
                        <i class="pi pi-exclamation-circle text-[0.78rem]"/>
                        <span>Adicione pelo menos um e-mail.</span>
                      </div>
                    </div>
                  </div>
                  <ContactEmailsEditor
                    ref="emailsEditorRef"
                    entity-type="patient"
                    :entity-id="patientId || null"
                    @change="(arr) => emailsCount = (arr || []).length"
                  />
                </div>

                <div class="grid grid-cols-1 gap-4 xl:grid-cols-2">

                  <!-- Canal preferido -->
                  <div>
                    <FloatLabel variant="on">
                      <Select id="f_canal" v-model="form.canal_preferido" :options="canalOpts" optionLabel="label" optionValue="value" class="w-full" variant="filled"/>
                      <label for="f_canal">Canal preferido de contato</label>
                    </FloatLabel>
                  </div>

                  <!-- Horário de contato -->
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-clock"/><InputText id="f_horario" v-model="form.horario_contato" class="w-full" variant="filled" placeholder="Ex: 08h–18h"/></IconField>
                      <label for="f_horario">Horário de contato</label>
                    </FloatLabel>
                  </div>

                  <!-- Observações de endereço -->
                  <div class="xl:col-span-2">
                    <FloatLabel variant="on">
                      <Textarea id="f_obs" v-model="form.observacoes" rows="2" class="w-full" variant="filled"/>
                      <label for="f_obs">Observações de endereço</label>
                    </FloatLabel>
                    <div class="mt-2 text-[0.85rem] text-[var(--primary-color)] flex items-center gap-1.5">
                      <i class="pi pi-info-circle text-[0.78rem]"/>
                      <span>Ex: Próximo ao posto, portão azul, sem interfone.</span>
                    </div>
                  </div>
                </div>
              </div>
            </AccordionContent>
          </AccordionPanel>

          <!-- ╔═══════════════════════════════════════════╗
               ║  SEÇÃO 1 — ENDEREÇO  (teal)              ║
               ║  → card Contato: "CEP · São Carlos"      ║
               ║  → header: "São Carlos, SP"              ║
               ╚═══════════════════════════════════════════╝ -->
          <AccordionPanel value="1" :class="`border-b ${pal.teal.panelBorder}`">
            <AccordionHeader :ref="el => setPanelHeaderRef(el, 1)" :pt="{ root: { class: pal.teal.bg } }">
              <div class="flex items-center gap-3 w-full pr-2">
                <span class="flex items-center justify-center w-7 h-7 rounded-lg shrink-0 text-[0.78rem]" :class="pal.teal.iconBox"><i class="pi pi-map-marker"/></span>
                <div class="flex-1 min-w-0">
                  <div class="font-semibold text-[0.88rem]">Endereço</div>
                  <div class="text-[0.68rem] text-[var(--text-color-secondary)] font-normal">CEP auto-preenchido · cidade e UF aparecem no header</div>
                </div>
                <Tag v-if="p('1').filled===p('1').total" value="Completo" severity="success" class="text-[0.6rem] shrink-0" />
                <span v-else-if="p('1').filled>0" class="text-[0.67rem] text-amber-600 font-bold shrink-0">{{ p('1').filled }}/{{ p('1').total }}</span>
              </div>
            </AccordionHeader>
            <AccordionContent>
              <div class="p-5">
                <div :class="`flex items-start gap-2 mb-4 p-2.5 rounded-lg border text-[0.75rem] ${pal.teal.infoBox}`">
                  <i class="pi pi-lightbulb mt-0.5 shrink-0"/>
                  <span>Digite o CEP e cidade, estado, bairro e logradouro são preenchidos automaticamente via <strong>ViaCEP</strong>.</span>
                </div>
                <div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-map-marker"/><InputText id="f_cep" v-model="form.cep" class="w-full" @blur="onCepBlur" variant="filled" placeholder="00000-000"/></IconField>
                      <label for="f_cep">CEP</label>
                    </FloatLabel>
                  </div>
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-globe"/><InputText id="f_pais" v-model="form.pais" class="w-full" variant="filled"/></IconField>
                      <label for="f_pais">País</label>
                    </FloatLabel>
                  </div>
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-building"/><InputText id="f_city" v-model="form.cidade" class="w-full" variant="filled"/></IconField>
                      <label for="f_city">Cidade</label>
                    </FloatLabel>
                  </div>
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-compass"/><InputText id="f_uf" v-model="form.estado" class="w-full" variant="filled"/></IconField>
                      <label for="f_uf">Estado (UF)</label>
                    </FloatLabel>
                  </div>
                  <div class="xl:col-span-2">
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-map"/><InputText id="f_addr" v-model="form.endereco" class="w-full" variant="filled"/></IconField>
                      <label for="f_addr">Logradouro</label>
                    </FloatLabel>
                  </div>
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-hashtag"/><InputText id="f_num" v-model="form.numero" class="w-full" variant="filled"/></IconField>
                      <label for="f_num">Número</label>
                    </FloatLabel>
                  </div>
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-map-marker"/><InputText id="f_bairro" v-model="form.bairro" class="w-full" variant="filled"/></IconField>
                      <label for="f_bairro">Bairro</label>
                    </FloatLabel>
                  </div>
                  <div class="xl:col-span-2">
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-align-left"/><InputText id="f_compl" v-model="form.complemento" class="w-full" variant="filled"/></IconField>
                      <label for="f_compl">Complemento</label>
                    </FloatLabel>
                  </div>
                </div>
              </div>
            </AccordionContent>
          </AccordionPanel>

          <!-- ╔═══════════════════════════════════════════════╗
               ║  SEÇÃO 2 — CLÍNICO & ORIGEM  (violet)       ║
               ║  → header: badges Ativa / Unimed / Clínica  ║
               ║  → header: chips Ansiedade / TCC (tags)     ║
               ║  → card Origem: como chegou, pag, saída     ║
               ╚═══════════════════════════════════════════════╝ -->
          <AccordionPanel value="2" :class="`border-b ${pal.violet.panelBorder}`">
            <AccordionHeader :ref="el => setPanelHeaderRef(el, 2)" :pt="{ root: { class: pal.violet.bg } }">
              <div class="flex items-center gap-3 w-full pr-2">
                <span class="flex items-center justify-center w-7 h-7 rounded-lg shrink-0 text-[0.78rem]" :class="pal.violet.iconBox"><i class="pi pi-heart"/></span>
                <div class="flex-1 min-w-0">
                  <div class="font-semibold text-[0.88rem]">Clínico & origem</div>
                  <div class="text-[0.68rem] text-[var(--text-color-secondary)] font-normal">Badges de status/convênio · tags · card Origem</div>
                </div>
                <Tag v-if="p('2').filled===p('2').total" value="Completo" severity="success" class="text-[0.6rem] shrink-0" />
                <span v-else-if="p('2').filled>0" class="text-[0.67rem] text-amber-600 font-bold shrink-0">{{ p('2').filled }}/{{ p('2').total }}</span>
              </div>
            </AccordionHeader>
            <AccordionContent>
              <div class="p-5">

                <!-- Preview dos badges ao vivo -->
                <div v-if="form.status||convenioNome||form.patient_scope||tagIdsSelecionadas.length"
                  class="flex flex-wrap items-center gap-1.5 p-2.5 mb-4 rounded-lg border border-violet-200/50 bg-white/50">
                  <span class="text-[0.6rem] text-violet-400 w-full -mb-0.5">Preview dos badges no header:</span>
                  <Tag v-if="form.status"        :value="form.status"        severity="success"   class="text-[0.68rem]"/>
                  <Tag v-if="convenioNome"        :value="convenioNome"       severity="info"      class="text-[0.68rem]"/>
                  <Tag v-if="form.patient_scope" :value="form.patient_scope" severity="secondary" class="text-[0.68rem]"/>
                  <span
                    v-for="tid in tagIdsSelecionadas.slice(0,4)" :key="tid"
                    class="inline-flex items-center px-2 py-0.5 rounded-full text-[0.65rem] font-semibold text-white"
                    :style="{ background: tags.find(t=>t.id===tid)?.color||'#6366F1' }"
                  >
                    {{ tags.find(t=>t.id===tid)?.name||'' }}
                  </span>
                </div>

                <!-- Situação clínica -->
                <div class="flex items-center gap-2 mb-5">
                  <span class="text-[0.7rem] font-bold uppercase tracking-widest" :class="pal.violet.divTxt">Situação clínica</span>
                  <div class="flex-1 h-px" :class="pal.violet.divLine"/>
                </div>
                <div class="grid grid-cols-1 gap-4 xl:grid-cols-3 mb-7">
                  <div>
                    <FloatLabel variant="on">
                      <Select id="f_status" v-model="form.status" :options="statusOpts" optionLabel="label" optionValue="value" class="w-full" variant="filled"/>
                      <label for="f_status">Status</label>
                    </FloatLabel>
                  </div>
                  <div>
                    <!-- CONVÊNIO — seleciona de insurance_plans, máx 1 -->
                    <div class="text-[0.72rem] text-[var(--text-color-secondary)] mb-1.5 font-medium">Convênio</div>
                    <div class="flex gap-2 items-start">
                      <!-- Chip do selecionado -->
                      <div
                        v-if="convenioNome"
                        class="flex-1 flex items-center gap-2 px-3 py-2 rounded-lg border border-blue-200/70 bg-blue-50/60 min-w-0"
                      >
                        <i class="pi pi-shield text-blue-500 text-[0.8rem] shrink-0"/>
                        <span class="text-[0.85rem] font-semibold text-blue-700 truncate flex-1">{{ convenioNome }}</span>
                        <button type="button" class="shrink-0 text-blue-300 hover:text-blue-600 transition-colors" title="Remover convênio" @click="clearConvenio">
                          <i class="pi pi-times text-[0.7rem]"/>
                        </button>
                      </div>
                      <!-- Placeholder quando vazio -->
                      <div
                        v-else
                        class="flex-1 flex items-center gap-2 px-3 py-2.5 rounded-lg border border-dashed border-[var(--surface-border)] bg-[var(--surface-ground)] cursor-pointer hover:border-blue-300 hover:bg-blue-50/40 transition-all"
                        @click="showConvenioDlg = true"
                      >
                        <i class="pi pi-shield text-[var(--text-color-secondary)] opacity-40 text-[0.8rem]"/>
                        <span class="text-[0.82rem] text-[var(--text-color-secondary)] opacity-60">Nenhum convênio</span>
                      </div>
                      <Button
                        :icon="convenioNome ? 'pi pi-pencil' : 'pi pi-plus'"
                        severity="secondary" outlined
                        class="shrink-0 h-[38px] w-[38px]"
                        :title="convenioNome ? 'Trocar convênio' : 'Selecionar convênio'"
                        @click="showConvenioDlg = true"
                      />
                    </div>
                  </div>
                  <div>
                    <FloatLabel variant="on">
                      <Select id="f_scope" v-model="form.patient_scope" :options="scopeOpts" optionLabel="label" optionValue="value" class="w-full" variant="filled"/>
                      <label for="f_scope">Escopo de atendimento</label>
                    </FloatLabel>
                  </div>
                </div>

                <!-- Organização: grupo + tags -->
                <div class="flex items-center gap-2 mb-5">
                  <span class="text-[0.7rem] font-bold uppercase tracking-widest" :class="pal.violet.divTxt">Organização</span>
                  <div class="flex-1 h-px" :class="pal.violet.divLine"/>
                </div>
                <div class="grid grid-cols-1 gap-6 xl:grid-cols-2 mb-7">
                  <div class="flex gap-2">
                    <div class="flex-1 min-w-0">
                      <FloatLabel variant="on">
                        <IconField><InputIcon class="pi pi-folder-open"/>
                          <Select id="f_grupo" v-model="grupoIdSelecionado" :options="groups" optionLabel="name" optionValue="id" class="w-full pl-[25px]" showClear filter variant="filled"/>
                        </IconField>
                        <label for="f_grupo">Grupo</label>
                      </FloatLabel>
                      <div class="mt-2 text-[0.85rem] text-[var(--primary-color)] flex items-center gap-1.5">
                        <i class="pi pi-info-circle text-[0.78rem]"/>
                        <span>Define o modelo de anamnese aplicado ao paciente.</span>
                      </div>
                    </div>
                    <Button icon="pi pi-plus" severity="secondary" outlined class="shrink-0 h-[42px] mt-[1px]" title="Criar grupo" @click="openGroupDlg"/>
                  </div>
                  <div class="flex gap-2">
                    <div class="flex-1 min-w-0">
                      <FloatLabel variant="on">
                        <IconField><InputIcon class="pi pi-tag"/>
                          <MultiSelect id="f_tags" v-model="tagIdsSelecionadas" :options="tags" optionLabel="name" optionValue="id" class="w-full pl-[25px]" display="chip" filter variant="filled"/>
                        </IconField>
                        <label for="f_tags">Tags clínicas</label>
                      </FloatLabel>
                    </div>
                    <Button icon="pi pi-plus" severity="secondary" outlined class="shrink-0 h-[42px] mt-[1px]" title="Criar tag" @click="openTagDlg"/>
                  </div>
                </div>

                <!-- Origem — alimenta card "Origem" do detalhe -->
                <div class="flex items-center gap-2 mb-5">
                  <span class="text-[0.7rem] font-bold uppercase tracking-widest" :class="pal.violet.divTxt">Origem</span>
                  <div class="flex-1 h-px" :class="pal.violet.divLine"/>
                </div>
                <div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-megaphone"/><InputText id="f_lead" v-model="form.onde_nos_conheceu" class="w-full" variant="filled"/></IconField>
                      <label for="f_lead">Como chegou até mim?</label>
                    </FloatLabel>
                  </div>
                  <div>
                    <!-- ENCAMINHADO POR — múltiplos médicos -->
                    <div class="text-[0.72rem] text-[var(--text-color-secondary)] mb-1.5 font-medium">Encaminhado por</div>

                    <!-- Chips dos médicos selecionados via picker -->
                    <div v-if="medicosSelecionados.length" class="flex flex-wrap gap-1.5 mb-2">
                      <div
                        v-for="(m, idx) in medicosSelecionados" :key="m.id"
                        class="flex items-center gap-1.5 pl-2 pr-1 py-1 rounded-full border border-teal-200/70 bg-teal-50/70 max-w-full"
                      >
                        <div class="w-5 h-5 rounded-full bg-teal-200 flex items-center justify-center text-[0.6rem] font-black text-teal-800 shrink-0">
                          {{ (m.nome||'?').split(' ').map(w=>w[0].toUpperCase()).slice(0,1).join('') }}
                        </div>
                        <span class="text-[0.77rem] font-semibold text-teal-700 truncate max-w-[130px]">{{ m.nome }}</span>
                        <span v-if="m.especialidade" class="text-[0.65rem] text-teal-500/80 hidden sm:inline">· {{ m.especialidade }}</span>
                        <button type="button" class="shrink-0 text-teal-400 hover:text-teal-700 transition-colors p-0.5 rounded" title="Editar dados do médico" @click="openMedicoEdit(m.id)">
                          <i class="pi pi-pencil text-[0.62rem]"/>
                        </button>
                        <button type="button" class="shrink-0 text-teal-300 hover:text-red-500 transition-colors p-0.5 rounded" @click="removeMedico(idx)" title="Remover">
                          <i class="pi pi-times text-[0.65rem]"/>
                        </button>
                      </div>
                    </div>

                    <!-- Fallback: texto salvo no banco (edição sem picker) -->
                    <div
                      v-else-if="form.encaminhado_por && isEdit"
                      class="flex items-center gap-2 mb-2 px-3 py-2 rounded-lg border border-[var(--surface-border)] bg-[var(--surface-ground)] text-[0.82rem] text-[var(--text-color-secondary)]"
                    >
                      <i class="pi pi-user text-[0.75rem] shrink-0 opacity-50"/>
                      <span class="flex-1 truncate">{{ form.encaminhado_por }}</span>
                      <button
                        type="button"
                        class="shrink-0 text-[var(--text-color-secondary)] opacity-40 hover:opacity-80 hover:text-red-500 transition-all text-[0.68rem]"
                        title="Limpar"
                        @click="form.encaminhado_por = ''"
                      >
                        <i class="pi pi-times"/>
                      </button>
                    </div>

                    <Button
                      icon="pi pi-user-plus"
                      :label="medicosSelecionados.length || form.encaminhado_por ? 'Adicionar outro médico' : 'Selecionar médico'"
                      severity="secondary" outlined size="small"
                      class="rounded-full w-full"
                      @click="showMedicoDlg = true"
                    />
                    <div class="mt-2 text-[0.85rem] text-[var(--primary-color)] flex items-center gap-1.5">
                      <i class="pi pi-info-circle text-[0.78rem]"/>
                      <span>Você pode adicionar mais de um profissional de referência.</span>
                    </div>
                  </div>
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-sign-out"/><InputText id="f_saida" v-model="form.motivo_saida" class="w-full" variant="filled" placeholder="Se aplicável"/></IconField>
                      <label for="f_saida">Motivo de saída</label>
                    </FloatLabel>
                  </div>
                </div>
              </div>
            </AccordionContent>
          </AccordionPanel>

          <!-- ╔══════════════════════════════════════════════════╗
               ║  SEÇÃO 3 — REDE DE SUPORTE  (amber)            ║
               ║  → card "Contatos & rede de suporte" do detalhe║
               ║  → is_primario = badge vermelho "emergência"   ║
               ╚══════════════════════════════════════════════════╝ -->
          <AccordionPanel value="3" :class="`border-b ${pal.amber.panelBorder}`">
            <AccordionHeader :ref="el => setPanelHeaderRef(el, 3)" :pt="{ root: { class: pal.amber.bg } }">
              <div class="flex items-center gap-3 w-full pr-2">
                <span class="flex items-center justify-center w-7 h-7 rounded-lg shrink-0 text-[0.78rem]" :class="pal.amber.iconBox"><i class="pi pi-users"/></span>
                <div class="flex-1 min-w-0">
                  <div class="font-semibold text-[0.88rem]">Rede de suporte</div>
                  <div class="text-[0.68rem] text-[var(--text-color-secondary)] font-normal">Alimenta o card "Contatos &amp; rede de suporte" do perfil</div>
                </div>
                <Tag
                  v-if="contatosSuporte.filter(c=>c.nome.trim()).length"
                  :value="`${contatosSuporte.filter(c=>c.nome.trim()).length} contato${contatosSuporte.filter(c=>c.nome.trim()).length>1?'s':''}`"
                  severity="success" class="text-[0.6rem] shrink-0"
                />
                <Tag v-else value="Opcional" severity="secondary" class="text-[0.6rem] shrink-0"/>
              </div>
            </AccordionHeader>
            <AccordionContent>
              <div class="p-5">
                <div :class="`flex items-start gap-2 mb-4 p-2.5 rounded-lg border text-[0.75rem] ${pal.amber.infoBox}`">
                  <i class="pi pi-info-circle mt-0.5 shrink-0"/>
                  <span>Cada contato aqui aparece no card <strong>"Contatos &amp; rede de suporte"</strong> do perfil. O marcado como <strong>emergência primária</strong> recebe badge vermelho.</span>
                </div>

                <!-- Lista de contatos -->
                <div class="flex flex-col gap-3 mb-3">
                  <div
                    v-for="(c, idx) in contatosSuporte" :key="c._k"
                    class="rounded-xl border bg-white/70 p-3.5 transition-colors"
                    :class="c.is_primario ? 'border-red-200' : 'border-amber-200/60'"
                  >
                    <!-- Cabeçalho do contato -->
                    <div class="flex items-center justify-between mb-3">
                      <div class="flex items-center gap-2">
                        <div class="w-8 h-8 rounded-full bg-amber-200/70 flex items-center justify-center font-black text-[0.72rem] text-amber-800 select-none shrink-0">
                          {{ iniciaisFor(c.nome) || String(idx+1) }}
                        </div>
                        <span class="text-[0.82rem] font-semibold text-[var(--text-color)]">Contato {{ idx+1 }}</span>
                        <Tag v-if="c.is_primario" value="emergência" severity="danger" class="text-[0.6rem]"/>
                      </div>
                      <Button icon="pi pi-trash" severity="danger" text rounded size="small" @click="removeContato(idx)" title="Remover contato"/>
                    </div>

                    <!-- Campos -->
                    <div class="grid grid-cols-1 gap-3 xl:grid-cols-2">
                      <div class="xl:col-span-2">
                        <FloatLabel variant="on">
                          <IconField><InputIcon class="pi pi-user"/>
                            <InputText :id="`cn_${idx}`" v-model="c.nome" class="w-full" variant="filled"/>
                          </IconField>
                          <label :for="`cn_${idx}`">Nome completo</label>
                        </FloatLabel>
                      </div>
                      <div>
                        <FloatLabel variant="on">
                          <InputText :id="`cr_${idx}`" v-model="c.relacao" class="w-full" variant="filled" placeholder="Ex: mãe, psiquiatra"/>
                          <label :for="`cr_${idx}`">Relação / papel</label>
                        </FloatLabel>
                      </div>
                      <div>
                        <FloatLabel variant="on">
                          <Select :id="`ct_${idx}`" v-model="c.tipo" :options="tipoContatoOpts" optionLabel="label" optionValue="value" class="w-full" variant="filled"/>
                          <label :for="`ct_${idx}`">Tipo</label>
                        </FloatLabel>
                      </div>
                      <div>
                        <FloatLabel variant="on">
                          <IconField><InputIcon class="pi pi-phone"/>
                            <InputMask :id="`ctel_${idx}`" v-model="c.telefone" mask="(99) 99999-9999" :unmask="false" class="w-full" variant="filled"/>
                          </IconField>
                          <label :for="`ctel_${idx}`">Telefone</label>
                        </FloatLabel>
                      </div>
                      <div>
                        <FloatLabel variant="on">
                          <IconField><InputIcon class="pi pi-envelope"/>
                            <InputText :id="`cemail_${idx}`" v-model="c.email" class="w-full" variant="filled"/>
                          </IconField>
                          <label :for="`cemail_${idx}`">E-mail</label>
                        </FloatLabel>
                      </div>
                      <!-- Emergência primária -->
                      <div class="xl:col-span-2">
                        <div
                          class="flex items-center gap-3 p-2.5 rounded-lg border cursor-pointer select-none"
                          :class="c.is_primario ? 'border-red-300 bg-red-50/70' : 'border-[var(--surface-border)] bg-[var(--surface-section,#f8fafc)]'"
                          @click="c.is_primario = !c.is_primario"
                        >
                          <Checkbox :inputId="`cp_${idx}`" v-model="c.is_primario" :binary="true" @click.stop/>
                          <label :for="`cp_${idx}`" class="cursor-pointer flex-1">
                            <span class="block text-[0.82rem] font-semibold" :class="c.is_primario ? 'text-red-700' : 'text-[var(--text-color)]'">Contato de emergência primário</span>
                            <span class="block text-[0.68rem] text-[var(--text-color-secondary)]">Ganha badge vermelho <strong>"emergência"</strong> no card do perfil.</span>
                          </label>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>

                <Button icon="pi pi-plus" label="Adicionar contato" severity="secondary" outlined class="rounded-full w-full" @click="addContato"/>
                <p v-if="!contatosSuporte.length" class="text-center text-[0.75rem] text-[var(--text-color-secondary)] mt-3 opacity-50">
                  Nenhum contato adicionado ainda.
                </p>
              </div>
            </AccordionContent>
          </AccordionPanel>

          <!-- ╔══════════════════════════════════════════════╗
               ║  SEÇÃO 4 — RESPONSÁVEL  (sky)              ║
               ╚══════════════════════════════════════════════╝ -->
          <AccordionPanel value="4" :class="`border-b ${pal.sky.panelBorder}`">
            <AccordionHeader :ref="el => setPanelHeaderRef(el, 4)" :pt="{ root: { class: pal.sky.bg } }">
              <div class="flex items-center gap-3 w-full pr-2">
                <span class="flex items-center justify-center w-7 h-7 rounded-lg shrink-0 text-[0.78rem]" :class="pal.sky.iconBox"><i class="pi pi-id-card"/></span>
                <div class="flex-1 min-w-0">
                  <div class="font-semibold text-[0.88rem]">Responsável</div>
                  <div class="text-[0.68rem] text-[var(--text-color-secondary)] font-normal">Para menores de idade ou cobrança em terceiro</div>
                </div>
                <Tag v-if="p('4').filled===p('4').total" value="Completo" severity="success" class="text-[0.6rem] shrink-0" />
                <Tag v-else value="Opcional" severity="secondary" class="text-[0.6rem] shrink-0"/>
              </div>
            </AccordionHeader>
            <AccordionContent>
              <div class="p-5">
                <div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
                  <div class="xl:col-span-2">
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-user"/><InputText id="f_rn" v-model="form.nome_responsavel" class="w-full" variant="filled"/></IconField>
                      <label for="f_rn">Nome do responsável</label>
                    </FloatLabel>
                  </div>
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-id-card"/><InputMask id="f_rcpf" v-model="form.cpf_responsavel" mask="999.999.999-99" :unmask="false" class="w-full" variant="filled"/></IconField>
                      <label for="f_rcpf">CPF do responsável</label>
                    </FloatLabel>
                  </div>
                  <div>
                    <FloatLabel variant="on">
                      <IconField><InputIcon class="pi pi-phone"/><InputMask id="f_rtel" v-model="form.telefone_responsavel" mask="(99) 99999-9999" :unmask="false" class="w-full" variant="filled"/></IconField>
                      <label for="f_rtel">Telefone do responsável</label>
                    </FloatLabel>
                  </div>
                  <div class="xl:col-span-2">
                    <FloatLabel variant="on">
                      <Textarea id="f_robs" v-model="form.observacao_responsavel" rows="3" class="w-full" variant="filled"/>
                      <label for="f_robs">Observações sobre o responsável</label>
                    </FloatLabel>
                  </div>
                  <div class="xl:col-span-2">
                    <div
                      class="flex items-center gap-3 p-3 rounded-lg border cursor-pointer select-none"
                      :class="form.cobranca_no_responsavel ? 'border-sky-300 bg-sky-50/60' : 'border-[var(--surface-border)] bg-[var(--surface-section,#f8fafc)]'"
                      @click="form.cobranca_no_responsavel = !form.cobranca_no_responsavel"
                    >
                      <Checkbox inputId="f_bill" v-model="form.cobranca_no_responsavel" :binary="true" @click.stop/>
                      <div>
                        <label for="f_bill" class="block text-[0.85rem] font-semibold text-[var(--text-color)] cursor-pointer">Cobrar no responsável</label>
                        <div class="text-[0.7rem] text-[var(--text-color-secondary)]">Faturas serão geradas no nome do responsável.</div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </AccordionContent>
          </AccordionPanel>

          <!-- ╔═══════════════════════════════════════════╗
               ║  SEÇÃO 5 — ANOTAÇÕES INTERNAS  (rose)   ║
               ╚═══════════════════════════════════════════╝ -->
          <AccordionPanel value="5">
            <AccordionHeader :ref="el => setPanelHeaderRef(el, 5)" :pt="{ root: { class: pal.rose.bg } }">
              <div class="flex items-center gap-3 w-full pr-2">
                <span class="flex items-center justify-center w-7 h-7 rounded-lg shrink-0 text-[0.78rem]" :class="pal.rose.iconBox"><i class="pi pi-lock"/></span>
                <div class="flex-1 min-w-0">
                  <div class="font-semibold text-[0.88rem]">Anotações internas</div>
                  <div class="text-[0.68rem] text-[var(--text-color-secondary)] font-normal">Visível apenas para a equipe · não vai ao paciente</div>
                </div>
                <Tag v-if="p('5').filled>0" value="Preenchido" severity="success" class="text-[0.6rem] shrink-0"/>
              </div>
            </AccordionHeader>
            <AccordionContent>
              <div class="p-5">
                <div :class="`flex items-start gap-2 mb-4 p-2.5 rounded-lg border text-[0.75rem] ${pal.rose.infoBox}`">
                  <i class="pi pi-shield mt-0.5 shrink-0"/>
                  <span>Campo interno: <strong>não aparece</strong> no cadastro externo nem é compartilhado com o paciente.</span>
                </div>
                <FloatLabel variant="on">
                  <Textarea id="f_notas" v-model="form.notas_internas" rows="8" class="w-full" variant="filled"/>
                  <label for="f_notas">Notas internas</label>
                </FloatLabel>
              </div>
            </AccordionContent>
          </AccordionPanel>

        </Accordion>

        <!-- Footer salvar -->
        <div v-if="!dialogMode" class="flex justify-end gap-2 p-4 border-t border-[var(--surface-border)] bg-[var(--surface-card)]">
          <Button label="Cancelar" severity="secondary" outlined class="rounded-full" @click="goBack"/>
          <Button label="Salvar paciente" icon="pi pi-check" class="rounded-full" :loading="saving" @click="onSubmit"/>
        </div>
      </main>
    </div>
  </div>

  <!-- ══ Dialog: Criar grupo ═══════════════════════════ -->
  <Dialog
    v-model:visible="createGroupDialog"
    modal
    :draggable="false"
    :closable="!createGroupSaving"
    :dismissableMask="!createGroupSaving"
    maximizable
    class="dc-dialog w-[36rem]"
    :breakpoints="{ '1199px': '90vw', '768px': '94vw' }"
    :pt="{
      header:           { class: '!p-3 !rounded-t-[12px] border-b border-[var(--surface-border)] bg-[var(--surface-ground)]' },
      content:          { class: '!p-3' },
      footer:           { class: '!p-0 !rounded-b-[12px] border-t border-[var(--surface-border)] bg-[var(--surface-ground)]' },
      pcCloseButton:    { root: { class: '!rounded-md hover:!text-red-500' } },
      pcMaximizeButton: { root: { class: '!rounded-md hover:!text-primary' } },
    }"
    pt:mask:class="backdrop-blur-xs"
  >
    <template #header>
      <div class="flex w-full items-center justify-between gap-3 px-1">
        <div class="flex items-center gap-3 min-w-0">
          <span
            class="shrink-0 w-3.5 h-3.5 rounded-full border-2 border-white/30 shadow-[0_0_0_3px_rgba(0,0,0,0.08)] transition-colors duration-200"
            :style="{ backgroundColor: newGroup.color || '#6366F1' }"
          />
          <div class="min-w-0">
            <div class="text-base font-semibold truncate">{{ newGroup.name || 'Novo grupo' }}</div>
            <div class="text-xs opacity-50">Criando novo grupo de pacientes</div>
          </div>
        </div>
      </div>
    </template>

    <div class="flex flex-col gap-4">
      <div class="flex items-center gap-3">
        <label for="gn" class="w-16 text-[0.88rem] font-semibold shrink-0">Nome</label>
        <InputText id="gn" v-model="newGroup.name" class="flex-1" variant="filled" autocomplete="off" placeholder="Ex: Crianças"/>
      </div>
      <div class="flex items-center gap-3">
        <label class="w-16 text-[0.88rem] font-semibold shrink-0">Cor</label>
        <div class="flex flex-1 items-center gap-2.5">
          <input v-model="newGroup.color" type="color" class="h-9 w-12 cursor-pointer rounded-md border border-[var(--surface-border)] bg-transparent"/>
          <Chip :label="newGroup.color||'#—'" class="font-semibold" :style="{ backgroundColor:newGroup.color, color:'#fff' }"/>
        </div>
      </div>
      <div v-if="createGroupError" class="text-[0.82rem] text-red-500 flex items-center gap-1.5"><i class="pi pi-exclamation-circle"/>{{ createGroupError }}</div>
    </div>

    <template #footer>
      <div class="flex items-center justify-end gap-2 px-3 py-3">
        <Button label="Cancelar" severity="secondary" text class="rounded-full hover:!text-red-500" :disabled="createGroupSaving" @click="createGroupDialog=false"/>
        <Button label="Salvar" icon="pi pi-check" class="rounded-full" :loading="createGroupSaving" @click="createGroupPersist"/>
      </div>
    </template>
  </Dialog>

  <!-- ══ Dialog: Criar tag ═════════════════════════════ -->
  <Dialog
    v-model:visible="createTagDialog"
    modal
    :draggable="false"
    :closable="!createTagSaving"
    :dismissableMask="!createTagSaving"
    maximizable
    class="dc-dialog w-[36rem]"
    :breakpoints="{ '1199px': '90vw', '768px': '94vw' }"
    :pt="{
      header:           { class: '!p-3 !rounded-t-[12px] border-b border-[var(--surface-border)] bg-[var(--surface-ground)]' },
      content:          { class: '!p-3' },
      footer:           { class: '!p-0 !rounded-b-[12px] border-t border-[var(--surface-border)] bg-[var(--surface-ground)]' },
      pcCloseButton:    { root: { class: '!rounded-md hover:!text-red-500' } },
      pcMaximizeButton: { root: { class: '!rounded-md hover:!text-primary' } },
    }"
    pt:mask:class="backdrop-blur-xs"
  >
    <template #header>
      <div class="flex w-full items-center justify-between gap-3 px-1">
        <div class="flex items-center gap-3 min-w-0">
          <span
            class="shrink-0 w-3.5 h-3.5 rounded-full border-2 border-white/30 shadow-[0_0_0_3px_rgba(0,0,0,0.08)] transition-colors duration-200"
            :style="{ backgroundColor: newTag.color || '#22C55E' }"
          />
          <div class="min-w-0">
            <div class="text-base font-semibold truncate">{{ newTag.name || 'Nova tag' }}</div>
            <div class="text-xs opacity-50">Criando nova tag clínica</div>
          </div>
        </div>
      </div>
    </template>

    <div class="flex flex-col gap-4">
      <div class="flex items-center gap-3">
        <label for="tn" class="w-16 text-[0.88rem] font-semibold shrink-0">Nome</label>
        <InputText id="tn" v-model="newTag.name" class="flex-1" variant="filled" autocomplete="off" placeholder="Ex: Ansiedade"/>
      </div>
      <div class="flex items-center gap-3">
        <label class="w-16 text-[0.88rem] font-semibold shrink-0">Cor</label>
        <div class="flex flex-1 items-center gap-2.5">
          <input v-model="newTag.color" type="color" class="h-9 w-12 cursor-pointer rounded-md border border-[var(--surface-border)] bg-transparent"/>
          <Chip :label="newTag.color||'#—'" class="font-semibold" :style="{ backgroundColor:newTag.color, color:'#fff' }"/>
        </div>
      </div>
      <div v-if="createTagError" class="text-[0.82rem] text-red-500 flex items-center gap-1.5"><i class="pi pi-exclamation-circle"/>{{ createTagError }}</div>
    </div>

    <template #footer>
      <div class="flex items-center justify-end gap-2 px-3 py-3">
        <Button label="Cancelar" severity="secondary" text class="rounded-full hover:!text-red-500" :disabled="createTagSaving" @click="createTagDialog=false"/>
        <Button label="Salvar" icon="pi pi-check" class="rounded-full" :loading="createTagSaving" @click="createTagPersist"/>
      </div>
    </template>
  </Dialog>

  <!-- ══ Dialog: Convênio (CadastroRapidoConvenio) ══════ -->
  <CadastroRapidoConvenio
    v-model:visible="showConvenioDlg"
    :modelValue="convenioId"
    @update:modelValue="convenioId = $event"
    @selected="onConvenioSelected"
  />

  <!-- ══ Dialog: Médico (CadastroRapidoMedico) ══════════ -->
  <CadastroRapidoMedico
    v-model:visible="showMedicoDlg"
    :editId="medicoEditandoId"
    @update:visible="v => { showMedicoDlg = v; if (!v) medicoEditandoId = null }"
    @selected="onMedicoSelected"
    @created="onMedicoSelected"
  />

  <!-- ══ Dialog: Export LGPD (Art. 18, II) ═════════════ -->
  <Dialog
    v-model:visible="lgpdDialog"
    modal
    :draggable="false"
    :closable="!lgpdExporting"
    :dismissableMask="!lgpdExporting"
    class="w-[34rem]"
    :breakpoints="{ '768px': '94vw' }"
    header="Exportar dados do paciente (LGPD)"
  >
    <div class="flex flex-col gap-3 text-sm">
      <Message severity="info" :closable="false" class="text-xs">
        Atendendo ao <strong>art. 18, II da LGPD</strong> — direito de portabilidade do titular.
        O evento será registrado na auditoria da clínica.
      </Message>

      <p>Serão exportados: cadastro, contatos, histórico de status, eventos de agenda, registros financeiros, documentos (metadados), notificações enviadas e auditoria de alterações.</p>

      <p class="text-xs text-surface-500">Escolha o formato:</p>

      <div class="grid grid-cols-2 gap-2">
        <Button label="JSON (completo)" icon="pi pi-code" severity="secondary" outlined :loading="lgpdExporting" @click="onLgpdExport('json')" />
        <Button label="PDF (relatório)" icon="pi pi-file-pdf" :loading="lgpdExporting" @click="onLgpdExport('pdf')" />
      </div>

      <p class="text-[0.7rem] text-surface-400 mt-2">JSON é o formato canônico portável. PDF é legível e adequado pra entregar ao titular.</p>
    </div>

    <template #footer>
      <Button label="Fechar" text :disabled="lgpdExporting" @click="lgpdDialog = false" />
    </template>
  </Dialog>
</template>

<style scoped>
/* Em modo horizontal, esconde os headers do Accordion (a navegação fica nas tabs em cima do main) */
.pcd-horizontal :deep(.p-accordionheader) {
  display: none !important;
}
.pcd-horizontal :deep(.p-accordion-header) {
  display: none !important;
}
.pcd-horizontal :deep(.p-accordioncontent),
.pcd-horizontal :deep(.p-accordion-content) {
  border: none !important;
}

/* Tira o padding interno do wrapper do AccordionContent — o conteúdo já tem
   o próprio padding (.p-5) por seção, então o do PrimeVue duplicava o
   espaçamento e dava sensação de elementos descolados. */
:deep(.p-accordioncontent-content) {
  padding: 0 !important;
}
</style>
