/*
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Arquivo: src/features/agenda/composables/useAgendaEventComposer.js
| Data: 2026-05-04
|
| Composable factory do AgendaEventDialog — A66 sub-sessão 1B.
|
| Responsabilidade: state + computeds derivados que NÃO dependem de
| efeitos colaterais (sem watchers, sem I/O, sem confirm dialogs). Tudo
| reativo, mas sem side-effects. Watchers e handlers ficam no .vue (1C).
|
| Estrutura:
|   1. Visibility (v-model do Dialog)
|   2. Step + edit scope state
|   3. Recurrence state (avulsa/semanal/quinzenal/diasEspecificos)
|   4. Form factory (resetForm) + form ref
|   5. Computeds derivados de props/state:
|      - série (hasSerie, isFirstOccurrence, editScopeOptions, ...)
|      - recorrência (proximasOcorrencias, ocorrenciasComConflito, ...)
|      - commitment (commitmentCards, selectedCommitment, requiresPatient, ...)
|      - permissions (agendaPerms, isArchivedPastEdit, ...)
|      - datetime (inicioDateTime, fimDateTime, previewRange, ...)
|      - validação (canSave, timeConflict)
|
| Não recebe refs externos (services, insurancePlans, etc) — esses ficam
| no .vue até 1C onde os watchers consomem-os.
|
| commitmentItems é passado via `extras` porque é um array reativo do
| .vue usado em canSave (validação de billing particular).
|
| serieEvents idem — usado em isFirstOccurrence.
|--------------------------------------------------------------------------
*/
import { ref, computed } from 'vue';
import { getPatientAgendaPermissions } from '@/composables/usePatientLifecycle';
import {
    isNativeSession,
    isForaDoPlano as _isForaDoPlanoPure,
    isoToHHMM,
    addMinutesDate,
    fmtTime,
    fmtDateBR,
    calcMinutes
} from './agendaEventHelpers';

export function useAgendaEventComposer(props, emit, extras = {}) {
    // Refs externos consumidos pelo composer.
    // Default = ref([]) pra que o composable seja testável sem que
    // o caller precise sempre passar — em produção, .vue passa os
    // refs reais (`commitmentItems`, `serieEvents`).
    const commitmentItems = extras.commitmentItems ?? ref([]);
    const serieEvents     = extras.serieEvents ?? ref([]);

    // ── 1. Visibilidade (v-model:visible) ──────────────────────────
    const visible = computed({
        get: () => props.modelValue,
        set: (v) => emit('update:modelValue', v)
    });

    // ── 2. Step + edit ─────────────────────────────────────────────
    const step = ref(1);
    const isEdit = computed(() => !!props.eventRow?.id || !!props.eventRow?.is_occurrence);
    const allowBack = computed(() => !props.lockCommitment && !props.presetCommitmentId);

    // ── 3. Série (eventRow já carrega; isFirstOccurrence usa serieEvents)
    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;
        const list = serieEvents.value;
        if (list?.length) {
            const dates = list
                .map((e) => e.recurrence_date || e.original_date)
                .filter(Boolean)
                .sort();
            return dates[0] === rDate;
        }
        return false;
    });

    // 'todos_sem_excecao' removido da UI em 2026-05-12 — padrao SimplePractice
    // nao expoe override de customizacoes (e destrutivo e raro). Backend ainda
    // suporta caso outro fluxo precise, mas dialog so oferece os 3 escopos
    // padrao do mercado.
    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' }
    ]);

    // ── 4. Recorrência (criação) ───────────────────────────────────
    const recorrenciaType = ref('avulsa');
    const diasSelecionados = ref([]);
    const qtdSessoesMode = ref('4');
    const qtdSessoesCustom = ref(12);
    const dataLimiteManual = ref(null);

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

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

    // ── 5. Form factory + form ref ─────────────────────────────────
    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
        };
    }

    const form = ref(resetForm());

    // ── 6. Recorrência computeds (usam form.dia + state) ──────────
    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 stepDays = 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() + stepDays);
            }
        } 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);

    function isForaDoPlano(d) {
        return _isForaDoPlanoPure(d, dataLimiteManual.value);
    }
    const sessoesForaDoPlano = computed(() =>
        proximasOcorrencias.value.filter((d) => isForaDoPlano(d)).length
    );

    // Conflito por data: folga | feriado | bloqueado | pausa
    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')}`;

        if (props.workRules?.length && !props.workRules.some((r) => Number(r.dia_semana) === dow)) {
            return { type: 'folga', label: 'dia de folga' };
        }
        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 || ''}` };
        if ((props.blockedDates || []).includes(iso)) {
            return { type: 'bloqueado', label: 'dia bloqueado' };
        }
        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);

    // ── 7. Billing state ───────────────────────────────────────────
    const billingType = ref('particular');

    // ── 8. Commitment computeds ────────────────────────────────────
    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');
        });
    });

    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);
    const patientLocked = computed(() => isEdit.value && isSessionEvent.value && !!props.eventRow?.paciente_id);
    const hasInsurance = computed(() => !!form.value.insurance_plan_id);

    // ── 9. Permissions (status do paciente) ────────────────────────
    const agendaPerms = computed(() => getPatientAgendaPermissions(form.value.paciente_status || ''));
    const isSessionFuture = computed(() => {
        if (!isEdit.value) return true;
        const iso = props.eventRow?.inicio_em;
        return iso ? new Date(iso) > new Date() : true;
    });
    const isArchivedPastEdit = computed(
        () => isEdit.value && form.value.paciente_status === 'Arquivado' && !isSessionFuture.value
    );
    const isInativoFutureEdit = computed(
        () => isEdit.value && form.value.paciente_status === 'Inativo' && isSessionFuture.value
    );
    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 }
    ]);

    // ── 10. Datetime ───────────────────────────────────────────────
    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)}`;
    });

    // ── 11. Título ─────────────────────────────────────────────────
    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 headerTitle = computed(() => {
        if (isEdit.value) return 'Editar compromisso';
        return step.value === 1 ? 'Novo compromisso — escolha o tipo' : 'Novo compromisso';
    });

    // ── 12. Validação (canSave) ────────────────────────────────────
    // Núcleo do business logic: cobre 6 categorias de check pra
    // habilitar o botão Salvar. Testado isoladamente em 1B.
    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 (sessões antigas no DB
        // podem ter null — não bloquear edição por isso)
        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;
            if (!isEdit.value && !perms.canCreateSession) return false;
            if (!isEdit.value && recorrenciaType.value !== 'avulsa' && !perms.canCreateRecurrence) return false;
            if (isArchivedPastEdit.value) return false;
            // Edição de sessão FUTURA: bloqueia se status não permite remarcação.
            // Pra Inativo => canReschedule=false => bloqueia (antes só tinha aviso
            // visual "Remarcação bloqueada" que mentia: o save acontecia mesmo
            // assim, pq canReschedule era dead code). Pra Arquivado canReschedule
            // continua true (futura editável, vide aviso info no template).
            if (isEdit.value && isSessionFuture.value && !perms.canReschedule) return false;
        }
        return true;
    });

    // ── 13. Conflito de horário (timeConflict) ─────────────────────
    // Pre-check client-side ANTES do PATCH ir pro DB. Sem isso, o constraint
    // EXCLUDE do Postgres dispara 400 no console toda vez que o user salva
    // num horário ocupado.
    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;
    });

    return {
        // refs
        visible,
        step,
        editScope,
        recorrenciaType,
        diasSelecionados,
        qtdSessoesMode,
        qtdSessoesCustom,
        dataLimiteManual,
        billingType,
        form,
        // edit/série computeds
        isEdit,
        allowBack,
        hasSerie,
        currentRecurrenceDate,
        isFirstOccurrence,
        editScopeOptions,
        // recorrência computeds
        qtdSessoesEfetiva,
        diaSemanaRecorrencia,
        proximasOcorrencias,
        dataFimCalculada,
        totalOcorrencias,
        sessoesForaDoPlano,
        ocorrenciasComConflito,
        totalConflitos,
        // commitment computeds
        commitmentCards,
        selectedCommitment,
        selectedCommitmentName,
        selectedCommitmentFields,
        requiresPatient,
        isSessionEvent,
        patientLocked,
        hasInsurance,
        // permissions computeds
        agendaPerms,
        isSessionFuture,
        isArchivedPastEdit,
        isInativoFutureEdit,
        statusOptionsFiltered,
        // datetime computeds
        startTimeDate,
        inicioDateTime,
        fimDateTime,
        dataHoraDisplay,
        previewRange,
        // título computeds
        computedTitulo,
        headerTitle,
        // validação
        canSave,
        timeConflict,
        // métodos
        toggleDiaSelecionado,
        isForaDoPlano,
        conflictForDate,
        resetForm
    };
}
