<template>
  <div class="eli-tabela">
    <EliTabelaDebug :isDev="isDev" :menuAberto="menuAberto" :menuPopupPos="menuPopupPos">
      <div>paginaAtual: {{ paginaAtual }}</div>
      <div>limit: {{ registrosPorConsulta }}</div>
      <div>texto_busca: {{ (valorBusca || '').trim() }}</div>
      <div>filtrosAvancadosAtivos: {{ JSON.stringify(filtrosAvancadosAtivos) }}</div>
      <div>quantidadeTotal: {{ quantidade }}</div>
    </EliTabelaDebug>

    <EliTabelaCabecalho
      v-if="exibirBusca || temAcoesCabecalho"
      :exibirBusca="exibirBusca"
      :exibirBotaoFiltroAvancado="exibirFiltroAvancado"
      :valorBusca="valorBusca"
      :acoesCabecalho="acoesCabecalho"
      @buscar="atualizarBusca"
      @colunas="abrirModalColunas"
      @filtroAvancado="abrirModalFiltro"
    />

    <EliTabelaModalColunas
      :aberto="modalColunasAberto"
      :rotulosColunas="rotulosColunas"
      :configInicial="configColunas"
      :colunas="tabela.colunas"
      @fechar="fecharModalColunas"
      @salvar="salvarModalColunas"
    />

    <EliTabelaModalFiltroAvancado
      :aberto="modalFiltroAberto"
      :filtrosBase="tabela.filtroAvancado ?? []"
      :modelo="filtrosUi"
      @fechar="fecharModalFiltro"
      @limpar="limparFiltrosAvancados"
      @salvar="salvarFiltrosAvancados"
    />

    <table class="eli-tabela__table">
      <EliTabelaHead
        :colunas="colunasEfetivas"
        :temAcoes="temAcoes"
        :temColunasInvisiveis="temColunasInvisiveis"
        :colunaOrdenacao="colunaOrdenacao"
        :direcaoOrdenacao="direcaoOrdenacao"
        @alternar-ordenacao="alternarOrdenacao"
      />

      <tbody v-if="carregando || Boolean(erro) || !linhas.length">
        <tr>
          <td :colspan="totalColunas" style="padding: 0;">
            <EliTabelaEstados
              :carregando="carregando"
              :erro="erro"
              :mensagemVazio="tabela.mensagemVazio"
              style="border: none; border-radius: 0;"
            />
          </td>
        </tr>
      </tbody>

      <EliTabelaBody
        v-else
        :colunas="colunasEfetivas"
        :colunasInvisiveis="colunasInvisiveisEfetivas"
        :temColunasInvisiveis="temColunasInvisiveis"
        :linhasExpandidas="linhasExpandidas"
        :linhas="linhasPaginadas"
        :temAcoes="temAcoes"
        :menuAberto="menuAberto"
        :possuiAcoes="possuiAcoes"
        :toggleMenu="toggleMenu"
        :alternarLinhaExpandida="alternarLinhaExpandida"
      />
    </table>

    <EliTabelaMenuAcoes
      ref="menuPopup"
      :menuAberto="menuAberto"
      :posicao="menuPopupPos"
      :acoes="menuAberto === null ? [] : acoesDisponiveisPorLinha(menuAberto)"
      :linha="menuAberto === null ? null : linhasPaginadas[menuAberto]"
      @executar="({ acao, linha }) => { menuAberto = null; acao.acao(linha as never); }"
    />

    <EliTabelaPaginacao
      v-if="totalPaginas > 1 && quantidadeFiltrada > 0"
      :pagina="paginaAtual"
      :totalPaginas="totalPaginas"
      :maximoBotoes="tabela.maximo_botoes_paginacao"
      @alterar="irParaPagina"
    />
  </div>
</template>

<script lang="ts">
/**
 * EliTabela
 * Componente de tabela consultável com busca, paginação, ordenação e ações por linha.
 */

/** Dependências do Vue (Composition API) */
import {
  computed,
  defineComponent,
  onBeforeUnmount,
  onMounted,
  PropType,
  ref,
  watch,
} from "vue";
/** Enum de códigos de resposta utilizado na consulta */
import { codigosResposta } from "p-respostas";
/** Componentes auxiliares */
import EliTabelaCabecalho from "./EliTabelaCabecalho.vue";
import EliTabelaEstados from "./EliTabelaEstados.vue";
import EliTabelaDebug from "./EliTabelaDebug.vue";
import EliTabelaHead from "./EliTabelaHead.vue";
import EliTabelaBody from "./EliTabelaBody.vue";
import EliTabelaMenuAcoes from "./EliTabelaMenuAcoes.vue";
import EliTabelaPaginacao from "./EliTabelaPaginacao.vue";
import EliTabelaModalColunas from "./EliTabelaModalColunas.vue";
import EliTabelaModalFiltroAvancado from "./EliTabelaModalFiltroAvancado.vue";
import type { tipoEliColuna } from "./types-eli-tabela";
/** Tipos da configuração/contrato da tabela */
import type { tipoEliTabelaConsulta } from "./types-eli-tabela";
import type { tipoFiltro } from "./types-eli-tabela";
// operadores usados no tipo de configuração; o operador aplicado vem travado no filtroAvancado.
import {
  carregarConfigColunas,
  salvarConfigColunas,
  type EliTabelaColunasConfig,
} from "./colunasStorage";

import {
  carregarFiltroAvancado,
  salvarFiltroAvancado,
  limparFiltroAvancado,
} from "./filtroAvancadoStorage";

export default defineComponent({
  name: "EliTabela",
  inheritAttrs: false,
  components: {
    EliTabelaCabecalho,
    EliTabelaEstados,
    EliTabelaDebug,
    EliTabelaHead,
    EliTabelaBody,
    EliTabelaMenuAcoes,
    EliTabelaPaginacao,
    EliTabelaModalColunas,
    EliTabelaModalFiltroAvancado,
  },
  props: {
    /** Configuração principal da tabela (colunas, consulta e ações) */
    tabela: {
      type: Object as PropType<tipoEliTabelaConsulta<any>>,
      required: true,
    },
  },
  setup(props) {
    /** Flag para habilitar elementos de debug */
    const isDev = import.meta.env.DEV;
    /** Estados de carregamento/erro e dados retornados */
    const carregando = ref(false);
    const erro = ref<string | null>(null);
    const linhas = ref<unknown[]>([]);
    const quantidade = ref<number>(0);

    /** Controle de visibilidade das ações por linha */
    const acoesVisiveis = ref<boolean[][]>([]);
    /** Estado do menu de ações (aberto, elemento e posição) */
    const menuAberto = ref<number | null>(null);
    // O componente EliTabelaMenuAcoes expõe `menuEl` (ref do elemento <ul>)
    const menuPopup = ref<{ menuEl: { value: HTMLElement | null } } | null>(null);
    const menuPopupPos = ref({ top: 0, left: 0 });

    /** Filtros e ordenação */
    const valorBusca = ref<string>("");
    const paginaAtual = ref(1);
    const colunaOrdenacao = ref<string | null>(null);
    const direcaoOrdenacao = ref<"asc" | "desc">("asc");

    /** Filtro avançado (config + estado modal) */
    const modalFiltroAberto = ref(false);
    type LinhaFiltroUI<T> = {
      coluna: keyof T;
      valor: any;
    };

    const filtrosUi = ref<Array<LinhaFiltroUI<any>>>(carregarFiltroAvancado<any>(props.tabela.nome) as any);

    function abrirModalFiltro() {
      modalFiltroAberto.value = true;
    }

    function fecharModalFiltro() {
      modalFiltroAberto.value = false;
    }

    function limparFiltrosAvancados() {
      filtrosUi.value = [];
      limparFiltroAvancado(props.tabela.nome);
      modalFiltroAberto.value = false;
      // Se o usuário estiver usando filtro avançado, a busca deixa de ter efeito.
      // Mantemos a regra combinatória (busca tem prioridade), então limpamos a busca.
      valorBusca.value = "";
      if (paginaAtual.value !== 1) paginaAtual.value = 1;
      else void carregar();
    }

    function salvarFiltrosAvancados(novo: any[]) {
      filtrosUi.value = (novo ?? []) as any;
      salvarFiltroAvancado(props.tabela.nome, (novo ?? []) as any);
      modalFiltroAberto.value = false;
      // Ao aplicar filtros, limpamos a busca para garantir que os filtros sejam efetivos.
      // (busca tem prioridade sobre filtros)
      valorBusca.value = "";
      if (paginaAtual.value !== 1) paginaAtual.value = 1;
      else void carregar();
    }

    const filtrosAvancadosAtivos = computed<tipoFiltro[]>(() => {
      // Operador vem travado na definição (`tabela.filtroAvancado`).
      const base = (props.tabela.filtroAvancado ?? []) as Array<{ coluna: string; operador: any }>;

      return (filtrosUi.value ?? [])
        .filter((f) => f && f.coluna !== undefined)
        .map((f) => {
          const b = base.find((x) => String(x.coluna) === String(f.coluna));
          if (!b) return null;

          return {
            coluna: String(b.coluna),
            operador: b.operador as any,
            valor: (f as any).valor,
          } as tipoFiltro;
        })
        .filter(Boolean) as tipoFiltro[];
    });

    /** Alias reativo da prop tabela */
    const tabela = computed(() => props.tabela);

    /** Exibição da busca e ações do cabeçalho */
    const exibirBusca = computed(() => Boolean(props.tabela.mostrarCaixaDeBusca));
    const acoesCabecalho = computed(() => props.tabela.acoesTabela ?? []);
    const temAcoesCabecalho = computed(() => acoesCabecalho.value.length > 0);

    /** Colunas: visibilidade/ordem com persistência */
    const modalColunasAberto = ref(false);
    const configColunas = ref<EliTabelaColunasConfig>(
      carregarConfigColunas(props.tabela.nome)
    );

    /** Linhas expandidas (para exibir colunas invisíveis) */
    const linhasExpandidas = ref<Record<number, boolean>>({});

    const rotulosColunas = computed(() => props.tabela.colunas.map((c) => c.rotulo));

    const colunasInvisiveisEfetivas = computed(() => {
      const colunas = props.tabela.colunas as Array<tipoEliColuna<any>>;

      const configTemDados =
        (configColunas.value.visiveis?.length ?? 0) > 0 ||
        (configColunas.value.invisiveis?.length ?? 0) > 0;

      const invisiveisBaseRotulos = configTemDados
        ? configColunas.value.invisiveis ?? []
        : colunas.filter((c) => c.visivel === false).map((c) => c.rotulo);

      const invisiveisSet = new Set(invisiveisBaseRotulos);
      const base = colunas.filter((c) => invisiveisSet.has(c.rotulo));

      // ordenação: usa a lista (salva ou derivada do default) e adiciona novas ao final
      const ordemSalva = invisiveisBaseRotulos;
      const mapa = new Map<string, tipoEliColuna<any>>();
      for (const c of base) {
        if (!mapa.has(c.rotulo)) mapa.set(c.rotulo, c);
      }

      const ordenadas: Array<tipoEliColuna<any>> = [];
      for (const r of ordemSalva) {
        const c = mapa.get(r);
        if (c) ordenadas.push(c);
      }
      for (const c of base) {
        if (!ordenadas.includes(c)) ordenadas.push(c);
      }

      return ordenadas;
    });

    const temColunasInvisiveis = computed(() => colunasInvisiveisEfetivas.value.length > 0);

    const colunasEfetivas = computed(() => {
      const colunas = props.tabela.colunas;
      const todosRotulos = rotulosColunas.value;

      const configTemDados =
        (configColunas.value.visiveis?.length ?? 0) > 0 ||
        (configColunas.value.invisiveis?.length ?? 0) > 0;

      const invisiveisBaseRotulos = configTemDados
        ? configColunas.value.invisiveis ?? []
        : (props.tabela.colunas as Array<tipoEliColuna<any>>)
            .filter((c) => c.visivel === false)
            .map((c) => c.rotulo);

      const invisiveisSet = new Set(invisiveisBaseRotulos);

      // base visiveis: remove invisiveis
      const visiveisBaseRotulos = todosRotulos.filter((r) => !invisiveisSet.has(r));
      const visiveisSet = new Set(visiveisBaseRotulos);

      // aplica ordem salva; novas (sem definicao) entram no fim, respeitando ordem original
      const ordemSalva = configTemDados ? configColunas.value.visiveis ?? [] : [];
      const ordemFinal: string[] = [];

      for (const r of ordemSalva) {
        if (visiveisSet.has(r)) ordemFinal.push(r);
      }
      for (const r of visiveisBaseRotulos) {
        if (!ordemFinal.includes(r)) ordemFinal.push(r);
      }

      // mapeia rótulo -> coluna, preservando duplicatas (se existirem) pelo primeiro match.
      // OBS: pressupoe rotulo unico; se repetir, comportamento fica indefinido.
      const mapa = new Map<string, any>();
      for (const c of colunas) {
        if (!mapa.has(c.rotulo)) mapa.set(c.rotulo, c);
      }

      return ordemFinal.map((r) => mapa.get(r)).filter(Boolean);
    });

    const totalColunas = computed(() => {
      return (
        colunasEfetivas.value.length +
        (temAcoes.value ? 1 : 0) +
        (temColunasInvisiveis.value ? 1 : 0)
      );
    });

    function abrirModalColunas() {
      modalColunasAberto.value = true;
    }

    function fecharModalColunas() {
      modalColunasAberto.value = false;
    }

    function salvarModalColunas(cfg: EliTabelaColunasConfig) {
      configColunas.value = cfg;
      salvarConfigColunas(props.tabela.nome, cfg);
      modalColunasAberto.value = false;

      // ao mudar colunas, fecha detalhes expandidos
      linhasExpandidas.value = {};
    }

    function alternarLinhaExpandida(indice: number) {
      const atual = Boolean(linhasExpandidas.value[indice]);
      linhasExpandidas.value = {
        ...linhasExpandidas.value,
        [indice]: !atual,
      };
    }

    /** Registros por consulta (normaliza para inteiro positivo) */
    const registrosPorConsulta = computed(() => {
      const valor = props.tabela.registros_por_consulta;
      if (typeof valor === "number" && valor > 0) {
        return Math.floor(valor);
      }
      return 10;
    });

    /** Total de páginas calculado com base no total retornado pela API */
    const totalPaginas = computed(() => {
      const limite = registrosPorConsulta.value;
      if (!limite || limite <= 0) return 1;
      const total = quantidade.value ?? 0;
      if (!total) return 1;
      return Math.max(1, Math.ceil(total / limite));
    });

    /** As linhas já vêm paginadas do backend */
    const linhasPaginadas = computed(() => linhas.value ?? []);

    /** Quantidade exibida é a quantidade total retornada pela consulta */
    const quantidadeFiltrada = computed(() => quantidade.value ?? 0);

    /** Indica se existem ações por linha */
    const temAcoes = computed(() => (props.tabela.acoesLinha ?? []).length > 0);

    const exibirFiltroAvancado = computed(() => (props.tabela.filtroAvancado ?? []).length > 0);

    /** Sequencial para evitar race conditions entre consultas */
    let carregamentoSequencial = 0;

    /** Calcula a posição do menu de ações na viewport */
    function atualizarPosicaoMenu(anchor: HTMLElement) {
      const rect = anchor.getBoundingClientRect();
      const gap = 8;

      // Alinha no canto inferior direito do botão.
      // Se estourar a tela para baixo, abre para cima.
      const alturaMenu = menuPopup.value?.menuEl?.value?.offsetHeight ?? 0;
      const larguraMenu = menuPopup.value?.menuEl?.value?.offsetWidth ?? 180;

      let top = rect.bottom + gap;
      const left = rect.right - larguraMenu;

      if (alturaMenu && top + alturaMenu > window.innerHeight - gap) {
        top = rect.top - gap - alturaMenu;
      }

      menuPopupPos.value = {
        top: Math.max(gap, Math.round(top)),
        left: Math.max(gap, Math.round(left)),
      };
    }

    /** Fecha o menu quando ocorre clique fora */
    function handleClickFora(evento: MouseEvent) {
      if (menuAberto.value === null) return;

      const alvo = evento.target as Node;
      if (menuPopup.value?.menuEl?.value && menuPopup.value.menuEl.value.contains(alvo)) return;

      if (import.meta.env.DEV) {
        // eslint-disable-next-line no-console
        console.log("[EliTabela] click fora => fechar menu", { menuAberto: menuAberto.value });
      }

      menuAberto.value = null;
    }

    /** Alterna ordenação e recarrega os dados */
    function alternarOrdenacao(chave?: string) {
      if (!chave) return;

      if (colunaOrdenacao.value === chave) {
        direcaoOrdenacao.value = direcaoOrdenacao.value === "asc" ? "desc" : "asc";
        void carregar();
        return;
      }

      colunaOrdenacao.value = chave;
      direcaoOrdenacao.value = "asc";
      if (paginaAtual.value !== 1) {
        paginaAtual.value = 1;
      } else {
        void carregar();
      }
    }

    /** Atualiza a busca e reinicia paginação, se necessário */
    function atualizarBusca(texto: string) {
      if (valorBusca.value === texto) return;

      valorBusca.value = texto;
      if (paginaAtual.value !== 1) {
        paginaAtual.value = 1;
      } else {
        void carregar();
      }
    }

    /** Navega para a página solicitada com limites */
    function irParaPagina(pagina: number) {
      const alvo = Math.min(Math.max(1, pagina), totalPaginas.value);
      if (alvo !== paginaAtual.value) {
        paginaAtual.value = alvo;
      }
    }

    /**
     * Lista ações visíveis por linha, respeitando regras sync/async de `exibir`.
     */
    function acoesDisponiveisPorLinha(i: number) {
      const acoesLinha = props.tabela.acoesLinha ?? [];
      const visibilidade = acoesVisiveis.value[i] ?? [];

      return acoesLinha
        .map((acao, indice) => {
          const fallbackVisivel =
            acao.exibir === undefined
              ? true
              : typeof acao.exibir === "boolean"
              ? acao.exibir
              : false;

          return {
            acao,
            indice,
            visivel: visibilidade[indice] ?? fallbackVisivel,
          };
        })
        .filter((item) => item.visivel);
    }

    /** Informa se a linha possui ações disponíveis */
    function possuiAcoes(i: number) {
      return acoesDisponiveisPorLinha(i).length > 0;
    }

    /** Abre/fecha o menu de ações da linha e posiciona o popup */
    function toggleMenu(i: number, evento?: MouseEvent) {
      if (!possuiAcoes(i)) return;

      if (import.meta.env.DEV) {
        // eslint-disable-next-line no-console
        console.log("[EliTabela] toggleMenu (antes)", { i, atual: menuAberto.value });
      }

      if (menuAberto.value === i) {
        menuAberto.value = null;

        if (import.meta.env.DEV) {
          // eslint-disable-next-line no-console
          console.log("[EliTabela] toggleMenu => fechou", { i });
        }
        return;
      }

      menuAberto.value = i;

      if (import.meta.env.DEV) {
        // eslint-disable-next-line no-console
        console.log("[EliTabela] toggleMenu => abriu", { i });
      }

      // posiciona assim que abrir
      const anchor = (evento?.currentTarget as HTMLElement | null) ?? null;
      if (anchor) {
        // primeiro posicionamento (antes do menu medir)
        atualizarPosicaoMenu(anchor);
        // reposiciona no próximo frame para pegar altura real
        requestAnimationFrame(() => atualizarPosicaoMenu(anchor));
      }
    }

    /**
     * Executa consulta, aplica filtros e resolve visibilidade de ações.
     * Usa o contador sequencial para ignorar respostas antigas.
     */
    async function carregar() {
      const idCarregamento = ++carregamentoSequencial;
      carregando.value = true;
      erro.value = null;
      acoesVisiveis.value = [];
      menuAberto.value = null;
      linhasExpandidas.value = {};

      const limite = Math.max(1, registrosPorConsulta.value);
      const offset = (paginaAtual.value - 1) * limite;

      const parametrosConsulta: {
        filtros?: tipoFiltro[];
        coluna_ordem?: never;
        direcao_ordem?: "asc" | "desc";
        offSet: number;
        limit: number;
        texto_busca?: string;
      } = {
        offSet: offset,
        limit: limite,
      };

      // Regra combinatória definida: busca tem prioridade.
      const busca = (valorBusca.value ?? "").trim();
      if (busca) {
        parametrosConsulta.texto_busca = busca;
      } else {
        const filtros = filtrosAvancadosAtivos.value;
        if (filtros.length) parametrosConsulta.filtros = filtros;
      }

      if (colunaOrdenacao.value) {
        parametrosConsulta.coluna_ordem = colunaOrdenacao.value as never;
        parametrosConsulta.direcao_ordem = direcaoOrdenacao.value;
      }

      if (import.meta.env.DEV) {
        // eslint-disable-next-line no-console
        console.log("[EliTabela] consulta(parametros)", parametrosConsulta);
      }

      try {
        const tabelaConfig = props.tabela;
        const res = await tabelaConfig.consulta(parametrosConsulta);

        if (idCarregamento !== carregamentoSequencial) return;

        if (res.cod !== codigosResposta.sucesso) {
          linhas.value = [];
          quantidade.value = 0;
          erro.value = res.mensagem;
          return;
        }

        const valores = res.valor?.valores ?? [];
        const total = (res.valor as any)?.quantidade ?? valores.length;

        linhas.value = valores;
        quantidade.value = Number(total) || 0;

        const totalPaginasRecalculado = Math.max(1, Math.ceil((quantidade.value || 0) / limite));
        if (paginaAtual.value > totalPaginasRecalculado) paginaAtual.value = totalPaginasRecalculado;

        const acoesLinhaConfiguradas = tabelaConfig.acoesLinha ?? [];
        if (!acoesLinhaConfiguradas.length) {
          acoesVisiveis.value = [];
          return;
        }

        const preResultado = valores.map(() =>
          acoesLinhaConfiguradas.map((acao) => {
            if (acao.exibir === undefined) return true;
            if (typeof acao.exibir === "boolean") return acao.exibir;
            return false;
          })
        );

        acoesVisiveis.value = preResultado;

        const visibilidade = await Promise.all(
          valores.map(async (linha) =>
            Promise.all(
              acoesLinhaConfiguradas.map(async (acao) => {
                if (acao.exibir === undefined) return true;
                if (typeof acao.exibir === "boolean") return acao.exibir;

                try {
                  const resultado = acao.exibir(linha as never);
                  return Boolean(await Promise.resolve(resultado));
                } catch {
                  return false;
                }
              })
            )
          )
        );

        if (idCarregamento === carregamentoSequencial) {
          acoesVisiveis.value = visibilidade;
        }
      } catch (e) {
        if (idCarregamento !== carregamentoSequencial) return;

        linhas.value = [];
        quantidade.value = 0;
        erro.value = e instanceof Error ? e.message : "Erro ao carregar dados.";
      } finally {
        if (idCarregamento === carregamentoSequencial) {
          carregando.value = false;
        }
      }
    }

    /** Ciclo de vida: registra listener global e carrega dados iniciais */
    onMounted(() => {
      document.addEventListener("click", handleClickFora);
      void carregar();
    });

    /** Ciclo de vida: remove listener global */
    onBeforeUnmount(() => {
      document.removeEventListener("click", handleClickFora);
    });

    /** Watch: ao desabilitar busca, limpa termo e recarrega */
    watch(
      () => props.tabela.mostrarCaixaDeBusca,
      (mostrar) => {
        if (!mostrar && valorBusca.value) {
          valorBusca.value = "";
          if (paginaAtual.value !== 1) {
            paginaAtual.value = 1;
          } else {
            void carregar();
          }
        }
      }
    );

    /** Watch: mudança de página dispara nova consulta */
    watch(paginaAtual, (nova, antiga) => {
      if (nova !== antiga) void carregar();
    });

    /** Watch: troca de configuração reseta estados e recarrega */
    watch(
      () => props.tabela,
      () => {
        menuAberto.value = null;
        colunaOrdenacao.value = null;
        direcaoOrdenacao.value = "asc";
        valorBusca.value = "";
        modalColunasAberto.value = false;
        modalFiltroAberto.value = false;
        configColunas.value = carregarConfigColunas(props.tabela.nome);
        filtrosUi.value = carregarFiltroAvancado<any>(props.tabela.nome) as any;
        linhasExpandidas.value = {};
        if (paginaAtual.value !== 1) {
          paginaAtual.value = 1;
        } else {
          void carregar();
        }
      }
    );

    /** Watch: alteração do limite de registros reinicia paginação */
    watch(
      () => props.tabela.registros_por_consulta,
      () => {
        if (paginaAtual.value !== 1) {
          paginaAtual.value = 1;
        } else {
          void carregar();
        }
      }
    );

    /** Watch: mudança nas linhas fecha o menu aberto */
    watch(linhas, () => {
      menuAberto.value = null;
      linhasExpandidas.value = {};
    });

    /** Exposição para o template (state, computed, helpers e actions) */
    return {
      // state
      isDev,
      tabela,
      carregando,
      erro,
      linhas,
      linhasPaginadas,
      filtrosAvancadosAtivos,
      quantidadeFiltrada,
      quantidade,
      menuAberto,
      valorBusca,
      paginaAtual,
      colunaOrdenacao,
      direcaoOrdenacao,
      totalPaginas,
      registrosPorConsulta,

      // computed
      exibirBusca,
      exibirFiltroAvancado,
      acoesCabecalho,
      temAcoesCabecalho,
      temAcoes,
      totalColunas,
      colunasEfetivas,
      rotulosColunas,
      modalColunasAberto,
      configColunas,

      temColunasInvisiveis,
      colunasInvisiveisEfetivas,
      linhasExpandidas,

      abrirModalColunas,
      abrirModalFiltro,
      fecharModalColunas,
      salvarModalColunas,

      modalFiltroAberto,
      filtrosUi,
      salvarFiltrosAvancados,
      limparFiltrosAvancados,
      fecharModalFiltro,

      alternarLinhaExpandida,

      // actions
      alternarOrdenacao,
      atualizarBusca,
      irParaPagina,
      acoesDisponiveisPorLinha,
      possuiAcoes,
      toggleMenu,

      // popup
      menuPopup,
      menuPopupPos,
    };
  },
});
</script>

<style src="./EliTabela.css"></style>
