<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/features/agenda/components/AgendaClinicMosaic.vue
| Data: 2026
| Local: São Carlos/SP — Brasil
|--------------------------------------------------------------------------
| © 2026 — Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { computed, ref, watch, nextTick } from 'vue';

import FullCalendar from '@fullcalendar/vue3';
import timeGridPlugin from '@fullcalendar/timegrid';
import dayGridPlugin from '@fullcalendar/daygrid';
import listPlugin from '@fullcalendar/list';
import interactionPlugin from '@fullcalendar/interaction';
import ptBrLocale from '@fullcalendar/core/locales/pt-br';
import { FC_TOUCH_DEFAULTS } from '@/features/agenda/utils/fcDefaults';

const props = defineProps({
    view: { type: String, default: 'day' }, // 'day' | 'week' | 'month'
    mode: { type: String, default: 'work_hours' }, // 'full_24h' | 'work_hours'
    timezone: { type: String, default: 'local' },

    slotDuration: { type: String, default: '00:15:00' },
    slotMinTime: { type: String, default: '06:00:00' },
    slotMaxTime: { type: String, default: '22:00:00' },

    // [{ id, title }]
    staff: { type: Array, default: () => [] },

    // todos os eventos (com extendedProps.owner_id)
    events: { type: Array, default: () => [] },

    loading: { type: Boolean, default: false },

    // largura mínima de cada coluna (terapeutas)
    minColWidth: { type: Number, default: 360 },

    // ✅ coluna da clínica
    showClinicColumn: { type: Boolean, default: true },
    clinicId: { type: String, default: '' },
    clinicTitle: { type: String, default: 'Clínica' },
    clinicSubtitle: { type: String, default: 'Agenda da clínica' },

    // subtitle terapeutas
    staffSubtitle: { type: String, default: 'Visão diária operacional' },

    // jornada por dia: [{ daysOfWeek:[n], startTime:'HH:MM', endTime:'HH:MM' }]
    businessHours: { type: Array, default: () => [] },

    // Array de ISO strings (yyyy-mm-dd) de dias totalmente bloqueados.
    // Exibe banner vermelho no topo de cada coluna (view "day") e fundo no calendário.
    blockedDates: { type: Array, default: () => [] }
});

const emit = defineEmits(['rangeChange', 'slotSelect', 'eventClick', 'eventDrop', 'eventResize', 'debugColumn']);

const calendarRefs = ref([]);
function setCalendarRef(el, idx) {
    if (!el) return;
    calendarRefs.value[idx] = el;
}

const initialView = computed(() => {
    if (props.view === 'week') return 'timeGridWeek';
    if (props.view === 'month') return 'dayGridMonth';
    return 'timeGridDay';
});

const computedSlotMinTime = computed(() => (props.mode === 'full_24h' ? '00:00:00' : props.slotMinTime));
const computedSlotMaxTime = computed(() => (props.mode === 'full_24h' ? '23:59:59' : props.slotMaxTime));

// ✅ coluna fixa (clínica)
const clinicColumn = computed(() => {
    if (!props.showClinicColumn) return null;
    const id = String(props.clinicId || '').trim();
    if (!id) return null;
    return { id, title: props.clinicTitle || 'Clínica', __kind: 'clinic' };
});

const staffColumns = computed(() => {
    const base = Array.isArray(props.staff) ? props.staff : [];
    return base.filter((s) => s?.id).map((s) => ({ ...s, __kind: 'staff' }));
});

function apiAt(idx) {
    const fc = calendarRefs.value[idx];
    return fc?.getApi?.();
}
function forEachApi(fn) {
    for (let i = 0; i < calendarRefs.value.length; i++) {
        const api = apiAt(i);
        if (api) fn(api, i);
    }
}

function goToday() {
    const d = new Date();
    d.setHours(12, 0, 0, 0);
    trackedDate = d;
    forEachApi((api) => api.today());
}
function prev() {
    trackedDate = null;
    forEachApi((api) => api.prev());
}
function next() {
    trackedDate = null;
    forEachApi((api) => api.next());
}
function gotoDate(date) {
    if (!date) return;
    const dt = date instanceof Date ? new Date(date) : new Date(date);
    dt.setHours(12, 0, 0, 0);
    trackedDate = new Date(dt);
    forEachApi((api) => api.gotoDate(dt));
}

function setView(v) {
    const target = v === 'week' ? 'timeGridWeek' : v === 'month' ? 'dayGridMonth' : v === 'list' ? 'listWeek' : 'timeGridDay';
    forEachApi((api) => api.changeView(target));
}
function setMode() {}

defineExpose({ goToday, prev, next, gotoDate, setView, setMode });

// ── Dias bloqueados ────────────────────────────────────────────
const blockedSet = computed(() => new Set(props.blockedDates || []));

// Eventos de background que colorem o dia inteiro de vermelho suave no FullCalendar
const blockedBgEvents = computed(() =>
    (props.blockedDates || []).map((iso) => ({
        id: `_blocked_bg_${iso}`,
        start: iso,
        allDay: true,
        display: 'background',
        color: 'rgba(239,68,68,0.13)',
        classNames: ['fc-blocked-day']
    }))
);

// ISO do dia sendo exibido atualmente — atualizado pelo datesSet
const currentViewISO = ref('');

// Retorna true se o dia exibido está bloqueado (banner só aparece na view "day")
function isCurrentDayBlocked() {
    return props.view === 'day' && !!currentViewISO.value && blockedSet.value.has(currentViewISO.value);
}

function eventsFor(ownerId) {
    const list = (props.events || []).filter((e) => String(e?.extendedProps?.owner_id || '') === String(ownerId || ''));
    return [...list, ...blockedBgEvents.value];
}

// ---- range sync ----
let lastRangeKey = '';
let suppressSync = false;
let trackedDate = null; // data-alvo atual (definida via gotoDate/goToday; limpa em prev/next)

function sameDay(a, b) {
    if (!a || !b) return false;
    return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
}

function onDatesSet(arg) {
    const cd = arg.view?.currentStart || arg.start;
    if (cd) {
        currentViewISO.value = `${cd.getFullYear()}-${String(cd.getMonth() + 1).padStart(2, '0')}-${String(cd.getDate()).padStart(2, '0')}`;
    }

    // Calendário recém-montado disparou datesSet com data antiga (ex: hoje) enquanto
    // a agenda já estava em outra data (trackedDate). Navega silenciosamente para a
    // data correta sem emitir rangeChange nem atualizar lastRangeKey.
    if (trackedDate && cd && !sameDay(cd, trackedDate)) {
        if (!suppressSync) {
            suppressSync = true;
            forEachApi((api) => {
                const cur = api.view?.currentStart;
                if (!cur || sameDay(cur, trackedDate)) return;
                api.gotoDate(trackedDate);
            });
            Promise.resolve().then(() => {
                suppressSync = false;
            });
        }
        return;
    }

    const key = `${arg.startStr}__${arg.endStr}__${arg.view?.type || ''}`;
    if (key === lastRangeKey) return;
    lastRangeKey = key;

    emit('rangeChange', {
        start: arg.start,
        end: arg.end,
        startStr: arg.startStr,
        endStr: arg.endStr,
        viewType: arg.view.type,
        currentDate: cd
    });

    if (suppressSync) return;
    suppressSync = true;

    const masterDate = cd;
    forEachApi((api) => {
        const cur = api.view?.currentStart;
        if (!cur || !masterDate) return;
        if (cur.getTime() !== masterDate.getTime()) api.gotoDate(masterDate);
    });

    Promise.resolve().then(() => {
        suppressSync = false;
    });
}

watch(
    () => props.view,
    async () => {
        await nextTick();
        setView(props.view);
    }
);

// ✅ Fix: watch combinado — evita render intermediário que colapsava labels de hora cheia
watch([computedSlotMinTime, computedSlotMaxTime], async ([minT, maxT]) => {
    await nextTick();
    forEachApi((api) => {
        api.setOption?.('slotMinTime', minT);
        api.setOption?.('slotMaxTime', maxT);
        api.updateSize?.();
    });
});

// ---------- helpers UI ----------
function colSubtitle(p) {
    return p?.__kind === 'clinic' ? props.clinicSubtitle : props.staffSubtitle;
}

// ✅ debug emitter (cabeçalho clicável)
function emitDebug(col) {
    emit('debugColumn', {
        staffCol: col,
        staffUserId: col?.id || null,
        staffTitle: col?.title || null,
        kind: col?.__kind || null,
        at: new Date().toISOString()
    });
}

function buildFcOptions(ownerId) {
    const base = {
        ...FC_TOUCH_DEFAULTS,
        plugins: [timeGridPlugin, dayGridPlugin, listPlugin, interactionPlugin],
        locale: ptBrLocale,
        timeZone: props.timezone,

        headerToolbar: false,
        initialView: initialView.value,

        nowIndicator: true,
        editable: true,
        selectable: true,
        selectMirror: true,

        slotDuration: props.slotDuration,
        snapDuration: '00:15:00',
        slotMinTime: computedSlotMinTime.value,
        slotMaxTime: computedSlotMaxTime.value,
        slotLabelInterval: '00:30',
        slotLabelContent: (arg) => {
            const min = arg.date.getMinutes();
            if (min === 0) {
                const h = String(arg.date.getHours()).padStart(2, '0');
                return { html: `<span class="fc-slot-label-hour">${h}:00</span>` };
            }
            return { html: `<span class="fc-slot-label-half">:${String(min).padStart(2, '0')}</span>` };
        },

        businessHours: props.businessHours,

        views: {
            timeGridDay: {
                dayHeaderFormat: { day: 'numeric', month: 'long', year: 'numeric' }
            }
        },

        height: 'auto',
        expandRows: true,
        allDaySlot: false,

        events: eventsFor(ownerId),
        datesSet: onDatesSet,

        eventClick: (info) => emit('eventClick', info),
        eventDrop: (info) => emit('eventDrop', info),
        eventResize: (info) => emit('eventResize', info),

        eventContent: (arg) => {
            const ext = arg.event.extendedProps || {};
            const avatarUrl = ext.paciente_avatar || '';
            const nome = ext.paciente_nome || '';
            const obs = ext.observacoes || '';
            const title = arg.event.title || '';
            const pacienteStatus = ext.paciente_status || '';

            const esc = (s) =>
                String(s ?? '')
                    .replace(/&/g, '&amp;')
                    .replace(/</g, '&lt;')
                    .replace(/>/g, '&gt;')
                    .replace(/"/g, '&quot;');

            const initials = (n) => {
                const p = String(n).trim().split(/\s+/).filter(Boolean);
                if (!p.length) return '?';
                if (p.length === 1) return p[0].slice(0, 2).toUpperCase();
                return (p[0][0] + p[p.length - 1][0]).toUpperCase();
            };

            const fmtHour = (d) => {
                if (!d) return '';
                const h = d.getHours();
                const m = d.getMinutes();
                return m === 0 ? `${h}h` : `${h}:${String(m).padStart(2, '0')}h`;
            };
            const range = arg.event.start && arg.event.end ? `${fmtHour(arg.event.start)}-${fmtHour(arg.event.end)}` : arg.timeText || '';

            const avatarHtml = avatarUrl ? `<img src="${esc(avatarUrl)}" class="ev-avatar ev-avatar-img" />` : nome ? `<div class="ev-avatar ev-avatar-initials">${esc(initials(nome))}</div>` : '';

            const obsHtml = obs ? `<div class="ev-obs">${esc(obs)}</div>` : '';
            const titleLine = `<div class="ev-title"><span class="ev-name">${esc(title)}</span>${range ? ` <span class="ev-hour">(${esc(range)})</span>` : ''}</div>`;
            const statusBadge =
                pacienteStatus === 'Inativo' || pacienteStatus === 'Arquivado'
                    ? `<span class="ev-badge">${pacienteStatus === 'Arquivado' ? 'paciente arquivado' : 'paciente desativado'}</span>`
                    : '';

            return {
                html: `<div class="ev-custom">
          ${avatarHtml}
          <div class="ev-body">
            ${titleLine}
            ${statusBadge}
            ${obsHtml}
          </div>
        </div>`
            };
        },

        eventClassNames: (arg) => {
            const classes = [];
            if (arg?.event?.backgroundColor) classes.push('evt-has-color');
            return classes;
        },

        eventDidMount: (info) => {
            const bgColor = info.event.extendedProps?.commitment_bg_color;
            if (bgColor) {
                info.el.style.setProperty('background-color', bgColor, 'important');
                info.el.style.setProperty('border-color', bgColor, 'important');
            }
        }
    };

    base.select = (selection) => {
        emit('slotSelect', {
            ownerId,
            start: selection.start,
            end: selection.end,
            startStr: selection.startStr,
            endStr: selection.endStr,
            jsEvent: selection.jsEvent || null,
            viewType: selection.view?.type || initialView.value
        });
    };

    return base;
}
</script>

<template>
    <div class="rounded-[1.5rem] border border-[var(--surface-border)] bg-[var(--surface-card)] overflow-hidden">
        <div v-if="loading" class="p-4 text-sm opacity-70 border-b border-[var(--surface-border)]">Carregando agenda da clínica…</div>

        <div class="mosaic-shell">
            <!-- Coluna fixa: Clínica -->
            <div v-if="clinicColumn" class="mosaic-fixed">
                <div class="mosaic-col">
                    <div class="mosaic-col-head cursor-pointer" @click="emitDebug(clinicColumn)" title="Debug desta coluna">
                        <div class="min-w-0">
                            <div class="font-semibold truncate">{{ clinicColumn.title }}</div>
                            <div class="text-xs opacity-70 truncate">{{ colSubtitle(clinicColumn) }}</div>
                        </div>
                        <div class="text-xs opacity-70 whitespace-nowrap">
                            {{ mode === 'full_24h' ? '24h' : 'Horário' }}
                        </div>
                    </div>

                    <!-- Banner: dia bloqueado — aparece apenas na view "day" -->
                    <div v-if="isCurrentDayBlocked()" class="mosaic-blocked-banner">
                        <i class="pi pi-lock text-xs" />
                        <span>Dia bloqueado — apenas compromissos de sessão não são permitidos</span>
                    </div>

                    <div class="p-2">
                        <FullCalendar :ref="(el) => setCalendarRef(el, 0)" :options="buildFcOptions(clinicColumn.id)" />
                    </div>
                </div>
            </div>

            <!-- Área rolável: Terapeutas -->
            <div class="mosaic-scroll">
                <div class="mosaic-grid" :style="{ gridAutoColumns: `minmax(${minColWidth}px, 1fr)` }">
                    <div v-for="(p, sIdx) in staffColumns" :key="p.id" class="mosaic-col">
                        <div class="mosaic-col-head cursor-pointer" @click="emitDebug(p)" title="Debug desta coluna">
                            <div class="min-w-0">
                                <div class="font-semibold truncate">{{ p.title }}</div>
                                <div class="text-xs opacity-70 truncate">{{ colSubtitle(p) }}</div>
                            </div>
                            <div class="text-xs opacity-70 whitespace-nowrap">
                                {{ mode === 'full_24h' ? '24h' : 'Horário' }}
                            </div>
                        </div>

                        <!-- Banner: dia bloqueado — aparece apenas na view "day" -->
                        <div v-if="isCurrentDayBlocked()" class="mosaic-blocked-banner">
                            <i class="pi pi-lock text-xs" />
                            <span>Dia bloqueado — apenas compromissos de sessão não são permitidos</span>
                        </div>

                        <div class="p-2">
                            <FullCalendar :ref="(el) => setCalendarRef(el, clinicColumn ? sIdx + 1 : sIdx)" :options="buildFcOptions(p.id)" />
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<style>
/* Evento customizado — unscoped pois é HTML injetado pelo FullCalendar */
.ev-custom {
    display: flex;
    align-items: flex-start;
    gap: 5px;
    overflow: hidden;
    padding: 1px 2px;
    height: 100%;
    width: 100%;
}
.ev-avatar {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    flex-shrink: 0;
    margin-top: 1px;
}
.ev-avatar-img {
    object-fit: cover;
}
.ev-avatar-initials {
    background: rgba(255, 255, 255, 0.25);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.5px;
}
.ev-body {
    min-width: 0;
    flex: 1;
    overflow: hidden;
}
.ev-title {
    font-size: 11px;
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ev-name {
    font-weight: 600;
}
.ev-hour {
    font-weight: 400;
    font-size: 10px;
    opacity: 0.75;
    margin-left: 2px;
}
.ev-badge {
    display: inline-block;
    background: #f97316;
    color: #fff;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    padding: 1px 5px;
    border-radius: 3px;
    line-height: 1.4;
    margin-top: 2px;
}
.ev-obs {
    font-size: 10px;
    opacity: 0.75;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-top: 1px;
}
</style>

<style scoped>
.mosaic-shell {
    display: flex;
    gap: 12px;
    padding: 8px;
}
@media (min-width: 768px) {
    .mosaic-shell {
        padding: 12px;
    }
}

.mosaic-fixed {
    flex: 0 0 auto;
    width: 420px;
    min-width: 320px;
    max-width: 460px;
}

.mosaic-scroll {
    flex: 1 1 auto;
    min-width: 0;
    overflow-x: auto;
}

.mosaic-grid {
    display: grid;
    grid-auto-flow: column;
    gap: 12px;
}

.mosaic-col {
    border-radius: 1.25rem;
    border: 1px solid var(--surface-border);
    background: color-mix(in srgb, var(--surface-card), transparent 12%);
    overflow: hidden;
}

.mosaic-col-head {
    padding: 12px;
    border-bottom: 1px solid var(--surface-border);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

/* Banner vermelho de dia bloqueado no topo de cada coluna */
.mosaic-blocked-banner {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 14px;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--red-700, #b91c1c);
    background: color-mix(in srgb, var(--red-400, #f87171) 15%, var(--surface-card));
    border-bottom: 1px solid color-mix(in srgb, var(--red-400, #f87171) 30%, transparent);
}
</style>

<style>
.fc-slot-label-hour {
    display: inline-block;
    font-size: 0.8rem;
    font-weight: 700;
    color: var(--text-color);
    letter-spacing: -0.01em;
    line-height: 1;
}
.fc-slot-label-half {
    display: inline-block;
    font-size: 0.68rem;
    font-weight: 400;
    color: var(--text-color-secondary);
    opacity: 0.5;
    line-height: 1;
    padding-left: 2px;
}
/* Garante opacidade total nos dias bloqueados (background event do FullCalendar) */
.fc-blocked-day {
    opacity: 1 !important;
}
</style>
