<script setup>
/*
 * MelissaFinanceiroLancamentos — Página nativa Melissa pros lançamentos
 * financeiros (substitui o embed via MelissaEmbed que duplicava headers).
 *
 * Aplica o blueprint melissa-table-page-blueprint.md:
 *   - Sidebar com stats + filtros (Status / Tipo / Paciente / Período)
 *     + Xs inline + footer fixo "Limpar filtros"
 *   - Main com DataTable lazy paginada (6 colunas + ações por status)
 *
 * Lógica idêntica à FinanceiroPage (composable useFinancialRecords +
 * RPCs + dialogs registrar pagamento e lançamento manual). Só o chrome
 * muda pra eliminar o triplo-header.
 */
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
import { useToast } from 'primevue/usetoast';
import { useConfirm } from 'primevue/useconfirm';
import { supabase } from '@/lib/supabase/client';
import { tenantDb } from '@/lib/supabase/tenantClient';
import { useTenantStore } from '@/stores/tenantStore';
import { useFinancialRecords } from '@/composables/useFinancialRecords';
// DataTable/Column/Dialog/Tag/Select/InputNumber/DatePicker/Textarea/Button: auto via PrimeVueResolver

const emit = defineEmits(['close']);

const { records, loading, error, summary, fetchRecords, markAsPaid, cancelRecord, createManualRecord } = useFinancialRecords();
const toast = useToast();
const confirm = useConfirm();
const tenantStore = useTenantStore();

// ── Breakpoints + drawer mobile (mesmo pattern das demais Melissa Pages) ──
const drawerOpen = ref(false);
const isMobile = ref(false);
let _mqMobile = null;
function _onMqMobileChange(e) {
    isMobile.value = e.matches;
    if (!e.matches) drawerOpen.value = false;
}
function toggleDrawer() { drawerOpen.value = !drawerOpen.value; }
function fecharDrawer() { drawerOpen.value = false; }

// ── Helpers de formatação ─────────────────────────────
const _brl = new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' });
function fmtBRL(v) { return _brl.format(v ?? 0); }
function fmtDate(iso) {
    if (!iso) return '—';
    const d = iso.includes('T') ? new Date(iso) : new Date(iso + 'T00:00:00');
    return new Intl.DateTimeFormat('pt-BR').format(d);
}
function fmtDateTime(iso) {
    if (!iso) return null;
    return new Intl.DateTimeFormat('pt-BR', {
        day: '2-digit', month: '2-digit', year: 'numeric',
        hour: '2-digit', minute: '2-digit'
    }).format(new Date(iso));
}
function isOverdueDate(dueDateIso) {
    if (!dueDateIso) return false;
    return new Date(dueDateIso + 'T00:00:00') < new Date(new Date().toDateString());
}

// ── Pacientes pro filtro ──────────────────────────────
const patients = ref([]);
async function loadPatients() {
    const tenantId = tenantStore.activeTenantId;
    if (!tenantId) return;
    const { data } = await tenantDb().from('patients')
        .select('id, nome_completo, identification_color')
        
        .order('nome_completo');
    patients.value = data ?? [];
}

// ── Opções de filtro / pagamento / tipo ───────────────
const STATUS_FILTER_OPTIONS = [
    { key: 'pending',   label: 'Pendentes',  icon: 'pi pi-clock' },
    { key: 'overdue',   label: 'Vencidos',   icon: 'pi pi-exclamation-circle' },
    { key: 'paid',      label: 'Pagos',      icon: 'pi pi-check-circle' },
    { key: 'cancelled', label: 'Cancelados', icon: 'pi pi-times-circle' }
];

const TYPE_FILTER_OPTIONS = [
    { key: 'receita', label: 'Receita', icon: 'pi pi-arrow-up-right' },
    { key: 'despesa', label: 'Despesa', icon: 'pi pi-arrow-down-left' }
];

const PAYMENT_METHOD_OPTIONS = [
    { label: 'Pix',                value: 'pix' },
    { label: 'Depósito',           value: 'deposito' },
    { label: 'Dinheiro',           value: 'dinheiro' },
    { label: 'Cartão',             value: 'cartao' },
    { label: 'Cartão (maquininha)', value: 'cartao_maquininha' },
    { label: 'Convênio',           value: 'convenio' },
    { label: 'Asaas',              value: 'asaas' }
];

function paymentLabel(method) {
    return PAYMENT_METHOD_OPTIONS.find((o) => o.value === method)?.label ?? method ?? '—';
}

// Abre link de cobrança externa (Asaas/etc) em nova aba.
// noopener/noreferrer pra segurança (gateway não vira janela parent). 2026-05-14.
function openPaymentLink(url) {
    if (!url) return;
    window.open(url, '_blank', 'noopener,noreferrer');
}

// ── Filtros reativos ──────────────────────────────────
const filterStatus = ref(null);
const filterType = ref(null);
const filterPatient = ref(null);
const filterDateRange = ref(null);

function setStatusFilter(s) {
    filterStatus.value = filterStatus.value === s ? null : s;
}
function setTypeFilter(t) {
    filterType.value = filterType.value === t ? null : t;
}

const hasActiveFilters = computed(() =>
    filterStatus.value !== null
    || filterType.value !== null
    || filterPatient.value !== null
    || filterDateRange.value !== null
);

function clearAllFilters() {
    filterStatus.value = null;
    filterType.value = null;
    filterPatient.value = null;
    filterDateRange.value = null;
}

// Aninhamento visual (2026-05-14): records com mesmo agenda_evento_id viram
// "pai + filho(s)" — o mais antigo (created_at) é o pai (sessão); demais
// (multa, taxa de cancelamento, etc) aparecem indentados embaixo. Pai
// sempre antes dos filhos na lista. Records sem agenda_evento_id (avulso
// manual) ficam como itens soltos. Não reordena entre grupos — só dentro
// de cada grupo, preservando ordem de chegada do servidor.
const recordsGrouped = computed(() => {
    const list = records.value || [];
    if (list.length === 0) return list;
    const groupOrder = [];
    const groups = new Map();
    for (const r of list) {
        const key = r.agenda_evento_id || `solo-${r.id}`;
        if (!groups.has(key)) {
            groups.set(key, []);
            groupOrder.push(key);
        }
        groups.get(key).push(r);
    }
    const out = [];
    for (const key of groupOrder) {
        const group = groups
            .get(key)
            .slice()
            .sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
        group.forEach((r, idx) => {
            out.push({ ...r, _isChild: idx > 0 && group.length > 1, _hasChildren: idx === 0 && group.length > 1 });
        });
    }
    // Ordena por paciente pra rowGroupMode='subheader' do PrimeVue agrupar
    // corretamente. Sort estável preserva ordem interna (parent → children
    // do mesmo agenda_evento_id; séries em ordem cronológica).
    out.sort((a, b) => {
        const aName = a.patients?.nome_completo || '~'; // sem paciente vai pro final
        const bName = b.patients?.nome_completo || '~';
        return aName.localeCompare(bName, 'pt-BR');
    });
    return out;
});

// Grupos expandidos (default: todos os pacientes da página atual).
// Watcher recalcula sempre que recordsGrouped muda — quando user troca
// pagina/filtro, todos os grupos da nova lista entram expandidos.
const expandedGroups = ref([]);
watch(
    recordsGrouped,
    (list) => {
        const ids = [...new Set((list || []).map((r) => r.patient_id).filter(Boolean))];
        expandedGroups.value = ids;
    },
    { immediate: true }
);
// Conta lançamentos por paciente na lista atual — exibido no header
// de grupo. Inclui só "pais" (não conta filhos de cobranças extras pra
// não inflar o número).
function _countForPatient(pid) {
    if (!pid) return 0;
    return (recordsGrouped.value || []).filter((r) => r.patient_id === pid && !r._isChild).length;
}

// ── Paginação server-side ─────────────────────────────
const pageFirst = ref(0);
const pageRows = ref(20);
const totalRecords = ref(0);
const hasLoaded = ref(false);

async function applyFilters(resetPage = true) {
    if (resetPage) pageFirst.value = 0;
    const f = { limit: pageRows.value, offset: pageFirst.value };
    if (filterStatus.value) f.status = filterStatus.value;
    if (filterType.value) f.type = filterType.value;
    if (filterPatient.value) f.patient_id = filterPatient.value.id;
    if (filterDateRange.value) {
        const [from, to] = filterDateRange.value;
        if (from) f.due_date_from = from instanceof Date ? from.toISOString().slice(0, 10) : from;
        if (to) f.due_date_to = to instanceof Date ? to.toISOString().slice(0, 10) : to;
    }
    const result = await fetchRecords(f);
    totalRecords.value = result?.total ?? records.value.length;
    hasLoaded.value = true;
}

async function onPageChange(e) {
    pageFirst.value = e.first;
    pageRows.value = e.rows;
    await applyFilters(false);
}

watch([filterStatus, filterType, filterPatient, filterDateRange], () => applyFilters(true), { deep: true });

// ── Status visual config ──────────────────────────────
const STATUS_CFG = {
    pending:   { label: 'Pendente',  severity: 'warn' },
    paid:      { label: 'Pago',      severity: 'success' },
    overdue:   { label: 'Vencido',   severity: 'danger' },
    partial:   { label: 'Parcial',   severity: 'info' },
    cancelled: { label: 'Cancelado', severity: 'secondary' },
    refunded:  { label: 'Estornado', severity: 'secondary', extraClass: 'tag-refunded' }
};

// ── Dialog: Registrar pagamento ───────────────────────
const payDlgVisible = ref(false);
const payDlgRecord = ref(null);
const payDlgMethod = ref(null);
const payDlgLoading = ref(false);

function openPayDialog(record) {
    payDlgRecord.value = record;
    payDlgMethod.value = null;
    payDlgVisible.value = true;
}

async function confirmPayment() {
    if (!payDlgMethod.value || !payDlgRecord.value) return;
    payDlgLoading.value = true;
    try {
        const result = await markAsPaid(payDlgRecord.value.id, payDlgMethod.value);
        if (!result.ok) throw new Error(result.error);
        payDlgVisible.value = false;
        toast.add({
            severity: 'success',
            summary: 'Pagamento registrado',
            detail: `${fmtBRL(payDlgRecord.value.final_amount)} via ${paymentLabel(payDlgMethod.value)}.`,
            life: 3000
        });
    } catch (e) {
        toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || 'Não foi possível registrar o pagamento.', life: 4000 });
    } finally {
        payDlgLoading.value = false;
    }
}

// ── Cancelar cobrança ─────────────────────────────────
function requestCancel(record) {
    confirm.require({
        message: `Deseja cancelar a cobrança de ${fmtBRL(record.final_amount)} para ${record.patients?.nome_completo ?? '—'}? Esta ação não pode ser desfeita.`,
        header: 'Cancelar cobrança',
        icon: 'pi pi-exclamation-triangle',
        rejectLabel: 'Não',
        acceptLabel: 'Sim, cancelar',
        acceptSeverity: 'danger',
        accept: async () => {
            const result = await cancelRecord(record.id);
            if (result.ok) {
                toast.add({ severity: 'info', summary: 'Cancelado', detail: 'Cobrança cancelada com sucesso.', life: 3000 });
            } else {
                toast.add({ severity: 'error', summary: 'Erro', detail: result.error || 'Não foi possível cancelar.', life: 4000 });
            }
        }
    });
}

// ── Dialog: Lançamento manual ─────────────────────────
const manualDlgVisible = ref(false);
const manualDlgLoading = ref(false);
const manualForm = ref({
    patient: null,
    amount: null,
    discount_amount: 0,
    due_date: null,
    payment_method: null,
    notes: ''
});

const manualFinalAmount = computed(() => {
    const a = manualForm.value.amount ?? 0;
    const d = manualForm.value.discount_amount ?? 0;
    return Math.max(0, a - d);
});

function openManualDlg() {
    manualForm.value = { patient: null, amount: null, discount_amount: 0, due_date: null, payment_method: null, notes: '' };
    manualDlgVisible.value = true;
}

async function saveManualRecord() {
    if (!manualForm.value.amount || !manualForm.value.due_date) {
        toast.add({ severity: 'warn', summary: 'Campos obrigatórios', detail: 'Preencha o valor e a data de vencimento.', life: 3500 });
        return;
    }
    manualDlgLoading.value = true;
    try {
        const due = manualForm.value.due_date instanceof Date
            ? manualForm.value.due_date.toISOString().slice(0, 10)
            : manualForm.value.due_date;
        const result = await createManualRecord({
            patient_id: manualForm.value.patient?.id ?? null,
            amount: manualForm.value.amount,
            discount_amount: manualForm.value.discount_amount ?? 0,
            due_date: due,
            payment_method: manualForm.value.payment_method ?? null,
            notes: manualForm.value.notes || null
        });
        if (!result.ok) throw new Error(result.error);
        manualDlgVisible.value = false;
        toast.add({ severity: 'success', summary: 'Lançamento criado', detail: 'Registro financeiro adicionado.', life: 3000 });
        await applyFilters(false);
    } catch (e) {
        toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || 'Não foi possível salvar.', life: 4000 });
    } finally {
        manualDlgLoading.value = false;
    }
}

// ── Lifecycle ─────────────────────────────────────────
onMounted(async () => {
    if (typeof window !== 'undefined' && window.matchMedia) {
        _mqMobile = window.matchMedia('(max-width: 1023px)');
        isMobile.value = _mqMobile.matches;
        _mqMobile.addEventListener('change', _onMqMobileChange);
    }
    // Garante tenant carregado antes de fetch — sem isso, o useFinancialRecords
    // retorna vazio na primeira render direta via URL (bug: lista vazia até
    // navegar pelo menu e remontar o componente).
    if (typeof tenantStore.ensureLoaded === 'function') {
        await tenantStore.ensureLoaded();
    }
    await Promise.all([loadPatients(), applyFilters()]);
});
onBeforeUnmount(() => {
    if (_mqMobile) _mqMobile.removeEventListener('change', _onMqMobileChange);
});
</script>

<template>
    <ConfirmDialog />

    <!-- Drawer host (mobile) — sidebar é teleportada pra cá quando isMobile -->
    <aside
        class="mfl-mobile-drawer"
        :class="{ 'is-open': drawerOpen }"
        v-show="isMobile"
        aria-label="Estatísticas e filtros"
    >
        <div id="mfl-mobile-drawer-target" class="mfl-mobile-drawer__scroll" />
    </aside>
    <Transition name="mfl-drawer-fade">
        <div
            v-if="isMobile && drawerOpen"
            class="mfl-mobile-drawer__backdrop"
            @click="fecharDrawer"
        />
    </Transition>

    <section class="mfl-page">
        <header class="mfl-page__head">
            <button
                class="mfl-menu-btn mfl-menu-btn--mobile-only"
                v-tooltip.bottom="'Estatísticas & filtros'"
                @click="toggleDrawer"
            >
                <i class="pi pi-bars" />
                <span>Menu Lançamentos</span>
            </button>
            <div class="mfl-page__title">
                <i class="pi pi-list mfl-page__title-icon" />
                <span>Lançamentos financeiros</span>
                <span class="mfl-page__count">{{ totalRecords }}</span>
            </div>
            <div class="mfl-page__actions">
                <button
                    class="mfl-act-btn mfl-act-btn--primary"
                    v-tooltip.bottom="'Novo lançamento manual'"
                    @click="openManualDlg"
                >
                    <i class="pi pi-plus" />
                    <span>Lançamento manual</span>
                </button>
                <button
                    class="mfl-head-btn"
                    v-tooltip.bottom="'Recarregar'"
                    :disabled="loading"
                    @click="applyFilters(false)"
                >
                    <i :class="loading ? 'pi pi-spin pi-spinner' : 'pi pi-refresh'" />
                </button>
                <button class="mfl-close" v-tooltip.bottom="'Voltar (Esc)'" @click="emit('close')">
                    <i class="pi pi-times" />
                </button>
            </div>
        </header>

        <!-- Subheader -->
        <div class="mfl-subheader">
            <i class="pi pi-info-circle mfl-subheader__icon" />
            <span class="mfl-subheader__text">
                Lista detalhada de cobranças e lançamentos financeiros — filtre por
                <strong>status</strong>, <strong>tipo</strong>, <strong>paciente</strong>
                ou <strong>período de vencimento</strong>. Click em "Receber" pra
                registrar pagamento.
            </span>
        </div>

        <div class="mfl-body">
            <!-- ═══ COL 1: Stats + filtros ═══ -->
            <Teleport to="#mfl-mobile-drawer-target" :disabled="!isMobile">
            <aside class="mfl-side">
                <div class="mfl-side__scroll">
                    <!-- Stats -->
                    <div class="mfl-w mfl-w--side">
                        <div class="mfl-w__head">
                            <span class="mfl-w__title"><i class="pi pi-chart-bar" /> Estatísticas</span>
                        </div>
                        <div class="mfl-stats">
                            <div class="mfl-stat is-warn">
                                <div class="mfl-stat__val">{{ fmtBRL(summary.totalPending) }}</div>
                                <div class="mfl-stat__lbl">Pendente</div>
                            </div>
                            <div class="mfl-stat is-danger">
                                <div class="mfl-stat__val">{{ fmtBRL(summary.totalOverdue) }}</div>
                                <div class="mfl-stat__lbl">Vencido</div>
                            </div>
                            <div class="mfl-stat is-ok">
                                <div class="mfl-stat__val">{{ fmtBRL(summary.totalPaidThisMonth) }}</div>
                                <div class="mfl-stat__lbl">Pago (mês)</div>
                            </div>
                            <div class="mfl-stat is-neutral">
                                <div class="mfl-stat__val">{{ totalRecords }}</div>
                                <div class="mfl-stat__lbl">Total</div>
                            </div>
                        </div>
                    </div>

                    <!-- Filtro Status -->
                    <div class="mfl-w mfl-w--side">
                        <div class="mfl-w__head">
                            <span class="mfl-w__title"><i class="pi pi-filter" /> Status</span>
                            <button
                                v-if="filterStatus"
                                class="mfl-side__clear-inline"
                                v-tooltip.top="'Limpar filtro de status'"
                                @click="filterStatus = null"
                            >
                                <i class="pi pi-times" />
                            </button>
                        </div>
                        <div class="mfl-side__list">
                            <button
                                v-for="o in STATUS_FILTER_OPTIONS"
                                :key="o.key"
                                class="mfl-side__item"
                                :class="[`is-status-${o.key}`, { 'is-active': filterStatus === o.key }]"
                                @click="setStatusFilter(o.key)"
                            >
                                <i :class="o.icon" />
                                <span>{{ o.label }}</span>
                            </button>
                        </div>
                    </div>

                    <!-- Filtro Tipo -->
                    <div class="mfl-w mfl-w--side">
                        <div class="mfl-w__head">
                            <span class="mfl-w__title"><i class="pi pi-tag" /> Tipo</span>
                            <button
                                v-if="filterType"
                                class="mfl-side__clear-inline"
                                v-tooltip.top="'Limpar filtro de tipo'"
                                @click="filterType = null"
                            >
                                <i class="pi pi-times" />
                            </button>
                        </div>
                        <div class="mfl-side__list">
                            <button
                                v-for="o in TYPE_FILTER_OPTIONS"
                                :key="o.key"
                                class="mfl-side__item"
                                :class="[`is-type-${o.key}`, { 'is-active': filterType === o.key }]"
                                @click="setTypeFilter(o.key)"
                            >
                                <i :class="o.icon" />
                                <span>{{ o.label }}</span>
                            </button>
                        </div>
                    </div>

                    <!-- Filtro Paciente -->
                    <div class="mfl-w mfl-w--side">
                        <div class="mfl-w__head">
                            <span class="mfl-w__title"><i class="pi pi-user" /> Paciente</span>
                            <button
                                v-if="filterPatient"
                                class="mfl-side__clear-inline"
                                v-tooltip.top="'Limpar filtro de paciente'"
                                @click="filterPatient = null"
                            >
                                <i class="pi pi-times" />
                            </button>
                        </div>
                        <Select
                            v-model="filterPatient"
                            :options="patients"
                            optionLabel="nome_completo"
                            filter
                            :filterFields="['nome_completo']"
                            showClear
                            placeholder="Todos os pacientes"
                            class="w-full mfl-side__select"
                            :disabled="patients.length === 0"
                        >
                            <template #option="{ option }">
                                <div class="flex items-center gap-2">
                                    <span
                                        class="h-2 w-2 rounded-full shrink-0"
                                        :style="option.identification_color ? { background: option.identification_color } : { background: 'var(--m-border-strong)' }"
                                    />
                                    <span class="truncate">{{ option.nome_completo }}</span>
                                </div>
                            </template>
                        </Select>
                    </div>

                    <!-- Filtro Período -->
                    <div class="mfl-w mfl-w--side">
                        <div class="mfl-w__head">
                            <span class="mfl-w__title"><i class="pi pi-calendar" /> Período</span>
                            <button
                                v-if="filterDateRange"
                                class="mfl-side__clear-inline"
                                v-tooltip.top="'Limpar filtro de período'"
                                @click="filterDateRange = null"
                            >
                                <i class="pi pi-times" />
                            </button>
                        </div>
                        <DatePicker
                            v-model="filterDateRange"
                            selectionMode="range"
                            showIcon
                            iconDisplay="input"
                            dateFormat="dd/mm/yy"
                            :manualInput="false"
                            placeholder="Vencimento"
                            class="w-full"
                        />
                    </div>
                </div>

                <!-- Footer fixo: Limpar filtros -->
                <Transition name="mfl-clear">
                    <div v-if="hasActiveFilters" class="mfl-side__footer">
                        <button class="mfl-side__clear-all" @click="clearAllFilters">
                            <i class="pi pi-filter-slash" />
                            <span>Limpar filtros</span>
                        </button>
                    </div>
                </Transition>
            </aside>
            </Teleport>

            <!-- ═══ COL 2: DataTable ═══ -->
            <div class="mfl-main">
                <!-- Erro de carregamento -->
                <div v-if="error" class="mfl-error">
                    <i class="pi pi-exclamation-triangle" />
                    <span>{{ error }}</span>
                    <Button
                        icon="pi pi-refresh"
                        severity="danger"
                        text
                        size="small"
                        class="ml-auto"
                        @click="applyFilters(false)"
                    />
                </div>

                <DataTable
                    :value="recordsGrouped"
                    dataKey="id"
                    :loading="loading"
                    lazy
                    paginator
                    :rows="pageRows"
                    :first="pageFirst"
                    :totalRecords="totalRecords"
                    :rowsPerPageOptions="[10, 20, 50]"
                    paginatorTemplate="RowsPerPageDropdown FirstPageLink PrevPageLink CurrentPageReport NextPageLink LastPageLink"
                    currentPageReportTemplate="{first}–{last} de {totalRecords}"
                    scrollable
                    scrollHeight="flex"
                    tableStyle="min-width: 880px"
                    :rowClass="(r) => [r.status === 'overdue' ? 'mfl-row-overdue' : '', r._isChild ? 'mfl-row-child' : '', r._hasChildren ? 'mfl-row-parent' : ''].filter(Boolean).join(' ')"
                    class="mfl-table"
                    rowGroupMode="subheader"
                    groupRowsBy="patient_id"
                    :expandableRowGroups="true"
                    v-model:expandedRowGroups="expandedGroups"
                    @page="onPageChange"
                >
                    <template #groupheader="{ data }">
                        <div class="mfl-group-header">
                            <span
                                class="mfl-row__avatar"
                                :style="data.patients?.identification_color ? { background: data.patients.identification_color } : null"
                            >
                                {{ data.patients?.nome_completo?.[0]?.toUpperCase() ?? '?' }}
                            </span>
                            <span class="mfl-group-header__name">{{ data.patients?.nome_completo ?? 'Sem paciente' }}</span>
                            <span class="mfl-group-header__count">{{ _countForPatient(data.patient_id) }} lançamento(s)</span>
                        </div>
                    </template>
                    <Column header="Paciente" style="min-width: 13rem">
                        <template #body="{ data }">
                            <!-- Em records "filhos" (multa, taxa) do mesmo agenda_evento_id,
                                 esconde avatar+nome e mostra "↳ {descrição}" indentado.
                                 Mesmo paciente do pai logo acima → reduz ruído visual. -->
                            <div v-if="data._isChild" class="mfl-row__child">
                                <i class="pi pi-arrow-right-and-arrow-left-up-down mfl-row__child-icon" />
                                <span class="mfl-row__child-label">{{ data.description || 'Cobrança extra' }}</span>
                            </div>
                            <div v-else class="mfl-row__patient">
                                <span
                                    class="mfl-row__avatar"
                                    :style="data.patients?.identification_color ? { background: data.patients.identification_color } : null"
                                >
                                    {{ data.patients?.nome_completo?.[0]?.toUpperCase() ?? '?' }}
                                </span>
                                <span class="mfl-row__name">{{ data.patients?.nome_completo ?? '—' }}</span>
                            </div>
                        </template>
                    </Column>

                    <Column header="Sessão" style="min-width: 11rem">
                        <template #body="{ data }">
                            <span v-if="data.agenda_eventos" class="mfl-row__session">
                                {{ fmtDateTime(data.agenda_eventos.inicio_em) }}
                            </span>
                            <span v-else class="mfl-row__manual">
                                <i class="pi pi-pencil" />
                                Manual
                            </span>
                        </template>
                    </Column>

                    <Column header="Tipo" style="width: 7rem">
                        <template #body="{ data }">
                            <Tag
                                :value="data.type === 'receita' ? 'Receita' : 'Despesa'"
                                :severity="data.type === 'receita' ? 'success' : 'danger'"
                                class="text-xs"
                            />
                        </template>
                    </Column>

                    <Column header="Valor" style="width: 9rem" sortable field="final_amount">
                        <template #body="{ data }">
                            <div class="flex flex-col">
                                <span class="mfl-row__amount">{{ fmtBRL(data.final_amount) }}</span>
                                <span v-if="data.discount_amount > 0" class="mfl-row__amount-original">
                                    {{ fmtBRL(data.amount) }}
                                </span>
                            </div>
                        </template>
                    </Column>

                    <Column header="Vencimento" style="width: 9rem" sortable field="due_date">
                        <template #body="{ data }">
                            <span
                                class="mfl-row__due"
                                :class="{ 'is-overdue': data.status === 'overdue' || (data.status === 'pending' && isOverdueDate(data.due_date)) }"
                            >
                                <i v-if="data.status === 'overdue'" class="pi pi-exclamation-circle" />
                                {{ fmtDate(data.due_date) }}
                            </span>
                        </template>
                    </Column>

                    <Column header="Status" style="width: 8rem">
                        <template #body="{ data }">
                            <Tag
                                :value="STATUS_CFG[data.status]?.label ?? data.status"
                                :severity="STATUS_CFG[data.status]?.severity ?? 'secondary'"
                                :class="['text-xs', STATUS_CFG[data.status]?.extraClass ?? '']"
                            />
                        </template>
                    </Column>

                    <Column header="Ações" style="width: 11rem; min-width: 11rem">
                        <template #body="{ data }">
                            <div v-if="data.status === 'pending' || data.status === 'overdue'" class="flex items-center gap-2">
                                <!-- Info do método (Asaas/etc): ícone + texto em azul info
                                     na linha de cima; "Ver boleto" como texto-link na linha
                                     de baixo (disabled enquanto integração Asaas não preenche
                                     payment_link, tooltip muda dinâmico). 2026-05-14. -->
                                <div v-if="data.payment_method === 'asaas'" class="mfl-row__pending-asaas">
                                    <div class="mfl-row__pending-method">
                                        <i class="pi pi-link" />
                                        {{ paymentLabel(data.payment_method) }}
                                    </div>
                                    <button
                                        type="button"
                                        class="mfl-row__pending-link"
                                        :disabled="!data.payment_link"
                                        v-tooltip.top="data.payment_link ? 'Abrir link de pagamento' : 'Aguardando integração Asaas'"
                                        @click="openPaymentLink(data.payment_link)"
                                    >
                                        Ver boleto
                                    </button>
                                </div>
                                <!-- Convênio: aguarda fechamento mensal do plano —
                                     pill visual pra distinguir de cobrança particular
                                     direto ao paciente. 2026-05-19. -->
                                <div v-else-if="data.payment_method === 'convenio'" class="mfl-row__pending-convenio">
                                    <i class="pi pi-id-card" />
                                    {{ paymentLabel(data.payment_method) }}
                                </div>
                                <Button
                                    label="Receber"
                                    icon="pi pi-check"
                                    size="small"
                                    class="rounded-full"
                                    @click="openPayDialog(data)"
                                />
                                <Button
                                    icon="pi pi-times"
                                    size="small"
                                    severity="danger"
                                    outlined
                                    class="rounded-full h-7 w-7"
                                    v-tooltip.top="'Cancelar cobrança'"
                                    @click="requestCancel(data)"
                                />
                            </div>
                            <div v-else-if="data.status === 'paid'" class="mfl-row__paid">
                                <div class="mfl-row__paid-method">
                                    <i class="pi pi-check-circle" />
                                    {{ paymentLabel(data.payment_method) }}
                                </div>
                                <div class="mfl-row__paid-date">{{ fmtDate(data.paid_at) }}</div>
                            </div>
                            <span v-else class="mfl-row__none">—</span>
                        </template>
                    </Column>

                    <template #empty>
                        <div class="mfl-empty">
                            <i class="pi pi-wallet mfl-empty__icon" />
                            <div class="mfl-empty__title">Nenhum registro encontrado</div>
                            <div class="mfl-empty__hint">
                                <template v-if="hasActiveFilters">
                                    Ajuste os filtros pra ver mais resultados.
                                </template>
                                <template v-else>
                                    Use "Lançamento manual" pra adicionar um registro.
                                </template>
                            </div>
                            <button class="mfl-act-btn mfl-act-btn--primary mfl-empty__btn" @click="openManualDlg">
                                <i class="pi pi-plus" />
                                <span>Lançamento manual</span>
                            </button>
                        </div>
                    </template>

                    <template #loading>
                        <div class="mfl-table__loading">
                            <i class="pi pi-spin pi-spinner" />
                            <span>Carregando lançamentos…</span>
                        </div>
                    </template>
                </DataTable>
            </div>
        </div>

        <!-- ═══ Dialog: Registrar pagamento ═══ -->
        <Dialog
            v-model:visible="payDlgVisible"
            modal
            :draggable="false"
            header="Registrar pagamento"
            class="w-[94vw] max-w-md"
        >
            <div v-if="payDlgRecord" class="flex flex-col gap-5 pt-1">
                <div class="mfl-pay-summary">
                    <div class="min-w-0">
                        <div class="mfl-pay-summary__name">{{ payDlgRecord.patients?.nome_completo ?? '—' }}</div>
                        <div class="mfl-pay-summary__sub">
                            Vencimento: {{ fmtDate(payDlgRecord.due_date) }}
                        </div>
                    </div>
                    <div class="text-right shrink-0">
                        <div class="mfl-pay-summary__amount">{{ fmtBRL(payDlgRecord.final_amount) }}</div>
                        <div v-if="payDlgRecord.discount_amount > 0" class="mfl-pay-summary__discount">
                            {{ fmtBRL(payDlgRecord.amount) }}
                        </div>
                    </div>
                </div>

                <div>
                    <div class="mfl-pay-label">Método de pagamento</div>
                    <div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
                        <button
                            v-for="opt in PAYMENT_METHOD_OPTIONS"
                            :key="opt.value"
                            type="button"
                            class="mfl-pay-method"
                            :class="{ 'is-selected': payDlgMethod === opt.value }"
                            @click="payDlgMethod = opt.value"
                        >
                            <i
                                :class="{
                                    'pi pi-bolt': opt.value === 'pix',
                                    'pi pi-building': opt.value === 'deposito',
                                    'pi pi-money-bill': opt.value === 'dinheiro',
                                    'pi pi-credit-card': opt.value === 'cartao',
                                    'pi pi-id-card': opt.value === 'convenio'
                                }"
                            />
                            {{ opt.label }}
                        </button>
                    </div>
                </div>
            </div>
            <template #footer>
                <Button
                    label="Cancelar"
                    severity="secondary"
                    outlined
                    class="rounded-full"
                    :disabled="payDlgLoading"
                    @click="payDlgVisible = false"
                />
                <Button
                    label="Confirmar pagamento"
                    icon="pi pi-check"
                    class="rounded-full"
                    :loading="payDlgLoading"
                    :disabled="!payDlgMethod"
                    @click="confirmPayment"
                />
            </template>
        </Dialog>

        <!-- ═══ Dialog: Lançamento manual ═══ -->
        <Dialog
            v-model:visible="manualDlgVisible"
            modal
            :draggable="false"
            header="Lançamento manual"
            class="w-[94vw] max-w-lg"
        >
            <div class="flex flex-col gap-4 pt-1">
                <div>
                    <label class="mfl-pay-label">
                        Paciente <span class="opacity-60 font-normal">(opcional)</span>
                    </label>
                    <Select
                        v-model="manualForm.patient"
                        :options="patients"
                        optionLabel="nome_completo"
                        filter
                        :filterFields="['nome_completo']"
                        showClear
                        placeholder="Selecionar paciente…"
                        class="w-full"
                    >
                        <template #option="{ option }">
                            <div class="flex items-center gap-2">
                                <span
                                    class="h-2 w-2 rounded-full shrink-0"
                                    :style="option.identification_color ? { background: option.identification_color } : { background: 'var(--m-border-strong)' }"
                                />
                                <span>{{ option.nome_completo }}</span>
                            </div>
                        </template>
                    </Select>
                </div>

                <div class="grid grid-cols-2 gap-3">
                    <div>
                        <label class="mfl-pay-label">Valor *</label>
                        <InputNumber
                            v-model="manualForm.amount"
                            mode="currency"
                            currency="BRL"
                            locale="pt-BR"
                            placeholder="R$ 0,00"
                            class="w-full"
                            :min="0"
                        />
                    </div>
                    <div>
                        <label class="mfl-pay-label">Desconto</label>
                        <InputNumber
                            v-model="manualForm.discount_amount"
                            mode="currency"
                            currency="BRL"
                            locale="pt-BR"
                            placeholder="R$ 0,00"
                            class="w-full"
                            :min="0"
                        />
                    </div>
                </div>

                <div class="mfl-pay-summary mfl-pay-summary--small">
                    <span class="mfl-pay-summary__sub">Valor final a cobrar</span>
                    <span class="mfl-pay-summary__amount">{{ fmtBRL(manualFinalAmount) }}</span>
                </div>

                <div>
                    <label class="mfl-pay-label">Data de vencimento *</label>
                    <DatePicker
                        v-model="manualForm.due_date"
                        showIcon
                        iconDisplay="input"
                        dateFormat="dd/mm/yy"
                        :manualInput="false"
                        class="w-full"
                    />
                </div>

                <div>
                    <label class="mfl-pay-label">
                        Método <span class="opacity-60 font-normal">(opcional)</span>
                    </label>
                    <Select
                        v-model="manualForm.payment_method"
                        :options="PAYMENT_METHOD_OPTIONS"
                        optionLabel="label"
                        optionValue="value"
                        showClear
                        placeholder="Selecionar método…"
                        class="w-full"
                    />
                </div>

                <div>
                    <label class="mfl-pay-label">Observações</label>
                    <Textarea
                        v-model="manualForm.notes"
                        placeholder="Anotações internas…"
                        rows="3"
                        class="w-full resize-none"
                    />
                </div>
            </div>
            <template #footer>
                <Button
                    label="Cancelar"
                    severity="secondary"
                    outlined
                    class="rounded-full"
                    :disabled="manualDlgLoading"
                    @click="manualDlgVisible = false"
                />
                <Button
                    label="Salvar lançamento"
                    icon="pi pi-check"
                    class="rounded-full"
                    :loading="manualDlgLoading"
                    @click="saveManualRecord"
                />
            </template>
        </Dialog>
    </section>
</template>

<style scoped>
/* ─── Page chrome ─── */
.mfl-page {
    position: absolute;
    inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) 6px;
    z-index: 40;
    display: flex;
    flex-direction: column;
    background: var(--m-bg-medium);
    backdrop-filter: blur(32px) saturate(160%);
    -webkit-backdrop-filter: blur(32px) saturate(160%);
    border: 1px solid var(--m-border);
    border-radius: 18px;
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4);
    overflow: hidden;
    font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
    color: var(--m-text);
    animation: mfl-page-enter 240ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
@keyframes mfl-page-enter {
    from { opacity: 0; transform: scale(0.985); }
    to   { opacity: 1; transform: scale(1); }
}

.mfl-page__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 18px;
    border-bottom: 1px solid var(--m-border);
    flex-shrink: 0;
    gap: 10px;
}
.mfl-page__title {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 1rem;
    font-weight: 500;
}
.mfl-page__title-icon { color: var(--p-primary-color); font-size: 1.05rem; }
.mfl-page__title > span:not(.mfl-page__count) {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mfl-page__count {
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--m-accent);
    background: var(--m-accent-soft);
    border: 1px solid color-mix(in srgb, var(--m-accent) 35%, transparent);
    padding: 2px 8px;
    border-radius: 999px;
}
.mfl-page__actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

.mfl-close, .mfl-head-btn {
    width: 32px; height: 32px;
    display: grid; place-items: center;
    background: var(--m-bg-soft);
    border: 1px solid var(--m-border);
    color: var(--m-text);
    border-radius: 9px;
    cursor: pointer;
    font-family: inherit;
    transition: background-color 140ms ease;
}
.mfl-close:hover, .mfl-head-btn:hover { background: var(--m-bg-soft-hover); }
.mfl-head-btn > i { font-size: 0.85rem; }
.mfl-head-btn:disabled { opacity: 0.5; cursor: not-allowed; }

.mfl-act-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 32px;
    padding: 0 12px;
    border-radius: 9px;
    cursor: pointer;
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 600;
    transition: background-color 140ms ease, transform 140ms ease;
    background: var(--m-bg-soft);
    border: 1px solid var(--m-border);
    color: var(--m-text);
}
.mfl-act-btn--primary {
    background: var(--m-accent);
    border-color: var(--m-accent);
    color: white;
}
.mfl-act-btn--primary:hover {
    background: color-mix(in srgb, var(--m-accent) 88%, white);
    transform: translateY(-1px);
}
.mfl-act-btn > i { font-size: 0.78rem; }

/* Subheader */
.mfl-subheader {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 10px 18px;
    border-bottom: 1px solid var(--m-border);
    background: var(--m-bg-soft);
    font-size: 0.78rem;
    color: var(--m-text-muted);
    line-height: 1.45;
    flex-shrink: 0;
}
.mfl-subheader__icon { color: var(--p-primary-color); font-size: 0.92rem; flex-shrink: 0; margin-top: 1px; }
.mfl-subheader__text { flex: 1; min-width: 0; }
.mfl-subheader__text strong { color: var(--m-text); font-weight: 600; }

/* Body */
.mfl-body {
    flex: 1;
    display: flex;
    min-height: 0;
    gap: 0;
    padding: 0;
}

/* ─── Sidebar ─── */
.mfl-side {
    width: 280px;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    background: var(--m-bg-soft);
    border-right: 1px solid var(--m-border);
    overflow: hidden;
}
.mfl-side__scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}
.mfl-side__scroll::-webkit-scrollbar { width: 5px; }
.mfl-side__scroll::-webkit-scrollbar-thumb { background: var(--m-border-strong); border-radius: 3px; }

.mfl-side__footer {
    flex-shrink: 0;
    padding: 12px;
    background: var(--m-bg-soft);
    border-top: 1px solid var(--m-border);
}
.mfl-side__clear-all {
    width: 100%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 9px 12px;
    background: var(--m-bg-medium);
    border: 1px solid var(--m-border);
    color: var(--m-text);
    border-radius: 10px;
    cursor: pointer;
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 600;
    transition: background-color 140ms ease, border-color 140ms ease;
}
.mfl-side__clear-all:hover {
    background: var(--m-bg-soft-hover);
    border-color: var(--m-border-strong);
}
.mfl-side__clear-all > i {
    font-size: 0.78rem;
    color: var(--m-text-muted);
}

.mfl-side__clear-inline {
    width: 18px;
    height: 18px;
    display: grid;
    place-items: center;
    background: transparent;
    border: 1px solid color-mix(in srgb, rgb(220, 38, 38) 30%, var(--m-border));
    color: rgb(220, 38, 38);
    border-radius: 4px;
    cursor: pointer;
    transition: background-color 140ms ease, border-color 140ms ease;
}
.mfl-side__clear-inline:hover {
    background: rgba(220, 38, 38, 0.10);
    border-color: rgba(220, 38, 38, 0.55);
}
.mfl-side__clear-inline > i { font-size: 0.6rem; }

.mfl-clear-enter-active,
.mfl-clear-leave-active {
    transition: opacity 220ms ease, transform 220ms ease, max-height 240ms ease;
    overflow: hidden;
}
.mfl-clear-enter-from, .mfl-clear-leave-to {
    opacity: 0; transform: translateY(6px); max-height: 0;
}
.mfl-clear-enter-to, .mfl-clear-leave-from {
    opacity: 1; transform: translateY(0); max-height: 80px;
}

.mfl-w {
    background: var(--m-bg-medium);
    border: 1px solid var(--m-border);
    border-radius: 12px;
    padding: 12px;
}
.mfl-w--side {
    margin: 12px 12px 0;
    flex-shrink: 0;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
.mfl-w--side:last-of-type { margin-bottom: 12px; }
.mfl-w__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 10px;
}
.mfl-w__title {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--m-text-muted);
    font-weight: 600;
}
.mfl-w__title > i { color: var(--m-text-muted); font-size: 0.7rem; }

.mfl-stats {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 6px;
}
.mfl-stat {
    background: var(--m-bg-soft);
    border: 1px solid var(--m-border);
    border-radius: 10px;
    padding: 8px 10px;
}
.mfl-stat__val {
    font-size: 0.95rem;
    font-weight: 700;
    line-height: 1.1;
}
.mfl-stat__lbl {
    font-size: 0.62rem;
    color: var(--m-text-muted);
    margin-top: 4px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.mfl-stat.is-warn .mfl-stat__val   { color: rgb(217, 119, 6); }
.mfl-stat.is-danger .mfl-stat__val { color: rgb(220, 38, 38); }
.mfl-stat.is-ok .mfl-stat__val     { color: rgb(22, 163, 74); }

/* Filter buttons */
.mfl-side__list {
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.mfl-side__item {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    background: transparent;
    border: 1px solid transparent;
    color: var(--m-text);
    border-radius: 10px;
    cursor: pointer;
    font-family: inherit;
    font-size: 0.82rem;
    text-align: left;
    transition: background-color 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
}
.mfl-side__item > i { font-size: 0.78rem; width: 14px; text-align: center; }

/* Status colors: pending amber / overdue red / paid green / cancelled neutral */
.mfl-side__item.is-status-pending {
    background: rgba(217, 119, 6, 0.05);
    border-color: rgba(217, 119, 6, 0.18);
}
.mfl-side__item.is-status-pending > i { color: rgb(217, 119, 6); }
.mfl-side__item.is-status-pending:hover {
    background: rgba(217, 119, 6, 0.10);
    border-color: rgba(217, 119, 6, 0.30);
}
.mfl-side__item.is-active.is-status-pending {
    background: rgba(217, 119, 6, 0.16);
    border-color: rgba(217, 119, 6, 0.55);
    box-shadow: 0 0 0 1px rgba(217, 119, 6, 0.35);
}

.mfl-side__item.is-status-overdue {
    background: rgba(220, 38, 38, 0.05);
    border-color: rgba(220, 38, 38, 0.18);
}
.mfl-side__item.is-status-overdue > i { color: rgb(220, 38, 38); }
.mfl-side__item.is-status-overdue:hover {
    background: rgba(220, 38, 38, 0.10);
    border-color: rgba(220, 38, 38, 0.30);
}
.mfl-side__item.is-active.is-status-overdue {
    background: rgba(220, 38, 38, 0.16);
    border-color: rgba(220, 38, 38, 0.55);
    box-shadow: 0 0 0 1px rgba(220, 38, 38, 0.35);
}

.mfl-side__item.is-status-paid {
    background: rgba(22, 163, 74, 0.05);
    border-color: rgba(22, 163, 74, 0.18);
}
.mfl-side__item.is-status-paid > i { color: rgb(22, 163, 74); }
.mfl-side__item.is-status-paid:hover {
    background: rgba(22, 163, 74, 0.10);
    border-color: rgba(22, 163, 74, 0.30);
}
.mfl-side__item.is-active.is-status-paid {
    background: rgba(22, 163, 74, 0.16);
    border-color: rgba(22, 163, 74, 0.55);
    box-shadow: 0 0 0 1px rgba(22, 163, 74, 0.35);
}

.mfl-side__item.is-status-cancelled {
    background: var(--m-bg-soft);
    border-color: var(--m-border);
}
.mfl-side__item.is-status-cancelled > i { color: var(--m-text-muted); }
.mfl-side__item.is-status-cancelled:hover {
    background: var(--m-bg-soft-hover);
    border-color: var(--m-border-strong);
}
.mfl-side__item.is-active.is-status-cancelled {
    background: var(--m-bg-soft-hover);
    border-color: var(--m-border-strong);
    box-shadow: 0 0 0 1px var(--m-border-strong);
}

/* Tipo: receita verde / despesa vermelho */
.mfl-side__item.is-type-receita {
    background: rgba(22, 163, 74, 0.05);
    border-color: rgba(22, 163, 74, 0.18);
}
.mfl-side__item.is-type-receita > i { color: rgb(22, 163, 74); }
.mfl-side__item.is-type-receita:hover {
    background: rgba(22, 163, 74, 0.10);
    border-color: rgba(22, 163, 74, 0.30);
}
.mfl-side__item.is-active.is-type-receita {
    background: rgba(22, 163, 74, 0.16);
    border-color: rgba(22, 163, 74, 0.55);
    box-shadow: 0 0 0 1px rgba(22, 163, 74, 0.35);
}

.mfl-side__item.is-type-despesa {
    background: rgba(220, 38, 38, 0.05);
    border-color: rgba(220, 38, 38, 0.18);
}
.mfl-side__item.is-type-despesa > i { color: rgb(220, 38, 38); }
.mfl-side__item.is-type-despesa:hover {
    background: rgba(220, 38, 38, 0.10);
    border-color: rgba(220, 38, 38, 0.30);
}
.mfl-side__item.is-active.is-type-despesa {
    background: rgba(220, 38, 38, 0.16);
    border-color: rgba(220, 38, 38, 0.55);
    box-shadow: 0 0 0 1px rgba(220, 38, 38, 0.35);
}

/* Select de Paciente na sidebar */
.mfl-side__select :deep(.p-select) {
    width: 100%;
    background: var(--m-bg-soft);
    border-radius: 9px;
    height: 36px;
}

/* ─── Main + DataTable ─── */
.mfl-main {
    flex: 1;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    padding: 12px;
    gap: 10px;
}

.mfl-error {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 14px;
    background: rgba(220, 38, 38, 0.10);
    border: 1px solid rgba(220, 38, 38, 0.30);
    border-radius: 10px;
    color: rgb(220, 38, 38);
    font-size: 0.85rem;
    flex-shrink: 0;
}

.mfl-table {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.mfl-table :deep(.p-datatable) {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    background: transparent;
    border: 1px solid var(--m-border);
    border-radius: 10px;
    overflow: hidden;
}
.mfl-table :deep(.p-datatable-table-container) {
    flex: 1;
    min-height: 0;
    background: transparent;
}
.mfl-table :deep(.p-datatable-thead),
.mfl-table :deep(.p-datatable-thead > tr) { background: transparent !important; }
.mfl-table :deep(.p-datatable-thead > tr > th) {
    background: var(--p-content-background) !important;
    color: var(--m-text);
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 10px 14px;
    border-bottom: 1px solid var(--m-border);
}
.mfl-table :deep(.p-datatable-tbody > tr) {
    background: transparent;
    color: var(--m-text);
    transition: background-color 140ms ease;
}
.mfl-table :deep(.p-datatable-tbody > tr > td) {
    padding: 10px 14px;
    border-bottom: 1px solid var(--m-border);
    background: transparent;
    vertical-align: middle;
    font-size: 0.85rem;
}
.mfl-table :deep(.p-datatable-tbody > tr:hover) { background: var(--m-bg-soft-hover); }
.mfl-table :deep(.p-datatable-tbody > tr.mfl-row-overdue) {
    background: rgba(220, 38, 38, 0.04);
}
.mfl-table :deep(.p-datatable-tbody > tr.mfl-row-overdue:hover) {
    background: rgba(220, 38, 38, 0.08);
}
/* Aninhamento visual (2026-05-14): pai ganha border-bottom mais discreto,
   filho herda fundo sutil + sem border-top → parece "continuação" do pai. */
.mfl-table :deep(.p-datatable-tbody > tr.mfl-row-parent > td) {
    border-bottom-style: dashed !important;
    border-bottom-color: var(--m-border, rgba(255, 255, 255, 0.08)) !important;
}
.mfl-table :deep(.p-datatable-tbody > tr.mfl-row-child) {
    background: color-mix(in srgb, var(--p-primary-color) 4%, transparent);
}
.mfl-table :deep(.p-datatable-tbody > tr.mfl-row-child > td) {
    border-top: none !important;
}
.mfl-table :deep(.p-datatable-tbody > tr.mfl-row-child:hover) {
    background: color-mix(in srgb, var(--p-primary-color) 8%, transparent);
}

.mfl-table :deep(.p-datatable-loading-overlay) {
    background: color-mix(in srgb, var(--m-bg-medium) 70%, transparent);
    backdrop-filter: blur(2px);
}
.mfl-table__loading {
    display: inline-flex; align-items: center; gap: 8px;
    color: var(--m-text); font-size: 0.85rem;
}

/* Paginator */
.mfl-table :deep(.p-paginator) {
    background: var(--m-bg-medium);
    border: none;
    border-top: 1px solid var(--m-border);
    padding: 8px 12px;
    justify-content: center;
    flex-wrap: wrap;
    gap: 6px;
}
.mfl-table :deep(.p-paginator-current) {
    color: var(--m-text-muted);
    font-size: 0.78rem;
    background: transparent;
    border: none;
    padding: 0 6px;
}
.mfl-table :deep(.p-paginator-first),
.mfl-table :deep(.p-paginator-prev),
.mfl-table :deep(.p-paginator-next),
.mfl-table :deep(.p-paginator-last),
.mfl-table :deep(.p-paginator-page) {
    min-width: 30px; height: 30px;
    color: var(--m-text);
    background: transparent;
    border: 1px solid var(--m-border);
    border-radius: 8px;
    font-size: 0.8rem;
}
.mfl-table :deep(.p-paginator-page.p-paginator-page-selected) {
    background: var(--m-accent-soft);
    border-color: var(--m-accent-strong);
    color: var(--m-accent);
}

/* Row content */
/* Group header — paciente como subheader da DataTable, com avatar
   pequeno + nome + contagem. Click no chevron (auto via PrimeVue
   expandableRowGroups) expande/contrai o bloco do paciente. */
.mfl-group-header {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-weight: 600;
}
.mfl-group-header__name {
    color: var(--text-color);
    font-size: 0.92rem;
}
.mfl-group-header__count {
    font-size: 0.72rem;
    color: var(--text-color-secondary);
    font-weight: 500;
    padding: 2px 8px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--surface-border), transparent 40%);
}
.mfl-row__patient {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}
/* Bloco "filho" (multa/taxa do mesmo agenda_evento): indent + ícone setinha. */
.mfl-row__child {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-left: 22px;
    min-width: 0;
}
.mfl-row__child-icon {
    color: var(--m-text-muted);
    font-size: 0.65rem;
    transform: scaleY(-1);
    flex-shrink: 0;
}
.mfl-row__child-label {
    font-size: 0.82rem;
    font-style: italic;
    color: var(--m-text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.mfl-row__avatar {
    width: 28px; height: 28px;
    border-radius: 50%;
    background: var(--m-accent-strong);
    color: white;
    font-size: 0.7rem;
    font-weight: 700;
    display: grid; place-items: center;
    flex-shrink: 0;
}
.mfl-row__name {
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.mfl-row__session { color: var(--m-text); }
.mfl-row__manual {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.74rem;
    color: var(--m-text-muted);
    background: var(--m-bg-soft);
    border: 1px solid var(--m-border);
    padding: 2px 8px;
    border-radius: 999px;
}
.mfl-row__manual > i { font-size: 0.62rem; }

.mfl-row__amount {
    font-weight: 600;
    color: var(--m-text);
}
.mfl-row__amount-original {
    font-size: 0.7rem;
    color: var(--m-text-muted);
    text-decoration: line-through;
}

.mfl-row__due {
    color: var(--m-text);
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.mfl-row__due.is-overdue {
    color: rgb(220, 38, 38);
    font-weight: 600;
}
.mfl-row__due > i { font-size: 0.7rem; }

.mfl-row__paid {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.mfl-row__paid-method {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.74rem;
    color: rgb(22, 163, 74);
    font-weight: 500;
}
.mfl-row__paid-date {
    font-size: 0.7rem;
    color: var(--m-text-muted);
}
.mfl-row__pending-asaas {
    display: flex;
    flex-direction: column;
    gap: 2px;
}
/* Pill "Convênio" — visual distinto do Asaas (violeta) pra deixar claro
   que essa cobrança aguarda fechamento mensal do plano, não webhook. */
.mfl-row__pending-convenio {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.74rem;
    color: rgb(124, 58, 237); /* violet-600 */
    font-weight: 500;
}
.mfl-row__pending-method {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.74rem;
    color: rgb(37, 99, 235); /* azul info — cobrança aguardando, não paga */
    font-weight: 500;
}
/* "Ver boleto" como texto-link (sem botão visual). Habilitado quando
   payment_link existe — vira underline + cursor pointer. Disabled hoje
   enquanto integração Asaas não preenche — tooltip explica. 2026-05-14. */
.mfl-row__pending-link {
    background: none;
    border: none;
    padding: 0;
    font-size: 0.7rem;
    color: rgb(37, 99, 235);
    cursor: pointer;
    text-align: left;
    font-family: inherit;
}
.mfl-row__pending-link:hover:not(:disabled) {
    text-decoration: underline;
}
.mfl-row__pending-link:disabled {
    color: var(--m-text-muted);
    cursor: not-allowed;
    opacity: 0.7;
}
.mfl-row__none {
    color: var(--m-text-faint);
    font-style: italic;
    font-size: 0.78rem;
}

/* Empty state */
.mfl-empty {
    margin: 24px 0;
    padding: 56px 28px;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    color: var(--m-text-muted);
    border: 2px dashed var(--m-border-strong);
    border-radius: 12px;
    background: color-mix(in srgb, var(--m-bg-soft) 40%, transparent);
    gap: 8px;
}
.mfl-empty__icon { font-size: 2rem; color: var(--m-text-faint); }
.mfl-empty__title { font-size: 0.92rem; font-weight: 600; color: var(--m-text); }
.mfl-empty__hint { font-size: 0.78rem; }
.mfl-empty__btn { margin-top: 8px; }

/* ─── Dialog: Pay summary + method buttons ─── */
.mfl-pay-summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 14px;
    border: 1px solid var(--m-border);
    background: var(--m-bg-soft);
    border-radius: 10px;
}
.mfl-pay-summary--small {
    padding: 8px 12px;
}
.mfl-pay-summary__name {
    font-weight: 600;
    color: var(--m-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.mfl-pay-summary__sub {
    font-size: 0.74rem;
    color: var(--m-text-muted);
    margin-top: 2px;
}
.mfl-pay-summary__amount {
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--m-text);
}
.mfl-pay-summary__discount {
    font-size: 0.7rem;
    color: var(--m-text-muted);
    text-decoration: line-through;
}

.mfl-pay-label {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--m-text);
    display: block;
    margin-bottom: 6px;
}

.mfl-pay-method {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 10px 12px;
    background: var(--m-bg-soft);
    border: 1px solid var(--m-border);
    border-radius: 10px;
    color: var(--m-text-muted);
    cursor: pointer;
    font-family: inherit;
    font-size: 0.85rem;
    font-weight: 500;
    transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease;
}
.mfl-pay-method:hover {
    border-color: color-mix(in srgb, var(--p-primary-color) 40%, var(--m-border));
}
.mfl-pay-method.is-selected {
    background: color-mix(in srgb, var(--p-primary-color) 12%, transparent);
    border-color: var(--p-primary-color);
    color: var(--p-primary-color);
}
.mfl-pay-method > i { font-size: 1rem; }

/* Estornado tag (roxo — sem severity nativo) */
:deep(.tag-refunded) {
    background: rgb(168, 85, 247) !important;
    color: white !important;
}

/* ─── Botão "Menu" mobile (abre drawer com sidebar) ─── */
.mfl-menu-btn {
    display: none;
    height: 32px;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    background: var(--m-accent);
    border: 1px solid var(--m-accent);
    color: white;
    padding: 0 11px;
    border-radius: 9px;
    cursor: pointer;
    font-family: inherit;
    font-size: 0.78rem;
    font-weight: 600;
    transition: background-color 140ms ease, transform 140ms ease;
}
.mfl-menu-btn:hover {
    background: color-mix(in srgb, var(--m-accent) 88%, white);
    transform: translateY(-1px);
}
.mfl-menu-btn > i { font-size: 0.85rem; }

/* ─── Drawer mobile (Teleport target) ─────────────────────
   Pattern espelhado do AppMenu/layout-sidebar do Rail:
   - .xx-mobile-drawer = flex column, altura 100vh
   - __scroll = container do Teleport (flex: 1, sem scroll proprio,
     overflow hidden — o scroll fica num filho interno)
   - .xx-side teleportada = flex column ocupando todo o espaco,
     com __scroll interno (flex: 1, overflow-y auto) e __footer
     (flex-shrink: 0) que naturalmente fica colado no bottom. */
.mfl-mobile-drawer {
    position: fixed;
    top: 0;
    left: 0;
    height: 100vh;
    height: 100dvh;
    width: min(360px, 88vw);
    z-index: 80;
    background: var(--m-bg-medium);
    backdrop-filter: blur(28px) saturate(160%);
    -webkit-backdrop-filter: blur(28px) saturate(160%);
    border-right: 1px solid var(--m-border);
    transform: translateX(-100%);
    transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1);
    color: var(--m-text);
    font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
    display: flex;
    flex-direction: column;
}
.mfl-mobile-drawer.is-open { transform: translateX(0); }
.mfl-mobile-drawer__scroll {
    flex: 1;
    min-height: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

/* Sidebar teleportada — vira a "casca" flex column do drawer */
.mfl-mobile-drawer__scroll .mfl-side {
    flex: 1;
    min-height: 0;
    width: 100%;
    overflow: hidden;
    background: transparent;
    border-right: none;
    display: flex;
    flex-direction: column;
}
/* O scroll interno vive aqui — só os cards rolam, o footer NÃO.
   Padding compensa a remoção do padding do __scroll do host. */
.mfl-mobile-drawer__scroll .mfl-side__scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    scrollbar-width: thin;
    scrollbar-color: var(--m-border-strong) transparent;
}
.mfl-mobile-drawer__scroll .mfl-side__scroll::-webkit-scrollbar { width: 5px; }
.mfl-mobile-drawer__scroll .mfl-side__scroll::-webkit-scrollbar-thumb {
    background: var(--m-border-strong);
    border-radius: 3px;
}
.mfl-mobile-drawer__scroll .mfl-w--side {
    margin: 0;
    flex-shrink: 0;
}
.mfl-mobile-drawer__scroll .mfl-w--side:last-of-type { margin-bottom: 0; }
/* Footer fixo no bottom do drawer — flex-shrink:0 + estar fora do
   __scroll garante que sempre fica visivel, sem precisar de sticky. */
.mfl-mobile-drawer__scroll .mfl-side__footer {
    flex-shrink: 0;
    margin: 0;
    padding: 12px;
    background: var(--m-bg-medium);
    border-top: 1px solid var(--m-border);
    backdrop-filter: blur(24px) saturate(160%);
    -webkit-backdrop-filter: blur(24px) saturate(160%);
}

.mfl-mobile-drawer__backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    z-index: 79;
}
.mfl-drawer-fade-enter-active,
.mfl-drawer-fade-leave-active { transition: opacity 200ms ease; }
.mfl-drawer-fade-enter-from,
.mfl-drawer-fade-leave-to { opacity: 0; }

/* ─── Mobile (<1024px) ─── */
@media (max-width: 1023px) {
    /* Sidebar saiu pro drawer via Teleport — body fica só com .mfl-main. */
    .mfl-body { flex-direction: column; padding: 0; }
    .mfl-main { width: 100%; padding: 8px; }
    .mfl-page__title > span:first-of-type { display: none; }
    .mfl-page__title-icon { display: none; }
    .mfl-menu-btn--mobile-only { display: inline-flex; }
    .mfl-act-btn--primary span { display: none; }
    .mfl-act-btn--primary { width: 32px; padding: 0; justify-content: center; }
}
</style>
