"use client";

import { useMemo, useState } from "react";
import {
  BarChart3, Boxes, Calendar, Check, ChevronRight, CreditCard, FileText, Folder,
  Gauge, Globe, Handshake, LayoutGrid, Megaphone, MessageCircle, Minus, MonitorPlay, Package,
  Percent, Search, Settings, ShoppingCart, Sparkles, Store, Tag, Users, Wallet, X,
} from "lucide-react";
import { canCompanyRole, type CompanyRole } from "@/lib/company-context";
import {
  CATALOGO, ROTULO_NIVEL, niveisDoModulo,
  type AcaoCatalogo, type ModuloCatalogo, type NivelModulo, type PaginaCatalogo,
} from "@/lib/catalogo-permissoes";

/**
 * O painel de permissões do modal de funcionários.
 *
 * A versão anterior era uma grade de 22 cartões abertos ao mesmo tempo, e o que
 * o dono realmente queria configurar — a tela — morava dois cliques abaixo, atrás
 * de "Configurações avançadas", em cada um dos 22. Somando, 110 chaves para
 * caçar de olho. A queixa foi literal: "confuso, não dá pra entender".
 *
 * Três coisas mudaram:
 *
 * 1. TEM BUSCA. Digitar "caixa" leva ao Fluxo de caixa sem saber em que módulo
 *    ele mora. Era o caminho mais curto para deixar de caçar.
 *
 * 2. UMA LINHA POR MÓDULO, fechada. Quem procura Financeiro não rola por
 *    Marketing. Abrir o módulo já mostra as TELAS dele — não existe mais o
 *    degrau "avançadas" entre o dono e a coisa que ele veio configurar.
 *
 * 3. UM VOCABULÁRIO SÓ. Antes eram três: "Sim/Não/Padrão" na ação,
 *    "Abrir/Fechar/Padrão" na tela e a escada no módulo. Agora tela e ação
 *    falam igual: Padrão, Liberar, Bloquear. A escada continua existindo para
 *    o módulo inteiro, porque ali a escolha é mesmo de outra natureza, e fica
 *    escrita como o que é: o atalho de configurar tudo de uma vez.
 *
 * O que a tela mostra como resultado não é recalculado aqui: sai de
 * `canCompanyRole`, o mesmo resolvedor que a API e o banco usam. Reimplementar
 * a regra para pintar um selo seria criar uma segunda verdade, e a que aparece
 * para o admin é a que ele acredita ter configurado.
 */

type Props = {
  papel: CompanyRole;
  ajustes: Record<string, boolean | NivelModulo>;
  aoMudar: (proximo: Record<string, boolean | NivelModulo>) => void;
  podeEditar: boolean;
  /* Chaves de recursos que esta empresa ainda não tem. Elas saem da lista, da
     busca e do placar: oferecer permissão para uma tela que não existe só
     confundiria o dono. */
  ocultas?: readonly string[];
};

/* Ícone e cor por módulo. Mora aqui, e não no catálogo, porque é aparência:
   o catálogo decide quem pode o quê e é espelhado no banco, que não tem
   opinião sobre ícones. */
const APARENCIA: Record<string, { Icone: typeof Package; cor: string }> = {
  dashboard: { Icone: Gauge, cor: "var(--ks-sky)" },
  pdv: { Icone: Store, cor: "var(--ks-emerald)" },
  products: { Icone: Package, cor: "var(--ks-brand-600)" },
  categories: { Icone: Folder, cor: "var(--ks-sky)" },
  catalog: { Icone: Globe, cor: "var(--ks-teal)" },
  marketing: { Icone: Megaphone, cor: "var(--ks-pink)" },
  clients: { Icone: Users, cor: "var(--ks-lime)" },
  quotes: { Icone: FileText, cor: "var(--ks-amber)" },
  orders: { Icone: ShoppingCart, cor: "var(--ks-brand-600)" },
  production: { Icone: LayoutGrid, cor: "var(--ks-teal)" },
  materials: { Icone: Boxes, cor: "var(--ks-lime)" },
  suppliers: { Icone: Handshake, cor: "var(--ks-amber)" },
  financial: { Icone: Wallet, cor: "var(--ks-emerald)" },
  pricing: { Icone: Tag, cor: "var(--ks-rose)" },
  commissions: { Icone: Percent, cor: "var(--ks-pink)" },
  reports: { Icone: BarChart3, cor: "var(--ks-sky)" },
  agenda: { Icone: Calendar, cor: "var(--ks-brand-600)" },
  chats: { Icone: MessageCircle, cor: "var(--ks-brand-600)" },
  whatsapp: { Icone: MessageCircle, cor: "var(--ks-emerald)" },
  ia: { Icone: Sparkles, cor: "var(--ks-pink)" },
  company: { Icone: Settings, cor: "var(--ks-rose)" },
  users: { Icone: Users, cor: "var(--ks-rose)" },
  subscription: { Icone: CreditCard, cor: "var(--ks-amber)" },
  digital: { Icone: MonitorPlay, cor: "var(--ks-pink)" },
};

const PADRAO: Record<string, never> = {};
const PADRAO_DE_OCULTAS: readonly string[] = [];

/* Busca sem acento e sem maiúscula: quem digita "precificacao" tem de achar
   "Precificação". */
function enxuto(texto: string) {
  /* A faixa dos acentos combinantes vai escrita em código, não com os
     caracteres crus: são invisíveis no editor, e a primeira coisa que um
     copiar e colar estraga. */
  return texto.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
}

function acaoCombina(acao: AcaoCatalogo, alvo: string) {
  return enxuto(`${acao.rotulo} ${acao.descricao ?? ""} ${acao.chave}`).includes(alvo);
}

export default function PainelPermissoes({ papel, ajustes, aoMudar, podeEditar, ocultas = PADRAO_DE_OCULTAS }: Props) {
  const catalogo = useMemo(() => {
    if (ocultas.length === 0) return CATALOGO;
    return CATALOGO.map((modulo) => {
      const acoes = modulo.acoes.filter((acao) => !ocultas.includes(acao.chave));
      if (acoes.length === modulo.acoes.length) return modulo;
      return { ...modulo, acoes, paginas: (modulo.paginas ?? []).filter((pagina) => acoes.some((acao) => acao.pagina === pagina.chave)) };
    }).filter((modulo) => modulo.acoes.length > 0);
  }, [ocultas]);
  const [busca, setBusca] = useState("");
  const [modulosAbertos, setModulosAbertos] = useState<Set<string>>(new Set());
  const [telasAbertas, setTelasAbertas] = useState<Set<string>>(new Set());
  const [soAjustados, setSoAjustados] = useState(false);

  const alvo = enxuto(busca.trim());

  /* Cada módulo com o que sobrou dele depois da busca e do filtro. Módulo que
     não sobrou nada some da lista: é o que faz a busca valer a pena. */
  const visiveis = useMemo(() => {
    return catalogo.map((modulo) => {
      const combinaModulo = alvo.length > 0 && enxuto(modulo.rotulo).includes(alvo);

      const acoes = modulo.acoes.filter((acao) => {
        if (soAjustados && !Object.prototype.hasOwnProperty.call(ajustes, acao.chave)) return false;
        if (!alvo) return true;
        if (combinaModulo) return true;
        if (acaoCombina(acao, alvo)) return true;
        const pagina = (modulo.paginas ?? []).find((p) => p.chave === acao.pagina);
        return pagina ? enxuto(`${pagina.rotulo} ${pagina.descricao ?? ""}`).includes(alvo) : false;
      });

      return { modulo, acoes };
    }).filter((item) => item.acoes.length > 0);
  }, [catalogo, alvo, soAjustados, ajustes]);

  /* O placar do topo, para o dono saber onde parou sem abrir nada. */
  const placar = useMemo(() => {
    let telas = 0;
    let abertas = 0;
    for (const modulo of catalogo) {
      for (const pagina of modulo.paginas ?? []) {
        const acoes = modulo.acoes.filter((a) => a.pagina === pagina.chave);
        if (acoes.length === 0) continue;
        telas += 1;
        if (acoes.some((a) => canCompanyRole(papel, ajustes, a.chave))) abertas += 1;
      }
    }
    const marcados = Object.keys(ajustes).length;
    return { telas, abertas, marcados };
  }, [catalogo, papel, ajustes]);

  const alternar = (conjunto: Set<string>, definir: (s: Set<string>) => void, chave: string) => {
    const proximo = new Set(conjunto);
    if (proximo.has(chave)) proximo.delete(chave);
    else proximo.add(chave);
    definir(proximo);
  };

  const definirNivel = (modulo: string, nivel: NivelModulo | null) => {
    const proximo = { ...ajustes };
    if (nivel === null) delete proximo[modulo];
    else proximo[modulo] = nivel;
    aoMudar(proximo);
  };

  const definirAcao = (chave: string, valor: boolean | null) => {
    const proximo = { ...ajustes };
    if (valor === null) delete proximo[chave];
    else proximo[chave] = valor;
    aoMudar(proximo);
  };

  /* Mexer em várias ações de uma vez tem de ser UMA mudança de estado. Chamar
     `definirAcao` num laço fazia cada volta partir do mesmo `ajustes` velho e
     só a última sobrevivia — a tela "abria" uma ação por clique. */
  const definirVarias = (chaves: string[], valor: boolean | null) => {
    const proximo = { ...ajustes };
    for (const chave of chaves) {
      if (valor === null) delete proximo[chave];
      else proximo[chave] = valor;
    }
    aoMudar(proximo);
  };

  return (
    <div className="space-y-3">
      {/* ------------------------------------------------------- a barra de cima */}
      <div className="sticky top-0 z-10 -mt-1 bg-[var(--ks-color-bg-elevated)] pt-1 pb-2 space-y-2">
        <div className="relative">
          <Search
            size={15}
            className="absolute left-3 top-1/2 -translate-y-1/2 text-[var(--ks-color-text-soft)]"
            aria-hidden="true"
          />
          <input
            type="text"
            value={busca}
            onChange={(e) => setBusca(e.target.value)}
            placeholder="Procurar uma tela ou uma ação. Ex.: caixa, estoque, desconto"
            className="w-full rounded-[var(--ks-radius-sm)] border border-[var(--ks-color-border-subtle)] bg-[var(--ks-color-bg-inset)] pl-9 pr-9 py-2 text-sm text-[var(--ks-color-text-primary)] placeholder:text-[var(--ks-color-text-soft)] focus:outline-none focus:border-[var(--ks-color-primary)]"
          />
          {busca ? (
            <button
              type="button"
              onClick={() => setBusca("")}
              className="absolute right-2 top-1/2 -translate-y-1/2 h-6 w-6 inline-flex items-center justify-center rounded-full hover:bg-[var(--ks-color-bg-hover)]"
              aria-label="Limpar a busca"
            >
              <X size={13} className="text-[var(--ks-color-text-muted)]" />
            </button>
          ) : null}
        </div>

        <div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-[var(--ks-color-text-soft)]">
          <span>
            Entra em <strong className="text-[var(--ks-color-text-secondary)]">{placar.abertas}</strong> de{" "}
            {placar.telas} telas.
          </span>
          <button
            type="button"
            onClick={() => setSoAjustados((v) => !v)}
            className={[
              "rounded-full border px-2 py-0.5 font-semibold transition",
              soAjustados
                ? "border-[var(--ks-color-primary)] text-[var(--ks-color-primary-strong)] bg-[var(--ks-color-primary-soft)]"
                : "border-[var(--ks-color-border-subtle)] text-[var(--ks-color-text-muted)] hover:bg-[var(--ks-color-bg-hover)]",
            ].join(" ")}
            aria-pressed={soAjustados}
          >
            {placar.marcados === 0
              ? "Nada marcado à mão"
              : `Ver só o que você marcou (${placar.marcados})`}
          </button>
        </div>
      </div>

      {/* ------------------------------------------------------------ a lista */}
      {visiveis.length === 0 ? (
        <p className="text-xs text-[var(--ks-color-text-soft)] py-6 text-center">
          {soAjustados
            ? "Nada marcado à mão por aqui: vale o padrão da função."
            : `Nada encontrado para "${busca}".`}
        </p>
      ) : (
        <div className="space-y-2">
          {visiveis.map(({ modulo, acoes }) => (
            <LinhaModulo
              key={modulo.chave}
              modulo={modulo}
              acoesVisiveis={acoes}
              papel={papel}
              ajustes={ajustes}
              podeEditar={podeEditar}
              /* Buscando ou filtrando, o que sobrou já vem aberto: esconder o
                 resultado da busca atrás de um clique é desfazer a busca. */
              aberto={Boolean(alvo) || soAjustados || modulosAbertos.has(modulo.chave)}
              aoAlternar={() => alternar(modulosAbertos, setModulosAbertos, modulo.chave)}
              telasAbertas={telasAbertas}
              aoAlternarTela={(chave) => alternar(telasAbertas, setTelasAbertas, chave)}
              forcarTelasAbertas={Boolean(alvo) || soAjustados}
              aoDefinirNivel={(n) => definirNivel(modulo.chave, n)}
              aoDefinirAcao={definirAcao}
              aoDefinirVarias={definirVarias}
            />
          ))}
        </div>
      )}
    </div>
  );
}

/* ----------------------------------------------------------------- o módulo */

function LinhaModulo({
  modulo, acoesVisiveis, papel, ajustes, podeEditar, aberto, aoAlternar,
  telasAbertas, aoAlternarTela, forcarTelasAbertas,
  aoDefinirNivel, aoDefinirAcao, aoDefinirVarias,
}: {
  modulo: ModuloCatalogo;
  acoesVisiveis: AcaoCatalogo[];
  papel: CompanyRole;
  ajustes: Record<string, boolean | NivelModulo>;
  podeEditar: boolean;
  aberto: boolean;
  aoAlternar: () => void;
  telasAbertas: Set<string>;
  aoAlternarTela: (chave: string) => void;
  forcarTelasAbertas: boolean;
  aoDefinirNivel: (nivel: NivelModulo | null) => void;
  aoDefinirAcao: (chave: string, valor: boolean | null) => void;
  aoDefinirVarias: (chaves: string[], valor: boolean | null) => void;
}) {
  const { Icone, cor } = APARENCIA[modulo.chave] ?? { Icone: Package, cor: "var(--ks-brand-600)" };
  const niveis = useMemo(() => niveisDoModulo(modulo), [modulo]);

  const nivelEscolhido =
    typeof ajustes[modulo.chave] === "string" ? (ajustes[modulo.chave] as NivelModulo) : null;

  const liberadas = modulo.acoes.filter((a) => canCompanyRole(papel, ajustes, a.chave));
  const marcadas = modulo.acoes.filter((a) =>
    Object.prototype.hasOwnProperty.call(ajustes, a.chave)
  );
  const peloPapel = modulo.acoes.filter((a) => canCompanyRole(papel, PADRAO, a.chave));

  /* As telas do módulo, e quantas delas a pessoa abre. Módulo sem telas
     declaradas continua sendo uma lista de ações, como sempre foi. */
  const telas = (modulo.paginas ?? [])
    .map((pagina) => ({
      pagina,
      acoes: acoesVisiveis.filter((a) => a.pagina === pagina.chave),
      todas: modulo.acoes.filter((a) => a.pagina === pagina.chave),
    }))
    .filter((t) => t.acoes.length > 0);

  const telasAbertasQtd = (modulo.paginas ?? []).filter((pagina) => {
    const acoes = modulo.acoes.filter((a) => a.pagina === pagina.chave);
    return acoes.length > 0 && acoes.some((a) => canCompanyRole(papel, ajustes, a.chave));
  }).length;
  const telasQtd = (modulo.paginas ?? []).filter(
    (pagina) => modulo.acoes.some((a) => a.pagina === pagina.chave)
  ).length;

  /* Ação sem tela vale para o módulo inteiro. */
  const soltas = acoesVisiveis.filter((a) => !a.pagina);

  const parcial = liberadas.length > 0 && liberadas.length < modulo.acoes.length;
  const estado =
    liberadas.length === 0
      ? { texto: "Sem acesso", classe: "ks-chip-danger" }
      : !parcial
        ? { texto: "Acesso total", classe: "ks-chip-success" }
        : { texto: "Acesso parcial", classe: "ks-chip-warning" };

  /* As contas, em vez do selo: "2 de 2 telas" pintado de aviso dizia duas
     coisas contrárias ao mesmo tempo. */
  const contas = [
    telasQtd > 0 ? `entra em ${telasAbertasQtd} de ${telasQtd} telas` : null,
    `${liberadas.length} de ${modulo.acoes.length} ações`,
  ]
    .filter(Boolean)
    .join(" · ");

  return (
    <section className="rounded-[var(--ks-radius-md)] border border-[var(--ks-color-border-subtle)] bg-[var(--ks-color-bg-elevated)] overflow-hidden">
      <button
        type="button"
        onClick={aoAlternar}
        aria-expanded={aberto}
        className="w-full flex items-center gap-3 p-3 text-left hover:bg-[var(--ks-color-bg-hover)] transition"
      >
        <ChevronRight
          size={15}
          className={`shrink-0 text-[var(--ks-color-text-soft)] transition-transform ${aberto ? "rotate-90" : ""}`}
          aria-hidden="true"
        />
        <span className="ks-tile ks-tile-sm shrink-0" style={{ background: cor }} aria-hidden="true">
          <Icone size={16} />
        </span>
        <span className="min-w-0 flex-1">
          <span className="flex items-center gap-2 flex-wrap">
            <span className="text-sm font-semibold text-[var(--ks-color-text-primary)]">
              {modulo.rotulo}
            </span>
            <span className={`ks-chip ${estado.classe} shrink-0`}>{estado.texto}</span>
            {marcadas.length > 0 || nivelEscolhido !== null ? (
              <span className="ks-chip ks-chip-info shrink-0">
                {nivelEscolhido !== null && marcadas.length === 0
                  ? ROTULO_NIVEL[nivelEscolhido]
                  : `${marcadas.length} marcada${marcadas.length === 1 ? "" : "s"} à mão`}
              </span>
            ) : null}
          </span>
          {/* Nos extremos a conta so repete o selo ("Acesso total. 5 de 5 acoes"),
              entao ali vale mais a descricao do modulo. */}
          <span className="block text-[11px] text-[var(--ks-color-text-soft)] mt-0.5 truncate">
            {parcial ? contas : modulo.descricao}
          </span>
        </span>
      </button>

      {aberto ? (
        <div className="border-t border-[var(--ks-color-border-subtle)] p-3 space-y-2.5">
          {/* O atalho de tudo de uma vez. Continua existindo porque resolve a
              maioria dos casos num clique; deixou é de ser a única porta. */}
          <div className="rounded-[var(--ks-radius-sm)] bg-[var(--ks-color-bg-inset)] px-3 py-2.5">
            <p className="text-[11px] font-semibold text-[var(--ks-color-text-muted)]">
              O módulo inteiro, de uma vez
            </p>
            {modulo.descricao ? (
              <p className="text-[11px] text-[var(--ks-color-text-soft)] mb-1.5">{modulo.descricao}</p>
            ) : (
              <span className="block mb-1.5" />
            )}
            <div className="flex flex-wrap gap-1.5">
              <BotaoNivel
                ativo={nivelEscolhido === null}
                rotulo={`Padrão da função`}
                desabilitado={!podeEditar}
                aoClicar={() => aoDefinirNivel(null)}
              />
              {niveis.map((nivel) => (
                <BotaoNivel
                  key={nivel}
                  ativo={nivelEscolhido === nivel}
                  rotulo={ROTULO_NIVEL[nivel]}
                  perigo={nivel === "nenhum"}
                  desabilitado={!podeEditar}
                  aoClicar={() => aoDefinirNivel(nivel)}
                />
              ))}
            </div>

            {nivelEscolhido === null ? (
              <p className="text-[11px] text-[var(--ks-color-text-soft)] mt-1.5">
                {peloPapel.length === 0
                  ? "Esta função não recebe nada aqui por padrão."
                  : `A função dá, sozinha: ${peloPapel.map((a) => a.rotulo.toLowerCase()).join(", ")}.`}
              </p>
            ) : null}

            {/* Uma ação marcada à mão vence o degrau escolhido. Sem este aviso,
                o dono escolhe "Acesso total", vê uma ação negada e não tem
                como descobrir o porquê. */}
            {nivelEscolhido !== null && marcadas.length > 0 ? (
              <div className="flex items-start gap-2 mt-2 rounded-[var(--ks-radius-xs)] bg-[var(--ks-color-status-warning-soft)] px-2.5 py-1.5">
                <p className="text-[11px] text-[var(--ks-color-text-secondary)] flex-1">
                  {marcadas.length === 1
                    ? "Uma ação marcada à mão continua valendo"
                    : `${marcadas.length} ações marcadas à mão continuam valendo`}{" "}
                  por cima de &ldquo;{ROTULO_NIVEL[nivelEscolhido]}&rdquo;.
                </p>
                <button
                  type="button"
                  onClick={() => aoDefinirVarias(modulo.acoes.map((a) => a.chave), null)}
                  disabled={!podeEditar}
                  className="text-[11px] font-semibold text-[var(--ks-color-primary-strong)] hover:underline shrink-0 disabled:opacity-50"
                >
                  Limpar
                </button>
              </div>
            ) : null}
          </div>

          {/* As ações que valem para o módulo inteiro. São a porta: o que não
              tiver regra própria responde por elas. */}
          {soltas.length > 0 ? (
            <div className="space-y-1.5">
              {telas.length > 0 ? (
                <p className="text-[11px] font-semibold text-[var(--ks-color-text-muted)] px-0.5">
                  Vale para o módulo inteiro
                </p>
              ) : null}
              {soltas.map((acao) => (
                <LinhaAcao
                  key={acao.chave}
                  acao={acao}
                  permitida={canCompanyRole(papel, ajustes, acao.chave)}
                  marcada={
                    Object.prototype.hasOwnProperty.call(ajustes, acao.chave)
                      ? (ajustes[acao.chave] as boolean)
                      : null
                  }
                  podeEditar={podeEditar}
                  aoDefinir={(valor) => aoDefinirAcao(acao.chave, valor)}
                />
              ))}
            </div>
          ) : null}

          {/* E cada tela, que é o que o dono veio configurar. */}
          {telas.length > 0 ? (
            <div className="space-y-1.5">
              <p className="text-[11px] font-semibold text-[var(--ks-color-text-muted)] px-0.5">
                Tela por tela
              </p>
              {telas.map(({ pagina, acoes, todas }) => (
                <BlocoDeTela
                  key={pagina.chave}
                  pagina={pagina}
                  acoes={acoes}
                  todas={todas}
                  papel={papel}
                  ajustes={ajustes}
                  podeEditar={podeEditar}
                  aberta={forcarTelasAbertas || telasAbertas.has(`${modulo.chave}/${pagina.chave}`)}
                  aoAlternar={() => aoAlternarTela(`${modulo.chave}/${pagina.chave}`)}
                  aoDefinirAcao={aoDefinirAcao}
                  aoDefinirVarias={aoDefinirVarias}
                />
              ))}
            </div>
          ) : null}
        </div>
      ) : null}
    </section>
  );
}

/* ------------------------------------------------------------------ a tela */

/**
 * Uma tela do módulo, com as ações dela.
 *
 * É a exceção que o dono descreveu: o vendedor entra em contas a receber para
 * conferir se o cliente pagou, e não entra em contas a pagar. A linha fechada
 * responde "essa pessoa entra aqui?"; abrir responde "e fazendo o quê?".
 */
function BlocoDeTela({
  pagina, acoes, todas, papel, ajustes, podeEditar, aberta, aoAlternar,
  aoDefinirAcao, aoDefinirVarias,
}: {
  pagina: PaginaCatalogo;
  acoes: AcaoCatalogo[];
  todas: AcaoCatalogo[];
  papel: CompanyRole;
  ajustes: Record<string, boolean | NivelModulo>;
  podeEditar: boolean;
  aberta: boolean;
  aoAlternar: () => void;
  aoDefinirAcao: (chave: string, valor: boolean | null) => void;
  aoDefinirVarias: (chaves: string[], valor: boolean | null) => void;
}) {
  const liberadas = todas.filter((a) => canCompanyRole(papel, ajustes, a.chave));
  const marcadas = todas.filter((a) => Object.prototype.hasOwnProperty.call(ajustes, a.chave));

  /* "Entra" é a primeira pergunta, e ela se responde pela ação de LER a tela.
     Uma tela em que a pessoa só pode gravar não existe: ela não chega lá. */
  const entra = liberadas.length > 0;

  const estado = !entra
    ? { texto: "Não entra", classe: "ks-chip-danger" }
    : liberadas.length === todas.length
      ? { texto: "Faz tudo", classe: "ks-chip-success" }
      : { texto: `${liberadas.length} de ${todas.length}`, classe: "ks-chip-warning" };

  const chaves = todas.map((a) => a.chave);

  return (
    <div className="rounded-[var(--ks-radius-sm)] border border-[var(--ks-color-border-subtle)] overflow-hidden">
      <div className="flex flex-wrap items-center gap-x-2 gap-y-1.5 bg-[var(--ks-color-bg-subtle)] px-2.5 py-2">
        <button
          type="button"
          onClick={aoAlternar}
          aria-expanded={aberta}
          className="flex items-center gap-1.5 min-w-0 text-left"
        >
          <ChevronRight
            size={13}
            className={`shrink-0 text-[var(--ks-color-text-soft)] transition-transform ${aberta ? "rotate-90" : ""}`}
            aria-hidden="true"
          />
          <span className="text-[12px] font-semibold text-[var(--ks-color-text-primary)]">
            {pagina.rotulo}
          </span>
          <span className={`ks-chip ${estado.classe} shrink-0`}>{estado.texto}</span>
        </button>

        <div className="ml-auto flex items-center rounded-[var(--ks-radius-xs)] border border-[var(--ks-color-border-subtle)] overflow-hidden bg-[var(--ks-color-bg-elevated)]">
          <BotaoTri
            ativo={marcadas.length === 0}
            rotulo="Padrão"
            desabilitado={!podeEditar}
            aoClicar={() => aoDefinirVarias(chaves, null)}
          />
          <BotaoTri
            ativo={marcadas.length === todas.length && marcadas.every((a) => ajustes[a.chave] === true)}
            rotulo="Liberar"
            tom="sucesso"
            desabilitado={!podeEditar}
            aoClicar={() => aoDefinirVarias(chaves, true)}
          />
          <BotaoTri
            ativo={marcadas.length === todas.length && marcadas.every((a) => ajustes[a.chave] === false)}
            rotulo="Bloquear"
            tom="perigo"
            desabilitado={!podeEditar}
            aoClicar={() => aoDefinirVarias(chaves, false)}
          />
        </div>

        <p className="w-full text-[11px] text-[var(--ks-color-text-soft)] pl-[18px]">
          {pagina.descricao ? `${pagina.descricao} ` : ""}
          {!entra ? "Não aparece no menu desta pessoa." : ""}
        </p>
      </div>

      {aberta ? (
        <div className="p-2 space-y-1.5">
          {acoes.map((acao) => (
            <LinhaAcao
              key={acao.chave}
              acao={acao}
              permitida={canCompanyRole(papel, ajustes, acao.chave)}
              marcada={
                Object.prototype.hasOwnProperty.call(ajustes, acao.chave)
                  ? (ajustes[acao.chave] as boolean)
                  : null
              }
              podeEditar={podeEditar}
              aoDefinir={(valor) => aoDefinirAcao(acao.chave, valor)}
            />
          ))}
        </div>
      ) : null}
    </div>
  );
}

/* ------------------------------------------------------------------ a ação */

function LinhaAcao({
  acao, permitida, marcada, podeEditar, aoDefinir,
}: {
  acao: AcaoCatalogo;
  permitida: boolean;
  marcada: boolean | null;
  podeEditar: boolean;
  aoDefinir: (valor: boolean | null) => void;
}) {
  return (
    <div className="flex flex-col sm:flex-row sm:items-center gap-2 rounded-[var(--ks-radius-sm)] bg-[var(--ks-color-bg-inset)] px-3 py-2">
      <div className="min-w-0 flex-1">
        <div className="flex items-center gap-1.5">
          {/* Sim ou não, escrito com forma além de cor: o daltônico também
              precisa saber se a linha está liberada. */}
          <span
            className="h-4 w-4 shrink-0 inline-flex items-center justify-center rounded-full"
            style={{
              background: permitida
                ? "var(--ks-color-status-success-soft)"
                : "var(--ks-color-status-danger-soft)",
              color: permitida
                ? "var(--ks-color-status-success)"
                : "var(--ks-color-status-danger)",
            }}
            aria-hidden="true"
          >
            {permitida ? <Check size={11} strokeWidth={3} /> : <Minus size={11} strokeWidth={3} />}
          </span>
          <span className="text-xs font-medium text-[var(--ks-color-text-primary)]">
            {acao.rotulo}
          </span>
        </div>
        {acao.descricao ? (
          <p className="text-[11px] text-[var(--ks-color-text-soft)] mt-0.5 ml-[22px]">
            {acao.descricao}
          </p>
        ) : null}
      </div>

      <div className="flex shrink-0 rounded-[var(--ks-radius-xs)] border border-[var(--ks-color-border-subtle)] overflow-hidden">
        <BotaoTri ativo={marcada === null} rotulo="Padrão" desabilitado={!podeEditar} aoClicar={() => aoDefinir(null)} />
        <BotaoTri ativo={marcada === true} rotulo="Liberar" tom="sucesso" desabilitado={!podeEditar} aoClicar={() => aoDefinir(true)} />
        <BotaoTri ativo={marcada === false} rotulo="Bloquear" tom="perigo" desabilitado={!podeEditar} aoClicar={() => aoDefinir(false)} />
      </div>
    </div>
  );
}

function BotaoNivel({
  ativo, rotulo, perigo, desabilitado, aoClicar,
}: {
  ativo: boolean;
  rotulo: string;
  perigo?: boolean;
  desabilitado: boolean;
  aoClicar: () => void;
}) {
  const corAtiva = perigo ? "var(--ks-color-status-danger)" : "var(--ks-color-primary)";
  return (
    <button
      type="button"
      onClick={aoClicar}
      disabled={desabilitado}
      aria-pressed={ativo}
      className="px-2.5 py-1.5 rounded-[var(--ks-radius-full)] text-[11px] font-semibold transition border disabled:opacity-50 disabled:cursor-not-allowed"
      style={
        ativo
          ? { background: corAtiva, borderColor: corAtiva, color: "#fff" }
          : {
              background: "var(--ks-color-bg-elevated)",
              borderColor: "var(--ks-color-border-subtle)",
              color: "var(--ks-color-text-muted)",
            }
      }
    >
      {rotulo}
    </button>
  );
}

function BotaoTri({
  ativo, rotulo, tom, desabilitado, aoClicar,
}: {
  ativo: boolean;
  rotulo: string;
  tom?: "sucesso" | "perigo";
  desabilitado: boolean;
  aoClicar: () => void;
}) {
  const fundo =
    tom === "sucesso"
      ? "var(--ks-color-status-success-soft)"
      : tom === "perigo"
        ? "var(--ks-color-status-danger-soft)"
        : "var(--ks-color-primary-soft)";
  const texto =
    tom === "sucesso"
      ? "var(--ks-color-status-success)"
      : tom === "perigo"
        ? "var(--ks-color-status-danger)"
        : "var(--ks-color-primary-strong)";

  return (
    <button
      type="button"
      onClick={aoClicar}
      disabled={desabilitado}
      aria-pressed={ativo}
      className="px-2.5 py-1 text-[11px] font-semibold transition border-r last:border-r-0 border-[var(--ks-color-border-subtle)] disabled:opacity-50 disabled:cursor-not-allowed"
      style={
        ativo
          ? { background: fundo, color: texto }
          : { background: "transparent", color: "var(--ks-color-text-soft)" }
      }
    >
      {rotulo}
    </button>
  );
}
