<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/AgendaEventDialog.vue
| Data: 2026
| Local: São Carlos/SP — Brasil
|--------------------------------------------------------------------------
| © 2026 — Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { computed, ref, watch, nextTick, onBeforeUnmount } from 'vue';
import { generateGoogleCalendarLink, formatGCalDate, addMinutesToHHMM } from '@/utils/googleCalendarLink';
import { useRouter, useRoute } from 'vue-router';
import Select from 'primevue/select';
import Textarea from 'primevue/textarea';
import DatePicker from 'primevue/datepicker';
import InputNumber from 'primevue/inputnumber';
// RadioButton removido (auto-import via PrimeVueResolver caso necessário no template)
import Message from 'primevue/message';
import { useConfirm } from 'primevue/useconfirm';
import { useToast } from 'primevue/usetoast';
import { supabase } from '@/lib/supabase/client';
import { tenantDb } from '@/lib/supabase/tenantClient';
import ComponentCadastroRapido from '@/components/ComponentCadastroRapido.vue';
import PatientCadastroDialog from '@/components/ui/PatientCadastroDialog.vue';
import AgendaEventoFinanceiroPanel from '@/components/agenda/AgendaEventoFinanceiroPanel.vue';
import ServiceQuickCreateDialog from './ServiceQuickCreateDialog.vue';
import InsurancePlanQuickCreateDialog from './InsurancePlanQuickCreateDialog.vue';
import InsurancePlanServiceQuickCreateDialog from './InsurancePlanServiceQuickCreateDialog.vue';
import { useServices } from '@/features/agenda/composables/useServices';
import { useCommitmentServices } from '@/features/agenda/composables/useCommitmentServices';
import { usePatientDiscounts } from '@/features/agenda/composables/usePatientDiscounts';
import { useInsurancePlans } from '@/features/agenda/composables/useInsurancePlans';
// getPatientAgendaPermissions agora é importado dentro do composer (1B)

// A66 sub-sessão 1A — helpers PUROS extraídos pra módulo isolado e
// testável. Antes viviam inline neste arquivo (24 funções, ~150 linhas).
// Agora vem de um único import; lógica idêntica, só repousada.
import {
    patientInitials,
    fmtBRL,
    fmtJornadaHora,
    fmtDateBR,
    fmtDateBRLong,
    fmtTime,
    fmtDuracao,
    nomeDiaSemana,
    fmtWeekdayShort,
    fmtDayNum,
    fmtMonthShort,
    hhmmToMin,
    minToHHMM,
    isPast,
    isNativeSession,
    calcFinalPrice,
    labelStatusSessao,
    statusSeverity,
    statusExtraClass
} from '@/features/agenda/composables/agendaEventHelpers';

// A66 sub-sessão 1B — composable factory com state + computeds derivados.
import { useAgendaEventComposer } from '@/features/agenda/composables/useAgendaEventComposer';

// A66 sub-sessão 1C-i — watchers + handlers de save/delete.
import { useAgendaEventActions } from '@/features/agenda/composables/useAgendaEventActions';

// A66 sub-sessão 1C-ii-a — patient picker + billing items + 2 watchers
// (form.commitment_id auto-fill price, form.insurance_plan_id limpa items).
import { useAgendaEventPickerBilling } from '@/features/agenda/composables/useAgendaEventPickerBilling';

// A66 sub-sessão 1C-ii-b — lifecycle: 4 watchers (modelValue init, tenant
// scope, solicitação pendente, online slots) + series pills + selectSlot
// + quick-creates wiring + onSendManualReminder.
import { useAgendaEventLifecycle } from '@/features/agenda/composables/useAgendaEventLifecycle';

const props = defineProps({
    modelValue: { type: Boolean, default: false },
    eventRow: { type: Object, default: null },
    initialStartISO: { type: String, default: '' },
    initialEndISO: { type: String, default: '' },

    ownerId: { type: String, default: '' },
    planOwnerId: { type: String, default: '' }, // owner dos convênios (clínica); fallback: ownerId
    allowOwnerEdit: { type: Boolean, default: false },
    ownerOptions: { type: Array, default: () => [] },

    tenantId: { type: String, default: '' },

    commitmentOptions: { type: Array, default: () => [] },
    presetCommitmentId: { type: [String, null], default: null },
    lockCommitment: { type: Boolean, default: false },

    restrictPatientsToOwner: { type: Boolean, default: false },
    patientScopeOwnerId: { type: String, default: null },

    workRules: { type: Array, default: () => [] },
    blockedDates: { type: Array, default: () => [] },
    agendaSettings: { type: Object, default: null },
    allEvents: { type: Array, default: () => [] },
    pausasSemanais: { type: Array, default: () => [] },
    feriados: { type: Array, default: () => [] },

    // Rota para cadastro completo de paciente (abre em nova aba)
    newPatientRoute: { type: String, default: '' },

    // ── Locks aditivos (default false — comportamento atual) ──────────
    // Usados pelo MelissaPaciente.vue pra abrir esse dialog ja em
    // contexto de Sessao + paciente fixo do prontuario. Os 5 callsites
    // existentes (TherapistDashboard, PatientsListPage, MelissaAgenda,
    // MelissaAgendamentosRecebidos, MelissaLayout) seguem com defaults.
    //
    // lockType=true: pula o step de escolha de tipo (commitment cards) e
    //   vai direto pro form. Espera que eventRow ja venha com tipo+commitment
    //   resolvidos (no MelissaPaciente: tipo='sessao').
    //
    // lockPatient=true: esconde os botoes "trocar"/"limpar" do paciente
    //   e mostra o icon de lock (mesma UX do patientLocked computed que
    //   ja existia pra modo edit, agora cobre tambem cenarios "criar sessao
    //   pra paciente fixo").
    lockType: { type: Boolean, default: false },
    lockPatient: { type: Boolean, default: false },

    // occurrenceMode=true: dialog empilhado pra editar UMA ocorrencia da
    // serie (acionado pelo botao "Editar" das pills). Layout enxuto:
    // titulo "Pacote · X de Y Sessoes", apenas 4 cards (recorrencia,
    // status, horario, escopo). O card "Aplicar alteracoes em" some do
    // dialog pai e migra pra ca. Adicionado 2026-05-12.
    occurrenceMode: { type: Boolean, default: false },

    // serieRefreshTick: contador que bumpa quando o 2º dialog empilhado
    // salva uma ocorrencia. Watch interno re-roda loadSerieEvents pra
    // atualizar status das pills no dialog pai. So usado no dialog pai
    // (no occurrenceMode é ignorado). Adicionado 2026-05-12.
    serieRefreshTick: { type: Number, default: 0 },

    // blockOverlapWarning: aviso quando o slot escolhido cai em cima de
    // um bloqueio próprio do terapeuta. Renderizado como Message warn no
    // topo do step 1. Shape: { titulo: string } ou null. O fluxo NÃO é
    // bloqueado (só agendador público veta); a mensagem só sinaliza.
    // Substitui o toast antigo que ficava atrás do overlay do dialog.
    blockOverlapWarning: { type: Object, default: null }
});

const emit = defineEmits(['update:modelValue', 'save', 'delete', 'updateSeriesEvent', 'editSeriesOccurrence', 'updated', 'usar-sessao', 'revogar-sessao', 'cobranca-atualizada']);

// Helper: chamado pelo AgendaEventoFinanceiroPanel quando a cobrança
// muda (gerada, paga, cancelada). Refresha estado interno do dialog
// E bubble pra MelissaLayout disparar refetch da agenda (sem isso, o
// card do FC fica com paymentState stale até trocar de view).
function _onCobrancaAtualizada() {
    loadOccFinancialRecord();
    emit('cobranca-atualizada');
}
const confirm = useConfirm();
const toast = useToast();
const router = useRouter();
const route = useRoute();

// ──────────────────────────────────────────────────────────────────
// A66 sub-sessão 1B — state + computeds derivados extraídos pra
// composable factory (testado isoladamente em useAgendaEventComposer.spec).
// Refs externos (commitmentItems, serieEvents) declarados aqui antes
// pra serem passados via extras — o composer usa em canSave e isFirstOccurrence.
// ──────────────────────────────────────────────────────────────────
const commitmentItems = ref([]);
const serieEvents = ref([]);

const _composer = useAgendaEventComposer(props, emit, { commitmentItems, serieEvents });
const {
    visible,
    step,
    editScope,
    recorrenciaType,
    diasSelecionados,
    qtdSessoesMode,
    qtdSessoesCustom,
    dataLimiteManual,
    billingType,
    form,
    isEdit,
    allowBack,
    hasSerie,
    currentRecurrenceDate,
    isFirstOccurrence,
    editScopeOptions,
    qtdSessoesEfetiva,
    diaSemanaRecorrencia,
    proximasOcorrencias,
    dataFimCalculada,
    totalOcorrencias,
    sessoesForaDoPlano,
    ocorrenciasComConflito,
    totalConflitos,
    commitmentCards,
    selectedCommitment,
    selectedCommitmentName,
    selectedCommitmentFields,
    requiresPatient,
    isSessionEvent,
    patientLocked,
    hasInsurance,
    agendaPerms,
    isSessionFuture,
    isArchivedPastEdit,
    isInativoFutureEdit,
    statusOptionsFiltered,
    startTimeDate,
    inicioDateTime,
    fimDateTime,
    dataHoraDisplay,
    previewRange,
    computedTitulo,
    headerTitle,
    canSave,
    timeConflict,
    toggleDiaSelecionado,
    isForaDoPlano,
    resetForm
} = _composer;

// Lock de tipo: quando lockType=true (ex: MelissaPaciente), simula o
// click no card "Sessão" do step 1. Chama selectCommitment com o
// commitment native_key='session' — isso seta form.commitment_id +
// extra_fields + step.value=2. Sem isso, pular pro step 2 sem
// commitment_id deixaria isSessionEvent=false e esconderia jornada,
// billing (particular/convenio/gratuito) e frequencia.
//
// Re-roda quando commitmentOptions popula tarde (caso usual: dialog
// abre antes do tenant load terminar). Idempotente: so chama se ainda
// nao tem commitment_id ou step != 2.
watch(
    [() => props.lockType, () => props.modelValue, () => props.commitmentOptions],
    ([locked, open]) => {
        if (!locked || !open) return;
        const sessao = commitmentCards.value.find((c) => c.native_key === 'session');
        if (!sessao) return;
        if (!form.value.commitment_id || form.value.commitment_id !== sessao.id) {
            selectCommitment(sessao);
        } else if (step.value !== 2) {
            step.value = 2;
        }
    },
    { immediate: true }
);

// ── recorrência: opções estáticas (consts data — não migradas) ──
const freqOpcoes = [
    { value: 'avulsa', label: 'Avulsa' },
    { value: 'semanal', label: 'Semanal' },
    { value: 'quinzenal', label: 'Quinzenal' },
    { value: 'diasEspecificos', label: 'Dias específicos' }
];

const diasSemanaOpcoes = [
    { value: 1, short: 'Seg' },
    { value: 2, short: 'Ter' },
    { value: 3, short: 'Qua' },
    { value: 4, short: 'Qui' },
    { value: 5, short: 'Sex' },
    { value: 6, short: 'Sáb' },
    { value: 0, short: 'Dom' }
];

function setHoje() {
    form.value.dia = new Date();
}

const qtdSessoesOpcoes = [
    { value: '4', label: '4 sessões' },
    { value: '8', label: '8 sessões' },
    { value: '12', label: '12 sessões' },
    { value: 'personalizar', label: 'Personalizar' }
];

// Linha superior de cada opção — equivalente em meses, varia por frequência.
// Semanal: 4/8/12 sessões = 1/2/3 meses. Quinzenal: 4/8/12 = 2/4/6 meses.
// Outros tipos (diasEspecificos, avulsa): sem topLabel.
function qtdSessoesTopLabel(value) {
    if (value === 'personalizar') return null;
    const t = recorrenciaType.value;
    if (t === 'semanal') {
        if (value === '4') return '1 mês';
        if (value === '8') return '2 meses';
        if (value === '12') return '3 meses';
    }
    if (t === 'quinzenal') {
        if (value === '4') return '2 meses';
        if (value === '8') return '4 meses';
        if (value === '12') return '6 meses';
    }
    return null;
}
// recorrenciaType, diasSelecionados, toggleDiaSelecionado, qtdSessoesMode,
// qtdSessoesCustom, qtdSessoesEfetiva, diaSemanaRecorrencia → composer (1B)

// proximasOcorrencias, dataFimCalculada, totalOcorrencias, dataLimiteManual,
// isForaDoPlano, sessoesForaDoPlano, conflictForDate, ocorrenciasComConflito,
// totalConflitos, commitmentCards, form (+ resetForm) → composer (1B)
// isNativeSession → agendaEventHelpers (1A)

// Watcher status confirm (cancelado/remarcado), watcher billingType,
// watcher [paciente_id, dia], onSave, onDelete, onEncerrarSerie
// → useAgendaEventActions (1C-i). Refs internos (_skipStatusWatch,
// _prevStatus, _restoringConvenio, samePatientConflict) vêm do composable
// abaixo, declarado depois das dependências (services/insurancePlans/etc).

// ── Precificação / Serviços ─────────────────────────────────────────
const { services, getDefaultPrice, load: loadServices } = useServices();
const { loadItems: _csLoadItems, saveItems: saveCommitmentItems, loadItemsOrTemplate: _csLoadItemsOrTemplate } = useCommitmentServices();
const { loadActive: loadActiveDiscount } = usePatientDiscounts();
const { plans: insurancePlans, load: loadInsurancePlans } = useInsurancePlans();
const selectedPlanService = ref(null);

// Quick-create dialogs (serviceQuickDlgOpen/insuranceQuickDlgOpen) +
// openServiceQuickCreate / onServiceCreated / openInsuranceQuickCreate /
// onInsuranceCreated → useAgendaEventLifecycle (1C-ii-b)

const activePlans = computed(() => insurancePlans.value.filter((p) => p.active !== false));

const planServices = computed(() => {
    if (!form.value.insurance_plan_id) return [];
    return (insurancePlans.value.find((p) => p.id === form.value.insurance_plan_id)?.insurance_plan_services || []).filter((s) => s.active);
});
// ensureServicesLoaded + applyDefaultPrice → useAgendaEventPickerBilling (1C-ii-a)

// ── Itens de serviço (commitment_services) ──────────────────────────
// commitmentItems já declarado no topo (passado ao composer via extras)
const servicePickerSel = ref(null);
// serieValorMode usado no template + watcher init (1C-ii-b) — declarado
// antes do useAgendaEventLifecycle abaixo.
const serieValorMode = ref('multiplicar'); // 'multiplicar' | 'dividir'

// isDynamic precisa existir antes do useAgendaEventPickerBilling abaixo
// (passado como dep). Movido pra cá da seção de computeds derivados.
const isDynamic = computed(() => (props.agendaSettings?.slot_mode ?? 'fixed') === 'dynamic');

// billingType → composer (1B); watcher → useAgendaEventActions (1C-i)
const billingTypeOptions = [
    { label: 'Sessão Particular', value: 'particular' },
    { label: 'Sessão Gratuita', value: 'gratuito' },
    { label: 'Convênio', value: 'convenio' }
];

// ──────────────────────────────────────────────────────────────────
// A66 sub-sessão 1C-i — watchers + handlers de save/delete via factory.
// Cobrança ao salvar — SelectButton com opções dinâmicas (Opção C1, 2026-05-13).
// - Avulsa: ['none' | 'session']
// - Recorrente: ['none' | 'package' | 'per_session']
// Declarado ANTES do _actions pq onSave precisa incluir chargeMode no payload.
// Substituiu o checkbox boolean gerarCobrancaAoSalvar (Fase 1 original).
//
// Default (user pediu 2026-05-14): em avulsa, vem com 'session' selecionado
// (fluxo normal é cobrar). Em recorrente, mantém 'none' (mais conservador —
// criar pacote ou per-session requer decisão consciente).
const chargeMode = ref('session');
function _defaultChargeModeFor(recorrType) {
    return recorrType === 'avulsa' ? 'session' : 'none';
}
watch(
    () => props.modelValue,
    (open) => {
        if (open) chargeMode.value = _defaultChargeModeFor(_composer.recorrenciaType?.value);
    }
);
// Quando user troca entre avulsa ↔ recorrente sem fechar o dialog,
// reseta pro default daquele modo (avulsa='session', recorrente='none').
// Mostra mensagem warning de mudança que PERSISTE até user fechar o dialog
// OU até nova troca de tipo (que sobrescreve). 2026-05-14.
const chargeChangeMsg = ref('');
// Key incrementa a cada troca pra forçar remount → re-dispara animação de pulse.
const chargeChangeMsgKey = ref(0);
watch(
    () => _composer.recorrenciaType?.value,
    (newType, oldType) => {
        chargeMode.value = _defaultChargeModeFor(newType);
        // Só mostra msg de mudança se houve transição real (não no init)
        if (oldType && newType && oldType !== newType) {
            const labels = {
                avulsa: 'sessão avulsa',
                semanal: 'recorrência semanal',
                quinzenal: 'recorrência quinzenal',
                diasEspecificos: 'recorrência personalizada'
            };
            const from = labels[oldType] || oldType;
            const to = labels[newType] || newType;
            chargeChangeMsg.value = `Você alterou de <b>${from}</b> para <b>${to}</b>. Confirme a forma de cobrança abaixo.`;
            chargeChangeMsgKey.value++;
        }
    }
);
// Reset da mensagem só quando dialog reabre (zero state).
watch(
    () => props.modelValue,
    (open) => {
        if (open) chargeChangeMsg.value = '';
    }
);
const chargeModeOptions = computed(() => {
    const isAvulsa = _composer.recorrenciaType?.value === 'avulsa';
    if (isAvulsa) {
        return [
            { value: 'none', label: 'Não cobrar agora' },
            { value: 'session', label: 'Gerar cobrança' }
        ];
    }
    return [
        { value: 'none', label: 'Não cobrar agora' },
        { value: 'package', label: 'Pacote único' },
        { value: 'per_session', label: '1 por sessão' }
    ];
});
const chargeModeHint = computed(() => {
    switch (chargeMode.value) {
        case 'session':
            return 'Cria 1 cobrança no Financeiro com vencimento na data da sessão.';
        case 'package':
            return 'Cria 1 contrato de pacote no Financeiro com o valor total da série.';
        case 'per_session':
            return 'Materializa todas as ocorrências e cria 1 cobrança por sessão.';
        case 'none':
        default:
            return 'Você pode gerar a cobrança depois pelo Financeiro ou pelo painel da sessão.';
    }
});

// Estilo do pacote (2026-05-14): híbrido — quando chargeMode='package',
// admin escolhe se quer cobrar tudo upfront (1 financial_record total)
// ou só registrar saldo (Cliniko-style, sem cobrança imediata).
const packageStyle = ref('upfront');
const packageStyleOptions = [
    { value: 'upfront', label: 'Cobrar total agora (1 boleto fechado)' },
    { value: 'saldo', label: 'Saldo de pacote (sem cobrança imediata)' }
];
const showPackageStyle = computed(() => chargeMode.value === 'package');

// Forma de pagamento (Request 1, 2026-05-14; refatorado 2026-05-16).
// Antes era um único Select que misturava método + status ("Já recebi — PIX").
// Agora separamos em 2 controles:
//   - paymentMethod: forma de recebimento (link/pix/dinheiro/deposito/cartao_maquininha)
//   - markPaidNow: bool — quando true, record nasce paid (paciente pagou na hora)
// Quando method === 'link' (Asaas), markPaidNow é sempre false (gateway externo
// só liquida via webhook). Os 2 controles juntam-se em paymentMethodOptions +
// SelectButton "Cobrança pendente / Já recebi (dar baixa)".
const paymentMethod = ref('link');
const paymentMethodOptions = [
    { value: 'link', label: 'Enviar link de pagamento (Asaas)' },
    { value: 'pix', label: 'PIX' },
    { value: 'dinheiro', label: 'Dinheiro' },
    { value: 'deposito', label: 'Depósito' },
    { value: 'cartao_maquininha', label: 'Cartão (maquininha)' }
];
const markPaidNow = ref(false);
const markPaidOptions = [
    { value: false, label: 'Cobrança pendente' },
    { value: true, label: 'Já recebi (dar baixa)' }
];
// SelectButton de baixa só faz sentido quando método é direto (não Asaas).
const showMarkPaidToggle = computed(() => paymentMethod.value !== 'link');
watch(
    () => props.modelValue,
    (open) => {
        if (open) {
            paymentMethod.value = 'link';
            markPaidNow.value = false;
            packageStyle.value = 'upfront';
        }
    }
);
// Reset method/status e packageStyle quando user muda chargeMode.
watch(chargeMode, () => {
    paymentMethod.value = 'link';
    markPaidNow.value = false;
    packageStyle.value = 'upfront';
});
// Se user volta pra 'link' (Asaas), garante que markPaidNow=false (gateway
// externo nunca nasce paid — depende do webhook).
watch(paymentMethod, (m) => {
    if (m === 'link') markPaidNow.value = false;
});
// Settlement aparece em:
//   - avulsa + 'session': 1 cobrança da sessão
//   - recorrente + 'package' + 'upfront': 1 cobrança fechada do pacote total
// Não aparece em 'per_session' (N cobranças futuras, "já recebi" não cabe)
// nem em 'package' + 'saldo' (sem cobrança imediata).
const showPaymentSettlement = computed(() => {
    if (_composer.recorrenciaType?.value === 'avulsa' && chargeMode.value === 'session') return true;
    if (chargeMode.value === 'package' && packageStyle.value === 'upfront') return true;
    return false;
});

// Recebe o composer (1B) + refs externos (commitmentItems, servicePickerSel,
// selectedPlanService) + saveCommitmentItems do useCommitmentServices.
// Retorna: refs internos (_skipStatusWatch, _prevStatus, _restoringConvenio,
// samePatientConflict) + handlers (onSave, onDelete, onEncerrarSerie).
// ──────────────────────────────────────────────────────────────────
const _actions = useAgendaEventActions({
    composer: _composer,
    commitmentItems,
    servicePickerSel,
    selectedPlanService,
    saveCommitmentItems,
    chargeMode,
    packageStyle,
    paymentMethod,
    markPaidNow,
    props,
    emit
});
const {
    _skipStatusWatch,
    _prevStatus,
    _restoringConvenio,
    samePatientConflict,
    onSave,
    onDelete,
    onEncerrarSerie
} = _actions;

// ──────────────────────────────────────────────────────────────────
// A66 sub-sessão 1C-ii-a — patient picker + billing items + 2 watchers.
// Recebe composer + actions (pra _restoringConvenio e samePatientConflict)
// + refs externos + composables (services, insurancePlans, discounts).
// ──────────────────────────────────────────────────────────────────
const _pickerBilling = useAgendaEventPickerBilling({
    composer: _composer,
    actions: _actions,
    commitmentItems,
    servicePickerSel,
    selectedPlanService,
    services,
    loadServices,
    getDefaultPrice,
    planServices,
    loadActiveDiscount,
    _csLoadItems,
    _csLoadItemsOrTemplate,
    isDynamic,
    props
});
const {
    pacientePickerOpen,
    pacienteSearch,
    pacientesLoading,
    pacientesError,
    patients,
    cadRapidoOpen,
    ensureServicesLoaded,
    resetServicesGate,
    applyDefaultPrice,
    addItem,
    removeItem,
    onItemChange,
    _loadCommitmentItemsForEvent,
    onProcedureSelect,
    selectCommitment,
    goBack,
    openPacientePicker,
    clearPatientsCache,
    loadPatients,
    selectPaciente,
    clearPaciente,
    openCadastroRapido,
    abrirCadastroCompleto
} = _pickerBilling;

// ──────────────────────────────────────────────────────────────────
// A66 sub-sessão 1C-ii-b — lifecycle: 4 watchers (modelValue init,
// tenant/scope, solicitação pendente, online slots) + series pills
// + selectSlot + quick-creates wiring + onSendManualReminder.
// ──────────────────────────────────────────────────────────────────
const _lifecycle = useAgendaEventLifecycle({
    composer: _composer,
    actions: _actions,
    pickerBilling: _pickerBilling,
    commitmentItems,
    serieEvents,
    servicePickerSel,
    selectedPlanService,
    serieValorMode,
    services,
    loadServices,
    loadInsurancePlans,
    props,
    emit,
    confirm,
    toast
});
const {
    solicitacaoPendente,
    onlineSlots,
    loadingOnlineSlots,
    serieLoading,
    pillDeleteMenuRef,
    pillDeleteTarget,
    sendingReminder,
    serviceQuickDlgOpen,
    insuranceQuickDlgOpen,
    planServiceQuickDlgOpen,
    occFinancialRecord,
    occFinancialLoading,
    sessionPaymentRecord,
    sessionContract,
    serieCountByStatus,
    pillDeleteMenuItems,
    loadSerieEvents,
    loadOccFinancialRecord,
    loadSessionPaymentRecord,
    onPillEditClick,
    onPillStatusChange,
    onPillDeleteClick,
    onPillDelete,
    selectSlot,
    openServiceQuickCreate,
    onServiceCreated,
    openInsuranceQuickCreate,
    onInsuranceCreated,
    openPlanServiceQuickCreate,
    onPlanServiceCreated,
    onSendManualReminder
} = _lifecycle;

// serieRefreshTick: bumpa quando o 2º dialog empilhado salva/deleta uma
// ocorrencia. Re-roda loadSerieEvents pra refletir o novo status nas pills
// do dialog pai. No occurrenceMode é ignorado — só o pai tem pills/lista.
// 2026-05-12.
watch(
    () => props.serieRefreshTick,
    (tick, prev) => {
        if (props.occurrenceMode) return;
        if (tick === prev) return;
        if (!props.modelValue) return;
        if (!hasSerie.value) return;
        loadSerieEvents();
    }
);

const totalFromItems = computed(() => commitmentItems.value.reduce((sum, item) => sum + (item.final_price ?? 0), 0));

// Duração calculada como soma de services.duration_min dos itens (slot_mode=dynamic)
const dynamicDuration = computed(() => {
    if (!isDynamic.value) return null;
    return commitmentItems.value.reduce((sum, item) => {
        const svc = services.value.find((s) => s.id === item.service_id);
        return sum + (svc?.duration_min ?? 0);
    }, 0);
});

// Preço exibido no resumo: total dos itens quando há itens, form.price caso contrário
const displayPrice = computed(() => (commitmentItems.value.length > 0 ? totalFromItems.value : form.value.price));

// Aviso informativo de valor total da série (não altera os valores gravados)
const serieValorAviso = computed(() => {
    if (recorrenciaType.value === 'avulsa' || !commitmentItems.value.length) return null;
    const n = qtdSessoesEfetiva.value;
    if (!n || !totalFromItems.value) return null;
    if (serieValorMode.value === 'multiplicar') {
        return `Total da série: ${fmtBRL(totalFromItems.value * n)} (${fmtBRL(totalFromItems.value)} × ${n} sessões)`;
    }
    return `Valor por sessão: ${fmtBRL(totalFromItems.value / n)} (${fmtBRL(totalFromItems.value)} ÷ ${n} sessões)`;
});

// Sync: total dos itens → form.price
watch(totalFromItems, (total) => {
    if (commitmentItems.value.length > 0) form.value.price = total;
});

// Sync: duração dinâmica → form.duracaoMin (slot_mode=dynamic)
watch(dynamicDuration, (dur) => {
    if (isDynamic.value && dur != null && dur > 0) form.value.duracaoMin = dur;
});

// calcFinalPrice movido pra agendaEventHelpers.js (A66/1A)

// addItem, removeItem, onItemChange, _loadCommitmentItemsForEvent
// → useAgendaEventPickerBilling (1C-ii-a)

// fmtBRL movido pra agendaEventHelpers.js (A66/1A)

// selectedCommitment, selectedCommitmentName, selectedCommitmentFields,
// requiresPatient, isSessionEvent, patientLocked, hasInsurance → composer (1B)

// ── jornada ────────────────────────────────────────────────
// fmtJornadaHora (antes _fmtH) movido pra agendaEventHelpers.js (A66/1A)

const jornadaDialog = computed(() => {
    const rules = props.workRules;
    if (!rules?.length) return null;
    const d = form.value.dia ? new Date(form.value.dia) : new Date();
    const dow = d.getDay();
    const rule = rules.find((r) => Number(r.dia_semana) === dow);
    if (!rule) return { text: 'Este é um dia de folga na sua agenda.', isOff: true };
    const inicio = String(rule.hora_inicio || '').slice(0, 5);
    const fim = String(rule.hora_fim || '').slice(0, 5);
    const [h1, m1] = inicio.split(':').map(Number);
    const [h2, m2] = fim.split(':').map(Number);
    const totalMin = h2 * 60 + m2 - (h1 * 60 + m1);
    const durH = Math.floor(totalMin / 60);
    const durM = totalMin % 60;
    const durStr = durM > 0 ? `${durH}h${String(durM).padStart(2, '0')}` : `${durH}h`;
    return { text: `Jornada: das ${fmtJornadaHora(inicio)} às ${fmtJornadaHora(fim)} (${durStr})`, isOff: false };
});

// ── bloqueio de dia ────────────────────────────────────────
const blockedSet = computed(() => new Set(props.blockedDates || []));
const formDayISO = computed(() => {
    const d = form.value.dia ? new Date(form.value.dia) : null;
    if (!d) return '';
    return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
});
const isDayBlocked = computed(() => !!formDayISO.value && blockedSet.value.has(formDayISO.value));
const isSessionBlockedByDay = computed(() => isDayBlocked.value && isSessionEvent.value);

const isDiaFolga = computed(() => {
    if (!form.value.dia || !props.workRules?.length) return false;
    const dow = new Date(form.value.dia).getDay();
    return !props.workRules.some((r) => Number(r.dia_semana) === dow);
});

// ── time picker ────────────────────────────────────────────
const timePickerOpen = ref(false);
// Snapshot pra revert ao fechar sem confirmar. Sem isso, o user mudava
// dia/hora/duração no time picker e mesmo fechando via X / overlay / Esc,
// os valores vazavam pro card principal (porque os bindings v-model são
// diretos no form). User reportou em 2026-05-13.
const _tpSnap = ref(null);
const _tpCommitted = ref(false);
function onTimePickerShow() {
    _tpSnap.value = {
        dia: form.value.dia ? new Date(form.value.dia) : null,
        startTime: form.value.startTime,
        duracaoMin: form.value.duracaoMin
    };
    _tpCommitted.value = false;
}
function onTimePickerHide() {
    if (!_tpCommitted.value && _tpSnap.value) {
        form.value.dia = _tpSnap.value.dia;
        form.value.startTime = _tpSnap.value.startTime;
        form.value.duracaoMin = _tpSnap.value.duracaoMin;
    }
    _tpSnap.value = null;
    _tpCommitted.value = false;
}
function confirmTimePicker() {
    _tpCommitted.value = true;
    timePickerOpen.value = false;
}
function cancelTimePicker() {
    // _tpCommitted permanece false → onTimePickerHide reverte
    timePickerOpen.value = false;
}
// samePatientConflict → useAgendaEventActions (1C-i) — watcher
// [paciente_id, dia] já está dentro do composable, samePatientConflict
// vem do destructuring acima.

// Toggle de modalidade — botao unico que alterna Presencial/Online,
// substituindo o SelectButton em todas as resolucoes (mobile + desktop).
const modalidadeLabel = computed(() => (form.value.modalidade === 'online' ? 'Online' : 'Presencial'));
function toggleModalidade() {
    form.value.modalidade = form.value.modalidade === 'online' ? 'presencial' : 'online';
}

// ── novo paciente ──────────────────────────────────────────
// cadRapidoOpen, abrirCadastroCompleto → useAgendaEventPickerBilling (1C-ii-a)
// onPatientCreatedRapido fica aqui (autocontido, usa form + cadRapidoOpen)
function onPatientCreatedRapido(p) {
    if (!p) return;
    const nome = p.nome_completo || p.nome || p.name || '';
    form.value.paciente_id = p.id;
    form.value.paciente_nome = nome;
    form.value.paciente_avatar = p.avatar_url || null;
    cadRapidoOpen.value = false;
}

// "Cadastro completo" inline — substitui o open-em-nova-aba pra
// (a) nao vazar do layout Melissa pra /therapist/patients/cadastro
// (b) preservar o estado do form de evento sem precisar de nova aba.
// Reutiliza o PatientCadastroDialog (mesmo usado em MelissaPacientes).
const cadCompletoOpen = ref(false);
// Popovers de ajuda dos InputGroups do card Pagamento. Refs separados
// pra cada (servico/convenio) pra evitar conflito quando o user abre
// um e clica direto no outro sem fechar.
const servicoHelpRef = ref(null);
const convenioHelpRef = ref(null);
const extrasHelpRef = ref(null);
const onlineHelpRef = ref(null);

// Dialog "Ver lançamentos da sessão" (2026-05-14): lista todos os
// financial_records vinculados ao agenda_evento atual (sessão + multas
// /taxas/etc). Aparece em edição via botão "Ver lançamentos".
const sessionRecordsDialogOpen = ref(false);
const sessionRecordsList = ref([]);
const sessionRecordsLoading = ref(false);
async function openSessionRecordsDialog() {
    const eid = props.eventRow?.id;
    if (!eid) return;
    sessionRecordsDialogOpen.value = true;
    sessionRecordsLoading.value = true;
    try {
        const { data, error } = await tenantDb().from('financial_records')
            .select('id, description, amount, final_amount, status, due_date, paid_at, payment_method, created_at')
            .eq('agenda_evento_id', eid)
            .is('deleted_at', null)
            .order('created_at', { ascending: true });
        if (error) throw error;
        sessionRecordsList.value = data || [];
    } catch (e) {
        console.warn('[VerLancamentos] erro:', e?.message);
        sessionRecordsList.value = [];
    } finally {
        sessionRecordsLoading.value = false;
    }
}
function _fmtRecordBRL(v) {
    return Number(v ?? 0).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' });
}
function _fmtRecordDate(d) {
    if (!d) return '—';
    try {
        return new Date(d).toLocaleDateString('pt-BR');
    } catch {
        return '—';
    }
}
const _recordMethodLabels = {
    pix: 'PIX',
    dinheiro: 'Dinheiro',
    deposito: 'Depósito',
    cartao: 'Cartão',
    cartao_maquininha: 'Cartão (maquininha)',
    convenio: 'Convênio',
    asaas: 'Asaas'
};
const _recordStatusLabels = {
    pending: 'Pendente',
    paid: 'Pago',
    overdue: 'Vencido',
    cancelled: 'Cancelado',
    refunded: 'Reembolsado',
    partial: 'Parcial'
};
function _recordStatusSeverity(s) {
    return { pending: 'info', paid: 'success', overdue: 'danger', cancelled: 'secondary', refunded: 'warn', partial: 'warn' }[s] || 'secondary';
}

// Dialog de seleção de serviços (particular) ou convênio. Substitui a
// UI inline antiga: o card 3 (Sessão / Honorários) agora mostra empty
// state ou resumo + botão "Editar" — a configuração detalhada (qtd,
// desconto, procedimento, guia) acontece dentro deste dialog.
const serviceDialogOpen = ref(false);

// Dialog de configuração de Frequência (recorrência). Mesma lógica do
// serviceDialogOpen — card de Frequência mostra empty/resumo + Editar,
// e a config detalhada (chips, dias, qtd, preview) vive aqui.
const freqDialogOpen = ref(false);

// Resumo do card Frequência (display only)
const freqResumoMain = computed(() => {
    const t = recorrenciaType.value;
    if (t === 'avulsa') return 'Sessão única';
    if (t === 'semanal') return `Semanal · ${nomeDiaSemana(diaSemanaRecorrencia.value)}`;
    if (t === 'quinzenal') return `Quinzenal · ${nomeDiaSemana(diaSemanaRecorrencia.value)}`;
    if (t === 'diasEspecificos') {
        const dias = (diasSelecionados.value || []).map((d) => nomeDiaSemana(d).slice(0, 3)).join(', ');
        return `Dias específicos${dias ? ': ' + dias : ''}`;
    }
    return '—';
});
const freqResumoSub = computed(() => {
    if (recorrenciaType.value === 'avulsa') return 'Sem repetição';
    return totalOcorrencias.value
        ? `${totalOcorrencias.value} ${totalOcorrencias.value === 1 ? 'sessão prevista' : 'sessões previstas'}`
        : null;
});

// Total do pacote (items × ocorrências) quando há recorrência E valor.
// Só ativa com >= 2 sessões — 1 sessão "recorrente" via Personalizar
// não vira pacote (semanticamente esquisito).
const pacoteTotal = computed(() => {
    if (recorrenciaType.value === 'avulsa') return null;
    if (!totalFromItems.value) return null;
    if (!totalOcorrencias.value || totalOcorrencias.value < 2) return null;
    return totalFromItems.value * totalOcorrencias.value;
});

// É um pacote? (recorrência com ≥ 2 sessões na criação OU edição de
// sessão que faz parte de série já existente — hasSerie indica isso).
const isPacote = computed(() =>
    (recorrenciaType.value !== 'avulsa' && (totalOcorrencias.value || 0) >= 2) ||
    (isEdit.value && hasSerie.value)
);

// Plural simples PT-BR pro nome do commitment (Sessão → Sessões,
// Atividade → Atividades, etc.). Cobre os casos típicos do sistema.
function pluralCommitment(name) {
    if (!name) return 'sessões';
    if (/ão$/i.test(name)) return name.replace(/ão$/i, 'ões');
    if (/l$/i.test(name)) return name.replace(/l$/i, 'is');
    if (/s$/i.test(name)) return name;
    return name + 's';
}

// Posicao da ocorrencia atual na serie (1-based). Usa currentRecurrenceDate
// (que vem de eventRow.recurrence_date) + serieEvents pra calcular. Retorna
// null se nao for possivel localizar — fallback no headerMainLabel.
const occurrenceIndex = computed(() => {
    if (!currentRecurrenceDate.value || !serieEvents.value?.length) return null;
    const idx = serieEvents.value.findIndex((ev) => ev.recurrence_date === currentRecurrenceDate.value);
    return idx >= 0 ? idx + 1 : null;
});
const occurrenceTotalSessions = computed(() => serieEvents.value?.length || 0);

// Título do header — adaptativo:
//   Criando avulsa:    "Nova {Name}"
//   Criando pacote:    "Pacote · N {Names}"
//   Editando avulsa:   "Editar {Name}"
//   Editando pacote:   "Sessão do Pacote · {Name}"  (faz parte de série)
//   occurrenceMode:    "Pacote · X de Y Sessões"    (2º dialog empilhado)
const headerMainLabel = computed(() => {
    if (props.occurrenceMode) {
        const idx = occurrenceIndex.value;
        const total = occurrenceTotalSessions.value;
        if (idx && total) return `Pacote · ${idx} de ${total} Sessões`;
        if (total) return `Pacote · ${total} Sessões`;
        return 'Pacote · Sessão';
    }
    const name = selectedCommitment.value?.name || '';
    if (!name) return headerTitle.value;
    if (isEdit.value) {
        if (hasSerie.value) {
            // Evita "Sessão do Pacote · Sessão" (nome duplicado) — quando
            // o commitment chama 'Sessão' (default), mostra só "Sessão do
            // Pacote". Outros nomes (ex: Avaliação) entram normais.
            const lower = name.toLowerCase();
            if (lower === 'sessão' || lower === 'sessao') return 'Sessão do Pacote';
            return `Sessão do Pacote · ${name}`;
        }
        return `Editar ${name}`;
    }
    if (isPacote.value) {
        return `Pacote · ${totalOcorrencias.value} ${pluralCommitment(name)}`;
    }
    return `Nova ${name}`;
});

// ── occurrence billing lock state ──────────────────────────────────
// Mapeia financial_record.status pra label+severity do Tag no card
// Sessao/Honorarios do occurrenceMode. Quando ocorrencia ja tem cobranca
// emitida (qualquer status), trava edicao de tipo/itens — ajustes via
// fluxo do Financeiro (padrao SimplePractice). 2026-05-12.
const occBillingStatusLabel = computed(() => {
    const s = occFinancialRecord.value?.status;
    if (s === 'paid') return 'Cobrança paga';
    if (s === 'overdue') return 'Cobrança atrasada';
    if (s === 'pending') return 'Cobrança pendente';
    return '';
});
const occBillingStatusSeverity = computed(() => {
    const s = occFinancialRecord.value?.status;
    if (s === 'paid') return 'success';
    if (s === 'overdue') return 'danger';
    if (s === 'pending') return 'warn';
    return 'info';
});

// ── Linha "Cobrança" do Resumo lateral (2026-05-18) ──────────────
// Renderiza no Resumo do dialog uma linha curta com o estado atual
// da cobrança (Pago / Pendente / Atrasada / Sem cobrança). Espelha
// os 3 canais visuais da agenda (verde pago / amber pendente /
// neutro). Sources:
//   - occFinancialRecord (lifecycle, agora carrega em ambos modos —
//     guard de occurrenceMode foi removido em 2026-05-19 pra ativar
//     lock-edit em Melissa tambem). Cobre record direto OU sintetizado
//     a partir de contrato upfront ativo.
//   - eventRow.price (fallback pra "Sem cobrança · R$ X" quando
//     ainda nao ha record)
const paymentSummary = computed(() => {
    if (!isSessionEvent.value) return null;
    const rec = occFinancialRecord.value;
    const fmtDate = (d) => {
        if (!d) return '';
        try {
            return new Date(d).toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' });
        } catch {
            return '';
        }
    };
    if (rec) {
        const value = fmtBRL(rec.final_amount ?? rec.amount ?? 0);
        if (rec.status === 'paid') {
            const when = fmtDate(rec.paid_at);
            return { icon: 'pi-check-circle', cls: 'aed-pay-summary-row--paid', label: when ? `Pago · ${value} · ${when}` : `Pago · ${value}` };
        }
        if (rec.status === 'overdue') {
            const when = fmtDate(rec.due_date);
            return { icon: 'pi-exclamation-circle', cls: 'aed-pay-summary-row--overdue', label: when ? `Atrasada · ${value} · venceu ${when}` : `Atrasada · ${value}` };
        }
        if (rec.status === 'pending') {
            const when = fmtDate(rec.due_date);
            return { icon: 'pi-dollar', cls: 'aed-pay-summary-row--pending', label: when ? `Pendente · ${value} · vence ${when}` : `Pendente · ${value}` };
        }
    }
    // Sem record. Mostra só quando ja eh edit + tem paciente — em
    // criacao nao faz sentido (a cobranca ainda nem foi decidida).
    if (isEdit.value && form.value?.paciente_id) {
        const price = form.value?.price;
        if (price != null && price > 0) {
            return { icon: 'pi-dollar', cls: 'aed-pay-summary-row--none', label: `Sem cobrança · ${fmtBRL(price)}` };
        }
        return { icon: 'pi-info-circle', cls: 'aed-pay-summary-row--none', label: 'Sem cobrança gerada' };
    }
    return null;
});

// Info card do pacote (saldo/upfront) — quando a sessão pertence a uma
// série com billing_contract ativo. Pra saldo é a única forma do user
// entender o que tá acontecendo (nada aparece em /financeiro até
// realizar). Pra upfront é redundante com o lock-edit mas ainda útil.
const sessionContractInfo = computed(() => {
    const c = sessionContract.value;
    if (!c || c.type !== 'package') return null;
    const total = c.total_sessions || 0;
    const used = c.sessions_used || 0;
    const remaining = Math.max(0, total - used);
    const totalPrice = Number(c.package_price || 0);
    const perSession = total > 0 ? totalPrice / total : 0;
    const remainingValue = perSession * remaining;
    return {
        id: c.id,
        style: c.charging_style || 'upfront',
        total,
        used,
        remaining,
        totalPrice,
        perSession,
        remainingValue,
        // Forma "popover-shape" pra reusar no emit usar-sessao (handler em
        // MelissaLayout aceita ambos os fluxos: popover ou dialog)
        _normalized: {
            id: c.id,
            style: c.charging_style || 'upfront',
            totalSessions: total,
            sessionsUsed: used,
            packagePrice: totalPrice
        }
    };
});

// ── Preview "antes e depois" das ocorrencias afetadas ─────────────────
// So vale no occurrenceMode quando o user escolhe escopo > somente_este.
// Mostra abaixo do card "Aplicar alteracoes em" duas colunas: as datas
// originais (esquerda) e como ficam apos aplicar o delta de horario
// (direita). Util pra confirmar antes de salvar.
//
// Delta = inicioDateTime.value - eventRow.inicio_em original.
// Aplicamos uniforme em todas as ocorrencias afetadas. Para mudancas
// de time-of-day puras (caso comum) o delta funciona perfeito; para
// shift de dia-da-semana e aproximacao boa o bastante pro preview.
const previewDeltaMs = computed(() => {
    if (!props.occurrenceMode) return 0;
    if (!inicioDateTime.value) return 0;
    const origIso = props.eventRow?.inicio_em;
    if (!origIso) return 0;
    return inicioDateTime.value.getTime() - new Date(origIso).getTime();
});

const previewAffectedOccurrences = computed(() => {
    if (!props.occurrenceMode) return [];
    if (!hasSerie.value) return [];
    if (editScope.value === 'somente_este') return [];
    const list = serieEvents.value || [];
    if (!list.length) return [];

    let affected = list;
    if (editScope.value === 'este_e_seguintes') {
        const curIdx = list.findIndex((ev) => ev.recurrence_date === currentRecurrenceDate.value);
        affected = curIdx >= 0 ? list.slice(curIdx) : list;
    }
    // 'todos' / 'todos_sem_excecao' — usa lista completa do serieEvents.
    // (Exceptions canceladas ja aparecem em serieEvents marcadas como _cancelled.)

    const delta = previewDeltaMs.value;
    return affected.map((ev) => {
        const origStart = new Date(ev.inicio_em);
        const origEnd = new Date(ev.fim_em);
        const newStart = delta ? new Date(origStart.getTime() + delta) : origStart;
        const newEnd = delta ? new Date(origEnd.getTime() + delta) : origEnd;
        return {
            ev,
            origStart,
            origEnd,
            newStart,
            newEnd,
            changed: delta !== 0
        };
    });
});

// Label do wallet no Resumo flutuante: valor total (pacote × N quando
// recorrente, ou displayPrice base). O indicador "Pacote" aparece junto
// com a modalidade ("Presencial · Pacote") em vez do wallet.
const walletLabel = computed(() => {
    if (pacoteTotal.value != null) {
        return fmtBRL(pacoteTotal.value);
    }
    return displayPrice.value != null ? fmtBRL(displayPrice.value) : '—';
});

// Conta da multiplicação no card Frequência: "4× R$ 40 = R$ 160".
const freqValorConta = computed(() => {
    if (pacoteTotal.value == null) return null;
    return `${totalOcorrencias.value}× ${fmtBRL(totalFromItems.value)} = ${fmtBRL(pacoteTotal.value)}`;
});

// Helpers da seção "Próximas ocorrências" — separador por mês +
// referência temporal relativa ("em 2 semanas" etc.)
function freqShouldShowMonthSep(idx) {
    const cur = ocorrenciasComConflito.value?.[idx];
    if (!cur) return false;
    if (idx === 0) return true;
    const prev = ocorrenciasComConflito.value[idx - 1];
    const cd = new Date(cur.date);
    const pd = new Date(prev.date);
    return cd.getMonth() !== pd.getMonth() || cd.getFullYear() !== pd.getFullYear();
}
function freqMonthLabel(date) {
    const d = new Date(date);
    return d.toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' })
        .replace(/^./, (c) => c.toUpperCase());
}
function freqRelativeLabel(date) {
    const target = new Date(date);
    target.setHours(0, 0, 0, 0);
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    const diff = Math.round((target.getTime() - today.getTime()) / 86400000);
    if (diff === 0) return 'hoje';
    if (diff === 1) return 'amanhã';
    if (diff < 0) {
        const past = -diff;
        if (past < 7) return `${past} dia${past === 1 ? '' : 's'} atrás`;
        const w = Math.round(past / 7);
        if (w < 8) return `${w} semana${w === 1 ? '' : 's'} atrás`;
        const m = Math.round(past / 30);
        return `${m} ${m === 1 ? 'mês' : 'meses'} atrás`;
    }
    if (diff < 14) return `em ${diff} dias`;
    const w = Math.round(diff / 7);
    if (w < 8) return `em ${w} semana${w === 1 ? '' : 's'}`;
    const m = Math.round(diff / 30);
    if (m < 12) return `em ${m} ${m === 1 ? 'mês' : 'meses'}`;
    const y = Math.round(diff / 365);
    return `em ${y} ano${y === 1 ? '' : 's'}`;
}

// Flags pra decidir empty vs resumo no card body
const hasParticularConfigured = computed(() => commitmentItems.value.length > 0);
const hasConvenioConfigured = computed(() => !!form.value.insurance_plan_id);
const hasBillingConfigured = computed(() =>
    billingType.value === 'particular' ? hasParticularConfigured.value :
    billingType.value === 'convenio' ? hasConvenioConfigured.value :
    false
);

// Helpers de desconto por item (display only — cálculo real vive no composer)
function itemHasDiscount(item) {
    return (item?.discount_pct > 0) || (item?.discount_flat > 0);
}
function itemSubtotal(item) {
    return (Number(item?.unit_price) || 0) * (Number(item?.quantity) || 1);
}
function itemDiscountValue(item) {
    return Math.max(0, itemSubtotal(item) - (Number(item?.final_price) || 0));
}
// Soma do desconto total aplicado nos itens — pro card resumo no body
const totalDiscountFromItems = computed(() =>
    commitmentItems.value.reduce((acc, it) => acc + itemDiscountValue(it), 0)
);

// Nome do convênio selecionado (pra resumo)
const selectedPlanName = computed(() => {
    const id = form.value.insurance_plan_id;
    if (!id) return '';
    const plan = (activePlans.value || []).find((p) => p.id === id);
    return plan?.name || '';
});
// Nome do procedimento selecionado (pra resumo)
const selectedPlanServiceName = computed(() => {
    if (!selectedPlanService.value) return '';
    const ps = (planServices.value || []).find((p) => p.id === selectedPlanService.value);
    return ps?.name || '';
});
function onPatientCreatedCompleto(p) {
    if (!p) return;
    const nome = p.nome_completo || p.nome || p.name || '';
    form.value.paciente_id = p.id;
    form.value.paciente_nome = nome;
    form.value.paciente_avatar = p.avatar_url || null;
    cadCompletoOpen.value = false;
}

// ── paciente picker ────────────────────────────────────────
// pacientePickerOpen, pacienteSearch, pacientesLoading, pacientesError, patients,
// selectCommitment, goBack, openPacientePicker, clearPatientsCache, loadPatients,
// selectPaciente, clearPaciente → useAgendaEventPickerBilling (1C-ii-a)
// filteredPatients depende de patients/pacienteSearch (do composable) — fica no .vue:
// Mostra TODOS os pacientes (inclusive Inativo/Arquivado) — UX intencional pra
// o user achar quem procura. Os nao-Ativos vem com badge + disabled (template),
// e selectPaciente bloqueia o clique se status !== 'Ativo'. Ordenacao:
// Ativos primeiro, depois Inativos, depois Arquivados.
const _statusRank = { Ativo: 0, Inativo: 1, Arquivado: 2 };
const filteredPatients = computed(() => {
    const q = String(pacienteSearch.value || '')
        .trim()
        .toLowerCase();
    const list = patients.value || [];
    const matched = !q
        ? [...list]
        : list.filter((p) => {
              const nome = String(p.nome || '').toLowerCase();
              const email = String(p.email || '').toLowerCase();
              const tel = String(p.telefone || '').toLowerCase();
              return nome.includes(q) || email.includes(q) || tel.includes(q);
          });
    return matched.sort((a, b) => (_statusRank[a.status] ?? 3) - (_statusRank[b.status] ?? 3));
});

// 4 watchers (modelValue init, tenant/scope, solicitação pendente, online
// slots) + solicitacaoPendente ref → useAgendaEventLifecycle (1C-ii-b)
// watcher form.commitment_id, form.insurance_plan_id, onProcedureSelect
// → useAgendaEventPickerBilling (1C-ii-a)
// watcher [paciente_id, dia] → useAgendaEventActions (1C-i)

function goToAgendamentosRecebidos() {
    visible.value = false;
    const prefix = route.path.startsWith('/admin') ? '/admin' : '/therapist';
    router.push(`${prefix}/agendamentos-recebidos`);
}

// Rota dos Compromissos Determinados — Melissa usa /melissa/compromissos;
// admin/therapist usam /agenda/compromissos sob seus prefixos.
function goToCompromissosConfig() {
    visible.value = false;
    const path = route.path.startsWith('/melissa')
        ? '/melissa/compromissos'
        : route.path.startsWith('/admin')
            ? '/admin/agenda/compromissos'
            : '/therapist/agenda/compromissos';
    router.push(path);
}

// Rota das Configurações da Agenda (seção Online) — Melissa usa
// /melissa/agenda-config; admin/therapist usam /configuracoes/agenda
// dentro do contexto Melissa não existe (sempre embedado em MelissaLayout).
function goToAgendaConfig() {
    visible.value = false;
    const path = route.path.startsWith('/melissa') ? '/melissa/agenda-config' : '/configuracoes/agenda';
    router.push(path);
}

// ── duração / slots ────────────────────────────────────────
// Chips de duração rápida no Time Picker (mesmo set do AgendaEventDialogV2
// pra paridade visual entre os dois dialogs).
const duracaoQuickChips = [30, 50, 60, 90];

// ── Mini calendar do Time Picker (estilo MelissaAgenda mini-cal) ─────
// Versao enxuta: sem dots de eventos, sem feriados, sem dias fechados.
// So renderiza grid 6x7 com hoje (cor primary) + selecionado (fundo
// primary) + outros meses (cinza). Sincroniza com form.dia ao abrir.
const miniRefDate = ref(new Date());

watch(
    () => timePickerOpen.value,
    (open) => {
        if (!open) return;
        const d = form.value.dia ? new Date(form.value.dia) : new Date();
        if (!Number.isNaN(d.getTime())) miniRefDate.value = d;
    }
);

const miniMesAno = computed(() =>
    miniRefDate.value
        .toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' })
        .replace(/(^|\s)\S/g, (l) => l.toUpperCase())
);

const miniDias = computed(() => {
    const refD = miniRefDate.value;
    const ano = refD.getFullYear();
    const mes = refD.getMonth();
    const ultimoDia = new Date(ano, mes + 1, 0).getDate();
    const inicio = new Date(ano, mes, 1).getDay(); // 0=dom

    const hoje = new Date();
    hoje.setHours(0, 0, 0, 0);
    const sel = form.value.dia ? new Date(form.value.dia) : null;
    if (sel) sel.setHours(0, 0, 0, 0);

    const sameDay = (a, b) => a && b && a.getTime() === b.getTime();
    const arr = [];

    // Padding antes (mês anterior, em cinza)
    for (let i = inicio - 1; i >= 0; i--) {
        const d = new Date(ano, mes, -i);
        d.setHours(0, 0, 0, 0);
        arr.push({ dia: d.getDate(), outro: true, isHoje: false, isSel: sameDay(d, sel), date: d });
    }
    // Dias do mês corrente
    for (let d = 1; d <= ultimoDia; d++) {
        const data = new Date(ano, mes, d);
        data.setHours(0, 0, 0, 0);
        arr.push({ dia: d, outro: false, isHoje: sameDay(data, hoje), isSel: sameDay(data, sel), date: data });
    }
    // Padding depois até 42 (6 semanas completas)
    while (arr.length < 42) {
        const last = arr[arr.length - 1].date;
        const next = new Date(last);
        next.setDate(last.getDate() + 1);
        next.setHours(0, 0, 0, 0);
        arr.push({ dia: next.getDate(), outro: true, isHoje: false, isSel: sameDay(next, sel), date: next });
    }
    return arr;
});

function miniPrev() {
    const d = new Date(miniRefDate.value);
    d.setMonth(d.getMonth() - 1);
    miniRefDate.value = d;
}
function miniNext() {
    const d = new Date(miniRefDate.value);
    d.setMonth(d.getMonth() + 1);
    miniRefDate.value = d;
}
function miniToday() {
    const t = new Date();
    miniRefDate.value = t;
    form.value.dia = new Date(t.getFullYear(), t.getMonth(), t.getDate());
}
function selecionarDiaMini(d) {
    if (!d?.date) return;
    form.value.dia = new Date(d.date);
    miniRefDate.value = new Date(d.date);
}

// grouped duration options: default preset first, then com pausa, then sem pausa
const duracaoOptions = computed(() => {
    const defaultDur = props.agendaSettings?.session_duration_min || 50;
    const defaultGap = props.agendaSettings?.session_break_min || 0;

    const ALL_PRESETS = [
        { label: '30 min', dur: 30, gap: 0 },
        { label: '40 min', dur: 40, gap: 0 },
        { label: '45 min', dur: 45, gap: 15 },
        { label: '50 min', dur: 50, gap: 10 },
        { label: '60 min', dur: 60, gap: 0 },
        { label: '90 min', dur: 90, gap: 0 },
        { label: '2h', dur: 120, gap: 0 }
    ];

    const defaultItem = {
        label: `${defaultDur} min${defaultGap > 0 ? ` + ${defaultGap}min pausa` : ''}  ✦ padrão`,
        value: defaultDur
    };

    const others = ALL_PRESETS.filter((p) => p.dur !== defaultDur);
    const wBreak = others.filter((p) => p.gap > 0);
    const noBreak = others.filter((p) => p.gap === 0);

    const groups = [{ label: 'Padrão da agenda', items: [defaultItem] }];
    if (wBreak.length) groups.push({ label: 'Com pausa', items: wBreak.map((p) => ({ label: `${p.label} + ${p.gap}min pausa`, value: p.dur })) });
    if (noBreak.length) groups.push({ label: 'Sem pausa', items: noBreak.map((p) => ({ label: p.label, value: p.dur })) });
    return groups;
});

// hhmmToMin / minToHHMM movidos pra agendaEventHelpers.js (A66/1A)

// Slots disponíveis no dia selecionado (respeitando jornada + pausas + eventos existentes)
const availableSlots = computed(() => {
    const dia = form.value.dia;
    if (!dia || !props.workRules?.length) return [];
    const dow = new Date(dia).getDay();
    const dur = props.agendaSettings?.session_duration_min || 50;
    const gap = props.agendaSettings?.session_break_min || 0;
    const cycle = Math.max(1, dur + gap);

    const windows = (props.workRules || [])
        .filter((r) => Number(r.dia_semana) === dow && r.ativo !== false)
        .map((r) => ({ start: String(r.hora_inicio || '').slice(0, 5), end: String(r.hora_fim || '').slice(0, 5) }))
        .sort((a, b) => a.start.localeCompare(b.start));
    if (!windows.length) return [];

    const breaks = (props.pausasSemanais || []).filter((p) => p.dia_semana == null || Number(p.dia_semana) === dow).map((p) => ({ s: hhmmToMin(p.hora_inicio || '00:00'), e: hhmmToMin(p.hora_fim || '00:00') }));

    // eventos já agendados no mesmo dia (para marcar como ocupado)
    const todayStr = dia instanceof Date ? `${dia.getFullYear()}-${String(dia.getMonth() + 1).padStart(2, '0')}-${String(dia.getDate()).padStart(2, '0')}` : String(dia).slice(0, 10);
    const busySlots = (props.allEvents || [])
        .filter((e) => {
            if (!e.inicio_em) return false;
            return e.inicio_em.slice(0, 10) === todayStr && e.id !== form.value.id;
        })
        .map((e) => ({
            s: hhmmToMin(e.inicio_em.slice(11, 16)),
            e: e.fim_em ? hhmmToMin(e.fim_em.slice(11, 16)) : hhmmToMin(e.inicio_em.slice(11, 16)) + dur
        }));

    const slots = [];
    for (const w of windows) {
        const wStart = hhmmToMin(w.start);
        const wEnd = hhmmToMin(w.end);
        let t = wStart;
        while (t + dur <= wEnd) {
            const aEnd = t + dur;
            const brk = breaks.find((b) => !(aEnd <= b.s || t >= b.e));
            if (brk) {
                t = brk.e;
                continue;
            }
            const busy = busySlots.some((b) => !(aEnd <= b.s || t >= b.e));
            slots.push({ hhmm: minToHHMM(t), endHhmm: minToHHMM(aEnd), busy });
            t += cycle;
        }
    }
    const seen = new Set();
    return slots.filter((s) => {
        if (seen.has(s.hhmm)) return false;
        seen.add(s.hhmm);
        return true;
    });
});

const PERIODOS = [
    { label: 'Manhã', icon: 'pi-sun', from: '05:00', to: '12:00' },
    { label: 'Tarde', icon: 'pi-cloud', from: '12:00', to: '18:00' },
    { label: 'Noite', icon: 'pi-moon', from: '18:00', to: '24:00' }
];
const selectedPeriodo = ref(null);

// Apenas períodos que têm ao menos 1 slot disponível
const activePeriodos = computed(() => PERIODOS.filter((p) => availableSlots.value.some((s) => s.hhmm >= p.from && s.hhmm < p.to)));

// Set dos horários online pré-configurados (para marcar nos pills)
const onlineConfigSet = computed(() => new Set((onlineSlots.value || []).map((s) => s.hhmm)));

const filteredSlots = computed(() => {
    const base = selectedPeriodo.value ? availableSlots.value.filter((s) => s.hhmm >= selectedPeriodo.value.from && s.hhmm < selectedPeriodo.value.to) : availableSlots.value;
    return base.map((s) => ({ ...s, isOnlineConfigured: onlineConfigSet.value.has(s.hhmm) }));
});

// selectSlot, onlineSlots/loadingOnlineSlots refs + watcher
// → useAgendaEventLifecycle (1C-ii-b)

const onlineAtivo = computed(() => !!props.agendaSettings?.online_ativo);
const onlineDisponivel = computed(() => (form.value.modalidade === 'online' ? onlineAtivo.value && onlineSlots.value.length > 0 : true));

const dayWorkRule = computed(() => {
    if (!form.value.dia || !props.workRules?.length) return null;
    const dow = new Date(form.value.dia).getDay();
    return props.workRules.find((r) => Number(r.dia_semana) === dow) ?? null;
});

// ── série: info do banner ──────────────────────────────────
const serieDiaSemana = computed(() => {
    if (props.eventRow?.serie_dia_semana != null) return Number(props.eventRow.serie_dia_semana);
    const iso = props.eventRow?.inicio_em;
    if (iso) return new Date(iso).getDay();
    return form.value.dia ? new Date(form.value.dia).getDay() : null;
});
const serieHoraDisplay = computed(() => {
    if (props.eventRow?.serie_hora) return props.eventRow.serie_hora;
    return form.value.startTime || null;
});

// ── série: lista de sessões ────────────────────────────────
// serieEvents já declarado no topo (passado ao composer via extras).
// serieLoading, pillDeleteMenuRef, pillDeleteTarget, serieCountByStatus,
// pillDeleteMenuItems, generateRuleDates, loadSerieEvents,
// onPillEditClick, onPillStatusChange, onPillDeleteClick, onPillDelete
// → useAgendaEventLifecycle (1C-ii-b)

const statusOptions = [
    { label: 'Agendado', value: 'agendado' },
    { label: 'Realizado', value: 'realizado' },
    { label: 'Faltou', value: 'faltou' },
    { label: 'Cancelado', value: 'cancelado' },
    { label: 'Remarcar', value: 'remarcado' }
];

// isPast movido pra agendaEventHelpers.js (A66/1A)

// agendaPerms, isSessionFuture, isArchivedPastEdit, isInativoFutureEdit,
// statusOptionsFiltered → composer (1B)
// fmtWeekdayShort / fmtDayNum / fmtMonthShort → agendaEventHelpers (1A)

// timeConflict, startTimeDate, inicioDateTime, fimDateTime, dataHoraDisplay,
// previewRange, computedTitulo, canSave, headerTitle → composer (1B)

// ── save / delete ──────────────────────────────────────────
// onSave / onDelete / onEncerrarSerie → useAgendaEventActions (1C-i)

// onSendManualReminder + sendingReminder → useAgendaEventLifecycle (1C-ii-b)

// onDelete → useAgendaEventActions (1C-i)

// ── helpers ────────────────────────────────────────────────
// addMinutesDate / fmtDateBR / fmtDateBRLong / fmtTime / fmtDuracao /
// fmtSerieHora / nomeDiaSemana / isoToHHMM / calcMinutes movidos pra
// agendaEventHelpers.js (A66/1A)

// resetForm → composer (1B). Se algum watcher do .vue precisa resetar
// o form (ex: ao reabrir o dialog), chamar `form.value = resetForm()`
// usando a função exportada do composer.

// ── Google Calendar link ────────────────────────────────────────
const googleCalendarUrl = computed(() => {
    const dia = form.value.dia;
    const hora = form.value.startTime;
    if (!dia || !hora) return null;

    const start = formatGCalDate(dia, hora);
    const endHHMM = addMinutesToHHMM(hora, form.value.duracaoMin || 50);
    const end = formatGCalDate(dia, endHHMM);

    const paciente = form.value.paciente_nome ? ` — ${form.value.paciente_nome}` : '';
    const title = (form.value.titulo_custom?.trim() || 'Sessão') + paciente;
    const location = form.value.modalidade === 'online' ? 'Atendimento Online' : '';

    return generateGoogleCalendarLink({
        title,
        description: form.value.observacoes?.trim() || '',
        location,
        start,
        end
    });
});

// labelStatusSessao / statusSeverity / statusExtraClass movidos pra
// agendaEventHelpers.js (A66/1A)

// ── Dialog width: compacto no step 1, full no step 2 ───────────
// Step 1 (escolha de tipo) e uma lista de rows finas — 400px da
// foco. Step 2 (formulario) precisa de espaco pro grid 2-col.
// Quando lockType=true, abre direto no step 2 (sem step 1).
const dialogStyle = computed(() => {
    const isStep1 = step.value === 1 && !props.lockType;
    return {
        width: isStep1 ? '420px' : '600px',
        maxWidth: '96vw'
    };
});

// Some o card flutuante (Resumo) quando QUALQUER sub-dialog/picker
// abre. Sem isso, o card (z-index alto) ficaria por cima do
// pacientePicker/timePicker/cadRapido/quickCreate, atrapalhando.
const anyChildDialogOpen = computed(() =>
    pacientePickerOpen.value ||
    timePickerOpen.value ||
    cadRapidoOpen.value ||
    cadCompletoOpen.value ||
    serviceQuickDlgOpen.value ||
    insuranceQuickDlgOpen.value ||
    planServiceQuickDlgOpen.value ||
    serviceDialogOpen.value ||
    freqDialogOpen.value
);

// ── Sincronização do Resumo flutuante com a posição do Dialog ──
// O Dialog do PrimeVue centra vertical quando o conteudo cabe no viewport
// (commitment "Bloqueio" / "Atividade" sao baixos), e ancora ~5vh do topo
// quando o conteudo é alto (Sessao com paciente+frequencia). O Resumo
// flutuante estava com top:5vh fixo, então em dialog baixo ficava lá em
// cima desalinhado. ResizeObserver mede o .p-dialog e atualiza o style do
// aside pra acompanhar top + altura — funciona em qualquer cenario.
const resumoStyle = ref({ top: '5vh', maxHeight: '90vh' });
let _dialogObserver = null;

function _syncResumoToDialog() {
    const dialogEl = document.querySelector('.p-dialog.agenda-event-composer');
    if (!dialogEl) return;
    const rect = dialogEl.getBoundingClientRect();
    resumoStyle.value = {
        top: `${Math.round(rect.top)}px`,
        maxHeight: `${Math.round(rect.height)}px`
    };
}

watch([visible, () => step.value], async ([open, stp]) => {
    if (_dialogObserver) {
        _dialogObserver.disconnect();
        _dialogObserver = null;
    }
    if (!open || stp !== 2) return;
    await nextTick();
    const dialogEl = document.querySelector('.p-dialog.agenda-event-composer');
    if (!dialogEl || typeof ResizeObserver === 'undefined') return;
    _syncResumoToDialog();
    _dialogObserver = new ResizeObserver(() => _syncResumoToDialog());
    _dialogObserver.observe(dialogEl);
}, { immediate: true });

onBeforeUnmount(() => {
    if (_dialogObserver) {
        _dialogObserver.disconnect();
        _dialogObserver = null;
    }
});
</script>

<template>
    <Dialog v-model:visible="visible" modal :draggable="false" :dismissableMask="!anyChildDialogOpen" :closeOnEscape="!anyChildDialogOpen" :style="dialogStyle" :breakpoints="{ '960px': '96vw', '640px': '98vw' }" class="agenda-event-composer">
        <template #header>
            <div class="w-full flex items-center justify-between gap-3">
                <!-- Slot headerLeft (override de chamadas como MelissaPaciente
                     que precisam icon+title+subtitle custom). Default: dot
                     colorido + headerTitle + previewRange. -->
                <slot name="headerLeft">
                    <div class="flex items-center gap-2 min-w-0">
                        <div class="header-dot shrink-0" :style="selectedCommitment?.bg_color ? { background: `#${selectedCommitment.bg_color}` } : {}" />
                        <div class="min-w-0">
                            <div class="flex items-center gap-1.5">
                                <span class="font-semibold truncate text-base">
                                    {{ (occurrenceMode || (step === 2 && selectedCommitment?.name)) ? headerMainLabel : headerTitle }}
                                </span>
                                <button
                                    v-if="step === 2 && !isEdit && allowBack"
                                    type="button"
                                    class="commit-badge header-swap-btn shrink-0"
                                    @click="goBack"
                                >
                                    <i class="pi pi-refresh" />
                                    trocar tipo
                                </button>
                            </div>
                            <div v-if="step === 2" class="text-xs text-color-secondary truncate">{{ previewRange }}</div>
                        </div>
                    </div>
                </slot>

                <!-- ── Cadastro Rapido — promovido pro topo do dialog (ao lado do close button)
                       So aparece no step 2, quando o tipo exige paciente (sessao) E
                       é criação (em edição faz pouco sentido cadastrar paciente novo). -->
                <Button
                    v-if="step === 2 && isSessionEvent && !isEdit"
                    label="Cadastro Rápido"
                    icon="pi pi-user-plus"
                    size="small"
                    severity="secondary"
                    outlined
                    class="rounded-full text-xs h-7 ml-auto shrink-0"
                    @click="cadRapidoOpen = true"
                />
            </div>
        </template>

        <!-- ConfirmDialog renderizado na página pai para evitar conflito de z-index com o Dialog -->

        <!-- ══════════════════════════════════════════════ -->
        <!-- STEPS 1 + 2 — wrapper com transition slide-fade  -->
        <!-- ══════════════════════════════════════════════ -->
        <Transition name="step-fade" mode="out-in">
        <div v-if="step === 1 && !lockType" key="step-1" class="p-2">
            <Message v-if="blockOverlapWarning" severity="warn" class="mb-4" :closable="false">
                <i class="pi pi-exclamation-triangle mr-1" />
                <span>Este horário está dentro do bloqueio <strong>"{{ blockOverlapWarning.titulo || 'Bloqueio' }}"</strong>. O compromisso pode ser criado mesmo assim.</span>
            </Message>

            <div class="mb-4 text-sm text-color-secondary">Selecione o tipo de compromisso para começar.</div>

            <Message v-if="isDayBlocked" severity="warn" class="mb-4" :closable="false">
                <i class="pi pi-lock mr-1" />
                <span>Este dia está bloqueado. Compromissos do tipo <strong>Sessão</strong> não podem ser agendados.</span>
            </Message>

            <div class="commitment-list">
                <button
                    v-for="c in commitmentCards"
                    :key="c.id"
                    class="commitment-row"
                    :class="{ 'commitment-row--blocked': isDayBlocked && isNativeSession(c) }"
                    :style="c.bg_color ? { '--card-color': `#${c.bg_color}` } : {}"
                    :disabled="isDayBlocked && isNativeSession(c)"
                    :title="isDayBlocked && isNativeSession(c) ? 'Dia bloqueado — sessões não permitidas' : (c.description || '')"
                    @click="isDayBlocked && isNativeSession(c) ? null : selectCommitment(c)"
                >
                    <span class="commitment-row__icon" :style="c.bg_color ? { background: `#${c.bg_color}1f`, color: `#${c.bg_color}` } : {}">
                        <i :class="isNativeSession(c) ? 'pi pi-user' : 'pi pi-calendar'" />
                    </span>
                    <span class="commitment-row__body">
                        <span class="commitment-row__name">{{ c.name }}</span>
                        <span v-if="c.description" class="commitment-row__desc">{{ c.description }}</span>
                    </span>
                    <i :class="isDayBlocked && isNativeSession(c) ? 'pi pi-lock' : 'pi pi-chevron-right'" class="commitment-row__chevron" />
                </button>
            </div>
        </div>

        <!-- ══════════════════════════════════════════════ -->
        <!-- STEP 2 — formulário + painel lateral          -->
        <!-- ══════════════════════════════════════════════ -->
        <div v-else key="step-2" class="composer-grid">
            <!-- ══════════════════════════════════════════ -->
            <!-- OCCURRENCE MODE — layout enxuto pra editar  -->
            <!-- UMA ocorrência da série. Ordem fixa:        -->
            <!--   1) Dados da Recorrência                   -->
            <!--   2) Status                                 -->
            <!--   3) Horário                                -->
            <!--   4) Aplicar alterações em                  -->
            <!-- ══════════════════════════════════════════ -->
            <div v-if="occurrenceMode" class="composer-occurrence">
                <!-- ── 1) DADOS DA RECORRÊNCIA ────────────────────── -->
                <div class="field-card mb-4">
                    <div class="field-card__header">
                        <i class="pi pi-refresh" />
                        <span>Dados da Recorrência</span>
                    </div>
                    <div class="field-card__body aed-occ-data">
                        <div class="aed-occ-data__row">
                            <i class="pi pi-bookmark aed-occ-data__icon" :style="selectedCommitment?.bg_color ? { color: `#${selectedCommitment.bg_color}` } : {}" />
                            <span class="aed-occ-data__label">Compromisso</span>
                            <span class="aed-occ-data__value">{{ selectedCommitment?.name || '—' }}</span>
                        </div>
                        <div v-if="isSessionEvent" class="aed-occ-data__row">
                            <i class="pi pi-user aed-occ-data__icon" />
                            <span class="aed-occ-data__label">Paciente</span>
                            <span class="aed-occ-data__value truncate">{{ form.paciente_nome || '—' }}</span>
                        </div>
                        <div class="aed-occ-data__row">
                            <i class="pi pi-calendar-clock aed-occ-data__icon" />
                            <span class="aed-occ-data__label">Frequência</span>
                            <span class="aed-occ-data__value">{{ freqResumoMain }}</span>
                        </div>
                        <div v-if="occurrenceIndex && occurrenceTotalSessions" class="aed-occ-data__row">
                            <i class="pi pi-list aed-occ-data__icon" />
                            <span class="aed-occ-data__label">Posição</span>
                            <span class="aed-occ-data__value">Sessão {{ occurrenceIndex }} de {{ occurrenceTotalSessions }}</span>
                        </div>
                        <div class="aed-occ-data__row">
                            <i class="pi pi-map-marker aed-occ-data__icon" />
                            <span class="aed-occ-data__label">Modalidade</span>
                            <span class="aed-occ-data__value capitalize">{{ form.modalidade || '—' }}</span>
                        </div>
                    </div>
                </div>

                <!-- ── 2) STATUS DA SESSÃO ─────────────────────────── -->
                <div v-if="isSessionEvent" class="field-card mb-4">
                    <div class="field-card__header">
                        <i class="pi pi-tag" />
                        <span>Status da Sessão</span>
                    </div>
                    <div class="field-card__body">
                        <SelectButton v-model="form.status" :options="statusOptionsFiltered" optionLabel="label" optionValue="value" optionDisabled="disabled" :allowEmpty="false" :disabled="isArchivedPastEdit" class="w-full status-select-btn" />
                    </div>
                </div>

                <!-- ── 3) HORÁRIO ──────────────────────────────────── -->
                <div class="field-card mb-4">
                    <div class="field-card__header">
                        <i class="pi pi-calendar" />
                        <span class="md:hidden">Data e Horário</span>
                        <Button
                            label="Ajustar horário"
                            icon="pi pi-pencil"
                            size="small"
                            severity="secondary"
                            outlined
                            class="rounded-full text-xs h-7 ml-auto md:ml-0"
                            @click="timePickerOpen = true"
                        />
                    </div>
                    <div class="field-card__body">
                        <div class="time-hero" @click="timePickerOpen = true">
                            <div class="time-hero__line">
                                <span class="time-hero__date">{{ form.dia ? fmtDateBR(form.dia) : '—' }}</span>
                                <span class="time-hero__bullet">•</span>
                                <span class="time-hero__duration">{{ fmtDuracao(form.duracaoMin) }}</span>
                            </div>
                            <div class="time-hero__line">
                                <span class="time-hero__time">{{ form.startTime || '—' }}</span>
                                <span class="time-hero__arrow">→</span>
                                <span class="time-hero__time">{{ fimDateTime ? fmtTime(fimDateTime) : '—' }}</span>
                            </div>
                        </div>
                        <Message v-if="timeConflict" severity="warn" class="m-2 time-conflict-msg time-conflict-msg--inline" :closable="false">
                            <i class="pi pi-exclamation-triangle mr-1" />
                            {{ timeConflict }}
                        </Message>
                    </div>
                </div>

                <!-- ── 4) SESSÃO / HONORÁRIOS ────────────────────────
                     Lock baseado em financial_record (padrao SimplePractice):
                     - Sem cobranca emitida: edicao livre de tipo+itens.
                     - Cobranca em pending/paid/overdue: trava edicao, mostra
                       o panel embutido pra gerar/registrar pagamento no
                       fluxo do Financeiro. Garante imutabilidade fiscal das
                       cobrancas ja emitidas. 2026-05-12. -->
                <div v-if="isSessionEvent" class="field-card mb-4">
                    <div class="field-card__header">
                        <i class="pi pi-wallet" />
                        <span>Sessão / Honorários</span>
                        <Tag v-if="occFinancialRecord" :value="occBillingStatusLabel" :severity="occBillingStatusSeverity" class="ml-auto" />
                        <Select v-else-if="!occFinancialLoading"
                            v-model="billingType"
                            :options="billingTypeOptions"
                            optionLabel="label"
                            optionValue="value"
                            size="small"
                            class="aed-pay-mod-select ml-auto"
                        />
                    </div>
                    <div class="field-card__body">
                        <!-- LOCKED: cobranca ja emitida.
                             AgendaEventoFinanceiroPanel só renderiza quando
                             o record é REAL (não sintetizado de contrato
                             upfront em sibling). Sintetizado: a sessão não
                             tem record próprio, então o panel exibiria
                             "Gerar cobrança" indevidamente. O lock Message
                             já comunica o estado. -->
                        <template v-if="occFinancialRecord">
                            <Message severity="info" :closable="false" class="m-2">
                                <div class="text-sm">
                                    <i class="pi pi-lock mr-1" />
                                    Cobrança de <b>{{ fmtBRL(occFinancialRecord.final_amount ?? occFinancialRecord.amount) }}</b> {{ occFinancialRecord._synthesized ? 'do pacote' : 'já emitida' }}.
                                    Para alterar tipo ou serviços, ajuste a cobrança no Financeiro abaixo.
                                </div>
                            </Message>
                            <AgendaEventoFinanceiroPanel v-if="!occFinancialRecord._synthesized" :evento="eventRow" class="m-3" @cobranca-atualizada="_onCobrancaAtualizada" />
                        </template>

                        <!-- LOADING -->
                        <div v-else-if="occFinancialLoading" class="text-xs text-color-secondary p-3">
                            <i class="pi pi-spinner pi-spin mr-1" /> Verificando estado da cobrança…
                        </div>

                        <!-- UNLOCKED: sem cobranca, edicao livre -->
                        <template v-else>
                            <div v-if="billingType === 'gratuito'" class="aed-pay-gratuito">
                                <i class="pi pi-gift" />
                                <span><b>Esta sessão será gratuita</b> — sem cobrança ao paciente.</span>
                            </div>
                            <template v-else>
                                <button v-if="!hasBillingConfigured" type="button" class="patient-hero__empty" @click="serviceDialogOpen = true">
                                    <div class="patient-hero__empty-icon">
                                        <i :class="billingType === 'convenio' ? 'pi pi-shield' : 'pi pi-shopping-bag'" />
                                    </div>
                                    <div>
                                        <div class="font-semibold">
                                            {{ billingType === 'convenio' ? 'Selecione convênio para adicionar e calcular' : 'Selecione serviços para adicionar e calcular' }}
                                        </div>
                                        <div class="text-xs text-color-secondary">Toque para configurar</div>
                                    </div>
                                    <i class="pi pi-chevron-right ml-auto text-color-secondary opacity-50" />
                                </button>
                                <div v-else class="aed-pay-summary">
                                    <div class="aed-pay-summary__main">
                                        <template v-if="billingType === 'particular'">
                                            <div class="aed-pay-summary__count">
                                                {{ commitmentItems.length }} {{ commitmentItems.length === 1 ? 'serviço' : 'serviços' }}
                                                <span v-if="totalDiscountFromItems > 0" class="aed-pay-summary__discount">· desconto de {{ fmtBRL(totalDiscountFromItems) }}</span>
                                            </div>
                                            <div class="aed-pay-summary__value">{{ fmtBRL(totalFromItems) }}</div>
                                        </template>
                                        <template v-else-if="billingType === 'convenio'">
                                            <div class="aed-pay-summary__count">
                                                {{ selectedPlanName || 'Convênio' }}
                                                <span v-if="selectedPlanServiceName" class="opacity-70">· {{ selectedPlanServiceName }}</span>
                                            </div>
                                            <div class="aed-pay-summary__value">{{ form.insurance_value != null ? fmtBRL(form.insurance_value) : '—' }}</div>
                                        </template>
                                    </div>
                                    <Button label="Editar" icon="pi pi-pencil" size="small" severity="secondary" outlined class="rounded-full text-xs h-7" @click="serviceDialogOpen = true" />
                                </div>
                            </template>
                        </template>
                    </div>
                </div>

                <!-- Hint contextual abaixo do card Sessão / Honorários.
                     Esclarece dúvidas comuns por tipo de cobrança sem
                     poluir o form. Renderiza só quando o card está
                     unlocked (sem cobrança emitida) — quando travado,
                     a Message do lock ja explica o estado. -->
                <div v-if="isSessionEvent && !occFinancialRecord && billingType === 'convenio'" class="aed-billing-hint mb-3">
                    <i class="pi pi-info-circle" />
                    <span><b>Nº da guia é opcional</b> — você pode salvar a sessão e preencher depois, quando o convênio responder.</span>
                </div>
                <div v-else-if="isSessionEvent && !occFinancialRecord && billingType === 'gratuito'" class="aed-billing-hint mb-3">
                    <i class="pi pi-gift" />
                    <span>Sessão <b>gratuita</b> — nenhum lançamento será gerado no Financeiro.</span>
                </div>

                <!-- ── 5) APLICAR ALTERAÇÕES EM ──────────────────────
                     Travado quando cobranca ja emitida: mudancas estruturais
                     em escopo > somente_este podem afetar serie inteira,
                     mas cobrancas existentes permanecem imutaveis (filtro
                     em propagateToSerie). Aqui apenas oculta o card pra
                     simplificar — user nao confunde. 2026-05-12. -->
                <div v-if="hasSerie && !occFinancialRecord" class="side-card mb-3">
                    <div class="side-card__title mb-2">Aplicar alterações em</div>
                    <SelectButton v-model="editScope" :options="editScopeOptions" optionLabel="label" optionValue="value" :optionDisabled="(o) => !!o.disabled" class="w-full" size="small" />
                    <Message v-if="isFirstOccurrence" severity="info" :closable="false" class="mt-2">
                        <span class="text-sm"> Esta é a primeira sessão da série. Para alterar todas as ocorrências, use <b>Todas da série</b>. </span>
                    </Message>
                </div>

                <!-- ── 6) PREVIEW ANTES/DEPOIS ─────────────────────────
                     So renderiza no occurrenceMode com escopo > somente_este
                     e quando ha ocorrencias afetadas. Mostra duas colunas
                     lado a lado: original (esquerda) e como ficara apos a
                     mudanca (direita). Apoia o user a entender o impacto da
                     escolha de escopo antes de salvar. 2026-05-12. -->
                <div v-if="previewAffectedOccurrences.length" class="aed-preview-card mb-3">
                    <div class="aed-preview-card__header">
                        <i class="pi pi-arrow-right-arrow-left" />
                        <span>Pré-visualização — {{ previewAffectedOccurrences.length }} {{ previewAffectedOccurrences.length === 1 ? 'sessão afetada' : 'sessões afetadas' }}</span>
                    </div>
                    <div class="aed-preview-card__cols">
                        <div class="aed-preview-col">
                            <div class="aed-preview-col__title">Antes</div>
                            <div class="aed-preview-list">
                                <div
                                    v-for="(item, idx) in previewAffectedOccurrences"
                                    :key="`before-${item.ev.id || item.ev.recurrence_date}`"
                                    class="aed-preview-row"
                                    :class="[`aed-preview-row--${item.ev._status || 'agendado'}`, { 'aed-preview-row--past': isPast(item.ev.inicio_em) }]"
                                >
                                    <span class="aed-preview-row__num">{{ idx + 1 }}</span>
                                    <div class="aed-preview-row__date">
                                        <span class="aed-preview-row__weekday">{{ fmtWeekdayShort(item.origStart) }}</span>
                                        <span class="aed-preview-row__daynum">{{ fmtDayNum(item.origStart) }}</span>
                                        <span class="aed-preview-row__month">{{ fmtMonthShort(item.origStart) }}</span>
                                    </div>
                                    <span class="aed-preview-row__time">{{ fmtTime(item.origStart) }}</span>
                                </div>
                            </div>
                        </div>
                        <div class="aed-preview-col aed-preview-col--after">
                            <div class="aed-preview-col__title">Depois</div>
                            <div class="aed-preview-list">
                                <div
                                    v-for="(item, idx) in previewAffectedOccurrences"
                                    :key="`after-${item.ev.id || item.ev.recurrence_date}`"
                                    class="aed-preview-row aed-preview-row--after"
                                    :class="[`aed-preview-row--${item.ev._status || 'agendado'}`, { 'aed-preview-row--changed': item.changed }]"
                                >
                                    <span class="aed-preview-row__num">{{ idx + 1 }}</span>
                                    <div class="aed-preview-row__date">
                                        <span class="aed-preview-row__weekday">{{ fmtWeekdayShort(item.newStart) }}</span>
                                        <span class="aed-preview-row__daynum">{{ fmtDayNum(item.newStart) }}</span>
                                        <span class="aed-preview-row__month">{{ fmtMonthShort(item.newStart) }}</span>
                                    </div>
                                    <span class="aed-preview-row__time">{{ fmtTime(item.newStart) }}</span>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <!-- ─── COLUNA ESQUERDA — campos ─── -->
            <div v-if="!occurrenceMode" class="composer-left">
                <!-- Avisos topo -->
                <Message v-if="form.conflito" severity="warn" class="mb-3" :closable="false"> <span class="font-semibold">Conflito:</span> {{ form.conflito }} </Message>
                <Message v-if="isDayBlocked && !isSessionEvent" severity="warn" class="mb-3" :closable="false">
                    <i class="pi pi-lock mr-1" />
                    Atenção: dia bloqueado. Este tipo pode ser salvo normalmente.
                </Message>
                <Message v-if="jornadaDialog" :severity="jornadaDialog.isOff ? 'warn' : 'info'" class="mb-3 aed-msg-jornada aed-msg-jornada--inline" :closable="false">
                    <i :class="jornadaDialog.isOff ? 'pi pi-moon mr-1' : 'pi pi-clock mr-1'" />
                    {{ jornadaDialog.text }}
                </Message>
                <Message v-if="isDiaFolga && isSessionEvent" severity="warn" class="mb-3" :closable="false">
                    <i class="pi pi-moon mr-1" />
                    Este dia é folga na sua jornada. Você ainda pode salvar se necessário.
                </Message>

                <!-- ── Restrições de status do paciente ───────────── -->
                <Message v-if="isArchivedPastEdit" severity="warn" class="mb-3" :closable="false">
                    <i class="pi pi-lock mr-1" />
                    <b>Paciente arquivado.</b> O histórico de sessões é somente leitura.
                </Message>
                <Message v-if="isEdit && form.paciente_status === 'Inativo' && isSessionFuture" severity="warn" class="mb-3" :closable="false">
                    <i class="pi pi-ban mr-1" />
                    <b>Paciente inativo.</b> Remarcação de sessões está bloqueada.
                </Message>
                <Message v-if="isEdit && form.paciente_status === 'Arquivado' && isSessionFuture" severity="info" class="mb-3" :closable="false">
                    <i class="pi pi-info-circle mr-1" />
                    <b>Paciente arquivado.</b> Sessão futura pode ser editada, mas novos agendamentos e recorrências estão bloqueados.
                </Message>
                <Message v-if="!isEdit && isSessionEvent && form.paciente_id && !agendaPerms.canCreateSession" severity="error" class="mb-3" :closable="false">
                    <i class="pi pi-ban mr-1" />
                    <b>{{ form.paciente_status === 'Arquivado' ? 'Paciente arquivado.' : 'Paciente inativo.' }}</b>
                    Novos agendamentos estão bloqueados.
                </Message>

                <!-- ── Alerta: solicitação pendente neste horário ─── -->
                <Message v-if="solicitacaoPendente && isSessionEvent && !isEdit" severity="info" class="mb-3" :closable="false">
                    <div class="flex items-center justify-between gap-3 w-full flex-wrap">
                        <span>
                            <i class="pi pi-inbox mr-1" />
                            Solicitação pendente de
                            <b>{{ solicitacaoPendente.paciente_nome }} {{ solicitacaoPendente.paciente_sobrenome }}</b>
                            para este horário.
                        </span>
                        <Button label="Ver agendamentos recebidos" icon="pi pi-arrow-right" size="small" severity="info" outlined class="rounded-full shrink-0" @click="goToAgendamentosRecebidos" />
                    </div>
                </Message>

                <!-- ── PACIENTE + DATA E HORÁRIO (lado a lado em desktop) ──
                     Em sessao, ambos lado a lado (aed-row-50, 50/50 >=768px).
                     Em commitments nao-sessao (sem paciente), wrapper vira
                     div vazio e Data e Horario usa seu proprio mb-4. -->
                <div :class="isSessionEvent ? 'aed-row-50' : ''">
                <div v-if="isSessionEvent" class="patient-hero">
                    <div class="patient-hero__label">
                        <div class="flex items-center gap-1.5">
                            <i class="pi pi-user" />
                            <!-- Mobile: label "Paciente" textual.
                                 Desktop: substituido pelo botao toggle modalidade abaixo. -->
                            <span class="patient-hero__label-text">Paciente</span>
                            <Button
                                v-tooltip.top="'Clique para alternar Presencial/Online'"
                                :label="modalidadeLabel"
                                icon="pi pi-pencil"
                                size="small"
                                severity="secondary"
                                outlined
                                class="patient-hero__modal-btn rounded-full text-xs h-7"
                                @click="toggleModalidade"
                            />
                        </div>
                        <div class="flex items-center gap-2 ml-auto">
                            <Button
                                icon="pi pi-id-card"
                                severity="primary"
                                text
                                class="patient-hero__cad-completo-btn"
                                v-tooltip.top="isEdit ? 'Edição de paciente indisponível no fluxo de sessão' : 'Cadastro completo'"
                                :disabled="isEdit"
                                @click="cadCompletoOpen = true"
                            />
                        </div>
                    </div>

                    <!-- Sem paciente selecionado -->
                    <button v-if="!form.paciente_id" class="patient-hero__empty" @click="openPacientePicker">
                        <div class="patient-hero__empty-icon">
                            <i class="pi pi-user-plus" />
                        </div>
                        <div>
                            <div class="font-semibold">Selecionar paciente</div>
                            <div class="text-xs text-color-secondary">Toque para buscar</div>
                        </div>
                        <i class="pi pi-chevron-right ml-auto text-color-secondary opacity-50" />
                    </button>

                    <!-- Com paciente selecionado -->
                    <div v-else class="patient-hero__selected">
                        <Avatar v-if="form.paciente_avatar" :image="form.paciente_avatar" shape="circle" size="large" class="shrink-0" />
                        <Avatar v-else :label="patientInitials(form.paciente_nome)" shape="circle" size="large" class="shrink-0 patient-avatar-bg" />
                        <div class="min-w-0 flex-1">
                            <div class="font-bold text-base truncate">{{ form.paciente_nome }}</div>
                            <div class="flex items-center gap-1.5 flex-wrap">
                                <!-- Mini links Editar / Limpar — substituem o texto "Paciente vinculado"
                                     + botoes redondos a direita. Locked mostra texto e cadeado. -->
                                <template v-if="!patientLocked && !lockPatient">
                                    <button type="button" class="patient-hero__link" @click="openPacientePicker">Editar</button>
                                    <span class="patient-hero__link-sep">/</span>
                                    <button type="button" class="patient-hero__link patient-hero__link--danger" @click="clearPaciente">Limpar</button>
                                </template>
                                <span v-else class="text-xs text-color-secondary inline-flex items-center gap-1" v-tooltip.top="lockPatient ? 'Paciente do prontuário' : 'Paciente não pode ser alterado após criação'">
                                    <i class="pi pi-lock" style="font-size: 0.7rem" />
                                    Paciente vinculado
                                </span>
                                <span
                                    v-if="form.paciente_status === 'Inativo' || form.paciente_status === 'Arquivado'"
                                    style="display: inline-block; background: #f97316; color: #fff; font-size: 9px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; padding: 1px 6px; border-radius: 3px; line-height: 1.5"
                                    >{{ form.paciente_status === 'Arquivado' ? 'arquivado' : 'desativado' }}</span
                                >
                            </div>
                        </div>
                    </div>

                    <Message v-if="samePatientConflict && isSessionEvent" severity="warn" class="mt-2" :closable="false">
                        Paciente já tem sessão neste dia:
                        <b>{{ fmtTime(new Date(samePatientConflict.inicio_em)) }} → {{ fmtTime(new Date(samePatientConflict.fim_em)) }}</b>
                    </Message>
                </div>

                <!-- ── DATA E HORÁRIO ─────────────────────────────── -->
                <div class="field-card mb-4">
                    <div class="field-card__header">
                        <i class="pi pi-calendar" />
                        <!-- Mobile: label visível; Desktop (>=md/768): label some
                             e botão "Ajustar horário" toma o lugar. Mesmo padrão
                             do patient-hero com o toggle Modalidade. -->
                        <span class="md:hidden">Data e Horário</span>
                        <!-- Ajustar horário:
                             - Em criação: sempre visível
                             - Em edição avulsa: visível (terapeuta pode reagendar)
                             - Em edição de série: escondido (use o 3º dialog de ocorrência)
                             Política "esconder em vez de disabled" pra não criar dúvida.
                             2026-05-13. -->
                        <Button
                            v-if="!isEdit || !hasSerie"
                            label="Ajustar horário"
                            icon="pi pi-pencil"
                            size="small"
                            severity="secondary"
                            outlined
                            class="rounded-full text-xs h-7 ml-auto md:ml-0"
                            @click="timePickerOpen = true"
                        />
                    </div>
                    <div class="field-card__body">
                        <div class="time-hero" :class="{ 'time-hero--readonly': isEdit && hasSerie }" @click="(!isEdit || !hasSerie) && (timePickerOpen = true)">
                            <!-- Linha 1: Data • Duração -->
                            <div class="time-hero__line">
                                <span class="time-hero__date">{{ form.dia ? fmtDateBR(form.dia) : '—' }}</span>
                                <span class="time-hero__bullet">•</span>
                                <span class="time-hero__duration">{{ fmtDuracao(form.duracaoMin) }}</span>
                            </div>
                            <!-- Linha 2: Início → Término -->
                            <div class="time-hero__line">
                                <span class="time-hero__time">{{ form.startTime || '—' }}</span>
                                <span class="time-hero__arrow">→</span>
                                <span class="time-hero__time">{{ fimDateTime ? fmtTime(fimDateTime) : '—' }}</span>
                            </div>
                        </div>
                        <!-- Inline (mobile) — no desktop migra pro Teleport abaixo do Resumo flutuante -->
                        <Message v-if="timeConflict" severity="warn" class="m-2 time-conflict-msg time-conflict-msg--inline" :closable="false">
                            <i class="pi pi-exclamation-triangle mr-1" />
                            {{ timeConflict }}
                        </Message>
                    </div>
                    <!-- /field-card__body -->
                </div>
                <!-- /field-card -->
                </div>
                <!-- /aed-row-50 (Paciente + Data e Horário) -->

                <!-- ── STATUS DA SESSÃO ───────────────────────────── -->
                <!-- Não aparece em pacote — o status é por ocorrência da série,
                     gerenciado no calendário, não aqui. -->
                <div v-if="isSessionEvent && isEdit && !isPacote" class="field-card mb-4">
                    <div class="field-card__header">
                        <i class="pi pi-tag" />
                        <span>Status da Sessão</span>
                    </div>
                    <div class="field-card__body">
                        <SelectButton v-model="form.status" :options="statusOptionsFiltered" optionLabel="label" optionValue="value" optionDisabled="disabled" :allowEmpty="false" :disabled="isArchivedPastEdit" class="w-full status-select-btn" />
                    </div>
                </div>

                <!-- Extras movido pro final do form (depois de Sessão/Honorários + Frequência) -->

                <!-- ── DEMAIS CAMPOS ──────────────────────────────── -->
                <div class="fields-grid">
                    <!-- Título (apenas para não-sessão) -->
                    <div v-if="!isSessionEvent">
                        <FloatLabel variant="on">
                            <IconField>
                                <InputIcon class="pi pi-pencil" />
                                <InputText id="aed-titulo" v-model="form.titulo_custom" class="w-full" variant="filled" />
                            </IconField>
                            <label for="aed-titulo">Título</label>
                        </FloatLabel>
                    </div>

                    <!-- Profissional -->
                    <div v-if="allowOwnerEdit">
                        <FloatLabel variant="on">
                            <IconField>
                                <InputIcon class="pi pi-briefcase" />
                                <Select inputId="aed-owner" class="w-full" v-model="form.owner_id" :options="ownerOptions" optionLabel="label" optionValue="value" variant="filled" />
                            </IconField>
                            <label for="aed-owner">Profissional</label>
                        </FloatLabel>
                    </div>

                    <!-- Observação nativa removida em 2026-05-11. Agora vem como
                         campo extra do compromisso determinado (key='notes').
                         Migration: 20260511000001_session_default_notes_field.sql. -->
                </div>

                <!-- ── RECORRÊNCIAS APLICADAS ──────────────────────────── -->
                <div v-if="hasSerie" class="serie-panel mt-4">
                    <div class="serie-panel__header">
                        <i class="pi pi-refresh shrink-0" />
                        <span>Recorrências Aplicadas</span>
                        <div v-if="!serieLoading && serieEvents.length" class="serie-panel__stats">
                            <span class="serie-stat serie-stat--total">{{ serieEvents.length }} sessões</span>
                            <span v-if="serieCountByStatus.realizado" class="serie-stat serie-stat--realizado"> · {{ serieCountByStatus.realizado }} realizadas</span>
                            <span v-if="serieCountByStatus.faltou" class="serie-stat serie-stat--faltou"> · {{ serieCountByStatus.faltou }} faltaram</span>
                            <span v-if="serieCountByStatus.cancelado" class="serie-stat serie-stat--cancelado"> · {{ serieCountByStatus.cancelado }} canceladas</span>
                            <span v-if="serieCountByStatus.remarcado" class="serie-stat serie-stat--remarcado"> · {{ serieCountByStatus.remarcado }} para remarcar</span>
                        </div>
                        <span v-if="serieLoading" class="ml-auto text-xs opacity-50">Carregando…</span>
                    </div>

                    <div v-if="!serieLoading && serieEvents.length" class="serie-pills-wrap">
                        <div
                            v-for="(ev, idx) in serieEvents"
                            :key="ev.id || ev.recurrence_date"
                            class="serie-pill"
                            :class="[`serie-pill--${ev._status || 'agendado'}`, { 'serie-pill--current': ev.recurrence_date === currentRecurrenceDate, 'serie-pill--past': isPast(ev.inicio_em) }]"
                        >
                            <span class="serie-pill__num">{{ idx + 1 }}</span>
                            <div class="serie-pill__date">
                                <span class="serie-pill__weekday">{{ fmtWeekdayShort(ev.inicio_em) }}</span>
                                <span class="serie-pill__daynum">{{ fmtDayNum(ev.inicio_em) }}</span>
                                <span class="serie-pill__month">{{ fmtMonthShort(ev.inicio_em) }}</span>
                            </div>
                            <span class="serie-pill__time">{{ fmtTime(new Date(ev.inicio_em)) }}</span>
                            <!-- Status: badge read-only. Edicao do status vive
                                 dentro do dialog "Editar Ocorrencia" (botao
                                 Editar abre o 2º dialog). 2026-05-12. -->
                            <span class="serie-pill__status-badge">{{ labelStatusSessao(ev._status || 'agendado') }}</span>
                            <span v-if="ev.recurrence_date === currentRecurrenceDate" class="serie-pill__cur-badge">Atual</span>
                            <Button label="Editar" icon="pi pi-pencil" text size="small" severity="secondary" class="serie-pill__edit" v-tooltip.left="'Editar esta sessão'" @click="onPillEditClick(ev)" />
                            <Button icon="pi pi-times" text size="small" severity="secondary" class="serie-pill__del" v-tooltip.left="'Remover'" @click="onPillDeleteClick(ev, $event)" />
                        </div>
                    </div>

                    <div v-if="!serieLoading && !serieEvents.length" class="serie-panel__empty">Nenhuma sessão encontrada na série.</div>

                    <Menu ref="pillDeleteMenuRef" :model="pillDeleteMenuItems" :popup="true" />
                </div>
            </div>

            <!-- ─── ABAIXO DO FORM — Financeiro, e Resumo (mobile) ─── -->
            <!-- Escopo "Aplicar alterações em" migrou pro occurrenceMode em
                 2026-05-12 — só aparece no 2º dialog empilhado de ocorrência. -->
            <div v-if="!occurrenceMode" class="composer-right">

                <!-- Info card do pacote (saldo/upfront) — quando a sessão
                     pertence a série com billing_contract ativo. Pra saldo
                     é a única forma do user entender que existe um saldo
                     (sem record em /financeiro até realizar). 2026-05-19 noite. -->
                <div v-if="sessionContractInfo && isSessionEvent" class="aed-contract-card mt-4" :class="`aed-contract-card--${sessionContractInfo.style}`">
                    <div class="aed-contract-card__head">
                        <i class="pi pi-box" />
                        <span class="aed-contract-card__title">
                            Pacote {{ sessionContractInfo.style === 'saldo' ? 'saldo' : 'fechado (upfront)' }}
                        </span>
                        <span class="aed-contract-card__count">{{ sessionContractInfo.used }} / {{ sessionContractInfo.total }} usadas</span>
                    </div>
                    <div class="aed-contract-card__body">
                        <div class="aed-contract-card__row">
                            <span class="aed-contract-card__label">Total contratado:</span>
                            <span class="aed-contract-card__value">{{ fmtBRL(sessionContractInfo.totalPrice) }}</span>
                        </div>
                        <div class="aed-contract-card__row">
                            <span class="aed-contract-card__label">Por sessão:</span>
                            <span class="aed-contract-card__value">{{ fmtBRL(sessionContractInfo.perSession) }}</span>
                        </div>
                        <div v-if="sessionContractInfo.remaining > 0" class="aed-contract-card__row">
                            <span class="aed-contract-card__label">Restam:</span>
                            <span class="aed-contract-card__value">{{ sessionContractInfo.remaining }} sessão(ões) · {{ fmtBRL(sessionContractInfo.remainingValue) }}</span>
                        </div>
                        <!-- Spinner enquanto carrega occFinancialRecord pra
                             evitar piscar entre "Usar" e "Revogar". -->
                        <div v-if="occFinancialLoading" class="aed-contract-card__loading">
                            <i class="pi pi-spinner pi-spin" />
                            <span>Verificando estado…</span>
                        </div>
                        <!-- Revogar: sessão já consumida (record pending vinculado),
                             paciente ainda não pagou. Atalho pra desfazer. -->
                        <Button
                            v-else-if="sessionContractInfo.style === 'saldo' && occFinancialRecord && occFinancialRecord.status === 'pending'"
                            label="Revogar uso"
                            icon="pi pi-undo"
                            size="small"
                            severity="danger"
                            outlined
                            class="rounded-full mt-2 aed-contract-card__usar"
                            @click="emit('revogar-sessao', { eventRow, contract: sessionContractInfo._normalized })"
                        />
                        <!-- Usar agora: sessão ainda não usada + tem saldo -->
                        <Button
                            v-else-if="sessionContractInfo.style === 'saldo' && sessionContractInfo.remaining > 0 && !occFinancialRecord"
                            label="Usar agora"
                            icon="pi pi-check"
                            size="small"
                            class="rounded-full mt-2 aed-contract-card__usar"
                            @click="emit('usar-sessao', { eventRow, contract: sessionContractInfo._normalized })"
                        />
                        <div class="aed-contract-card__hint">
                            <i class="pi pi-info-circle" />
                            <template v-if="sessionContractInfo.style === 'saldo'">
                                Modelo <b>saldo</b>: cada sessão gera cobrança ao ser marcada como <b>realizada</b>. Use <b>"Usar agora"</b> pra consumir 1 do saldo (marca realizada + gera fatura de {{ fmtBRL(sessionContractInfo.perSession) }}).
                            </template>
                            <template v-else>
                                Modelo <b>upfront</b>: todas as sessões foram cobradas de uma vez no início do pacote.
                            </template>
                        </div>
                    </div>
                </div>

                <!-- ── SESSÃO/HONORÁRIOS + FREQUÊNCIA (lado a lado em desktop) ──
                     Quando ha serie (edicao de evento ja vinculado a regra),
                     Frequencia some e Sessao/Honorarios ocupa a linha sozinho. -->
                <div :class="(isSessionEvent && !hasSerie) ? 'aed-row-50' : ''">

                <!-- ── FINANCEIRO (só sessão) ────────────────────────── -->
                <!-- Padrao .field-card: header com icone + label (mobile only) +
                     Select Gratuito/Particular/Convenio. Default Particular.
                     Body so renderiza pra particular/convenio (Gratuito =
                     card so com header, compacto). -->
                <div v-if="isSessionEvent" class="field-card mb-4">
                    <div class="field-card__header">
                        <i class="pi pi-wallet" />
                        <!-- Mobile: label visível; Desktop: label some e Select
                             ocupa o espaço. Mesmo padrão do Paciente e Data/Horário. -->
                        <span class="md:hidden">Sessão / Honorários</span>
                        <!-- Lock-edit (Fase 6, ativada em Melissa 2026-05-19):
                             quando há cobrança paga/pendente OU contrato upfront
                             ativo, mostra Tag em vez do Select pra impedir
                             troca de billingType. -->
                        <Tag v-if="occFinancialRecord" :value="occBillingStatusLabel" :severity="occBillingStatusSeverity" class="ml-auto" />
                        <Select
                            v-else-if="!occFinancialLoading"
                            v-model="billingType"
                            :options="billingTypeOptions"
                            optionLabel="label"
                            optionValue="value"
                            size="small"
                            class="aed-pay-mod-select ml-auto md:ml-0"
                        />
                    </div>
                    <!-- LOCKED: cobrança emitida (real OU sintetizada via
                         contrato upfront pago/pendente). Trava edição de
                         tipo/serviços/preço. Ajustes só via Financeiro. -->
                    <template v-if="occFinancialRecord">
                        <Message severity="info" :closable="false" class="m-2">
                            <div class="text-sm">
                                <i class="pi pi-lock mr-1" />
                                Cobrança de <b>{{ fmtBRL(occFinancialRecord.final_amount ?? occFinancialRecord.amount) }}</b> {{ occFinancialRecord._synthesized ? 'do pacote' : 'já emitida' }}.
                                Para alterar tipo ou serviços, ajuste a cobrança no Financeiro abaixo.
                            </div>
                        </Message>
                        <AgendaEventoFinanceiroPanel v-if="!occFinancialRecord._synthesized && isEdit && eventRow?.id" :evento="eventRow" class="m-3" @cobranca-atualizada="_onCobrancaAtualizada" />
                    </template>
                    <Transition v-else-if="!occFinancialLoading" name="aed-pay-expand">
                    <div class="field-card__body aed-pay-body">
                        <!-- Gratuito: aviso informativo, sem ações -->
                        <div v-if="billingType === 'gratuito'" class="aed-pay-gratuito">
                            <i class="pi pi-gift" />
                            <span><b>Esta sessão será gratuita</b> — sem cobrança ao paciente.</span>
                        </div>

                        <template v-else>
                            <!-- Empty state — convida a abrir o dialog de seleção/cálculo -->
                            <button
                                v-if="!hasBillingConfigured"
                                type="button"
                                class="patient-hero__empty"
                                @click="serviceDialogOpen = true"
                            >
                                <div class="patient-hero__empty-icon">
                                    <i :class="billingType === 'convenio' ? 'pi pi-shield' : 'pi pi-shopping-bag'" />
                                </div>
                                <div>
                                    <div class="font-semibold">
                                        {{ billingType === 'convenio' ? 'Selecione convênio para adicionar e calcular' : 'Selecione serviços para adicionar e calcular' }}
                                    </div>
                                    <div class="text-xs text-color-secondary">Toque para configurar</div>
                                </div>
                                <i class="pi pi-chevron-right ml-auto text-color-secondary opacity-50" />
                            </button>

                            <!-- Resumo configurado — total + count/nome + botão Editar -->
                            <div v-else class="aed-pay-summary">
                                <div class="aed-pay-summary__main">
                                    <template v-if="billingType === 'particular'">
                                        <div class="aed-pay-summary__count">
                                            {{ commitmentItems.length }} {{ commitmentItems.length === 1 ? 'serviço' : 'serviços' }}
                                            <span v-if="totalDiscountFromItems > 0" class="aed-pay-summary__discount">· desconto de {{ fmtBRL(totalDiscountFromItems) }}</span>
                                        </div>
                                        <div class="aed-pay-summary__value">{{ fmtBRL(totalFromItems) }}</div>
                                    </template>
                                    <template v-else-if="billingType === 'convenio'">
                                        <div class="aed-pay-summary__count">
                                            {{ selectedPlanName || 'Convênio' }}
                                            <span v-if="selectedPlanServiceName" class="opacity-70">· {{ selectedPlanServiceName }}</span>
                                        </div>
                                        <div class="aed-pay-summary__value">{{ form.insurance_value != null ? fmtBRL(form.insurance_value) : '—' }}</div>
                                    </template>
                                </div>
                                <Button
                                    label="Editar"
                                    icon="pi pi-pencil"
                                    size="small"
                                    severity="secondary"
                                    outlined
                                    class="rounded-full text-xs h-7"
                                    @click="serviceDialogOpen = true"
                                />
                            </div>
                        </template>
                    </div>
                    </Transition>

                    <!-- UNLOCKED: panel pra gerar cobrança inline (atalho do
                         popover é o caminho principal, mas mantido aqui pra
                         paridade com flow anterior). Só aparece sem record. -->
                    <AgendaEventoFinanceiroPanel
                        v-if="!occFinancialRecord && !occFinancialLoading && isEdit && eventRow?.id"
                        :evento="eventRow"
                        class="m-3"
                        @cobranca-atualizada="_onCobrancaAtualizada"
                    />

                    <!-- Botão "Ver lançamentos" (2026-05-14): abre dialog
                         listando todos os financial_records vinculados à
                         sessão (cobrança original + multas/extras). Aparece
                         só em edição com evento real. -->
                    <div v-if="isSessionEvent && isEdit && eventRow?.id" class="px-3 pb-3">
                        <Button
                            label="Ver lançamentos da sessão"
                            icon="pi pi-list"
                            severity="secondary"
                            outlined
                            size="small"
                            class="rounded-full text-xs h-8"
                            @click="openSessionRecordsDialog"
                        />
                    </div>

                    <!-- Invariante de propagacao em series (padrao SimplePractice):
                         alteracoes no template da regra so afetam sessoes futuras
                         AINDA NAO cobradas. financial_records emitidos sao imutaveis
                         pelo dialog — alteracoes vao via fluxo do Financeiro. Aviso
                         fixo abaixo do panel financeiro pra usuario nao se surpreender
                         com cobrancas inalteradas. 2026-05-12. -->
                    <Message v-if="isSessionEvent && isEdit && hasSerie" severity="info" :closable="false" class="m-3">
                        <div class="text-xs">
                            <i class="pi pi-info-circle mr-1" />
                            Alterações de tipo ou serviços afetam apenas sessões futuras
                            ainda não cobradas. <b>Cobranças já emitidas permanecem inalteradas</b> —
                            para ajustá-las, acesse o Financeiro.
                        </div>
                    </Message>
                </div>

                <!-- ── FREQUÊNCIA (só sessão, sem série) ──────────── -->
                <div v-if="isSessionEvent && !hasSerie" class="field-card mb-4">
                    <div class="field-card__header">
                        <i class="pi pi-refresh" />
                        <span>Frequência</span>
                    </div>
                    <div class="field-card__body">
                        <!-- Avulsa = padrão. Renderiza como "selecionado" (mesma
                             estrutura do estado configurado) pra ficar claro
                             que JÁ É uma escolha válida — não um estado vazio.
                             Botão Editar abre o dialog pra trocar pra recorrente. -->
                        <div v-if="recorrenciaType === 'avulsa'" class="aed-pay-summary">
                            <div class="aed-pay-summary__main">
                                <div class="aed-pay-summary__count">Tipo</div>
                                <div class="aed-pay-summary__value text-base">Avulsa</div>
                                <div class="aed-freq-conta">Sessão única, sem repetição</div>
                            </div>
                            <Button
                                label="Editar"
                                icon="pi pi-pencil"
                                size="small"
                                severity="secondary"
                                outlined
                                class="rounded-full text-xs h-7"
                                @click="freqDialogOpen = true"
                            />
                        </div>

                        <!-- Recorrência configurada — resumo + botão Editar -->
                        <div v-else class="aed-pay-summary">
                            <div class="aed-pay-summary__main">
                                <div class="aed-pay-summary__count">{{ freqResumoMain }}</div>
                                <div v-if="freqResumoSub" class="aed-pay-summary__value text-base">{{ freqResumoSub }}</div>
                                <div v-if="freqValorConta" class="aed-freq-conta">{{ freqValorConta }}</div>
                            </div>
                            <Button
                                label="Editar"
                                icon="pi pi-pencil"
                                size="small"
                                severity="secondary"
                                outlined
                                class="rounded-full text-xs h-7"
                                @click="freqDialogOpen = true"
                            />
                        </div>
                    </div>
                </div>
                </div>
                <!-- /aed-row-50 (Sessão/Honorários + Frequência) -->

                <!-- ── COBRANÇA (Card dedicado fullwidth, 2026-05-14)
                     Opções dinâmicas baseadas em recorrenciaType (avulsa/recorrente).
                     Quando user troca tipo (avulsa↔recorrente), mostra mensagem
                     vermelha de mudança e reseta as opções. -->
                <div
                    v-if="!isEdit && isSessionEvent && billingType === 'particular' && hasBillingConfigured"
                    class="field-card mb-4 aed-charge-card"
                >
                    <div class="field-card__header">
                        <i class="pi pi-bolt" />
                        <span>Cobrança ao salvar</span>
                    </div>
                    <div class="field-card__body aed-charge-card__body">
                        <Transition name="aed-charge-warn">
                            <div v-if="chargeChangeMsg" :key="chargeChangeMsgKey" class="aed-charge-change-msg">
                                <i class="pi pi-exclamation-triangle" />
                                <span v-html="chargeChangeMsg" />
                            </div>
                        </Transition>
                        <SelectButton
                            v-model="chargeMode"
                            :options="chargeModeOptions"
                            optionLabel="label"
                            optionValue="value"
                            :allowEmpty="false"
                            class="aed-charge-mode-buttons"
                        />
                        <div v-if="showPackageStyle" class="aed-payment-settle">
                            <label class="aed-payment-settle__label">Estilo do pacote</label>
                            <Select
                                v-model="packageStyle"
                                :options="packageStyleOptions"
                                optionLabel="label"
                                optionValue="value"
                                class="aed-payment-settle__select"
                                size="small"
                            />
                        </div>
                        <div v-if="showPaymentSettlement" class="aed-payment-settle">
                            <label class="aed-payment-settle__label">Forma de pagamento</label>
                            <Select
                                v-model="paymentMethod"
                                :options="paymentMethodOptions"
                                optionLabel="label"
                                optionValue="value"
                                class="aed-payment-settle__select"
                                size="small"
                            />
                        </div>
                        <div v-if="showPaymentSettlement && showMarkPaidToggle" class="aed-payment-settle">
                            <label class="aed-payment-settle__label">Status do pagamento</label>
                            <SelectButton
                                v-model="markPaidNow"
                                :options="markPaidOptions"
                                optionLabel="label"
                                optionValue="value"
                                :allowEmpty="false"
                                class="aed-charge-mode-buttons"
                            />
                        </div>
                        <span class="aed-charge-mode-hint">{{ chargeModeHint }}</span>
                    </div>
                </div>

                <!-- Hint contextual por tipo de cobrança — fullwidth, acima
                     do card Extras. Estava antes dentro do aed-row-50 (grid
                     50/50) e ocupava uma cell, empurrando Frequência pra
                     baixo. Movido pra fora do row em 2026-05-19.
                     Esconde quando há cobrança paga/pendente (lock-edit) —
                     a Message do panel já cobre. -->
                <div v-if="isSessionEvent && !occFinancialRecord && billingType === 'convenio'" class="aed-billing-hint mb-4">
                    <i class="pi pi-info-circle" />
                    <span><b>Nº da guia é opcional</b> — você pode salvar a sessão e preencher depois, quando o convênio responder.</span>
                </div>
                <div v-else-if="isSessionEvent && !occFinancialRecord && billingType === 'gratuito'" class="aed-billing-hint mb-4">
                    <i class="pi pi-gift" />
                    <span>Sessão <b>gratuita</b> — nenhum lançamento será gerado no Financeiro.</span>
                </div>

                <!-- ── EXTRAS (herdados do compromisso determinado) — último card.
                     'notes' é caso especial: bindamos em form.observacoes pra
                     manter compat com a coluna nativa agenda_eventos.observacoes
                     (consumida por relatórios/prontuário). Outros campos vão pra
                     agenda_eventos.extra_fields (JSONB) via form.extra_fields. -->
                <div v-if="selectedCommitmentFields.length" class="field-card mb-4">
                    <div class="field-card__header">
                        <i class="pi pi-list" />
                        <span>Extras</span>
                        <Button
                            icon="pi pi-question-circle"
                            v-tooltip.top="'Como funciona'"
                            severity="secondary"
                            outlined
                            size="small"
                            class="ml-auto rounded-full h-7 w-7"
                            @click="(e) => extrasHelpRef?.toggle(e)"
                        />
                        <Popover ref="extrasHelpRef">
                            <div class="aed-help-pop">
                                <div class="aed-help-pop__title"><i class="pi pi-info-circle" /> Como funcionam os campos Extras</div>
                                <ul class="aed-help-pop__list">
                                    <li>Esses campos vêm do <b>Compromisso Determinado</b> selecionado (ex.: Observação na Sessão, Livro/Autor na Leitura).</li>
                                    <li>Cada tipo de compromisso pode ter <b>campos próprios</b> — você define quais quer registrar em cada atendimento.</li>
                                    <li>Pra <b>adicionar, editar ou remover</b> campos extras, edite o tipo na tela de Compromissos.</li>
                                </ul>
                                <div class="aed-help-pop__link">
                                    <Button label="Gerenciar Compromissos" icon="pi pi-external-link" size="small" text @click="goToCompromissosConfig" />
                                </div>
                            </div>
                        </Popover>
                    </div>
                    <div class="field-card__body aed-extras-body">
                        <div class="fields-grid">
                            <div v-for="f in selectedCommitmentFields" :key="f.key" :class="{ 'col-span-full': f.field_type === 'textarea' }">
                                <FloatLabel variant="on">
                                    <template v-if="f.field_type === 'textarea' && f.key === 'notes'">
                                        <Textarea :id="`aed-extra-${f.key}`" v-model="form.observacoes" class="w-full" variant="filled" rows="3" autoResize :disabled="isArchivedPastEdit" />
                                    </template>
                                    <Textarea v-else-if="f.field_type === 'textarea'" :id="`aed-extra-${f.key}`" v-model="form.extra_fields[f.key]" class="w-full" variant="filled" rows="2" autoResize />
                                    <InputText v-else :id="`aed-extra-${f.key}`" v-model="form.extra_fields[f.key]" class="w-full" variant="filled" />
                                    <label :for="`aed-extra-${f.key}`">{{ f.label }}{{ f.required ? ' *' : '' }}</label>
                                </FloatLabel>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- ── RESUMO (mobile inline; desktop migra pro Teleport flutuante abaixo) ── -->
                <div class="side-card agenda-resumo agenda-resumo--mobile">
                    <div class="side-card__title">Resumo</div>

                    <div class="flex items-center gap-2 mb-2">
                        <span v-if="selectedCommitment?.bg_color" class="commit-badge" :style="{ background: `#${selectedCommitment.bg_color}20`, color: `#${selectedCommitment.bg_color}`, borderColor: `#${selectedCommitment.bg_color}40` }">{{
                            selectedCommitmentName
                        }}</span>
                        <Tag v-else :value="selectedCommitmentName" severity="info" />
                        <Tag v-if="isSessionEvent" :value="labelStatusSessao(form.status)" :severity="statusSeverity(form.status)" :class="statusExtraClass(form.status)" />
                    </div>

                    <div class="summary-row">
                        <i class="pi pi-user summary-icon" />
                        <span class="truncate">{{ form.paciente_nome || (isSessionEvent ? 'Sem paciente' : '—') }}</span>
                    </div>
                    <div class="summary-row">
                        <i class="pi pi-calendar summary-icon" />
                        <span class="truncate">{{ form.dia ? fmtDateBR(form.dia) : '—' }}</span>
                    </div>
                    <div class="summary-row">
                        <i class="pi pi-clock summary-icon" />
                        <span>{{ form.startTime || '—' }} → {{ fimDateTime ? fmtTime(fimDateTime) : '—' }}</span>
                    </div>
                    <div v-if="paymentSummary" class="summary-row aed-pay-summary-row" :class="paymentSummary.cls">
                        <i class="pi summary-icon" :class="paymentSummary.icon" />
                        <span class="truncate">{{ paymentSummary.label }}</span>
                    </div>
                    <div class="summary-row">
                        <i class="pi pi-map-marker summary-icon" />
                        <span><span class="capitalize">{{ form.modalidade || '—' }}</span><span v-if="isPacote"> · Pacote</span></span>
                    </div>
                    <div v-if="isSessionEvent" class="summary-row">
                        <i class="pi pi-wallet summary-icon" />
                        <span>{{ walletLabel }}</span>
                    </div>
                    <div v-if="isSessionEvent && pacoteTotal != null" class="summary-row aed-summary-row--calc">
                        <i class="pi pi-calculator summary-icon" />
                        <span>{{ freqValorConta }}</span>
                    </div>
                </div>
            </div>
        </div>
        </Transition>

        <!-- ══════════════════════════════════════════════ -->
        <!-- Patient Picker Dialog                          -->
        <!-- ══════════════════════════════════════════════ -->
        <Dialog
            v-model:visible="pacientePickerOpen"
            modal
            :draggable="false"
            :style="{ width: '860px', maxWidth: '96vw', border: '3px solid var(--p-primary-color)' }"
            :breakpoints="{ '960px': '96vw', '640px': '98vw' }"
        >
            <template #header>
                <div class="aed-svc-dlg-head">
                    <div class="aed-svc-dlg-head__icon">
                        <i class="pi pi-user" />
                    </div>
                    <div class="aed-svc-dlg-head__text">
                        <div class="aed-svc-dlg-head__title">Selecionar paciente</div>
                        <div class="aed-svc-dlg-head__sub">Busque por nome, e-mail ou telefone</div>
                    </div>
                </div>
            </template>
            <div class="flex flex-col gap-3 pt-3">
                <div class="flex flex-wrap items-center gap-3">
                    <div class="flex-1 min-w-[240px]">
                        <FloatLabel variant="on">
                            <IconField>
                                <InputIcon class="pi pi-search" />
                                <InputText id="aed-paciente-search" v-model="pacienteSearch" class="w-full" autocomplete="off" variant="filled" />
                            </IconField>
                            <label for="aed-paciente-search">Buscar por nome, e-mail ou telefone…</label>
                        </FloatLabel>
                    </div>
                    <Button label="Recarregar" icon="pi pi-refresh" severity="secondary" outlined class="rounded-full" :loading="pacientesLoading" @click="loadPatients(true)" />
                </div>

                <Message v-if="pacientesError" severity="warn">{{ pacientesError }}</Message>

                <DataTable
                    :value="filteredPatients"
                    dataKey="id"
                    :loading="pacientesLoading"
                    stripedRows
                    scrollable
                    paginator
                    :rows="10"
                    :rowsPerPageOptions="[10, 25, 50]"
                    paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
                    currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} paciente(s)"
                    class="aed-patient-dt"
                    :rowClass="(r) => (r.status && r.status !== 'Ativo' ? 'patient-row-blocked' : '')"
                >
                    <Column header="Paciente" sortable sortField="nome" style="min-width: 18rem">
                        <template #body="{ data }">
                            <div class="flex items-center gap-3">
                                <Avatar v-if="data.avatar_url" :image="data.avatar_url" shape="circle" />
                                <Avatar v-else :label="patientInitials(data.nome)" shape="circle" />
                                <div class="min-w-0">
                                    <div class="font-medium truncate">{{ data.nome || 'Sem nome' }}</div>
                                    <div class="text-[0.75rem] text-[var(--text-color-secondary)] truncate">
                                        <span v-if="data.email">{{ data.email }}</span>
                                        <span v-if="data.email && data.telefone"> • </span>
                                        <span v-if="data.telefone">{{ data.telefone }}</span>
                                        <span v-if="!data.email && !data.telefone">—</span>
                                    </div>
                                </div>
                            </div>
                        </template>
                    </Column>

                    <Column header="Status" sortable sortField="status" style="width: 9rem">
                        <template #body="{ data }">
                            <Tag v-if="data.status === 'Arquivado'" value="Arquivado" severity="danger" />
                            <Tag v-else-if="data.status === 'Inativo'" value="Inativo" severity="warn" />
                            <Tag v-else-if="data.status === 'Ativo'" value="Ativo" severity="success" />
                            <Tag v-else-if="data.status" :value="String(data.status)" severity="secondary" />
                            <span v-else class="text-[var(--text-color-secondary)] opacity-60">—</span>
                        </template>
                    </Column>

                    <Column header="Ação" style="width: 10rem; min-width: 10rem" :exportable="false" frozen alignFrozen="right">
                        <template #body="{ data }">
                            <Button
                                v-if="!data.status || data.status === 'Ativo'"
                                label="Selecionar"
                                icon="pi pi-check"
                                size="small"
                                outlined
                                rounded
                                @click="selectPaciente(data)"
                            />
                            <Button
                                v-else
                                icon="pi pi-lock"
                                size="small"
                                outlined
                                rounded
                                severity="secondary"
                                disabled
                                v-tooltip.top="data.status === 'Arquivado' ? 'Paciente arquivado — não é possível agendar' : 'Paciente inativo — não é possível agendar'"
                            />
                        </template>
                    </Column>

                    <template #empty>
                        <div class="py-8 text-center">
                            <i class="pi pi-search text-2xl opacity-20 mb-2 block" />
                            <div class="font-semibold text-[1rem]">Nenhum paciente encontrado</div>
                            <div class="text-[0.75rem] opacity-60 mt-1">Tente outro termo de busca.</div>
                            <Button v-if="pacienteSearch" class="mt-3" severity="secondary" outlined icon="pi pi-filter-slash" label="Limpar busca" size="small" @click="pacienteSearch = ''" />
                        </div>
                    </template>
                </DataTable>
            </div>
            <template #footer>
                <Button label="Fechar" icon="pi pi-times" text @click="pacientePickerOpen = false" />
            </template>
        </Dialog>

        <!-- ══════════════════════════════════════════════ -->
        <!-- Time Picker Dialog                             -->
        <!-- ══════════════════════════════════════════════ -->
        <!-- ══════════════════════════════════════════════ -->
        <!-- "Ver lançamentos da sessão" — sub-dialog       -->
        <!-- ══════════════════════════════════════════════ -->
        <Dialog v-model:visible="sessionRecordsDialogOpen" modal :draggable="false" header="Lançamentos da sessão" :style="{ width: '640px', maxWidth: '96vw' }">
            <div v-if="sessionRecordsLoading" class="py-6 text-center text-sm opacity-70">
                <i class="pi pi-spin pi-spinner mr-1" /> Carregando…
            </div>
            <div v-else-if="!sessionRecordsList.length" class="py-6 text-center text-sm opacity-70">
                <i class="pi pi-info-circle mr-1" /> Nenhum lançamento vinculado a esta sessão.
            </div>
            <div v-else class="aed-records-list">
                <div v-for="(r, idx) in sessionRecordsList" :key="r.id" class="aed-record" :class="{ 'aed-record--child': idx > 0 }">
                    <div class="aed-record__head">
                        <i v-if="idx > 0" class="pi pi-arrow-right-and-arrow-left-up-down aed-record__indent" />
                        <span class="aed-record__desc">{{ r.description || (idx === 0 ? 'Sessão' : 'Cobrança extra') }}</span>
                        <Tag :value="_recordStatusLabels[r.status] || r.status" :severity="_recordStatusSeverity(r.status)" class="text-xs ml-auto" />
                    </div>
                    <div class="aed-record__body">
                        <div class="aed-record__row">
                            <i class="pi pi-money-bill" />
                            <span class="aed-record__amount">{{ _fmtRecordBRL(r.final_amount || r.amount) }}</span>
                        </div>
                        <div v-if="r.payment_method" class="aed-record__row">
                            <i class="pi pi-credit-card" />
                            <span>{{ _recordMethodLabels[r.payment_method] || r.payment_method }}</span>
                        </div>
                        <div class="aed-record__row">
                            <i class="pi pi-calendar" />
                            <span>Vencimento: {{ _fmtRecordDate(r.due_date) }}</span>
                        </div>
                        <div v-if="r.paid_at" class="aed-record__row">
                            <i class="pi pi-check-circle" />
                            <span>Pago em {{ _fmtRecordDate(r.paid_at) }}</span>
                        </div>
                    </div>
                </div>
            </div>
            <template #footer>
                <Button label="Fechar" severity="secondary" outlined @click="sessionRecordsDialogOpen = false" />
            </template>
        </Dialog>

        <Dialog v-model:visible="timePickerOpen" modal :draggable="false" :style="{ width: '420px', maxWidth: '96vw' }" :breakpoints="{ '640px': '98vw' }" @show="onTimePickerShow" @hide="onTimePickerHide">
            <template #header>
                <div class="aed-svc-dlg-head">
                    <div class="aed-svc-dlg-head__icon">
                        <i class="pi pi-clock" />
                    </div>
                    <div class="aed-svc-dlg-head__text">
                        <div class="aed-svc-dlg-head__title">
                            {{ selectedCommitment?.name ? `Nova ${selectedCommitment.name}` : 'Nova sessão' }}
                        </div>
                        <div class="aed-svc-dlg-head__sub">Início da sessão e duração</div>
                    </div>
                </div>
            </template>
            <div class="flex flex-col gap-4">
                <!-- Mini calendar (estilo MelissaAgenda — compacto, com botao Hoje no header) -->
                <div class="mc-mini">
                    <div class="mc-mini__head">
                        <span class="mc-mini__title"><i class="pi pi-calendar" /> {{ miniMesAno }}</span>
                        <div class="mc-mini__nav">
                            <button type="button" class="mc-mini__icon" v-tooltip.top="'Mês anterior'" @click="miniPrev"><i class="pi pi-chevron-left" /></button>
                            <button type="button" class="mc-mini__today" @click="miniToday">Hoje</button>
                            <button type="button" class="mc-mini__icon" v-tooltip.top="'Próximo mês'" @click="miniNext"><i class="pi pi-chevron-right" /></button>
                        </div>
                    </div>
                    <div class="mc-mini__weekdays">
                        <span v-for="(d, i) in ['D','S','T','Q','Q','S','S']" :key="i">{{ d }}</span>
                    </div>
                    <div class="mc-mini__grid">
                        <button
                            v-for="(d, i) in miniDias"
                            :key="i"
                            type="button"
                            class="mc-mini__day"
                            :class="{ 'is-outro': d.outro, 'is-hoje': d.isHoje, 'is-selected': d.isSel }"
                            @click="selecionarDiaMini(d)"
                        >
                            {{ d.dia }}
                        </button>
                    </div>
                </div>

                <!-- Card: Horários disponíveis (presencial e online unificado) -->
                <div v-if="availableSlots.length" class="aed-card">
                    <div class="aed-card__header">
                        <span class="aed-card__title">Horários disponíveis</span>
                        <div class="flex gap-1">
                            <button v-for="p in activePeriodos" :key="p.label" class="periodo-chip" :class="{ 'periodo-chip--active': selectedPeriodo === p }" @click="selectedPeriodo = selectedPeriodo === p ? null : p">
                                <i :class="`pi ${p.icon}`" />
                                {{ p.label }}
                            </button>
                        </div>
                    </div>
                    <div class="aed-card__body">
                        <!-- Info online: lista os slots pré-configurados e orienta o usuário -->
                        <template v-if="form.modalidade === 'online'">
                            <Message v-if="!onlineAtivo" severity="warn" :closable="false" class="mb-2"> Atendimento online não está ativado. Ative em <strong>Configurações → Online</strong>. </Message>
                            <div v-else class="online-info-bar mb-2">
                                <i class="pi pi-video shrink-0" />
                                <span v-if="loadingOnlineSlots" class="opacity-60">Carregando horários online…</span>
                                <span v-else-if="onlineSlots.length">
                                    Horários online configurados para hoje:
                                    <strong>{{ onlineSlots.map((s) => s.hhmm).join(', ') }}</strong>
                                    <span class="online-info-bar__hint"> — marcados com <span class="online-dot-inline">●</span></span>
                                </span>
                                <span v-else class="opacity-70"> Nenhum horário online configurado para este dia — você pode agendar em qualquer horário da sua jornada. </span>
                                <!-- Botão cog: abre popover educativo sobre horário online + link
                                     pras configurações. Padrão espelhado do Card Extras. 2026-05-13. -->
                                <Button
                                    icon="pi pi-cog"
                                    v-tooltip.top="'Sobre horários online'"
                                    severity="secondary"
                                    text
                                    rounded
                                    size="small"
                                    class="online-info-bar__cog ml-auto h-6 w-6"
                                    @click="(e) => onlineHelpRef?.toggle(e)"
                                />
                                <Popover ref="onlineHelpRef">
                                    <div class="aed-help-pop">
                                        <div class="aed-help-pop__title"><i class="pi pi-video" /> Como funcionam os horários online</div>
                                        <ul class="aed-help-pop__list">
                                            <li>Você define <b>slots específicos</b> da semana para atendimento <b>online</b> (ex.: terça às 14h, quinta às 19h).</li>
                                            <li>No dialog, esses horários aparecem destacados com <span class="online-dot-inline">●</span>.</li>
                                            <li>Quando <b>não há</b> slot online configurado para o dia, você ainda pode agendar em qualquer horário da sua jornada — só não terá destaque visual.</li>
                                        </ul>
                                        <div class="aed-help-pop__link">
                                            <Button label="Configurar horários online" icon="pi pi-external-link" size="small" text @click="goToAgendaConfig" />
                                        </div>
                                    </div>
                                </Popover>
                            </div>
                        </template>

                        <!-- Grid de pills -->
                        <div class="slots-grid">
                            <button
                                v-for="s in filteredSlots"
                                :key="s.hhmm"
                                class="slot-pill"
                                :class="{
                                    'slot-pill--busy': s.busy,
                                    'slot-pill--current': form.startTime === s.hhmm,
                                    'slot-pill--online-cfg': form.modalidade === 'online' && s.isOnlineConfigured
                                }"
                                :title="s.busy ? 'Horário ocupado' : form.modalidade === 'online' && s.isOnlineConfigured ? `${s.hhmm} – ${s.endHhmm} · horário online configurado` : `${s.hhmm} – ${s.endHhmm}`"
                                @click="!s.busy && selectSlot(s.hhmm)"
                            >
                                {{ s.hhmm }}
                                <span v-if="form.modalidade === 'online' && s.isOnlineConfigured" class="slot-online-dot" />
                            </button>
                        </div>
                        <div v-if="filteredSlots.length === 0" class="text-xs opacity-50 mt-2">Nenhum horário disponível neste período.</div>
                    </div>
                </div>
                <div v-else-if="form.dia" class="text-xs opacity-50">Nenhum horário disponível para este dia (verifique a jornada nas configurações).</div>

                <!-- Início + Término lado a lado (Término readonly, derivado de fimDateTime) -->
                <div class="flex gap-3 items-end">
                    <div class="time-picker-wrap">
                        <label class="time-label">Início</label>
                        <DatePicker v-model="startTimeDate" timeOnly hourFormat="24" :stepMinute="5" showIcon iconDisplay="input" fluid placeholder="HH:MM">
                            <template #inputicon="slotProps">
                                <i class="pi pi-clock" @click="slotProps.clickCallback" />
                            </template>
                        </DatePicker>
                    </div>
                    <div class="time-picker-wrap">
                        <label class="time-label">Término</label>
                        <InputText :model-value="fimDateTime ? fmtTime(fimDateTime) : ''" readonly placeholder="—" fluid />
                    </div>
                </div>

                <!-- Card: Duração da sessão (pills + Select estilo V2) -->
                <div class="aed-card">
                    <div class="aed-card__header">
                        <span class="aed-card__title">Duração da sessão</span>
                    </div>
                    <div class="aed-card__body">
                        <div class="aed-duration-row">
                            <button
                                v-for="d in duracaoQuickChips"
                                :key="d"
                                type="button"
                                class="aed-pill"
                                :class="{ 'aed-pill--active': form.duracaoMin === d }"
                                :disabled="isDynamic && commitmentItems.length > 0"
                                @click="form.duracaoMin = d"
                            >
                                {{ d }}m
                            </button>
                            <Select
                                v-model="form.duracaoMin"
                                :options="duracaoOptions"
                                optionLabel="label"
                                optionValue="value"
                                optionGroupLabel="label"
                                optionGroupChildren="items"
                                size="small"
                                class="aed-duration-other"
                                placeholder="Outra"
                                :disabled="isDynamic && commitmentItems.length > 0"
                            />
                        </div>
                        <small v-if="isDynamic && commitmentItems.length > 0" class="text-color-secondary text-xs mt-2 block"> Calculado pelos serviços adicionados </small>
                    </div>
                </div>

                <Message v-if="timeConflict" severity="warn" class="mt-2" :closable="false">
                    <i class="pi pi-exclamation-triangle mr-1" />
                    {{ timeConflict }}
                </Message>
            </div>
            <template #footer>
                <Button label="Cancelar" severity="secondary" outlined @click="cancelTimePicker" />
                <Button label="Confirmar" icon="pi pi-check" @click="confirmTimePicker" />
            </template>
        </Dialog>

        <!-- Footer -->
        <template v-if="step === 2" #footer>
            <div class="flex items-center justify-between gap-2">
                <div class="flex items-center gap-2">
                    <Button v-if="!isEdit && allowBack" label="Voltar" icon="pi pi-arrow-left" severity="secondary" outlined size="small" class="rounded-full" @click="goBack" />
                    <Button v-if="isEdit && hasSerie" label="Encerrar série" icon="pi pi-trash" severity="danger" outlined size="small" class="rounded-full text-xs h-8" @click="onEncerrarSerie" />
                    <Button v-if="isEdit && !hasSerie" icon="pi pi-trash" severity="danger" outlined size="small" class="rounded-full h-9 w-9" v-tooltip.bottom="'Remover'" @click="onDelete" />

                    <!-- Lembrar paciente (WhatsApp on-demand) -->
                    <Button
                        v-if="isEdit && isSessionEvent && form.paciente_id"
                        icon="pi pi-whatsapp"
                        severity="success"
                        outlined
                        size="small"
                        class="rounded-full h-9 w-9"
                        v-tooltip.bottom="'Enviar lembrete WhatsApp agora'"
                        :loading="sendingReminder"
                        @click="onSendManualReminder"
                    />

                    <!-- Google Calendar link -->
                    <a
                        v-if="isEdit && googleCalendarUrl"
                        :href="googleCalendarUrl"
                        target="_blank"
                        rel="noopener noreferrer"
                        class="gcal-btn"
                        v-tooltip.top="'Abre o Google Agenda com o compromisso pré-preenchido. Em breve: sincronização automática e bidirecional com sua conta Google.'"
                    >
                        <svg class="gcal-btn__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
                            <rect x="3" y="4" width="18" height="18" rx="2" stroke="currentColor" stroke-width="1.7" />
                            <path d="M3 9h18" stroke="currentColor" stroke-width="1.7" />
                            <path d="M8 2v4M16 2v4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" />
                            <path d="M8 13h.01M12 13h.01M16 13h.01M8 17h.01M12 17h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
                        </svg>
                        <span>Google Agenda</span>
                    </a>
                </div>
                <Button label="Salvar" icon="pi pi-check" size="small" class="rounded-full" :disabled="!canSave" @click="onSave" />
            </div>
        </template>

        <!-- ══════════════════════════════════════════════ -->
        <!-- Cadastro Rápido de Paciente                    -->
        <!-- ══════════════════════════════════════════════ -->
        <ComponentCadastroRapido
            v-model="cadRapidoOpen"
            title="Novo Paciente"
            table-name="patients"
            name-field="nome_completo"
            email-field="email_principal"
            phone-field="telefone"
            :extra-payload="{ status: 'Ativo' }"
            hide-view-list-button
            @created="onPatientCreatedRapido"
        />

        <!-- ── Cadastro completo (PatientCadastroDialog inline — substitui
                 o open-em-nova-aba pra nao vazar do layout Melissa) ── -->
        <PatientCadastroDialog v-model="cadCompletoOpen" hide-view-list-button @created="onPatientCreatedCompleto" />

        <!-- ── Quick-create: Serviço ─────────────────────────── -->
        <ServiceQuickCreateDialog
            v-model="serviceQuickDlgOpen"
            :owner-id="ownerId"
            @created="onServiceCreated"
        />

        <!-- ── Quick-create: Convênio ────────────────────────── -->
        <InsurancePlanQuickCreateDialog
            v-model="insuranceQuickDlgOpen"
            :owner-id="planOwnerId || ownerId"
            @created="onInsuranceCreated"
        />

        <!-- ── Quick-create: Procedimento do convênio ────────── -->
        <InsurancePlanServiceQuickCreateDialog
            v-model="planServiceQuickDlgOpen"
            :insurance-plan-id="form.insurance_plan_id"
            :insurance-plan-name="selectedPlanName"
            @created="onPlanServiceCreated"
        />

        <!-- ══════════════════════════════════════════════ -->
        <!-- Service Selection Dialog (Particular OU Convênio) -->
        <!-- Substituiu a UI inline antiga do card Sessão / Honorários. -->
        <!-- ══════════════════════════════════════════════ -->
        <Dialog
            v-model:visible="serviceDialogOpen"
            modal
            :draggable="false"
            :style="{ width: '560px', maxWidth: '96vw' }"
            :breakpoints="{ '640px': '98vw' }"
        >
            <template #header>
                <div class="aed-svc-dlg-head">
                    <div class="aed-svc-dlg-head__icon">
                        <i :class="billingType === 'convenio' ? 'pi pi-shield' : 'pi pi-shopping-bag'" />
                    </div>
                    <div class="aed-svc-dlg-head__text">
                        <div class="aed-svc-dlg-head__title">{{ billingType === 'convenio' ? 'Convênio' : 'Serviços' }}</div>
                        <div class="aed-svc-dlg-head__sub">
                            {{ billingType === 'convenio' ? 'Plano, procedimento e valor da sessão' : 'Adicionar serviços para calcular o valor da sessão' }}
                        </div>
                    </div>
                </div>
            </template>

            <div class="pt-3">
            <!-- PARTICULAR: seletor de serviço + items list (unidades/preço/desconto/total) -->
            <template v-if="billingType === 'particular'">
                <!-- Hint educativo: clarifica a diferença entre "unidades" (qtd
                     do MESMO serviço NESTA sessão) e "recorrência" (qtd de
                     sessões agendadas em datas diferentes). -->
                <Message severity="info" :closable="false" class="mb-3">
                    <div class="aed-svc-hint">
                        <div class="aed-svc-hint__row">
                            <i class="pi pi-box" />
                            <span>
                                <b>Unidades</b> — quantas vezes o <b>mesmo serviço</b> é cobrado <b>nesta sessão</b>.
                                Ex.: <i>2 aplicações da mesma técnica numa única consulta</i>, ou <i>3 doses do mesmo procedimento no mesmo atendimento</i>.
                            </span>
                        </div>
                        <div class="aed-svc-hint__row">
                            <i class="pi pi-refresh" />
                            <span>
                                <b>Recorrência</b> — multiplica o valor desta sessão pela <b>quantidade de datas agendadas</b>.
                                Ex.: pacote semanal de 4 sessões. Configurado no card <b>Frequência</b>.
                            </span>
                        </div>
                    </div>
                </Message>

                <InputGroup class="aed-pay-inputgroup">
                    <Select
                        v-if="services.filter((s) => s.active).length"
                        v-model="servicePickerSel"
                        :options="services.filter((s) => s.active)"
                        optionLabel="name"
                        optionValue="id"
                        placeholder="Adicionar serviço..."
                        class="flex-1"
                        size="small"
                        @update:modelValue="
                            (id) => {
                                addItem(services.find((s) => s.id === id));
                                servicePickerSel = null;
                            }
                        "
                    />
                    <span v-else class="flex-1 text-xs text-color-secondary py-2 px-3 italic">Sem serviços cadastrados</span>
                    <Button icon="pi pi-plus" v-tooltip.top="'Cadastrar novo serviço'" severity="secondary" size="small" @click="openServiceQuickCreate" />
                    <Button icon="pi pi-question-circle" v-tooltip.top="'Como funciona'" severity="secondary" outlined size="small" @click="(e) => servicoHelpRef?.toggle(e)" />
                </InputGroup>
                <Popover ref="servicoHelpRef">
                    <div class="aed-help-pop">
                        <div class="aed-help-pop__title"><i class="pi pi-info-circle" /> Como adicionar serviços</div>
                        <ul class="aed-help-pop__list">
                            <li>Clique em um item do select pra <b>adicioná-lo</b> à sessão.</li>
                            <li>Clicando novamente no mesmo item, o valor é <b>somado</b> (aumenta a quantidade).</li>
                            <li>Use o botão <i class="pi pi-plus" /> pra <b>cadastrar</b> um serviço novo.</li>
                        </ul>
                    </div>
                </Popover>

                <div v-if="commitmentItems.length" class="aed-svc-list mt-3">
                    <div v-for="(item, idx) in commitmentItems" :key="idx" class="aed-svc-card">
                        <!-- Header: nome + remover -->
                        <div class="aed-svc-card__head">
                            <span class="aed-svc-card__name">{{ item.service_name }}</span>
                            <Button icon="pi pi-times" size="small" severity="secondary" text rounded class="aed-svc-card__remove" v-tooltip.top="'Remover'" @click="removeItem(idx)" />
                        </div>

                        <!-- Preço unitário + Total -->
                        <div class="aed-svc-card__row">
                            <div class="aed-svc-card__field">
                                <label class="aed-svc-card__label">Preço unitário</label>
                                <InputNumber v-model="item.unit_price" mode="currency" currency="BRL" locale="pt-BR" :min="0" :minFractionDigits="2" size="small" inputClass="w-28" @update:modelValue="onItemChange(item)" />
                            </div>
                            <div class="aed-svc-card__total">
                                <span class="aed-svc-card__total-label">Total</span>
                                <span class="aed-svc-card__total-value">{{ fmtBRL(item.final_price) }}</span>
                                <span v-if="itemHasDiscount(item)" class="aed-svc-card__calc">
                                    {{ fmtBRL(itemSubtotal(item)) }} − {{ fmtBRL(itemDiscountValue(item)) }}
                                </span>
                            </div>
                        </div>

                        <!-- Linha de ações: aplicar desconto + alterar quantidade -->
                        <div class="aed-svc-card__actions">
                            <!-- Desconto: botão quando fechado, painel inline quando aberto -->
                            <Button
                                v-if="!(item._showDiscount || item.discount_pct > 0 || item.discount_flat > 0)"
                                label="Aplicar desconto"
                                icon="pi pi-percentage"
                                size="small"
                                severity="secondary"
                                text
                                class="text-xs h-7"
                                @click="item._showDiscount = true"
                            />
                            <div v-else class="aed-svc-card__discount">
                                <InputNumber v-model="item.discount_pct" :min="0" :max="100" suffix="%" size="small" inputClass="w-14 text-center" placeholder="%" @update:modelValue="onItemChange(item)" />
                                <InputNumber v-model="item.discount_flat" mode="currency" currency="BRL" locale="pt-BR" :min="0" :minFractionDigits="2" size="small" inputClass="w-24" placeholder="R$" @update:modelValue="onItemChange(item)" />
                                <Button
                                    icon="pi pi-times"
                                    size="small"
                                    severity="secondary"
                                    text
                                    rounded
                                    v-tooltip.top="'Remover desconto'"
                                    @click="item._showDiscount = false; item.discount_pct = 0; item.discount_flat = 0; onItemChange(item)"
                                />
                            </div>

                            <!-- Quantidade: display "1 unidade ✎" / Select 1-9 quando editando -->
                            <div class="aed-svc-card__qty">
                                <template v-if="item._editingQty">
                                    <Select
                                        v-model="item.quantity"
                                        :options="[1,2,3,4,5,6,7,8,9]"
                                        size="small"
                                        class="aed-svc-card__qty-select"
                                        @update:modelValue="(v) => { item._editingQty = false; onItemChange(item); }"
                                    />
                                </template>
                                <button
                                    v-else
                                    type="button"
                                    class="aed-svc-card__qty-btn"
                                    v-tooltip.top="'Alterar quantidade'"
                                    @click="item._editingQty = true"
                                >
                                    <span>{{ item.quantity || 1 }} {{ (item.quantity || 1) === 1 ? 'unidade' : 'unidades' }}</span>
                                    <i class="pi pi-pencil" />
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </template>

            <!-- CONVÊNIO: plano + procedimento + nº guia + valor -->
            <template v-if="billingType === 'convenio'">
                <InputGroup class="aed-pay-inputgroup mb-2">
                    <Select
                        v-model="form.insurance_plan_id"
                        :options="activePlans"
                        optionLabel="name"
                        optionValue="id"
                        placeholder="Selecionar convênio..."
                        showClear
                        class="flex-1"
                        size="small"
                    />
                    <Button icon="pi pi-plus" v-tooltip.top="'Cadastrar novo convênio'" severity="secondary" size="small" @click="openInsuranceQuickCreate" />
                    <Button icon="pi pi-question-circle" v-tooltip.top="'Como funciona'" severity="secondary" outlined size="small" @click="(e) => convenioHelpRef?.toggle(e)" />
                </InputGroup>
                <Popover ref="convenioHelpRef">
                    <div class="aed-help-pop">
                        <div class="aed-help-pop__title"><i class="pi pi-info-circle" /> Como configurar o convênio</div>
                        <ul class="aed-help-pop__list">
                            <li><b>Selecione o convênio</b> do paciente no select.</li>
                            <li>Depois escolha o <b>procedimento</b> coberto e preencha <b>nº da guia</b> e <b>valor</b>.</li>
                            <li>Use o botão <i class="pi pi-plus" /> pra <b>cadastrar</b> um convênio novo na hora.</li>
                        </ul>
                    </div>
                </Popover>

                <template v-if="hasInsurance">
                    <template v-if="planServices.length > 0">
                        <label class="text-xs text-color-secondary mb-1 block">Procedimento</label>
                        <Select v-model="selectedPlanService" :options="planServices" optionLabel="name" optionValue="id" placeholder="Selecionar procedimento..." showClear class="w-full mb-2" size="small" @update:modelValue="onProcedureSelect" />
                    </template>
                    <!-- Cadastrar procedimento inline — sempre visível pra
                         convênio selecionado. Quando 0 procedimentos: chamada
                         pra cadastrar o primeiro (caso bloqueante). Quando
                         já tem 1+: oferta extra pra adicionar mais sem sair
                         do dialog. Abre InsurancePlanServiceQuickCreateDialog. -->
                    <div class="flex items-center justify-between gap-2 mb-2 p-2 rounded-lg bg-surface-100">
                        <span class="text-xs text-color-secondary">
                            <template v-if="planServices.length === 0">
                                Este convênio ainda não tem procedimentos cadastrados.
                            </template>
                            <template v-else>
                                Se quiser adicionar mais procedimentos a este convênio:
                            </template>
                        </span>
                        <Button label="Cadastrar" icon="pi pi-plus" size="small" severity="secondary" outlined class="rounded-full shrink-0 text-xs h-7" @click="openPlanServiceQuickCreate" />
                    </div>

                    <template v-if="selectedPlanService != null || planServices.length === 0">
                        <div class="flex gap-2">
                            <div class="flex-1">
                                <label class="text-xs text-color-secondary mb-1 block">Nº da Guia <span class="opacity-70">(opcional)</span></label>
                                <InputText v-model="form.insurance_guide_number" placeholder="Ex: 123456789" class="w-full" size="small" />
                            </div>
                            <div class="flex-1">
                                <label class="text-xs text-color-secondary mb-1 block">Valor (R$)</label>
                                <InputNumber v-model="form.insurance_value" mode="currency" currency="BRL" locale="pt-BR" :readonly="planServices.length > 0 && selectedPlanService != null" class="w-full" size="small" />
                            </div>
                        </div>
                    </template>
                </template>
            </template>
            </div>

            <template #footer>
                <div class="aed-svc-footer">
                    <div v-if="billingType === 'particular' && commitmentItems.length" class="aed-svc-footer__total ml-auto">
                        <span class="text-xs text-color-secondary">Valor desta sessão:</span>
                        <span class="aed-svc-list__total-value">{{ fmtBRL(totalFromItems) }}</span>
                    </div>
                    <Button label="Concluir" icon="pi pi-check" @click="serviceDialogOpen = false" />
                </div>
            </template>
        </Dialog>

        <!-- ══════════════════════════════════════════════ -->
        <!-- Frequência Dialog (config detalhada da recorrência) -->
        <!-- Substituiu a UI inline antiga do card Frequência. -->
        <!-- ══════════════════════════════════════════════ -->
        <Dialog
            v-model:visible="freqDialogOpen"
            modal
            :draggable="false"
            :style="{ width: '560px', maxWidth: '96vw' }"
            :breakpoints="{ '640px': '98vw' }"
        >
            <template #header>
                <div class="aed-svc-dlg-head">
                    <div class="aed-svc-dlg-head__icon">
                        <i class="pi pi-refresh" />
                    </div>
                    <div class="aed-svc-dlg-head__text">
                        <div class="aed-svc-dlg-head__title">Frequência</div>
                        <div class="aed-svc-dlg-head__sub">Repetição da sessão em datas futuras</div>
                    </div>
                </div>
            </template>

            <div class="pt-3 aed-freq-dlg">
                <Message v-if="form.paciente_id && !agendaPerms.canCreateRecurrence" severity="warn" class="mb-3" :closable="false">
                    <i class="pi pi-ban mr-1" />
                    <b>{{ form.paciente_status === 'Arquivado' ? 'Paciente arquivado.' : 'Paciente inativo.' }}</b>
                    Criação de recorrências está bloqueada.
                </Message>

                <!-- Card: Tipo de frequência -->
                <div class="aed-freq-section">
                    <div class="aed-freq-section__label">Tipo</div>
                    <div class="aed-freq-section__body">
                        <div class="flex gap-1.5 flex-wrap">
                            <button
                                v-for="f in freqOpcoes"
                                :key="f.value"
                                class="freq-tab"
                                :class="{
                                    'freq-tab--active': recorrenciaType === f.value,
                                    'opacity-40 cursor-not-allowed': f.value !== 'avulsa' && !agendaPerms.canCreateRecurrence
                                }"
                                :disabled="f.value !== 'avulsa' && !agendaPerms.canCreateRecurrence"
                                @click="!agendaPerms.canCreateRecurrence && f.value !== 'avulsa' ? null : (recorrenciaType = f.value)"
                            >
                                {{ f.label }}
                            </button>
                        </div>
                        <!-- Preview semanal/quinzenal — dentro do card de Tipo -->
                        <div v-if="recorrenciaType === 'semanal' || recorrenciaType === 'quinzenal'" class="aed-freq-preview">
                            <i class="pi pi-refresh" />
                            <span>
                                {{ recorrenciaType === 'quinzenal' ? 'A cada 2 semanas, toda' : 'Toda' }}
                                {{ nomeDiaSemana(diaSemanaRecorrencia) }}, às {{ form.startTime || '—' }}
                            </span>
                        </div>
                    </div>
                </div>

                <!-- Card: Dias da semana (só em diasEspecificos) -->
                <div v-if="recorrenciaType === 'diasEspecificos'" class="aed-freq-section">
                    <div class="aed-freq-section__label">Dias da semana</div>
                    <div class="aed-freq-section__body">
                        <div class="dias-semana-grid">
                            <button v-for="d in diasSemanaOpcoes" :key="d.value" class="dia-chip" :class="{ 'dia-chip--active': diasSelecionados.includes(d.value) }" @click="toggleDiaSelecionado(d.value)">{{ d.short }}</button>
                        </div>
                    </div>
                </div>

                <!-- Card: Quantidade de sessões (só quando não avulsa) -->
                <div v-if="recorrenciaType !== 'avulsa'" class="aed-freq-section">
                    <div class="aed-freq-section__label">Quantidade de sessões</div>
                    <div class="aed-freq-section__body">
                        <div class="flex gap-1.5 flex-wrap">
                            <button
                                v-for="opt in qtdSessoesOpcoes"
                                :key="opt.value"
                                class="freq-tab"
                                :class="{ 'freq-tab--active': qtdSessoesMode === opt.value, 'freq-tab--two-line': !!qtdSessoesTopLabel(opt.value) }"
                                @click="qtdSessoesMode = opt.value"
                            >
                                <span v-if="qtdSessoesTopLabel(opt.value)" class="freq-tab__top">{{ qtdSessoesTopLabel(opt.value) }}</span>
                                <span class="freq-tab__bottom">{{ opt.label }}</span>
                            </button>
                        </div>
                        <div v-if="qtdSessoesMode === 'personalizar'" class="personalizar-box mt-2">
                            <InputNumber v-model="qtdSessoesCustom" :min="1" :max="200" showButtons buttonLayout="horizontal" fluid>
                                <template #decrementbuttonicon><i class="pi pi-minus" /></template>
                                <template #incrementbuttonicon><i class="pi pi-plus" /></template>
                            </InputNumber>
                        </div>
                    </div>
                </div>

                <!-- Card: Próximas ocorrências -->
                <div v-if="recorrenciaType !== 'avulsa' && ocorrenciasComConflito.length" class="aed-freq-section">
                    <div class="aed-freq-section__label">
                        Próximas ocorrências
                        <span v-if="totalConflitos > 0" class="aed-freq-section__warn">
                            <i class="pi pi-exclamation-triangle" />
                            {{ totalConflitos }} com conflito
                        </span>
                    </div>
                    <div class="aed-freq-section__body">
                        <div class="ocorrencias-scroll">
                            <template v-for="(o, i) in ocorrenciasComConflito" :key="i">
                                <div v-if="freqShouldShowMonthSep(i)" class="aed-freq-month-sep">
                                    <span>{{ freqMonthLabel(o.date) }}</span>
                                </div>
                                <div
                                    class="ocorrencia-item"
                                    :class="{
                                        'ocorrencia-item--fora': o.conflict?.type === 'feriado' || o.conflict?.type === 'bloqueado',
                                        'ocorrencia-item--warn': o.conflict?.type === 'folga' || o.conflict?.type === 'pausa'
                                    }"
                                >
                                    <i
                                        class="pi"
                                        :class="{
                                            'pi-circle-fill': !o.conflict,
                                            'pi-exclamation-triangle': o.conflict?.type === 'folga' || o.conflict?.type === 'pausa',
                                            'pi-ban': o.conflict?.type === 'feriado' || o.conflict?.type === 'bloqueado'
                                        }"
                                        style="font-size: 9px"
                                    />
                                    <span class="text-xs">{{ fmtDateBRLong(o.date) }}</span>
                                    <span class="aed-freq-rel">{{ freqRelativeLabel(o.date) }}</span>
                                    <span v-if="o.conflict" class="ocorrencia-conflict-label">{{ o.conflict.label }}</span>
                                </div>
                            </template>
                        </div>
                        <Message v-if="totalConflitos > 0" severity="warn" class="mt-2" :closable="false">
                            <span class="text-xs">{{ totalConflitos }} sessão(ões) com conflito serão marcadas automaticamente para ajuste.</span>
                        </Message>
                    </div>
                </div>
            </div>

            <template #footer>
                <div class="aed-svc-footer">
                    <div v-if="recorrenciaType !== 'avulsa' && totalOcorrencias" class="aed-svc-footer__total ml-auto">
                        <span class="text-xs text-color-secondary">Sessões previstas:</span>
                        <span class="aed-svc-list__total-value">{{ totalOcorrencias }}</span>
                    </div>
                    <Button label="Concluir" icon="pi pi-check" @click="freqDialogOpen = false" />
                </div>
            </template>
        </Dialog>
    </Dialog>

    <!-- ── RESUMO flutuante (desktop only — mobile usa o card inline no fim do form) ──
         Teleporta pra body pra escapar do containing block do Dialog (transform
         centering quebraria position:fixed). Hidden via CSS em viewports
         <960px (la o card inline aparece). -->
    <Teleport to="body">
        <Transition name="resumo-fade">
        <div v-if="visible && step === 2 && !anyChildDialogOpen" class="aed-resumo-wrap" :style="{ top: resumoStyle.top, zIndex: 100000 }">
        <aside class="side-card agenda-resumo agenda-resumo--floating" :style="{ maxHeight: resumoStyle.maxHeight }">
            <div class="side-card__title">Resumo</div>

            <div class="flex items-center gap-2 mb-2">
                <span v-if="selectedCommitment?.bg_color" class="commit-badge" :style="{ background: `#${selectedCommitment.bg_color}20`, color: `#${selectedCommitment.bg_color}`, borderColor: `#${selectedCommitment.bg_color}40` }">{{
                    selectedCommitmentName
                }}</span>
                <Tag v-else :value="selectedCommitmentName" severity="info" />
                <Tag v-if="isSessionEvent" :value="labelStatusSessao(form.status)" :severity="statusSeverity(form.status)" :class="statusExtraClass(form.status)" />
            </div>

            <div class="summary-row">
                <i class="pi pi-user summary-icon" />
                <span class="truncate">{{ form.paciente_nome || (isSessionEvent ? 'Sem paciente' : '—') }}</span>
            </div>
            <div class="summary-row">
                <i class="pi pi-calendar summary-icon" />
                <span class="truncate">{{ form.dia ? fmtDateBR(form.dia) : '—' }}</span>
            </div>
            <div class="summary-row">
                <i class="pi pi-clock summary-icon" />
                <span>{{ form.startTime || '—' }} → {{ fimDateTime ? fmtTime(fimDateTime) : '—' }}</span>
            </div>
            <div v-if="paymentSummary" class="summary-row aed-pay-summary-row" :class="paymentSummary.cls">
                <i class="pi summary-icon" :class="paymentSummary.icon" />
                <span class="truncate">{{ paymentSummary.label }}</span>
            </div>
            <div class="summary-row">
                <i class="pi pi-map-marker summary-icon" />
                <span><span class="capitalize">{{ form.modalidade || '—' }}</span><span v-if="isPacote"> · Pacote</span></span>
            </div>
            <div v-if="isSessionEvent" class="summary-row">
                <i class="pi pi-wallet summary-icon" />
                <span>{{ walletLabel }}</span>
            </div>
            <div v-if="isSessionEvent && pacoteTotal != null" class="summary-row aed-summary-row--calc">
                <i class="pi pi-calculator summary-icon" />
                <span>{{ freqValorConta }}</span>
            </div>

            <!-- Message de conflito de horario (so desktop — mobile usa a inline embaixo do time-hero) -->
            <Message v-if="timeConflict" severity="warn" class="time-conflict-msg time-conflict-msg--floating" :closable="false">
                <i class="pi pi-exclamation-triangle mr-1" />
                {{ timeConflict }}
            </Message>
        </aside>

        <!-- Message de jornada FORA do card resumo (so desktop — mobile usa
             a inline no topo do form). Dentro do wrapper, fica logo abaixo
             do aside, com gap controlado pelo flex column do wrapper. -->
        <Message v-if="jornadaDialog" :severity="jornadaDialog.isOff ? 'warn' : 'info'" class="aed-msg-jornada aed-msg-jornada--floating" :closable="false">
            <i :class="jornadaDialog.isOff ? 'pi pi-moon mr-1' : 'pi pi-clock mr-1'" />
            {{ jornadaDialog.text }}
        </Message>
        </div>
        </Transition>
    </Teleport>
</template>

<style scoped>
.agenda-event-composer :deep(.p-dialog-content) {
    padding: 0.75rem;
}

/* ── Step transition: width do dialog + slide-fade do conteudo ──
   Step 1 (escolha de tipo) abre em 420px; Step 2 (formulario)
   expande pra 1000px. Transition cubic-bezier mais "easy-out"
   pra sensacao de elasticidade na expansao. */
.agenda-event-composer {
    transition: width 320ms cubic-bezier(0.16, 1, 0.3, 1);
}

/* Conteudo do step: fade + slide horizontal sutil. mode=out-in
   garante que step 1 sai antes de step 2 entrar (sem overlap). */
.step-fade-enter-active,
.step-fade-leave-active {
    transition: opacity 200ms ease, transform 240ms cubic-bezier(0.16, 1, 0.3, 1);
}
.step-fade-enter-from {
    opacity: 0;
    transform: translateX(16px);
}
.step-fade-leave-to {
    opacity: 0;
    transform: translateX(-12px);
}

/* ═══════════════════════════════════════════════════════════════════
   Polish visual disruptivo (V2) — overrides aplicados sobre o template
   existente sem mexer na lógica. Foco:
   1. Header do dialog com gradiente sutil + tipografia hierárquica
   2. Botões de quick-create destacados (mais "convidativos" no fluxo)
   3. Footer sticky com Salvar primário + sombra superior leve
   4. Dialog mask sem blur (animação/efeito nativo PrimeVue)
   5. Borda dos cards laterais com cor de seção (paciente/quando/quê)
   ═══════════════════════════════════════════════════════════════════ */

/* Backdrop do Dialog do composer (sem blur — animacao/efeito nativo
   do PrimeVue). */
.agenda-event-composer :deep(.p-dialog-mask) {
    background: rgba(15, 23, 42, 0.32);
}

/* "trocar tipo" — badge clicavel inline com o titulo (padrao .commit-badge
   + bg/border neutros + hover primary) */
.commit-badge.header-swap-btn {
    gap: 0.3rem;
    background: var(--surface-100, color-mix(in srgb, var(--surface-card) 92%, var(--text-color-secondary) 8%));
    color: var(--text-color-secondary);
    border-color: var(--surface-border);
    font-weight: 600;
    cursor: pointer;
    transition: background-color 140ms ease, color 140ms ease, border-color 140ms ease;
}
.commit-badge.header-swap-btn .pi {
    font-size: 0.62rem;
}
.commit-badge.header-swap-btn:hover {
    background: color-mix(in srgb, var(--p-primary-color) 12%, var(--surface-card));
    color: var(--p-primary-color);
    border-color: color-mix(in srgb, var(--p-primary-color) 35%, var(--surface-border));
}

/* Header do Dialog — gradient sutil + título com peso firme */
.agenda-event-composer :deep(.p-dialog-header) {
    background:
        radial-gradient(ellipse 600px 80px at 0% 0%, color-mix(in srgb, var(--p-primary-color) 8%, transparent), transparent 70%),
        var(--surface-card, #fff);
    border-bottom: 1px solid var(--surface-border);
}
.agenda-event-composer :deep(.p-dialog-title) {
    font-weight: 700;
    letter-spacing: -0.01em;
}

/* Footer sticky com sombra leve no topo, Salvar primary destacado */
.agenda-event-composer :deep(.p-dialog-footer) {
    border-top: 1px solid var(--surface-border);
    background: var(--surface-card, #fff);
    box-shadow: 0 -4px 18px -10px color-mix(in srgb, var(--p-primary-color) 25%, transparent);
}

/* Select Gratuito/Particular/Convenio no header do card Sessão/Honorários.
   Altura 28px (1.75rem) — mesma do botão "Ajustar horário" (h-7) e do
   toggle "Presencial/Online" no Paciente. Mantém consistência visual
   entre os 3 cards principais. */
.aed-pay-mod-select.p-select,
.aed-pay-mod-select :deep(.p-select) {
    height: 1.75rem;
    min-height: 1.75rem;
}
.aed-pay-mod-select :deep(.p-select-label) {
    padding: 0 0.7rem !important;
    font-size: 0.78rem;
    line-height: 1.75rem;
    text-transform: none;
    letter-spacing: 0;
    font-weight: 500;
}
.aed-pay-mod-select :deep(.p-select-dropdown) {
    width: 1.75rem;
}

/* Body do card Sessão/Honorários SEM padding — segue o pattern dos
   cards Paciente e Data e Horário, onde o padding mora nos filhos
   (.patient-hero__empty/__selected, .time-hero) pra os elementos
   ocuparem borda a borda. */
.aed-pay-body {
    padding: 0 !important;
}

/* Resumo compacto do billing quando configurado — padding proprio
   (igual .patient-hero__selected). */
.aed-pay-summary {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.9rem;
}
.aed-pay-summary__main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}
.aed-pay-summary__count {
    font-size: 0.78rem;
    color: var(--text-color-secondary);
}
.aed-pay-summary__value {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--p-primary-color);
}
.aed-freq-conta {
    font-size: 0.8rem;
    color: var(--text-color-secondary);
    font-variant-numeric: tabular-nums;
    margin-top: 0.15rem;
}
.aed-summary-row--calc {
    opacity: 0.75;
    font-size: 0.78rem;
}
.aed-pay-summary__discount {
    color: var(--p-red-500, #ef4444);
    font-weight: 500;
}

/* Aviso "Sessão gratuita" — leve, info, sem call-to-action. Padding
   próprio (body do card é zero), bordas removidas — fica encostado
   no padrão dos outros cards (.patient-hero__selected). */
.aed-pay-gratuito {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.9rem;
    font-size: 0.85rem;
    color: var(--text-color);
}
.aed-pay-gratuito i {
    color: var(--p-primary-color);
    font-size: 0.95rem;
}

/* Hint contextual abaixo do card Sessão / Honorários. Visual leve
   pra não competir com o card; cor neutra (não é warning nem error,
   apenas esclarecimento). */
.aed-billing-hint {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    font-size: 0.78rem;
    color: var(--text-color-secondary);
    background: color-mix(in srgb, var(--surface-border), transparent 60%);
    border-left: 3px solid var(--p-primary-color);
    border-radius: 0 6px 6px 0;
    line-height: 1.4;
}
.aed-billing-hint > i {
    color: var(--p-primary-color);
    font-size: 0.85rem;
    margin-top: 1px;
    flex-shrink: 0;
}
.aed-billing-hint b {
    color: var(--text-color);
}

/* Info card de pacote (saldo/upfront) — 2026-05-19 noite. Mostra status
   do contrato quando sessão pertence a série com billing_contract ativo.
   Saldo: tom violeta (contexto, modelo Cliniko). Upfront: tom verde
   (já cobrado, status positivo). */
.aed-contract-card {
    border-radius: 10px;
    border: 1px solid;
    overflow: hidden;
    font-size: 0.82rem;
}
.aed-contract-card--saldo {
    background: color-mix(in srgb, rgb(124, 58, 237) 6%, var(--surface-card));
    border-color: color-mix(in srgb, rgb(124, 58, 237) 30%, transparent);
}
.aed-contract-card--upfront {
    background: color-mix(in srgb, rgb(16, 185, 129) 6%, var(--surface-card));
    border-color: color-mix(in srgb, rgb(16, 185, 129) 30%, transparent);
}
.aed-contract-card__head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0.55rem 0.85rem;
    border-bottom: 1px solid color-mix(in srgb, var(--surface-border), transparent 30%);
    font-weight: 600;
}
.aed-contract-card--saldo .aed-contract-card__head > i {
    color: rgb(124, 58, 237);
}
.aed-contract-card--upfront .aed-contract-card__head > i {
    color: rgb(16, 185, 129);
}
.aed-contract-card__title {
    flex: 1;
}
.aed-contract-card__count {
    font-size: 0.74rem;
    padding: 2px 8px;
    border-radius: 999px;
    background: color-mix(in srgb, currentColor 10%, transparent);
    font-weight: 600;
    letter-spacing: 0.02em;
}
.aed-contract-card__body {
    padding: 0.7rem 0.85rem 0.85rem;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.aed-contract-card__row {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    font-size: 0.78rem;
}
.aed-contract-card__label {
    color: var(--text-color-secondary);
}
.aed-contract-card__value {
    font-weight: 600;
    color: var(--text-color);
}
.aed-contract-card__hint {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px dashed color-mix(in srgb, var(--surface-border), transparent 30%);
    font-size: 0.74rem;
    color: var(--text-color-secondary);
    line-height: 1.4;
}
.aed-contract-card__hint > i {
    margin-top: 1px;
    flex-shrink: 0;
}
.aed-contract-card--saldo .aed-contract-card__hint > i {
    color: rgb(124, 58, 237);
}
.aed-contract-card--upfront .aed-contract-card__hint > i {
    color: rgb(16, 185, 129);
}
.aed-contract-card__hint b {
    color: var(--text-color);
}
.aed-contract-card__loading {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: 0.5rem;
    font-size: 0.74rem;
    color: var(--text-color-secondary);
}
.aed-contract-card__loading > i {
    color: var(--p-primary-color);
}

/* Padding interno do card "Campos Extras (compromisso)" — mesmo
   tratamento do aed-pay-body. Sem isso os inputs ficam grudados nas
   bordas. */
.aed-extras-body {
    padding: 0.85rem 0.85rem 0.65rem !important;
}

/* Padding interno do card "Frequência" — mesmo tratamento dos outros
   bodies. Override do .field-card__body { padding: 0 } padrao. */
.aed-freq-body {
    padding: 0.85rem !important;
}

/* InputGroup do Particular/Convenio — botoes "+" e "?" grudam
   no select sem o gap separado de antes. Os addons herdam altura
   automaticamente do select via PrimeVue. */
.aed-pay-inputgroup :deep(.p-button) {
    flex-shrink: 0;
}
.aed-pay-inputgroup :deep(.p-button:last-child) {
    /* botao "?" outlined no canto direito — visual mais sutil
       pra nao competir com o "+" primario. */
    color: var(--text-color-secondary);
}

/* Popover de ajuda — bullets compactos, max-width pra nao crescer
   demais em telas grandes. padding interno (4px) pra evitar conteudo
   grudado nas bordas do balao do Popover. */
.aed-help-pop {
    max-width: 320px;
    font-size: 0.8rem;
    line-height: 1.45;
    padding: 10px;
}
.aed-help-pop__link {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--surface-border);
    display: flex;
    justify-content: flex-end;
}
.aed-help-pop__title {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-weight: 600;
    color: var(--p-primary-color);
    margin-bottom: 0.5rem;
    font-size: 0.85rem;
}
.aed-help-pop__list {
    margin: 0;
    padding-left: 1.1rem;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    color: var(--text-color);
}
.aed-help-pop__list li {
    list-style: disc;
}
.aed-help-pop__list .pi {
    font-size: 0.75rem;
    color: var(--p-primary-color);
    margin: 0 0.1rem;
}

/* Expand animation pro body do card Pagamento quando troca de
   Gratuito pra Particular/Convenio. max-height generoso (500px)
   cobre o caso convenio com todos os passos abertos sem cortar. */
.aed-pay-expand-enter-active,
.aed-pay-expand-leave-active {
    transition: opacity 180ms ease, max-height 220ms ease, transform 180ms ease, padding 180ms ease;
    overflow: hidden;
}
.aed-pay-expand-enter-from,
.aed-pay-expand-leave-to {
    opacity: 0;
    max-height: 0;
    padding-top: 0 !important;
    padding-bottom: 0 !important;
    transform: translateY(-4px);
}
.aed-pay-expand-enter-to,
.aed-pay-expand-leave-from {
    opacity: 1;
    max-height: 500px;
    transform: translateY(0);
}

/* Botão "+" de cadastro rápido (serviço/convênio) — outlined sutil que
   convida pra ação sem competir com o Select ao lado. */
.aed-quick-btn {
    flex-shrink: 0;
    border-radius: 10px !important;
    border-style: dashed !important;
    border-color: color-mix(in srgb, var(--p-primary-color) 40%, var(--surface-border)) !important;
    color: var(--p-primary-color) !important;
    transition: background-color 140ms ease, border-color 140ms ease, transform 140ms ease;
}
.aed-quick-btn:hover {
    background: color-mix(in srgb, var(--p-primary-color) 8%, transparent) !important;
    border-style: solid !important;
    transform: translateY(-1px);
}

/* "side-card" (paciente/financeiro/quando) — borda esquerda colorida pra
   diferenciar visualmente as seções. Cores diferentes ajudam a localizar
   info rapidamente sem precisar ler o título. */
.agenda-event-composer :deep(.side-card) {
    position: relative;
    background: var(--surface-card);
    border: 1px solid var(--surface-border);
    border-radius: 12px;
    padding: 14px;
    transition: border-color 160ms ease, box-shadow 160ms ease;
}
.agenda-event-composer :deep(.side-card)::before {
    content: '';
    position: absolute;
    top: 12px;
    bottom: 12px;
    left: 0;
    width: 3px;
    border-radius: 0 3px 3px 0;
    background: color-mix(in srgb, var(--p-primary-color) 60%, transparent);
    opacity: 0.7;
}
.agenda-event-composer :deep(.side-card):hover {
    border-color: color-mix(in srgb, var(--p-primary-color) 25%, var(--surface-border));
    box-shadow: 0 2px 12px -4px color-mix(in srgb, var(--p-primary-color) 18%, transparent);
}
.agenda-event-composer :deep(.side-card):hover::before {
    opacity: 1;
}

/* ── tag: remarcado (roxo — sem severity nativo no PrimeVue) ─ */
:deep(.tag-remarcado) {
    background: #a855f7 !important;
    color: #fff !important;
}

/* ── header dot ─────────────────────────────────── */
.header-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--p-primary-500, #6366f1);
    flex-shrink: 0;
}

/* ── step 1: commitment list (rows finas) ───────── */
.commitment-list {
    display: flex;
    flex-direction: column;
    gap: 0.375rem;
}
.commitment-row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    width: 100%;
    text-align: left;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--surface-border);
    border-radius: 8px;
    background: var(--surface-card);
    cursor: pointer;
    transition: background-color 120ms ease, border-color 120ms ease;
}
.commitment-row:hover {
    background: color-mix(in srgb, var(--card-color, var(--p-primary-500)) 6%, var(--surface-card));
    border-color: color-mix(in srgb, var(--card-color, var(--p-primary-500)) 45%, var(--surface-border));
}
.commitment-row:hover .commitment-row__chevron {
    transform: translateX(2px);
    opacity: 0.9;
}
.commitment-row__icon {
    display: grid;
    place-items: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 6px;
    background: color-mix(in srgb, var(--p-primary-500) 12%, transparent);
    color: var(--p-primary-500);
    font-size: 0.8rem;
    flex-shrink: 0;
}
.commitment-row__body {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    min-width: 0;
    flex: 1;
}
.commitment-row__name {
    font-weight: 600;
    font-size: 0.875rem;
    flex-shrink: 0;
    line-height: 1.2;
}
.commitment-row__desc {
    font-size: 0.75rem;
    color: var(--text-color-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    flex: 1;
    line-height: 1.2;
}
.commitment-row__chevron {
    color: var(--text-color-secondary);
    opacity: 0.5;
    font-size: 0.75rem;
    flex-shrink: 0;
    transition: transform 120ms ease, opacity 120ms ease;
}
.commitment-row--blocked {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;
    border-color: color-mix(in srgb, var(--red-500) 35%, var(--surface-border));
    background: color-mix(in srgb, var(--red-50, #fef2f2) 30%, var(--surface-card));
}
.commitment-row--blocked .commitment-row__chevron {
    color: var(--red-500);
    opacity: 0.8;
}

/* ── step 2: layout em coluna unica (todos os cards stacked) ──
   Antes era grid 2-col em desktop, 1-col em mobile. Agora sempre
   1-col (form + Escopo + Financeiro + Resumo, nessa ordem). No
   desktop o Resumo migra pro painel flutuante (Teleport p/ body)
   ancorado a direita do dialog — ver .agenda-resumo--floating. */
.composer-grid {
    display: flex;
    flex-direction: column;
    gap: 0;
    margin-top: 2px;
}

/* ── occurrence mode: layout enxuto pro 2º dialog empilhado ─── */
.composer-occurrence {
    display: flex;
    flex-direction: column;
    gap: 0;
    margin-top: 2px;
}
.aed-occ-data {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.75rem;
}
.aed-occ-data__row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.85rem;
    min-height: 1.5rem;
}
.aed-occ-data__icon {
    width: 1rem;
    color: var(--text-color-secondary);
    flex-shrink: 0;
    text-align: center;
}
.aed-occ-data__label {
    color: var(--text-color-secondary);
    font-weight: 500;
    min-width: 5.5rem;
    flex-shrink: 0;
}
.aed-occ-data__value {
    color: var(--text-color);
    font-weight: 600;
    min-width: 0;
}

/* ── preview antes/depois (occurrenceMode + scope > somente_este) ── */
.aed-preview-card {
    border: 1.5px solid var(--surface-border);
    border-radius: 6px;
    background: color-mix(in srgb, var(--surface-card), transparent 10%);
    overflow: hidden;
}
.aed-preview-card__header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    background: color-mix(in srgb, var(--surface-ground), transparent 30%);
    border-bottom: 1px solid var(--surface-border);
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-color-secondary);
}
.aed-preview-card__cols {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0;
}
.aed-preview-col {
    padding: 0.5rem;
    min-width: 0;
}
.aed-preview-col + .aed-preview-col {
    border-left: 1px solid var(--surface-border);
}
.aed-preview-col__title {
    font-size: 0.65rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-color-secondary);
    margin-bottom: 0.4rem;
    padding-left: 0.25rem;
}
.aed-preview-col--after .aed-preview-col__title {
    color: var(--p-primary-500);
}
.aed-preview-list {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}
.aed-preview-row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.3rem 0.45rem;
    border: 1px solid var(--surface-border);
    border-radius: 4px;
    background: var(--surface-card);
    font-size: 0.72rem;
}
.aed-preview-row--past {
    opacity: 0.55;
}
.aed-preview-row--changed {
    border-color: color-mix(in srgb, var(--p-primary-500) 50%, var(--surface-border));
    background: color-mix(in srgb, var(--p-primary-500) 6%, var(--surface-card));
}
.aed-preview-row__num {
    font-weight: 700;
    font-size: 0.7rem;
    color: var(--text-color-secondary);
    min-width: 1.2rem;
    flex-shrink: 0;
}
.aed-preview-row__date {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    line-height: 1.05;
    min-width: 0;
}
.aed-preview-row__weekday {
    font-size: 0.58rem;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--text-color-secondary);
}
.aed-preview-row__daynum {
    font-size: 0.85rem;
    font-weight: 700;
    color: var(--text-color);
}
.aed-preview-row__month {
    font-size: 0.55rem;
    font-weight: 600;
    text-transform: uppercase;
    color: var(--text-color-secondary);
}
.aed-preview-row__time {
    margin-left: auto;
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--text-color);
    flex-shrink: 0;
}

/* ── SelectButton "Cobrança ao salvar" (Opção C1, 2026-05-13) ─── */
.aed-charge-mode-row {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin: 0.5rem 0.75rem 0.75rem;
    padding: 0.7rem 0.85rem;
    border: 1px dashed var(--surface-border);
    border-radius: 6px;
    background: color-mix(in srgb, var(--p-primary-500) 3%, var(--surface-card));
}
.aed-charge-mode-label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-color);
    line-height: 1.3;
}
.aed-charge-mode-label i.pi-bolt {
    color: var(--p-primary-500);
}
.aed-charge-mode-buttons {
    align-self: flex-start;
}
/* Card "Cobrança" — fullwidth dedicado, 2026-05-14. Reusa pattern field-card. */
.aed-charge-card__body {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 0.85rem !important;
}
/* Mensagem de mudança de frequência → cobrança. Warning amarelo, persiste
   até user fechar o dialog OU trocar de tipo de novo. Slide+fade in/out +
   pulse de ~2.4s a cada troca (re-dispara via :key incrementado). */
.aed-charge-change-msg {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    padding: 0.6rem 0.8rem;
    border-radius: 6px;
    background: color-mix(in srgb, var(--amber-500, #f59e0b) 12%, var(--surface-card));
    border: 1px solid color-mix(in srgb, var(--amber-500, #f59e0b) 50%, transparent);
    font-size: 0.78rem;
    color: var(--amber-800, #92400e);
    line-height: 1.4;
    animation: aed-charge-msg-pulse 0.8s ease-in-out 3;
}
@keyframes aed-charge-msg-pulse {
    0%, 100% {
        box-shadow: 0 0 0 0 color-mix(in srgb, var(--amber-500, #f59e0b) 0%, transparent);
        transform: scale(1);
    }
    50% {
        box-shadow: 0 0 0 6px color-mix(in srgb, var(--amber-500, #f59e0b) 22%, transparent);
        transform: scale(1.012);
    }
}
.aed-charge-change-msg i {
    color: var(--amber-600, #d97706);
    font-size: 0.95rem;
    flex-shrink: 0;
    margin-top: 1px;
}
.aed-charge-warn-enter-active,
.aed-charge-warn-leave-active {
    transition: opacity 220ms ease, transform 220ms ease, max-height 220ms ease;
    overflow: hidden;
    max-height: 200px;
}
.aed-charge-warn-enter-from,
.aed-charge-warn-leave-to {
    opacity: 0;
    transform: translateY(-6px);
    max-height: 0;
}
.aed-charge-mode-hint {
    font-size: 0.8125rem;
    color: var(--text-color-secondary);
    line-height: 1.45;
}
.aed-payment-settle {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin-top: 0.2rem;
}
.aed-payment-settle__label {
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--text-color);
    flex-shrink: 0;
}
.aed-payment-settle__select {
    min-width: 16rem;
    flex: 1;
}

/* ── paciente hero ──────────────────────────────── */
.patient-hero {
    border: 1.5px solid var(--surface-border);
    border-radius: 6px;
    overflow: hidden;
    background: color-mix(in srgb, var(--surface-card), transparent 10%);
}
.patient-hero__label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0 0.6rem 0 0.9rem;
    height: 40px;
    min-height: 40px;
    max-height: 40px;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-color-secondary);
    border-bottom: 1px solid var(--surface-border);
    background: color-mix(in srgb, var(--surface-ground), transparent 30%);
}

/* Toggle Presencial/Online — substitui o label "Paciente" e o SelectButton
   antigo em todas as resolucoes. Mesmo visual do botao "Ajustar horário":
   pi-pencil + 1 palavra por vez (clicou alterna Presencial<->Online). */
.patient-hero__label-text {
    display: none;
}
.patient-hero__modal-btn {
    display: inline-flex;
}
/* Cancela o text-transform/letter-spacing herdados do .patient-hero__label
   (que sao UPPERCASE pra "PACIENTE"). O botao precisa do estilo proprio. */
.patient-hero__modal-btn :deep(.p-button-label) {
    text-transform: none;
    letter-spacing: 0;
    font-weight: 500;
}

/* Modalidade SelectButton inline no header do paciente — altura
   bate com o botao "Cadastro Rapido" h-7 (1.75rem = 28px) */
.patient-hero__mod-select :deep(.p-togglebutton) {
    height: 1.75rem !important;
    min-height: 1.75rem;
    padding: 0 0.7rem !important;
    font-size: 0.7rem !important;
    text-transform: none;
    letter-spacing: 0;
    font-weight: 600;
}

/* Botao "Cadastro completo" (pi-id-card) ao lado do SelectButton —
   icone maior + cor primary, altura igual ao SelectButton (28px).
   Classe vai direto no elemento .p-button da PrimeVue (sem :deep) */
.agenda-event-composer .patient-hero__cad-completo-btn {
    height: 1.75rem !important;
    width: 1.75rem !important;
    padding: 0 !important;
    color: var(--p-primary-color) !important;
}
.agenda-event-composer .patient-hero__cad-completo-btn :deep(.p-button-icon) {
    font-size: 1.15rem !important;
}
.agenda-event-composer .patient-hero__cad-completo-btn:hover {
    background: color-mix(in srgb, var(--p-primary-color) 12%, transparent) !important;
}

/* Time conflict Message — inline (mobile) hidden no desktop;
   floating versao aparece dentro do Teleport do Resumo. */
@media (min-width: 1200px) {
    .time-conflict-msg--inline {
        display: none !important;
    }
}
.time-conflict-msg--floating {
    margin-top: 0.65rem;
}

/* Jornada Message — segue o mesmo padrao do timeConflict: inline no
   topo do form em mobile (<1200px), e teleportada pra dentro do aside
   do Resumo em desktop (>=1200px). */
@media (min-width: 1200px) {
    .aed-msg-jornada--inline {
        display: none !important;
    }
}
.aed-msg-jornada--floating {
    /* Margin-top removido — gap do .aed-resumo-wrap (flex column) ja faz
       o espaçamento entre o aside (Resumo) e essa Message. */
    margin-top: 0;
}

/* Card genérico para seções (data/horário, etc.) */
.field-card {
    border-radius: 6px;
    border: 1px solid var(--surface-border);
    background: var(--surface-card);
    overflow: hidden;
}
.field-card__header {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 0 12px;
    height: 40px;
    min-height: 40px;
    max-height: 40px;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-color-secondary);
    border-bottom: 1px solid var(--surface-border);
    background: color-mix(in srgb, var(--surface-ground), transparent 30%);
    cursor: pointer;
}
.field-card__body {
    padding: 0;
}
.status-select-btn :deep(.p-selectbutton) {
    display: flex;
    width: 100%;
}
.status-select-btn :deep(.p-togglebutton) {
    flex: 1;
    justify-content: center;
    border-radius: 0 !important;
    border: none !important;
    border-right: 1px solid var(--surface-border) !important;
    padding: 0.65rem 0.5rem;
}
.status-select-btn :deep(.p-togglebutton:last-child) {
    border-right: none !important;
}
.patient-hero__empty {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    width: 100%;
    padding: 0.9rem;
    text-align: left;
    cursor: pointer;
    transition: background 0.1s;
}
.patient-hero__empty:hover {
    background: color-mix(in srgb, var(--p-primary-500) 5%, transparent);
}
.patient-hero__empty-icon {
    display: grid;
    place-items: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
    flex-shrink: 0;
    background: color-mix(in srgb, var(--p-primary-500) 12%, transparent);
    color: var(--p-primary-500);
    font-size: 1.1rem;
}
.patient-hero__selected {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.75rem 0.9rem;
}
.patient-avatar-bg :deep(.p-avatar) {
    background: color-mix(in srgb, var(--p-primary-500) 18%, transparent);
    color: var(--p-primary-500);
}

/* Mini links "Editar / Limpar" embaixo do nome do paciente —
   substituem os botoes redondos do canto direito. Pequenos,
   inline, com cores semanticas (primary p/ Editar, danger p/ Limpar). */
.patient-hero__link {
    background: transparent;
    border: 0;
    padding: 0;
    font-size: 0.95rem;
    color: var(--p-primary-color);
    cursor: pointer;
    font-family: inherit;
    text-decoration: none;
    transition: color 0.12s;
}
.patient-hero__link:hover {
    text-decoration: underline;
}
.patient-hero__link--danger {
    color: var(--p-red-500, #ef4444);
}
.patient-hero__link-sep {
    font-size: 0.95rem;
    color: var(--text-color-secondary);
    opacity: 0.5;
}

/* ── time hero ──────────────────────────────────── */
/* Layout em 2 linhas: linha 1 (Data • Duração), linha 2 (Início → Término). */
.time-hero {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 0.7rem 0.9rem;
    cursor: pointer;
    transition: background 0.12s;
    background: color-mix(in srgb, var(--surface-card), transparent 10%);
}
.time-hero:hover {
    background: color-mix(in srgb, var(--p-primary-500) 4%, var(--surface-card));
}
/* Readonly em modo edição — sem cursor pointer nem hover. */
.time-hero.time-hero--readonly {
    cursor: default;
}
.time-hero.time-hero--readonly:hover {
    background: color-mix(in srgb, var(--surface-card), transparent 10%);
}
.time-hero__line {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    flex-wrap: wrap;
}
.time-hero__date {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text-color);
}
.time-hero__bullet {
    color: var(--text-color-secondary);
    opacity: 0.5;
}
.time-hero__duration {
    font-size: 0.85rem;
    color: var(--text-color-secondary);
}
.time-hero__time {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--p-primary-color);
    font-variant-numeric: tabular-nums;
}
.time-hero__arrow {
    color: var(--text-color-secondary);
    opacity: 0.6;
}

/* ── fields grid ────────────────────────────────── */
.fields-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.85rem;
}
.fields-grid .col-span-full {
    grid-column: 1 / -1;
}
@media (max-width: 640px) {
    .fields-grid {
        grid-template-columns: 1fr;
    }
}

/* Row 50/50 para pares de cards (Paciente|Data+Horario,
   Sessao/Honorarios|Frequencia). Mobile (<768) empilha 1 coluna.
   Cancela mb-4 dos cards filhos pra a margem ser controlada
   pelo proprio row. */
.aed-row-50 {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.85rem;
    /* margin-bottom removido em 2026-05-19 (user request) — o espaçamento
       agora vem do mt do próximo .field-card.mb-4 (ver regra abaixo). */
}
@media (min-width: 768px) {
    .aed-row-50 {
        grid-template-columns: 1fr 1fr;
    }
}
.aed-row-50 > .field-card,
.aed-row-50 > .patient-hero {
    margin-bottom: 0 !important;
}

/* mt-4 em .field-card.mb-4 (user request 2026-05-19) — pra compensar
   a remoção do mb do .aed-row-50 e dar respiro vertical entre os
   cards do composer. Restrito aos wrappers do composer pra não
   afetar field-cards de outros lugares. */
.composer-left .field-card.mb-4,
.composer-right .field-card.mb-4,
.composer-occurrence .field-card.mb-4 {
    margin-top: 1rem;
}

/* ── side panel ─────────────────────────────────── */
.composer-right {
    /* layout single-col agora — sticky removido (nao faz sentido) */
    display: flex;
    flex-direction: column;
    gap: 0;
}

/* ── Resumo: mobile inline / desktop flutuante via Teleport ────
   Threshold em 1200px pra garantir espaco real ao lado do Dialog
   600px (600/2 + 14 gap + 280 card + 6 margem = ~600px de cada
   lado do centro = viewport >= 1200). Abaixo disso o card cai
   pro fim do form como inline, evitando overflow lateral. */
.agenda-resumo--mobile {
    display: block;
}
.aed-resumo-wrap,
.agenda-resumo--floating {
    display: none;
}
@media (min-width: 1200px) {
    .agenda-resumo--mobile {
        display: none;
    }
    /* Wrapper ancorado a direita do dialog. Hospeda o card de Resumo +
       Message de jornada (que fica FORA do card, abaixo). position+top+left
       saem daqui; aside agora é elemento normal no flow do wrapper.
       top é injetado via :style reativo (resumoStyle.top), sincronizado
       com o .p-dialog via ResizeObserver. */
    .aed-resumo-wrap {
        display: flex;
        flex-direction: column;
        gap: 0.55rem;
        position: fixed;
        left: calc(50% + 314px);
        width: 280px;
    }
    .agenda-resumo--floating {
        display: block;
        /* max-height aplicado via :style reativo (resumoStyle.maxHeight) pra
           que scroll interno acompanhe a altura do dialog. */
        overflow-y: auto;
        background: color-mix(in srgb, var(--surface-card) 88%, transparent);
        border: 1px solid var(--surface-border);
        border-radius: 14px;
        padding: 14px 16px;
        box-shadow: 0 16px 48px -8px rgba(0, 0, 0, 0.28), 0 4px 12px -2px rgba(0, 0, 0, 0.08);
        backdrop-filter: blur(20px) saturate(140%);
        -webkit-backdrop-filter: blur(20px) saturate(140%);
    }
    /* Accent stripe lateral (igual aos side-cards do dialog — pra
       sensacao de "mesmo familiar visual"). Usa a cor do commitment
       quando disponivel via CSS var. */
    .agenda-resumo--floating::before {
        content: '';
        position: absolute;
        top: 12px;
        bottom: 12px;
        left: 0;
        width: 3px;
        border-radius: 0 3px 3px 0;
        background: color-mix(in srgb, var(--p-primary-color) 60%, transparent);
        opacity: 0.7;
    }
}

/* Espaco extra entre cards no painel flutuante (summary-rows
   compactos, mas o badge no topo precisa respirar) */
.agenda-resumo--floating {
    position: relative; /* host pro ::before quando ativado */
}

/* Fade-up no enter (entra subindo + fade in) / fade-down no leave
   (sai descendo + fade out).

   Enter tem delay de 360ms — espera o dialog terminar a sua propria
   transition (step-fade leave 200ms + width 320ms = ~360ms total)
   antes do resumo aparecer. Sem isso, o resumo dispara junto com o
   click no commitment, antes do dialog mudar de tamanho/conteudo.

   Leave roda imediato (sem delay) — quando step volta pra 1 ou um
   sub-dialog abre, o resumo precisa sumir RAPIDO pra dar lugar. */
.resumo-fade-enter-active {
    transition: opacity 220ms cubic-bezier(0.16, 1, 0.3, 1) 360ms,
                transform 220ms cubic-bezier(0.16, 1, 0.3, 1) 360ms;
}
.resumo-fade-leave-active {
    transition: opacity 160ms ease,
                transform 160ms ease;
}
.resumo-fade-enter-from {
    opacity: 0;
    transform: translateY(12px);
}
.resumo-fade-leave-to {
    opacity: 0;
    transform: translateY(12px);
}

.side-card {
    border: 1px solid var(--surface-border);
    border-radius: 6px;
    padding: 0.9rem 1rem;
    background: color-mix(in srgb, var(--surface-card), transparent 10%);
}
.side-card__title {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-color-secondary);
    margin-bottom: 0.65rem;
}

/* resumo rows */
.summary-row {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    font-size: 0.82rem;
    color: var(--text-color);
    padding: 0.3rem 0;
    border-bottom: 1px solid color-mix(in srgb, var(--surface-border), transparent 40%);
}
.summary-row:last-child {
    border-bottom: none;
}
.summary-icon {
    font-size: 0.8rem;
    color: var(--text-color-secondary);
    opacity: 0.7;
    width: 1rem;
    flex-shrink: 0;
}

/* Linha "Cobrança" do Resumo — espelha as 3 cores da agenda
   (verde pago / amber pendente / vermelho atrasada / neutro none). */
.aed-pay-summary-row .summary-icon {
    opacity: 1;
    font-weight: 600;
}
.aed-pay-summary-row--paid {
    color: #047857; /* emerald-700 */
}
.aed-pay-summary-row--paid .summary-icon {
    color: #10b981; /* emerald-500 */
}
.aed-pay-summary-row--pending {
    color: #b45309; /* amber-700 */
}
.aed-pay-summary-row--pending .summary-icon {
    color: #f59e0b; /* amber-500 */
}
.aed-pay-summary-row--overdue {
    color: #b91c1c; /* red-700 */
}
.aed-pay-summary-row--overdue .summary-icon {
    color: #ef4444; /* red-500 */
}
.aed-pay-summary-row--none {
    color: var(--text-color-secondary);
}
.aed-pay-summary-row--none .summary-icon {
    color: var(--text-color-secondary);
    opacity: 0.85;
}

/* commit badge */
.commit-badge {
    display: inline-flex;
    align-items: center;
    padding: 0.2rem 0.65rem;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 700;
    border: 1px solid transparent;
    white-space: nowrap;
}

/* ── serie banner ───────────────────────────────── */
.serie-banner {
    border-radius: 6px;
    padding: 0.75rem 0.9rem;
    background: color-mix(in srgb, var(--blue-500, #3b82f6) 8%, var(--surface-card));
    border: 1px solid color-mix(in srgb, var(--blue-400, #60a5fa) 30%, transparent);
}
.serie-banner__head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.82rem;
    font-weight: 700;
    color: var(--blue-600, #2563eb);
}
.serie-banner__detail {
    font-size: 0.78rem;
    color: var(--text-color-secondary);
    margin-top: 0.25rem;
}

/* escopo de edição */
.scope-option {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    cursor: pointer;
}

/* ── freq tabs ──────────────────────────────────── */
.freq-tab {
    padding: 0.6rem 1.15rem;
    border-radius: 10px;
    font-size: 0.9rem;
    font-weight: 600;
    border: 1px solid var(--surface-border);
    background: var(--surface-card);
    color: var(--text-color);
    cursor: pointer;
    transition:
        background 0.12s,
        color 0.12s,
        border-color 0.12s;
}
.freq-tab:not(.freq-tab--active):hover {
    border-color: var(--p-primary-color);
    color: var(--p-primary-color);
    background: color-mix(in srgb, var(--p-primary-color) 6%, var(--surface-card));
}
.freq-tab--active {
    background: var(--p-primary-500, #6366f1);
    border-color: var(--p-primary-500, #6366f1);
    color: #fff;
}
.freq-tab--active:hover {
    background: color-mix(in srgb, var(--p-primary-500, #6366f1) 88%, black);
}

/* Variante 2 linhas — semanal/quinzenal exibem mes equivalente em cima. */
.freq-tab--two-line {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 0.1rem;
    line-height: 1.15;
    padding: 0.55rem 1.1rem;
}
.freq-tab__top {
    font-size: 0.95rem;
    font-weight: 700;
}
.freq-tab__bottom {
    font-size: 0.75rem;
    font-weight: 500;
    opacity: 0.8;
}

/* ── recorrência preview ────────────────────────── */
.recorrencia-preview {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border-radius: 6px;
    background: color-mix(in srgb, var(--p-primary-500) 8%, transparent);
    border: 1px solid color-mix(in srgb, var(--p-primary-400) 25%, transparent);
}

/* ocorrências */
.ocorrencias-preview {
    border-top: 1px solid var(--surface-border);
    padding-top: 0.65rem;
    margin-top: 0.65rem;
}
.ocorrencias-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 0.5rem;
}
.ocorrencias-aviso {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--orange-600, #ea580c);
}
.ocorrencias-scroll {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    max-height: 300px;
    overflow-y: auto;
    padding-right: 0.2rem;
}
.ocorrencia-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.25rem 0.3rem;
    border-radius: 0.4rem;
    color: var(--text-color);
    transition: background 0.1s;
}
.ocorrencia-item i {
    color: var(--p-primary-400);
    flex-shrink: 0;
}
.ocorrencia-item--fora {
    background: color-mix(in srgb, var(--red-500) 8%, transparent);
    color: var(--red-600, #dc2626);
}
.ocorrencia-item--fora i {
    color: var(--red-400, #f87171);
}
.ocorrencia-fora-label {
    margin-left: auto;
    font-size: 0.65rem;
    font-weight: 700;
    color: var(--red-500, #ef4444);
    white-space: nowrap;
    flex-shrink: 0;
}
.ocorrencia-item--warn {
    background: color-mix(in srgb, var(--orange-500) 8%, transparent);
    color: var(--orange-700, #c2410c);
}
.ocorrencia-item--warn i {
    color: var(--orange-400, #fb923c);
}
.ocorrencia-conflict-label {
    margin-left: auto;
    font-size: 0.65rem;
    font-weight: 700;
    color: inherit;
    white-space: nowrap;
    flex-shrink: 0;
    opacity: 0.8;
}

/* ── time picker ────────────────────────────────── */
.time-controls {
    display: flex;
    gap: 1rem;
    align-items: flex-end;
    flex-wrap: wrap;
}
.time-picker-wrap {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    min-width: 120px;
    flex: 1;
}
.time-label {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--text-color-secondary);
}
.time-fim {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    justify-content: flex-end;
    padding-bottom: 0.45rem;
}
.time-fim-value {
    font-size: 1rem;
    font-weight: 700;
    color: var(--text-color);
    white-space: nowrap;
}

/* Período filter chips */
.periodo-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 10px;
    border-radius: 999px;
    font-size: 0.72rem;
    font-weight: 600;
    border: 1px solid var(--surface-border);
    background: var(--surface-card);
    color: var(--text-color-secondary);
    cursor: pointer;
    transition:
        background 0.12s,
        color 0.12s;
}
.periodo-chip--active {
    background: var(--primary-500, #6366f1);
    color: #fff;
    border-color: var(--primary-500, #6366f1);
}

/* Slots grid */
.slots-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    max-height: 140px;
    overflow-y: auto;
}

.slot-pill {
    padding: 4px 10px;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 700;
    border: 1px solid var(--surface-border);
    background: var(--surface-card);
    color: var(--text-color);
    cursor: pointer;
    transition:
        background 0.1s,
        color 0.1s;
    letter-spacing: -0.01em;
}
.slot-pill:hover:not(.slot-pill--busy) {
    background: var(--primary-100, #e0e7ff);
    border-color: var(--primary-400, #818cf8);
    color: var(--primary-700, #3730a3);
}
.slot-pill--current {
    background: var(--primary-500, #6366f1) !important;
    color: #fff !important;
    border-color: var(--primary-500, #6366f1) !important;
}
.slot-pill--busy {
    opacity: 0.4;
    cursor: not-allowed;
    text-decoration: line-through;
}

/* Online slots card */
/* Info bar online (substitui o card separado) */
.online-info-bar {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 8px 12px;
    border-radius: 0.75rem;
    background: color-mix(in srgb, var(--blue-50, #eff6ff) 70%, var(--surface-card));
    border: 1px solid color-mix(in srgb, var(--blue-300, #93c5fd) 35%, transparent);
    font-size: 0.78rem;
    color: var(--blue-800, #1e40af);
}
.online-info-bar .pi-video {
    color: var(--blue-500, #3b82f6);
    margin-top: 1px;
}
.online-info-bar__hint {
    opacity: 0.7;
}

/* Dot inline para o texto de dica */
.online-dot-inline {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--blue-500, #3b82f6);
    vertical-align: middle;
}

/* Pill marcado como configurado para online */
.slot-pill--online-cfg {
    border-color: color-mix(in srgb, var(--blue-400, #60a5fa) 60%, transparent) !important;
    background: color-mix(in srgb, var(--blue-50, #eff6ff) 80%, var(--surface-card)) !important;
    color: var(--blue-700, #1d4ed8) !important;
}
.slot-pill--online-cfg:hover:not(.slot-pill--busy) {
    background: var(--blue-100, #dbeafe) !important;
}
/* Combo selected + online-cfg: precedência pro selected (mais especificidade
   que .slot-pill--online-cfg sozinha). Sem isso, o azul-claro do online
   ganhava sobre o primary do selected. 2026-05-13. */
.slot-pill--current.slot-pill--online-cfg {
    background: var(--primary-500, #6366f1) !important;
    color: #fff !important;
    border-color: var(--primary-500, #6366f1) !important;
}

/* Dot azul dentro do pill quando é slot online configurado */
.slot-online-dot {
    display: inline-block;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--blue-500, #3b82f6);
    margin-left: 3px;
    vertical-align: middle;
    flex-shrink: 0;
}

/* ── rec startdate row ──────────────────────────── */
.rec-startdate-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.45rem 0.65rem;
    border-radius: 6px;
    background: color-mix(in srgb, var(--surface-ground), transparent 30%);
    border: 1px solid var(--surface-border);
}

/* ── freq chips (frequência principal) ──────────── */
.freq-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}
.freq-chip {
    padding: 0.3rem 0.7rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    border: 1px solid var(--surface-border);
    background: transparent;
    color: var(--text-color-secondary);
    cursor: pointer;
    transition:
        background 0.1s,
        color 0.1s,
        border-color 0.1s;
    white-space: nowrap;
}
.freq-chip:hover {
    border-color: var(--p-primary-400);
    color: var(--p-primary-500);
}
.freq-chip--active {
    background: var(--p-primary-500, #6366f1);
    border-color: var(--p-primary-500, #6366f1);
    color: #fff;
}

/* ── dias da semana grid ────────────────────────── */
.dias-semana-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}
.dia-chip {
    padding: 0.3rem 0.6rem;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 700;
    border: 1px solid var(--surface-border);
    background: transparent;
    color: var(--text-color-secondary);
    cursor: pointer;
    transition:
        background 0.1s,
        color 0.1s,
        border-color 0.1s;
    min-width: 2.6rem;
    text-align: center;
}
.dia-chip:hover {
    border-color: var(--p-primary-400);
    color: var(--p-primary-500);
}
.dia-chip--active {
    background: var(--p-primary-500, #6366f1);
    border-color: var(--p-primary-500, #6366f1);
    color: #fff;
}

/* ── personalizar box ───────────────────────────── */
.personalizar-box {
    border: 1px solid var(--surface-border);
    border-radius: 6px;
    padding: 0.75rem;
    background: color-mix(in srgb, var(--surface-ground), transparent 40%);
    display: flex;
    flex-direction: column;
    gap: 0.65rem;
}

/* ── patient list ───────────────────────────────── */
/* DataTable do picker de paciente (.aed-patient-dt). Linha cujo status !==
   Ativo recebe .patient-row-blocked — opacidade reduzida e cursor not-allowed,
   pareando com o botao de acao desabilitado. */
.aed-patient-dt :deep(.patient-row-blocked) {
    opacity: 0.6;
}
.aed-patient-dt :deep(.patient-row-blocked) > td {
    cursor: not-allowed;
}

/* ─── Time Picker — mini calendar (estilo MelissaAgenda) ───
   Versao compacta, sem dots/feriados. Tokens PrimeVue pra herdar tema. */
.mc-mini {
    width: 100%;
    max-width: 270px;
    margin: 0 auto;
    padding: 0.65rem;
    border: 1px solid var(--surface-border);
    border-radius: 8px;
    background: var(--surface-card);
}
.mc-mini__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 0.5rem;
}
.mc-mini__title {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.78rem;
    font-weight: 600;
    color: var(--text-color);
    text-transform: capitalize;
}
.mc-mini__title i {
    font-size: 0.72rem;
    color: var(--text-color-secondary);
}
.mc-mini__nav {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
}
.mc-mini__icon,
.mc-mini__today {
    background: transparent;
    border: 0;
    color: var(--text-color-secondary);
    cursor: pointer;
    padding: 0.25rem 0.5rem;
    border-radius: 6px;
    font-family: inherit;
    font-size: 0.72rem;
    transition: background 0.12s, color 0.12s;
}
.mc-mini__icon { padding: 0.25rem 0.4rem; }
.mc-mini__icon i { font-size: 0.7rem; }
.mc-mini__icon:hover,
.mc-mini__today:hover {
    background: color-mix(in srgb, var(--p-primary-color) 10%, transparent);
    color: var(--p-primary-color);
}
.mc-mini__weekdays {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 0.1rem;
    margin-bottom: 0.2rem;
}
.mc-mini__weekdays > span {
    text-align: center;
    font-size: 0.6rem;
    font-weight: 600;
    color: var(--text-color-secondary);
    opacity: 0.6;
    padding: 0.2rem 0;
    text-transform: uppercase;
}
.mc-mini__grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 0.15rem;
}
.mc-mini__day {
    background: transparent;
    border: 0;
    font-family: inherit;
    font-size: 0.72rem;
    color: var(--text-color);
    aspect-ratio: 1;
    padding: 0;
    border-radius: 6px;
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}
.mc-mini__day:hover:not(.is-selected) {
    background: color-mix(in srgb, var(--p-primary-color) 8%, transparent);
}
.mc-mini__day.is-outro {
    color: var(--text-color-secondary);
    opacity: 0.4;
}
.mc-mini__day.is-hoje:not(.is-selected) {
    color: var(--p-primary-color);
    font-weight: 700;
}
.mc-mini__day.is-selected {
    background: var(--p-primary-color);
    color: var(--p-primary-contrast-color, #fff);
    font-weight: 600;
}

/* ─── Time Picker — cards (Horarios disponiveis / Duracao da sessao) ───
   Padrao visual consistente: borda neutra, header com titulo + acoes,
   corpo com padding. Combina com a borda primary do dialog inteiro. */
.aed-card {
    border: 1px solid var(--surface-border);
    border-radius: 8px;
    background: var(--surface-card);
    overflow: hidden;
}
.aed-card__header {
    padding: 0.6rem 0.85rem;
    border-bottom: 1px solid var(--surface-border);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}
.aed-card__title {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text-color);
}
.aed-card__body {
    padding: 0.85rem;
}

/* Pills + Select de duracao — pattern do AgendaEventDialogV2 */
.aed-duration-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
}
.aed-pill {
    border: 1px solid var(--surface-border);
    background: transparent;
    padding: 0.4rem 0.9rem;
    border-radius: 999px;
    font-size: 0.78rem;
    color: var(--text-color);
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.aed-pill:hover:not(:disabled) {
    border-color: var(--p-primary-400, #818cf8);
    color: var(--p-primary-500, #6366f1);
}
.aed-pill--active {
    background: var(--p-primary-500, #6366f1);
    border-color: var(--p-primary-500, #6366f1);
    color: #fff;
    font-weight: 600;
}
.aed-pill:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.aed-duration-other {
    width: 130px;
}

/* ── serie panel (Recorrências Aplicadas) ─────────── */
.serie-panel {
    border: 1px solid var(--surface-border);
    border-radius: 6px;
    overflow: hidden;
}
.serie-panel__header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.9rem;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-color-secondary);
    background: color-mix(in srgb, var(--surface-ground), transparent 20%);
    border-bottom: 1px solid var(--surface-border);
}
.serie-panel__stats {
    display: flex;
    gap: 0.3rem;
    font-size: 0.7rem;
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0;
    margin-left: 0.25rem;
}
/* Contadores coloridos por estado no header de Recorrências Aplicadas */
.serie-stat--total {
    color: rgb(37, 99, 235); /* blue-600 */
}
.serie-stat--realizado {
    color: rgb(5, 150, 105); /* emerald-600 */
}
.serie-stat--faltou {
    color: rgb(217, 119, 6); /* amber-600 */
}
.serie-stat--cancelado {
    color: rgb(120, 113, 108); /* stone-500 */
}
.serie-stat--remarcado {
    color: rgb(124, 58, 237); /* violet-600 */
}
html.app-dark .serie-stat--total {
    color: rgb(96, 165, 250); /* blue-400 */
}
html.app-dark .serie-stat--realizado {
    color: rgb(52, 211, 153); /* emerald-400 */
}
html.app-dark .serie-stat--faltou {
    color: rgb(251, 191, 36); /* amber-400 */
}
html.app-dark .serie-stat--cancelado {
    color: rgb(168, 162, 158); /* stone-400 */
}
html.app-dark .serie-stat--remarcado {
    color: rgb(167, 139, 250); /* violet-400 */
}
.serie-panel__empty {
    padding: 0.85rem;
    font-size: 0.82rem;
    color: var(--text-color-secondary);
    text-align: center;
}
.serie-pills-wrap {
    display: flex;
    flex-direction: column;
    max-height: 340px;
    overflow-y: auto;
}
.serie-pill {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.4rem 0.9rem;
    border-bottom: 1px solid color-mix(in srgb, var(--surface-border), transparent 55%);
    transition: background 0.1s;
    border-left: 3px solid transparent;
}
.serie-pill:last-child {
    border-bottom: none;
}
.serie-pill:hover {
    background: color-mix(in srgb, var(--surface-ground), transparent 10%);
}
.serie-pill--current {
    background: color-mix(in srgb, var(--p-primary-500) 7%, transparent);
    border-left-color: var(--p-primary-500);
}
.serie-pill--past {
    opacity: 0.5;
}
.serie-pill--realizado {
    border-left-color: var(--green-400, #4ade80);
}
.serie-pill--faltou {
    border-left-color: var(--red-400, #f87171);
}
.serie-pill--cancelado {
    border-left-color: var(--surface-border);
}
.serie-pill--remarcado {
    border-left-color: var(--orange-400, #fb923c);
}
.serie-pill__num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.5rem;
    height: 1.5rem;
    border-radius: 999px;
    background: color-mix(in srgb, var(--p-primary-color) 12%, transparent);
    color: var(--p-primary-color);
    font-size: 0.72rem;
    font-weight: 700;
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
    padding: 0 0.4rem;
}
.serie-pill__date {
    display: flex;
    flex-direction: column;
    align-items: center;
    min-width: 2.5rem;
    flex-shrink: 0;
    line-height: 1.15;
}
.serie-pill__weekday {
    font-size: 0.58rem;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--text-color-secondary);
    letter-spacing: 0.04em;
}
.serie-pill__daynum {
    font-size: 1rem;
    font-weight: 800;
    color: var(--text-color);
}
.serie-pill__month {
    font-size: 0.58rem;
    font-weight: 600;
    text-transform: uppercase;
    color: var(--text-color-secondary);
}
.serie-pill__time {
    font-size: 0.72rem;
    color: var(--text-color-secondary);
    flex-shrink: 0;
    min-width: 2.8rem;
}
.serie-pill__status-sel {
    flex: 1;
    min-width: 0;
    max-width: 125px;
}
.serie-pill__status-badge {
    flex: 1;
    min-width: 0;
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--text-color-secondary);
    text-transform: capitalize;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Badge sólido por status quando relevante (realizado/faltou/cancelado/
   remarcado). Agendado fica neutro (default acima). */
.serie-pill--realizado .serie-pill__status-badge,
.serie-pill--realizada .serie-pill__status-badge {
    flex: 0 0 auto;
    color: white;
    background: rgb(5, 150, 105); /* emerald-600 */
    padding: 0.15rem 0.6rem;
    border-radius: 999px;
    font-size: 0.66rem;
    letter-spacing: 0.02em;
}
.serie-pill--faltou .serie-pill__status-badge {
    flex: 0 0 auto;
    color: white;
    background: rgb(217, 119, 6); /* amber-600 */
    padding: 0.15rem 0.6rem;
    border-radius: 999px;
    font-size: 0.66rem;
    letter-spacing: 0.02em;
}
.serie-pill--cancelado .serie-pill__status-badge,
.serie-pill--cancelada .serie-pill__status-badge {
    flex: 0 0 auto;
    color: white;
    background: rgb(120, 113, 108); /* stone-500 */
    padding: 0.15rem 0.6rem;
    border-radius: 999px;
    font-size: 0.66rem;
    letter-spacing: 0.02em;
}
.serie-pill--remarcado .serie-pill__status-badge,
.serie-pill--remarcada .serie-pill__status-badge {
    flex: 0 0 auto;
    color: white;
    background: rgb(124, 58, 237); /* violet-600 */
    padding: 0.15rem 0.6rem;
    border-radius: 999px;
    font-size: 0.66rem;
    letter-spacing: 0.02em;
}
.serie-pill__cur-badge {
    font-size: 0.58rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--p-primary-500);
    background: color-mix(in srgb, var(--p-primary-500) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--p-primary-500) 30%, transparent);
    border-radius: 999px;
    padding: 0.1rem 0.45rem;
    flex-shrink: 0;
    white-space: nowrap;
}
.serie-pill__del {
    flex-shrink: 0;
    width: 2rem;
}

/* ── Service cards (dialog Sessão / Honorários) ─────────
   Layout novo (2026-05-11): cada serviço adicionado vira um card
   individual com nome em destaque, preço unitário + total, e ações
   colapsáveis (Aplicar desconto, Alterar quantidade). */
.aed-svc-list {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
}
.aed-svc-card {
    border: 1px solid var(--surface-border);
    border-radius: 10px;
    background: color-mix(in srgb, var(--surface-card), transparent 0%);
    padding: 0.85rem 0.95rem;
    transition: border-color 0.12s, box-shadow 0.12s;
}
.aed-svc-card:hover {
    border-color: color-mix(in srgb, var(--p-primary-color) 40%, var(--surface-border));
}
.aed-svc-card__head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.65rem;
}
.aed-svc-card__name {
    flex: 1;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--text-color);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.aed-svc-card__remove {
    flex-shrink: 0;
}
.aed-svc-card__row {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 0.6rem;
}
.aed-svc-card__field {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}
.aed-svc-card__label {
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--text-color-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.aed-svc-card__total {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 0.1rem;
}
.aed-svc-card__total-label {
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--text-color-secondary);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.aed-svc-card__total-value {
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--p-primary-color);
    font-variant-numeric: tabular-nums;
}
.aed-svc-card__calc {
    font-size: 0.7rem;
    color: var(--p-red-500, #ef4444);
    font-variant-numeric: tabular-nums;
}
.aed-svc-card__actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    flex-wrap: wrap;
    padding-top: 0.5rem;
    border-top: 1px dashed var(--surface-border);
}
.aed-svc-card__discount {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}
.aed-svc-card__qty {
    display: inline-flex;
    align-items: center;
}
.aed-svc-card__qty-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    background: transparent;
    border: 1px dashed var(--surface-border);
    border-radius: 999px;
    padding: 0.25rem 0.7rem;
    font-size: 0.75rem;
    color: var(--text-color-secondary);
    cursor: pointer;
    font-family: inherit;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.aed-svc-card__qty-btn:hover {
    border-color: var(--p-primary-color);
    color: var(--p-primary-color);
    background: color-mix(in srgb, var(--p-primary-color) 6%, transparent);
}
.aed-svc-card__qty-btn i {
    font-size: 0.7rem;
}
.aed-svc-card__qty-select :deep(.p-select) {
    height: 1.75rem;
    min-height: 1.75rem;
}
.aed-svc-card__qty-select :deep(.p-select-label) {
    padding: 0 0.5rem !important;
    font-size: 0.8rem;
    line-height: 1.75rem;
    min-width: 1.5rem;
}
.aed-svc-list__total-value {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--p-primary-color);
    font-variant-numeric: tabular-nums;
}

/* Footer do dialog Serviços — total fixo no canto esquerdo, botão
   Concluir no direito. Usa o area nativa do PrimeVue Dialog footer
   (que ja fica fixa no bottom). */
.aed-svc-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 1rem;
    width: 100%;
}
.aed-svc-footer__total {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0 0.85rem;
    height: 2.25rem;
    border: 1px dashed var(--p-primary-color);
    border-radius: 8px;
    background: color-mix(in srgb, var(--p-primary-color) 5%, transparent);
    line-height: 1;
}
.aed-svc-footer__total .aed-svc-list__total-value {
    font-size: 0.95rem;
}

/* Sections do dialog Frequência — cada bloco (Tipo, Dias, Quantidade,
   Próximas ocorrências) vira um sub-card com label superior + body.
   Estilo coerente com o .field-card principal mas mais compacto. */
.aed-freq-dlg {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}
.aed-freq-section {
    border: 1px solid var(--surface-border);
    border-radius: 10px;
    overflow: hidden;
    background: var(--surface-card);
}
.aed-freq-section__label {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0 0.85rem;
    height: 32px;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-color-secondary);
    border-bottom: 1px solid var(--surface-border);
    background: color-mix(in srgb, var(--surface-ground), transparent 30%);
}
.aed-freq-section__warn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    text-transform: none;
    letter-spacing: 0;
    font-size: 0.7rem;
    color: var(--p-amber-500, #f59e0b);
}
.aed-freq-section__body {
    padding: 0.85rem;
}

/* Separador de mês na lista de ocorrências — linha discreta com
   nome do mês centralizado. Aparece antes do primeiro item de cada mês. */
.aed-freq-month-sep {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    margin: 0.55rem 0 0.25rem;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-color-secondary);
}
.aed-freq-month-sep::before,
.aed-freq-month-sep::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--surface-border);
}
.aed-freq-month-sep:first-child {
    margin-top: 0;
}

/* "Em X dias/semanas/meses" do lado da data — escala discreta */
.aed-freq-rel {
    margin-left: auto;
    font-size: 0.7rem;
    color: var(--text-color-secondary);
    opacity: 0.85;
    font-style: italic;
}

/* Preview semanal/quinzenal dentro do card Tipo. */
.aed-freq-preview {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 0.75rem;
    margin-top: 0.65rem;
    border-radius: 8px;
    background: color-mix(in srgb, var(--p-primary-color) 6%, transparent);
    border: 1px dashed color-mix(in srgb, var(--p-primary-color) 40%, transparent);
    font-size: 0.82rem;
    color: var(--text-color);
}
.aed-freq-preview i {
    color: var(--p-primary-color);
    font-size: 0.85rem;
}

/* Hint educativo "Unidades vs Recorrência" no topo do dialog Serviços —
   2 linhas com icone + explicacao, fonte pequena. */
.aed-svc-hint {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    font-size: 0.9rem;
    line-height: 1.45;
}
.aed-svc-hint__row {
    display: flex;
    align-items: flex-start;
    gap: 0.55rem;
}
.aed-svc-hint__row > i {
    flex-shrink: 0;
    margin-top: 0.2rem;
    font-size: 0.95rem;
}

/* Header bonito do dialog Serviços/Convênio — icone redondo + titulo +
   subtitulo. Mesmo pattern usado no AgendaEventDialog "Nova {commitment}"
   e no MelissaPaciente dialog de Nova Sessao. */
.aed-svc-dlg-head {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    min-width: 0;
}
.aed-svc-dlg-head__icon {
    display: grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 999px;
    background: color-mix(in srgb, var(--p-primary-color) 14%, transparent);
    color: var(--p-primary-color);
    font-size: 1rem;
    flex-shrink: 0;
}
.aed-svc-dlg-head__text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.aed-svc-dlg-head__title {
    font-size: 1rem;
    font-weight: 700;
    color: var(--text-color);
    line-height: 1.2;
}
.aed-svc-dlg-head__sub {
    font-size: 0.78rem;
    color: var(--text-color-secondary);
    line-height: 1.3;
}

/* ── Commitment items (serviços vinculados ao evento) — legacy CSS,
   ainda usado pelos AgendaEventDialogV2/outras integrações ── */
.commitment-items-list {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    border: 1px solid var(--surface-border);
    border-radius: 6px;
    padding: 0.5rem;
}
.commitment-item-row {
    margin-bottom: 4px;
}
.commitment-item-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 4px;
}
.commitment-item-name {
    flex: 1;
    font-size: 0.85rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.commitment-item-controls {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: flex-end;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--p-content-border-color);
    margin-bottom: 8px;
}
.commitment-item-field {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.commitment-item-field--final {
    margin-left: auto;
}
.commitment-item-label {
    font-size: 0.65rem;
    color: var(--p-text-muted-color);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}
.commitment-item-price {
    font-size: 0.85rem;
    font-weight: 600;
    white-space: nowrap;
    min-width: 5rem;
    text-align: right;
}
.commitment-items-total {
    display: flex;
    justify-content: space-between;
    padding-top: 0.35rem;
    margin-top: 0.25rem;
    border-top: 1px solid var(--surface-border);
}

/* ── Google Calendar button ─────────────────────── */
.gcal-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.45rem 0.9rem;
    border-radius: 6px;
    border: 1px solid #4285f4;
    background: transparent;
    color: #4285f4;
    font-size: 0.85rem;
    font-weight: 500;
    text-decoration: none;
    transition:
        background 0.15s ease,
        color 0.15s ease;
    cursor: pointer;
    white-space: nowrap;
}
.gcal-btn:hover {
    background: #4285f4;
    color: #fff;
}
.gcal-btn__icon {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
}

/* ── Dialog "Ver lançamentos da sessão" (2026-05-14) ── */
.aed-records-list {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}
.aed-record {
    border: 1px solid var(--surface-border);
    border-radius: 8px;
    padding: 0.7rem 0.85rem;
    background: var(--surface-card);
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}
.aed-record--child {
    background: color-mix(in srgb, var(--p-primary-color) 4%, var(--surface-card));
    margin-left: 1.5rem;
    border-color: color-mix(in srgb, var(--p-primary-color) 25%, var(--surface-border));
}
.aed-record__head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}
.aed-record__indent {
    color: var(--text-color-secondary);
    font-size: 0.7rem;
    transform: scaleY(-1);
}
.aed-record__desc {
    font-size: 0.88rem;
    font-weight: 600;
    color: var(--text-color);
}
.aed-record__body {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}
.aed-record__row {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.78rem;
    color: var(--text-color-secondary);
}
.aed-record__row i {
    font-size: 0.72rem;
}
.aed-record__amount {
    font-weight: 600;
    color: var(--text-color);
}
</style>
