<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/views/pages/saas/SaasPlanFeaturesMatrixPage.vue
| Data: 2026
| Local: São Carlos/SP — Brasil
|--------------------------------------------------------------------------
| © 2026 — Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { ref, onMounted, onBeforeUnmount, computed } from 'vue';
import { supabase } from '@/lib/supabase/client';

import Checkbox from 'primevue/checkbox';
import { useToast } from 'primevue/usetoast';
import { useConfirm } from 'primevue/useconfirm';

const toast = useToast();
const confirm = useConfirm();

const loading = ref(false); // carregamento geral (fetch)
const saving = ref(false); // salvando pendências
const hasPending = ref(false);

const plans = ref([]);
const features = ref([]);
const links = ref([]); // estado atual (reflete UI)
const originalLinks = ref([]); // snapshot do banco (para diff / cancelar)

const q = ref('');

const targetFilter = ref('all'); // 'all' | 'clinic' | 'therapist' | 'supervisor'
const targetOptions = [
    { label: 'Todos', value: 'all' },
    { label: 'Clínica', value: 'clinic' },
    { label: 'Terapeuta', value: 'therapist' },
    { label: 'Supervisor', value: 'supervisor' }
];

// trava por célula (evita corrida)
const busySet = ref(new Set());

function cellKey(planId, featureId) {
    return `${planId}::${featureId}`;
}

function isBusy(planId, featureId) {
    return busySet.value.has(cellKey(planId, featureId));
}

function setBusy(planId, featureId, v) {
    const k = cellKey(planId, featureId);
    const next = new Set(busySet.value);
    if (v) next.add(k);
    else next.delete(k);
    busySet.value = next;
}

function isUniqueViolation(err) {
    if (!err) return false;
    if (err.code === '23505') return true;
    const msg = String(err.message || '');
    return msg.includes('duplicate key value') || msg.includes('unique constraint');
}

// set de enablement (usa links do estado da UI)
const enabledSet = computed(() => {
    const s = new Set();
    for (const r of links.value) s.add(`${r.plan_id}::${r.feature_id}`);
    return s;
});

const originalSet = computed(() => {
    const s = new Set();
    for (const r of originalLinks.value) s.add(`${r.plan_id}::${r.feature_id}`);
    return s;
});

const filteredPlans = computed(() => {
    const t = targetFilter.value;
    if (t === 'all') return plans.value;
    return plans.value.filter((p) => p.target === t);
});

const filteredFeatures = computed(() => {
    const term = String(q.value || '')
        .trim()
        .toLowerCase();
    if (!term) return features.value;
    return features.value.filter((f) => {
        const key = String(f.key || '').toLowerCase();
        const desc = String(f.descricao || '').toLowerCase();
        const descEn = String(f.description || '').toLowerCase();
        return key.includes(term) || desc.includes(term) || descEn.includes(term);
    });
});

function targetLabel(t) {
    if (t === 'clinic') return 'Clínica';
    if (t === 'therapist') return 'Terapeuta';
    if (t === 'supervisor') return 'Supervisor';
    return '—';
}

function targetSeverity(t) {
    if (t === 'clinic') return 'info';
    if (t === 'therapist') return 'success';
    if (t === 'supervisor') return 'warn';
    return 'secondary';
}

function planTitle(p) {
    // ✅ Mostrar nome do plano; fallback para key
    return p?.name || p?.plan_name || p?.public_name || p?.key || 'Plano';
}

function markDirtyIfNeeded() {
    // compara tamanhos e conteúdo (set diff)
    const a = enabledSet.value;
    const b = originalSet.value;

    if (a.size !== b.size) {
        hasPending.value = true;
        return;
    }

    for (const k of a) {
        if (!b.has(k)) {
            hasPending.value = true;
            return;
        }
    }

    hasPending.value = false;
}

async function fetchAll() {
    loading.value = true;
    try {
        const [{ data: p, error: ep }, { data: f, error: ef }, { data: pf, error: epf }] = await Promise.all([
            supabase.from('plans').select('*').order('key', { ascending: true }),
            supabase.from('features').select('*').order('key', { ascending: true }),
            supabase.from('plan_features').select('plan_id, feature_id')
        ]);

        if (ep) throw ep;
        if (ef) throw ef;
        if (epf) throw epf;

        plans.value = p || [];
        features.value = f || [];
        links.value = pf || [];
        originalLinks.value = pf || [];
        hasPending.value = false;
    } catch (e) {
        toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || String(e), life: 5000 });
    } finally {
        loading.value = false;
        busySet.value = new Set();
    }
}

function isEnabled(planId, featureId) {
    return enabledSet.value.has(`${planId}::${featureId}`);
}

/**
 * ✅ Toggle agora NÃO salva no banco.
 * Apenas altera o estado local (links) e marca como "pendente".
 */
function toggleLocal(planId, featureId, nextValue) {
    if (loading.value || saving.value) return;
    if (isBusy(planId, featureId)) return;

    setBusy(planId, featureId, true);

    try {
        if (nextValue) {
            if (!enabledSet.value.has(`${planId}::${featureId}`)) {
                links.value = [...links.value, { plan_id: planId, feature_id: featureId }];
            }
        } else {
            links.value = links.value.filter((x) => !(x.plan_id === planId && x.feature_id === featureId));
        }

        markDirtyIfNeeded();
    } finally {
        setBusy(planId, featureId, false);
    }
}

/**
 * ✅ Ação em massa local (sem salvar)
 */
function setAllForPlanLocal(planId, mode) {
    if (!planId) return;
    if (loading.value || saving.value) return;

    const feats = filteredFeatures.value || [];
    if (!feats.length) return;

    if (mode === 'enable') {
        const next = links.value.slice();
        const exists = new Set(next.map((x) => `${x.plan_id}::${x.feature_id}`));

        let changed = 0;
        for (const f of feats) {
            const k = `${planId}::${f.id}`;
            if (!exists.has(k)) {
                next.push({ plan_id: planId, feature_id: f.id });
                exists.add(k);
                changed++;
            }
        }

        links.value = next;
        markDirtyIfNeeded();

        if (!changed) {
            toast.add({ severity: 'info', summary: 'Sem mudanças', detail: 'Todos os recursos filtrados já estavam marcados.', life: 2200 });
        }
        return;
    }

    if (mode === 'disable') {
        const toRemove = new Set(feats.map((f) => `${planId}::${f.id}`));
        const before = links.value.length;
        links.value = links.value.filter((x) => !toRemove.has(`${x.plan_id}::${x.feature_id}`));
        const changed = before - links.value.length;

        markDirtyIfNeeded();

        if (!changed) {
            toast.add({ severity: 'info', summary: 'Sem mudanças', detail: 'Nenhum recurso filtrado estava marcado para remover.', life: 2200 });
        }
    }
}

/**
 * ✅ Confirmação dupla antes de aplicar ação em massa (local)
 * (1) confirma ação
 * (2) confirma impacto (quantidade)
 */
function confirmMassAction(plan, mode) {
    if (!plan?.id) return;

    const feats = filteredFeatures.value || [];
    const qtd = feats.length;
    if (!qtd) {
        toast.add({ severity: 'info', summary: 'Nada a fazer', detail: 'Não há recursos no filtro atual.', life: 2200 });
        return;
    }

    const modeLabel = mode === 'enable' ? 'marcar' : 'desmarcar';
    const modeLabel2 = mode === 'enable' ? 'MARCAR' : 'DESMARCAR';

    confirm.require({
        header: 'Confirmação',
        icon: 'pi pi-exclamation-triangle',
        message: `Você quer realmente ${modeLabel} TODOS os recursos filtrados para o plano "${planTitle(plan)}"?`,
        acceptClass: mode === 'disable' ? 'p-button-danger' : 'p-button-success',
        accept: () => {
            // ✅ importante: deixa o primeiro confirm fechar antes de abrir o segundo
            setTimeout(() => {
                confirm.require({
                    header: 'Confirmação final',
                    icon: 'pi pi-exclamation-triangle',
                    message: `Isso vai ${modeLabel} ${qtd} recurso(s) (apenas na tela) e ficará como "alterações pendentes". Confirmar ${modeLabel2}?`,
                    acceptClass: mode === 'disable' ? 'p-button-danger' : 'p-button-success',
                    accept: () => {
                        setAllForPlanLocal(plan.id, mode); // ✅ aplica local
                    }
                });
            }, 0);
        }
    });
}

function confirmReset() {
    if (!hasPending.value || saving.value || loading.value) return;

    confirm.require({
        header: 'Descartar alterações?',
        icon: 'pi pi-exclamation-triangle',
        message: 'Você quer descartar as alterações pendentes e voltar ao estado do banco?',
        acceptClass: 'p-button-danger',
        accept: () => {
            links.value = (originalLinks.value || []).slice();
            markDirtyIfNeeded();
            toast.add({ severity: 'info', summary: 'Ok', detail: 'Alterações descartadas.', life: 2200 });
        }
    });
}

/**
 * ✅ Persistência: calcula diff entre originalLinks e links
 * - inserts: (UI tem e original não tinha)
 * - deletes: (original tinha e UI removeu)
 */
async function saveChanges() {
    if (loading.value || saving.value) return;
    if (!hasPending.value) {
        toast.add({ severity: 'info', summary: 'Nada a salvar', detail: 'Não há alterações pendentes.', life: 2200 });
        return;
    }

    saving.value = true;
    try {
        const nowSet = enabledSet.value;
        const wasSet = originalSet.value;

        const inserts = [];
        const deletes = [];

        for (const k of nowSet) {
            if (!wasSet.has(k)) {
                const [plan_id, feature_id] = k.split('::');
                inserts.push({ plan_id, feature_id });
            }
        }

        for (const k of wasSet) {
            if (!nowSet.has(k)) {
                const [plan_id, feature_id] = k.split('::');
                deletes.push({ plan_id, feature_id });
            }
        }

        // aplica inserts
        if (inserts.length) {
            const { error } = await supabase.from('plan_features').insert(inserts);
            if (error && !isUniqueViolation(error)) throw error;
        }

        // aplica deletes (em lote por plano)
        if (deletes.length) {
            const byPlan = new Map();
            for (const d of deletes) {
                const arr = byPlan.get(d.plan_id) || [];
                arr.push(d.feature_id);
                byPlan.set(d.plan_id, arr);
            }

            for (const [planId, featureIds] of byPlan.entries()) {
                const { error } = await supabase.from('plan_features').delete().eq('plan_id', planId).in('feature_id', featureIds);

                if (error) throw error;
            }
        }

        // snapshot novo
        originalLinks.value = links.value.slice();
        markDirtyIfNeeded();

        toast.add({ severity: 'success', summary: 'Salvo', detail: 'Alterações aplicadas com sucesso.', life: 2600 });
    } catch (e) {
        toast.add({ severity: 'error', summary: 'Erro ao salvar', detail: e?.message || String(e), life: 5200 });
    } finally {
        saving.value = false;
    }
}

// ── Hero sticky ───────────────────────────────────────────
const heroEl = ref(null);
const heroSentinelRef = ref(null);
const heroMenuRef = ref(null);
const heroStuck = ref(false);
let disconnectStickyObserver = null;

const heroMenuItems = computed(() => [
    { label: 'Recarregar', icon: 'pi pi-refresh', command: fetchAll, disabled: loading.value || saving.value || hasPending.value },
    { label: 'Descartar', icon: 'pi pi-undo', command: confirmReset, disabled: loading.value || saving.value || !hasPending.value },
    { label: 'Salvar alterações', icon: 'pi pi-save', command: saveChanges, disabled: loading.value || !hasPending.value },
    { separator: true },
    {
        label: 'Filtrar por público',
        items: targetOptions.map((o) => ({
            label: o.label,
            command: () => {
                targetFilter.value = o.value;
            }
        }))
    }
]);

onMounted(async () => {
    await fetchAll();

    const sentinel = heroSentinelRef.value;
    if (sentinel) {
        const io = new IntersectionObserver(
            ([entry]) => {
                heroStuck.value = !entry.isIntersecting;
            },
            { rootMargin: `${document.querySelector('.l2-main') ? '0px' : '-56px'} 0px 0px 0px` }
        );
        io.observe(sentinel);
        disconnectStickyObserver = () => io.disconnect();
    }
});

onBeforeUnmount(() => {
    try {
        disconnectStickyObserver?.();
    } catch {}
});
</script>

<template>
    <ConfirmDialog />

    <div class="flex flex-col gap-4 p-4">
        <!-- Header -->
        <div class="cfg-subheader">
            <div class="cfg-subheader__icon"><i class="pi pi-th-large" /></div>
            <div class="min-w-0 flex-1">
                <div class="cfg-subheader__title">Controle de Recursos</div>
                <div class="cfg-subheader__sub">Defina quais recursos cada plano habilita. Mudanças ficam pendentes até salvar.</div>
            </div>
            <div class="hidden xl:flex items-center gap-2 shrink-0">
                <SelectButton v-model="targetFilter" :options="targetOptions" optionLabel="label" optionValue="value" size="small" :disabled="loading || saving" />
                <Button
                    label="Recarregar"
                    icon="pi pi-refresh"
                    severity="secondary"
                    outlined
                    size="small"
                    :loading="loading"
                    :disabled="saving || hasPending"
                    v-tooltip.top="hasPending ? 'Salve ou descarte antes de recarregar.' : ''"
                    @click="fetchAll"
                />
                <Button label="Descartar" icon="pi pi-undo" severity="secondary" outlined size="small" :disabled="loading || saving || !hasPending" @click="confirmReset" />
                <Button label="Salvar alterações" icon="pi pi-save" size="small" :loading="saving" :disabled="loading || !hasPending" @click="saveChanges" />
            </div>
            <div class="flex xl:hidden shrink-0">
                <Button label="Ações" icon="pi pi-ellipsis-v" severity="warn" size="small" aria-haspopup="true" aria-controls="matrix_hero_menu" @click="(e) => heroMenuRef.toggle(e)" />
                <Menu ref="heroMenuRef" id="matrix_hero_menu" :model="heroMenuItems" :popup="true" />
            </div>
        </div>
        <!-- Search -->
        <div>
            <FloatLabel variant="on" class="w-full md:w-[340px]">
                <IconField class="w-full">
                    <InputIcon class="pi pi-search" />
                    <InputText v-model="q" id="features_search" class="w-full pr-10" variant="filled" :disabled="loading || saving" autocomplete="off" />
                </IconField>
                <label for="features_search">Filtrar recursos (key ou descrição)</label>
            </FloatLabel>
        </div>

        <div class="rounded-md border border-[var(--surface-border)] bg-[var(--surface-card)] p-3">
            <div class="flex flex-wrap items-center justify-between gap-2">
                <div class="flex gap-2 items-center flex-wrap">
                    <Tag :value="`Planos: ${filteredPlans.length}`" severity="info" icon="pi pi-list" rounded />
                    <Tag :value="`Recursos: ${filteredFeatures.length}`" severity="success" icon="pi pi-bolt" rounded />
                    <Tag v-if="hasPending" value="Alterações pendentes" severity="warn" icon="pi pi-clock" rounded />
                </div>

                <div class="text-[1rem] text-[var(--text-color-secondary)]">Dica: use a busca para reduzir a lista e aplique ações em massa com confirmação.</div>
            </div>
        </div>

        <Divider class="my-0" />

        <DataTable :value="filteredFeatures" dataKey="id" :loading="loading" stripedRows responsiveLayout="scroll" :scrollable="true" scrollHeight="70vh">
            <Column header="" frozen style="min-width: 28rem">
                <template #body="{ data }">
                    <div class="flex flex-col">
                        <span class="font-medium">{{ data.key }}</span>
                        <div class="text-[1rem] text-[var(--text-color-secondary)] leading-snug mt-1">
                            {{ data.descricao || data.description || '—' }}
                        </div>
                    </div>
                </template>
            </Column>

            <Column v-for="p in filteredPlans" :key="p.id" :style="{ minWidth: '14rem' }">
                <template #header>
                    <div class="flex flex-col items-center gap-2 w-full text-center">
                        <div class="font-semibold truncate w-full" :title="planTitle(p)">
                            {{ planTitle(p) }}
                        </div>
                        <div class="flex items-center justify-center gap-1 flex-wrap">
                            <div class="text-[1rem] text-[var(--text-color-secondary)] truncate" :title="p.key">{{ p.key }}</div>
                            <Tag :value="targetLabel(p.target)" :severity="targetSeverity(p.target)" rounded />
                        </div>
                        <div class="flex gap-2 justify-center">
                            <Button icon="pi pi-check" severity="success" size="small" outlined :disabled="loading || saving" v-tooltip.top="'Marcar todas as features filtradas (fica pendente até salvar)'" @click="confirmMassAction(p, 'enable')" />
                            <Button
                                icon="pi pi-times"
                                severity="danger"
                                size="small"
                                outlined
                                :disabled="loading || saving"
                                v-tooltip.top="'Desmarcar todas as features filtradas (fica pendente até salvar)'"
                                @click="confirmMassAction(p, 'disable')"
                            />
                        </div>
                    </div>
                </template>

                <template #body="{ data }">
                    <div class="flex justify-center">
                        <Checkbox
                            :binary="true"
                            :modelValue="isEnabled(p.id, data.id)"
                            :disabled="loading || saving || isBusy(p.id, data.id)"
                            :aria-label="`Alternar ${p.key} -> ${data.key}`"
                            @update:modelValue="(val) => toggleLocal(p.id, data.id, val)"
                        />
                    </div>
                </template>
            </Column>
        </DataTable>
    </div>
</template>