<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/views/pages/therapist/TherapistDashboard.vue
| Data: 2026
| Local: São Carlos/SP — Brasil
|--------------------------------------------------------------------------
| © 2026 — Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue';
import { useRouter } from 'vue-router';
import { useLayout } from '@/layout/composables/layout';
import { useToast } from 'primevue/usetoast';
import Menu from 'primevue/menu';
import { supabase } from '@/lib/supabase/client';
import { tenantDb } from '@/lib/supabase/tenantClient';
import { useTenantStore } from '@/stores/tenantStore';
import { useAgendaEvents } from '@/features/agenda/composables/useAgendaEvents';
import AgendaEventDialog from '@/features/agenda/components/AgendaEventDialog.vue';
import { useDeterminedCommitments } from '@/features/agenda/composables/useDeterminedCommitments';
import PatientProntuario from '@/features/patients/prontuario/PatientProntuario.vue';
import FirstResponseCard from '@/components/dashboard/FirstResponseCard.vue';
// Timeline com paridade ao Melissa: respeita jornada (agenda_regras_semanais),
// folga/feriado, scroll horizontal com min-slot + auto-scroll, eco lateral.
import { useAgendaSettings } from '@/features/agenda/composables/useAgendaSettings';
import { useFeriados } from '@/composables/useFeriados';

const dashHeroSentinelRef = ref(null);
const heroStuck = ref(false);
let _heroObserver = null;

const agora = ref(new Date());
const asideOpen = ref(false);

const { effectiveVariant, layoutState, layoutConfig, isMobile, railPanelPushesLayout } = useLayout();
// ≤ xl: aside é drawer mobile, não usa left fixo
const isMobileLayout = computed(() => isMobile.value);
// left do aside fixo:
//   classic static ativo  → 20rem (sidebar ocupa espaço)
//   classic overlay/static inativo → 0 (sidebar flutua, não reserva espaço)
//   rail painel aberto    → 60px + 260px
//   rail painel fechado   → 60px
const asideLeft = computed(() => {
    if (isMobileLayout.value) return undefined;
    if (effectiveVariant.value !== 'rail') {
        const isStaticActive = layoutConfig.menuMode === 'static' && !layoutState.staticMenuInactive;
        return isStaticActive ? '20rem' : '0';
    }
    return railPanelPushesLayout.value ? 'calc(60px + 260px)' : '60px';
});
let timer = null;
onBeforeUnmount(() => {
    clearInterval(timer);
    _heroObserver?.disconnect();
});

const horaAtual = computed(() => {
    const h = agora.value.getHours().toString().padStart(2, '0');
    const m = agora.value.getMinutes().toString().padStart(2, '0');
    return `${h}:${m}`;
});

const saudacao = computed(() => {
    const h = agora.value.getHours();
    if (h < 12) return 'Bom dia';
    if (h < 18) return 'Boa tarde';
    return 'Boa noite';
});

const tenantStore = useTenantStore();
const router = useRouter();
const toast = useToast();
const { create: createEvento, update: updateEvento } = useAgendaEvents();

// ── Prontuário ────────────────────────────────────────────────
const prontuarioOpen = ref(false);
const selectedPatient = ref(null);

function openProntuario(patientId, patientNome) {
    if (!patientId) return;
    selectedPatient.value = { id: patientId, nome_completo: patientNome || '' };
    prontuarioOpen.value = true;
}
function closeProntuario() {
    prontuarioOpen.value = false;
    selectedPatient.value = null;
}

// ── Tipos de compromisso (para o dialog) ─────────────────────
const clinicTenantId = computed(() => tenantStore.activeTenantId || tenantStore.tenantId || null);
const { rows: determinedCommitments, load: loadCommitments } = useDeterminedCommitments(clinicTenantId);

const COMMITMENT_PRIORITY = new Map([
    ['session', 0],
    ['class', 1],
    ['study', 2],
    ['reading', 3],
    ['supervision', 4],
    ['content_creation', 5]
]);

const commitmentOptionsNormalized = computed(() => {
    const list = Array.isArray(determinedCommitments.value) ? determinedCommitments.value : [];
    return [...list]
        .filter((i) => i?.id && i?.active !== false)
        .sort((a, b) => {
            const pa = COMMITMENT_PRIORITY.get(a.native_key) ?? 99;
            const pb = COMMITMENT_PRIORITY.get(b.native_key) ?? 99;
            if (pa !== pb) return pa - pb;
            return String(a.name || '').localeCompare(String(b.name || ''), 'pt-BR');
        })
        .map((i) => ({
            id: i.id,
            tenant_id: i.tenant_id ?? null,
            created_by: i.created_by ?? null,
            name: String(i.name || '').trim() || 'Sem nome',
            description: i.description || '',
            native_key: i.native_key || null,
            is_native: !!i.is_native,
            is_locked: !!i.is_locked,
            active: i.active !== false,
            bg_color: i.bg_color || null,
            text_color: i.text_color || null,
            fields: Array.isArray(i.determined_commitment_fields) ? [...i.determined_commitment_fields].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0)) : []
        }));
});

// ── Mini calendário: menu de contexto ────────────────────────
const calDayMenuRef = ref(null);

const calDayMenuItems = computed(() => [
    {
        label: 'Opções do dia',
        items: [
            {
                label: 'Novo Compromisso',
                icon: 'pi pi-plus-circle',
                command: () => openNovoCompromisso()
            },
            {
                label: 'Ver dia na agenda',
                icon: 'pi pi-calendar',
                command: () => verDiaNaAgenda()
            }
        ]
    }
]);

function onCalDayClick(event, day) {
    selectedDay.value = day;
    calDayMenuRef.value?.toggle(event);
}

function verDiaNaAgenda() {
    const d = String(selectedDay.value).padStart(2, '0');
    const m = String(mesAtual + 1).padStart(2, '0');
    router.push(`/therapist/agenda?date=${anoAtual}-${m}-${d}`);
}

// ── Menu de contexto: Eventos do dia ─────────────────────────
const evMenuRef = ref(null);
const _evAtivo = ref(null); // evento clicado

const evMenuItems = computed(() => [
    {
        label: 'Opções',
        items: [
            {
                label: 'Ver prontuário',
                icon: 'pi pi-file-edit',
                disabled: !_evAtivo.value?.patientId,
                command: () => openProntuario(_evAtivo.value?.patientId, _evAtivo.value?.nome)
            },
            {
                label: 'Ver na agenda',
                icon: 'pi pi-calendar',
                command: () => {
                    if (!_evAtivo.value?.inicioISO) return;
                    const d = new Date(_evAtivo.value.inicioISO);
                    const ds = d.toISOString().slice(0, 10);
                    router.push(`/therapist/agenda?date=${ds}`);
                }
            }
        ]
    }
]);

function openEvMenu(event, ev) {
    _evAtivo.value = ev;
    evMenuRef.value?.toggle(event);
}

// ── Menu de contexto: Recorrências ativas ────────────────────
const recMenuRef = ref(null);
const _recAtivo = ref(null); // recorrência clicada

const recMenuItems = computed(() => [
    {
        label: 'Opções',
        items: [
            {
                label: 'Ver prontuário',
                icon: 'pi pi-file-edit',
                disabled: !_recAtivo.value?.patientId,
                command: () => openProntuario(_recAtivo.value?.patientId, _recAtivo.value?.nome)
            },
            {
                label: 'Ver na agenda',
                icon: 'pi pi-calendar',
                command: () => router.push('/therapist/agenda')
            }
        ]
    }
]);

function openRecMenu(event, r) {
    _recAtivo.value = r;
    recMenuRef.value?.toggle(event);
}

// ── Dialog: Novo Compromisso ──────────────────────────────────
const agendaDialogOpen = ref(false);
const agendaDialogEventRow = ref(null);
const agendaDialogStartISO = ref('');
const agendaDialogEndISO = ref('');

function openNovoCompromisso() {
    if (!ownerId.value) return;
    const durMin = 50;
    const now = new Date();
    const base = new Date(anoAtual, mesAtual, selectedDay.value, now.getHours(), now.getMinutes(), 0, 0);

    agendaDialogEventRow.value = {
        owner_id: ownerId.value,
        tipo: 'sessao',
        status: 'agendado',
        titulo: null,
        observacoes: null
    };
    agendaDialogStartISO.value = base.toISOString();
    agendaDialogEndISO.value = new Date(base.getTime() + durMin * 60000).toISOString();
    agendaDialogOpen.value = true;
}

function _isUuid(v) {
    return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(String(v || ''));
}

function _pickDbFields(obj) {
    const allowed = [
        'owner_id',
        'terapeuta_id',
        'patient_id',
        'tipo',
        'status',
        'titulo',
        'observacoes',
        'inicio_em',
        'fim_em',
        'visibility_scope',
        'determined_commitment_id',
        'titulo_custom',
        'extra_fields',
        'recurrence_id',
        'recurrence_date',
        'price',
        'insurance_plan_id',
        'insurance_guide_number',
        'insurance_value',
        'insurance_plan_service_id'
    ];
    const out = {};
    for (const k of allowed) {
        if (obj[k] !== undefined) out[k] = obj[k];
    }
    return out;
}

async function onAgendaDialogSave(arg) {
    try {
        const isWrapped = !!arg && typeof arg === 'object' && Object.prototype.hasOwnProperty.call(arg, 'payload');
        const payload = isWrapped ? arg.payload : arg;
        const id = isWrapped ? (arg.id ?? null) : (arg?.id ?? null);

        const normalized = { ...(payload || {}) };

        if (!normalized.owner_id && ownerId.value) normalized.owner_id = ownerId.value;

        if (!normalized.visibility_scope) normalized.visibility_scope = 'public';
        if (!normalized.status) normalized.status = 'agendado';
        if (!normalized.tipo) normalized.tipo = 'sessao';
        if (!String(normalized.titulo || '').trim()) normalized.titulo = normalized.tipo === 'bloqueio' ? 'Ocupado' : 'Sessão';
        if (!_isUuid(normalized.paciente_id)) normalized.paciente_id = null;
        if (normalized.determined_commitment_id && !_isUuid(normalized.determined_commitment_id)) normalized.determined_commitment_id = null;

        const dbPayload = _pickDbFields(normalized);

        if (id) {
            await updateEvento(id, dbPayload);
            toast.add({ severity: 'success', summary: 'Salvo', detail: 'Compromisso atualizado.', life: 2500 });
        } else {
            await createEvento(dbPayload);
            toast.add({ severity: 'success', summary: 'Criado', detail: 'Compromisso criado com sucesso.', life: 2500 });
        }

        agendaDialogOpen.value = false;
        await load();
    } catch (e) {
        const msg = String(e?.message || '');
        const isOverlap = e?.code === '23P01' || msg.includes('agenda_eventos_sem_sobreposicao') || msg.includes('exclusion constraint');
        if (isOverlap) {
            toast.add({ severity: 'warn', summary: 'Conflito de horário', detail: 'Já existe um compromisso neste horário.', life: 4000 });
        } else {
            toast.add({ severity: 'error', summary: 'Erro ao salvar', detail: msg || 'Tente novamente.', life: 4000 });
        }
    }
}

const loading = ref(true);
const ownerId = ref(null);
const eventosDoMes = ref([]);
const regraRecorrencias = ref([]);
const _solicitacoesBruto = ref([]);
const _cadastrosBruto = ref([]);

const diasSemana = ['D', 'S', 'T', 'Q', 'Q', 'S', 'S'];
const hoje = agora.value.getDate();
const mesAtual = agora.value.getMonth();
const anoAtual = agora.value.getFullYear();
const selectedDay = ref(hoje);

const eventMap = computed(() => {
    const map = {};
    for (const ev of eventosDoMes.value) {
        if (!ev.inicio_em) continue;
        const d = new Date(ev.inicio_em);
        if (d.getMonth() !== mesAtual || d.getFullYear() !== anoAtual) continue;
        const day = d.getDate();
        if (!map[day]) map[day] = { count: 0, hasEvent: true };
        map[day].count++;
    }
    for (const day in map) {
        const c = map[day].count;
        map[day].urgency = c >= 5 ? 'urg-alta' : c >= 3 ? 'urg-media' : 'urg-baixa';
    }
    return map;
});

const calCells = computed(() => {
    const primeiroDia = new Date(anoAtual, mesAtual, 1).getDay();
    const totalDias = new Date(anoAtual, mesAtual + 1, 0).getDate();
    const cells = [];
    for (let i = 0; i < primeiroDia; i++) cells.push({ key: 'e' + i, day: null, isOther: true, isToday: false, hasEvent: false, count: 0, urgency: '' });
    for (let d = 1; d <= totalDias; d++) {
        const ev = eventMap.value[d] || {};
        cells.push({ key: 'd' + d, day: d, isToday: d === hoje, isOther: false, hasEvent: !!ev.hasEvent, count: ev.count || 0, urgency: ev.urgency || '' });
    }
    return cells;
});

const labelDiaSelecionado = computed(() => {
    if (selectedDay.value === hoje) return 'Hoje';
    if (selectedDay.value === hoje + 1) return 'Amanhã';
    return `Dia ${selectedDay.value}/${String(mesAtual + 1).padStart(2, '0')}`;
});

const STATUS_ICON = {
    realizado: 'pi pi-check-circle',
    faltou: 'pi pi-times-circle',
    cancelado: 'pi pi-times-circle',
    remarcar: 'pi pi-undo',
    agendado: 'pi pi-clock'
};

const commitmentColorMap = computed(() => new Map(commitmentOptionsNormalized.value.filter((c) => c.id && c.bg_color).map((c) => [c.id, { bg_color: c.bg_color, text_color: c.text_color }])));

function buildEventoItem(ev) {
    const inicio = new Date(ev.inicio_em);
    const fim = ev.fim_em ? new Date(ev.fim_em) : null;
    const durMin = fim ? Math.round((fim - inicio) / 60000) : 50;
    const h = inicio.getHours().toString().padStart(2, '0');
    const m = inicio.getMinutes().toString().padStart(2, '0');
    const joinColor = ev.determined_commitments;
    const mapColor = ev.determined_commitment_id ? commitmentColorMap.value.get(ev.determined_commitment_id) : null;
    const bgColor = joinColor?.bg_color ? `#${joinColor.bg_color}` : mapColor?.bg_color ? `#${mapColor.bg_color}` : null;
    const txtColor = joinColor?.text_color ? `#${joinColor.text_color}` : mapColor?.text_color ? `#${mapColor.text_color}` : null;
    return {
        id: ev.id,
        hora: `${h}:${m}`,
        dur: `${durMin}min`,
        nome: ev.patients?.nome_completo || ev.titulo || ev.titulo_custom || '—',
        modalidade: ev.modalidade || 'Presencial',
        recorrente: !!ev.recurrence_id,
        status: ev.status || 'agendado',
        statusIcon: STATUS_ICON[ev.status] || 'pi pi-clock',
        tipo: ev.tipo || 'sessao',
        patientId: ev.patient_id || null,
        inicioISO: ev.inicio_em || null,
        bgColor,
        txtColor
    };
}

const eventosDoDia = computed(() =>
    eventosDoMes.value
        .filter((ev) => {
            if (!ev.inicio_em) return false;
            const d = new Date(ev.inicio_em);
            return d.getDate() === selectedDay.value && d.getMonth() === mesAtual && d.getFullYear() === anoAtual;
        })
        .sort((a, b) => new Date(a.inicio_em) - new Date(b.inicio_em))
        .map(buildEventoItem)
);

const FREQ_LABEL = { weekly: 'Semanal', biweekly: 'Quinzenal', monthly: 'Mensal', custom_weekdays: 'Personalizado' };
const DIAS_PT = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'];

function initials(nome) {
    const parts = (nome || '').trim().split(/\s+/);
    if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
    return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
}

function hashColor(str) {
    const palette = ['#6366f1', '#0ea5e9', '#ec4899', '#f59e0b', '#22c55e', '#8b5cf6', '#ef4444', '#14b8a6'];
    let h = 0;
    for (let i = 0; i < (str || '').length; i++) h = (h * 31 + str.charCodeAt(i)) & 0xffffffff;
    return palette[Math.abs(h) % palette.length];
}

function nextOccurrenceLabel(rule) {
    const todayDow = new Date().getDay();
    const weekdays = Array.isArray(rule.weekdays) ? rule.weekdays : [];
    if (!weekdays.length) return '—';
    if (rule.type === 'weekly' || rule.type === 'custom_weekdays') {
        if (weekdays.includes(todayDow)) return 'Hoje';
        let minDiff = 8;
        for (const w of weekdays) {
            let diff = w - todayDow;
            if (diff <= 0) diff += 7;
            if (diff < minDiff) minDiff = diff;
        }
        if (minDiff === 1) return 'Amanhã';
        if (minDiff < 8) return `Em ${minDiff} dias`;
    }
    if (rule.type === 'biweekly') {
        if (weekdays.includes(todayDow)) return 'Hoje';
        if (weekdays.includes((todayDow + 1) % 7)) return 'Amanhã';
    }
    return '—';
}

const recorrencias = computed(() =>
    regraRecorrencias.value.slice(0, 8).map((r) => {
        const nome = r._patientNome || '—';
        const parts = nome.split(' ');
        const nomeAb = parts[0] + (parts[1] ? ' ' + parts[1][0] + '.' : '');
        const freq = FREQ_LABEL[r.type] || r.type || '—';
        const weekdays = Array.isArray(r.weekdays) ? r.weekdays : [];
        const diaLabel = weekdays.map((d) => DIAS_PT[d]).join(', ');
        const hora = r.start_time ? String(r.start_time).slice(0, 5) : '';
        const proxLabel = nextOccurrenceLabel(r);
        return { id: r.id, nome: nomeAb, freq: `${freq} · ${diaLabel}${hora ? ' ' + hora : ''}`, proxLabel, proxHoje: proxLabel === 'Hoje', color: hashColor(r.patient_id || r.id), initials: initials(nome), patientId: r.patient_id || null };
    })
);

// ── Derivados de eventosDoMes — single pass ───────────────────
// Um único computed varre o array uma vez e extrai tudo,
// evitando N loops separados que re-executam a cada reatividade.
const _statsDoMes = computed(() => {
    const now = agora.value;
    const semIni = new Date(now);
    semIni.setDate(now.getDate() - now.getDay());
    semIni.setHours(0, 0, 0, 0);
    const semFim = new Date(semIni);
    semFim.setDate(semIni.getDate() + 6);
    semFim.setHours(23, 59, 59, 999);
    const daqui30 = new Date(now);
    daqui30.setDate(now.getDate() + 30);

    let hojeCnt = 0,
        semanaCnt = 0,
        realizadosCnt = 0,
        encerradosCnt = 0;
    const hojeLista = [],
        timelineLista = [];
    const diasSemanaMap = [[], [], [], [], [], [], []];

    for (const ev of eventosDoMes.value) {
        if (!ev.inicio_em) continue;
        const d = new Date(ev.inicio_em);
        const dDay = d.getDate(),
            dMon = d.getMonth(),
            dYear = d.getFullYear();
        const isHoje = dDay === hoje && dMon === mesAtual && dYear === anoAtual;

        if (isHoje) {
            hojeCnt++;
            hojeLista.push(ev);
            timelineLista.push(ev);
        }
        if (d >= semIni && d <= semFim) {
            semanaCnt++;
            diasSemanaMap[d.getDay()].push(ev);
        }
        if (d < now && ['realizado', 'faltou', 'cancelado'].includes(ev.status)) {
            encerradosCnt++;
            if (ev.status === 'realizado') realizadosCnt++;
        }
    }

    const taxaPresenca = encerradosCnt > 0 ? Math.round((realizadosCnt / encerradosCnt) * 100) : null;

    return { hojeCnt, semanaCnt, taxaPresenca, hojeLista, timelineLista, diasSemanaMap };
});

const eventosHoje = computed(() => _statsDoMes.value.hojeLista);
const eventosSemana = computed(() => ({ length: _statsDoMes.value.semanaCnt }));
const taxaPresenca = computed(() => _statsDoMes.value.taxaPresenca);

const quickStats = computed(() => {
    const pendentes = _solicitacoesBruto.value.length + _cadastrosBruto.value.length;
    const pct = taxaPresenca.value;
    return [
        { value: String(_statsDoMes.value.hojeCnt), label: 'Hoje', cls: '' },
        { value: String(pendentes), label: 'Pendentes', cls: pendentes > 0 ? 'qs-urgente' : '' },
        { value: String(_statsDoMes.value.semanaCnt), label: 'Semana', cls: '' },
        { value: pct !== null ? `${pct}%` : '—', label: 'Presença', cls: pct !== null && pct >= 85 ? 'qs-ok' : '' }
    ];
});

const resumoHoje = computed(() => {
    const sessoes = _statsDoMes.value.hojeLista.filter((ev) => ev.tipo !== 'bloqueio').length;
    const sols = _solicitacoesBruto.value.length;
    const parts = [];
    if (sessoes === 1) parts.push('1 sessão hoje');
    else if (sessoes > 1) parts.push(`${sessoes} sessões hoje`);
    else parts.push('Nenhuma sessão hoje');
    if (sols > 0) parts.push(`${sols} solicitaç${sols === 1 ? 'ão' : 'ões'} aguarda${sols === 1 ? '' : 'm'} sua atenção`);
    return parts.join(' · ');
});

const solicitacoes = computed(() =>
    _solicitacoesBruto.value.map((s) => {
        const nome = `${s.paciente_nome || ''} ${s.paciente_sobrenome || ''}`.trim() || '—';
        const dia = s.data_solicitada ? new Date(s.data_solicitada + 'T00:00:00').toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' }) : '—';
        const hora = s.hora_solicitada ? String(s.hora_solicitada).slice(0, 5) : '';
        const tipo = s.tipo === 'primeira' ? 'Primeira consulta' : s.tipo === 'retorno' ? 'Retorno' : s.tipo || '—';
        const modal = s.modalidade === 'online' ? 'Online' : s.modalidade === 'presencial' ? 'Presencial' : s.modalidade || '—';
        return { id: s.id, nome, initials: initials(nome), detalhe: `${tipo} · ${modal} · ${dia}${hora ? ' ' + hora : ''}` };
    })
);
const solicitacoesPendentes = computed(() => solicitacoes.value.length);

async function aceitarSol(id) {
    try {
        await supabase.from('agendador_solicitacoes').update({ status: 'autorizado' }).eq('id', id);
        _solicitacoesBruto.value = _solicitacoesBruto.value.filter((s) => s.id !== id);
    } catch (e) {
        console.error('[TherapistDashboard] aceitarSol:', e);
    }
}
async function recusarSol(id) {
    try {
        await supabase.from('agendador_solicitacoes').update({ status: 'recusado' }).eq('id', id);
        _solicitacoesBruto.value = _solicitacoesBruto.value.filter((s) => s.id !== id);
    } catch (e) {
        console.error('[TherapistDashboard] recusarSol:', e);
    }
}

const cadastros = computed(() =>
    _cadastrosBruto.value.map((c) => {
        const nome = c.nome_completo || '—';
        const criado = c.created_at ? new Date(c.created_at).toLocaleDateString('pt-BR') : '—';
        return { id: c.id, nome, initials: initials(nome), detalhe: `Via portal · ${criado}` };
    })
);
const cadastrosPendentes = computed(() => cadastros.value.length);

const recAlerta = computed(() => {
    const now = new Date(),
        daqui30 = new Date();
    daqui30.setDate(now.getDate() + 30);
    const alerts = [];
    for (const r of regraRecorrencias.value) {
        const nome = (r._patientNome || '—').split(' ').slice(0, 2).join(' ');
        if (r.end_date) {
            const ed = new Date(r.end_date + 'T00:00:00');
            if (ed >= now && ed <= daqui30) alerts.push({ id: r.id + '_end', nome, motivo: 'Encerramento próximo', tipo: 'feriado' });
        }
        if (r.max_occurrences && r._sessionsCount !== undefined) {
            const pct = (r._sessionsCount / r.max_occurrences) * 100;
            if (pct > 75) alerts.push({ id: r.id + '_limit', nome, motivo: 'Limite próximo', tipo: 'limite', sessoesUsadas: r._sessionsCount, totalSessoes: r.max_occurrences, progresso: Math.round(pct) });
        }
    }
    return alerts.slice(0, 5);
});

const radarSemana = computed(() => {
    const diasMap = _statsDoMes.value.diasSemanaMap;
    const dow = agora.value.getDay();
    return DIAS_PT.map((dia, i) => {
        const evs = diasMap[i];
        const total = evs.length;
        const presentes = evs.filter((ev) => ev.status === 'realizado').length;
        const faltas = evs.filter((ev) => ev.status === 'faltou').length;
        const reposicao = evs.filter((ev) => ['reposicao', 'reposição'].includes(ev.status)).length;
        let status = 'ok';
        if (faltas > 0 && faltas >= presentes) status = 'falta';
        else if (reposicao > 0 && reposicao > presentes) status = 'repo';
        return { dia, total, pct: total === 0 ? 0 : Math.min(Math.round((total / 6) * 100), 100), status, isToday: i === dow };
    });
});

const CHIP_CORES = ['blue', 'purple', 'green', 'orange', 'red'];
const STATUS_LABEL_MAP = { agendado: 'Agendado', confirmado: 'Confirmado', cancelado: 'Cancelado', realizado: 'Realizado', pendente: 'Pendente' };

const commitments = computed(() => {
    const now = new Date();
    return eventosDoMes.value
        .filter((ev) => ev.tipo === 'bloqueio' && ev.inicio_em && new Date(ev.inicio_em) >= now && (ev.titulo || ev.titulo_custom))
        .sort((a, b) => new Date(a.inicio_em) - new Date(b.inicio_em))
        .slice(0, 5)
        .map((ev, i) => {
            const d = new Date(ev.inicio_em);
            return {
                id: ev.id,
                titulo: ev.titulo || ev.titulo_custom,
                sub: ev.modalidade || '—',
                data: d.toLocaleDateString('pt-BR'),
                cor: CHIP_CORES[i % CHIP_CORES.length],
                status: ev.status || 'agendado',
                statusLabel: STATUS_LABEL_MAP[ev.status] || 'Agendado'
            };
        });
});

// ── Timeline: range derivado de agenda_regras_semanais (regra do dia
// atual). Fallback: agenda_configuracoes global → 07–20h. Range expande
// pra incluir eventos fora do expediente. Espelha o Melissa.
const { settings: agendaSettings, workRules: agendaWorkRules, load: loadAgendaSettings } = useAgendaSettings();
const { todos: feriadosList, load: loadFeriadosBase } = useFeriados();
onMounted(() => {
    loadAgendaSettings();
});

function _timeStrToHour(s, fb) {
    const str = String(s || '').slice(0, 5);
    const [h, m] = str.split(':').map(Number);
    if (Number.isFinite(h) && Number.isFinite(m)) return h + m / 60;
    return fb;
}
const todayRules = computed(() => {
    const dow = new Date().getDay();
    return (agendaWorkRules.value || []).filter((r) => r.dia_semana === dow && r.ativo !== false);
});
const isFolga = computed(() => todayRules.value.length === 0);
const todayFeriado = computed(() => {
    const d = new Date();
    const k = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
    return (feriadosList.value || []).find((f) => f.data === k) || null;
});
function _baseTimelineRange() {
    const rules = todayRules.value;
    if (rules.length > 0) {
        const starts = rules.map((r) => _timeStrToHour(r.hora_inicio, 7));
        const ends = rules.map((r) => _timeStrToHour(r.hora_fim, 20));
        return { start: Math.min(...starts), end: Math.max(...ends) };
    }
    const s = agendaSettings.value;
    const fbStart = (s?.usar_horario_admin_custom && s?.admin_inicio_visualizacao) || s?.agenda_custom_start || '07:00';
    const fbEnd = (s?.usar_horario_admin_custom && s?.admin_fim_visualizacao) || s?.agenda_custom_end || '20:00';
    return { start: _timeStrToHour(fbStart, 7), end: _timeStrToHour(fbEnd, 20) };
}

// `timelineEventsRaw` extrai a info dos eventos SEM depender de TL_START/TL_END
// (pra evitar dep circular: TL_START depende dos eventos pra expandir o range).
const timelineEventsRaw = computed(() =>
    _statsDoMes.value.timelineLista
        .slice()
        .sort((a, b) => new Date(a.inicio_em) - new Date(b.inicio_em))
        .map((ev) => {
            const item = buildEventoItem(ev);
            const [hh, mm] = item.hora.split(':').map(Number);
            const durMin = parseInt(item.dur) || 50;
            const startH = hh + mm / 60;
            const endH = startH + durMin / 60;
            return {
                id: item.id,
                inicio_em: ev.inicio_em,
                label: item.nome.split(' ')[0],
                tipo: item.tipo,
                status: item.status,
                modalidade: item.modalidade,
                tooltip: `${item.hora} · ${item.nome} · ${item.modalidade}`,
                badge: item.modalidade?.toLowerCase() === 'online' ? '📱' : '',
                bgColor: item.bgColor,
                txtColor: item.txtColor,
                durMin,
                startH,
                endH
            };
        })
);

const TL_START = computed(() => {
    const { start } = _baseTimelineRange();
    const evs = timelineEventsRaw.value || [];
    const minEv = evs.length ? Math.min(...evs.map((e) => e.startH)) : Infinity;
    return Math.max(0, Math.floor(Math.min(start, minEv)));
});
const TL_END = computed(() => {
    const { end } = _baseTimelineRange();
    const evs = timelineEventsRaw.value || [];
    const maxEv = evs.length ? Math.max(...evs.map((e) => e.endH)) : -Infinity;
    return Math.min(24, Math.ceil(Math.max(end, maxEv)));
});
const hoursRange = computed(() => {
    const arr = [];
    for (let h = TL_START.value; h <= TL_END.value; h++) arr.push(h);
    return arr;
});
function toPercent(h, m) {
    const span = TL_END.value - TL_START.value;
    if (span <= 0) return 0;
    return ((h + m / 60 - TL_START.value) / span) * 100;
}

// `timelineEvents` adiciona positioning (left/width %) baseado em TL_START/END.
const timelineEvents = computed(() =>
    timelineEventsRaw.value.map((ev) => {
        const span = TL_END.value - TL_START.value;
        const left = span > 0 ? ((ev.startH - TL_START.value) / span) * 100 : 0;
        const width = span > 0 ? Math.max(((ev.endH - ev.startH) / span) * 100, 4) : 4;
        return { ...ev, style: { left: `${left}%`, width: `${width}%` } };
    })
);

const nowCursorLeft = computed(() => {
    const pct = toPercent(agora.value.getHours(), agora.value.getMinutes());
    return Math.min(Math.max(pct, 0), 100) + '%';
});

// ── Scroll horizontal + eco lateral (paridade Melissa) ──────────
// scroll state, eco state, auto-scroll to-now, scrollToEvent.
const tlHScrollEl = ref(null);
const tlScrollState = ref({ scrollL: 0, viewW: 0, innerW: 0 });
function _updateTlScrollState() {
    const el = tlHScrollEl.value;
    if (!el) {
        tlScrollState.value = { scrollL: 0, viewW: 0, innerW: 0 };
        return;
    }
    const inner = el.firstElementChild;
    tlScrollState.value = {
        scrollL: el.scrollLeft,
        viewW: el.clientWidth,
        innerW: inner ? (inner.scrollWidth || inner.offsetWidth) : 0
    };
}
function onTimelineScroll() {
    _updateTlScrollState();
}
const tlEcoState = computed(() => {
    const { scrollL, viewW, innerW } = tlScrollState.value;
    const total = TL_END.value - TL_START.value;
    const empty = { left: [], right: [], vStart: TL_START.value, vEnd: TL_END.value };
    if (total <= 0 || !innerW || !viewW || innerW <= viewW) return empty;
    const vStart = TL_START.value + (scrollL / innerW) * total;
    const vEnd = TL_START.value + ((scrollL + viewW) / innerW) * total;
    const left = [];
    const right = [];
    for (const ev of timelineEventsRaw.value) {
        if (ev.endH <= vStart) left.push(ev);
        else if (ev.startH >= vEnd) right.push(ev);
    }
    return { left, right, vStart, vEnd };
});
function ecoTickStyle(ev, side) {
    const { vStart, vEnd } = tlEcoState.value;
    let topPct = 50;
    if (side === 'left') {
        const span = vStart - TL_START.value;
        if (span > 0) topPct = ((ev.startH - TL_START.value) / span) * 100;
    } else {
        const span = TL_END.value - vEnd;
        if (span > 0) topPct = ((ev.startH - vEnd) / span) * 100;
    }
    return {
        top: `${Math.max(0, Math.min(100, topPct))}%`,
        backgroundColor: ev.bgColor || '#6366f1'
    };
}
function scrollToEvent(ev) {
    const el = tlHScrollEl.value;
    if (!el) return;
    const inner = el.firstElementChild;
    if (!inner) return;
    const innerWidth = inner.scrollWidth || inner.offsetWidth;
    const visibleWidth = el.clientWidth;
    const total = TL_END.value - TL_START.value;
    if (total <= 0) return;
    const ratio = (ev.startH - TL_START.value) / total;
    const target = Math.max(0, Math.min(innerWidth - visibleWidth, ratio * innerWidth - visibleWidth / 2));
    el.scrollTo({ left: target, behavior: 'smooth' });
}

let _tlAutoScrolled = false;
function _scrollTimelineToNow() {
    const el = tlHScrollEl.value;
    if (!el) return;
    const h = agora.value.getHours() + agora.value.getMinutes() / 60;
    const total = TL_END.value - TL_START.value;
    if (total <= 0 || h < TL_START.value || h > TL_END.value) return;
    const inner = el.firstElementChild;
    if (!inner) return;
    const innerWidth = inner.scrollWidth || inner.offsetWidth;
    const visibleWidth = el.clientWidth;
    if (innerWidth <= visibleWidth) return;
    const ratio = (h - TL_START.value) / total;
    el.scrollLeft = Math.max(0, ratio * innerWidth - visibleWidth / 2);
}
onMounted(() => {
    const stop = watch(
        [TL_START, TL_END],
        () => {
            nextTick(() => {
                if (!_tlAutoScrolled) {
                    _scrollTimelineToNow();
                    const el = tlHScrollEl.value;
                    const inner = el?.firstElementChild;
                    if (inner && inner.scrollWidth > el.clientWidth) {
                        _tlAutoScrolled = true;
                        stop();
                    }
                }
                _updateTlScrollState();
            });
        },
        { immediate: true }
    );

    const el = tlHScrollEl.value;
    if (el && typeof ResizeObserver !== 'undefined') {
        const ro = new ResizeObserver(() => _updateTlScrollState());
        ro.observe(el);
        if (el.firstElementChild) ro.observe(el.firstElementChild);
        onBeforeUnmount(() => ro.disconnect());
    }
});

function _fmtHora(h) {
    const horas = Math.floor(h);
    const mins = Math.round((h - horas) * 60);
    return `${String(horas).padStart(2, '0')}:${String(mins).padStart(2, '0')}`;
}

async function load() {
    loading.value = true;
    const { data: authData } = await supabase.auth.getUser();
    ownerId.value = authData?.user?.id || null;
    if (!ownerId.value) {
        loading.value = false;
        return;
    }
    await tenantStore.ensureLoaded();
    const tid = tenantStore.activeTenantId || tenantStore.tenantId || null;
    // Feriados pra badge da timeline (precisa do tenant — RLS).
    if (tid) {
        loadFeriadosBase(tid).catch(() => {});
    }
    await loadCommitments();
    const mesInicio = new Date(anoAtual, mesAtual, 1, 0, 0, 0, 0).toISOString();
    const mesFim = new Date(anoAtual, mesAtual + 1, 0, 23, 59, 59, 999).toISOString();
    try {
        const [eventosRes, recRes, solRes, cadRes] = await Promise.all([
            (() => {
                let q = tenantDb().from('agenda_eventos')
                    .select('id, inicio_em, fim_em, status, modalidade, tipo, titulo, titulo_custom, patient_id, recurrence_id, determined_commitment_id, patients(nome_completo), determined_commitments(bg_color, text_color)')
                    .eq('owner_id', ownerId.value)
                    .gte('inicio_em', mesInicio)
                    .lte('inicio_em', mesFim)
                    .order('inicio_em', { ascending: true });
                return q;
            })(),
            (() => {
                let q = tenantDb().from('recurrence_rules')
                    .select('id, patient_id, type, interval, weekdays, start_date, end_date, max_occurrences, start_time')
                    .eq('owner_id', ownerId.value)
                    .eq('status', 'ativo')
                    .order('start_date', { ascending: false });
                return q;
            })(),
            supabase
                .from('agendador_solicitacoes')
                .select('id, paciente_nome, paciente_sobrenome, tipo, modalidade, data_solicitada, hora_solicitada')
                .eq('owner_id', ownerId.value)
                .eq('status', 'pendente')
                .order('created_at', { ascending: false })
                .limit(10),
            supabase.from('patient_intake_requests').select('id, nome_completo, status, created_at').eq('owner_id', ownerId.value).eq('status', 'new').order('created_at', { ascending: false }).limit(10)
        ]);
        eventosDoMes.value = eventosRes.data || [];
        _solicitacoesBruto.value = solRes.data || [];
        _cadastrosBruto.value = cadRes.data || [];
        const rawRules = recRes.data || [];
        const patientIds = [...new Set(rawRules.map((r) => r.patient_id).filter(Boolean))];
        if (patientIds.length) {
            const { data: pts } = await tenantDb().from('patients').select('id, nome_completo').in('id', patientIds);
            const ptMap = Object.fromEntries((pts || []).map((p) => [p.id, p.nome_completo]));
            for (const r of rawRules) r._patientNome = ptMap[r.patient_id] || '—';
        } else {
            for (const r of rawRules) r._patientNome = '—';
        }
        const rulesComLimite = rawRules.filter((r) => r.max_occurrences);
        if (rulesComLimite.length) {
            const { data: sessData } = await tenantDb().from('agenda_eventos')
                .select('id, recurrence_id')
                .in(
                    'recurrence_id',
                    rulesComLimite.map((r) => r.id)
                )
                .in('status', ['agendado', 'realizado']);
            const countMap = {};
            for (const s of sessData || []) {
                if (s.recurrence_id) countMap[s.recurrence_id] = (countMap[s.recurrence_id] || 0) + 1;
            }
            for (const r of rawRules) r._sessionsCount = countMap[r.id] || 0;
        }
        regraRecorrencias.value = rawRules;
    } catch (e) {
        console.error('[TherapistDashboard] load:', e);
    } finally {
        loading.value = false;
    }
}

onMounted(async () => {
    timer = setInterval(() => {
        agora.value = new Date();
    }, 60000);
    _heroObserver = new IntersectionObserver(
        ([entry]) => {
            heroStuck.value = !entry.isIntersecting;
        },
        { threshold: 0 }
    );
    if (dashHeroSentinelRef.value) _heroObserver.observe(dashHeroSentinelRef.value);
    await load();
});
</script>

<template>
    <div class="flex min-h-[calc(100vh-4.5rem)] bg-[var(--surface-ground,#f5f7fa)]">
        <!-- ═══════════════════════════════════════
         OVERLAY (mobile) + ASIDE
    ══════════════════════════════════════════ -->
        <div v-if="asideOpen" class="fixed inset-0 bg-black/40 backdrop-blur-sm z-[39] xl:hidden" @click="asideOpen = false" />

        <aside class="aside-drawer flex flex-col overflow-y-auto shrink-0 bg-[var(--surface-card,#fff)] border-r border-[var(--surface-border,#e2e8f0)]" :class="asideOpen ? 'translate-x-0 visible' : 'max-xl:-translate-x-full max-xl:invisible'" :style="{ left: asideLeft }">
            <!-- Mini calendário -->
            <div class="p-3.5 pb-2.5 border-b border-[var(--surface-border,#e2e8f0)]">
                <div class="flex items-center gap-1.5 text-xs font-bold uppercase tracking-widest text-[var(--text-color-secondary,#64748b)] mb-2.5">
                    <i class="pi pi-calendar" />
                    <span>Março 2026</span>
                </div>

                <!-- Skeleton -->
                <Skeleton v-if="loading" width="100%" height="180px" border-radius="6px" />

                <Transition name="fade-up" appear>
                    <div v-if="!loading">
                        <div class="grid grid-cols-7 mb-0.5">
                            <span v-for="d in diasSemana" :key="d" class="text-center text-xs font-bold text-[var(--text-color-secondary,#94a3b8)] py-0.5">{{ d }}</span>
                        </div>
                        <div class="grid grid-cols-7 gap-px">
                            <button
                                v-for="cell in calCells"
                                :key="cell.key"
                                class="relative aspect-square flex items-center justify-center text-xs font-medium rounded border-none cursor-pointer transition-colors duration-100"
                                :class="{
                                    'bg-[var(--primary-color,#6366f1)] text-white font-bold rounded-md': cell.isToday,
                                    'text-[var(--text-color-secondary,#cbd5e1)] cursor-default': cell.isOther,
                                    'bg-indigo-500/10 text-[var(--primary-color,#6366f1)] font-bold': cell.day === selectedDay && !cell.isToday,
                                    'text-[var(--text-color,#1e293b)] hover:bg-[var(--surface-hover,#f1f5f9)]': !cell.isToday && !cell.isOther
                                }"
                                @click="cell.day && onCalDayClick($event, cell.day)"
                            >
                                <span>{{ cell.day }}</span>
                                <span
                                    v-if="cell.count"
                                    class="absolute bottom-px right-0.5 w-1 h-1 rounded-full"
                                    :class="{
                                        'bg-red-500': cell.urgency === 'urg-alta',
                                        'bg-amber-500': cell.urgency === 'urg-media',
                                        'bg-green-500': cell.urgency === 'urg-baixa'
                                    }"
                                />
                            </button>
                        </div>
                    </div>
                </Transition>
            </div>

            <!-- Eventos do dia -->
            <div class="p-3.5 pb-2.5 border-b border-[var(--surface-border,#e2e8f0)]">
                <div class="flex items-center gap-1.5 text-xs font-bold uppercase tracking-widest text-[var(--text-color-secondary,#64748b)] mb-2.5">
                    <i class="pi pi-clock" /><span>{{ labelDiaSelecionado }}</span>
                </div>

                <!-- Skeleton -->
                <Skeleton v-if="loading" width="100%" height="160px" border-radius="6px" />

                <Transition name="fade-up" appear>
                <div v-if="!loading" class="flex flex-col gap-2 max-h-[260px] overflow-y-auto pr-0.5">
                    <div
                        v-for="ev in eventosDoDia"
                        :key="ev.id"
                        class="aside-ev"
                        :style="ev.bgColor ? { '--ev-color': ev.bgColor } : {}"
                        :class="{
                            'aside-ev--reuniao': !ev.bgColor && ev.tipo === 'reuniao',
                            'aside-ev--realizado': !ev.bgColor && ev.status === 'realizado',
                            'aside-ev--default': !ev.bgColor && ev.tipo !== 'reuniao' && ev.status !== 'realizado'
                        }"
                        @click="openEvMenu($event, ev)"
                    >
                        <div class="aside-ev__time">
                            <span class="aside-ev__hora">{{ ev.hora }}</span>
                            <span class="aside-ev__dur">{{ ev.dur }}</span>
                        </div>
                        <Avatar :label="initials(ev.nome)" shape="square" size="small" class="shrink-0" />
                        <div class="flex-1 min-w-0">
                            <span class="block text-[0.8125rem] font-semibold truncate text-[var(--text-color)]">{{ ev.nome }}</span>
                            <div class="flex gap-1.5 mt-0.5 items-center">
                                <span class="aside-ev__badge">{{ ev.modalidade }}</span>
                                <i v-if="ev.recorrente" class="pi pi-sync text-[0.6rem] text-[var(--primary-color,#6366f1)]" title="Recorrente" />
                            </div>
                        </div>
                        <i :class="ev.statusIcon" class="text-xs text-[var(--text-color-secondary)] shrink-0" />
                    </div>
                    <div v-if="!eventosDoDia.length" class="flex items-center gap-2 py-3 text-[var(--text-color-secondary)] text-sm"><i class="pi pi-sun" /><span>Sem compromissos</span></div>
                </div>
                </Transition>
            </div>

            <!-- Recorrências ativas -->
            <div class="p-3.5 pb-2.5 border-b border-[var(--surface-border,#e2e8f0)]">
                <div class="flex items-center gap-1.5 text-xs font-bold uppercase tracking-widest text-[var(--text-color-secondary,#64748b)] mb-2.5">
                    <i class="pi pi-sync" /><span>Recorrências ativas</span>
                    <span class="ml-auto bg-[var(--primary-color,#6366f1)] text-white rounded-full px-1.5 text-xs font-bold">{{ recorrencias.length }}</span>
                </div>

                <!-- Skeleton -->
                <Skeleton v-if="loading" width="100%" height="180px" border-radius="6px" />

                <Transition name="fade-up" appear>
                <div v-if="!loading" class="flex flex-col gap-1.5 max-h-[210px] overflow-y-auto pr-0.5">
                    <div v-for="r in recorrencias" :key="r.id" class="aside-rec" @click="openRecMenu($event, r)">
                        <Avatar :label="r.initials" shape="square" size="normal" class="shrink-0" />
                        <div class="flex-1 min-w-0">
                            <span class="block text-[0.8125rem] font-semibold truncate text-[var(--text-color)]">{{ r.nome }}</span>
                            <span class="block text-xs text-[var(--text-color-secondary)]">{{ r.freq }}</span>
                        </div>
                        <div class="aside-rec__prox" :class="r.proxHoje ? 'text-[var(--primary-color,#6366f1)] font-bold' : 'text-[var(--text-color-secondary)]'">{{ r.proxLabel }}</div>
                    </div>
                    <div v-if="!recorrencias.length" class="flex items-center gap-2 py-3 text-[var(--text-color-secondary)] text-sm"><i class="pi pi-info-circle" /><span>Nenhuma recorrência ativa</span></div>
                </div>
                </Transition>
            </div>
        </aside>

        <!-- ═══════════════════════════════════════
         CONTEÚDO PRINCIPAL
    ══════════════════════════════════════════ -->
        <div ref="dashHeroSentinelRef" class="h-px" />

        <main class="flex-1 min-w-0 py-4 xl:py-[1.125rem] px-3 md:px-4 flex flex-col gap-4 overflow-y-auto" :style="{ marginLeft: isMobileLayout ? undefined : '272px' }">
            <!-- Hero Header -->
            <!-- Skeleton hero -->
            <Skeleton v-if="loading" width="100%" height="140px" border-radius="6px" />

            <Transition name="fade-up" appear>
            <section v-if="!loading" class="relative overflow-hidden rounded-md border border-[var(--surface-border)] bg-[var(--surface-card)] p-2.5" :class="{ 'rounded-tl-none rounded-tr-none': heroStuck }">
                <div class="absolute inset-0 pointer-events-none overflow-hidden" aria-hidden="true">
                    <div class="absolute w-72 h-72 -top-16 -right-12 rounded-full blur-[70px] bg-indigo-500/10" />
                    <div class="absolute w-80 h-80 top-2 -left-20 rounded-full blur-[70px] bg-emerald-400/[0.08]" />
                </div>

                <!-- Linha 1 -->
                <div class="relative z-1 flex items-center gap-4">
                    <div class="flex items-center gap-3 flex-1 min-w-0">
                        <div class="grid place-items-center w-10 h-10 rounded-md shrink-0 bg-indigo-500/10 text-indigo-500">
                            <i class="pi pi-home text-lg" />
                        </div>
                        <div class="min-w-0">
                            <div class="text-[1.1rem] font-bold tracking-tight text-[var(--text-color-secondary)]">{{ saudacao }} <span class="text-[var(--primary-color,#6366f1)]">👋</span></div>
                            <div class="text-[0.78rem] text-[var(--text-color-secondary)] mt-0.5">{{ resumoHoje }}</div>
                        </div>
                    </div>
                    <!-- Controles (desktop e mobile — mesmo conteúdo, sempre visível) -->
                    <div class="flex items-center gap-2 shrink-0">
                        <Button icon="pi pi-refresh" severity="secondary" outlined class="h-9 w-9 rounded-full" title="Recarregar" @click="load" />
                        <Button icon="pi pi-cog" severity="secondary" outlined class="h-9 w-9 rounded-full" title="Configurações" @click="$router.push('/configuracoes')" />
                    </div>
                </div>

                <Divider class="hidden xl:block my-2" />

                <!-- Quick stats -->
                <div class="relative z-1 mt-2">
                    <div class="grid grid-cols-2 md:grid-cols-4 gap-2.5">
                        <div
                            v-for="s in quickStats"
                            :key="s.label"
                            class="flex flex-col gap-0.5 px-4 py-2.5 rounded-md border flex-1 min-w-[90px] text-center xl:text-left transition-colors duration-150"
                            :class="{
                                'border-red-500/25 bg-red-500/5': s.cls === 'qs-urgente',
                                'border-green-500/25 bg-green-500/5': s.cls === 'qs-ok',
                                'border-[var(--surface-border)] bg-[var(--surface-ground)]': !s.cls
                            }"
                        >
                            <div
                                class="text-[1.35rem] font-bold leading-none"
                                :class="{
                                    'text-red-500': s.cls === 'qs-urgente',
                                    'text-green-500': s.cls === 'qs-ok',
                                    'text-[var(--text-color)]': !s.cls
                                }"
                            >
                                {{ s.value }}
                            </div>
                            <div class="text-[0.7rem] text-[var(--text-color-secondary)] opacity-75 truncate">{{ s.label }}</div>
                        </div>
                    </div>
                </div>
            </section>
            </Transition>

            <!-- Toggle aside — só mobile -->
            <button
                class="xl:hidden flex w-full items-center justify-between gap-3 px-4 py-3 bg-[var(--surface-card,#fff)] border-b border-[var(--surface-border,#e2e8f0)] text-sm font-semibold text-[var(--text-color)] cursor-pointer sticky top-0 z-30"
                @click="asideOpen = !asideOpen"
            >
                <div class="flex items-center gap-2 min-w-0">
                    <i class="pi pi-calendar-clock text-[var(--primary-color,#6366f1)]" />
                    <span class="truncate">Agenda · {{ labelDiaSelecionado }}</span>
                    <span v-if="eventosDoDia.length" class="inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 rounded-full bg-[var(--primary-color,#6366f1)] text-white text-xs font-bold shrink-0">{{ eventosDoDia.length }}</span>
                </div>
                <i class="pi transition-transform duration-200" :class="asideOpen ? 'pi-chevron-up' : 'pi-chevron-down'" />
            </button>

            <!-- Linha do tempo -->
            <Skeleton v-if="loading" width="100%" height="110px" border-radius="6px" />

            <Transition name="fade-up" appear>
            <section v-if="!loading" class="bg-[var(--surface-card,#fff)] rounded-md border border-[var(--surface-border,#e2e8f0)] p-2.5 shadow-[0_0_0_3px_color-mix(in_srgb,var(--primary-color)_7%,transparent)]">
                <div class="flex items-center justify-between mb-2.5">
                    <div class="flex items-center gap-2.5">
                        <div class="grid place-items-center w-10 h-10 rounded-md shrink-0 bg-indigo-500/10 text-indigo-500">
                            <i class="pi pi-chart-bar text-lg" />
                        </div>

                        <div class="flex flex-col leading-tight">
                            <div class="flex items-center gap-2 flex-wrap">
                                <div class="font-bold tracking-tight text-[var(--text-color-secondary)]">Linha do tempo — Hoje</div>
                                <span
                                    v-if="todayFeriado"
                                    class="dash-tl-badge dash-tl-badge--feriado"
                                    :title="`Feriado: ${todayFeriado.nome}`"
                                >
                                    <i class="pi pi-star text-[0.6rem]" />
                                    Feriado{{ todayFeriado.nome ? `: ${todayFeriado.nome}` : '' }}
                                </span>
                                <span
                                    v-else-if="isFolga"
                                    class="dash-tl-badge dash-tl-badge--folga"
                                    title="Hoje não é dia de trabalho na sua agenda — sessões fora do expediente continuam permitidas."
                                >
                                    <i class="pi pi-moon text-[0.6rem]" />
                                    Folga
                                </span>
                            </div>
                            <div class="text-xs font-semibold text-[var(--text-color-secondary)] flex items-center gap-1.5">
                                <span class="pulse-dot w-[15px] h-[5px] rounded-full bg-red-500"></span>
                                Agora: {{ horaAtual }}
                            </div>
                        </div>
                    </div>

                    <span class="text-xs font-semibold text-[var(--text-color-secondary)]">
                        <Button icon="pi pi-cog" severity="secondary" outlined class="rounded-full" title="Ver sua Agenda" @click="$router.push('/therapist/agenda')" label="Agenda" />
                    </span>
                </div>
                <!-- Frame relativo abriga scroll horizontal + eco lateral overlay -->
                <div class="dash-tl-frame mt-2.5 relative">
                    <div ref="tlHScrollEl" class="dash-tl-scroll" @scroll.passive="onTimelineScroll">
                        <div class="dash-tl-inner relative" :style="{ '--m-tl-cols': TL_END - TL_START }">
                            <div class="flex justify-between mb-1">
                                <div v-for="h in hoursRange" :key="h" class="flex-1 text-left">
                                    <span class="text-[0.80rem] text-[var(--text-color-secondary)] font-semibold">{{ h }}h</span>
                                </div>
                            </div>
                            <div class="relative h-10 bg-[var(--surface-ground,#f8fafc)] rounded-md overflow-visible">
                                <div
                                    v-for="ev in timelineEvents"
                                    :key="ev.id"
                                    class="absolute top-[3px] h-[34px] rounded flex items-center px-1.5 overflow-hidden cursor-default min-w-[32px] hover:brightness-110 transition-[filter] duration-150 z-10"
                                    :style="{ ...ev.style, ...(ev.bgColor ? { backgroundColor: ev.bgColor, color: ev.txtColor || '#fff' } : {}) }"
                                    :class="{
                                        'bg-sky-400': !ev.bgColor && ev.tipo === 'reuniao',
                                        'bg-green-500': !ev.bgColor && ev.status === 'realizado',
                                        'bg-[var(--primary-color,#6366f1)]': !ev.bgColor && ev.tipo !== 'reuniao' && ev.status !== 'realizado'
                                    }"
                                    :title="ev.tooltip"
                                >
                                    <span class="text-[0.58rem] font-bold text-white truncate">{{ ev.label }}</span>
                                    <span v-if="ev.badge" class="text-xs ml-auto">{{ ev.badge }}</span>
                                </div>
                                <div class="absolute top-0 h-full flex flex-col items-center pointer-events-none z-20" :style="{ left: nowCursorLeft }">
                                    <div class="w-0.5 h-full bg-red-500 opacity-80" />
                                    <div class="absolute -top-0.5 w-[7px] h-[7px] rounded-full bg-red-500" />
                                </div>
                            </div>
                        </div>
                    </div>

                    <!-- Eco lateral — minimap pulsante de cores. Tracinhos
                         posicionados por tempo, click suaviza scroll até o evento. -->
                    <div
                        v-if="tlEcoState.left.length"
                        class="dash-tl-eco dash-tl-eco--left"
                        :title="`${tlEcoState.left.length} antes — clique pra centralizar`"
                    >
                        <button
                            v-for="ev in tlEcoState.left"
                            :key="`eco-l-${ev.id}`"
                            type="button"
                            class="dash-tl-eco__tick"
                            :style="ecoTickStyle(ev, 'left')"
                            :title="`${_fmtHora(ev.startH)} · ${ev.label}`"
                            @click="scrollToEvent(ev)"
                        />
                    </div>
                    <div
                        v-if="tlEcoState.right.length"
                        class="dash-tl-eco dash-tl-eco--right"
                        :title="`${tlEcoState.right.length} à frente — clique pra centralizar`"
                    >
                        <button
                            v-for="ev in tlEcoState.right"
                            :key="`eco-r-${ev.id}`"
                            type="button"
                            class="dash-tl-eco__tick"
                            :style="ecoTickStyle(ev, 'right')"
                            :title="`${_fmtHora(ev.startH)} · ${ev.label}`"
                            @click="scrollToEvent(ev)"
                        />
                    </div>
                </div>
            </section>
            </Transition>

            <!-- Cards de notificação -->
            <section class="grid grid-cols-1 lg:grid-cols-2 gap-3.5">
                <!-- ══ SKELETON dos cards ══════════════════════════════ -->
                <template v-if="loading">
                    <Skeleton v-for="n in 4" :key="n" width="100%" height="180px" border-radius="6px" />
                    <!-- frase durante carregamento -->
                    <div class="lg:col-span-2">
                        <AppLoadingPhrases action="Carregando seu dashboard..." containerClass="py-8" />
                    </div>
                </template>

                <!-- Wrapper único para stagger via anim-child (duration cobre o delay do último card) -->
                <Transition name="fade-up" appear :duration="700">
                <div v-if="!loading" class="contents">

                <!-- Agendador Online -->
                <div id="card-agendador" class="anim-child [--delay:0ms] dash-card rounded-md" :class="{ '': solicitacoesPendentes > 0 }">
                    <div class="dash-card__head gap-2.5 p-2.5">
                        <div class="grid place-items-center w-10 h-10 rounded-md shrink-0 bg-indigo-500/10 text-indigo-500">
                            <i class="pi pi-inbox text-lg" />
                        </div>
                        <div>
                            <div class="font-bold tracking-tight text-[var(--text-color-secondary)]">Agendamentos Recebidos</div>
                            <div class="dash-card__sub">Solicitações vindas do agendador online</div>
                        </div>
                        <span v-if="solicitacoesPendentes > 0" class="dash-card__badge" style="background: color-mix(in srgb, #ef4444 10%, transparent); color: #ef4444; border: 1px solid color-mix(in srgb, #ef4444 30%, transparent)">{{
                            solicitacoesPendentes
                        }}</span>
                    </div>

                    <div class="dash-card__body">
                        <div v-for="sol in solicitacoes" :key="sol.id" class="dash-item bg-[color-mix(in_srgb,var(--surface-ground)_50%,transparent)] p-2 gap-2 rounded-md">
                            <Avatar :label="sol.initials" shape="square" size="normal" />
                            <div class="min-w-0 flex-1">
                                <div class="dash-item__name">{{ sol.nome }}</div>
                                <div class="dash-item__sub">{{ sol.detalhe }}</div>
                            </div>
                            <div class="dash-item__actions">
                                <button class="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-green-100 text-green-700 text-sm font-bold hover:bg-green-200 transition-colors" @click="aceitarSol(sol.id)">
                                    <i class="pi pi-check text-xs" /> Aceitar
                                </button>
                                <button class="px-2.5 py-1.5 rounded-full bg-red-50 text-red-500 hover:bg-red-100 transition-colors" @click="recusarSol(sol.id)">
                                    <i class="pi pi-times text-xs" />
                                </button>
                            </div>
                        </div>
                        <div v-if="!solicitacoes.length" class="dash-empty"><i class="pi pi-check-circle" /> Nenhuma solicitação pendente</div>
                    </div>

                    <div class="dash-card__foot" @click="$router.push('/therapist/agendamentos-recebidos')">Ver todas →</div>
                </div>

                <!-- Analytics: tempo de 1ª resposta WhatsApp (minhas) -->
                <div id="card-first-response" class="anim-child [--delay:40ms]">
                    <FirstResponseCard :therapistId="ownerId" :showRanking="false" />
                </div>

                <!-- Cadastros externos -->
                <div id="card-cadastros" class="anim-child [--delay:80ms] dash-card">
                    <div class="dash-card__head gap-2.5 p-2.5">
                        <div class="grid place-items-center w-10 h-10 rounded-md shrink-0" style="background: color-mix(in srgb, #0ea5e9 15%, transparent); color: #0ea5e9">
                            <i class="pi pi-user-plus text-lg" />
                        </div>
                        <div>
                            <div class="font-bold tracking-tight text-[var(--text-color-secondary)]">Cadastros Recebidos (Externos)</div>
                            <div class="dash-card__sub">Pacientes que preencheram seus próprios dados</div>
                        </div>
                        <span v-if="cadastrosPendentes > 0" class="dash-card__badge" style="background: color-mix(in srgb, #0ea5e9 10%, transparent); color: #0ea5e9; border: 1px solid color-mix(in srgb, #0ea5e9 30%, transparent)">{{
                            cadastrosPendentes
                        }}</span>
                    </div>

                    <div class="dash-card__body">
                        <div v-for="c in cadastros" :key="c.id" class="dash-item bg-[color-mix(in_srgb,var(--surface-ground)_50%,transparent)] p-2 gap-2 rounded-md">
                            <Avatar :label="c.initials" shape="square" size="normal" />
                            <div class="min-w-0 flex-1">
                                <div class="dash-item__name">{{ c.nome }}</div>
                                <div class="dash-item__sub">{{ c.detalhe }}</div>
                            </div>
                            <button
                                class="w-8 h-8 rounded-full border border-[var(--surface-border)] bg-[var(--surface-ground)] text-[var(--text-color-secondary)] flex items-center justify-center transition-all hover:bg-[var(--primary-color)] hover:text-white hover:border-[var(--primary-color)]"
                                @click="$router.push('/therapist/pacientes/triagem/' + c.id)"
                            >
                                <i class="pi pi-arrow-right text-xs" />
                            </button>
                        </div>
                        <div v-if="!cadastros.length" class="dash-empty"><i class="pi pi-check-circle" /> Nenhum cadastro pendente</div>
                    </div>

                    <div class="dash-card__foot" @click="$router.push('/therapist/patients/cadastro/recebidos')">Gerenciar triagem →</div>
                </div>

                <!-- Recorrências com alerta -->
                <div id="card-recorrencias" class="anim-child [--delay:160ms] dash-card">
                    <div class="dash-card__head gap-2.5 p-2.5">
                        <div class="grid place-items-center w-10 h-10 rounded-md shrink-0" style="background: color-mix(in srgb, #f59e0b 15%, transparent); color: #f59e0b">
                            <i class="pi pi-refresh text-lg" />
                        </div>
                        <div>
                            <div class="font-bold tracking-tight text-[var(--text-color-secondary)]">Recorrências</div>
                            <div class="dash-card__sub">Atenção necessária</div>
                        </div>
                        <span v-if="recAlerta.length" class="dash-card__badge" style="background: color-mix(in srgb, #f59e0b 10%, transparent); color: #f59e0b; border: 1px solid color-mix(in srgb, #f59e0b 30%, transparent)">{{
                            recAlerta.length
                        }}</span>
                    </div>

                    <div class="dash-card__body">
                        <div v-for="r in recAlerta" :key="r.id" class="dash-item bg-[color-mix(in_srgb,var(--surface-ground)_50%,transparent)] p-2 gap-2 rounded-md">
                            <div class="min-w-0 flex-1">
                                <div class="dash-item__name">{{ r.nome }}</div>
                                <div class="dash-item__sub" :class="{ 'text-red-500': r.tipo === 'limite', 'text-amber-500': r.tipo === 'conflito', 'text-sky-500': r.tipo === 'feriado' }">
                                    {{ r.motivo }}
                                </div>
                            </div>
                            <div v-if="r.progresso !== undefined" class="flex items-center gap-2 shrink-0">
                                <div class="w-14 h-1.5 rounded-full bg-[var(--surface-border)] overflow-hidden">
                                    <div class="h-full rounded-full transition-all duration-300" :style="{ width: r.progresso + '%' }" :class="r.progresso > 75 ? 'bg-red-500' : 'bg-[var(--primary-color)]'" />
                                </div>
                                <span class="text-sm text-[var(--text-color-secondary)] whitespace-nowrap">{{ r.sessoesUsadas }}/{{ r.totalSessoes }}</span>
                            </div>
                        </div>
                        <div v-if="!recAlerta.length" class="dash-empty"><i class="pi pi-check-circle" /> Recorrências em dia</div>
                    </div>

                    <div class="dash-card__foot" @click="$router.push('/therapist/agenda/recorrencias')">Ver recorrências →</div>
                </div>

                <!-- Radar da semana -->
                <div id="card-radar" class="anim-child [--delay:240ms] dash-card">
                    <div class="dash-card__head gap-2.5 p-2.5">
                        <div class="grid place-items-center w-10 h-10 rounded-md shrink-0" style="background: color-mix(in srgb, #6366f1 15%, transparent); color: #6366f1">
                            <i class="pi pi-chart-pie text-lg" />
                        </div>
                        <div>
                            <div class="font-bold tracking-tight text-[var(--text-color-secondary)]">Radar da Semana</div>
                            <div class="dash-card__sub">Presença, faltas e reposições</div>
                        </div>
                    </div>

                    <div class="dash-card__body" style="min-height: auto">
                        <div class="flex gap-1.5 items-end h-[88px] mt-6">
                            <div v-for="d in radarSemana" :key="d.dia" class="flex-1 flex flex-col items-center gap-1">
                                <div class="flex-1 w-full flex items-end">
                                    <div class="w-full h-[68px] bg-[var(--surface-ground)] rounded overflow-hidden flex items-end">
                                        <div
                                            class="w-full rounded transition-all duration-500 ease-in-out"
                                            :style="{ height: d.pct + '%' }"
                                            :class="{
                                                'bg-gradient-to-t from-indigo-500 to-indigo-400': d.status === 'ok',
                                                'bg-gradient-to-t from-red-500 to-red-300': d.status === 'falta',
                                                'bg-gradient-to-t from-amber-500 to-amber-300': d.status === 'repo'
                                            }"
                                        />
                                    </div>
                                </div>
                                <span class="text-sm font-semibold" :class="d.isToday ? 'text-[var(--primary-color)] font-extrabold' : 'text-[var(--text-color-secondary)]'">{{ d.dia }}</span>
                                <span class="text-xs text-[var(--text-color-secondary)]">{{ d.total }}</span>
                            </div>
                        </div>
                        <div class="flex gap-3 mt-2 flex-wrap">
                            <span class="flex items-center gap-1.5 text-sm text-[var(--text-color-secondary)] font-semibold"> <span class="w-4 h-2.5 rounded-sm bg-gradient-to-r from-indigo-500 to-indigo-400 shrink-0" />Presentes </span>
                            <span class="flex items-center gap-1.5 text-sm text-[var(--text-color-secondary)] font-semibold"> <span class="w-4 h-2.5 rounded-sm bg-gradient-to-r from-red-500 to-red-300 shrink-0" />Faltas </span>
                            <span class="flex items-center gap-1.5 text-sm text-[var(--text-color-secondary)] font-semibold"> <span class="w-4 h-2.5 rounded-sm bg-gradient-to-r from-amber-500 to-amber-300 shrink-0" />Reposição </span>
                        </div>
                    </div>
                </div>

                </div><!-- /contents wrapper -->
                </Transition>
            </section>

            <!-- Compromissos especiais -->
            <Skeleton v-if="loading" width="100%" height="180px" border-radius="6px" />

            <Transition name="fade-up" appear>
            <section v-if="!loading" class="bg-[var(--surface-card,#fff)] rounded-md border border-[var(--surface-border,#e2e8f0)] p-2.5">
                <div class="flex items-center justify-between">
                    <div class="flex items-center gap-1.5 text-xs font-bold uppercase tracking-[0.06em] text-[var(--text-color-secondary)]"><i class="pi pi-briefcase" /> Compromissos especiais (Em breve)</div>
                    <span
                        v-tooltip.top="{
                            value: 'Em breve!',
                            showDelay: 0,
                            hideDelay: 100
                        }"
                    >
                        <button class="flex items-center gap-1 bg-transparent border-none cursor-pointer text-xs font-semibold text-[var(--primary-color,#6366f1)] p-0" disabled>Ver todos <i class="pi pi-arrow-right" /></button>
                    </span>
                </div>
                <div class="flex flex-col gap-1.5">
                    <div v-for="c in commitments" :key="c.id" class="flex items-center gap-2.5 px-2.5 py-2 rounded-md bg-[var(--surface-ground,#f8fafc)] hover:bg-[var(--surface-hover,#f1f5f9)] transition-colors duration-100">
                        <div
                            class="w-[3px] h-7 rounded-sm shrink-0"
                            :class="{
                                'bg-indigo-500': c.cor === 'blue',
                                'bg-purple-500': c.cor === 'purple',
                                'bg-green-500': c.cor === 'green',
                                'bg-orange-500': c.cor === 'orange',
                                'bg-red-500': c.cor === 'red'
                            }"
                        />
                        <div class="flex-1">
                            <span class="block text-xs font-semibold text-[var(--text-color)]">{{ c.titulo }}</span>
                            <span class="block text-xs text-[var(--text-color-secondary)]">{{ c.sub }}</span>
                        </div>
                        <div class="text-right">
                            <span class="block text-xs font-semibold text-[var(--text-color)]">{{ c.data }}</span>
                            <span
                                class="inline-block text-xs font-bold px-1 py-px rounded mt-0.5"
                                :class="{
                                    'bg-blue-50 text-blue-500': c.status === 'agendado',
                                    'bg-green-50 text-green-600': c.status === 'confirmado',
                                    'bg-amber-50 text-amber-600': c.status === 'pendente'
                                }"
                                >{{ c.statusLabel }}</span
                            >
                        </div>
                    </div>
                </div>
            </section>
            </Transition>

            <Transition name="fade-up" appear>
                <LoadedPhraseBlock v-if="!loading" />
            </Transition>
        </main>

        <!-- Menus de contexto (fora do aside para evitar visibility:hidden) -->
        <Menu ref="calDayMenuRef" :model="calDayMenuItems" :popup="true" />
        <Menu ref="evMenuRef" :model="evMenuItems" :popup="true" />
        <Menu ref="recMenuRef" :model="recMenuItems" :popup="true" />

        <!-- Dialog: Novo Compromisso (aberto pelo menu de contexto do mini calendário) -->
        <AgendaEventDialog
            v-if="agendaDialogOpen"
            v-model="agendaDialogOpen"
            :eventRow="agendaDialogEventRow"
            :initialStartISO="agendaDialogStartISO"
            :initialEndISO="agendaDialogEndISO"
            :ownerId="ownerId"
            :tenantId="clinicTenantId"
            :commitmentOptions="commitmentOptionsNormalized"
            newPatientRoute="/therapist/patients/cadastro"
            @save="onAgendaDialogSave"
            @delete="
                () => {
                    agendaDialogOpen = false;
                    load();
                }
            "
        />

        <!-- Dialog: Prontuário do paciente -->
        <PatientProntuario :key="selectedPatient?.id || 'none'" v-model="prontuarioOpen" :patient="selectedPatient" @close="closeProntuario" />
    </div>
</template>

<style scoped>
/* Aside drawer — transição e comportamento responsivo não cobertos por Tailwind base */
.aside-drawer {
    position: fixed;
    top: calc(56px + var(--notice-banner-height, 0px));
    left: 0;
    height: calc(100dvh - 56px - var(--notice-banner-height, 0px));
    width: min(300px, 85vw);
    z-index: 40;
    overflow-y: auto;
    transition:
        transform 0.25s cubic-bezier(0.4, 0, 0.2, 1),
        visibility 0.25s;
    box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15);
}
@media (min-width: 1280px) {
    .aside-drawer {
        position: fixed;
        top: calc(56px + var(--notice-banner-height, 0px));
        height: calc(100vh - 56px - var(--notice-banner-height, 0px));
        width: 272px;
        transform: none;
        visibility: visible;
        box-shadow: none;
        z-index: auto;
        overflow-y: auto;
    }
}

/* Pulse dot — animação não disponível sem JIT */
.pulse-dot {
    animation: pulse-red 1.5s infinite;
}
@keyframes pulse-red {
    0%,
    100% {
        box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4);
    }
    50% {
        box-shadow: 0 0 0 4px rgba(239, 68, 68, 0);
    }
}

/* ── Aside: Eventos do dia ───────────────────────── */
.aside-ev {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.5rem 0.625rem;
    border-radius: 8px;
    border-left: 3px solid var(--ev-color, var(--primary-color, #6366f1));
    background: var(--surface-ground, #f8fafc);
    cursor: pointer;
    transition:
        background 0.12s,
        box-shadow 0.12s;
}
.aside-ev:hover {
    background: var(--surface-hover, #f1f5f9);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.aside-ev--reuniao {
    --ev-color: #38bdf8;
}
.aside-ev--realizado {
    --ev-color: #22c55e;
}
.aside-ev--default {
    --ev-color: var(--primary-color, #6366f1);
}
.aside-ev--skeleton {
    cursor: default;
    pointer-events: none;
}

.aside-ev__time {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    min-width: 36px;
    flex-shrink: 0;
    gap: 1px;
}
.aside-ev__hora {
    font-size: 0.78rem;
    font-weight: 800;
    color: var(--text-color);
    letter-spacing: -0.01em;
    line-height: 1;
}
.aside-ev__dur {
    font-size: 0.68rem;
    color: var(--text-color-secondary);
    line-height: 1;
}
.aside-ev__badge {
    font-size: 0.68rem;
    font-weight: 600;
    padding: 1px 6px;
    border-radius: 4px;
    background: var(--surface-border, #e2e8f0);
    color: var(--text-color-secondary);
    line-height: 1.5;
    white-space: nowrap;
}

/* ── Aside: Recorrências ativas ──────────────────── */
.aside-rec {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.45rem 0.5rem;
    border-radius: 8px;
    border: 1px solid transparent;
    cursor: pointer;
    transition:
        background 0.12s,
        border-color 0.12s;
}
.aside-rec:hover {
    background: var(--surface-ground, #f8fafc);
    border-color: var(--surface-border, #e2e8f0);
}
.aside-rec--skeleton {
    cursor: default;
    pointer-events: none;
}
.aside-rec__prox {
    font-size: 0.72rem;
    white-space: nowrap;
    flex-shrink: 0;
    padding: 2px 7px;
    border-radius: 999px;
    background: color-mix(in srgb, currentColor 10%, transparent);
}

/* ─── Timeline horizontal — paridade com o Melissa ──────────────
   Range derivado de agenda_regras_semanais + scroll horizontal
   com min-width de slot pra legibilidade + eco lateral pra eventos
   off-screen. CSS escopado ao Dashboard (mesmas classes existem
   no MelissaLayout sob outros nomes — duplicação consciente até
   eventual extração pra componente compartilhado). */
.dash-tl-frame { position: relative; }

.dash-tl-scroll {
    overflow-x: auto;
    overflow-y: visible;
    padding-bottom: 4px;
    scrollbar-width: thin;
    scrollbar-color: var(--surface-300, #cbd5e1) transparent;
}
.dash-tl-scroll::-webkit-scrollbar { height: 6px; }
.dash-tl-scroll::-webkit-scrollbar-track { background: transparent; }
.dash-tl-scroll::-webkit-scrollbar-thumb {
    background: var(--surface-300, #cbd5e1);
    border-radius: 9999px;
}
.dash-tl-inner {
    /* --m-tl-cols inline = TL_END - TL_START. Default 13 cobre 7→20. */
    min-width: calc(var(--m-tl-cols, 13) * 80px);
}

/* Badges (Folga / Feriado) — light theme nativo */
.dash-tl-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border-radius: 9999px;
    font-size: 0.7rem;
    font-weight: 500;
    line-height: 1.2;
    white-space: nowrap;
}
.dash-tl-badge--folga {
    background: var(--surface-100, #f1f5f9);
    color: var(--text-color-secondary, #64748b);
    border: 1px solid var(--surface-300, #cbd5e1);
}
.dash-tl-badge--feriado {
    background: color-mix(in srgb, rgb(217, 119, 6) 12%, transparent);
    color: rgb(180, 83, 9);
    border: 1px solid color-mix(in srgb, rgb(217, 119, 6) 32%, transparent);
}

/* Eco lateral — minimap pulsante */
.dash-tl-eco {
    position: absolute;
    top: 24px;     /* alinha com a barra (descontando linha de horas) */
    bottom: 8px;
    width: 8px;
    z-index: 6;
    pointer-events: auto;
    border-radius: 4px;
    background: color-mix(in srgb, var(--surface-200, #e2e8f0) 70%, transparent);
    border: 1px solid var(--surface-300, #cbd5e1);
    box-shadow: 0 0 0 0 transparent;
    animation: dash-tl-eco-pulse 2400ms ease-in-out infinite;
    transition: opacity 180ms ease;
}
.dash-tl-eco--left  { left: -2px; }
.dash-tl-eco--right { right: -2px; }

.dash-tl-eco__tick {
    position: absolute;
    left: 1px;
    right: 1px;
    height: 4px;
    transform: translateY(-50%);
    border: 0;
    padding: 0;
    border-radius: 2px;
    cursor: pointer;
    opacity: 0.85;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15);
    transition: opacity 140ms ease, transform 140ms ease, height 140ms ease;
}
.dash-tl-eco__tick:hover {
    opacity: 1;
    height: 6px;
    transform: translateY(-50%) scaleX(2.2);
    z-index: 1;
}
.dash-tl-eco--left  .dash-tl-eco__tick:hover { transform-origin: left center; }
.dash-tl-eco--right .dash-tl-eco__tick:hover { transform-origin: right center; }

@keyframes dash-tl-eco-pulse {
    0%, 100% {
        box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary-color, #6366f1) 0%, transparent);
    }
    50% {
        box-shadow: 0 0 10px 1px color-mix(in srgb, var(--primary-color, #6366f1) 28%, transparent);
    }
}
</style>
