<!--
|--------------------------------------------------------------------------
| 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 } 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';
import RadioButton from 'primevue/radiobutton';
import Message from 'primevue/message';
import { useConfirm } from 'primevue/useconfirm';
import { useToast } from 'primevue/usetoast';
import { supabase } from '@/lib/supabase/client';
import ComponentCadastroRapido from '@/components/ComponentCadastroRapido.vue';
import AgendaEventoFinanceiroPanel from '@/components/agenda/AgendaEventoFinanceiroPanel.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';
import { getPatientAgendaPermissions } from '@/composables/usePatientLifecycle';

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

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: '' }
});

const emit = defineEmits(['update:modelValue', 'save', 'delete', 'updateSeriesEvent', 'editSeriesOccurrence', 'updated']);
const confirm = useConfirm();
const toast = useToast();
const router = useRouter();
const route = useRoute();

const visible = computed({
    get: () => props.modelValue,
    set: (v) => emit('update:modelValue', v)
});

const step = ref(1);
const isEdit = computed(() => !!props.eventRow?.id || !!props.eventRow?.is_occurrence);

const allowBack = computed(() => !props.lockCommitment && !props.presetCommitmentId);

// ── série ─────────────────────────────────────────────────
const hasSerie = computed(() => !!(props.eventRow?.recurrence_id || props.eventRow?.serie_id || props.eventRow?.is_occurrence));

const currentRecurrenceDate = computed(() => props.eventRow?.recurrence_date || props.eventRow?.inicio_em?.slice(0, 10) || null);

const editScope = ref('somente_este');

const isFirstOccurrence = computed(() => {
    if (!hasSerie.value) return false;
    const rDate = props.eventRow?.recurrence_date || props.eventRow?.original_date;
    if (!rDate) return false;
    if (serieEvents.value?.length) {
        const dates = serieEvents.value
            .map((e) => e.recurrence_date || e.original_date)
            .filter(Boolean)
            .sort();
        return dates[0] === rDate;
    }
    return false;
});

const editScopeOptions = computed(() => [
    { value: 'somente_este', label: 'Somente esta sessão' },
    { value: 'este_e_seguintes', label: 'Esta e as seguintes', disabled: isFirstOccurrence.value },
    { value: 'todos', label: 'Todas da série' },
    { value: 'todos_sem_excecao', label: 'Todas sem exceção' }
]);

// ── recorrência (criação / sessão avulsa) ──────────────────
// 'avulsa' | 'semanal' | 'quinzenal' | 'diasEspecificos'
const recorrenciaType = ref('avulsa');

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' }
];
const diasSelecionados = ref([]);

function toggleDiaSelecionado(dow) {
    const idx = diasSelecionados.value.indexOf(dow);
    if (idx === -1) diasSelecionados.value.push(dow);
    else diasSelecionados.value.splice(idx, 1);
}

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

// Seletor de quantidade
const qtdSessoesMode = ref('4');
const qtdSessoesCustom = ref(12);

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

const qtdSessoesEfetiva = computed(() => {
    if (qtdSessoesMode.value === '4') return 4;
    if (qtdSessoesMode.value === '8') return 8;
    if (qtdSessoesMode.value === '12') return 12;
    return Math.max(1, Number(qtdSessoesCustom.value || 1));
});

const diaSemanaRecorrencia = computed(() => {
    const d = form.value.dia ? new Date(form.value.dia) : new Date();
    return d.getDay();
});

const proximasOcorrencias = computed(() => {
    if (recorrenciaType.value === 'avulsa' || !form.value.dia) return [];
    const result = [];
    const total = qtdSessoesEfetiva.value;

    if (recorrenciaType.value === 'semanal' || recorrenciaType.value === 'quinzenal') {
        const step = recorrenciaType.value === 'quinzenal' ? 14 : 7;
        const cursor = new Date(form.value.dia);
        while (result.length < total) {
            result.push(new Date(cursor));
            cursor.setDate(cursor.getDate() + step);
        }
    } else if (recorrenciaType.value === 'diasEspecificos') {
        if (!diasSelecionados.value.length) return [];
        const sorted = [...diasSelecionados.value].sort((a, b) => a - b);
        const start = new Date(form.value.dia);
        const cur = new Date(start);
        let safety = 0;
        while (result.length < total && safety < 1000) {
            if (sorted.includes(cur.getDay()) && cur >= start) result.push(new Date(cur));
            cur.setDate(cur.getDate() + 1);
            safety++;
        }
    }
    return result;
});

const dataFimCalculada = computed(() => {
    const oc = proximasOcorrencias.value;
    return oc.length ? oc[oc.length - 1] : null;
});

const totalOcorrencias = computed(() => proximasOcorrencias.value.length);

const dataLimiteManual = ref(null);
function isForaDoPlano(d) {
    if (!dataLimiteManual.value) return false;
    return new Date(d) > new Date(dataLimiteManual.value);
}
const sessoesForaDoPlano = computed(() => proximasOcorrencias.value.filter((d) => isForaDoPlano(d)).length);

function _conflictForDate(date) {
    if (!date) return null;
    const dow = date.getDay();
    const iso = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;

    // 1. Folga
    if (props.workRules?.length && !props.workRules.some((r) => Number(r.dia_semana) === dow)) {
        return { type: 'folga', label: 'dia de folga' };
    }

    // 2. Feriado
    const feriado = (props.feriados || []).find((f) => {
        const fiso = f.date || f.data || f.iso || '';
        return String(fiso).slice(0, 10) === iso;
    });
    if (feriado) return { type: 'feriado', label: `feriado: ${feriado.name || feriado.nome || ''}` };

    // 3. Bloqueio manual (blockedDates já são ISO strings YYYY-MM-DD)
    if ((props.blockedDates || []).includes(iso)) {
        return { type: 'bloqueado', label: 'dia bloqueado' };
    }

    // 4. Pausa (checa se o horário da sessão sobrepõe alguma pausa do dia)
    if (form.value.startTime) {
        const [sh, sm] = form.value.startTime.split(':').map(Number);
        const slotS = sh * 60 + sm;
        const slotE = slotS + (form.value.duracaoMin || 50);
        const pausas = (props.pausasSemanais || []).filter((p) => p.dia_semana == null || Number(p.dia_semana) === dow);
        for (const p of pausas) {
            const [ph, pm] = String(p.hora_inicio || '00:00')
                .split(':')
                .map(Number);
            const [eh, em] = String(p.hora_fim || '00:00')
                .split(':')
                .map(Number);
            if (slotS < eh * 60 + em && slotE > ph * 60 + pm) {
                return { type: 'pausa', label: 'horário de pausa' };
            }
        }
    }

    return null;
}

const ocorrenciasComConflito = computed(() =>
    proximasOcorrencias.value.map((d) => ({
        date: d,
        conflict: _conflictForDate(d)
    }))
);

const totalConflitos = computed(() => ocorrenciasComConflito.value.filter((o) => o.conflict).length);

// ── commitments ────────────────────────────────────────────
const commitmentCards = computed(() => {
    const list = Array.isArray(props.commitmentOptions) ? props.commitmentOptions : [];
    const prio = new Map([['session', 0]]);
    return [...list].sort((a, b) => {
        const pa = prio.has(a.native_key) ? prio.get(a.native_key) : 99;
        const pb = prio.has(b.native_key) ? prio.get(b.native_key) : 99;
        if (pa !== pb) return pa - pb;
        return String(a.name || '').localeCompare(String(b.name || ''), 'pt-BR');
    });
});

function isNativeSession(c) {
    return String(c?.native_key || '').toLowerCase() === 'session';
}

const form = ref(resetForm());

// ── ConfirmDialog para status sensíveis (cancelado / remarcar) ────────────
const _prevStatus = ref(null);
const _skipStatusWatch = ref(false);
watch(
    () => form.value?.status,
    async (newVal, oldVal) => {
        if (_skipStatusWatch.value) return;
        if (!isEdit.value || !form.value?.id) return;
        if (newVal !== 'cancelado' && newVal !== 'remarcado') return;

        _prevStatus.value = oldVal;

        const isCancelar = newVal === 'cancelado';
        confirm.require({
            header: isCancelar ? 'Cancelar sessão' : 'Remarcar sessão',
            message: isCancelar ? 'Tem certeza que deseja cancelar esta sessão? O status será salvo imediatamente.' : 'Tem certeza que deseja marcar esta sessão para remarcação? O status será salvo imediatamente.',
            icon: isCancelar ? 'pi pi-times-circle' : 'pi pi-refresh',
            acceptLabel: 'Sim, confirmar',
            rejectLabel: 'Não',
            acceptSeverity: isCancelar ? 'danger' : 'warn',
            accept: async () => {
                try {
                    const { data, error } = await supabase.from('agenda_eventos').update({ status: newVal }).eq('id', form.value.id).select().single();
                    if (error) throw error;
                    toast.add({ severity: 'success', summary: 'Status atualizado', detail: `Sessão marcada como ${labelStatusSessao(newVal)}.`, life: 3000 });
                    emit('updated', data);
                } catch (e) {
                    toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || 'Não foi possível atualizar o status.', life: 4000 });
                    form.value.status = _prevStatus.value;
                }
            },
            reject: () => {
                form.value.status = _prevStatus.value;
            }
        });
    }
);

// ── 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);

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);
});
let _servicesLoaded = false;

async function ensureServicesLoaded() {
    if (_servicesLoaded || !props.ownerId) return;
    _servicesLoaded = true;
    await loadServices(props.ownerId);
}

function applyDefaultPrice() {
    // Pula quando pago: o preço vem dos commitmentItems, não de um default
    if (billingType.value === 'particular') return;
    // Só auto-preenche se price ainda não foi definido manualmente (ou é novo evento)
    if (!isEdit.value) {
        const suggested = getDefaultPrice();
        if (suggested != null) form.value.price = suggested;
    }
}

// ── Itens de serviço (commitment_services) ──────────────────────────
const commitmentItems = ref([]);
const servicePickerSel = ref(null);
const serieValorMode = ref('multiplicar'); // 'multiplicar' | 'dividir'

const billingType = ref('particular'); // 'gratuito' | 'particular' | 'convenio'
const _restoringConvenio = ref(false); // flag para ignorar watch durante restauração
const billingTypeOptions = [
    { label: 'Gratuito', value: 'gratuito' },
    { label: 'Particular', value: 'particular' },
    { label: 'Convênio', value: 'convenio' }
];

watch(billingType, (val) => {
    if (val === 'gratuito') {
        commitmentItems.value = [];
        form.value.price = 0;
        form.value.insurance_plan_id = null;
        form.value.insurance_guide_number = null;
        form.value.insurance_value = null;
        selectedPlanService.value = null;
    }
    if (val === 'particular') {
        form.value.insurance_plan_id = null;
        form.value.insurance_guide_number = null;
        form.value.insurance_value = null;
        selectedPlanService.value = null;
    }
    if (val === 'convenio') {
        commitmentItems.value = [];
        servicePickerSel.value = null;
    }
});

const isDynamic = computed(() => (props.agendaSettings?.slot_mode ?? 'fixed') === 'dynamic');

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;
});

function calcFinalPrice(unit_price, quantity, discount_pct, discount_flat) {
    const subtotal = Number(unit_price) * Number(quantity);
    const discPct = subtotal * (Number(discount_pct ?? 0) / 100);
    const discFlat = Number(discount_flat ?? 0);
    return Math.max(0, subtotal - discPct - discFlat);
}

async function addItem(svc) {
    if (!svc?.id) return;
    // Regra: não duplicar — incrementa quantity do item existente
    const existing = commitmentItems.value.find((i) => i.service_id === svc.id);
    if (existing) {
        existing.quantity++;
        existing.final_price = calcFinalPrice(existing.unit_price, existing.quantity, existing.discount_pct, existing.discount_flat);
        return;
    }
    const unit_price = Number(svc.price);
    const patientId = form.value.patient_id ?? form.value.paciente_id ?? null;
    let discount_pct = 0;
    let discount_flat = 0;

    if (patientId && props.ownerId) {
        const discount = await loadActiveDiscount(props.ownerId, patientId);
        if (discount) {
            discount_pct = Number(discount.discount_pct ?? 0);
            discount_flat = Number(discount.discount_flat ?? 0);
        }
    }

    commitmentItems.value.push({
        service_id: svc.id,
        service_name: svc.name,
        quantity: 1,
        unit_price,
        discount_pct,
        discount_flat,
        final_price: calcFinalPrice(unit_price, 1, discount_pct, discount_flat)
    });
}

function removeItem(index) {
    commitmentItems.value.splice(index, 1);
    // Quando lista esvazia em modo dynamic, restaura duração padrão
    if (commitmentItems.value.length === 0 && isDynamic.value) {
        form.value.duracaoMin = props.agendaSettings?.session_duration_min ?? 50;
    }
}

function onItemChange(item) {
    item.final_price = calcFinalPrice(item.unit_price, item.quantity, item.discount_pct, item.discount_flat);
}

async function _loadCommitmentItemsForEvent(eventId) {
    const ruleId = props.eventRow?.recurrence_id ?? null;
    const isCustomized = props.eventRow?.services_customized ?? false;
    // Lê os dados de convênio diretamente do eventRow (form.value pode ter sido limpo por watches)
    const origPlanId = props.eventRow?.insurance_plan_id ?? null;
    const origGuide = props.eventRow?.insurance_guide_number ?? null;
    const origInsValue = props.eventRow?.insurance_value != null ? Number(props.eventRow.insurance_value) : null;

    function applyConvenio() {
        const origPsId = props.eventRow?.insurance_plan_service_id ?? null;
        _restoringConvenio.value = true;
        form.value.insurance_plan_id = origPlanId;
        form.value.insurance_guide_number = origGuide;
        form.value.insurance_value = origInsValue;
        form.value.insurance_plan_service_id = origPsId;
        billingType.value = 'convenio';
        nextTick(() => {
            if (origPsId && planServices.value.find((s) => s.id === origPsId)) {
                selectedPlanService.value = origPsId;
            } else {
                selectedPlanService.value = null;
            }
            _restoringConvenio.value = false;
        });
    }

    if (!eventId && !ruleId) {
        commitmentItems.value = [];
        if (origPlanId) applyConvenio();
        else billingType.value = 'particular';
        return;
    }
    try {
        commitmentItems.value = ruleId ? await _csLoadItemsOrTemplate(eventId, ruleId, { allowEmpty: isCustomized }) : await _csLoadItems(eventId);
        if (origPlanId) applyConvenio();
        else billingType.value = commitmentItems.value.length > 0 ? 'particular' : 'gratuito';
    } catch (e) {
        console.warn('[AgendaEventDialog] commitment_services load error:', e?.message);
        commitmentItems.value = [];
        if (origPlanId) applyConvenio();
        else billingType.value = 'gratuito';
    }
}

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

const selectedCommitment = computed(() => {
    const id = form.value.commitment_id;
    if (!id) return null;
    return commitmentCards.value.find((x) => x.id === id) || null;
});
const selectedCommitmentName = computed(() => selectedCommitment.value?.name || '—');
const selectedCommitmentFields = computed(() => {
    const fields = selectedCommitment.value?.fields;
    return Array.isArray(fields) ? fields : [];
});

const requiresPatient = computed(() => isNativeSession(selectedCommitment.value));
const isSessionEvent = computed(() => requiresPatient.value);
// Bloqueia troca de paciente quando editando sessão que já tinha paciente vinculado
const patientLocked = computed(() => isEdit.value && isSessionEvent.value && !!props.eventRow?.paciente_id);
const hasInsurance = computed(() => !!form.value.insurance_plan_id);

// ── jornada ────────────────────────────────────────────────
function _fmtH(hhmm) {
    const [h, m] = String(hhmm || '00:00')
        .slice(0, 5)
        .split(':')
        .map(Number);
    return m > 0 ? `${h}h${String(m).padStart(2, '0')}` : `${h}h`;
}

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 ${_fmtH(inicio)} às ${_fmtH(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);
const samePatientConflict = ref(null);

const modalidadeOptions = [
    { label: 'Presencial', value: 'presencial' },
    { label: 'Online', value: 'online' }
];

// ── novo paciente ──────────────────────────────────────────
const cadRapidoOpen = ref(false);

function abrirCadastroCompleto() {
    const route = props.newPatientRoute || '/therapist/patients/cadastro';
    window.open(route, '_blank', 'noopener');
}

function onPatientCreatedRapido(p) {
    // auto-seleciona o paciente recém-criado
    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;
}

// ── paciente picker ────────────────────────────────────────
const pacientePickerOpen = ref(false);
const pacienteSearch = ref('');
const pacientesLoading = ref(false);
const pacientesError = ref('');
const patients = ref([]);

const filteredPatients = computed(() => {
    const q = String(pacienteSearch.value || '')
        .trim()
        .toLowerCase();
    // Somente pacientes Ativos podem ser selecionados para novos agendamentos
    const list = (patients.value || []).filter((p) => p.status === 'Ativo');
    if (!q) return list;
    return 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);
    });
});

function selectCommitment(c) {
    if (!c?.id) return;
    form.value.commitment_id = c.id;
    form.value.extra_fields = {};
    if (Array.isArray(c.fields)) {
        for (const f of c.fields) form.value.extra_fields[f.key] = '';
    }
    step.value = 2;
    if (requiresPatient.value) loadPatients(true);
}

function goBack() {
    if (isEdit.value || !allowBack.value) return;
    step.value = 1;
    form.value.commitment_id = null;
    form.value.paciente_id = null;
    form.value.paciente_nome = '';
}

function openPacientePicker() {
    if (!requiresPatient.value) return;
    pacientePickerOpen.value = true;
    loadPatients(false);
}

function clearPatientsCache() {
    patients.value = [];
    pacientesError.value = '';
    pacienteSearch.value = '';
}

async function loadPatients(force = false) {
    try {
        if (pacientesLoading.value) return;
        if (!force && patients.value?.length) return;
        pacientesError.value = '';
        pacientesLoading.value = true;

        let q = supabase.from('patients').select('id,nome_completo,email_principal,telefone,status,avatar_url,tenant_id,responsible_member_id,created_at').order('created_at', { ascending: false }).limit(500);

        if (props.tenantId) q = q.eq('tenant_id', props.tenantId);
        if (props.restrictPatientsToOwner && props.patientScopeOwnerId) {
            q = q.eq('responsible_member_id', props.patientScopeOwnerId);
        }

        const { data, error } = await q;
        if (error) throw error;

        patients.value = (data || []).map((r) => ({
            id: r.id,
            nome: r.nome_completo ?? '',
            email: r.email_principal ?? '',
            telefone: r.telefone ?? '',
            status: r.status ?? '',
            avatar_url: r.avatar_url ?? ''
        }));
    } catch (e) {
        pacientesError.value = e?.message || 'Falha ao carregar pacientes.';
        patients.value = [];
    } finally {
        pacientesLoading.value = false;
    }
}

function selectPaciente(p) {
    if (!p?.id) return;
    form.value.paciente_id = p.id;
    form.value.paciente_nome = p.nome || '';
    form.value.paciente_avatar = p.avatar_url || '';
    pacientePickerOpen.value = false;
}
function clearPaciente() {
    form.value.paciente_id = null;
    form.value.paciente_nome = '';
    form.value.paciente_avatar = '';
    samePatientConflict.value = null;
}

// ── quando abre ────────────────────────────────────────────
watch(
    () => props.modelValue,
    async (open) => {
        if (!open) return;
        await nextTick();
        console.log('[AgendaEventDialog] abriu — eventRow:', JSON.parse(JSON.stringify(props.eventRow || {})));
        console.log('[AgendaEventDialog] isEdit:', isEdit.value, 'hasSerie:', hasSerie.value);

        _skipStatusWatch.value = true;
        form.value = resetForm();
        await nextTick();
        _skipStatusWatch.value = false;
        samePatientConflict.value = null;
        recorrenciaType.value = 'avulsa';
        diasSelecionados.value = [];
        dataLimiteManual.value = null;
        qtdSessoesMode.value = '4';
        qtdSessoesCustom.value = 12;
        editScope.value = 'somente_este';
        serieValorMode.value = 'multiplicar';

        if (isEdit.value && form.value.paciente_id && !form.value.paciente_nome) {
            supabase
                .from('patients')
                .select('id, nome_completo')
                .eq('id', form.value.paciente_id)
                .maybeSingle()
                .then(({ data }) => {
                    if (data?.nome_completo) form.value.paciente_nome = data.nome_completo;
                });
        }

        if (hasSerie.value) loadSerieEvents();
        else serieEvents.value = [];

        if (isEdit.value) {
            step.value = 2;
        } else {
            const preset = props.presetCommitmentId;
            if (preset) {
                form.value.commitment_id = preset;
                step.value = 2;
            } else step.value = 1;
        }

        clearPatientsCache();
        if (requiresPatient.value) loadPatients(true);

        // Pré-carrega serviços para auto-fill de preço
        ensureServicesLoaded();
        const insuranceOwner = props.planOwnerId || props.ownerId;
        if (insuranceOwner) {
            await loadInsurancePlans(insuranceOwner);
            // planServices é computed — atualiza automaticamente após insurancePlans carregar
        }

        // Reset convênio (será restaurado por _loadCommitmentItemsForEvent se necessário)
        selectedPlanService.value = null;
        _restoringConvenio.value = false;

        // Reset e carrega itens de serviço do evento (commitment_services)
        commitmentItems.value = [];
        servicePickerSel.value = null;
        // Carrega serviços para eventos reais (form.value.id) ou template para ocorrências virtuais (só recurrence_id)
        // _loadCommitmentItemsForEvent determina o billingType correto (incluindo 'convenio')
        if (isEdit.value && (form.value.id || props.eventRow?.recurrence_id)) {
            _loadCommitmentItemsForEvent(form.value.id);
        } else {
            billingType.value = 'particular';
        }
    }
);

watch(
    () => [props.tenantId, props.restrictPatientsToOwner, props.patientScopeOwnerId],
    () => {
        if (!visible.value) return;
        clearPatientsCache();
        if (requiresPatient.value) loadPatients(true);
    }
);

// Auto-fill price when commitment changes (novo evento apenas)
watch(
    () => form.value.commitment_id,
    async (newId) => {
        if (!newId || isEdit.value || !visible.value) return;
        await ensureServicesLoaded();
        applyDefaultPrice();
    }
);

watch(
    () => form.value.insurance_plan_id,
    (planId) => {
        if (_restoringConvenio.value) return;
        selectedPlanService.value = null;
        form.value.insurance_plan_service_id = null;
        if (!planId) {
            // Limpa campos de convênio ao desmarcar
            form.value.insurance_value = null;
            form.value.insurance_guide_number = null;
            return;
        }
        // Ao selecionar convênio: exclusividade — remove serviços do caminho A
        commitmentItems.value = [];
        servicePickerSel.value = null;
    }
);

function onProcedureSelect(psId) {
    form.value.insurance_plan_service_id = psId ?? null;
    if (!psId) {
        form.value.insurance_value = null;
        return;
    }
    const ps = planServices.value.find((s) => s.id === psId);
    form.value.insurance_value = ps?.value != null ? Number(ps.value) : null;
}

watch(
    () => [form.value.paciente_id, form.value.dia?.toString()],
    async () => {
        const pid = form.value.paciente_id;
        samePatientConflict.value = null;
        if (!pid || !isSessionEvent.value || !visible.value) return;

        const d = form.value.dia ? new Date(form.value.dia) : new Date();
        const dayStart = new Date(d.getFullYear(), d.getMonth(), d.getDate()).toISOString();
        const dayEnd = new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1).toISOString();

        let q = supabase.from('agenda_eventos').select('id, inicio_em, fim_em, titulo').eq('patient_id', pid).gte('inicio_em', dayStart).lt('inicio_em', dayEnd).limit(1);

        if (form.value.id) q = q.neq('id', form.value.id);
        const { data } = await q.maybeSingle();
        samePatientConflict.value = data || null;
    }
);

// ── Check solicitações pendentes no horário ───────────────────
const solicitacaoPendente = ref(null);

watch(
    () => [form.value.dia?.toString(), form.value.startTime],
    async ([dia, startTime]) => {
        solicitacaoPendente.value = null;
        if (!isSessionEvent.value || !visible.value || isEdit.value) return;
        if (!props.ownerId || !dia || !startTime) return;

        const d = new Date(form.value.dia);
        const isoDate = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
        const { data } = await supabase
            .from('agendador_solicitacoes')
            .select('id, paciente_nome, paciente_sobrenome, paciente_email')
            .eq('owner_id', props.ownerId)
            .eq('status', 'pendente')
            .eq('data_solicitada', isoDate)
            .eq('hora_solicitada', startTime)
            .maybeSingle();
        solicitacaoPendente.value = data || null;
    }
);

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

function goToConveniosConfig() {
    visible.value = false;
    const prefix = route.path.startsWith('/admin') ? '/admin' : '/therapist';
    router.push(`${prefix}/configuracoes/convenios`);
}

// ── duração / slots ────────────────────────────────────────
// 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;
});

// ── time slot helpers ──────────────────────────────────────────
function hhmmToMin(hhmm) {
    const [h, m] = String(hhmm || '00:00')
        .slice(0, 5)
        .split(':')
        .map(Number);
    return (h || 0) * 60 + (m || 0);
}
function minToHHMM(min) {
    const h = Math.floor(min / 60) % 24;
    const m = min % 60;
    return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
}

// 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) }));
});

function selectSlot(hhmm) {
    // monta um Date com a hora selecionada e atualiza startTimeDate
    const [h, m] = hhmm.split(':').map(Number);
    const d = new Date();
    d.setHours(h, m, 0, 0);
    startTimeDate.value = d;
}

// ── online slots ──────────────────────────────────────────────
const onlineSlots = ref([]);
const loadingOnlineSlots = ref(false);

watch(
    [() => form.value.dia, () => form.value.modalidade],
    async ([dia, mod]) => {
        if (mod !== 'online' || !dia || !props.ownerId) {
            onlineSlots.value = [];
            return;
        }
        const dow = new Date(dia).getDay();
        loadingOnlineSlots.value = true;
        try {
            const { data } = await supabase.from('agenda_online_slots').select('time').eq('owner_id', props.ownerId).eq('weekday', dow).eq('enabled', true).order('time');
            onlineSlots.value = (data || []).map((s) => ({ hhmm: String(s.time || '').slice(0, 5) }));
        } catch {
            onlineSlots.value = [];
        } finally {
            loadingOnlineSlots.value = false;
        }
    },
    { immediate: true }
);

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 ────────────────────────────────
const serieEvents = ref([]);
const serieLoading = ref(false);
const pillDeleteMenuRef = ref(null);
const pillDeleteTarget = ref(null);

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

const serieCountByStatus = computed(() => {
    const counts = {};
    for (const ev of serieEvents.value) {
        const s = ev._status || 'agendado';
        counts[s] = (counts[s] || 0) + 1;
    }
    return counts;
});

const pillDeleteMenuItems = computed(() => {
    if (!pillDeleteTarget.value) return [];
    const ev = pillDeleteTarget.value;
    return [
        { label: 'Remover apenas esta', icon: 'pi pi-minus-circle', command: () => onPillDelete(ev, 'somente_este') },
        { label: 'Remover esta e as seguintes', icon: 'pi pi-forward', command: () => onPillDelete(ev, 'este_e_seguintes') },
        { separator: true },
        { label: 'Remover todas as futuras', icon: 'pi pi-trash', command: () => onPillDelete(ev, 'todos') }
    ];
});

function isPast(iso) {
    return iso ? new Date(iso) < new Date() : false;
}

// ── Permissões de agenda por status do paciente ───────────────────────────
const agendaPerms = computed(() => getPatientAgendaPermissions(form.value.paciente_status || ''));

// Sessão atual é futura? (para edição: usa inicio_em do evento original)
const isSessionFuture = computed(() => {
    if (!isEdit.value) return true;
    const iso = props.eventRow?.inicio_em;
    return iso ? new Date(iso) > new Date() : true;
});

// Arquivado editando sessão passada → somente leitura
const isArchivedPastEdit = computed(() => isEdit.value && form.value.paciente_status === 'Arquivado' && !isSessionFuture.value);

// Inativo editando sessão futura → remarcar bloqueado
const isInativoFutureEdit = computed(() => isEdit.value && form.value.paciente_status === 'Inativo' && isSessionFuture.value);

// StatusOptions com remarcar desabilitado para Inativo
const statusOptionsFiltered = computed(() => [
    { label: 'Agendado', value: 'agendado' },
    { label: 'Realizado', value: 'realizado' },
    { label: 'Faltou', value: 'faltou' },
    { label: 'Cancelado', value: 'cancelado' },
    { label: 'Remarcar', value: 'remarcado', disabled: isInativoFutureEdit.value }
]);
function fmtWeekdayShort(iso) {
    return new Date(iso).toLocaleDateString('pt-BR', { weekday: 'short' }).replace('.', '').slice(0, 3);
}
function fmtDayNum(iso) {
    return new Date(iso).getDate();
}
function fmtMonthShort(iso) {
    return new Date(iso).toLocaleDateString('pt-BR', { month: 'short' }).replace('.', '');
}

// Gera datas de ocorrência a partir de uma regra de recorrência
function generateRuleDates(rule) {
    const { type, interval = 1, weekdays = [], start_date, end_date, max_occurrences } = rule;
    if (!start_date || !weekdays?.length) return [];
    const maxOcc = Math.min(max_occurrences || 365, 365);
    const endLimit = end_date ? new Date(end_date + 'T23:59:59') : null;
    const dates = [];

    if (type === 'custom_weekdays') {
        const cursor = new Date(start_date + 'T12:00:00');
        let safety = 0;
        while (dates.length < maxOcc && safety < 2000) {
            safety++;
            if (endLimit && cursor > endLimit) break;
            if (weekdays.includes(cursor.getDay())) dates.push(cursor.toISOString().slice(0, 10));
            cursor.setDate(cursor.getDate() + 1);
        }
    } else {
        // weekly (interval=1) ou quinzenal (interval=2)
        const cursor = new Date(start_date + 'T12:00:00');
        while (dates.length < maxOcc) {
            if (endLimit && cursor > endLimit) break;
            dates.push(cursor.toISOString().slice(0, 10));
            cursor.setDate(cursor.getDate() + 7 * (interval || 1));
        }
    }
    return dates;
}

async function loadSerieEvents() {
    const rid = props.eventRow?.recurrence_id ?? props.eventRow?.serie_id ?? null;
    if (!rid) {
        serieEvents.value = [];
        return;
    }
    serieLoading.value = true;
    try {
        // 1. Regra de recorrência
        const { data: rule, error: ruleErr } = await supabase.from('recurrence_rules').select('*').eq('id', rid).maybeSingle();
        if (ruleErr) throw ruleErr;

        // 2. Exceções (canceladas, feriados, etc.)
        const { data: excData } = await supabase.from('recurrence_exceptions').select('original_date, type, reason').eq('recurrence_id', rid);
        const exMap = new Map((excData || []).map((e) => [e.original_date, e]));

        // 3. Eventos reais já materializados em agenda_eventos
        const { data: realData } = await supabase.from('agenda_eventos').select('id, inicio_em, fim_em, status, recurrence_date').eq('recurrence_id', rid).is('mirror_of_event_id', null).order('inicio_em', { ascending: true });
        const realMap = new Map((realData || []).map((e) => [e.recurrence_date || e.inicio_em?.slice(0, 10), e]));

        // 4. Gera as datas a partir da regra
        const dates = rule ? generateRuleDates(rule) : [];
        const startTime = rule?.start_time || '00:00:00';
        const durMin = rule?.duration_min || 50;

        // 5. Monta a lista
        const list = dates.map((dateISO) => {
            const real = realMap.get(dateISO);
            const exc = exMap.get(dateISO);
            const isCancelled = exc?.type === 'cancel_session' || exc?.type === 'holiday_block';
            const [sh, sm] = String(startTime).slice(0, 5).split(':').map(Number);
            const inicioStr = real?.inicio_em || `${dateISO}T${startTime}`;
            const fimDate = new Date(`${dateISO}T${startTime}`);
            fimDate.setMinutes(fimDate.getMinutes() + durMin);
            const fimStr = real?.fim_em || fimDate.toISOString();
            return {
                id: real?.id || null,
                inicio_em: inicioStr,
                fim_em: fimStr,
                status: real?.status || (isCancelled ? 'cancelado' : 'agendado'),
                recurrence_date: dateISO,
                _status: real?.status || (isCancelled ? 'cancelado' : 'agendado'),
                _is_virtual: !real?.id,
                _cancelled: isCancelled,
                _reason: exc?.reason || null
            };
        });

        // Adiciona reais que não estão na lista gerada (edge cases)
        for (const [dateISO, real] of realMap) {
            if (!dates.includes(dateISO)) {
                list.push({
                    id: real.id,
                    inicio_em: real.inicio_em,
                    fim_em: real.fim_em,
                    status: real.status || 'agendado',
                    recurrence_date: dateISO,
                    _status: real.status || 'agendado',
                    _is_virtual: false,
                    _cancelled: false,
                    _reason: null
                });
            }
        }

        list.sort((a, b) => new Date(a.inicio_em) - new Date(b.inicio_em));
        serieEvents.value = list;
    } catch (e) {
        console.error('[serie] erro ao carregar:', e);
        serieEvents.value = [];
    } finally {
        serieLoading.value = false;
    }
}

function onPillEditClick(ev) {
    emit('editSeriesOccurrence', {
        id: ev.id,
        recurrence_date: ev.recurrence_date,
        inicio_em: ev.inicio_em,
        fim_em: ev.fim_em,
        is_virtual: ev._is_virtual
    });
}

function onPillStatusChange(ev) {
    emit('updateSeriesEvent', {
        id: ev.id,
        status: ev._status,
        recurrence_date: ev.recurrence_date,
        inicio_em: ev.inicio_em,
        fim_em: ev.fim_em,
        is_virtual: ev._is_virtual
    });
    // Se era virtual, recarrega a lista após a página materializar o evento
    // (necessário para obter o id real e evitar duplo INSERT)
    if (ev._is_virtual) {
        setTimeout(() => loadSerieEvents(), 700);
    }
}

function onPillDeleteClick(ev, event) {
    pillDeleteTarget.value = ev;
    nextTick(() => pillDeleteMenuRef.value?.toggle(event));
}

function onPillDelete(ev, mode) {
    const isTodos = mode === 'todos';
    confirm.require({
        header: isTodos ? 'Encerrar toda a série' : 'Excluir sessão recorrente',
        message: isTodos
            ? 'Todos os agendamentos futuros da série serão removidos permanentemente. Esta sessão será mantida como avulsa. Esta ação é irreversível.'
            : mode === 'este_e_seguintes'
              ? 'Esta sessão e todas as seguintes serão removidas. Tem certeza?'
              : 'Esta sessão será cancelada. Tem certeza?',
        icon: isTodos ? 'pi pi-trash' : 'pi pi-exclamation-triangle',
        acceptClass: 'p-button-danger',
        acceptLabel: isTodos ? 'Sim, encerrar série' : 'Confirmar',
        rejectLabel: 'Cancelar',
        accept: () =>
            emit('delete', {
                id: ev.id,
                editMode: mode,
                recurrence_id: props.eventRow?.recurrence_id ?? props.eventRow?.serie_id ?? null,
                original_date: ev.recurrence_date || (ev.inicio_em ? ev.inicio_em.slice(0, 10) : null),
                serie_id: props.eventRow?.serie_id ?? null
            })
    });
}

// Aviso de conflito de horário (não bloqueia, só informa)
const timeConflict = computed(() => {
    if (!form.value.dia || !form.value.startTime || !inicioDateTime.value) return null;
    const dur = form.value.duracaoMin || 50;
    const breakMin = props.agendaSettings?.session_break_min || 0;
    const slotS = inicioDateTime.value.getTime();
    const slotE = slotS + dur * 60000;

    const d = new Date(form.value.dia);
    const dayISO = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;

    const currentRow = props.eventRow;
    const dayEvts = (props.allEvents || []).filter((e) => {
        if (!e.inicio_em) return false;
        // Exclui evento real pelo id
        if (form.value.id && e.id === form.value.id) return false;
        // Exclui ocorrência virtual pelo recurrence_id + original_date
        if (currentRow?.is_occurrence && e.is_occurrence && e.recurrence_id && e.recurrence_id === currentRow.recurrence_id && String(e.original_date || '').slice(0, 10) === String(currentRow.original_date || '').slice(0, 10)) return false;
        const es = new Date(e.inicio_em);
        return `${es.getFullYear()}-${String(es.getMonth() + 1).padStart(2, '0')}-${String(es.getDate()).padStart(2, '0')}` === dayISO;
    });

    for (const evt of dayEvts) {
        const evtS = new Date(evt.inicio_em).getTime();
        const evtE = new Date(evt.fim_em || evt.inicio_em).getTime() + breakMin * 60000;
        if (slotS < evtE && slotE > evtS) {
            const nome = evt.paciente_nome || 'outro compromisso';
            return `Conflito com "${nome}" às ${fmtTime(new Date(evt.inicio_em))}`;
        }
    }

    // Pausas do dia
    const dow = d.getDay();
    const pausas = (props.pausasSemanais || []).filter((p) => p.hora_inicio && p.hora_fim && (p.dia_semana == null || Number(p.dia_semana) === dow));
    if (pausas.length && form.value.startTime) {
        const [sh, sm] = form.value.startTime.split(':').map(Number);
        const sS = sh * 60 + sm;
        const sE = sS + dur;
        for (const p of pausas) {
            const [ph, pm] = String(p.hora_inicio).split(':').map(Number);
            const [eh, em] = String(p.hora_fim).split(':').map(Number);
            const pS = ph * 60 + pm;
            const pE = eh * 60 + em;
            if (sS < pE && sE > pS) return `Horário coincide com uma pausa (${p.hora_inicio}–${p.hora_fim})`;
        }
    }

    return null;
});

const startTimeDate = computed({
    get() {
        const t = form.value.startTime;
        if (!t) return null;
        const [h, m] = String(t).split(':').map(Number);
        const d = new Date();
        d.setHours(h, m, 0, 0);
        return d;
    },
    set(v) {
        if (!v) {
            form.value.startTime = null;
            return;
        }
        form.value.startTime = `${String(v.getHours()).padStart(2, '0')}:${String(v.getMinutes()).padStart(2, '0')}`;
    }
});

const inicioDateTime = computed(() => {
    if (!form.value.dia || !form.value.startTime) return null;
    const d = new Date(form.value.dia);
    const [hh, mm] = String(form.value.startTime).split(':').map(Number);
    d.setHours(hh, mm, 0, 0);
    return d;
});
const fimDateTime = computed(() => {
    if (!inicioDateTime.value) return null;
    return addMinutesDate(inicioDateTime.value, Number(form.value.duracaoMin || 50));
});
const dataHoraDisplay = computed(() => {
    const parts = [];
    if (form.value.dia) parts.push(fmtDateBR(form.value.dia));
    if (form.value.startTime) parts.push(form.value.startTime);
    return parts.join(' • ');
});
const previewRange = computed(() => {
    if (!inicioDateTime.value || !fimDateTime.value) return '—';
    return `${fmtDateBR(inicioDateTime.value)} • ${fmtTime(inicioDateTime.value)} → ${fmtTime(fimDateTime.value)}`;
});

const computedTitulo = computed(() => {
    const forced = String(form.value.titulo_custom || '').trim();
    if (forced) return forced;
    const comp = selectedCommitmentName.value || 'Compromisso';
    if (requiresPatient.value) {
        const nome = String(form.value.paciente_nome || '').trim();
        return nome ? `${nome} [${comp}]` : comp;
    }
    return comp;
});

const canSave = computed(() => {
    if (!form.value.owner_id) return false;
    if (!form.value.dia) return false;
    if (!form.value.startTime) return false;
    // commitment_id é obrigatório só na criação (define o tipo do evento).
    // Em edição, sessões antigas podem ter determined_commitment_id=null
    // no DB — relaxar aqui evita travar o Salvar quando o usuário só
    // quer mudar horário/observação de uma sessão pré-existente.
    if (!isEdit.value && !form.value.commitment_id) return false;
    if (requiresPatient.value && !form.value.paciente_id) return false;
    if (isSessionEvent.value && billingType.value === 'particular' && commitmentItems.value.length === 0) return false;

    // ── Restrições por status do paciente ────────────────────
    if (isSessionEvent.value && form.value.paciente_status) {
        const perms = agendaPerms.value;
        // Criar sessão avulsa ou com recorrência: bloqueado para Inativo/Arquivado
        if (!isEdit.value && !perms.canCreateSession) return false;
        // Criar recorrência: bloqueado para Inativo/Arquivado
        if (!isEdit.value && recorrenciaType.value !== 'avulsa' && !perms.canCreateRecurrence) return false;
        // Arquivado tentando salvar sessão passada: bloqueado
        if (isArchivedPastEdit.value) return false;
    }

    return true;
});

const headerTitle = computed(() => {
    if (isEdit.value) return 'Editar compromisso';
    return step.value === 1 ? 'Novo compromisso — escolha o tipo' : 'Novo compromisso';
});

// ── save / delete ──────────────────────────────────────────
const EVENTO_TIPO = Object.freeze({ SESSAO: 'sessao' });

function onSave() {
    if (!canSave.value) return;

    // Pre-check client-side de conflito de horário. O banco também rejeita
    // via EXCLUDE constraint (agenda_eventos_sem_sobreposicao), o que vira
    // 400 no console. Bloquear aqui economiza round-trip e silencia o erro.
    if (timeConflict.value) {
        toast.add({
            severity: 'warn',
            summary: 'Conflito de horário',
            detail: `${timeConflict.value}. Ajuste o horário ou a duração.`,
            life: 4500
        });
        return;
    }

    const inicioISO = inicioDateTime.value?.toISOString() || null;
    const fimISO = fimDateTime.value?.toISOString() || null;

    const payload = {
        owner_id: form.value.owner_id,
        terapeuta_id: form.value.terapeuta_id,
        paciente_id: requiresPatient.value ? form.value.paciente_id : null,
        patient_id: requiresPatient.value ? form.value.paciente_id : null,
        tipo: EVENTO_TIPO.SESSAO,
        status: form.value.status || 'agendado',
        titulo: computedTitulo.value || null,
        modalidade: form.value.modalidade || null,
        observacoes: form.value.observacoes || null,
        inicio_em: inicioISO,
        fim_em: fimISO,
        determined_commitment_id: form.value.commitment_id || null,
        titulo_custom: form.value.titulo_custom || null,
        extra_fields: Object.keys(form.value.extra_fields || {}).length ? form.value.extra_fields : null,
        price: isSessionEvent.value ? (form.value.price ?? null) : null,
        insurance_plan_id: isSessionEvent.value ? (form.value.insurance_plan_id ?? null) : null,
        insurance_guide_number: isSessionEvent.value ? (form.value.insurance_guide_number ?? null) : null,
        insurance_value: isSessionEvent.value ? (form.value.insurance_value ?? null) : null,
        insurance_plan_service_id: isSessionEvent.value ? (form.value.insurance_plan_service_id ?? null) : null
    };

    // recorrência — só quando é sessão e não avulsa
    let recorrencia = null;
    if (isSessionEvent.value && recorrenciaType.value !== 'avulsa') {
        recorrencia = {
            tipo: 'recorrente',
            tipoFreq: recorrenciaType.value, // 'semanal' | 'quinzenal' | 'diasEspecificos'
            diaSemana: diaSemanaRecorrencia.value,
            diasSemana: diasSelecionados.value, // usado em diasEspecificos
            horaInicio: form.value.startTime ? `${form.value.startTime}:00` : null,
            duracaoMin: form.value.duracaoMin,
            dataFim: dataFimCalculada.value ? dataFimCalculada.value.toISOString() : null,
            qtdSessoes: qtdSessoesEfetiva.value,
            serieValorMode: serieValorMode.value,
            commitmentItems: commitmentItems.value.slice()
        };
        recorrencia.conflitos = ocorrenciasComConflito.value.filter((o) => o.conflict).map((o) => ({ date: o.date.toISOString().slice(0, 10), conflict: o.conflict }));
    }

    // escopo de edição — só quando edita série existente
    const emitEditMode = hasSerie.value ? editScope.value : null;
    const emitRecurrenceId = hasSerie.value ? (props.eventRow?.recurrence_id ?? props.eventRow?.serie_id ?? null) : null;
    const emitOriginalDate = hasSerie.value ? (props.eventRow?.original_date ?? null) : null;

    emit('save', {
        id: form.value.id,
        payload,
        recorrencia,
        editMode: emitEditMode,
        recurrence_id: emitRecurrenceId,
        original_date: emitOriginalDate,
        // legado — mantido para compatibilidade
        serie_id: props.eventRow?.serie_id ?? null,
        serviceItems: isSessionEvent.value ? commitmentItems.value.slice() : null,
        onSaved: isSessionEvent.value
            ? async (eventId, { markCustomized = false } = {}) => {
                  await saveCommitmentItems(eventId, commitmentItems.value, { markCustomized });
              }
            : null
    });
}

function onEncerrarSerie() {
    confirm.require({
        header: 'Encerrar toda a série',
        message: 'Todos os agendamentos da série serão removidos permanentemente, incluindo exceções e recorrências. Esta sessão será mantida como avulsa. Esta ação é irreversível.',
        icon: 'pi pi-trash',
        acceptClass: 'p-button-danger',
        acceptLabel: 'Sim, encerrar série',
        rejectLabel: 'Cancelar',
        accept: () =>
            emit('delete', {
                id: form.value.id,
                editMode: 'todos',
                recurrence_id: props.eventRow?.recurrence_id ?? props.eventRow?.serie_id ?? null,
                original_date: props.eventRow?.original_date ?? null,
                serie_id: props.eventRow?.serie_id ?? null
            })
    });
}

// ───── Lembrete manual WhatsApp (8.2) ─────
const sendingReminder = ref(false);
async function onSendManualReminder() {
    if (!form.value?.id) return;
    confirm.require({
        header: 'Enviar lembrete WhatsApp?',
        message: `Vou mandar o template "lembrete_sessao" pra ${form.value.paciente_nome || 'o paciente'} agora. Pode disparar?`,
        icon: 'pi pi-whatsapp',
        acceptLabel: 'Enviar',
        rejectLabel: 'Cancelar',
        accept: async () => {
            sendingReminder.value = true;
            try {
                const { data, error } = await supabase.functions.invoke('send-session-reminder-manual', {
                    body: { event_id: form.value.id }
                });
                if (error || !data?.ok) {
                    const err = data?.error || error?.message || 'unknown_error';
                    let friendly = err;
                    if (err === 'no_phone') friendly = 'Paciente sem telefone cadastrado.';
                    else if (err === 'invalid_phone') friendly = 'Telefone do paciente inválido.';
                    else if (err === 'no_active_channel') friendly = 'Nenhum canal WhatsApp ativo. Configure em Configurações → WhatsApp.';
                    else if (err === 'template_not_found') friendly = 'Template "lembrete_sessao" não encontrado. Configure em Configurações → WhatsApp.';
                    else if (err === 'forbidden') friendly = 'Você não tem permissão pra enviar por este canal.';
                    else if (String(err).startsWith('send_failed')) friendly = 'Não conseguimos enviar. Verifique a conexão do WhatsApp.';
                    throw new Error(friendly);
                }
                toast.add({ severity: 'success', summary: 'Lembrete enviado', detail: data.to ? `Para ${data.to}` : undefined, life: 3500 });
            } catch (e) {
                toast.add({ severity: 'error', summary: 'Erro ao enviar lembrete', detail: e.message, life: 5000 });
            } finally {
                sendingReminder.value = false;
            }
        }
    });
}

function onDelete() {
    if (!form.value.id) return;

    // se for evento de série, pede escopo
    if (hasSerie.value) {
        const isTodos = editScope.value === 'todos';
        confirm.require({
            header: isTodos ? 'Encerrar toda a série' : 'Excluir sessão recorrente',
            message: isTodos ? 'Todos os agendamentos futuros da série serão removidos permanentemente. Esta sessão será mantida como avulsa. Esta ação é irreversível.' : 'Esta sessão faz parte de uma série. O que deseja remover?',
            icon: isTodos ? 'pi pi-trash' : 'pi pi-exclamation-triangle',
            acceptClass: 'p-button-danger',
            acceptLabel: isTodos ? 'Sim, encerrar série' : editScopeOptions.value.find((o) => o.value === editScope.value)?.label || 'Excluir',
            rejectLabel: 'Cancelar',
            accept: () =>
                emit('delete', {
                    id: form.value.id,
                    editMode: editScope.value,
                    recurrence_id: props.eventRow?.recurrence_id ?? props.eventRow?.serie_id ?? null,
                    original_date: props.eventRow?.original_date ?? null,
                    serie_id: props.eventRow?.serie_id ?? null
                })
        });
        return;
    }

    confirm.require({
        header: 'Excluir compromisso',
        message: 'Tem certeza? Essa ação não pode ser desfeita.',
        icon: 'pi pi-exclamation-triangle',
        acceptClass: 'p-button-danger',
        acceptLabel: 'Excluir',
        rejectLabel: 'Cancelar',
        accept: () => emit('delete', form.value.id)
    });
}

// ── helpers ────────────────────────────────────────────────
function addMinutesDate(date, min) {
    const d = new Date(date);
    d.setMinutes(d.getMinutes() + Number(min || 0));
    return d;
}
function fmtDateBR(d) {
    const dt = d instanceof Date ? d : new Date(d);
    return dt.toLocaleDateString('pt-BR', { day: '2-digit', month: 'short', year: 'numeric' });
}
function fmtDateBRLong(d) {
    const dt = d instanceof Date ? d : new Date(d);
    return dt.toLocaleDateString('pt-BR', { weekday: 'short', day: '2-digit', month: 'short' });
}
function fmtTime(d) {
    if (!d) return '—';
    return new Date(d).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
}
function fmtDuracao(min) {
    const m = Number(min || 0);
    if (!m) return '—';
    const h = Math.floor(m / 60);
    const r = m % 60;
    if (h && r) return `${h}h ${r}min`;
    if (h) return `${h}h`;
    return `${r}min`;
}
function fmtSerieHora(hora) {
    if (!hora) return '—';
    return String(hora).slice(0, 5);
}
function nomeDiaSemana(dow) {
    const nomes = ['domingo', 'segunda', 'terça', 'quarta', 'quinta', 'sexta', 'sábado'];
    return nomes[Number(dow ?? 0)] ?? '—';
}
function isoToHHMM(iso) {
    if (!iso) return null;
    const s = String(iso);
    if (s.endsWith('Z') || /[+-]\d{2}:\d{2}$/.test(s)) {
        const d = new Date(s);
        return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
    }
    const match = s.match(/T(\d{2}):(\d{2})/);
    if (match) return `${match[1]}:${match[2]}`;
    const d = new Date(s);
    return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
}
function calcMinutes(a, b) {
    try {
        if (!a || !b) return null;
        const ms = new Date(b).getTime() - new Date(a).getTime();
        return Math.max(0, Math.round(ms / 60000));
    } catch {
        return null;
    }
}

function resetForm() {
    const r = props.eventRow;
    const startISO = r?.inicio_em || props.initialStartISO || '';
    const endISO = r?.fim_em || props.initialEndISO || '';
    const duracaoMin = calcMinutes(startISO, endISO) || props.agendaSettings?.session_duration_min || 50;

    return {
        id: r?.id || null,
        owner_id: r?.owner_id || props.ownerId || '',
        terapeuta_id: r?.terapeuta_id ?? null,
        paciente_id: r?.paciente_id ?? null,
        paciente_nome: r?.paciente_nome ?? r?.patient_name ?? '',
        paciente_avatar: r?.paciente_avatar ?? '',
        paciente_status: r?.paciente_status ?? '',
        commitment_id: r?.determined_commitment_id ?? null,
        titulo_custom: r?.titulo_custom || '',
        status: r?.status || 'agendado',
        observacoes: r?.observacoes || '',
        dia: startISO ? new Date(startISO) : new Date(),
        startTime: startISO ? isoToHHMM(startISO) : null,
        duracaoMin,
        modalidade: r?.modalidade || 'presencial',
        conflito: null,
        extra_fields: r?.extra_fields && typeof r.extra_fields === 'object' ? { ...r.extra_fields } : {},
        price: r?.price != null ? Number(r.price) : null,
        insurance_plan_id: r?.insurance_plan_id ?? null,
        insurance_guide_number: r?.insurance_guide_number ?? null,
        insurance_value: r?.insurance_value != null ? Number(r.insurance_value) : null,
        insurance_plan_service_id: r?.insurance_plan_service_id ?? null
    };
}

// ── 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
    });
});

function labelStatusSessao(v) {
    const map = { agendado: 'Agendado', realizado: 'Realizado', faltou: 'Faltou', cancelado: 'Cancelado', remarcado: 'Remarcado' };
    return map[v] || '—';
}
function statusSeverity(v) {
    if (v === 'agendado') return 'info';
    if (v === 'realizado') return 'success';
    if (v === 'faltou') return 'warn';
    if (v === 'cancelado') return 'danger';
    if (v === 'remarcado') return 'secondary'; // cor real via classe CSS
    return 'secondary';
}
function statusExtraClass(v) {
    return v === 'remarcado' ? 'tag-remarcado' : '';
}
</script>

<template>
    <Dialog v-model:visible="visible" modal :draggable="false" :dismissableMask="true" :style="{ width: '1000px', maxWidth: '96vw' }" :breakpoints="{ '960px': '96vw', '640px': '98vw' }" class="agenda-event-composer" pt:mask:class="backdrop-blur-xs">
        <template #header>
            <div class="w-full flex items-center justify-between gap-3">
                <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="font-semibold truncate text-base">{{ headerTitle }}</div>
                        <div v-if="step === 2" class="text-xs text-color-secondary truncate">{{ previewRange }}</div>
                    </div>
                </div>

                <!-- actions moved to footer -->
            </div>
        </template>

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

        <!-- ══════════════════════════════════════════════ -->
        <!-- STEP 1 — escolha o tipo                        -->
        <!-- ══════════════════════════════════════════════ -->
        <div v-if="step === 1" class="p-2">
            <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-grid">
                <button
                    v-for="c in commitmentCards"
                    :key="c.id"
                    class="commitment-card"
                    :class="{ 'commitment-card--blocked': isDayBlocked && isNativeSession(c) }"
                    :style="c.bg_color ? { '--card-color': `#${c.bg_color}`, boxShadow: `0 4px 20px #${c.bg_color}30` } : {}"
                    :disabled="isDayBlocked && isNativeSession(c)"
                    @click="isDayBlocked && isNativeSession(c) ? null : selectCommitment(c)"
                >
                    <div class="commitment-card__inner">
                        <div class="commitment-card__icon" :style="c.bg_color ? { background: `#${c.bg_color}20`, color: `#${c.bg_color}` } : {}">
                            <i :class="isNativeSession(c) ? 'pi pi-user' : 'pi pi-calendar'" />
                        </div>
                        <div class="min-w-0 flex-1">
                            <div class="font-semibold truncate">{{ c.name }}</div>
                            <div v-if="c.description" class="text-xs text-color-secondary line-clamp-2 mt-0.5">
                                {{ c.description }}
                            </div>
                            <div class="flex flex-wrap gap-1 mt-2">
                                <Tag v-if="isNativeSession(c)" value="Exige paciente" severity="info" />
                                <Tag v-if="isDayBlocked && isNativeSession(c)" value="Dia bloqueado" severity="danger" icon="pi pi-lock" />
                            </div>
                        </div>
                        <i class="pi pi-chevron-right text-color-secondary opacity-50 shrink-0 mt-1" />
                    </div>
                </button>
            </div>
        </div>

        <!-- ══════════════════════════════════════════════ -->
        <!-- STEP 2 — formulário + painel lateral          -->
        <!-- ══════════════════════════════════════════════ -->
        <div v-else class="composer-grid">
            <!-- ─── COLUNA ESQUERDA — campos ─── -->
            <div 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" :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 && 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 (destaque total) ──────────────────── -->
                <div v-if="isSessionEvent" class="patient-hero mb-4">
                    <div class="patient-hero__label">
                        <div class="flex items-center gap-1.5">
                            <i class="pi pi-user" />
                            <span>Paciente</span>
                        </div>
                        <div class="flex items-center gap-1 ml-auto">
                            <Button label="Cadastro Rápido" icon="pi pi-user-plus" size="small" severity="secondary" outlined class="rounded-full text-xs h-7" @click="cadRapidoOpen = true" />
                            <Button icon="pi pi-external-link" size="small" severity="secondary" text class="rounded-full h-7 w-7" v-tooltip.top="'Cadastro completo (nova aba)'" @click="abrirCadastroCompleto" />
                        </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">
                                <span class="text-xs text-color-secondary">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 class="flex gap-1 shrink-0">
                            <Button v-if="!patientLocked" icon="pi pi-pencil" severity="secondary" outlined size="small" class="rounded-full h-8 w-8" v-tooltip.top="'Trocar'" @click="openPacientePicker" />
                            <Button v-if="!patientLocked" icon="pi pi-times" severity="secondary" text size="small" class="rounded-full h-8 w-8" v-tooltip.top="'Limpar'" @click="clearPaciente" />
                            <span v-if="patientLocked" v-tooltip.top="'Paciente não pode ser alterado após criação'" class="flex items-center gap-1 text-xs text-color-secondary px-2"><i class="pi pi-lock" /></span>
                        </div>
                    </div>

                    <!-- Modalidade dentro do card de paciente -->
                    <div class="patient-hero__modalidade">
                        <span class="patient-hero__mod-label">Modalidade</span>
                        <SelectButton v-model="form.modalidade" :options="modalidadeOptions" optionLabel="label" optionValue="value" :allowEmpty="false" size="small" />
                    </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" />
                        <span>Data e Horário</span>
                        <div class="flex items-center gap-1 ml-auto">
                            <Button label="Ajustar horário" icon="pi pi-pencil" size="small" severity="secondary" outlined class="rounded-full text-xs h-7" @click="timePickerOpen = true" />
                        </div>
                    </div>
                    <div class="field-card__body">
                        <div class="time-hero" @click="timePickerOpen = true">
                            <div class="time-hero__block">
                                <span class="time-hero__label">Data</span>
                                <span class="time-hero__value">{{ form.dia ? fmtDateBR(form.dia) : '—' }}</span>
                            </div>
                            <div class="time-hero__sep" />
                            <div class="time-hero__block">
                                <span class="time-hero__label">Início</span>
                                <span class="time-hero__value">{{ form.startTime || '—' }}</span>
                            </div>
                            <div class="time-hero__sep" />
                            <div class="time-hero__block">
                                <span class="time-hero__label">Duração</span>
                                <span class="time-hero__value">{{ fmtDuracao(form.duracaoMin) }}</span>
                            </div>
                            <div class="time-hero__sep" />
                            <div class="time-hero__block">
                                <span class="time-hero__label">Término</span>
                                <span class="time-hero__value">{{ fimDateTime ? fmtTime(fimDateTime) : '—' }}</span>
                            </div>
                        </div>
                        <Message v-if="timeConflict" severity="warn" class="m-2" :closable="false">
                            <i class="pi pi-exclamation-triangle mr-1" />
                            {{ timeConflict }}
                        </Message>
                    </div>
                    <!-- /field-card__body -->
                </div>
                <!-- /field-card -->

                <!-- ── STATUS DA SESSÃO ───────────────────────────── -->
                <div v-if="isSessionEvent && isEdit" 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>

                <!-- ── 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>

                    <!-- Campos extras do compromisso -->
                    <template v-if="selectedCommitmentFields.length">
                        <div v-for="f in selectedCommitmentFields" :key="f.key">
                            <FloatLabel variant="on">
                                <Textarea v-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>
                    </template>

                    <!-- Observação (somente quando não é sessão — para sessões fica no card direito) -->
                    <div v-if="!isSessionEvent" class="col-span-full">
                        <FloatLabel variant="on">
                            <Textarea id="aed-observacoes" v-model="form.observacoes" class="w-full" variant="filled" rows="3" autoResize />
                            <label for="aed-observacoes">Observação</label>
                        </FloatLabel>
                    </div>
                </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>{{ serieEvents.length }} sessões</span>
                            <span v-if="serieCountByStatus.realizado"> · {{ serieCountByStatus.realizado }} realizadas</span>
                            <span v-if="serieCountByStatus.faltou"> · {{ serieCountByStatus.faltou }} faltaram</span>
                            <span v-if="serieCountByStatus.cancelado"> · {{ serieCountByStatus.cancelado }} canceladas</span>
                            <span v-if="serieCountByStatus.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 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) }]"
                        >
                            <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>
                            <Select v-model="ev._status" :options="statusOptions" optionLabel="label" optionValue="value" size="small" variant="filled" class="serie-pill__status-sel" @change="onPillStatusChange(ev)" />
                            <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>

            <!-- ─── COLUNA DIREITA — painel ─── -->
            <div class="composer-right">
                <!-- ── RESUMO ─────────────────────────────────────── -->
                <div class="side-card mb-3">
                    <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 class="summary-row">
                        <i class="pi pi-map-marker summary-icon" />
                        <span class="capitalize">{{ form.modalidade || '—' }}</span>
                    </div>
                    <div v-if="isSessionEvent" class="summary-row">
                        <i class="pi pi-wallet summary-icon" />
                        <span>{{ displayPrice != null ? fmtBRL(displayPrice) : '—' }}</span>
                    </div>
                </div>

                <!-- ── ESCOPO DE EDIÇÃO — só em modo edição de série ── -->
                <div v-if="isEdit && hasSerie" 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>Todos</b> ou <b>Todos sem exceção</b>. </span>
                    </Message>
                </div>

                <!-- ── FINANCEIRO (só sessão) ────────────────────────── -->
                <div v-if="isSessionEvent" class="side-card mb-3">
                    <!-- SelectButton: Gratuito / Particular / Convênio -->
                    <SelectButton v-model="billingType" :options="billingTypeOptions" optionLabel="label" optionValue="value" class="w-full mb-3" />

                    <!-- PARTICULAR: seletor de serviço -->
                    <template v-if="billingType === 'particular'">
                        <div v-if="services.filter((s) => s.active).length" class="flex gap-2 mb-2">
                            <Select
                                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;
                                    }
                                "
                            />
                        </div>
                    </template>

                    <!-- CONVÊNIO: fluxo progressivo -->
                    <template v-if="billingType === 'convenio'">
                        <!-- PASSO 1 — Select do plano (sempre visível) -->
                        <Select v-model="form.insurance_plan_id" :options="activePlans" optionLabel="name" optionValue="id" placeholder="Selecionar convênio..." showClear class="w-full mb-2" size="small" />

                        <!-- PASSO 2 — Procedimento (visível quando plano selecionado) -->
                        <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>
                            <div v-else class="flex items-center justify-between gap-2 mb-2 p-2 rounded-lg bg-surface-100">
                                <span class="text-xs text-color-secondary">Este convênio não tem procedimentos cadastrados.</span>
                                <Button label="Cadastrar" icon="pi pi-plus" size="small" severity="secondary" outlined class="rounded-full shrink-0 text-xs h-7" @click="goToConveniosConfig" />
                            </div>

                            <!-- PASSO 3 — Nº da Guia + Valor (visível após selecionar procedimento ou sem procedimentos) -->
                            <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</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>

                <!-- ── RECORRÊNCIA (só sessão) ───────────────────── -->
                <div v-if="isSessionEvent" class="side-card">
                    <div class="mb-3">
                        <!-- Lista de itens adicionados -->
                        <div v-if="commitmentItems.length" class="commitment-items-list mb-2">
                            <div v-for="(item, idx) in commitmentItems" :key="idx" class="commitment-item-row">
                                <!-- linha 1: nome + remover -->
                                <div class="commitment-item-header">
                                    <span class="commitment-item-name">{{ item.service_name }}</span>
                                    <Button icon="pi pi-times" size="small" severity="danger" text @click="removeItem(idx)" />
                                </div>

                                <!-- linha 2: qtd | preço unit (editável) | desconto % | desconto fixo | total -->
                                <div class="commitment-item-controls">
                                    <div class="commitment-item-field">
                                        <label class="commitment-item-label">Qtd</label>
                                        <InputNumber v-model="item.quantity" :min="1" :max="99" size="small" inputClass="w-12 text-center" @update:modelValue="onItemChange(item)" />
                                    </div>
                                    <div class="commitment-item-field">
                                        <label class="commitment-item-label">Preço unit.</label>
                                        <InputNumber v-model="item.unit_price" mode="currency" currency="BRL" locale="pt-BR" :min="0" :minFractionDigits="2" size="small" inputClass="w-24" @update:modelValue="onItemChange(item)" />
                                    </div>
                                    <div class="commitment-item-field">
                                        <label class="commitment-item-label">Desc %</label>
                                        <InputNumber v-model="item.discount_pct" :min="0" :max="100" suffix="%" size="small" inputClass="w-16 text-center" @update:modelValue="onItemChange(item)" />
                                    </div>
                                    <div class="commitment-item-field">
                                        <label class="commitment-item-label">Desc R$</label>
                                        <InputNumber v-model="item.discount_flat" mode="currency" currency="BRL" locale="pt-BR" :min="0" :minFractionDigits="2" size="small" inputClass="w-20" @update:modelValue="onItemChange(item)" />
                                    </div>
                                    <div class="commitment-item-field commitment-item-field--final">
                                        <label class="commitment-item-label">Total</label>
                                        <span class="commitment-item-price">{{ fmtBRL(item.final_price) }}</span>
                                    </div>
                                </div>
                            </div>

                            <div class="commitment-items-total">
                                <span class="text-sm text-color-secondary">Total da sessão</span>
                                <span class="font-semibold">{{ fmtBRL(totalFromItems) }}</span>
                            </div>
                        </div>
                    </div>

                    <!-- Observação -->
                    <div class="mb-3">
                        <FloatLabel variant="on">
                            <Textarea id="aed-observacoes-side" v-model="form.observacoes" class="w-full" variant="filled" rows="3" autoResize :disabled="isArchivedPastEdit" />
                            <label for="aed-observacoes-side">Observação</label>
                        </FloatLabel>
                    </div>

                    <!-- ── COBRANÇA DA SESSÃO ──────────────────────────── -->
                    <AgendaEventoFinanceiroPanel v-if="isSessionEvent && isEdit && eventRow?.id" :evento="eventRow" class="mb-3" />

                    <!-- Opção de recorrência para sessão SEM série (criação ou avulsa) -->
                    <template v-if="!hasSerie">
                        <div class="side-card__title mb-2">Frequência</div>
                        <Message v-if="isSessionEvent && 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>

                        <!-- Data de início (= form.dia) com botão Hoje -->
                        <div class="rec-startdate-row mb-3">
                            <div class="flex items-center gap-1.5 min-w-0">
                                <i class="pi pi-calendar text-primary-500 shrink-0" />
                                <span class="text-sm font-medium truncate">{{ form.dia ? fmtDateBR(form.dia) : 'Selecione a data' }}</span>
                            </div>
                            <Button label="Hoje" size="small" severity="secondary" outlined class="rounded-full shrink-0" @click="setHoje" />
                        </div>

                        <!-- Chips de frequência -->
                        <div class="freq-chips mb-3">
                            <button
                                v-for="f in freqOpcoes"
                                :key="f.value"
                                class="freq-chip"
                                :class="{
                                    'freq-chip--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>

                        <!-- Info semanal/quinzenal -->
                        <div v-if="recorrenciaType === 'semanal' || recorrenciaType === 'quinzenal'" class="recorrencia-preview mb-3">
                            <i class="pi pi-refresh text-primary-500" />
                            <span class="text-sm font-medium">
                                {{ recorrenciaType === 'quinzenal' ? 'A cada 2 semanas, toda' : 'Toda' }}
                                {{ nomeDiaSemana(diaSemanaRecorrencia) }}, às {{ form.startTime || '—' }}
                            </span>
                        </div>

                        <!-- Seletor de dias da semana (diasEspecificos) -->
                        <div v-if="recorrenciaType === 'diasEspecificos'" class="mb-3">
                            <label class="block text-xs font-semibold text-color-secondary mb-1.5">Dias da semana</label>
                            <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>

                        <!-- Quantidade de sessões (apenas quando não é avulsa) -->
                        <div v-if="recorrenciaType !== 'avulsa'" class="mb-3">
                            <label class="block text-xs font-semibold text-color-secondary mb-1.5">Quantidade de sessões</label>
                            <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 }" @click="qtdSessoesMode = opt.value">{{ opt.label }}</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>

                        <!-- Preview das ocorrências -->
                        <div v-if="recorrenciaType !== 'avulsa' && ocorrenciasComConflito.length" class="ocorrencias-preview">
                            <div class="ocorrencias-header">
                                <span class="text-xs font-semibold text-color-secondary">{{ totalOcorrencias }} sessões previstas</span>
                                <span v-if="totalConflitos > 0" class="ocorrencias-aviso">
                                    <i class="pi pi-exclamation-triangle" />
                                    {{ totalConflitos }} com conflito
                                </span>
                            </div>
                            <div class="ocorrencias-scroll">
                                <div
                                    v-for="(o, i) in ocorrenciasComConflito"
                                    :key="i"
                                    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 v-if="o.conflict" class="ocorrencia-conflict-label">{{ o.conflict.label }}</span>
                                </div>
                            </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>

                        <!-- Modo de valor da série — informativo, só quando há serviços + recorrência -->
                        <div v-if="recorrenciaType !== 'avulsa' && commitmentItems.length > 0" class="mb-1 mt-3">
                            <label class="block text-xs font-semibold text-color-secondary mb-1.5">Como interpretar o valor</label>
                            <SelectButton
                                v-model="serieValorMode"
                                :options="[
                                    { label: 'Por sessão', value: 'multiplicar' },
                                    { label: 'Pacote fechado', value: 'dividir' }
                                ]"
                                optionLabel="label"
                                optionValue="value"
                                size="small"
                                class="w-full mb-2"
                            />
                            <Message v-if="serieValorAviso" severity="info" :closable="false">
                                <span class="text-xs">{{ serieValorAviso }}</span>
                            </Message>
                        </div>
                    </template>
                </div>
            </div>
        </div>

        <!-- ══════════════════════════════════════════════ -->
        <!-- Patient Picker Dialog                          -->
        <!-- ══════════════════════════════════════════════ -->
        <Dialog v-model:visible="pacientePickerOpen" modal header="Selecionar paciente" :draggable="false" :style="{ width: '860px', maxWidth: '96vw' }" :breakpoints="{ '960px': '96vw', '640px': '98vw' }">
            <div class="flex flex-col gap-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>
                <div v-if="pacientesLoading" class="text-sm text-color-secondary">Carregando pacientes…</div>
                <div v-else-if="filteredPatients.length === 0" class="text-sm text-color-secondary">Nenhum paciente encontrado.</div>

                <div v-else class="patient-list">
                    <button v-for="p in filteredPatients" :key="p.id" class="patient-item" @click="selectPaciente(p)">
                        <Avatar v-if="p.avatar_url" :image="p.avatar_url" shape="circle" class="shrink-0" />
                        <Avatar v-else :label="patientInitials(p.nome)" shape="circle" class="shrink-0" />
                        <div class="min-w-0 flex-1">
                            <div class="font-semibold truncate">{{ p.nome || 'Sem nome' }}</div>
                            <div class="text-xs text-color-secondary truncate">
                                <span v-if="p.email">{{ p.email }}</span>
                                <span v-if="p.email && p.telefone"> • </span>
                                <span v-if="p.telefone">{{ p.telefone }}</span>
                            </div>
                        </div>
                        <div class="shrink-0 flex items-center gap-2">
                            <Tag v-if="p.status" :value="String(p.status)" severity="secondary" />
                            <Button icon="pi pi-check" class="rounded-full" size="small" />
                        </div>
                    </button>
                </div>
            </div>
            <template #footer>
                <Button label="Fechar" icon="pi pi-times" text @click="pacientePickerOpen = false" />
            </template>
        </Dialog>

        <!-- ══════════════════════════════════════════════ -->
        <!-- Time Picker Dialog                             -->
        <!-- ══════════════════════════════════════════════ -->
        <Dialog v-model:visible="timePickerOpen" modal header="Data e Horário" :draggable="false" :style="{ width: '560px', maxWidth: '96vw' }" :breakpoints="{ '640px': '98vw' }">
            <div class="flex flex-col gap-4">
                <!-- Data -->
                <div>
                    <label class="block text-sm font-medium mb-2">Data</label>
                    <DatePicker v-model="form.dia" dateFormat="dd/mm/yy" showIcon fluid inline />
                </div>

                <!-- ── Horários disponíveis (presencial e online unificado) ──── -->
                <div v-if="availableSlots.length">
                    <!-- 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>
                        </div>
                    </template>

                    <!-- Header: rótulo + period chips (só períodos com slots) -->
                    <div class="flex items-center justify-between mb-2">
                        <label class="text-sm font-medium">Horários disponíveis</label>
                        <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>

                    <!-- 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-1">Nenhum horário disponível neste período.</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>

                <Divider class="my-1" />

                <!-- Início + Duração -->
                <div class="time-controls">
                    <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">Duração</label>
                        <Select
                            v-model="form.duracaoMin"
                            :options="duracaoOptions"
                            optionLabel="label"
                            optionValue="value"
                            optionGroupLabel="label"
                            optionGroupChildren="items"
                            variant="filled"
                            class="w-full"
                            :disabled="isDynamic && commitmentItems.length > 0"
                        />
                        <small v-if="isDynamic && commitmentItems.length > 0" class="text-color-secondary text-xs mt-1 block"> Calculado pelos serviços adicionados </small>
                    </div>
                </div>

                <!-- Término em destaque -->
                <div v-if="fimDateTime" class="time-fim-card">
                    <div class="time-fim-card__label">
                        <i class="pi pi-flag-fill" />
                        Término
                    </div>
                    <div class="time-fim-card__value">{{ fmtTime(fimDateTime) }}</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="Confirmar" icon="pi pi-check" @click="timePickerOpen = false" />
            </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' }"
            @created="onPatientCreatedRapido"
        />
    </Dialog>
</template>

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

/* ── 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 grid ────────────────────── */
.commitment-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem;
}
@media (max-width: 640px) {
    .commitment-grid {
        grid-template-columns: 1fr;
    }
}

.commitment-card {
    width: 100%;
    text-align: left;
    border: 1px solid var(--surface-border);
    border-radius: 6px;
    background: color-mix(in srgb, var(--surface-card), transparent 10%);
    transition:
        box-shadow 0.12s ease,
        transform 0.12s ease,
        border-color 0.12s;
    overflow: hidden;
}
.commitment-card:hover {
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
    transform: translateY(-2px);
    border-color: color-mix(in srgb, var(--card-color, var(--p-primary-500)) 60%, transparent);
}
.commitment-card__inner {
    display: flex;
    align-items: flex-start;
    gap: 0.85rem;
    padding: 1rem;
}
.commitment-card__icon {
    display: grid;
    place-items: center;
    width: 2.2rem;
    height: 2.2rem;
    border-radius: 0.75rem;
    background: color-mix(in srgb, var(--p-primary-500) 12%, transparent);
    color: var(--p-primary-500);
    font-size: 1rem;
    flex-shrink: 0;
}
.commitment-card--blocked {
    opacity: 0.45;
    cursor: not-allowed;
    pointer-events: none;
    border-color: color-mix(in srgb, var(--red-500) 40%, var(--surface-border));
    background: color-mix(in srgb, var(--red-50, #fef2f2) 30%, var(--surface-card));
}

/* ── step 2: grid layout ────────────────────────── */
.composer-grid {
    display: grid;
    grid-template-columns: 1.3fr 0.7fr;
    gap: 1rem;
    align-items: start;
    margin-top: 2px;
}
@media (max-width: 960px) {
    .composer-grid {
        grid-template-columns: 1fr;
    }
}

/* ── 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.4rem 0.6rem 0.4rem 0.9rem;
    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%);
}

/* Modalidade dentro do card de paciente */
.patient-hero__modalidade {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px 10px;
    border-top: 1px solid var(--surface-border);
}
.patient-hero__mod-label {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-color-secondary);
    white-space: nowrap;
}

/* 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: 7px 12px;
    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);
}

/* ── time hero ──────────────────────────────────── */
.time-hero {
    display: flex;
    align-items: stretch;
    cursor: pointer;
    transition:
        border-color 0.12s,
        box-shadow 0.12s;
    background: color-mix(in srgb, var(--surface-card), transparent 10%);
}
.time-hero:hover {
    border-color: var(--p-primary-400, #818cf8);
    box-shadow: 0 2px 12px color-mix(in srgb, var(--p-primary-500) 15%, transparent);
}
.time-hero__block {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.2rem;
    padding: 0.7rem 0.5rem;
    min-width: 0;
}
.time-hero__label {
    font-size: 0.65rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-color-secondary);
    white-space: nowrap;
}
.time-hero__value {
    font-size: 0.92rem;
    font-weight: 700;
    color: var(--text-color);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
.time-hero__sep {
    width: 1px;
    background: var(--surface-border);
    margin: 0.5rem 0;
}
.time-hero__edit {
    display: flex;
    align-items: center;
    padding: 0 0.75rem;
    color: var(--text-color-secondary);
    opacity: 0.5;
    font-size: 0.8rem;
}

/* ── 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;
    }
}

/* ── side panel ─────────────────────────────────── */
.composer-right {
    position: sticky;
    top: 0;
}
.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;
}

/* 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.25rem 0.75rem;
    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;
}
.freq-tab:hover {
    border-color: var(--p-primary-400);
    color: var(--p-primary-500);
}
.freq-tab--active {
    background: var(--p-primary-500, #6366f1);
    border-color: var(--p-primary-500, #6366f1);
    color: #fff;
}

/* ── 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;
}

/* Término em destaque */
.time-fim-card {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 16px;
    border-radius: 6px;
    background: color-mix(in srgb, var(--primary-500, #6366f1) 8%, var(--surface-card));
    border: 1px solid color-mix(in srgb, var(--primary-400, #818cf8) 25%, transparent);
}
.time-fim-card__label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--primary-500, #6366f1);
    opacity: 0.75;
}
.time-fim-card__value {
    font-size: 1.5rem;
    font-weight: 800;
    letter-spacing: -0.03em;
    color: var(--primary-600, #4f46e5);
    margin-left: auto;
}

/* 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;
}

/* 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 ───────────────────────────────── */
.patient-list {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    max-height: 56vh;
    overflow: auto;
    padding-right: 0.25rem;
}
.patient-item {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    text-align: left;
    padding: 0.85rem 0.95rem;
    border: 1px solid var(--surface-border);
    border-radius: 6px;
    background: color-mix(in srgb, var(--surface-card), transparent 10%);
    transition:
        box-shadow 0.12s ease,
        transform 0.12s ease;
}
.patient-item:hover {
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.06);
    transform: translateY(-1px);
}

/* ── 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: 400;
    text-transform: none;
    letter-spacing: 0;
    opacity: 0.75;
    margin-left: 0.25rem;
}
.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__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__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;
}

/* ── Commitment items (serviços vinculados ao evento) ── */
.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;
}
</style>
