/* ============================================================
   PCP — App shell: routing, online/offline sync, toasts, tweaks
   ============================================================ */

const TWEAK_BASE = /*EDITMODE-BEGIN*/{
  "accent": "#0d6e6e",
  "theme": "light",
  "density": "regular",
  "radius": 9
}/*EDITMODE-END*/;
const TWEAK_LS_KEY = "pcp.tweaks.v1";
function loadTweaks() {
  try {
    const raw = localStorage.getItem(TWEAK_LS_KEY);
    if (!raw) return TWEAK_BASE;
    const saved = JSON.parse(raw) || {};
    return { ...TWEAK_BASE, ...saved };
  } catch (_) { return TWEAK_BASE; }
}
const TWEAK_DEFAULTS = loadTweaks();

function deriveAccent(hex) {
  const root = document.documentElement;
  root.style.setProperty("--accent", hex);
  root.style.setProperty("--accent-press", `color-mix(in srgb, ${hex} 80%, #000)`);
}

let toastId = 0;
let _rid = 0;

// generic mutable collection: every record gets a stable _id, plus add/update/remove
function useCollection(seed) {
  const [list, setList] = useState(() => seed.map((x) => ({ ...x, _id: "r" + (++_rid) })));
  const api = useMemo(() => ({
    add: (it, atEnd) => setList((a) => { const r = { ...it, _id: "r" + (++_rid) }; return atEnd ? [...a, r] : [r, ...a]; }),
    update: (id, patch) => setList((a) => a.map((x) => x._id === id ? { ...x, ...(typeof patch === "function" ? patch(x) : patch) } : x)),
    remove: (id) => setList((a) => a.filter((x) => x._id !== id)),
    replaceAll: (arr) => setList(arr.map((x) => ({ ...x, _id: "r" + (++_rid) }))),
  }), []);
  return [list, api];
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [authUser, setAuthUser] = useState(null);
  const [authReady, setAuthReady] = useState(false);
  const [hydrated, setHydrated] = useState(false);
  const authed = !!authUser;
  const [route, setRoute] = useState("dashboard");
  const [collapsed, setCollapsed] = useState(false);
  const [mobileNavOpen, setMobileNavOpen] = useState(false);
  const [conn, setConn] = useState("online");
  const [queue, setQueue] = useState([]);
  const [estoque, setEstoque] = useState(() => DB.ESTOQUE.map((e) => ({ ...e })));
  const [pedidos, pedidosApi] = useCollection(DB.PEDIDOS);
  const [ops, opsApi] = useCollection(DB.OPS);
  const [produtos, produtosApi] = useCollection(DB.PRODUTOS);
  const [clientes, clientesApi] = useCollection(DB.CLIENTES);
  const [notas, notasApi] = useCollection(DB.NOTAS);
  const [lancamentos, lancamentosApi] = useCollection(DB.LANCAMENTOS);
  const [compras, comprasApi] = useCollection(DB.COMPRAS);
  const [movimentos, movimentosApi] = useCollection(DB.MOVIMENTOS || []);
  const [etapasCatalogo, etapasApi] = useCollection((DB.ETAPAS || []).map((x) => typeof x === "string" ? { nome: x } : x));
  const [terceirizados, terceirizadosApi] = useCollection(DB.TERCEIRIZADOS || []);
  const [programas, programasApi] = useCollection(DB.PROGRAMAS || []);
  const [eventos, eventosApi] = useCollection(DB.EVENTOS || []);
  const [analyticsCanais, analyticsCanaisApi] = useCollection(DB.ANALYTICS_CANAIS || []);
  const [workspaces, setWorkspaces] = useState(() => DB.WORKSPACES.map((w) => ({ schedule: { ...DB.SCHEDULE_PADRAO }, ...w })));
  const [toasts, setToasts] = useState([]);
  const [cmdk, setCmdk] = useState(false);
  const [modal, setModal] = useState(null);
  const viewRef = useRef(null);
  const openModal = (type, props) => setModal({ type, props });
  const closeModal = () => setModal(null);
  const addItem = (it) => setEstoque((arr) => [recompute({ reservado: 0, ...it, disp: 0 }), ...arr]);
  const updateItem = (sku, patch) => setEstoque((arr) => arr.map((e) => e.sku === sku ? recompute({ ...e, ...patch }) : e));
  const removeItem = (sku) => setEstoque((arr) => arr.filter((e) => e.sku !== sku));

  // Firebase Auth: escuta o estado e mantém a sessão
  useEffect(() => {
    if (!window.pcpAuth) { setAuthReady(true); return; }
    const off = window.pcpAuth.onChange((user) => {
      setAuthUser(user || null);
      setAuthReady(true);
      if (!user) setHydrated(false); // logout: marca para re-hidratar no próximo login
    });
    return off;
  }, []);

  // Carrega snapshot do Firestore ao autenticar
  useEffect(() => {
    if (!authUser) return;
    if (!window.pcpDB || !window.pcpDB.ready()) { setHydrated(true); return; }
    let cancel = false;
    (async () => {
      const data = await window.pcpDB.loadState(authUser.uid);
      if (cancel) return;
      if (data) restore(data);
      setHydrated(true);
    })();
    return () => { cancel = true; };
  }, [authUser]);

  // Persiste no Firestore (debounced) a cada mudança após hidratação
  useEffect(() => {
    if (!hydrated || !authUser || !window.pcpDB || !window.pcpDB.ready()) return;
    window.pcpDB.saveState(authUser.uid, snapshot());
  }, [hydrated, authUser, estoque, pedidos, ops, produtos, clientes, notas, lancamentos, compras, movimentos, workspaces, etapasCatalogo, terceirizados, programas, eventos, analyticsCanais]);

  // apply tweaks to DOM
  useEffect(() => { document.documentElement.setAttribute("data-theme", t.theme); }, [t.theme]);
  useEffect(() => { document.documentElement.setAttribute("data-density", t.density); }, [t.density]);
  useEffect(() => { document.documentElement.style.setProperty("--radius", t.radius + "px"); }, [t.radius]);
  useEffect(() => { deriveAccent(t.accent); }, [t.accent]);
  // persiste tweaks no localStorage
  useEffect(() => {
    try { localStorage.setItem(TWEAK_LS_KEY, JSON.stringify(t)); } catch (_) {}
  }, [t.theme, t.density, t.radius, t.accent]);

  // toast helper
  const TOAST_MAX = 3;
  const toast = (msg, tone = "ok", opts = {}) => {
    const id = ++toastId;
    setToasts((ts) => {
      const next = [...ts, { id, msg, tone, ...opts }];
      // mantém só os TOAST_MAX mais recentes
      return next.length > TOAST_MAX ? next.slice(next.length - TOAST_MAX) : next;
    });
    setTimeout(() => setToasts((ts) => ts.filter((x) => x.id !== id)), opts.duration || 4200);
  };
  const dismissToast = (id) => setToasts((ts) => ts.filter((x) => x.id !== id));

  // recompute derived item fields
  const recompute = (e) => {
    e.status = e.saldo === 0 ? "crit" : e.saldo < e.min ? "warn" : "ok";
    e.disp = e.saldo - e.reservado;
    return e;
  };

  // stock movement — optimistic; queues when offline
  const move = (sku, delta) => {
    setEstoque((arr) => arr.map((e) => e.sku === sku ? recompute({ ...e, saldo: Math.max(0, e.saldo + delta) }) : e));
    const item = estoque.find((e) => e.sku === sku);
    const novo = Math.max(0, (item ? item.saldo : 0) + delta);
    if (item) {
      addMov({ tipo: delta > 0 ? "entrada" : "saida", sku, item: item.nome, qtd: Math.abs(delta), un: item.un, motivo: "Ajuste manual" });
      logEvento("ajuste_estoque", { tipo: "estoque", id: sku }, { delta, saldo_antes: item.saldo, saldo_novo: novo });
    }
    if (conn === "offline") {
      setQueue((qq) => [...qq, { sku, delta, t: Date.now() }]);
      toast(`${item ? item.sku : sku}: salvo no dispositivo`, "info", { sub: "sincroniza ao reconectar" });
    } else {
      toast(`Saldo de ${item ? item.sku : sku} → ${DB.NUM(novo)}`, "ok", {
        action: "Desfazer",
        onAction: () => setEstoque((arr) => arr.map((e) => e.sku === sku ? recompute({ ...e, saldo: Math.max(0, e.saldo - delta) }) : e)),
      });
    }
  };

  // ===== Regras de negócio =====
  const hojeBR = () => new Date().toLocaleDateString("pt-BR", { day: "2-digit", month: "short" }).replace(".", "");
  const horaBR = () => new Date().toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });

  const findProduto = (nome) => produtos.find((p) => p.nome === nome);
  const findItemSku = (sku) => estoque.find((e) => e.sku === sku);

  // === Capacidade produtiva (horário semanal) ===
  const WD_KEYS = ["dom", "seg", "ter", "qua", "qui", "sex", "sab"];

  // Soma horas das etapas do produto e agrupa por terceirizado (lead time externo)
  const calcProdutoHoras = (produto) => {
    if (!produto || !Array.isArray(produto.etapas)) return { local: 0, terc: 0, total: 0, porTerceirizado: {} };
    return produto.etapas.reduce((acc, e) => {
      const h = +e.horas || 0;
      acc.total += h;
      if (e.modo === "terceirizado") {
        acc.terc += h;
        const tid = e.terceirizadoId || "_default";
        acc.porTerceirizado[tid] = (acc.porTerceirizado[tid] || 0) + h;
      } else {
        acc.local += h;
      }
      return acc;
    }, { local: 0, terc: 0, total: 0, porTerceirizado: {} });
  };

  // Dias úteis entre hoje e a data alvo (considera schedule da empresa atual)
  const diasUteisAte = (alvo) => {
    const a = typeof alvo === "string" ? new Date(alvo + "T00:00:00") : alvo;
    if (!a || isNaN(a.getTime())) return null;
    const hoje = new Date(); hoje.setHours(0, 0, 0, 0); a.setHours(0, 0, 0, 0);
    if (a < hoje) return -1;
    const sch = (ws && ws.schedule) || DB.SCHEDULE_PADRAO;
    let d = 0, cur = new Date(hoje);
    while (cur < a) {
      cur.setDate(cur.getDate() + 1);
      const k = WD_KEYS[cur.getDay()];
      if (sch[k] && sch[k].ativo) d++;
    }
    return d;
  };

  // Horas úteis disponíveis até a data alvo, dado um schedule semanal
  const horasDisponiveis = (alvo, schedule) => {
    const a = typeof alvo === "string" ? new Date(alvo + "T00:00:00") : alvo;
    if (!a || isNaN(a.getTime())) return null;
    const sch = schedule || DB.SCHEDULE_PADRAO;
    const hoje = new Date(); hoje.setHours(0, 0, 0, 0); a.setHours(0, 0, 0, 0);
    if (a < hoje) return -1;
    let total = 0, cur = new Date(hoje);
    while (cur < a) {
      cur.setDate(cur.getDate() + 1);
      const k = WD_KEYS[cur.getDay()];
      if (sch[k] && sch[k].ativo) total += +sch[k].horas || 0;
    }
    return total;
  };

  // Avalia se um pedido cabe no prazo, usando horas reais por dia da semana
  const pedidoNoPrazo = (produtoNome, qtd, entregaISO) => {
    const p = findProduto(produtoNome);
    if (!p) return { neutro: true, motivo: "produto sem cadastro" };
    const h = calcProdutoHoras(p);
    if (h.total === 0) return { neutro: true, motivo: "produto sem etapas cadastradas" };
    if (!entregaISO) return null;
    const empresaSch = (ws && ws.schedule) || DB.SCHEDULE_PADRAO;
    const dispLocal = horasDisponiveis(entregaISO, empresaSch);
    if (dispLocal == null || dispLocal < 0) return { ok: false, neutro: false, motivo: "data inválida ou no passado", dispLocal: 0, horasLocal: 0, terceirizados: [] };
    const horasLocalNec = h.local * (+qtd || 1);
    const okLocal = horasLocalNec <= dispLocal;
    const terceirizadosDetalhe = Object.entries(h.porTerceirizado).map(([tid, horasUnit]) => {
      const horasNec = horasUnit * (+qtd || 1);
      const t = tid !== "_default" ? terceirizados.find((x) => x._id === tid) : null;
      const sch = t && t.schedule ? t.schedule : DB.SCHEDULE_PADRAO;
      const disp = horasDisponiveis(entregaISO, sch);
      return { tercId: tid, nome: t ? t.nome : (tid === "_default" ? "Terceirizado não definido" : "?"), horasNec, disp, ok: horasNec <= disp, definido: !!t };
    });
    const okTerc = terceirizadosDetalhe.every((d) => d.ok);
    return {
      ok: okLocal && okTerc,
      neutro: false,
      horasLocal: horasLocalNec, dispLocal,
      terceirizados: terceirizadosDetalhe,
      empresaOk: okLocal,
    };
  };

  // Calcula reservas (SKU x qtd total) baseado na BOM do produto
  const calcBOM = (produtoNome, qtd) => {
    const p = findProduto(produtoNome);
    if (!p || !Array.isArray(p.bom)) return [];
    return p.bom.filter((b) => b.sku).map((b) => ({ sku: b.sku, qtd: (b.qtd || 0) * (qtd || 0) }));
  };

  // Itens normalizados: array de {tipo, nome, sku, qtd, preco}
  // Pra retrocompat, se vier {produto, qtd}, converte em uma linha de produto
  const normItens = (pedido) => {
    if (!pedido) return [];
    if (Array.isArray(pedido.itens) && pedido.itens.length) return pedido.itens;
    if (pedido.produto) return [{ tipo: "produto", nome: pedido.produto, qtd: +pedido.qtd || 0, preco: 0 }];
    return [];
  };

  // Reservas agregadas (SKU → qtd) considerando todas as linhas do pedido
  const calcReservasItens = (itens) => {
    const map = {};
    (itens || []).forEach((it) => {
      const q = +it.qtd || 0;
      if (q <= 0) return;
      if (it.tipo === "estoque") {
        const sku = it.sku || (estoque.find((x) => x.nome === it.nome) || {}).sku;
        if (sku) map[sku] = (map[sku] || 0) + q;
      } else {
        calcBOM(it.nome, q).forEach((b) => {
          map[b.sku] = (map[b.sku] || 0) + b.qtd;
        });
      }
    });
    return Object.entries(map).map(([sku, qtd]) => ({ sku, qtd }));
  };

  // Aplica deltas (positivo = reserva, negativo = libera) em e.reservado
  const aplicarReservas = (deltas) => {
    if (!deltas.length) return;
    setEstoque((arr) => arr.map((e) => {
      const d = deltas.find((x) => x.sku === e.sku);
      if (!d) return e;
      return recompute({ ...e, reservado: Math.max(0, (e.reservado || 0) + d.qtd) });
    }));
  };

  // Registra um movimento de estoque (cap em 500 para não inflar memória)
  const addMov = (mov) => {
    movimentosApi.add({ data: hojeBR(), hora: horaBR(), ...mov });
  };

  // Registra um evento de auditoria (cap em 300 entradas, FIFO)
  const logEvento = (acao, alvo, detalhes) => {
    const u = authUser || {};
    eventosApi.add({
      ts: new Date().toISOString(),
      uid: u.uid || "",
      email: u.email || "",
      acao,
      alvo: alvo || null,
      detalhes: detalhes || null,
    });
  };
  // Mantém só 300 últimos eventos
  useEffect(() => {
    if (eventos.length > 300) {
      eventosApi.replaceAll(eventos.slice(0, 300));
    }
  }, [eventos.length]);

  // ----- Analytics de canais (Shopee / Mercado Livre) -----
  // upsert: chave única = canal + data + hora + fonte. Se existe, substitui.
  const upsertAnalytics = (registros) => {
    if (!Array.isArray(registros) || registros.length === 0) return { adicionados: 0, atualizados: 0 };
    let add = 0, upd = 0;
    setEstoque((arr) => arr); // no-op (mantém renderização sincronizada)
    const atuais = [...analyticsCanais];
    const idx = (r) => atuais.findIndex((x) =>
      x.canal === r.canal && x.data === r.data && (x.hora || null) === (r.hora || null) && (x.fonte || "") === (r.fonte || "")
    );
    registros.forEach((r) => {
      const i = idx(r);
      if (i >= 0) { atuais[i] = { ...atuais[i], ...r }; upd++; }
      else { atuais.push({ ...r }); add++; }
    });
    analyticsCanaisApi.replaceAll(atuais);
    return { adicionados: add, atualizados: upd };
  };

  // Cria/atualiza lançamentos financeiros diários a partir de analytics do canal
  // origem: 'marketplace_import' + chave única origemKey = `${canal}-${data}` impede duplicar
  const importarVendasCanalComoLancamentos = (canal, vendasPorDia) => {
    // vendasPorDia: [{data:'YYYY-MM-DD', valor:number}]
    const ja = new Set(lancamentos.filter((l) => l.origem === "marketplace_import").map((l) => l.origemKey));
    let add = 0, upd = 0;
    const novos = [];
    vendasPorDia.forEach(({ data, valor }) => {
      if (!data || !(valor > 0)) return;
      const origemKey = `${canal}-${data}`;
      const desc = `Vendas ${canal === "shopee" ? "Shopee" : "Mercado Livre"} — ${data}`;
      if (ja.has(origemKey)) {
        const existing = lancamentos.find((l) => l.origemKey === origemKey);
        if (existing && existing.valor !== valor) {
          lancamentosApi.update(existing._id, { valor, desc });
          upd++;
        }
      } else {
        novos.push({
          data, desc, cat: "Vendas marketplace", tipo: "entrada", origem: "marketplace_import",
          origemKey, canal, valor, status: "pago",
        });
        add++;
      }
    });
    if (novos.length) {
      novos.forEach((l) => lancamentosApi.add(l, true));
    }
    return { adicionados: add, atualizados: upd };
  };

  // ----- Pedidos -----
  const criarPedido = (payload) => {
    const itens = normItens(payload);
    const reservas = calcReservasItens(itens);
    aplicarReservas(reservas);
    pedidosApi.add({ status: "aberto", atrasado: false, ...payload, itens });
    logEvento("criar_pedido", { tipo: "pedido", id: payload.os }, { cliente: payload.cliente, valor: payload.valor, itens: itens.length });
    const sub = reservas.length ? `${reservas.length} insumos reservados` : "em aberto";
    toast(`Pedido OS ${payload.os} criado`, "ok", { sub });
  };

  const atualizarPedido = (id, patch) => {
    const old = pedidos.find((p) => p._id === id);
    if (!old) { pedidosApi.update(id, patch); return; }
    const ativos = ["aberto", "andamento"];
    const novoStatus = patch.status != null ? patch.status : old.status;
    const eraAtivo = ativos.includes(old.status);
    const seraAtivo = ativos.includes(novoStatus);
    const itensAntigos = normItens(old);
    const itensNovos = normItens({ ...old, ...patch });
    const deltas = [];
    if (eraAtivo) calcReservasItens(itensAntigos).forEach((r) => deltas.push({ sku: r.sku, qtd: -r.qtd }));
    if (seraAtivo) calcReservasItens(itensNovos).forEach((r) => deltas.push({ sku: r.sku, qtd: r.qtd }));
    const merged = Object.values(deltas.reduce((m, d) => { m[d.sku] = { sku: d.sku, qtd: (m[d.sku] ? m[d.sku].qtd : 0) + d.qtd }; return m; }, {}));
    aplicarReservas(merged);
    pedidosApi.update(id, patch);
  };

  const moverPedidoStatus = (id, novoStatus) => {
    const p = pedidos.find((x) => x._id === id);
    if (!p || p.status === novoStatus) return;
    const ativos = ["aberto", "andamento"];
    const eraAtivo = ativos.includes(p.status);
    const seraAtivo = ativos.includes(novoStatus);
    const itens = normItens(p);
    if (eraAtivo && !seraAtivo) aplicarReservas(calcReservasItens(itens).map((r) => ({ ...r, qtd: -r.qtd })));
    if (!eraAtivo && seraAtivo) aplicarReservas(calcReservasItens(itens));
    if (novoStatus === "concluido" && p.status !== "concluido" && p.valor) {
      lancamentosApi.add({
        data: hojeBR(),
        desc: `Recebimento OS ${p.os} — ${p.cliente}`,
        cat: "Vendas",
        tipo: "entrada", origem: "pedido", canal: p.canal || "loja",
        valor: p.valor, status: "pago",
      });
    }
    pedidosApi.update(id, { status: novoStatus, atrasado: ["concluido", "cancelado"].includes(novoStatus) ? false : p.atrasado });
    logEvento("mover_pedido_status", { tipo: "pedido", id: p.os }, { de: p.status, para: novoStatus });
  };

  const excluirPedido = (id) => {
    const p = pedidos.find((x) => x._id === id);
    if (!p) { pedidosApi.remove(id); return; }
    if (["aberto", "andamento"].includes(p.status)) {
      aplicarReservas(calcReservasItens(normItens(p)).map((r) => ({ ...r, qtd: -r.qtd })));
    }
    pedidosApi.remove(id);
  };

  // ----- OPs -----
  const criarOP = (payload) => {
    const itens = Array.isArray(payload.itens) ? payload.itens : [];
    const planej = payload.planej != null
      ? +payload.planej
      : (itens.length ? itens.reduce((s, x) => s + (+x.qtd || 0), 0) : 0);
    opsApi.add({ status: "planejado", etapa: "Aguardando insumo", recurso: "—", inicio: 1, dur: 2, atrasada: false, prod: 0, ...payload, planej, itens });
    logEvento("criar_op", { tipo: "op", id: payload.op }, { produto: payload.produto, planej, linhas: itens.length });
    const sub = itens.length > 1 ? `${itens.length} linhas · ${planej} un` : (payload.produto || "");
    toast(`${payload.op} criada`, "ok", { sub });
  };

  const finalizarOP = (id, opts = {}) => {
    const op = ops.find((x) => x._id === id);
    if (!op) return;
    // qtdReal = quanto efetivamente foi produzido nessa rodada (defaults: planej - prod = restante)
    const restante = Math.max(0, (+op.planej || 0) - (+op.prod || 0));
    const qtdReal = opts.qtdReal != null ? Math.max(0, +opts.qtdReal) : restante;
    if (qtdReal === 0) {
      opsApi.update(id, { status: "finalizado", etapa: "Concluído", atrasada: false });
      toast(`${op.op} finalizada`, "ok", { sub: "sem produção" });
      return;
    }
    const bom = calcBOM(op.produto, qtdReal);
    // Consome MP: libera reserva + baixa real do saldo
    if (bom.length) {
      setEstoque((arr) => arr.map((e) => {
        const b = bom.find((x) => x.sku === e.sku);
        if (!b) return e;
        const novoSaldo = Math.max(0, e.saldo - b.qtd);
        const novoRes = Math.max(0, (e.reservado || 0) - b.qtd);
        return recompute({ ...e, saldo: novoSaldo, reservado: novoRes });
      }));
      bom.forEach((b) => {
        const e = findItemSku(b.sku);
        if (!e) return;
        addMov({ tipo: "saida", sku: b.sku, item: e.nome, qtd: b.qtd, un: e.un, motivo: `Consumo ${op.op}` });
      });
    }
    const prodTotal = (+op.prod || 0) + qtdReal;
    const completa = prodTotal >= (+op.planej || 0);
    opsApi.update(id, {
      prod: prodTotal,
      status: completa ? "finalizado" : "producao",
      etapa: completa ? "Concluído" : op.etapa,
      atrasada: completa ? false : op.atrasada,
    });
    const sub = bom.length ? `${qtdReal} un · ${bom.length} insumos consumidos` : `${qtdReal} un · sem BOM`;
    toast(`${op.op} ${completa ? "finalizada" : "atualizada"}`, "ok", { sub });
    logEvento(completa ? "finalizar_op" : "registrar_producao_op", { tipo: "op", id: op.op }, { qtdReal, prodTotal, planej: op.planej });
  };

  const cancelarOP = (id) => {
    const op = ops.find((x) => x._id === id);
    if (!op) return;
    opsApi.update(id, { status: "cancelado", atrasada: false });
    logEvento("cancelar_op", { tipo: "op", id: op.op }, { prod_acumulado: op.prod });
    toast(`${op.op} cancelada`, "warn", { sub: op.prod > 0 ? `${op.prod} un já consumidas mantidas` : "sem produção" });
  };

  // ----- Compras -----
  const receberCompra = (id, { qtdRecebida, nfNum } = {}) => {
    const c = compras.find((x) => x._id === id);
    if (!c) return;
    const qtd = qtdRecebida != null && qtdRecebida !== "" ? +qtdRecebida : c.qtd;
    // entrada no estoque
    setEstoque((arr) => arr.map((e) => e.sku === c.sku ? recompute({ ...e, saldo: e.saldo + qtd }) : e));
    const e = findItemSku(c.sku);
    if (e) addMov({ tipo: "entrada", sku: c.sku, item: e.nome, qtd, un: e.un, motivo: nfNum ? `Compra recebida — NF ${nfNum}` : `Compra recebida — ${c.forn}` });
    // lançamento financeiro
    lancamentosApi.add({
      data: hojeBR(),
      desc: `Compra — ${c.item} (${c.forn})`,
      cat: "Matéria-prima",
      tipo: "saida", origem: "compra", canal: "loja",
      valor: c.valor, status: "pago",
    });
    comprasApi.remove(id);
    logEvento("receber_compra", { tipo: "compra", id: c.sku }, { item: c.item, qtd, forn: c.forn, nfNum, valor: c.valor });
    toast(`${c.item} recebido`, "ok", { sub: `${qtd} ${c.un || ""} → estoque + lançamento` });
  };

  // ----- Programas (agrupam OPs e mostram progresso por etapa) -----
  const progressoPrograma = (prog) => {
    const ops = (prog.opIds || []).map((id) => ops0Helper(id)).filter(Boolean);
    if (ops.length === 0) return { etapas: [], totalPlanej: 0, totalProd: 0, opsCount: 0 };
    // Coleta etapas únicas, na ordem de primeira aparição
    const etapasOrdem = [];
    ops.forEach((o) => {
      const prod = produtos.find((p) => p.nome === o.produto);
      const eps = (prod && Array.isArray(prod.etapas)) ? prod.etapas : [];
      eps.forEach((e) => { if (!etapasOrdem.includes(e.nome)) etapasOrdem.push(e.nome); });
    });
    const etapas = etapasOrdem.map((etapaNome) => {
      let totalPlanej = 0, completoPlanej = 0;
      ops.forEach((o) => {
        const prod = produtos.find((p) => p.nome === o.produto);
        const eps = (prod && Array.isArray(prod.etapas)) ? prod.etapas : [];
        const ix = eps.findIndex((e) => e.nome === etapaNome);
        if (ix < 0) return;
        const planej = +o.planej || 0;
        totalPlanej += planej;
        if (o.status === "finalizado") { completoPlanej += planej; return; }
        // Prefere apontamento real: se etapasFeitas[etapaNome] existe, conta como 100%
        if (o.etapasFeitas && o.etapasFeitas[etapaNome]) { completoPlanej += planej; return; }
        const ixAtual = eps.findIndex((e) => e.nome === o.etapa);
        if (ixAtual > ix) completoPlanej += planej;
        else if (ixAtual === ix) completoPlanej += planej * ((+o.prod || 0) / Math.max(planej, 1));
      });
      return { nome: etapaNome, pct: totalPlanej ? Math.round((completoPlanej / totalPlanej) * 100) : 0 };
    });
    const totalPlanej = ops.reduce((s, o) => s + (+o.planej || 0), 0);
    const totalProd = ops.reduce((s, o) => s + (+o.prod || 0), 0);
    return { etapas, totalPlanej, totalProd, opsCount: ops.length };
  };
  // helper interno usado pelo progressoPrograma — fecha por cima de ops state
  const ops0Helper = (id) => ops.find((o) => o._id === id);

  // ----- Sugestões automáticas de compra (estoque crítico) -----
  useEffect(() => {
    estoque.forEach((e) => {
      const precisa = e.status === "crit" || (e.status === "warn" && (e.disp || 0) <= 0);
      if (!precisa) return;
      const jaExiste = compras.some((c) => c.sku === e.sku && (c.status === "sugerida" || c.status === "solicitada"));
      if (jaExiste) return;
      const faltam = Math.max(e.min - e.saldo, 0);
      const qtdSug = Math.max(faltam, Math.round(e.min * 0.5));
      comprasApi.add({
        sku: e.sku, item: e.nome, qtd: qtdSug, un: e.un, forn: e.forn || "—",
        valor: Math.round(qtdSug * (e.preco || 0)),
        motivo: e.saldo === 0 ? "Estoque zerado" : "Abaixo do mínimo",
        status: "sugerida",
      }, true);
    });
  }, [estoque]);

  // connection toggle + sync flush
  const toggleConn = () => {
    if (conn === "online") {
      setConn("offline");
      toast("Você está offline", "info", { sub: "as alterações ficam salvas e sincronizam depois", icon: "cloudOff" });
    } else if (conn === "offline") {
      const n = queue.length;
      if (n === 0) { setConn("online"); toast("Reconectado", "ok"); return; }
      setConn("syncing");
      setTimeout(() => {
        setQueue([]);
        setConn("online");
        toast(`${n} ${n === 1 ? "alteração sincronizada" : "alterações sincronizadas"}`, "ok");
      }, 1600);
    }
  };
  const setConnTweak = (v) => { if (v !== conn) toggleConn(); };

  // keyboard: ⌘K, esc, theme g-combos
  useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { e.preventDefault(); setCmdk((o) => !o); return; }
      if (e.key === "Escape") setCmdk(false);
      const editing = /INPUT|TEXTAREA|SELECT/.test(e.target.tagName) || e.target.isContentEditable;
      if (editing) return;
      if (e.key === "[") setCollapsed((c) => !c);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  // scroll view to top on route change
  useEffect(() => { if (viewRef.current) viewRef.current.scrollTop = 0; }, [route]);

  const ws = workspaces.find((w) => w.current) || workspaces[0] || null;
  useEffect(() => {
    window.__PCP_WS__ = ws;
    document.title = ws && ws.name ? `PCP — ${ws.name}` : "PCP";
  }, [ws]);
  const switchWorkspace = (id) => {
    const target = workspaces.find((w) => w.id === id);
    if (!target || target.current) return;
    setWorkspaces((arr) => arr.map((w) => ({ ...w, current: w.id === id })));
    toast("Empresa ativa: " + target.name, "ok", { sub: target.sub, icon: "building" });
  };
  const addWorkspace = (payload) => {
    const id = (payload.name || "empresa").toLowerCase().normalize("NFD").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 16) + "-" + Math.floor(100 + Math.random() * 900);
    setWorkspaces((arr) => {
      const semCurrent = arr.length === 0;
      return [...arr, { schedule: { ...DB.SCHEDULE_PADRAO }, ...payload, id, current: semCurrent }];
    });
    toast("Empresa adicionada", "ok", { sub: payload.name, icon: "building" });
  };
  const counts = useMemo(() => ({
    estoqueCrit: estoque.filter((e) => e.status !== "ok").length,
    pedidosAtraso: pedidos.filter((p) => p.atrasado && p.status !== "cancelado" && p.status !== "concluido").length,
    opsAbertas: ops.filter((o) => o.status !== "finalizado").length,
    comprasSug: compras.filter((c) => c.status === "sugerida").length,
    contasVencer: lancamentos.filter((l) => l.status === "pendente").length,
  }), [estoque, pedidos, ops, compras, lancamentos]);

  // full snapshot for backup / restore
  const snapshot = () => ({ estoque, pedidos, ops, produtos, clientes, notas, lancamentos, compras, movimentos, etapasCatalogo, terceirizados, programas, eventos, analyticsCanais, workspaces });
  const restore = (data) => {
    if (!data) return;
    if (data.estoque) setEstoque(data.estoque.map((e) => ({ ...e })));
    if (data.pedidos) pedidosApi.replaceAll(data.pedidos);
    if (data.ops) opsApi.replaceAll(data.ops);
    if (data.produtos) produtosApi.replaceAll(data.produtos);
    if (data.clientes) clientesApi.replaceAll(data.clientes);
    if (data.notas) notasApi.replaceAll(data.notas);
    if (data.lancamentos) lancamentosApi.replaceAll(data.lancamentos);
    if (data.compras) comprasApi.replaceAll(data.compras);
    if (data.movimentos) movimentosApi.replaceAll(data.movimentos);
    if (data.etapasCatalogo) etapasApi.replaceAll(data.etapasCatalogo);
    if (data.terceirizados) terceirizadosApi.replaceAll(data.terceirizados);
    if (data.programas) programasApi.replaceAll(data.programas);
    if (data.eventos) eventosApi.replaceAll(data.eventos);
    if (data.analyticsCanais) analyticsCanaisApi.replaceAll(data.analyticsCanais);
    if (data.workspaces) setWorkspaces(data.workspaces.map((w) => ({ ...w })));
  };

  const appCtx = { route, setRoute, conn, toast, move, estoque, counts, openModal, addItem, updateItem, removeItem,
    pedidos, pedidosApi, ops, opsApi, produtos, produtosApi, clientes, clientesApi,
    notas, notasApi, lancamentos, lancamentosApi, compras, comprasApi,
    movimentos, movimentosApi, snapshot, restore,
    etapasCatalogo, etapasApi,
    terceirizados, terceirizadosApi,
    programas, programasApi, progressoPrograma,
    eventos, eventosApi, logEvento,
    analyticsCanais, analyticsCanaisApi, upsertAnalytics, importarVendasCanalComoLancamentos,
    workspaces, ws, switchWorkspace, addWorkspace,
    setEmpresaSchedule: (schedule) => setWorkspaces((arr) => arr.map((w) => w.current ? { ...w, schedule } : w)),
    // operações de negócio (cadeia lógica entre telas)
    criarPedido, atualizarPedido, moverPedidoStatus, excluirPedido,
    criarOP, finalizarOP, cancelarOP, receberCompra, calcBOM, normItens,
    calcProdutoHoras, pedidoNoPrazo, diasUteisAte, horasDisponiveis,
    theme: t.theme, setTheme: (v) => setTweak("theme", v),
    density: t.density, setDensity: (v) => setTweak("density", v),
    user: authUser ? { uid: authUser.uid, email: authUser.email, name: authUser.displayName || authUser.email } : null,
    logout: async () => {
      if (window.pcpDB && window.pcpDB.flushNow) { try { await window.pcpDB.flushNow(); } catch (_) {} }
      if (window.pcpAuth) window.pcpAuth.signOut();
    } };

  const SCREENS = {
    dashboard: Dashboard, estoque: Estoque, pedidos: Pedidos,
    pcp: PCP, financeiro: Financeiro, compras: Compras, produtos: Produtos,
    clientes: Clientes, notas: Notas, relatorios: Relatorios, configuracoes: Configuracoes,
    movimentacoes: Movimentacoes, auditoria: Auditoria,
    pedidos_ml: MercadoLivre, pedidos_shopee: Shopee, integracoes: Integracoes,
  };
  const Screen = SCREENS[route] || (() => <Placeholder route={route} />);

  if (!authReady) {
    return (
      <div style={{ height: "100%", display: "grid", placeItems: "center", color: "var(--muted)" }}>
        <div className="row gap8"><Icon name="refresh" size={14} /> Carregando…</div>
      </div>
    );
  }

  if (!authed) {
    return (
      <div style={{ height: "100%" }}>
        <Login theme={t.theme} />
        <Tweaks t={t} setTweak={setTweak} conn={conn} setConn={setConnTweak} />
      </div>
    );
  }

  if (!hydrated) {
    return (
      <div style={{ height: "100%", display: "grid", placeItems: "center", color: "var(--muted)" }}>
        <div className="row gap8"><Icon name="refresh" size={14} /> Carregando seus dados…</div>
      </div>
    );
  }

  if (workspaces.length === 0) {
    return (
      <div style={{ height: "100%", display: "grid", placeItems: "center", padding: 24 }}>
        <div className="login-card" style={{ maxWidth: 480, textAlign: "left" }}>
          <div className="login-head">
            <div className="login-logo" style={{ width: "60px", height: "60px", margin: "0 0 16px" }}><Icon name="building" size={24} /></div>
            <h1>Cadastre a primeira empresa</h1>
            <div className="lsub">Pra começar, precisamos do nome e cidade da sua empresa.</div>
          </div>
          <div style={{ marginTop: 18 }}>
            <Btn variant="primary" size="lg" icon="plus" onClick={() => openModal("novaEmpresa")} style={{ width: "100%" }}>Cadastrar empresa</Btn>
            <div className="row" style={{ marginTop: 12, justifyContent: "center" }}>
              <a className="small muted" style={{ cursor: "pointer" }} onClick={() => window.pcpAuth && window.pcpAuth.signOut()}>Sair da conta</a>
            </div>
          </div>
        </div>
        <ModalHost modal={modal} app={appCtx} onClose={closeModal} />
        <div className="toasts">
          {toasts.map((t) => (
            <div key={t.id} className={"toast " + t.tone}>
              <span className="t-ico"><Icon name={t.icon || (t.tone === "ok" ? "check" : t.tone === "crit" ? "alert" : "cloud")} size={14} /></span>
              <span className="t-msg">{t.msg}{t.sub && <span className="muted small"> · {t.sub}</span>}</span>
            </div>
          ))}
        </div>
      </div>
    );
  }

  const isMobile = () => typeof window !== "undefined" && window.matchMedia && window.matchMedia("(max-width: 820px)").matches;
  const onToggleNav = () => {
    if (isMobile()) setMobileNavOpen((o) => !o);
    else setCollapsed((c) => !c);
  };
  const setRouteAndClose = (r) => { setRoute(r); if (isMobile()) setMobileNavOpen(false); };

  return (
    <div className={"app" + (collapsed ? " nav-collapsed" : "") + (mobileNavOpen ? " nav-open" : "")} onClick={(e) => {
      // fecha drawer ao clicar fora no mobile
      if (mobileNavOpen && isMobile()) {
        const t = e.target;
        if (t && t.classList && !t.closest(".sidebar")) setMobileNavOpen(false);
      }
    }}>
      <Sidebar route={route} setRoute={setRouteAndClose} collapsed={collapsed} onSearch={() => setCmdk(true)} workspaces={workspaces} ws={ws} onSwitchWs={switchWorkspace} openModal={openModal} onUser={() => setRouteAndClose("configuracoes")} app={appCtx} />

      <div className="main">
        <Topbar route={route} onToggleNav={onToggleNav} conn={conn} queue={queue.length}
          onToggleConn={toggleConn} onSearch={() => setCmdk(true)} theme={t.theme} onGoRoute={setRoute}
          onTheme={() => setTweak("theme", t.theme === "dark" ? "light" : "dark")} app={appCtx} />

        {conn === "offline" && (
          <div className="offline-banner">
            <Icon name="cloudOff" size={15} />
            <span>Você está <b>trabalhando offline.</b> Tudo continua funcionando — as alterações sincronizam ao reconectar.</span>
            <span className="right">
              {queue.length > 0 && <span className="badge warn">{queue.length} na fila</span>}
              <Btn size="sm" icon="refresh" onClick={toggleConn}>Reconectar</Btn>
            </span>
          </div>
        )}

        <div className="view" ref={viewRef}>
          <Screen app={appCtx} />
        </div>
      </div>

      <CommandPalette open={cmdk} onClose={() => setCmdk(false)} setRoute={setRoute} toast={toast} openModal={openModal} app={appCtx} />

      <ModalHost modal={modal} app={appCtx} onClose={closeModal} />

      <div className="toasts">
        {toasts.map((t) => (
          <div key={t.id} className={"toast " + t.tone}>
            <span className="t-ico"><Icon name={t.icon || (t.tone === "ok" ? "check" : t.tone === "crit" ? "alert" : "cloud")} size={14} /></span>
            <span className="t-msg">{t.msg}{t.sub && <span className="muted small"> · {t.sub}</span>}</span>
            {t.action && <span className="t-act" onClick={() => { t.onAction && t.onAction(); dismissToast(t.id); }}>{t.action}</span>}
          </div>
        ))}
      </div>

      <Tweaks t={t} setTweak={setTweak} conn={conn} setConn={setConnTweak} />
    </div>
  );
}

// ---------------- Tweaks panel ----------------
function Tweaks({ t, setTweak, conn, setConn }) {
  const accents = ["#0d6e6e", "#1B365D", "#2563eb", "#7a5230", "#5b3fb8", "#333333"];
  return (
    <TweaksPanel title="Tweaks">
      <TweakSection label="Tema" />
      <TweakRadio label="Modo" value={t.theme} options={["dark", "light"]} onChange={(v) => setTweak("theme", v)} />
      <TweakColor label="Acento" value={t.accent} options={accents} onChange={(v) => setTweak("accent", v)} />

      <TweakSection label="Layout" />
      <TweakRadio label="Densidade" value={t.density} options={["compact", "regular", "comfortable"]} onChange={(v) => setTweak("density", v)} />
      <TweakSlider label="Raio das bordas" value={t.radius} min={2} max={16} step={1} unit="px" onChange={(v) => setTweak("radius", v)} />

      <TweakSection label="Conexão (demo)" />
      <TweakRadio label="Estado" value={conn === "offline" ? "offline" : "online"} options={["online", "offline"]} onChange={setConn} />
    </TweaksPanel>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
