<!-- src/views/pages/billing/PlanFeaturesMatrixPage.vue -->
<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>
  <Toast />
  <ConfirmDialog />

  <!-- Sentinel -->
  <div ref="heroSentinelRef" class="h-px" />

  <!-- Hero sticky -->
  <div
    ref="heroEl"
    class="sticky mx-3 md:mx-4 mb-4 z-20 overflow-hidden rounded-md border border-[var(--surface-border)] bg-[var(--surface-card)] p-5"
    :style="{ top: 'var(--layout-sticky-top, 56px)' }"
  >
    <div class="absolute inset-0 pointer-events-none overflow-hidden" aria-hidden="true">
      <div class="absolute rounded-full blur-[70px] w-72 h-72 -top-16 -right-20 bg-emerald-400/10" />
      <div class="absolute rounded-full blur-[70px] w-80 h-80 top-10 -left-24 bg-indigo-400/10" />
    </div>

    <div class="relative z-10 flex items-center justify-between gap-3 flex-wrap">
      <div class="min-w-0">
        <div class="text-[1rem] font-bold tracking-tight text-[var(--text-color)]">Controle de Recursos</div>
        <div class="text-[1rem] text-[var(--text-color-secondary)] mt-0.5">Defina quais recursos cada plano habilita. Mudanças ficam pendentes até salvar.</div>
      </div>

      <!-- Ações desktop (≥ 1200px) -->
      <div class="hidden xl:flex items-center gap-2 flex-wrap">
        <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>

      <!-- Ações mobile (< 1200px) -->
      <div class="flex xl:hidden">
        <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>
  </div>

  <!-- content -->
  <div class="px-3 md:px-4 pb-8 flex flex-col gap-4">

    <!-- 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>