// Emporium da Arte — App shell + Login + Sidebar + Topbar
// Exposes: AppShell, Login, useApp (context), Icon

const { createContext, useContext, useState, useEffect, useMemo, useRef, useCallback } = React;

/* ─── Persistência leve em sessionStorage ──────────────────────────
   Preserva estado (filtros, busca, posição de scroll) entre navegações
   de tela na mesma sessão. Reseta no F5 ou logout.

   useStickyState(key, initial) — drop-in pra useState que persiste.
   useScrollRestore(key, ready) — callback ref que vai num container
     scrollável; salva scrollTop ao rolar e restaura quando `ready` é true.
*/
function useStickyState(key, initial) {
  const [v, setV] = useState(() => {
    try {
      const raw = sessionStorage.getItem(key);
      return raw == null ? initial : JSON.parse(raw);
    } catch { return initial; }
  });
  useEffect(() => {
    try { sessionStorage.setItem(key, JSON.stringify(v)); } catch {}
  }, [key, v]);
  return [v, setV];
}

function useScrollRestore(key, ready) {
  const elRef     = useRef(null);
  const tRef      = useRef(null);
  const detachRef = useRef(null);
  const readyRef  = useRef(ready);
  readyRef.current = ready;

  const restore = (el) => {
    let raw = null;
    try { raw = sessionStorage.getItem(key); } catch { return; }
    const y = raw ? Number(raw) : 0;
    if (y > 0) {
      requestAnimationFrame(() => requestAnimationFrame(() => {
        if (elRef.current === el) el.scrollTop = y;
      }));
    }
  };

  const setRef = useCallback((el) => {
    if (detachRef.current) { detachRef.current(); detachRef.current = null; }
    elRef.current = el;
    if (!el) return;
    const onScroll = () => {
      clearTimeout(tRef.current);
      tRef.current = setTimeout(() => {
        try { sessionStorage.setItem(key, String(el.scrollTop)); } catch {}
      }, 120);
    };
    el.addEventListener("scroll", onScroll, { passive: true });
    detachRef.current = () => {
      clearTimeout(tRef.current);
      el.removeEventListener("scroll", onScroll);
      try { sessionStorage.setItem(key, String(el.scrollTop)); } catch {}
    };
    if (readyRef.current) restore(el);
  }, [key]);

  useEffect(() => {
    if (ready && elRef.current) restore(elRef.current);
  }, [ready, key]);

  return setRef;
}

// Variante que opera no scroll vertical da página (.app-content) — usar
// nas telas onde a tabela inteira é renderizada e quem rola é a página,
// não um container local com overflow:auto.
function usePageScrollRestore(key, ready) {
  useEffect(() => {
    const el = document.querySelector(".app-content");
    if (!el) return;
    let t;
    const save = () => {
      try { sessionStorage.setItem(key, String(el.scrollTop)); } catch {}
    };
    const onScroll = () => { clearTimeout(t); t = setTimeout(save, 120); };
    el.addEventListener("scroll", onScroll, { passive: true });
    return () => { clearTimeout(t); el.removeEventListener("scroll", onScroll); save(); };
  }, [key]);

  useEffect(() => {
    if (!ready) return;
    const el = document.querySelector(".app-content");
    if (!el) return;
    let raw = null;
    try { raw = sessionStorage.getItem(key); } catch { return; }
    const y = raw ? Number(raw) : 0;
    if (y > 0) {
      requestAnimationFrame(() => requestAnimationFrame(() => {
        const cur = document.querySelector(".app-content");
        if (cur) cur.scrollTop = y;
      }));
    }
  }, [ready, key]);
}

/* ─── Icon library ─────────────────────────────────────────────── */
const ICONS = {
  cart:     <path d="M3 4h2l2.5 12.5a2 2 0 0 0 2 1.5h8.5a2 2 0 0 0 2-1.5L22 7H6"/>,
  package:  <><path d="m21 8-9 5-9-5"/><path d="M3 8v9l9 5 9-5V8l-9-5-9 5z"/><path d="M12 13v9"/></>,
  truck:    <><path d="M14 18V6h4l4 4v8h-3"/><path d="M14 18H4V8a2 2 0 0 1 2-2h8"/><circle cx="7.5" cy="18.5" r="2"/><circle cx="18" cy="18.5" r="2"/></>,
  chart:    <><path d="M3 3v18h18"/><path d="M7 14l4-4 3 3 5-7"/></>,
  receipt:  <><path d="M4 2v20l3-2 3 2 3-2 3 2 3-2V2H4z"/><path d="M8 7h8M8 11h8M8 15h6"/></>,
  box:      <><path d="M3 7l9-4 9 4M3 7v10l9 4 9-4V7M3 7l9 4 9-4M12 11v10"/></>,
  cash:     <><rect x="2" y="8" width="20" height="12" rx="2"/><rect x="6" y="3" width="12" height="5" rx="1"/><path d="M2 16h20"/><path d="M7 12h2M11 12h2M15 12h2"/></>,
  search:   <><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></>,
  plus:     <><path d="M12 5v14M5 12h14"/></>,
  minus:    <path d="M5 12h14"/>,
  trash:    <><path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></>,
  close:    <><path d="M18 6 6 18M6 6l12 12"/></>,
  arrowL:   <><path d="m12 19-7-7 7-7M19 12H5"/></>,
  arrowR:   <><path d="M5 12h14M12 5l7 7-7 7"/></>,
  check:    <path d="m5 12 5 5 9-13"/>,
  user:     <><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></>,
  settings: <><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h.01a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v.01a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></>,
  bell:     <><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10 21a2 2 0 0 0 4 0"/></>,
  edit:     <><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/></>,
  filter:   <path d="M22 3H2l8 9.46V19l4 2v-8.54L22 3z"/>,
  download: <><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></>,
  star:     <path d="m12 2 3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>,
  // Pix — 4 losangos nos cantos diagonais (NE, SE, SW, NW), aproximando
  // o "X" de pétalas do logotipo oficial. Mantém o estilo stroke do
  // resto da biblioteca.
  pix:      <><path d="M17 4l3 3-3 3-3-3z"/><path d="M20 17l-3 3-3-3 3-3z"/><path d="M7 20l-3-3 3-3 3 3z"/><path d="M4 7l3-3 3 3-3 3z"/></>,
  card:     <><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20M6 15h2M11 15h3"/></>,
  money:    <><rect x="2" y="6" width="20" height="12" rx="2"/><circle cx="12" cy="12" r="2.5"/></>,
  qr:       <><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><path d="M14 14h3v3M21 14v7M14 21h3"/></>,
  barcode:  <><path d="M4 4v16M7 4v16M10 4v16M13 4v16M16 4v16M19 4v16"/></>,
  flower:   <><circle cx="12" cy="12" r="3"/><path d="M12 9V3M12 21v-6M9 12H3M21 12h-6M5 5l4 4M19 19l-4-4M5 19l4-4M19 5l-4 4"/></>,
  alert:    <><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></>,
  refresh:  <><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/><path d="M3 21v-5h5"/></>,
  archive:  <><rect x="3" y="4" width="18" height="4" rx="1"/><path d="M5 8v11a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V8"/><path d="M10 13h4"/></>,
  print:    <><path d="M6 9V2h12v7"/><rect x="6" y="14" width="12" height="8"/><rect x="2" y="9" width="20" height="9" rx="2"/></>,
  more:     <><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></>,
  moreV:    <><circle cx="12" cy="5" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="12" cy="19" r="1.5"/></>,
  grid:     <><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></>,
  list:     <><circle cx="4" cy="6"  r="1"/><circle cx="4" cy="12" r="1"/><circle cx="4" cy="18" r="1"/><path d="M8 6h13M8 12h13M8 18h13"/></>,
  eye:      <><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></>,
  receipt:  <><path d="M5 21V3l3 2 3-2 3 2 3-2 3 2v18l-3-2-3 2-3-2-3 2-3-2z"/><path d="M9 9h6M9 13h6"/></>,
  menu:     <><path d="M3 6h18M3 12h18M3 18h18"/></>,
  logout:   <><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><path d="m16 17 5-5-5-5"/><path d="M21 12H9"/></>,
  tag:      <><path d="M20.59 13.41 13.42 20.58a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/><circle cx="7" cy="7" r="1.5"/></>,
  // Graduation cap — usado pelo módulo de Cursos
  book:     <><path d="M22 10 12 5 2 10l10 5 10-5z"/><path d="M6 12v5c0 1 3 2 6 2s6-1 6-2v-5"/></>,
  // Cesta cheia — usada pelo módulo Vendas Online. Diferencia visualmente
  // do `cart` (Nova venda) ao mostrar a cesta com itens dentro (3 dots).
  basket:   <>
    <path d="M5 8h14l-1.6 11a2 2 0 0 1-2 1.7H8.6a2 2 0 0 1-2-1.7L5 8z"/>
    <path d="M9 8V6a3 3 0 0 1 6 0v2"/>
    <circle cx="9.5"  cy="13" r="1" fill="currentColor" stroke="none"/>
    <circle cx="14.5" cy="13" r="1" fill="currentColor" stroke="none"/>
    <circle cx="12"   cy="16.5" r="1" fill="currentColor" stroke="none"/>
  </>,
};

const Icon = ({ name, size = 18, stroke = 2, ...rest }) => {
  // Pix usa o ícone oficial via FontAwesome (fa-brands fa-pix) — o
  // FontAwesome 6 já tem a marca registrada do Pix. Mantém o tamanho/cor
  // alinhados com os demais ícones SVG via inline-style.
  if (name === "pix") {
    return (
      <i className="fa-brands fa-pix"
         style={{ fontSize: size, lineHeight: 1, display: "inline-flex",
                  width: size, height: size, alignItems: "center", justifyContent: "center",
                  color: "currentColor" }}
         {...rest}/>
    );
  }
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
         strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round" {...rest}>
      {ICONS[name] || <circle cx="12" cy="12" r="9"/>}
    </svg>
  );
};

/* ─── App context ──────────────────────────────────────────────── */
const AppCtx = createContext(null);
const useApp = () => useContext(AppCtx);

/* ─── Role labels (enum user_role → texto pra UI) ──────────────── */
const ROLE_LABELS = { dono: "Dono(a)", gerente: "Gerente", operador: "Operador(a)" };
const roleLabel = (r) => ROLE_LABELS[r] || r || "";

/* ─── FLAG DE TESTE ─────────────────────────────────────────────────
 * Quando true, o login pula a validação de PIN: clicar no usuário
 * já entra direto. Para reativar a senha, mudar para false. */
const SKIP_PIN_FOR_TESTING = true;

/* ─── Máscara monetária BRL ─────────────────────────────────────────
 * A vírgula fica fixa nas 2 últimas casas: tudo que o usuário digita
 * "empurra" os dígitos da direita pra esquerda.  Ex.:
 *   ""        → "0,00"
 *   "1"       → "0,01"
 *   "234"     → "2,34"
 *   "12345"   → "123,45"
 *   "1234567" → "12.345,67"
 * Backspace remove o dígito mais à direita (cents).
 * Limite de 12 dígitos (R$ 9.999.999.999,99) — mais que suficiente. */
const maskBRL = (raw) => {
  const digits = String(raw || "").replace(/\D/g, "").slice(0, 12);
  if (!digits) return "0,00";
  const padded = digits.padStart(3, "0");
  const reais  = padded.slice(0, -2).replace(/^0+(?=\d)/, "") || "0";
  const cents  = padded.slice(-2);
  const reaisFmt = reais.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
  return reaisFmt + "," + cents;
};
const parseBRL = (s) => parseFloat(String(s || "0").replace(/\./g, "").replace(",", ".")) || 0;

/* ─── Máscaras de documento e telefone ─────────────────────────────
 * Aplicam o formato conforme o usuário digita. Ignoram qualquer não
 * dígito do input — então copiar/colar com formatação também funciona.
 */
const maskCNPJ = (v) => {
  const d = String(v || "").replace(/\D/g, "").slice(0, 14);
  if (d.length <= 2)  return d;
  if (d.length <= 5)  return `${d.slice(0,2)}.${d.slice(2)}`;
  if (d.length <= 8)  return `${d.slice(0,2)}.${d.slice(2,5)}.${d.slice(5)}`;
  if (d.length <= 12) return `${d.slice(0,2)}.${d.slice(2,5)}.${d.slice(5,8)}/${d.slice(8)}`;
  return `${d.slice(0,2)}.${d.slice(2,5)}.${d.slice(5,8)}/${d.slice(8,12)}-${d.slice(12)}`;
};
const maskCPF = (v) => {
  const d = String(v || "").replace(/\D/g, "").slice(0, 11);
  if (d.length <= 3)  return d;
  if (d.length <= 6)  return `${d.slice(0,3)}.${d.slice(3)}`;
  if (d.length <= 9)  return `${d.slice(0,3)}.${d.slice(3,6)}.${d.slice(6)}`;
  return `${d.slice(0,3)}.${d.slice(3,6)}.${d.slice(6,9)}-${d.slice(9)}`;
};
// (00) 0000-0000 (fixo) ou (00) 00000-0000 (celular). Detecta pelo
// número de dígitos: até 10 → fixo; 11 → celular.
const maskPhoneBR = (v) => {
  const d = String(v || "").replace(/\D/g, "").slice(0, 11);
  if (d.length === 0) return "";
  if (d.length <= 2)  return `(${d}`;
  if (d.length <= 6)  return `(${d.slice(0,2)}) ${d.slice(2)}`;
  if (d.length <= 10) return `(${d.slice(0,2)}) ${d.slice(2,6)}-${d.slice(6)}`;
  return        `(${d.slice(0,2)}) ${d.slice(2,7)}-${d.slice(7)}`;
};
// Valida e-mail com pelo menos um @ e um ponto seguido de algo após o @.
// Aceita .com, .com.br, .io, .info, etc. Não checa TLDs específicos.
const isValidEmail = (s) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(s || "").trim());

// Máscara de CEP: 00000-000. Trabalha com 8 dígitos.
const maskCEP = (v) => {
  const d = String(v || "").replace(/\D/g, "").slice(0, 8);
  if (d.length <= 5) return d;
  return `${d.slice(0,5)}-${d.slice(5)}`;
};

// Busca endereço pelo CEP usando a API pública do ViaCEP.
// Retorna { zip, street, district, city, state, complement } ou null se
// o CEP for inválido / não encontrado. Não lança — chamadores tratam null.
async function fetchCepViaCEP(cepRaw) {
  const digits = String(cepRaw || "").replace(/\D/g, "");
  if (digits.length !== 8) return null;
  try {
    const res  = await fetch(`https://viacep.com.br/ws/${digits}/json/`);
    if (!res.ok) return null;
    const data = await res.json();
    if (data.erro) return null;
    return {
      zip:        maskCEP(data.cep || digits),
      street:     data.logradouro || "",
      district:   data.bairro     || "",
      city:       data.localidade || "",
      state:      data.uf         || "",
      complement: data.complemento|| "",
    };
  } catch {
    return null;
  }
}

/* ─── Login screen ─────────────────────────────────────────────── */
function Login({ onLogin, theme }) {
  const [users, setUsers] = useState(null);          // null = carregando, [] = vazio, [...] = ok
  const [loadError, setLoadError] = useState(null);
  const [user, setUser] = useState(null);
  const [pin, setPin] = useState("");
  const [verifying, setVerifying] = useState(false);
  const [pinError, setPinError] = useState(null);
  const [opening, setOpening] = useState(false);
  const [openCash, setOpenCash] = useState(maskBRL("20000"));
  const [openError, setOpenError] = useState(null);
  const [step, setStep] = useState("pin"); // pin | cash

  // Carrega usuários ativos do Supabase no mount.
  // Pré-seleciona a Karen sempre que ela existir (default da loja);
  // senão cai pro primeiro da lista.
  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const list = await dbListLoginUsers();
        if (cancelled) return;
        setUsers(list);
        const karen = list.find(u => /^karen\b/i.test(u.name));
        setUser(karen || list[0] || null);
      } catch (e) {
        if (cancelled) return;
        setLoadError(e?.message || String(e));
        setUsers([]);
      }
    })();
    return () => { cancelled = true; };
  }, []);

  const handlePin = (digit) => {
    if (verifying) return;
    setPinError(null);
    if (digit === "del") setPin(p => p.slice(0, -1));
    else if (digit === "clear") setPin("");
    else if (pin.length < 4) setPin(p => p + digit);
  };

  // Quando o PIN tiver 4 dígitos, valida no Supabase. Se ok e já tem caixa
  // aberto hoje, pula a tela de abertura e entra direto. Senão, vai pra tela
  // de abertura.
  // OBS: `verifying` NÃO entra nas deps — senão `setVerifying(true)` re-dispara
  // o effect, o cleanup anterior cancela a promise em andamento, e o setStep
  // nunca chega a rodar (tela trava em "Validando...").
  useEffect(() => {
    if (pin.length !== 4 || step !== "pin" || !user) return;
    let cancelled = false;
    setVerifying(true);
    (async () => {
      try {
        const ok = await dbVerifyPin(user.id, pin);
        if (cancelled) return;
        if (!ok) {
          setPinError("PIN incorreto");
          setPin("");
          return;
        }
        setPinError(null);
        // PIN ok — checa se já existe caixa aberto
        const active = await dbGetActiveCashSession();
        if (cancelled) return;
        if (active) {
          onLogin(user, active);   // pula a tela de abertura
        } else {
          setStep("cash");
        }
      } catch (e) {
        if (cancelled) return;
        setPinError(e?.message || "Falha ao validar PIN");
        setPin("");
      } finally {
        if (!cancelled) setVerifying(false);
      }
    })();
    return () => { cancelled = true; };
  }, [pin, step, user]);

  // Troca de usuário zera o PIN e qualquer erro.
  // Se SKIP_PIN_FOR_TESTING estiver ligado, clicar no usuário já faz o
  // login direto (pula PIN e vai pra checagem de caixa).
  const selectUser = async (u) => {
    if (verifying) return;
    setUser(u);
    setPin("");
    setPinError(null);

    if (!SKIP_PIN_FOR_TESTING) return;

    setVerifying(true);
    try {
      const active = await dbGetActiveCashSession();
      if (active) {
        onLogin(u, active);
      } else {
        setStep("cash");
      }
    } catch (e) {
      setPinError(e?.message || "Falha ao verificar caixa");
    } finally {
      setVerifying(false);
    }
  };

  // Abre o caixa de fato no banco e entra
  const openAndEnter = async () => {
    if (opening) return;
    setOpening(true);
    setOpenError(null);
    try {
      const session = await dbOpenCashSession(user.id, parseBRL(openCash));
      onLogin(user, session);
    } catch (e) {
      setOpenError(e?.message || "Falha ao abrir o caixa");
      setOpening(false);
    }
  };

  // Entra sem abrir caixa (modo conferência — não pode vender)
  const enterWithoutCash = () => {
    onLogin(user, null);
  };

  // Voltar pra tela de PIN — precisa zerar o PIN, senão o effect de
  // validação re-dispara e avança de novo (loop).
  const backToPin = () => {
    setPin("");
    setPinError(null);
    setOpenError(null);
    setStep("pin");
  };

  return (
    <div className="login-wrap" data-theme={theme}>
      <div className="login-bg">
        <div className="login-bg-flower" style={{ top: "-40px", left: "-60px" }}>
          <Icon name="flower" size={280} stroke={1} />
        </div>
        <div className="login-bg-flower" style={{ bottom: "-80px", right: "-40px" }}>
          <Icon name="flower" size={340} stroke={1} />
        </div>
      </div>

      <div className="login-card">
        <div className="login-brand">
          <img className="login-logo-img" src="uploads/logo.png" alt="Emporium da Arte" />
          <div className="login-tagline">Sistema de Vendas · v1.0</div>
        </div>

        {step === "pin" ? (
          users === null ? (
            <div style={{ padding: "40px 20px", textAlign: "center", color: "var(--text-mute)" }}>
              Carregando usuários...
            </div>
          ) : loadError ? (
            <div style={{ padding: "32px 20px", textAlign: "center" }}>
              <div style={{ color: "var(--pink-600)", fontWeight: 600, marginBottom: 8 }}>
                Não foi possível conectar ao banco
              </div>
              <div style={{ fontSize: 12, color: "var(--text-mute)", wordBreak: "break-word" }}>
                {loadError}
              </div>
              <div style={{ fontSize: 12, color: "var(--text-mute)", marginTop: 12 }}>
                Verifique se o <code>estrutura_completa.sql</code> foi executado no Supabase.
              </div>
            </div>
          ) : users.length === 0 ? (
            <div style={{ padding: "32px 20px", textAlign: "center", color: "var(--text-mute)" }}>
              Nenhum usuário ativo cadastrado.
            </div>
          ) : (
            <>
              <div className="login-users">
                {users.map(u => (
                  <button key={u.id} className={"login-user " + (user && user.id === u.id ? "active" : "")}
                          onClick={() => selectUser(u)}>
                    <div className="login-avatar">{u.initials}</div>
                    <div>
                      <div style={{ fontWeight: 600 }}>{u.name.split(" ")[0]}</div>
                      <div style={{ fontSize: 12, color: "var(--text-mute)" }}>{roleLabel(u.role)}</div>
                    </div>
                  </button>
                ))}
              </div>

              {SKIP_PIN_FOR_TESTING ? (
                <div style={{ padding: "20px 4px 8px", textAlign: "center" }}>
                  <div style={{ fontSize: 13, color: "var(--text-mute)", marginBottom: 6 }}>
                    {verifying ? "Entrando..." : "Toque no usuário para entrar"}
                  </div>
                  <div style={{ fontSize: 11, color: "var(--warn-600)", fontWeight: 600 }}>
                    ⚠ Modo teste — senha desabilitada
                  </div>
                  {pinError && (
                    <div style={{ fontSize: 13, color: "var(--pink-600)", marginTop: 12, fontWeight: 600 }}>
                      {pinError}
                    </div>
                  )}
                </div>
              ) : (
              <div className="login-pin-wrap">
                <div className="login-pin-label">
                  {verifying ? "Validando..." : "Digite o PIN de " + (user ? user.name.split(" ")[0] : "")}
                </div>
                <div className="login-pin-dots">
                  {[0,1,2,3].map(i => (
                    <div key={i} className={"login-pin-dot " + (pin.length > i ? "filled" : "")}/>
                  ))}
                </div>
                <div className="login-pin-keys">
                  {["1","2","3","4","5","6","7","8","9","clear","0","del"].map(k => (
                    <button key={k} className={"login-pin-key " + (k === "del" || k === "clear" ? "muted" : "")}
                            onClick={() => handlePin(k)} disabled={verifying}>
                      {k === "del" ? "←" : k === "clear" ? "C" : k}
                    </button>
                  ))}
                </div>
                {pinError && (
                  <div style={{ fontSize: 13, color: "var(--pink-600)", textAlign: "center", marginTop: 12, fontWeight: 600 }}>
                    {pinError}
                  </div>
                )}
              </div>
              )}
            </>
          )
        ) : (
          <div className="login-cash">
            <div className="login-step-h">Abertura de caixa</div>
            <div className="login-step-sub">
              Olá, <b>{user.name.split(" ")[0]}</b>. Nenhum caixa aberto hoje — confira o fundo de troco antes de abrir.
            </div>

            <div className="login-cash-summary">
              <div className="login-cash-row"><span>Operador(a)</span><b>{user.name}</b></div>
              <div className="login-cash-row"><span>Data</span><b>{
                new Date().toLocaleDateString("pt-BR", { day: "2-digit", month: "long", year: "numeric" })
              }</b></div>
            </div>

            <div className="field" style={{ marginTop: 20 }}>
              <label className="label" htmlFor="open-cash">Fundo de troco</label>
              <div className="input-currency-wrap">
                <span className="input-currency-prefix">R$</span>
                <input id="open-cash" name="open-cash"
                       className="input input-currency input-lg tabular" value={openCash}
                       onChange={e => setOpenCash(maskBRL(e.target.value))}
                       inputMode="numeric" disabled={opening} />
              </div>
            </div>

            {openError && (
              <div style={{ fontSize: 13, color: "var(--pink-600)", textAlign: "center", marginTop: 12, fontWeight: 600 }}>
                {openError}
              </div>
            )}

            <div style={{ display: "flex", gap: 10, marginTop: 24 }}>
              <button className="btn btn-secondary"
                      style={{ justifyContent: "center" }}
                      onClick={backToPin} disabled={opening}>
                ← Voltar
              </button>
              <button className="btn btn-primary btn-lg"
                      style={{ flex: 1, justifyContent: "center" }}
                      onClick={openAndEnter} disabled={opening}>
                {opening ? "Abrindo..." : "Abrir caixa e começar →"}
              </button>
            </div>

            <button
              className="btn btn-ghost"
              style={{ width: "100%", marginTop: 12, fontSize: 13, color: "var(--text-mute)", justifyContent: "center" }}
              onClick={enterWithoutCash} disabled={opening}>
              Entrar sem abrir caixa (só conferência)
            </button>
          </div>
        )}
      </div>
    </div>
  );
}

/* ─── Sidebar ───────────────────────────────────────────────── */
// `roles` é opcional: quando preenchido, só esses papéis enxergam o item.
// Sem `roles` = visível pra qualquer usuário logado.
const NAV_ITEMS = [
  { id: "dashboard",      label: "Dashboard",      icon: "chart",    perm: "dashboard"     },
  { id: "pdv",            label: "Nova venda",     icon: "cart", primary: true, perm: "vendas" },
  { id: "produtos",       label: "Produtos",       icon: "tag",      perm: "produtos"      },
  { id: "estoque",        label: "Estoque",        icon: "box",      perm: "estoque"       },
  { id: "etiquetas",      label: "Etiquetas",      icon: "barcode",  perm: "etiquetas"     },
  { id: "fornecedores",   label: "Fornecedores",   icon: "truck",    perm: "fornecedores"  },
  { id: "clientes",       label: "Clientes",       icon: "user",     perm: "clientes"      },
  { id: "cursos",         label: "Cursos",         icon: "book",     perm: "cursos"        },
  { id: "vendas",         label: "Vendas",         icon: "receipt",  perm: "vendas"        },
  { id: "vendas_online",  label: "Vendas Online",  icon: "basket",   perm: "vendas_online" },
  { id: "caixa",          label: "Caixa",          icon: "cash",     perm: "caixa"         },
  { id: "relatorios",     label: "Relatórios",     icon: "chart",    perm: "relatorios"    },
  // Configurações só aparece para dono/gerente — operador não enxerga
  { id: "configuracoes",  label: "Configurações",  icon: "settings", roles: ["dono", "gerente"], perm: "configuracoes" },
];

// Lista das chaves de permissão usadas no app — fonte única pra UI de
// edição de usuários e pra navVisibleFor.
const PERMISSION_KEYS = [
  { key: "dashboard",     label: "Dashboard"             },
  { key: "vendas",        label: "Realizar venda"        },
  { key: "produtos",      label: "Produtos"              },
  { key: "estoque",       label: "Estoque"               },
  { key: "fornecedores",  label: "Fornecedores"          },
  { key: "clientes",      label: "Clientes"              },
  { key: "caixa",         label: "Caixa"                 },
  { key: "cursos",        label: "Cursos"                },
  { key: "etiquetas",     label: "Etiquetas"             },
  { key: "vendas_online", label: "Vendas Online"         },
  { key: "relatorios",    label: "Relatórios"            },
  { key: "configuracoes", label: "Configurações"         },
];

// Permissões padrão por cargo. Usado quando um usuário não tem JSONB
// `permissions` definido (campo NULL).
const DEFAULT_PERMS_BY_ROLE = {
  dono: PERMISSION_KEYS.reduce((acc, p) => (acc[p.key] = true, acc), {}),
  gerente: {
    dashboard: true, vendas: true, produtos: true, estoque: true,
    fornecedores: true, clientes: true, cursos: true, caixa: true, etiquetas: true,
    vendas_online: true, relatorios: true, configuracoes: true,
  },
  operador: {
    dashboard: false, vendas: true, produtos: false, estoque: false,
    fornecedores: false, clientes: true, cursos: false, caixa: true, etiquetas: false,
    vendas_online: false, relatorios: false, configuracoes: false,
  },
};

// Resolve as permissões efetivas do usuário — sobrescreve os defaults do
// role com qualquer flag explicitamente definida em user.permissions.
const effectivePermissions = (user) => {
  const base = DEFAULT_PERMS_BY_ROLE[user?.role] || DEFAULT_PERMS_BY_ROLE.operador;
  const override = user?.permissions || {};
  return { ...base, ...override };
};

const userHasPerm = (user, key) => {
  if (!user) return false;
  if (user.role === "dono") return true; // dono sempre acessa tudo
  return effectivePermissions(user)[key] === true;
};

// Decide se o usuário corrente pode ver um item do nav. Considera tanto
// role lock-in (item.roles) quanto a permissão granular (item.perm).
const navVisibleFor = (item, user) => {
  if (item.roles && !item.roles.includes(user?.role)) return false;
  if (item.perm && !userHasPerm(user, item.perm)) return false;
  return true;
};

function Sidebar({ screen, setScreen, user, onLogout, theme, cashSession, collapsed, onToggleCollapsed }) {
  const cashLabel = cashSession
    ? "Caixa #" + cashSession.session_number
    : "Sem caixa aberto";
  const cashSub = cashSession
    ? "Aberto · " + new Date(cashSession.opened_at).toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" })
    : "Modo conferência";
  return (
    <aside className="sidebar" data-collapsed={collapsed ? "true" : "false"}>
      <button className="sidebar-burger" onClick={onToggleCollapsed}
              title={collapsed ? "Expandir menu" : "Recolher menu"}
              aria-label={collapsed ? "Expandir menu" : "Recolher menu"}>
        <Icon name="menu" size={20}/>
      </button>

      <button className="sidebar-new" onClick={() => setScreen("pdv")} title={collapsed ? "Nova venda" : undefined}>
        <Icon name="plus" size={16}/>
        <span className="sidebar-label">Nova venda</span>
      </button>

      <nav className="sidebar-nav">
        {NAV_ITEMS.filter(n => !n.primary && navVisibleFor(n, user)).map(n => (
          <button key={n.id}
                  className={"sidebar-link " + (screen === n.id ? "active" : "")}
                  onClick={() => setScreen(n.id)}
                  title={collapsed ? n.label : undefined}>
            <Icon name={n.icon} size={18}/>
            <span className="sidebar-label">{n.label}</span>
          </button>
        ))}
      </nav>

      <div className="sidebar-foot">
        <div className="sidebar-cash-status" title={collapsed ? cashLabel + " · " + cashSub : undefined}>
          <div className={"dot " + (cashSession ? "dot-ok" : "dot-warn")}/>
          <div className="sidebar-label">
            <div style={{ fontSize: 11, color: "var(--text-mute)", textTransform: "uppercase", letterSpacing: ".05em" }}>{cashLabel}</div>
            <div style={{ fontSize: 13, fontWeight: 600 }}>{cashSub}</div>
          </div>
        </div>
        <div className="sidebar-user">
          <div className="login-avatar" style={{ width: 36, height: 36, fontSize: 13 }}>{user.initials}</div>
          <div className="sidebar-label" style={{ flex: 1 }}>
            <div style={{ fontSize: 13, fontWeight: 600 }}>{user.name.split(" ")[0]}</div>
            <div style={{ fontSize: 11, color: "var(--text-mute)" }}>{roleLabel(user.role)}</div>
          </div>
          <button className="btn-icon-mini sidebar-logout" onClick={onLogout} title="Sair">
            <Icon name="logout" size={16}/>
          </button>
        </div>
      </div>
    </aside>
  );
}

/* ─── Topbar (removed per request) ──────────────────────────── */
function Topbar() { return null; }
function _UnusedTopbarTitles() {
  const titles = {
    dashboard: "Dashboard",
    pdv: "PDV — Nova venda",
    produtos: "Produtos",
    estoque: "Controle de estoque",
    fornecedores: "Fornecedores",
    vendas: "Histórico de vendas",
    caixa: "Caixa",
    etiquetas: "Etiquetas de produtos",
    relatorios: "Relatórios",
  };
  return (
    <header className="topbar">
      <div>
        <div className="topbar-crumb">Emporium da Arte · São Paulo</div>
        <h1 className="topbar-title">{titles[screen] || "—"}</h1>
      </div>
      <div className="topbar-actions">
        <div className="topbar-search">
          <Icon name="search" size={16}/>
          <input id="topbar-search" name="topbar-search"
                 placeholder="Buscar produto, venda, fornecedor..." />
          <span className="kbd">⌘K</span>
        </div>
        <button className="btn-icon-mini" title="Notificações">
          <Icon name="bell" size={18}/>
          <span className="topbar-dot"/>
        </button>
        <div className="topbar-date">
          <div style={{ fontSize: 11, color: "var(--text-mute)", textTransform: "uppercase", letterSpacing: ".05em" }}>Sábado</div>
          <div style={{ fontSize: 14, fontWeight: 600 }}>09 mai 2026</div>
        </div>
      </div>
    </header>
  );
}

/* ─── App Shell (wraps screens, provides context) ─────────────── */
const SIDEBAR_BREAKPOINT = 1024; // abaixo disso, sidebar inicia recolhida

function AppShell({ children, screen, setScreen, user, onLogout, theme, cart, setCart, cashSession, setCashSession }) {
  const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
    if (typeof window === "undefined") return false;
    // Default: respeita preferência salva; senão, decide pela largura.
    const saved = window.localStorage?.getItem("sidebarCollapsed");
    if (saved === "true")  return true;
    if (saved === "false") return false;
    return window.innerWidth < SIDEBAR_BREAKPOINT;
  });
  const [confirmingLogout, setConfirmingLogout] = useState(false);

  // Recolhe automaticamente ao reduzir a janela abaixo do breakpoint.
  // Não auto-expande ao crescer — respeita escolha do usuário.
  useEffect(() => {
    const onResize = () => {
      if (window.innerWidth < SIDEBAR_BREAKPOINT) setSidebarCollapsed(true);
    };
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, []);

  // ESC fecha o modal de confirmação
  useEffect(() => {
    if (!confirmingLogout) return;
    const onKey = (e) => { if (e.key === "Escape") setConfirmingLogout(false); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [confirmingLogout]);

  const toggleSidebar = () => {
    setSidebarCollapsed(prev => {
      const next = !prev;
      try { window.localStorage.setItem("sidebarCollapsed", String(next)); } catch {}
      return next;
    });
  };

  const requestLogout = () => setConfirmingLogout(true);
  const cancelLogout  = () => setConfirmingLogout(false);
  const confirmLogout = () => { setConfirmingLogout(false); onLogout(); };

  return (
    <AppCtx.Provider value={{ user, theme, screen, setScreen, cart, setCart, cashSession, setCashSession }}>
      <div className="app" data-theme={theme} data-sidebar-collapsed={sidebarCollapsed ? "true" : "false"}>
        <Sidebar screen={screen} setScreen={setScreen} user={user} onLogout={requestLogout}
                 theme={theme} cashSession={cashSession}
                 collapsed={sidebarCollapsed} onToggleCollapsed={toggleSidebar}/>
        <div className="app-main">
          <div className="app-content">
            {children}
          </div>
        </div>
      </div>

      {confirmingLogout && (
        <div className="modal-backdrop">
          <div className="modal" style={{ width: 420 }} onClick={e => e.stopPropagation()} role="dialog" aria-modal="true">
            <div className="modal-head">
              <h2 style={{ margin: 0, fontSize: 20, fontWeight: 700 }}>Sair do sistema?</h2>
              <button className="btn-icon-mini" onClick={cancelLogout} aria-label="Fechar">
                <Icon name="close" size={18}/>
              </button>
            </div>

            <div style={{ display: "flex", alignItems: "flex-start", gap: 14, marginBottom: 24 }}>
              <div style={{
                width: 44, height: 44, borderRadius: "50%",
                background: "var(--warn-50, #FFFBEB)", color: "var(--warn-600, #B45309)",
                display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0
              }}>
                <Icon name="logout" size={22} stroke={1.6}/>
              </div>
              <div style={{ fontSize: 14, color: "var(--text-soft)", lineHeight: 1.5 }}>
                Você vai sair como <b>{user?.name?.split(" ")[0] || "—"}</b>.
                {cashSession
                  ? <> O caixa <b>#{cashSession.session_number}</b> permanece aberto e segue disponível pra outro operador.</>
                  : <> Você está em modo conferência (sem caixa aberto).</>}
              </div>
            </div>

            <div style={{ display: "flex", gap: 10, justifyContent: "flex-end" }}>
              <button className="btn btn-secondary" onClick={cancelLogout}>Cancelar</button>
              <button className="btn btn-primary" onClick={confirmLogout} autoFocus>
                <Icon name="logout" size={16}/> Sair
              </button>
            </div>
          </div>
        </div>
      )}
    </AppCtx.Provider>
  );
}

Object.assign(window, {
  Login, AppShell, Icon, useApp, NAV_ITEMS, navVisibleFor, ROLE_LABELS, roleLabel,
  PERMISSION_KEYS, DEFAULT_PERMS_BY_ROLE, effectivePermissions, userHasPerm,
  maskBRL, parseBRL,
  maskCNPJ, maskCPF, maskPhoneBR, maskCEP, isValidEmail,
  fetchCepViaCEP,
});
