/**
 * useAgendaEventLifecycle.spec.js — A66 sub-sessão 1C-ii-b
 *
 * Cobre: generateRuleDates (pura), loadSerieEvents, 4 onPill handlers,
 * selectSlot, quick-creates wiring, onSendManualReminder, e os 4 watchers
 * (modelValue init, tenant scope, solicitação pendente, online slots).
 *
 * Mock supabase: dispatcher por tabela. Cada `.from(table)` retorna um
 * builder que registra a chain e retorna `_responses[table]` quando
 * awaited / .maybeSingle() / .single().
 */
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { ref, computed, nextTick } from 'vue';

// ── Mocks supabase ────────────────────────────────────────────
const _responses = {};            // { 'patients': { data, error }, ... }
const _calls = [];                // log das chamadas .from()
const _functionsInvoke = vi.fn(); // pra functions.invoke

function setResponse(table, payload) {
    _responses[table] = payload;
}
function clearMocks() {
    for (const k of Object.keys(_responses)) delete _responses[k];
    _calls.length = 0;
    _functionsInvoke.mockReset();
}

function makeBuilder(table) {
    const log = { table, ops: [] };
    _calls.push(log);
    const result = () => _responses[table] ?? { data: null, error: null };
    const b = {
        select: (...a) => { log.ops.push(['select', ...a]); return b; },
        eq: (...a) => { log.ops.push(['eq', ...a]); return b; },
        is: (...a) => { log.ops.push(['is', ...a]); return b; },
        order: (...a) => { log.ops.push(['order', ...a]); return b; },
        limit: (...a) => { log.ops.push(['limit', ...a]); return b; },
        maybeSingle: () => Promise.resolve(result()),
        single: () => Promise.resolve(result()),
        then: (resolve) => resolve(result())
    };
    return b;
}

vi.mock('@/lib/supabase/client', () => ({
    supabase: {
        from: (table) => makeBuilder(table),
        functions: { invoke: (...args) => _functionsInvoke(...args) }
    }
}));

const lifecycleModule = await import('../useAgendaEventLifecycle.js');
const { useAgendaEventLifecycle, generateRuleDates } = lifecycleModule;

// ── Helpers de fixture ────────────────────────────────────────
function makeComposer(overrides = {}) {
    const form = ref({
        id: null,
        commitment_id: null,
        paciente_id: null,
        paciente_nome: '',
        paciente_avatar: '',
        insurance_plan_id: null,
        insurance_plan_service_id: null,
        insurance_value: null,
        insurance_guide_number: null,
        dia: null,
        startTime: '',
        modalidade: 'presencial',
        ...(overrides.formExtra || {})
    });
    return {
        form,
        visible: ref(true),
        isEdit: ref(false),
        hasSerie: ref(false),
        isSessionEvent: ref(true),
        requiresPatient: ref(true),
        billingType: ref('particular'),
        recorrenciaType: ref('avulsa'),
        diasSelecionados: ref([]),
        dataLimiteManual: ref(null),
        qtdSessoesMode: ref('4'),
        qtdSessoesCustom: ref(12),
        editScope: ref('somente_este'),
        step: ref(1),
        startTimeDate: ref(null),
        resetForm: vi.fn(() => ({
            id: null,
            commitment_id: null,
            paciente_id: null,
            paciente_nome: '',
            paciente_avatar: '',
            insurance_plan_id: null,
            insurance_plan_service_id: null,
            insurance_value: null,
            insurance_guide_number: null,
            dia: null,
            startTime: '',
            modalidade: 'presencial'
        })),
        ...overrides
    };
}
function makeActions() {
    return {
        _skipStatusWatch: ref(false),
        _restoringConvenio: ref(false),
        samePatientConflict: ref(null)
    };
}
function makePickerBilling() {
    return {
        ensureServicesLoaded: vi.fn().mockResolvedValue(),
        _loadCommitmentItemsForEvent: vi.fn().mockResolvedValue(),
        clearPatientsCache: vi.fn(),
        loadPatients: vi.fn().mockResolvedValue(),
        addItem: vi.fn().mockResolvedValue()
    };
}
function makeConfirm() {
    return {
        require: vi.fn((opts) => {
            // por padrão, dispara accept imediatamente
            if (typeof opts.accept === 'function') return opts.accept();
        })
    };
}
function makeToast() {
    return { add: vi.fn() };
}

function setup(overrides = {}, propsOverrides = {}) {
    const composer = overrides.composer ?? makeComposer();
    const actions = overrides.actions ?? makeActions();
    const pickerBilling = overrides.pickerBilling ?? makePickerBilling();
    const commitmentItems = overrides.commitmentItems ?? ref([]);
    const serieEvents = overrides.serieEvents ?? ref([]);
    const servicePickerSel = overrides.servicePickerSel ?? ref(null);
    const selectedPlanService = overrides.selectedPlanService ?? ref(null);
    const serieValorMode = overrides.serieValorMode ?? ref('multiplicar');
    const services = overrides.services ?? ref([]);
    const loadServices = overrides.loadServices ?? vi.fn().mockResolvedValue();
    const loadInsurancePlans = overrides.loadInsurancePlans ?? vi.fn().mockResolvedValue();
    const props = ref({
        modelValue: false,
        eventRow: null,
        ownerId: 'owner-1',
        tenantId: 'tenant-1',
        planOwnerId: '',
        presetCommitmentId: null,
        restrictPatientsToOwner: false,
        patientScopeOwnerId: null,
        ...propsOverrides
    });
    const confirm = overrides.confirm ?? makeConfirm();
    const toast = overrides.toast ?? makeToast();
    const emit = vi.fn();

    const result = useAgendaEventLifecycle({
        composer,
        actions,
        pickerBilling,
        commitmentItems,
        serieEvents,
        servicePickerSel,
        selectedPlanService,
        serieValorMode,
        services,
        loadServices,
        loadInsurancePlans,
        // Vue não aceita `props` reativo aqui — passa o objeto direto.
        // Pra simular mudança, modificamos `props.value.X` antes do watcher rodar:
        // o `propsRefProxy` abaixo faz o lifecycle ver props como objeto plano,
        // mas testes que precisam reatividade no watcher de modelValue usam
        // props.value diretamente.
        props: new Proxy({}, {
            get(_, k) { return props.value[k]; },
            set(_, k, v) { props.value[k] = v; return true; }
        }),
        emit,
        confirm,
        toast
    });

    return {
        composer, actions, pickerBilling, commitmentItems, serieEvents,
        servicePickerSel, selectedPlanService, serieValorMode, services,
        loadServices, loadInsurancePlans, propsRef: props, confirm, toast, emit,
        ...result
    };
}

beforeEach(() => {
    clearMocks();
});

// ════════════════════════════════════════════════════════════════════
describe('generateRuleDates', () => {
    it('sem start_date → []', () => {
        expect(generateRuleDates({ weekdays: [1] })).toEqual([]);
    });
    it('sem weekdays → []', () => {
        expect(generateRuleDates({ start_date: '2026-05-04' })).toEqual([]);
    });
    it('regra null/undefined → []', () => {
        expect(generateRuleDates(null)).toEqual([]);
        expect(generateRuleDates(undefined)).toEqual([]);
    });

    it('weekly interval=1 com max_occurrences=3', () => {
        // 2026-05-04 é uma segunda-feira (weekday=1)
        const dates = generateRuleDates({
            type: 'weekly',
            interval: 1,
            weekdays: [1],
            start_date: '2026-05-04',
            max_occurrences: 3
        });
        expect(dates).toEqual(['2026-05-04', '2026-05-11', '2026-05-18']);
    });

    it('quinzenal interval=2 (gap de 14 dias)', () => {
        const dates = generateRuleDates({
            type: 'biweekly',
            interval: 2,
            weekdays: [1],
            start_date: '2026-05-04',
            max_occurrences: 3
        });
        expect(dates).toEqual(['2026-05-04', '2026-05-18', '2026-06-01']);
    });

    it('custom_weekdays — só inclui dias que casam', () => {
        // Ter (2) e Qui (4), 4 ocorrências
        const dates = generateRuleDates({
            type: 'custom_weekdays',
            weekdays: [2, 4],
            start_date: '2026-05-04',
            max_occurrences: 4
        });
        // Ter 5/5, Qui 7/5, Ter 12/5, Qui 14/5
        expect(dates).toEqual(['2026-05-05', '2026-05-07', '2026-05-12', '2026-05-14']);
    });

    it('respeita end_date', () => {
        const dates = generateRuleDates({
            type: 'weekly',
            interval: 1,
            weekdays: [1],
            start_date: '2026-05-04',
            end_date: '2026-05-15',
            max_occurrences: 365
        });
        expect(dates).toEqual(['2026-05-04', '2026-05-11']);
    });

    it('clamp max_occurrences a 365', () => {
        const dates = generateRuleDates({
            type: 'weekly',
            interval: 1,
            weekdays: [1],
            start_date: '2026-01-05',
            max_occurrences: 9999
        });
        expect(dates.length).toBeLessThanOrEqual(365);
    });
});

// ════════════════════════════════════════════════════════════════════
describe('loadSerieEvents', () => {
    it('sem recurrence_id/serie_id → zera serieEvents', async () => {
        const { loadSerieEvents, serieEvents } = setup();
        serieEvents.value = [{ id: 'old' }];
        await loadSerieEvents();
        expect(serieEvents.value).toEqual([]);
    });

    it('com recurrence_id: gera lista de virtuals + materializa reais', async () => {
        const { loadSerieEvents, serieEvents } = setup({}, { eventRow: { recurrence_id: 'rec-1' } });
        setResponse('recurrence_rules', {
            data: {
                id: 'rec-1', type: 'weekly', interval: 1, weekdays: [1],
                start_date: '2026-05-04', max_occurrences: 2,
                start_time: '14:00:00', duration_min: 50
            },
            error: null
        });
        setResponse('recurrence_exceptions', { data: [], error: null });
        setResponse('agenda_eventos', {
            data: [{ id: 'real-1', inicio_em: '2026-05-04T14:00:00', fim_em: '2026-05-04T14:50:00', status: 'realizado', recurrence_date: '2026-05-04' }],
            error: null
        });

        await loadSerieEvents();
        expect(serieEvents.value).toHaveLength(2);
        // primeira é a real (status='realizado', _is_virtual=false)
        const first = serieEvents.value.find((e) => e.recurrence_date === '2026-05-04');
        expect(first.id).toBe('real-1');
        expect(first._is_virtual).toBe(false);
        expect(first._status).toBe('realizado');
        // segunda é virtual
        const second = serieEvents.value.find((e) => e.recurrence_date === '2026-05-11');
        expect(second.id).toBeNull();
        expect(second._is_virtual).toBe(true);
        expect(second._status).toBe('agendado');
    });

    it('exception cancel_session vira _cancelled=true', async () => {
        const { loadSerieEvents, serieEvents } = setup({}, { eventRow: { recurrence_id: 'rec-1' } });
        setResponse('recurrence_rules', {
            data: { id: 'rec-1', type: 'weekly', interval: 1, weekdays: [1], start_date: '2026-05-04', max_occurrences: 1, start_time: '10:00:00', duration_min: 50 },
            error: null
        });
        setResponse('recurrence_exceptions', {
            data: [{ original_date: '2026-05-04', type: 'cancel_session', reason: 'paciente desmarcou' }],
            error: null
        });
        setResponse('agenda_eventos', { data: [], error: null });

        await loadSerieEvents();
        expect(serieEvents.value[0]._cancelled).toBe(true);
        expect(serieEvents.value[0]._status).toBe('cancelado');
        expect(serieEvents.value[0]._reason).toBe('paciente desmarcou');
    });

    it('engole erro do supabase e zera lista', async () => {
        const { loadSerieEvents, serieEvents } = setup({}, { eventRow: { recurrence_id: 'rec-1' } });
        setResponse('recurrence_rules', { data: null, error: new Error('boom') });
        const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
        await loadSerieEvents();
        expect(serieEvents.value).toEqual([]);
        errSpy.mockRestore();
    });
});

// ════════════════════════════════════════════════════════════════════
describe('onPill handlers', () => {
    it('onPillEditClick: emit editSeriesOccurrence com payload', () => {
        const { onPillEditClick, emit } = setup();
        onPillEditClick({ id: 'e-1', recurrence_date: '2026-05-04', inicio_em: 'X', fim_em: 'Y', _is_virtual: true });
        expect(emit).toHaveBeenCalledWith('editSeriesOccurrence', {
            id: 'e-1',
            recurrence_date: '2026-05-04',
            inicio_em: 'X',
            fim_em: 'Y',
            is_virtual: true
        });
    });

    it('onPillStatusChange: emit updateSeriesEvent', () => {
        const { onPillStatusChange, emit } = setup();
        onPillStatusChange({ id: 'e-1', _status: 'realizado', recurrence_date: '2026-05-04', inicio_em: 'X', fim_em: 'Y', _is_virtual: false });
        expect(emit).toHaveBeenCalledWith('updateSeriesEvent', expect.objectContaining({
            id: 'e-1', status: 'realizado', is_virtual: false
        }));
    });

    it('onPillStatusChange virtual agenda recarregamento (setTimeout)', () => {
        vi.useFakeTimers();
        const { onPillStatusChange, serieEvents } = setup({}, { eventRow: { recurrence_id: 'rec-1' } });
        setResponse('recurrence_rules', { data: null, error: null });
        setResponse('recurrence_exceptions', { data: [], error: null });
        setResponse('agenda_eventos', { data: [], error: null });

        onPillStatusChange({ id: null, _status: 'agendado', _is_virtual: true });
        vi.advanceTimersByTime(700);
        // Triggered loadSerieEvents — não vamos esperar await aqui;
        // basta saber que não quebrou.
        vi.useRealTimers();
        expect(serieEvents.value).toBeDefined();
    });

    it('onPillDelete (somente_este): confirm + emit delete', () => {
        const confirm = makeConfirm();
        const { onPillDelete, emit } = setup({ confirm }, { eventRow: { recurrence_id: 'rec-1', serie_id: 'ser-1' } });
        onPillDelete({ id: 'e-1', recurrence_date: '2026-05-04', inicio_em: '2026-05-04T14:00:00' }, 'somente_este');
        expect(confirm.require).toHaveBeenCalled();
        expect(emit).toHaveBeenCalledWith('delete', expect.objectContaining({
            id: 'e-1',
            editMode: 'somente_este',
            recurrence_id: 'rec-1',
            original_date: '2026-05-04',
            serie_id: 'ser-1'
        }));
    });

    it('onPillDelete (todos): label header diferente', () => {
        const confirm = makeConfirm();
        const { onPillDelete } = setup({ confirm });
        onPillDelete({ id: 'e-1', inicio_em: '2026-05-04T14:00:00' }, 'todos');
        const callArgs = confirm.require.mock.calls[0][0];
        expect(callArgs.header).toBe('Encerrar toda a série');
        expect(callArgs.icon).toBe('pi pi-trash');
        expect(callArgs.acceptLabel).toBe('Sim, encerrar série');
    });
});

// ════════════════════════════════════════════════════════════════════
describe('selectSlot', () => {
    it('atualiza startTimeDate com a hora', () => {
        const { selectSlot, composer } = setup();
        selectSlot('14:30');
        const d = composer.startTimeDate.value;
        expect(d).toBeInstanceOf(Date);
        expect(d.getHours()).toBe(14);
        expect(d.getMinutes()).toBe(30);
    });
});

// ════════════════════════════════════════════════════════════════════
describe('quick-creates', () => {
    it('openServiceQuickCreate seta serviceQuickDlgOpen=true', () => {
        const { openServiceQuickCreate, serviceQuickDlgOpen } = setup();
        expect(serviceQuickDlgOpen.value).toBe(false);
        openServiceQuickCreate();
        expect(serviceQuickDlgOpen.value).toBe(true);
    });

    it('onServiceCreated chama loadServices e addItem com o svc fresco se encontrado', async () => {
        const services = ref([{ id: 's-1', name: 'Sessão Atualizada', price: 200 }]);
        const pickerBilling = makePickerBilling();
        const { onServiceCreated, loadServices } = setup({ services, pickerBilling });
        await onServiceCreated({ id: 's-1', name: 'Sessão', price: 100 });
        expect(loadServices).toHaveBeenCalledWith('owner-1');
        expect(pickerBilling.addItem).toHaveBeenCalledWith({ id: 's-1', name: 'Sessão Atualizada', price: 200 });
    });

    it('onServiceCreated com svc não na lista usa o param', async () => {
        const services = ref([]);
        const pickerBilling = makePickerBilling();
        const { onServiceCreated } = setup({ services, pickerBilling });
        await onServiceCreated({ id: 's-1', name: 'Param', price: 50 });
        expect(pickerBilling.addItem).toHaveBeenCalledWith({ id: 's-1', name: 'Param', price: 50 });
    });

    it('onServiceCreated sem svc.id → não chama addItem', async () => {
        const pickerBilling = makePickerBilling();
        const { onServiceCreated } = setup({ pickerBilling });
        await onServiceCreated(null);
        expect(pickerBilling.addItem).not.toHaveBeenCalled();
    });

    it('openInsuranceQuickCreate seta flag', () => {
        const { openInsuranceQuickCreate, insuranceQuickDlgOpen } = setup();
        expect(insuranceQuickDlgOpen.value).toBe(false);
        openInsuranceQuickCreate();
        expect(insuranceQuickDlgOpen.value).toBe(true);
    });

    it('onInsuranceCreated chama loadInsurancePlans e seta plan_id', async () => {
        const { onInsuranceCreated, loadInsurancePlans, composer } = setup({}, { planOwnerId: 'planowner-1' });
        await onInsuranceCreated({ id: 'plan-99' });
        expect(loadInsurancePlans).toHaveBeenCalledWith('planowner-1');
        expect(composer.form.value.insurance_plan_id).toBe('plan-99');
    });

    it('onInsuranceCreated fallback ownerId quando não há planOwnerId', async () => {
        const { onInsuranceCreated, loadInsurancePlans } = setup();
        await onInsuranceCreated({ id: 'plan-1' });
        expect(loadInsurancePlans).toHaveBeenCalledWith('owner-1');
    });
});

// ════════════════════════════════════════════════════════════════════
describe('onSendManualReminder', () => {
    it('no-op se form.id é null', async () => {
        const { onSendManualReminder, confirm } = setup();
        await onSendManualReminder();
        expect(confirm.require).not.toHaveBeenCalled();
    });

    it('sucesso: chama functions.invoke + toast success', async () => {
        const composer = makeComposer({ formExtra: { id: 'evt-1', paciente_nome: 'Marina' } });
        _functionsInvoke.mockResolvedValueOnce({ data: { ok: true, to: '+5516988887777' }, error: null });
        const { onSendManualReminder, toast, sendingReminder } = setup({ composer });
        await onSendManualReminder();
        expect(_functionsInvoke).toHaveBeenCalledWith('send-session-reminder-manual', { body: { event_id: 'evt-1', tenant_id: 'tenant-1' } });
        expect(toast.add).toHaveBeenCalledWith(expect.objectContaining({ severity: 'success' }));
        expect(sendingReminder.value).toBe(false);
    });

    it('error no_phone vira "Paciente sem telefone cadastrado."', async () => {
        const composer = makeComposer({ formExtra: { id: 'evt-1' } });
        _functionsInvoke.mockResolvedValueOnce({ data: { ok: false, error: 'no_phone' }, error: null });
        const { onSendManualReminder, toast } = setup({ composer });
        await onSendManualReminder();
        expect(toast.add).toHaveBeenCalledWith(expect.objectContaining({
            severity: 'error',
            detail: 'Paciente sem telefone cadastrado.'
        }));
    });

    it('error template_not_found tem mensagem amigável', async () => {
        const composer = makeComposer({ formExtra: { id: 'evt-1' } });
        _functionsInvoke.mockResolvedValueOnce({ data: { ok: false, error: 'template_not_found' }, error: null });
        const { onSendManualReminder, toast } = setup({ composer });
        await onSendManualReminder();
        expect(toast.add).toHaveBeenCalledWith(expect.objectContaining({
            detail: expect.stringContaining('lembrete_sessao')
        }));
    });

    it('error send_failed_X também é amigável', async () => {
        const composer = makeComposer({ formExtra: { id: 'evt-1' } });
        _functionsInvoke.mockResolvedValueOnce({ data: { ok: false, error: 'send_failed_timeout' }, error: null });
        const { onSendManualReminder, toast } = setup({ composer });
        await onSendManualReminder();
        expect(toast.add).toHaveBeenCalledWith(expect.objectContaining({
            detail: 'Não conseguimos enviar. Verifique a conexão do WhatsApp.'
        }));
    });
});

// ════════════════════════════════════════════════════════════════════
describe('watchers — tenant scope', () => {
    it('quando tenantId muda e dialog visível, recarrega patients', async () => {
        const composer = makeComposer();
        const pickerBilling = makePickerBilling();
        const { propsRef } = setup({ composer, pickerBilling });
        pickerBilling.clearPatientsCache.mockClear();
        pickerBilling.loadPatients.mockClear();
        propsRef.value.tenantId = 'tenant-2';
        await nextTick();
        expect(pickerBilling.clearPatientsCache).toHaveBeenCalled();
        expect(pickerBilling.loadPatients).toHaveBeenCalledWith(true);
    });

    it('não recarrega se dialog não está visível', async () => {
        const composer = makeComposer();
        composer.visible.value = false;
        const pickerBilling = makePickerBilling();
        const { propsRef } = setup({ composer, pickerBilling });
        pickerBilling.clearPatientsCache.mockClear();
        propsRef.value.tenantId = 'tenant-2';
        await nextTick();
        expect(pickerBilling.clearPatientsCache).not.toHaveBeenCalled();
    });
});

describe('watchers — solicitação pendente', () => {
    it('busca solicitação quando dia + startTime estão setados (não-edit)', async () => {
        const composer = makeComposer({ formExtra: { dia: new Date('2026-05-04T12:00:00'), startTime: '14:00' } });
        setResponse('agendador_solicitacoes', { data: { id: 'sol-1', paciente_nome: 'João' }, error: null });
        const { solicitacaoPendente } = setup({ composer });
        // dispara watcher: já estava setado mas não imediato — mudamos pra forçar
        composer.form.value.startTime = '15:00';
        await nextTick();
        await new Promise((r) => setTimeout(r, 0));
        expect(solicitacaoPendente.value).toEqual({ id: 'sol-1', paciente_nome: 'João' });
    });

    it('em modo edit não busca', async () => {
        const composer = makeComposer({ formExtra: { dia: new Date('2026-05-04T12:00:00'), startTime: '14:00' } });
        composer.isEdit.value = true;
        const { solicitacaoPendente } = setup({ composer });
        composer.form.value.startTime = '15:00';
        await nextTick();
        await new Promise((r) => setTimeout(r, 0));
        expect(solicitacaoPendente.value).toBeNull();
    });

    it('sem ownerId não busca', async () => {
        const composer = makeComposer({ formExtra: { dia: new Date('2026-05-04T12:00:00'), startTime: '14:00' } });
        const { solicitacaoPendente } = setup({ composer }, { ownerId: '' });
        composer.form.value.startTime = '15:00';
        await nextTick();
        await new Promise((r) => setTimeout(r, 0));
        expect(solicitacaoPendente.value).toBeNull();
    });
});

describe('watchers — online slots', () => {
    it('modalidade=online + dia + ownerId → carrega slots', async () => {
        const composer = makeComposer({ formExtra: { dia: new Date('2026-05-04T12:00:00'), modalidade: 'online' } });
        setResponse('agenda_online_slots', {
            data: [{ time: '14:00:00' }, { time: '15:00:00' }],
            error: null
        });
        const { onlineSlots } = setup({ composer });
        // immediate=true já disparou
        await new Promise((r) => setTimeout(r, 0));
        expect(onlineSlots.value).toEqual([{ hhmm: '14:00' }, { hhmm: '15:00' }]);
    });

    it('modalidade=presencial → zera slots', async () => {
        const composer = makeComposer({ formExtra: { dia: new Date('2026-05-04T12:00:00'), modalidade: 'presencial' } });
        const { onlineSlots } = setup({ composer });
        await new Promise((r) => setTimeout(r, 0));
        expect(onlineSlots.value).toEqual([]);
    });

    it('engole erro do supabase e zera lista', async () => {
        const composer = makeComposer({ formExtra: { dia: new Date('2026-05-04T12:00:00'), modalidade: 'online' } });
        // Não setamos response, mas vamos mockar o erro substituindo o builder
        // — usa simply o caminho try/catch via reject do `then`
        // Para esse caso, deixamos sem setResponse e ele retorna { data: null, error: null }
        // (sem dispara erro). Vou setar erro explicitamente:
        setResponse('agenda_online_slots', { data: null, error: new Error('boom') });
        const { onlineSlots } = setup({ composer });
        await new Promise((r) => setTimeout(r, 0));
        // Como erro vem em `error` mas o código não checa explicitamente — só faz
        // map sobre data || []. Logo, vai ser [] mesmo.
        expect(onlineSlots.value).toEqual([]);
    });
});

describe('watcher — modelValue init', () => {
    it('ao abrir, reseta refs e chama orchestration', async () => {
        const composer = makeComposer();
        const actions = makeActions();
        const pickerBilling = makePickerBilling();
        const loadInsurancePlans = vi.fn().mockResolvedValue();
        const serieValorMode = ref('dividir');
        const { propsRef } = setup({ composer, actions, pickerBilling, serieValorMode, loadInsurancePlans });

        propsRef.value.modelValue = true;
        // deixa watcher async correr
        await nextTick();
        await new Promise((r) => setTimeout(r, 0));
        await nextTick();

        // resets aplicados
        expect(composer.recorrenciaType.value).toBe('avulsa');
        expect(composer.diasSelecionados.value).toEqual([]);
        expect(composer.qtdSessoesMode.value).toBe('4');
        expect(composer.qtdSessoesCustom.value).toBe(12);
        expect(composer.editScope.value).toBe('somente_este');
        expect(serieValorMode.value).toBe('multiplicar');
        // step 1 (não-edit, sem preset)
        expect(composer.step.value).toBe(1);
        // ensureServicesLoaded chamado
        expect(pickerBilling.ensureServicesLoaded).toHaveBeenCalled();
        // billingType default novo evento
        expect(composer.billingType.value).toBe('particular');
        // _restoringConvenio resetado
        expect(actions._restoringConvenio.value).toBe(false);
    });

    it('com presetCommitmentId vai pra step=2 + commitment_id setado', async () => {
        const composer = makeComposer();
        const { propsRef } = setup({ composer });
        propsRef.value.presetCommitmentId = 'commit-99';
        propsRef.value.modelValue = true;
        await nextTick();
        await new Promise((r) => setTimeout(r, 0));
        await nextTick();
        expect(composer.form.value.commitment_id).toBe('commit-99');
        expect(composer.step.value).toBe(2);
    });

    it('em modo edit vai pra step=2 e chama _loadCommitmentItemsForEvent quando há id', async () => {
        const composer = makeComposer({ formExtra: { id: 'evt-7' } });
        composer.isEdit.value = true;
        composer.resetForm = vi.fn(() => ({ ...composer.form.value })); // mantém id
        const pickerBilling = makePickerBilling();
        const { propsRef } = setup({ composer, pickerBilling });
        propsRef.value.modelValue = true;
        await nextTick();
        await new Promise((r) => setTimeout(r, 0));
        await nextTick();
        expect(composer.step.value).toBe(2);
        expect(pickerBilling._loadCommitmentItemsForEvent).toHaveBeenCalledWith('evt-7');
    });

    it('hasSerie=true dispara loadSerieEvents (sem rid → fica []) ', async () => {
        const composer = makeComposer();
        composer.hasSerie.value = true;
        const { propsRef, serieEvents } = setup({ composer });
        propsRef.value.modelValue = true;
        await nextTick();
        await new Promise((r) => setTimeout(r, 0));
        // sem eventRow.recurrence_id, loadSerieEvents zera
        expect(serieEvents.value).toEqual([]);
    });

    it('quando close (modelValue=false) não faz nada', async () => {
        const composer = makeComposer();
        composer.recorrenciaType.value = 'semanal';
        const { propsRef } = setup({ composer });
        propsRef.value.modelValue = false;
        await nextTick();
        // recorrenciaType permanece como estava
        expect(composer.recorrenciaType.value).toBe('semanal');
    });
});
