/* ============================================================
   core.jsx — shared state, router, mock data, UI primitives
   ============================================================ */

// ---------- helpers ----------
const fmtBRL = (n) =>
  (Number(n) || 0).toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
const fmtDate = (d) => {
  const dt = typeof d === "string" ? new Date(d) : d;
  return dt.toLocaleDateString("pt-BR", { day: "2-digit", month: "short", year: "numeric" });
};
const fmtDateTime = (d) => {
  const dt = typeof d === "string" ? new Date(d) : d;
  return dt.toLocaleString("pt-BR", { day: "2-digit", month: "2-digit", year: "2-digit", hour: "2-digit", minute: "2-digit" });
};
const cls = (...xs) => xs.filter(Boolean).join(" ");
const initials = (name = "") =>
  name
    .trim()
    .split(/\s+/)
    .map((p) => p[0])
    .slice(0, 2)
    .join("")
    .toUpperCase();
const ago = (d) => {
  const dt = typeof d === "string" ? new Date(d) : d;
  const s = Math.floor((Date.now() - dt.getTime()) / 1000);
  if (s < 60) return "agora";
  const m = Math.floor(s / 60);
  if (m < 60) return `${m} min`;
  const h = Math.floor(m / 60);
  if (h < 24) return `${h} h`;
  const days = Math.floor(h / 24);
  return `${days} d`;
};
const uid = () => Math.random().toString(36).slice(2, 9);

// ---------- icons ----------
const Icon = ({ name, size = 18, ...rest }) => {
  const common = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round", ...rest };
  switch (name) {
    case "dash":
      return <svg {...common}><rect x="3" y="3" width="7" height="9" rx="2"/><rect x="14" y="3" width="7" height="5" rx="2"/><rect x="14" y="12" width="7" height="9" rx="2"/><rect x="3" y="16" width="7" height="5" rx="2"/></svg>;
    case "plus":
      return <svg {...common}><path d="M12 5v14M5 12h14"/></svg>;
    case "minus":
      return <svg {...common}><path d="M5 12h14"/></svg>;
    case "link":
      return <svg {...common}><path d="M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1"/><path d="M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1"/></svg>;
    case "copy":
      return <svg {...common}><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg>;
    case "check":
      return <svg {...common}><polyline points="20 6 9 17 4 12"/></svg>;
    case "wa":
      return <svg {...common}><path d="M21 11.5a8.5 8.5 0 1 1-3.6-6.9L21 4l-1.4 3.6A8.4 8.4 0 0 1 21 11.5z"/><path d="M8.5 9.5c.4 1.5 1.5 3 3 4 1.3.9 2.7 1.2 3.3 1.2.4 0 .7-.3.7-.7v-1c0-.3-.2-.6-.5-.7l-1.1-.4c-.3-.1-.6 0-.8.2l-.4.4c-.7-.4-1.3-1-1.7-1.7l.4-.4c.2-.2.3-.5.2-.8l-.4-1.1c-.1-.3-.4-.5-.7-.5h-1c-.4 0-.7.3-.7.7 0 .5.1 1 .3 1.5z"/></svg>;
    case "qr":
      return <svg {...common}><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"/><path d="M14 14h3v3h-3zM19 19h2M14 19h1"/></svg>;
    case "cart":
      return <svg {...common}><circle cx="9" cy="20" r="1"/><circle cx="18" cy="20" r="1"/><path d="M3 4h2l3 12h11l2-8H7"/></svg>;
    case "tag":
      return <svg {...common}><path d="M20.6 13.4 13.4 20.6a2 2 0 0 1-2.8 0L3 13V3h10l7.6 7.6a2 2 0 0 1 0 2.8z"/><circle cx="7.5" cy="7.5" r="1.2"/></svg>;
    case "user":
      return <svg {...common}><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg>;
    case "users":
      return <svg {...common}><circle cx="9" cy="8" r="4"/><path d="M2 21a7 7 0 0 1 14 0"/><path d="M16 4a4 4 0 0 1 0 8"/><path d="M22 21a7 7 0 0 0-6-7"/></svg>;
    case "money":
      return <svg {...common}><rect x="2" y="6" width="20" height="12" rx="2"/><circle cx="12" cy="12" r="2.5"/><path d="M6 9v6M18 9v6"/></svg>;
    case "card":
      return <svg {...common}><rect x="3" y="5" width="18" height="14" rx="3"/><path d="M3 10h18"/><path d="M7 15h4"/></svg>;
    case "pix":
      return <svg {...common}><path d="M5 12 12 5l7 7-7 7z"/><path d="M9 16l-3-3 3-3M15 8l3 3-3 3"/></svg>;
    case "set":
      return <svg {...common}><circle cx="12" cy="12" r="3"/><path d="M19 12c0 .6-.1 1.1-.2 1.6l2 1.5-2 3.4-2.3-.9c-.8.7-1.7 1.2-2.7 1.5l-.4 2.4h-4l-.4-2.4c-1-.3-1.9-.8-2.7-1.5l-2.3.9-2-3.4 2-1.5C2.1 13.1 2 12.6 2 12s.1-1.1.2-1.6l-2-1.5 2-3.4 2.3.9c.8-.7 1.7-1.2 2.7-1.5L7.6 2.5h4l.4 2.4c1 .3 1.9.8 2.7 1.5l2.3-.9 2 3.4-2 1.5c.1.5.2 1 .2 1.6z"/></svg>;
    case "log-out":
      return <svg {...common}><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>;
    case "search":
      return <svg {...common}><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>;
    case "bell":
      return <svg {...common}><path d="M6 8a6 6 0 1 1 12 0c0 7 3 7 3 9H3c0-2 3-2 3-9"/><path d="M9 18a3 3 0 0 0 6 0"/></svg>;
    case "more":
      return <svg {...common}><circle cx="5" cy="12" r="1.3"/><circle cx="12" cy="12" r="1.3"/><circle cx="19" cy="12" r="1.3"/></svg>;
    case "edit":
      return <svg {...common}><path d="M12 20h9"/><path d="M16.5 3.5a2.1 2.1 0 1 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>;
    case "trash":
      return <svg {...common}><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>;
    case "dup":
      return <svg {...common}><rect x="8" y="8" width="13" height="13" rx="2"/><path d="M16 3H5a2 2 0 0 0-2 2v11"/></svg>;
    case "x":
      return <svg {...common}><path d="M18 6 6 18M6 6l12 12"/></svg>;
    case "lock":
      return <svg {...common}><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V8a5 5 0 0 1 10 0v3"/></svg>;
    case "arrow":
      return <svg {...common}><path d="M5 12h14M13 6l6 6-6 6"/></svg>;
    case "eye":
      return <svg {...common}><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>;
    case "eye-off":
      return <svg {...common}><path d="M17.94 17.94A10.9 10.9 0 0 1 12 19c-7 0-11-7-11-7a18.4 18.4 0 0 1 5.06-5.94"/><path d="M9.9 4.24A10.9 10.9 0 0 1 12 4c7 0 11 7 11 7a18.5 18.5 0 0 1-2.16 3.19M14.12 14.12A3 3 0 1 1 9.88 9.88M1 1l22 22"/></svg>;
    case "calendar":
      return <svg {...common}><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>;
    case "filter":
      return <svg {...common}><path d="M3 5h18M6 12h12M10 19h4"/></svg>;
    case "send":
      return <svg {...common}><path d="m22 2-7 20-4-9-9-4 20-7z"/></svg>;
    case "shield":
      return <svg {...common}><path d="M12 2 4 5v6c0 5 3.5 9.5 8 11 4.5-1.5 8-6 8-11V5l-8-3z"/></svg>;
    case "bolt":
      return <svg {...common}><path d="M13 2 3 14h7l-1 8 10-12h-7l1-8z"/></svg>;
    case "globe":
      return <svg {...common}><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>;
    default: return null;
  }
};

// ============================================================
// Mock data store (in-memory; can be lifted to localStorage later)
// ============================================================
const seedProducts = [
  { id: "p1", name: "Identidade Visual — Pacote Essencial",  desc: "Logotipo + paleta + tipografia",            price: 2490, active: true,  sales: 18, image: null },
  { id: "p2", name: "Identidade Visual — Pacote Prime",      desc: "Essencial + papelaria + brand book",        price: 5990, active: true,  sales: 12, image: null },
  { id: "p3", name: "Identidade Visual — Exclusive",         desc: "Pacote completo, naming e estratégia",      price: 12990, active: true, sales: 4,  image: null },
  { id: "p4", name: "Refresh de Logotipo",                   desc: "Modernização do logotipo existente",        price: 990,  active: false, sales: 26, image: null },
];

const seedBumps = [
  { id: "b1", name: "Social Kit Pro",         desc: "10 templates editáveis no Figma para Instagram",      price: 197, active: true, image: null },
  { id: "b2", name: "Manual da Marca Premium", desc: "Brand book em PDF com 24 páginas",                    price: 297, active: true, image: null },
  { id: "b3", name: "Sessão de Estratégia",   desc: "1h de consultoria de posicionamento ao vivo",         price: 397, active: true, image: null },
  { id: "b4", name: "Assinatura de e-mail",   desc: "Template HTML responsivo para sua equipe",            price: 97,  active: false, image: null },
];

const seedCustomers = [
  { id: "c1", name: "Mariana Coelho",  email: "mariana@nove.estudio",      phone: "(21) 99812-3344", spend: 7280, orders: 2 },
  { id: "c2", name: "Rafael Bittencourt", email: "rafa@bittencourt.adv",   phone: "(11) 99644-1010", spend: 12990, orders: 1 },
  { id: "c3", name: "Luiza Andrade",   email: "luiza@andrade.consult",     phone: "(31) 99125-7878", spend: 2787, orders: 3 },
  { id: "c4", name: "Felipe Tavares",  email: "ft@grupotavares.com",       phone: "(81) 99088-1212", spend: 5990, orders: 1 },
  { id: "c5", name: "Beatriz Nascimento", email: "bia@nascimento.studio",  phone: "(41) 99311-2244", spend: 2490, orders: 1 },
  { id: "c6", name: "Henrique Lopes",  email: "henrique@lopesgroup.com",   phone: "(11) 98741-9090", spend: 990,  orders: 1 },
];

const seedSales = [
  { id: "ORD-7841", date: "2026-05-13T14:22", customer: "Mariana Coelho",       product: "Pacote Prime",     method: "pix",  installments: 1,  amount: 5990,  status: "pago" },
  { id: "ORD-7840", date: "2026-05-13T11:08", customer: "Rafael Bittencourt",   product: "Exclusive",        method: "card", installments: 6,  amount: 12990, status: "pago" },
  { id: "ORD-7839", date: "2026-05-13T09:41", customer: "Luiza Andrade",        product: "Refresh Logo",     method: "card", installments: 3,  amount: 990,   status: "pendente" },
  { id: "ORD-7838", date: "2026-05-12T18:55", customer: "Felipe Tavares",       product: "Pacote Prime",     method: "card", installments: 12, amount: 5990,  status: "pago" },
  { id: "ORD-7837", date: "2026-05-12T17:02", customer: "Beatriz Nascimento",   product: "Essencial",        method: "pix",  installments: 1,  amount: 2490,  status: "pago" },
  { id: "ORD-7836", date: "2026-05-12T15:18", customer: "Henrique Lopes",      product: "Refresh Logo",     method: "card", installments: 2,  amount: 990,   status: "recusado" },
  { id: "ORD-7835", date: "2026-05-11T22:30", customer: "Mariana Coelho",       product: "Social Kit Pro",   method: "pix",  installments: 1,  amount: 1290,  status: "pago" },
  { id: "ORD-7834", date: "2026-05-11T20:12", customer: "Luiza Andrade",        product: "Brand book PDF",   method: "card", installments: 1,  amount: 897,   status: "expirado" },
  { id: "ORD-7833", date: "2026-05-11T16:44", customer: "Rafael Bittencourt",   product: "Sessão Estratégia",method: "pix",  installments: 1,  amount: 397,   status: "pago" },
];

// Sales series for chart (29 days)
const buildSeries = () => {
  const base = [];
  const days = 29;
  for (let i = 0; i < days; i++) {
    const v = 800 + Math.round(Math.sin(i / 3) * 200) + Math.round(Math.random() * 800) + (i > 22 ? 600 : 0);
    base.push(v);
  }
  return base;
};
const seriesSeed = buildSeries();

// ============================================================
// Global app context — exposes router, toasts, modal, store
// ============================================================
const AppContext = React.createContext(null);
const useApp = () => React.useContext(AppContext);

function AppProvider({ children }){
  const [route, _setRoute] = React.useState(() => {
    const h = (window.location.hash || "#/login").slice(1);
    return h || "/login";
  });
  const setRoute = (r) => {
    window.location.hash = r;
    _setRoute(r);
  };
  React.useEffect(() => {
    const onHash = () => _setRoute((window.location.hash || "#/login").slice(1));
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, []);

  // auth — restaura sessão do localStorage no boot (sobrevive a F5),
  // e escuta evento de 401 pra deslogar limpo.
  const [user, setUser] = React.useState(() => {
    try {
      if (window.api && window.api.isAuthed && window.api.isAuthed()) {
        return window.api.getUser();
      }
    } catch {}
    return null;
  });
  React.useEffect(() => {
    const onExpired = () => {
      setUser(null);
      setRoute("/login");
    };
    window.addEventListener("blackx:auth-expired", onExpired);
    return () => window.removeEventListener("blackx:auth-expired", onExpired);
  }, []);

  // ============================================================
  // Store — sincronizado com a API real do backend.
  // Mantém a mesma assinatura `setProducts(fn)` / `setBumps(fn)` que
  // o resto do código já usa, mas detecta automaticamente se a operação
  // é create / update / delete e chama o endpoint correspondente.
  // ============================================================

  const [products, setProductsState]   = React.useState([]);
  const [bumps, setBumpsState]         = React.useState([]);
  const [customers, setCustomersState] = React.useState([]);
  const [sales, setSalesState]         = React.useState([]);
  const [series] = React.useState(seriesSeed);

  // Normaliza um produto vindo do backend ({id, name, description, price, active, sales_count})
  // para o formato que o frontend espera ({id, name, desc, price, active, sales, image}).
  const normalizeProduct = (p) => ({
    id: String(p.id),
    name: p.name || "",
    desc: p.description || "",
    price: Number(p.price) || 0,
    active: !!Number(p.active),
    sales: Number(p.sales_count) || 0,
    image: null,
  });
  const normalizeBump = (b) => ({
    id: String(b.id),
    name: b.name || "",
    desc: b.description || "",
    price: Number(b.price) || 0,
    active: !!Number(b.active),
    image: null,
  });
  const normalizeCustomer = (c) => ({
    id: String(c.id),
    name: c.name || "",
    email: c.email || "",
    phone: c.phone || "",
    spend: Number(c.total_spend) || 0,
    orders: Number(c.orders_count) || 0,
  });
  const normalizeSale = (s) => ({
    id: s.order_id || String(s.id),
    date: s.created_at || s.paid_at || "",
    customer: s.customer_name || s.customer_email || "—",
    product: "—",
    method: s.method || "card",
    installments: Number(s.installments) || 1,
    amount: Number(s.amount) || 0,
    status: s.status || "pendente",
  });

  // Carrega coleções da API quando o usuário loga.
  // Só dispara depois do login pq antes não tem token.
  const fetchAll = React.useCallback(async () => {
    if (!window.api || !window.api.isAuthed()) return;
    try {
      const [pr, bu, cu, sa] = await Promise.all([
        window.api.products.list().catch(() => ({ items: [] })),
        window.api.bumps.list().catch(() => ({ items: [] })),
        window.api.customers.list({ per_page: 100 }).catch(() => ({ items: [] })),
        window.api.sales.list({ per_page: 100 }).catch(() => ({ items: [] })),
      ]);
      setProductsState((pr.items || []).map(normalizeProduct));
      setBumpsState((bu.items || []).map(normalizeBump));
      setCustomersState((cu.items || []).map(normalizeCustomer));
      setSalesState((sa.items || []).map(normalizeSale));
    } catch (e) {
      console.warn("Falha ao carregar dados do backend:", e);
    }
  }, []);

  // ------------------------------------------------------------
  // setProducts híbrido: aceita os mesmos updaters do React mas roda
  // contra a API. Detecta se virou create (item novo), update (item
  // alterado com o mesmo id) ou delete (item sumiu). Otimista: atualiza
  // UI antes de a API responder, faz rollback se falhar.
  // ------------------------------------------------------------
  const makeSyncSetter = (setLocal, apiKey, normalize, label) => {
    return (updater) => {
      const apiObj = (window.api && window.api[apiKey]) || null;
      // Usa o setLocal funcional pra ter acesso ao state mais recente,
      // independentemente de quando esta closure foi criada.
      setLocal((prev) => {
        const next = typeof updater === "function" ? updater(prev) : updater;

        if (!apiObj) {
          console.warn(`[${label}] api.${apiKey} indisponível — operando offline.`);
          return next;
        }

        const prevById = new Map(prev.map((x) => [String(x.id), x]));
        const nextById = new Map(next.map((x) => [String(x.id), x]));

        // CREATEs — items que estão em next mas não em prev
        for (const item of next) {
          const id = String(item.id);
          if (prevById.has(id)) continue;
          apiObj.create({
            name:        item.name,
            description: item.desc || "",
            price:       Number(item.price) || 0,
            active:      !!item.active,
          }).then((created) => {
            setLocal((arr) => arr.map((x) => String(x.id) === id ? normalize(created) : x));
          }).catch((err) => {
            console.error(`[${label}] create falhou`, err);
            setLocal(prev);  // rollback
            if (window.__pagblackx_toast) window.__pagblackx_toast(`Erro ao criar ${label}.`, "bad");
          });
        }

        // UPDATEs — items presentes em ambos mas com conteúdo diferente
        for (const item of next) {
          const id = String(item.id);
          const before = prevById.get(id);
          if (!before) continue;
          if (
            before.name !== item.name ||
            before.desc !== item.desc ||
            Number(before.price) !== Number(item.price) ||
            !!before.active !== !!item.active
          ) {
            if (!/^\d+$/.test(id)) continue;
            apiObj.update(Number(id), {
              name:        item.name,
              description: item.desc || "",
              price:       Number(item.price) || 0,
              active:      !!item.active,
            }).catch((err) => {
              console.error(`[${label}] update falhou`, err);
              setLocal(prev);
              if (window.__pagblackx_toast) window.__pagblackx_toast(`Erro ao atualizar ${label}.`, "bad");
            });
          }
        }

        // DELETEs — items que estavam em prev mas sumiram em next
        for (const item of prev) {
          const id = String(item.id);
          if (nextById.has(id)) continue;
          if (!/^\d+$/.test(id)) continue;
          apiObj.remove(Number(id)).catch((err) => {
            console.error(`[${label}] delete falhou`, err);
            setLocal(prev);
            if (window.__pagblackx_toast) window.__pagblackx_toast(`Erro ao remover ${label}.`, "bad");
          });
        }

        return next;
      });
    };
  };

  const setProducts = React.useMemo(() => makeSyncSetter(setProductsState, "products", normalizeProduct, "produto"), []);
  const setBumps    = React.useMemo(() => makeSyncSetter(setBumpsState,    "bumps",    normalizeBump,    "bump"),    []);
  // Sales não tem CRUD admin (são criadas pelo cliente final via /api/pay),
  // mas o painel ainda chama setSales em filtros etc — manter como state local.
  const setSales = setSalesState;

  // Integrations — Stripe must be explicitly enabled by the admin before
  // it shows up on the customer-facing checkout.
  const [stripeEnabled, setStripeEnabled] = React.useState(false);

  // Facebook Pixel + Webhooks personalizados (Make / Zapier / n8n / endpoint próprio)
  const [pixel, setPixel] = React.useState({
    fbPixelId: "",          // ex: 1234567890123456
    enabled: false,         // só dispara quando ativo + ID preenchido
    trackInitiate: true,
    trackPurchase: true,
  });
  // Carrega o Pixel sempre que o admin salva um ID novo.
  React.useEffect(() => {
    if (pixel.enabled && pixel.fbPixelId && window.__pagblackx) {
      window.__pagblackx.initPixel(pixel.fbPixelId);
    }
  }, [pixel.enabled, pixel.fbPixelId]);

  const [webhooks, setWebhooks] = React.useState([
    { id: "wh1", name: "Webhook Mercado Pago", url: "https://pagblackx.holdingcriativa.com.br/api/webhook/mp",     event: "system",     active: true, system: true },
    { id: "wh2", name: "Webhook Stripe",       url: "https://pagblackx.holdingcriativa.com.br/api/webhook/stripe", event: "system",     active: true, system: true },
  ]);

  // Toast queue
  const [toasts, setToasts] = React.useState([]);
  const pushToast = (msg, kind = "ok") => {
    const id = uid();
    setToasts((t) => [...t, { id, msg, kind }]);
    setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 3200);
  };
  // Expõe pushToast no window pro setter híbrido conseguir avisar erros
  React.useEffect(() => { window.__pagblackx_toast = pushToast; }, []);

  // Modal manager
  const [modal, setModal] = React.useState(null);

  // Sempre que houver login válido (user vira non-null), carrega dados reais.
  React.useEffect(() => {
    if (user) fetchAll();
  }, [user, fetchAll]);

  const value = {
    route, setRoute,
    user, setUser,
    products, setProducts,
    bumps, setBumps,
    customers,
    sales, setSales,
    series,
    stripeEnabled, setStripeEnabled,
    pixel, setPixel,
    webhooks, setWebhooks,
    pushToast,
    modal, setModal,
    refresh: fetchAll,
  };
  return <AppContext.Provider value={value}>{children}<Toasts toasts={toasts} /><ModalHost modal={modal} onClose={() => setModal(null)} /></AppContext.Provider>;
}

function Toasts({ toasts }){
  return (
    <div className="toasts">
      {toasts.map((t) => (
        <div key={t.id} className={cls("toast", t.kind)}>
          {t.kind === "ok" && <Icon name="check" size={16} />}
          {t.kind === "bad" && <Icon name="x" size={16} />}
          {t.kind === "warn" && <Icon name="bell" size={16} />}
          <span>{t.msg}</span>
        </div>
      ))}
    </div>
  );
}

function ModalHost({ modal, onClose }){
  if (!modal) return null;
  return (
    <div className="modal-back" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        {modal.title && (
          <div className="modal-head">
            <h3>{modal.title}</h3>
            {modal.sub && <div className="sub">{modal.sub}</div>}
          </div>
        )}
        <div className="modal-body">{modal.body}</div>
        {modal.foot && <div className="modal-foot">{modal.foot}</div>}
      </div>
    </div>
  );
}

// ============================================================
// Reusable UI primitives
// ============================================================
function StatusBadge({ status }){
  const map = {
    pago: { cls: "badge-ok", label: "Pago" },
    pendente: { cls: "badge-warn", label: "Pendente" },
    recusado: { cls: "badge-bad", label: "Recusado" },
    expirado: { cls: "badge-mute", label: "Expirado" },
    rascunho: { cls: "badge-mute", label: "Rascunho" },
    ativo: { cls: "badge-ok", label: "Ativo" },
    inativo: { cls: "badge-mute", label: "Inativo" },
  };
  const it = map[status] || { cls: "badge-mute", label: status };
  return (
    <span className={cls("badge", it.cls)}>
      <span className="dot"></span>{it.label}
    </span>
  );
}

function MethodPill({ method }){
  if (method === "pix") return <span className="tag"><Icon name="pix" size={12}/> Pix</span>;
  if (method === "card") return <span className="tag"><Icon name="card" size={12}/> Cartão</span>;
  return <span className="tag">{method}</span>;
}

// SVG line chart
function LineChart({ data, height = 220 }){
  const w = 800;
  const h = height;
  const pad = 16;
  const max = Math.max(...data);
  const min = Math.min(...data);
  const pts = data.map((v, i) => {
    const x = pad + (i / (data.length - 1)) * (w - pad * 2);
    const y = h - pad - ((v - min) / (max - min || 1)) * (h - pad * 2);
    return [x, y];
  });
  const path = pts.map((p, i) => (i === 0 ? `M ${p[0]} ${p[1]}` : `L ${p[0]} ${p[1]}`)).join(" ");
  const area = `${path} L ${pts[pts.length - 1][0]} ${h - pad} L ${pts[0][0]} ${h - pad} Z`;
  return (
    <svg viewBox={`0 0 ${w} ${h}`} width="100%" height={h} preserveAspectRatio="none">
      <defs>
        <linearGradient id="lcg" x1="0" x2="0" y1="0" y2="1">
          <stop offset="0%" stopColor="rgba(10,10,10,0.12)"/>
          <stop offset="100%" stopColor="rgba(10,10,10,0)"/>
        </linearGradient>
      </defs>
      {[0.25, 0.5, 0.75].map((p, i) => (
        <line key={i} x1={pad} x2={w - pad} y1={pad + (h - pad * 2) * p} y2={pad + (h - pad * 2) * p}
          stroke="rgba(10,10,10,0.06)" strokeDasharray="2 6" />
      ))}
      <path d={area} fill="url(#lcg)" />
      <path d={path} fill="none" stroke="#0a0a0a" strokeWidth="2" />
      {pts.map((p, i) => (i === pts.length - 1 ? (
        <g key={i}>
          <circle cx={p[0]} cy={p[1]} r="6" fill="#fff" stroke="#0a0a0a" strokeWidth="2"/>
          <circle cx={p[0]} cy={p[1]} r="2" fill="#0a0a0a"/>
        </g>
      ) : null))}
    </svg>
  );
}

// Mini sparkline for KPIs
function Sparkline({ data, width = 90, height = 30, color = "rgba(10,10,10,.45)" }){
  const max = Math.max(...data), min = Math.min(...data);
  const pts = data.map((v, i) => {
    const x = (i / (data.length - 1)) * width;
    const y = height - ((v - min) / (max - min || 1)) * height;
    return `${x},${y}`;
  });
  return (
    <svg className="spark" width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
      <polyline points={pts.join(" ")} fill="none" stroke={color} strokeWidth="1.8" />
    </svg>
  );
}

// Fake QR (deterministic from seed string)
function FakeQR({ seed = "blkk" }){
  const cells = 21;
  let s = 0; for (const c of seed) s = (s * 31 + c.charCodeAt(0)) >>> 0;
  const grid = [];
  for (let y = 0; y < cells; y++){
    for (let x = 0; x < cells; x++){
      s = (s * 1103515245 + 12345) >>> 0;
      const isFinder =
        (x < 7 && y < 7) || (x > cells - 8 && y < 7) || (x < 7 && y > cells - 8);
      const inFinder =
        (x < 7 && y < 7 && (x === 0 || x === 6 || y === 0 || y === 6 || (x >= 2 && x <= 4 && y >= 2 && y <= 4))) ||
        (x > cells - 8 && y < 7 && (x === cells - 7 || x === cells - 1 || y === 0 || y === 6 || (x >= cells - 5 && x <= cells - 3 && y >= 2 && y <= 4))) ||
        (x < 7 && y > cells - 8 && (x === 0 || x === 6 || y === cells - 7 || y === cells - 1 || (x >= 2 && x <= 4 && y >= cells - 5 && y <= cells - 3)));
      const on = isFinder ? inFinder : (s & 1) === 1;
      grid.push(on);
    }
  }
  return (
    <div className="qr" aria-label="QR Code">
      {grid.map((v, i) => (
        <div key={i} className={cls("qc", !v && "b")}></div>
      ))}
    </div>
  );
}

// Brand wordmark — auto-picks the right logo for the surface theme.
//   <BrandLogo theme="dark" />  → use on light backgrounds (sidebar, dark hero)
//   <BrandLogo theme="light" /> → use on dark backgrounds
function BrandLogo({ theme = "dark", height = 22, withPay = false }){
  const src = theme === "light"
    ? "assets/blackx-logo-light.png"
    : "assets/blackx-logo-dark.png";
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 8, lineHeight: 1 }}>
      <img src={src} alt="BlackX" style={{ height, width: "auto", display: "block" }} />
      {withPay && (
        <span style={{
          fontFamily: "'Instrument Serif', serif", fontStyle: "italic", fontWeight: 400,
          fontSize: height * 0.9, lineHeight: 1, opacity: .85,
          marginBottom: -2,
        }}>pay</span>
      )}
    </span>
  );
}

// Keep BrandMark as a compact square mark (favicon-ish), unused in most places now
function BrandMark({ size = 30 }){
  return (
    <div className="mark" style={{ width: size, height: size, borderRadius: size * 0.3 }}>
      B
    </div>
  );
}

// ============================================================
// ImageDropper — drag/drop or click to upload a product/bump image
// ============================================================
function ImageDropper({ value, onChange, label = "Imagem", hint = "PNG ou JPG · proporção 4:3 ideal" }){
  const [drag, setDrag] = React.useState(false);
  const inputRef = React.useRef(null);

  const handleFile = (file) => {
    if (!file || !file.type.startsWith("image/")) return;
    const reader = new FileReader();
    reader.onload = () => onChange(reader.result);
    reader.readAsDataURL(file);
  };

  return (
    <div className="field">
      <label>{label}</label>
      <div
        className={cls("img-dropper", drag && "dragging", value && "has-img")}
        onClick={() => inputRef.current?.click()}
        onDragOver={(e) => { e.preventDefault(); setDrag(true); }}
        onDragLeave={() => setDrag(false)}
        onDrop={(e) => {
          e.preventDefault();
          setDrag(false);
          handleFile(e.dataTransfer.files?.[0]);
        }}
      >
        {value ? (
          <>
            <img src={value} alt="" className="img-dropper-preview"/>
            <div className="img-dropper-actions">
              <button type="button" className="btn btn-light btn-sm"
                onClick={(e) => { e.stopPropagation(); inputRef.current?.click(); }}>
                <Icon name="edit" size={12}/> Trocar
              </button>
              <button type="button" className="btn btn-light btn-sm"
                onClick={(e) => { e.stopPropagation(); onChange(null); }}>
                <Icon name="trash" size={12}/> Remover
              </button>
            </div>
          </>
        ) : (
          <div className="img-dropper-empty">
            <div className="img-dropper-icon">
              <Icon name="plus" size={20}/>
            </div>
            <b style={{ fontSize: 14 }}>Adicionar imagem</b>
            <span style={{ fontSize: 12, color: "var(--mute)" }}>Arraste aqui ou clique para selecionar</span>
            <span style={{ fontSize: 11, color: "var(--graphite)", marginTop: 2 }}>{hint}</span>
          </div>
        )}
        <input ref={inputRef} type="file" accept="image/*" style={{ display: "none" }}
          onChange={(e) => handleFile(e.target.files?.[0])}/>
      </div>
    </div>
  );
}

// Expose globals (each Babel <script> has its own scope)
Object.assign(window, {
  fmtBRL, fmtDate, fmtDateTime, cls, initials, ago, uid,
  Icon, AppContext, AppProvider, useApp,
  StatusBadge, MethodPill, LineChart, Sparkline, FakeQR, BrandMark, BrandLogo,
  ImageDropper,
});
