/**
 * useAgendaEventComposer.spec.js — A66 sub-sessão 1B
 *
 * Cobre o composable factory extraído do AgendaEventDialog.
 * Foco do contrato: refs reativos + computeds derivados são consistentes
 * com o comportamento original do .vue (matriz de inputs, edge cases).
 *
 * Não cobre: watchers e handlers (1C — fica no .vue ainda).
 */
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { ref, reactive } from 'vue';
import { useAgendaEventComposer } from '../useAgendaEventComposer';

// Mock de getPatientAgendaPermissions — o composable importa direto
vi.mock('@/composables/usePatientLifecycle', () => ({
    getPatientAgendaPermissions: (status) => {
        const norm = String(status || '').toLowerCase();
        if (norm === 'inativo' || norm === 'arquivado') {
            return { canCreateSession: false, canCreateRecurrence: false };
        }
        return { canCreateSession: true, canCreateRecurrence: true };
    }
}));

// Helper: monta props default + emit fake
function setup(overrides = {}, extras = {}) {
    const props = reactive({
        modelValue: false,
        eventRow: null,
        initialStartISO: '',
        initialEndISO: '',
        ownerId: 'owner-1',
        planOwnerId: '',
        allowOwnerEdit: false,
        ownerOptions: [],
        tenantId: 'tenant-1',
        commitmentOptions: [],
        presetCommitmentId: null,
        lockCommitment: false,
        restrictPatientsToOwner: false,
        patientScopeOwnerId: null,
        workRules: [],
        blockedDates: [],
        agendaSettings: { session_duration_min: 50, session_break_min: 0 },
        allEvents: [],
        pausasSemanais: [],
        feriados: [],
        newPatientRoute: '',
        ...overrides
    });
    const emitted = [];
    const emit = (...args) => emitted.push(args);
    const composer = useAgendaEventComposer(props, emit, extras);
    return { props, emit, emitted, composer };
}

const SESSION_COMMITMENT = { id: 'c-session', native_key: 'session', name: 'Sessão' };
const MEETING_COMMITMENT = { id: 'c-meeting', native_key: 'meeting', name: 'Reunião' };

// ════════════════════════════════════════════════════════════════════════
describe('visible (v-model)', () => {
    it('lê de props.modelValue', () => {
        const { composer, props } = setup({ modelValue: true });
        expect(composer.visible.value).toBe(true);
        props.modelValue = false;
        expect(composer.visible.value).toBe(false);
    });
    it('escrever emite update:modelValue', () => {
        const { composer, emitted } = setup();
        composer.visible.value = true;
        expect(emitted).toContainEqual(['update:modelValue', true]);
    });
});

describe('isEdit', () => {
    it('false sem eventRow', () => {
        const { composer } = setup({ eventRow: null });
        expect(composer.isEdit.value).toBe(false);
    });
    it('true com id', () => {
        const { composer } = setup({ eventRow: { id: 'evt-1' } });
        expect(composer.isEdit.value).toBe(true);
    });
    it('true com is_occurrence (sem id)', () => {
        const { composer } = setup({ eventRow: { is_occurrence: true } });
        expect(composer.isEdit.value).toBe(true);
    });
});

describe('allowBack', () => {
    it('true por default', () => {
        const { composer } = setup();
        expect(composer.allowBack.value).toBe(true);
    });
    it('false quando lockCommitment', () => {
        const { composer } = setup({ lockCommitment: true });
        expect(composer.allowBack.value).toBe(false);
    });
    it('false quando presetCommitmentId', () => {
        const { composer } = setup({ presetCommitmentId: 'c-1' });
        expect(composer.allowBack.value).toBe(false);
    });
});

describe('hasSerie', () => {
    it('false sem indicadores de série', () => {
        const { composer } = setup({ eventRow: { id: 'evt-1' } });
        expect(composer.hasSerie.value).toBe(false);
    });
    it('true com recurrence_id', () => {
        const { composer } = setup({ eventRow: { id: 'evt-1', recurrence_id: 'r-1' } });
        expect(composer.hasSerie.value).toBe(true);
    });
    it('true com serie_id (legado)', () => {
        const { composer } = setup({ eventRow: { id: 'evt-1', serie_id: 's-1' } });
        expect(composer.hasSerie.value).toBe(true);
    });
    it('true com is_occurrence', () => {
        const { composer } = setup({ eventRow: { is_occurrence: true } });
        expect(composer.hasSerie.value).toBe(true);
    });
});

describe('isFirstOccurrence', () => {
    it('false quando não é série', () => {
        const { composer } = setup();
        expect(composer.isFirstOccurrence.value).toBe(false);
    });
    it('true quando recurrence_date é a menor data da série', () => {
        const serieEvents = ref([
            { recurrence_date: '2026-05-15' },
            { recurrence_date: '2026-05-22' },
            { recurrence_date: '2026-05-29' }
        ]);
        const { composer } = setup(
            { eventRow: { id: 'evt-1', recurrence_id: 'r-1', recurrence_date: '2026-05-15' } },
            { serieEvents }
        );
        expect(composer.isFirstOccurrence.value).toBe(true);
    });
    it('false quando recurrence_date NÃO é a menor', () => {
        const serieEvents = ref([
            { recurrence_date: '2026-05-15' },
            { recurrence_date: '2026-05-22' }
        ]);
        const { composer } = setup(
            { eventRow: { id: 'evt-1', recurrence_id: 'r-1', recurrence_date: '2026-05-22' } },
            { serieEvents }
        );
        expect(composer.isFirstOccurrence.value).toBe(false);
    });
    it('false quando serieEvents vazio', () => {
        const { composer } = setup(
            { eventRow: { id: 'evt-1', recurrence_id: 'r-1', recurrence_date: '2026-05-15' } },
            { serieEvents: ref([]) }
        );
        expect(composer.isFirstOccurrence.value).toBe(false);
    });
});

describe('editScopeOptions', () => {
    it('retorna 3 opções (todos_sem_excecao removido da UI em 2026-05-12)', () => {
        const { composer } = setup();
        expect(composer.editScopeOptions.value).toHaveLength(3);
        expect(composer.editScopeOptions.value.map((o) => o.value)).toEqual(['somente_este', 'este_e_seguintes', 'todos']);
    });
    it('"este_e_seguintes" disabled quando isFirstOccurrence', () => {
        const serieEvents = ref([{ recurrence_date: '2026-05-15' }, { recurrence_date: '2026-05-22' }]);
        const { composer } = setup(
            { eventRow: { id: 'evt-1', recurrence_id: 'r-1', recurrence_date: '2026-05-15' } },
            { serieEvents }
        );
        const opt = composer.editScopeOptions.value.find((o) => o.value === 'este_e_seguintes');
        expect(opt.disabled).toBe(true);
    });
});

describe('qtdSessoesEfetiva', () => {
    it('valores fixos pelo mode', () => {
        const { composer } = setup();
        composer.qtdSessoesMode.value = '4';
        expect(composer.qtdSessoesEfetiva.value).toBe(4);
        composer.qtdSessoesMode.value = '8';
        expect(composer.qtdSessoesEfetiva.value).toBe(8);
        composer.qtdSessoesMode.value = '12';
        expect(composer.qtdSessoesEfetiva.value).toBe(12);
    });
    it('"personalizar" usa qtdSessoesCustom', () => {
        const { composer } = setup();
        composer.qtdSessoesMode.value = 'personalizar';
        composer.qtdSessoesCustom.value = 24;
        expect(composer.qtdSessoesEfetiva.value).toBe(24);
    });
    it('clampa em 1 quando custom é 0/null', () => {
        const { composer } = setup();
        composer.qtdSessoesMode.value = 'personalizar';
        composer.qtdSessoesCustom.value = 0;
        expect(composer.qtdSessoesEfetiva.value).toBe(1);
        composer.qtdSessoesCustom.value = null;
        expect(composer.qtdSessoesEfetiva.value).toBe(1);
    });
});

describe('proximasOcorrencias', () => {
    function asISODate(d) {
        return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
    }

    it('retorna [] quando avulsa', () => {
        const { composer } = setup();
        composer.recorrenciaType.value = 'avulsa';
        composer.form.value.dia = new Date('2026-05-15');
        expect(composer.proximasOcorrencias.value).toEqual([]);
    });

    it('semanal: 4 datas separadas por 7 dias', () => {
        const { composer } = setup();
        composer.recorrenciaType.value = 'semanal';
        composer.qtdSessoesMode.value = '4';
        composer.form.value.dia = new Date('2026-05-15T10:00:00');
        const list = composer.proximasOcorrencias.value;
        expect(list).toHaveLength(4);
        expect(asISODate(list[0])).toBe('2026-05-15');
        expect(asISODate(list[1])).toBe('2026-05-22');
        expect(asISODate(list[2])).toBe('2026-05-29');
        expect(asISODate(list[3])).toBe('2026-06-05');
    });

    it('quinzenal: separação 14 dias', () => {
        const { composer } = setup();
        composer.recorrenciaType.value = 'quinzenal';
        composer.qtdSessoesMode.value = '4';
        composer.form.value.dia = new Date('2026-05-15T10:00:00');
        const list = composer.proximasOcorrencias.value;
        expect(list).toHaveLength(4);
        expect(asISODate(list[1])).toBe('2026-05-29');
        expect(asISODate(list[2])).toBe('2026-06-12');
    });

    it('diasEspecificos: respeita dias selecionados', () => {
        const { composer } = setup();
        composer.recorrenciaType.value = 'diasEspecificos';
        composer.qtdSessoesMode.value = '4';
        composer.diasSelecionados.value = [1, 3]; // segunda + quarta
        composer.form.value.dia = new Date('2026-05-18T10:00:00'); // segunda
        const list = composer.proximasOcorrencias.value;
        expect(list).toHaveLength(4);
        // 18 (seg) - 20 (qua) - 25 (seg) - 27 (qua)
        expect(list[0].getDay()).toBe(1);
        expect(list[1].getDay()).toBe(3);
        expect(list[2].getDay()).toBe(1);
        expect(list[3].getDay()).toBe(3);
    });

    it('diasEspecificos com array vazio retorna []', () => {
        const { composer } = setup();
        composer.recorrenciaType.value = 'diasEspecificos';
        composer.diasSelecionados.value = [];
        composer.form.value.dia = new Date('2026-05-15');
        expect(composer.proximasOcorrencias.value).toEqual([]);
    });
});

describe('toggleDiaSelecionado', () => {
    it('adiciona se não existe, remove se existe', () => {
        const { composer } = setup();
        composer.toggleDiaSelecionado(1);
        expect(composer.diasSelecionados.value).toEqual([1]);
        composer.toggleDiaSelecionado(3);
        expect(composer.diasSelecionados.value).toEqual([1, 3]);
        composer.toggleDiaSelecionado(1);
        expect(composer.diasSelecionados.value).toEqual([3]);
    });
});

describe('isForaDoPlano (com dataLimiteManual)', () => {
    it('false quando dataLimiteManual null', () => {
        const { composer } = setup();
        expect(composer.isForaDoPlano(new Date('2026-12-31'))).toBe(false);
    });
    it('true quando data > limite', () => {
        const { composer } = setup();
        composer.dataLimiteManual.value = '2026-06-30';
        expect(composer.isForaDoPlano(new Date('2026-12-31'))).toBe(true);
        expect(composer.isForaDoPlano(new Date('2026-06-29'))).toBe(false);
    });
});

describe('commitmentCards', () => {
    it('coloca native_key="session" primeiro', () => {
        const { composer } = setup({
            commitmentOptions: [MEETING_COMMITMENT, SESSION_COMMITMENT, { id: 'c-3', name: 'Avaliação' }]
        });
        const cards = composer.commitmentCards.value;
        // commitmentOptions vem via reactive(props) — itens viram proxies,
        // por isso comparamos id (toBe) em vez de identidade do objeto.
        expect(cards[0].id).toBe('c-session');
    });
    it('ordem alfabética entre não-session', () => {
        const { composer } = setup({
            commitmentOptions: [
                { id: 'c-1', name: 'Zeta' },
                { id: 'c-2', name: 'Alpha' },
                { id: 'c-3', name: 'Beta' }
            ]
        });
        const cards = composer.commitmentCards.value;
        expect(cards.map((c) => c.name)).toEqual(['Alpha', 'Beta', 'Zeta']);
    });
});

describe('selectedCommitment + relacionados', () => {
    it('selectedCommitment encontra pelo form.commitment_id', () => {
        const { composer } = setup({
            commitmentOptions: [SESSION_COMMITMENT, MEETING_COMMITMENT]
        });
        composer.form.value.commitment_id = 'c-meeting';
        expect(composer.selectedCommitment.value?.id).toBe('c-meeting');
    });
    it('selectedCommitmentName fallback "—" quando null', () => {
        const { composer } = setup();
        expect(composer.selectedCommitmentName.value).toBe('—');
    });
    it('requiresPatient true quando native_key=session', () => {
        const { composer } = setup({ commitmentOptions: [SESSION_COMMITMENT] });
        composer.form.value.commitment_id = 'c-session';
        expect(composer.requiresPatient.value).toBe(true);
        expect(composer.isSessionEvent.value).toBe(true);
    });
    it('requiresPatient false pra outros commitments', () => {
        const { composer } = setup({ commitmentOptions: [MEETING_COMMITMENT] });
        composer.form.value.commitment_id = 'c-meeting';
        expect(composer.requiresPatient.value).toBe(false);
    });
    it('patientLocked true só na edição de sessão com paciente', () => {
        const { composer } = setup({
            commitmentOptions: [SESSION_COMMITMENT],
            eventRow: { id: 'evt-1', paciente_id: 'p-1' }
        });
        composer.form.value.commitment_id = 'c-session';
        expect(composer.patientLocked.value).toBe(true);
    });
    it('hasInsurance reage a form.insurance_plan_id', () => {
        const { composer } = setup();
        expect(composer.hasInsurance.value).toBe(false);
        composer.form.value.insurance_plan_id = 'plan-1';
        expect(composer.hasInsurance.value).toBe(true);
    });
});

describe('agendaPerms', () => {
    it('Inativo bloqueia create', () => {
        const { composer } = setup();
        composer.form.value.paciente_status = 'Inativo';
        expect(composer.agendaPerms.value.canCreateSession).toBe(false);
    });
    it('Ativo permite tudo', () => {
        const { composer } = setup();
        composer.form.value.paciente_status = 'Ativo';
        expect(composer.agendaPerms.value.canCreateSession).toBe(true);
        expect(composer.agendaPerms.value.canCreateRecurrence).toBe(true);
    });
});

describe('isSessionFuture / isArchivedPastEdit / isInativoFutureEdit', () => {
    let mockNow;
    beforeEach(() => {
        mockNow = new Date('2026-05-15T12:00:00').getTime();
        vi.useFakeTimers();
        vi.setSystemTime(mockNow);
    });

    it('isSessionFuture true quando criando (não-edit)', () => {
        const { composer } = setup({ eventRow: null });
        expect(composer.isSessionFuture.value).toBe(true);
    });
    it('isSessionFuture true quando edit + sessão futura', () => {
        const { composer } = setup({
            eventRow: { id: 'evt-1', inicio_em: '2026-05-20T10:00:00' }
        });
        expect(composer.isSessionFuture.value).toBe(true);
    });
    it('isSessionFuture false quando edit + sessão passada', () => {
        const { composer } = setup({
            eventRow: { id: 'evt-1', inicio_em: '2026-05-10T10:00:00' }
        });
        expect(composer.isSessionFuture.value).toBe(false);
    });
    it('isArchivedPastEdit true: edit + Arquivado + passada', () => {
        const { composer } = setup({
            eventRow: { id: 'evt-1', inicio_em: '2026-05-10T10:00:00' }
        });
        composer.form.value.paciente_status = 'Arquivado';
        expect(composer.isArchivedPastEdit.value).toBe(true);
    });
    it('isInativoFutureEdit true: edit + Inativo + futura', () => {
        const { composer } = setup({
            eventRow: { id: 'evt-1', inicio_em: '2026-05-20T10:00:00' }
        });
        composer.form.value.paciente_status = 'Inativo';
        expect(composer.isInativoFutureEdit.value).toBe(true);
    });
});

describe('inicioDateTime / fimDateTime', () => {
    it('null quando dia ou startTime ausentes', () => {
        const { composer } = setup();
        composer.form.value.dia = null;
        expect(composer.inicioDateTime.value).toBe(null);
    });
    it('combina dia + startTime corretamente', () => {
        const { composer } = setup();
        composer.form.value.dia = new Date('2026-05-15T00:00:00');
        composer.form.value.startTime = '14:30';
        const ini = composer.inicioDateTime.value;
        expect(ini.getHours()).toBe(14);
        expect(ini.getMinutes()).toBe(30);
    });
    it('fimDateTime adiciona duracaoMin', () => {
        const { composer } = setup();
        composer.form.value.dia = new Date('2026-05-15T00:00:00');
        composer.form.value.startTime = '14:00';
        composer.form.value.duracaoMin = 50;
        const fim = composer.fimDateTime.value;
        expect(fim.getHours()).toBe(14);
        expect(fim.getMinutes()).toBe(50);
    });
});

describe('startTimeDate (computed get/set)', () => {
    it('getter null quando startTime null', () => {
        const { composer } = setup();
        composer.form.value.startTime = null;
        expect(composer.startTimeDate.value).toBe(null);
    });
    it('getter retorna Date com hora setada', () => {
        const { composer } = setup();
        composer.form.value.startTime = '09:30';
        const d = composer.startTimeDate.value;
        expect(d.getHours()).toBe(9);
        expect(d.getMinutes()).toBe(30);
    });
    it('setter atualiza form.startTime no formato HH:MM', () => {
        const { composer } = setup();
        const d = new Date();
        d.setHours(15, 5, 0, 0);
        composer.startTimeDate.value = d;
        expect(composer.form.value.startTime).toBe('15:05');
    });
    it('setter null limpa form.startTime', () => {
        const { composer } = setup();
        composer.form.value.startTime = '14:00';
        composer.startTimeDate.value = null;
        expect(composer.form.value.startTime).toBe(null);
    });
});

describe('canSave (matriz de validação — núcleo)', () => {
    function ready(composer) {
        composer.form.value.owner_id = 'owner-1';
        composer.form.value.dia = new Date('2026-05-15');
        composer.form.value.startTime = '14:00';
        composer.form.value.commitment_id = 'c-meeting';
        composer.form.value.duracaoMin = 50;
    }

    it('false sem owner_id', () => {
        const { composer } = setup({ commitmentOptions: [MEETING_COMMITMENT] });
        ready(composer);
        composer.form.value.owner_id = '';
        expect(composer.canSave.value).toBe(false);
    });
    it('false sem dia', () => {
        const { composer } = setup({ commitmentOptions: [MEETING_COMMITMENT] });
        ready(composer);
        composer.form.value.dia = null;
        expect(composer.canSave.value).toBe(false);
    });
    it('false sem startTime', () => {
        const { composer } = setup({ commitmentOptions: [MEETING_COMMITMENT] });
        ready(composer);
        composer.form.value.startTime = null;
        expect(composer.canSave.value).toBe(false);
    });
    it('false ao criar sem commitment_id', () => {
        const { composer } = setup({ commitmentOptions: [MEETING_COMMITMENT] });
        ready(composer);
        composer.form.value.commitment_id = null;
        expect(composer.canSave.value).toBe(false);
    });
    it('true ao EDITAR sem commitment_id (sessões antigas)', () => {
        const { composer } = setup({
            commitmentOptions: [MEETING_COMMITMENT],
            eventRow: { id: 'evt-1', inicio_em: '2026-05-15T14:00:00' }
        });
        ready(composer);
        composer.form.value.commitment_id = null; // legacy null
        expect(composer.canSave.value).toBe(true);
    });
    it('false em sessão sem paciente_id quando requiresPatient', () => {
        const { composer } = setup({ commitmentOptions: [SESSION_COMMITMENT] });
        ready(composer);
        composer.form.value.commitment_id = 'c-session';
        composer.form.value.paciente_id = null;
        expect(composer.canSave.value).toBe(false);
    });
    it('false em sessão particular sem itens de billing', () => {
        const items = ref([]);
        const { composer } = setup(
            { commitmentOptions: [SESSION_COMMITMENT] },
            { commitmentItems: items }
        );
        ready(composer);
        composer.form.value.commitment_id = 'c-session';
        composer.form.value.paciente_id = 'p-1';
        composer.billingType.value = 'particular';
        expect(composer.canSave.value).toBe(false);
    });
    it('true em sessão particular COM itens', () => {
        const items = ref([{ id: 'i-1' }]);
        const { composer } = setup(
            { commitmentOptions: [SESSION_COMMITMENT] },
            { commitmentItems: items }
        );
        ready(composer);
        composer.form.value.commitment_id = 'c-session';
        composer.form.value.paciente_id = 'p-1';
        composer.billingType.value = 'particular';
        expect(composer.canSave.value).toBe(true);
    });
    it('false ao criar sessão pra paciente Inativo', () => {
        const items = ref([{ id: 'i-1' }]);
        const { composer } = setup(
            { commitmentOptions: [SESSION_COMMITMENT] },
            { commitmentItems: items }
        );
        ready(composer);
        composer.form.value.commitment_id = 'c-session';
        composer.form.value.paciente_id = 'p-1';
        composer.form.value.paciente_status = 'Inativo';
        composer.billingType.value = 'particular';
        expect(composer.canSave.value).toBe(false);
    });
    it('false ao criar recorrência pra paciente Arquivado', () => {
        const items = ref([{ id: 'i-1' }]);
        const { composer } = setup(
            { commitmentOptions: [SESSION_COMMITMENT] },
            { commitmentItems: items }
        );
        ready(composer);
        composer.form.value.commitment_id = 'c-session';
        composer.form.value.paciente_id = 'p-1';
        composer.form.value.paciente_status = 'Arquivado';
        composer.billingType.value = 'particular';
        composer.recorrenciaType.value = 'semanal';
        expect(composer.canSave.value).toBe(false);
    });
    it('true em billing convenio sem precisar de itens', () => {
        const items = ref([]);
        const { composer } = setup(
            { commitmentOptions: [SESSION_COMMITMENT] },
            { commitmentItems: items }
        );
        ready(composer);
        composer.form.value.commitment_id = 'c-session';
        composer.form.value.paciente_id = 'p-1';
        composer.billingType.value = 'convenio';
        expect(composer.canSave.value).toBe(true);
    });
});

describe('timeConflict (pré-check antes do PATCH)', () => {
    function readyForm(composer) {
        composer.form.value.dia = new Date('2026-05-15T00:00:00');
        composer.form.value.startTime = '14:00';
        composer.form.value.duracaoMin = 50;
    }

    it('null quando form ainda incompleto', () => {
        const { composer } = setup();
        composer.form.value.dia = null;
        expect(composer.timeConflict.value).toBe(null);
    });
    it('null sem allEvents', () => {
        const { composer } = setup({ allEvents: [] });
        readyForm(composer);
        expect(composer.timeConflict.value).toBe(null);
    });
    it('detecta overlap com evento existente', () => {
        const { composer } = setup({
            allEvents: [{
                id: 'evt-x',
                inicio_em: '2026-05-15T14:30:00',
                fim_em: '2026-05-15T15:20:00',
                paciente_nome: 'Maria'
            }]
        });
        readyForm(composer);
        expect(composer.timeConflict.value).toMatch(/Maria/);
    });
    it('NÃO detecta overlap com o próprio evento (form.id === evt.id)', () => {
        const { composer } = setup({
            allEvents: [{
                id: 'self',
                inicio_em: '2026-05-15T14:00:00',
                fim_em: '2026-05-15T14:50:00'
            }]
        });
        readyForm(composer);
        composer.form.value.id = 'self';
        expect(composer.timeConflict.value).toBe(null);
    });
    it('null quando evento adjacente (sem overlap real)', () => {
        const { composer } = setup({
            allEvents: [{
                id: 'evt-x',
                inicio_em: '2026-05-15T15:00:00',
                fim_em: '2026-05-15T15:50:00'
            }]
        });
        readyForm(composer);
        composer.form.value.duracaoMin = 50; // 14:00–14:50
        expect(composer.timeConflict.value).toBe(null);
    });
    it('detecta sobreposição com pausa do dia', () => {
        const { composer } = setup({
            pausasSemanais: [{
                dia_semana: 5, // sexta (2026-05-15)
                hora_inicio: '14:30',
                hora_fim: '15:00'
            }]
        });
        readyForm(composer);
        expect(composer.timeConflict.value).toMatch(/pausa/i);
    });
});

describe('totalConflitos / sessoesForaDoPlano', () => {
    it('totalConflitos conta ocorrências com folga/feriado/bloqueado/pausa', () => {
        const { composer } = setup({
            workRules: [
                { dia_semana: 1 }, { dia_semana: 2 }, { dia_semana: 3 },
                { dia_semana: 4 }, { dia_semana: 5 } // seg-sex
            ],
            blockedDates: ['2026-05-22']
        });
        composer.recorrenciaType.value = 'semanal';
        composer.qtdSessoesMode.value = '4';
        composer.form.value.dia = new Date('2026-05-15T10:00:00'); // sexta
        // 15 (sex ok) - 22 (sex bloqueada) - 29 (sex ok) - 5/jun (sex ok)
        expect(composer.totalConflitos.value).toBe(1);
    });
    it('sessoesForaDoPlano com dataLimiteManual', () => {
        const { composer } = setup();
        composer.recorrenciaType.value = 'semanal';
        composer.qtdSessoesMode.value = '4';
        composer.form.value.dia = new Date('2026-05-15T10:00:00');
        composer.dataLimiteManual.value = '2026-05-22';
        // ocorrências carregam hora 10:00 (de form.dia); limite "2026-05-22"
        // vira meia-noite local. Logo 22/5 10:00 > 22/5 00:00 → fora.
        // Resultado: 15 (dentro), 22 (fora), 29 (fora), 5/jun (fora) = 3
        expect(composer.sessoesForaDoPlano.value).toBe(3);
    });
});

describe('headerTitle', () => {
    it('"Editar compromisso" quando isEdit', () => {
        const { composer } = setup({ eventRow: { id: 'evt-1' } });
        expect(composer.headerTitle.value).toBe('Editar compromisso');
    });
    it('"Novo compromisso — escolha o tipo" no step 1', () => {
        const { composer } = setup();
        composer.step.value = 1;
        expect(composer.headerTitle.value).toMatch(/escolha o tipo/);
    });
    it('"Novo compromisso" no step 2', () => {
        const { composer } = setup();
        composer.step.value = 2;
        expect(composer.headerTitle.value).toBe('Novo compromisso');
    });
});

describe('computedTitulo', () => {
    it('usa titulo_custom quando preenchido', () => {
        const { composer } = setup();
        composer.form.value.titulo_custom = 'Minha custom';
        expect(composer.computedTitulo.value).toBe('Minha custom');
    });
    it('"—" quando não há commitment selecionado (selectedCommitmentName fallback)', () => {
        // selectedCommitmentName retorna "—" quando sem commitment, então
        // computedTitulo herda esse "—" porque "—" || "Compromisso" → "—".
        // Comportamento original do .vue preservado.
        const { composer } = setup();
        expect(composer.computedTitulo.value).toBe('—');
    });
    it('combina nome paciente + commitment quando session', () => {
        const { composer } = setup({ commitmentOptions: [SESSION_COMMITMENT] });
        composer.form.value.commitment_id = 'c-session';
        composer.form.value.paciente_nome = 'Ana';
        expect(composer.computedTitulo.value).toBe('Ana [Sessão]');
    });
});
