/*
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Arquivo: src/features/agenda/composables/useAgendaEventLifecycle.js
| Data: 2026-05-04
|
| A66 sub-sessão 1C-ii-b — lifecycle do AgendaEventDialog:
|   - Watcher props.modelValue (init form ao abrir — orquestra
|     loadPatients/ensureServicesLoaded/loadInsurancePlans/
|     _loadCommitmentItemsForEvent + reset de refs)
|   - Watcher [tenantId, restrictPatients, patientScopeOwnerId]
|   - Watcher [dia, startTime] (solicitação pendente do agendador público)
|   - Watcher [dia, modalidade] (online slots loader)
|   - Series pills (loadSerieEvents + 4 handlers + generateRuleDates)
|   - selectSlot
|   - Quick-creates wiring (service + insurance)
|   - onSendManualReminder (lembrete WhatsApp)
|
| Recebe via argumento:
|   composer        — composer (1B)
|   actions         — actions (1C-i): _skipStatusWatch, _restoringConvenio,
|                     samePatientConflict
|   pickerBilling   — picker/billing (1C-ii-a): ensureServicesLoaded,
|                     _loadCommitmentItemsForEvent, clearPatientsCache,
|                     loadPatients, addItem
|   commitmentItems — ref<Item[]>
|   serieEvents     — ref<SerieEvent[]>
|   servicePickerSel — ref do picker
|   selectedPlanService — ref do procedure de convênio
|   serieValorMode  — ref<'multiplicar' | 'dividir'>
|   services        — ref<Service[]> (de useServices)
|   loadServices    — fn(ownerId)
|   loadInsurancePlans — fn(ownerId)
|   props           — props do dialog
|   emit            — emitter ('updateSeriesEvent', 'editSeriesOccurrence', 'delete')
|   confirm         — useConfirm()
|   toast           — useToast()
|--------------------------------------------------------------------------
*/
import { ref, computed, watch, nextTick } from 'vue';
import { supabase } from '@/lib/supabase/client';

import { tenantDb } from '@/lib/supabase/tenantClient';
export 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;
}

export function useAgendaEventLifecycle({
    composer,
    actions,
    pickerBilling,
    commitmentItems,
    serieEvents,
    servicePickerSel,
    selectedPlanService,
    serieValorMode,
    services,
    loadServices,
    loadInsurancePlans,
    props,
    emit,
    confirm,
    toast
}) {
    // ── refs locais ────────────────────────────────────────────
    const solicitacaoPendente = ref(null);
    const onlineSlots = ref([]);
    const loadingOnlineSlots = ref(false);
    const serieLoading = ref(false);
    const pillDeleteMenuRef = ref(null);
    const pillDeleteTarget = ref(null);
    const sendingReminder = ref(false);
    const serviceQuickDlgOpen = ref(false);
    const insuranceQuickDlgOpen = ref(false);
    const planServiceQuickDlgOpen = ref(false);

    // occurrenceMode: financial_record da ocorrencia atual (se existir).
    // Usado pra travar edicao de tipo/servicos quando ja ha cobranca emitida
    // (padrao SimplePractice — cobranca emitida e imutavel; ajustes via fluxo
    // do Financeiro, nao via dialog). 2026-05-12.
    const occFinancialRecord = ref(null);
    const occFinancialLoading = ref(false);

    // sessionPaymentRecord (2026-05-18): financial_record da sessão (mesmo
    // shape do occFinancialRecord) mas SEM o guard de occurrenceMode.
    // Carregado em qualquer edit de sessão pra alimentar a linha "Cobrança"
    // do Resumo lateral do AgendaEventDialog. Não dispara lock — esse
    // continua via occFinancialRecord (território da Fase 6/C13).
    const sessionPaymentRecord = ref(null);

    // sessionContract (2026-05-19 noite): billing_contract ativo do paciente
    // quando a sessão pertence a uma série com pacote (upfront ou saldo).
    // Usado pra exibir info card no dialog explicando o pacote (qtd
    // sessões usadas/restantes, valor, comportamento). Pra saldo, é a
    // única forma do user entender o que tá acontecendo (nada aparece
    // no /financeiro até realizar sessão).
    const sessionContract = ref(null);

    // ── computeds locais ───────────────────────────────────────
    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') }
        ];
    });

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

            const { data: excData } = await tenantDb().from('recurrence_exceptions').select('original_date, type, reason').eq('recurrence_id', rid);
            const exMap = new Map((excData || []).map((e) => [e.original_date, e]));

            const { data: realData } = await tenantDb().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]));

            const dates = rule ? generateRuleDates(rule) : [];
            const startTime = rule?.start_time || '00:00:00';
            const durMin = rule?.duration_min || 50;

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

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

    // ── occurrence financial record loader ────────────────────
    // Tenta 3 caminhos pra encontrar um record relevante:
    //   1) Record direto por agenda_evento_id (materializada com cobrança própria)
    //   2) Se a sessão pertence a contrato upfront PAGO, sintetiza um record
    //      'paid' com o package_price → assim TODAS as ocorrências da série
    //      mostram "Cobrança paga" no dialog (cobertas pelo pacote pago).
    //   3) Senão, null → card unlocked.
    async function loadOccFinancialRecord() {
        occFinancialRecord.value = null;
        // Guard de occurrenceMode REMOVIDO em 2026-05-19 — necessario pra
        // que o lock de "edit cobrada" (Fase 6) tambem ative em Melissa,
        // nao so em Rail/Clinica. Padrao SimplePractice: cobranca emitida
        // eh imutavel pelo dialog; ajustes via estorno no Financeiro.
        const evId = props.eventRow?.id;
        const ruleId = props.eventRow?.recurrence_id;
        const patientId = props.eventRow?.paciente_id || props.eventRow?.patient_id;
        occFinancialLoading.value = true;
        try {
            // 1) Record direto (materializada que tem agenda_evento_id real)
            const isVirtualId = typeof evId === 'string' && evId.startsWith('rec::');
            if (evId && !isVirtualId) {
                const { data, error } = await tenantDb().from('financial_records')
                    .select('id, amount, final_amount, status, due_date, paid_at, payment_method')
                    .eq('agenda_evento_id', evId)
                    .in('status', ['pending', 'paid', 'overdue'])
                    .order('created_at', { ascending: false })
                    .limit(1)
                    .maybeSingle();
                if (error) throw error;
                if (data) {
                    occFinancialRecord.value = data;
                    return;
                }
            }
            // 2) Sintetiza record 'paid' quando há contrato upfront pago.
            //    A 1ª materializada tem o record real; siblings (virtuais ou
            //    materializadas sem cobrança individual) herdam status do
            //    contrato pra UI mostrar "Cobrança paga" coerentemente.
            if (ruleId && patientId) {
                const { data: contracts } = await tenantDb().from('billing_contracts')
                    .select('id, package_price, charging_style, status')
                    .eq('patient_id', patientId)
                    .eq('type', 'package')
                    .eq('status', 'active')
                    .order('created_at', { ascending: false });
                const upfront = (contracts || []).find((c) => c.charging_style === 'upfront');
                if (upfront) {
                    // Confere se há record PAGO ligado a qualquer evento do
                    // mesmo recurrence_id (ou seja, contrato foi quitado).
                    const { data: siblingEvents } = await tenantDb().from('agenda_eventos')
                        .select('id')
                        .eq('recurrence_id', ruleId);
                    const ids = (siblingEvents || []).map((e) => e.id);
                    if (ids.length) {
                        // Pega o record mais recente NAO cancelado (paid OU
                        // pending OU overdue). Pacote upfront tem 1 record
                        // unico cobrindo toda a serie — qualquer status dele
                        // trava as siblings (cobranca ja emitida, imutavel).
                        const { data: anyRec } = await tenantDb().from('financial_records')
                            .select('id, amount, final_amount, status, due_date, paid_at, payment_method')
                            .in('agenda_evento_id', ids)
                            .in('status', ['paid', 'pending', 'overdue'])
                            .order('created_at', { ascending: false })
                            .limit(1)
                            .maybeSingle();
                        if (anyRec) {
                            // Sintetiza usando o package_price (não o per-session
                            // que pode ter mudado depois de editar).
                            occFinancialRecord.value = {
                                ...anyRec,
                                amount: upfront.package_price,
                                final_amount: upfront.package_price,
                                _synthesized: true
                            };
                            return;
                        }
                    }
                }
            }
            occFinancialRecord.value = null;
        } catch (e) {
            console.warn('[occurrence] erro ao carregar financial_record:', e?.message);
            occFinancialRecord.value = null;
        } finally {
            occFinancialLoading.value = false;
        }
    }

    // sessionPaymentRecord loader (2026-05-18): mesma query, sem guard
    // de occurrenceMode. Alimenta a linha "Cobrança" do Resumo do dialog
    // em qualquer edit de sessão (Melissa/Rail/Clínica) com eventRow.id.
    async function loadSessionPaymentRecord() {
        sessionPaymentRecord.value = null;
        const evId = props.eventRow?.id;
        if (!evId) return;
        try {
            const { data, error } = await tenantDb().from('financial_records')
                .select('id, amount, final_amount, status, due_date, paid_at, payment_method')
                .eq('agenda_evento_id', evId)
                .in('status', ['pending', 'paid', 'overdue'])
                .order('created_at', { ascending: false })
                .limit(1)
                .maybeSingle();
            if (error) throw error;
            sessionPaymentRecord.value = data ?? null;
        } catch (e) {
            console.warn('[session-payment] erro ao carregar financial_record:', e?.message);
            sessionPaymentRecord.value = null;
        }
    }

    // loadSessionContract (2026-05-19 noite): busca billing_contract ativo
    // do paciente quando o evento pertence a uma série com pacote. Usado
    // pra info card no dialog explicando o pacote saldo/upfront.
    async function loadSessionContract() {
        sessionContract.value = null;
        const patientId = props.eventRow?.paciente_id || props.eventRow?.patient_id;
        const ruleId = props.eventRow?.recurrence_id;
        // Só faz sentido pra sessão de série
        if (!patientId || !ruleId) return;
        try {
            const { data, error } = await tenantDb().from('billing_contracts')
                .select('id, type, total_sessions, sessions_used, package_price, charging_style, status, active_from')
                .eq('patient_id', patientId)
                .eq('type', 'package')
                .eq('status', 'active')
                .order('created_at', { ascending: false })
                .limit(1)
                .maybeSingle();
            if (error) throw error;
            sessionContract.value = data ?? null;
        } catch (e) {
            console.warn('[session-contract] erro ao carregar:', e?.message);
            sessionContract.value = null;
        }
    }

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

    // ── slot selection ─────────────────────────────────────────
    function selectSlot(hhmm) {
        const [h, m] = String(hhmm).split(':').map(Number);
        const d = new Date();
        d.setHours(h, m, 0, 0);
        composer.startTimeDate.value = d;
    }

    // ── quick-creates ──────────────────────────────────────────
    function openServiceQuickCreate() {
        serviceQuickDlgOpen.value = true;
    }
    async function onServiceCreated(svc) {
        await loadServices(props.ownerId);
        if (svc?.id) {
            const list = services?.value;
            const fresh = (Array.isArray(list) ? list.find((s) => s.id === svc.id) : null) || svc;
            if (typeof pickerBilling.addItem === 'function') {
                pickerBilling.addItem(fresh);
            }
        }
    }

    function openInsuranceQuickCreate() {
        insuranceQuickDlgOpen.value = true;
    }
    async function onInsuranceCreated(plan) {
        await loadInsurancePlans(props.planOwnerId || props.ownerId);
        if (plan?.id) {
            composer.form.value.insurance_plan_id = plan.id;
        }
    }

    // Quick-create de procedimento (insurance_plan_services) — inline,
    // sem sair do dialog. Trigger no card Sessao/Honorarios quando o
    // convenio selecionado nao tem procedimentos ou quando user quer
    // adicionar mais. Apos criar, recarrega os planos pra refletir no
    // computed planServices.
    function openPlanServiceQuickCreate() {
        if (!composer.form.value.insurance_plan_id) return;
        planServiceQuickDlgOpen.value = true;
    }
    async function onPlanServiceCreated(service) {
        await loadInsurancePlans(props.planOwnerId || props.ownerId);
        // Auto-seleciona o procedimento recem-criado se o user nao
        // tinha nenhum selecionado ainda (caso comum: convenio sem
        // procedimentos -> cadastra o primeiro -> ja entra selecionado).
        if (service?.id && !pickerBilling.selectedPlanService.value) {
            pickerBilling.selectedPlanService.value = service.id;
            pickerBilling.onProcedureSelect(service.id);
        }
    }

    // ── lembrete WhatsApp manual (8.2) ─────────────────────────
    async function onSendManualReminder() {
        if (!composer.form.value?.id) return;
        confirm.require({
            header: 'Enviar lembrete WhatsApp?',
            message: `Vou mandar o template "lembrete_sessao" pra ${composer.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: composer.form.value.id, tenant_id: props.tenantId }
                    });
                    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;
                }
            }
        });
    }

    // ── watchers ───────────────────────────────────────────────
    // Init form ao abrir o dialog (orquestra tudo)
    watch(
        () => props.modelValue,
        async (open) => {
            if (!open) return;
            await nextTick();

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

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

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

            // occurrenceMode: carrega financial_record desta ocorrencia
            // pra decidir se o card Sessao/Honorarios fica locked (cobranca
            // ja emitida) ou unlocked (sem cobranca, edicao livre).
            loadOccFinancialRecord();

            // sessionPaymentRecord: carrega em qualquer edit (Melissa
            // tambem) pra alimentar a linha "Cobrança" do Resumo lateral.
            loadSessionPaymentRecord();
            loadSessionContract();

            // occurrenceMode: editando UMA ocorrencia de serie ja existente —
            // tipo de compromisso ja foi escolhido (paciente + sessao). Pular
            // step 1 incondicionalmente. Defesa em camadas: useMelissaAgenda
            // ja seta is_occurrence=true na row (faz isEdit=true), mas se outro
            // call site esquecer essa flag o guard aqui salva.
            if (props.occurrenceMode || composer.isEdit.value) {
                composer.step.value = 2;
            } else {
                const preset = props.presetCommitmentId;
                if (preset) {
                    composer.form.value.commitment_id = preset;
                    composer.step.value = 2;
                } else composer.step.value = 1;
            }

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

            pickerBilling.ensureServicesLoaded();
            const insuranceOwner = props.planOwnerId || props.ownerId;
            if (insuranceOwner) {
                await loadInsurancePlans(insuranceOwner);
            }

            selectedPlanService.value = null;
            actions._restoringConvenio.value = false;

            commitmentItems.value = [];
            servicePickerSel.value = null;
            if (composer.isEdit.value && (composer.form.value.id || props.eventRow?.recurrence_id)) {
                pickerBilling._loadCommitmentItemsForEvent(composer.form.value.id);
            } else {
                composer.billingType.value = 'particular';
            }
        }
    );

    // Tenant/scope mudou — recarrega lista de pacientes
    watch(
        () => [props.tenantId, props.restrictPatientsToOwner, props.patientScopeOwnerId],
        () => {
            if (!composer.visible.value) return;
            pickerBilling.clearPatientsCache();
            if (composer.requiresPatient.value) pickerBilling.loadPatients(true);
        }
    );

    // Solicitação pendente do agendador público no horário escolhido
    watch(
        () => [composer.form.value.dia?.toString(), composer.form.value.startTime],
        async ([dia, startTime]) => {
            solicitacaoPendente.value = null;
            if (!composer.isSessionEvent.value || !composer.visible.value || composer.isEdit.value) return;
            if (!props.ownerId || !dia || !startTime) return;

            const d = new Date(composer.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;
        }
    );

    // Online slots: depende de [dia, modalidade]
    watch(
        [() => composer.form.value.dia, () => composer.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 tenantDb().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 }
    );

    return {
        // refs
        solicitacaoPendente,
        onlineSlots,
        loadingOnlineSlots,
        serieLoading,
        pillDeleteMenuRef,
        pillDeleteTarget,
        sendingReminder,
        serviceQuickDlgOpen,
        insuranceQuickDlgOpen,
        planServiceQuickDlgOpen,
        occFinancialRecord,
        occFinancialLoading,
        sessionPaymentRecord,
        // computeds
        serieCountByStatus,
        pillDeleteMenuItems,
        // series
        loadSerieEvents,
        loadOccFinancialRecord,
        loadSessionPaymentRecord,
        sessionContract,
        loadSessionContract,
        onPillEditClick,
        onPillStatusChange,
        onPillDeleteClick,
        onPillDelete,
        // slot
        selectSlot,
        // quick-creates
        openServiceQuickCreate,
        onServiceCreated,
        openInsuranceQuickCreate,
        onInsuranceCreated,
        openPlanServiceQuickCreate,
        onPlanServiceCreated,
        // reminder
        onSendManualReminder
    };
}
