// AXLU — screens 4: Categories & Mapping, Pricing Rules, Shopify Publications, Logs, Admin Users

// ─── Categories & Mapping ──────────────────────────────────────────────
// Bandeau « lecture seule » : affiché quand un AUTRE utilisateur édite déjà cette page/fiche.
function LockBanner({ resource }) {
  useAxluStore();
  const by = window.AxluStore.lockedByOther ? window.AxluStore.lockedByOther(resource) : null;
  if (!by) return null;
  return (
    <div className="sunken rounded-md p-3 mb-4 text-[12px] text-red-700 dark:text-red-300 flex items-start gap-2">
      <span className="text-[14px] leading-none">🔒</span>
      <div><strong>En cours d'utilisation par {by.userLabel || "un autre utilisateur"}.</strong> Lecture seule — les modifications sont désactivées tant qu'il/elle est sur cette page.</div>
    </div>
  );
}

function CategoriesScreen({ initialFilter }) {
  const v = useAxluStore();
  const { CATEGORIES_AXLU, CATEGORIES_PF, PRODUCTS } = window.AxluData;
  React.useEffect(() => { window.AxluStore.acquireLock && window.AxluStore.acquireLock('page:categories'); return () => window.AxluStore.releaseLock && window.AxluStore.releaseLock('page:categories'); }, []);
  const [filter, setFilter] = useState(initialFilter === "unmapped" || initialFilter === "mapped" ? initialFilter : "all");
  const [search, setSearch] = useState("");
  const [dragId, setDragId] = useState(null);
  const [editParent, setEditParent] = useState(null);
  const [addChildOf, setAddChildOf] = useState(null);
  const [renameChild, setRenameChild] = useState(null);
  const [deleteCat, setDeleteCat] = useState(null);
  const [newCatOpen, setNewCatOpen] = useState(false);
  const [expandedGroups, setExpandedGroups] = useState(() => new Set());
  const [mapModal, setMapModal] = useState(null);
  const [mapSearch, setMapSearch] = useState("");
  const [nameInput, setNameInput] = useState("");
  const [parentInput, setParentInput] = useState("");
  const toast = useToast();

  const visible = useMemo(() => CATEGORIES_PF.filter((c) => {
    if (filter === "mapped" && !c.mappedTo) return false;
    if (filter === "unmapped" && c.mappedTo) return false;
    if (search && !c.name.toLowerCase().includes(search.toLowerCase())) return false;
    return true;
  }), [v, CATEGORIES_PF, filter, search]);

  const unmappedCount = useMemo(() => CATEGORIES_PF.filter((c) => !c.mappedTo).length, [v, CATEGORIES_PF]);

  const map = (pfId, axluId) => {
    window.AxluStore.dispatch("category.map", { pfId, axluId });
    toast(axluId ? "Catégorie mappée" : "Mapping retiré");
  };

  const exportCsv = () => {
    const rows = CATEGORIES_PF.map((p) => {
      const target = CATEGORIES_AXLU.find((x) => x.id === p.mappedTo);
      return { pfId: p.id, pfName: p.name, axluId: target?.id || "", axluName: target?.name || "", count: p.count };
    });
    const csv = window.AxluStore.toCSV(rows, [
      { key: "pfId", label: "PF ID" },
      { key: "pfName", label: "PF Nom" },
      { key: "axluId", label: "AXLU ID" },
      { key: "axluName", label: "AXLU Nom" },
      { key: "count", label: "Produits" },
    ]);
    window.AxluStore.download(csv, "mapping.csv", "text/csv");
    toast("Mapping exporté", { tone: "success" });
  };

  const openEditParent = (p) => { setEditParent(p); setNameInput(p.name); };
  const openAddChild = (p) => { setAddChildOf(p); setNameInput(""); };
  const openRenameChild = (c) => { setRenameChild(c); setNameInput(c.name); };
  const openNewCat = () => { setNewCatOpen(true); setNameInput(""); setParentInput(""); };

  // Collapsible AXLU groups in the taxonomy panel.
  const groupIds = CATEGORIES_AXLU.filter((c) => !c.parent).map((c) => c.id);
  const allExpanded = groupIds.length > 0 && groupIds.every((id) => expandedGroups.has(id));
  const toggleGroup = (id) => setExpandedGroups((prev) => {
    const next = new Set(prev);
    if (next.has(id)) next.delete(id); else next.add(id);
    return next;
  });
  const toggleAllGroups = () => setExpandedGroups(allExpanded ? new Set() : new Set(groupIds));

  // AXLU sub-categories as searchable mapping targets ("Groupe › Sous-cat").
  const axluLeafOptions = CATEGORIES_AXLU.filter((c) => c.parent).map((c) => {
    const parent = CATEGORIES_AXLU.find((x) => x.id === c.parent);
    return {
      id: c.id, name: c.name, parentName: parent ? parent.name : null,
      search: ((parent ? parent.name + " " : "") + c.name).toLowerCase(),
    };
  });
  const mapQuery = mapSearch.trim().toLowerCase();
  const mapTargets = mapQuery ? axluLeafOptions.filter((o) => o.search.includes(mapQuery)) : axluLeafOptions;

  return (
    <div>
      <LockBanner resource="page:categories"/>
      <PageHeader
        breadcrumb={[{ label: "Catégories & mapping" }]}
        title="Catégories & mapping"
        subtitle={
          CATEGORIES_PF.length > 0
            ? `${CATEGORIES_PF.length} catégories fournisseur · ${unmappedCount} non mappée${unmappedCount > 1 ? "s" : ""}`
            : `${CATEGORIES_AXLU.filter((c) => !c.parent).length} groupes · ${CATEGORIES_AXLU.filter((c) => c.parent).length} sous-catégories AXLU`
        }
        actions={
          <Button variant="secondary" leftIcon="download" onClick={exportCsv}>Exporter</Button>
        }
      />

      <div className="p-6">
        <div className="grid grid-cols-2 gap-6" style={{ minHeight: 600 }}>
          {/* Left: Supplier categories */}
          <Card title="Catégories fournisseur" padding="none"
                action={<Badge tone="muted">{CATEGORIES_PF.length}</Badge>}>
            <div className="p-3 border-b flex items-center gap-2" style={{ borderColor: "var(--border)" }}>
              <Input size="sm" leftIcon="search" placeholder="Rechercher…" value={search} onChange={(e) => setSearch(e.target.value)}/>
              <Select size="sm" value={filter} onChange={setFilter} options={[
                { value: "all", label: "Toutes" },
                { value: "mapped", label: "Mappées" },
                { value: "unmapped", label: `Non mappées (${unmappedCount})` },
              ]}/>
            </div>
            <div className="p-2 space-y-1 overflow-y-auto" style={{ maxHeight: 540 }}>
              {visible.map((c) => {
                const target = CATEGORIES_AXLU.find((x) => x.id === c.mappedTo);
                return (
                  <div key={c.id}
                       draggable
                       onDragStart={() => setDragId(c.id)}
                       className={`group flex items-center gap-2 p-2 rounded-md border cursor-grab active:cursor-grabbing ${c.mappedTo ? "" : "border-amber-300 bg-amber-50/50 dark:bg-amber-950/20 dark:border-amber-900"}`}
                       style={{ borderColor: c.mappedTo ? "var(--border)" : undefined }}>
                    <Icon name="drag" size={14} className="text-subtle"/>
                    <div className="flex-1 min-w-0">
                      <div className="text-[12.5px] font-medium truncate">{c.name}</div>
                      <div className="text-[11px] text-subtle flex items-center gap-1.5">
                        {c.group && <span className="truncate">{c.group}</span>}
                        {c.group && <span>·</span>}
                        <span className="mono shrink-0">{c.count} produits</span>
                      </div>
                    </div>
                    {c.mappedTo ? (
                      <div className="flex items-center gap-1.5">
                        <Icon name="arrowRight" size={12} className="text-subtle"/>
                        <Badge tone="info">{target?.name}</Badge>
                        <button onClick={() => map(c.id, null)} className="text-subtle hover:text-app">
                          <Icon name="x" size={12}/>
                        </button>
                      </div>
                    ) : (
                      <Button variant="primary" size="sm" leftIcon="link"
                              onClick={() => { setMapModal(c); setMapSearch(""); }}>Mapper…</Button>
                    )}
                  </div>
                );
              })}
            </div>
          </Card>

          {/* Right: AXLU taxonomy */}
          <Card title="Taxonomie AXLU" padding="none"
                action={
                  <div className="flex items-center gap-1">
                    <Button size="sm" variant="ghost" onClick={toggleAllGroups}>{allExpanded ? "Tout replier" : "Tout déplier"}</Button>
                    <Button size="sm" variant="ghost" leftIcon="plus" onClick={openNewCat}>Ajouter</Button>
                  </div>
                }>
            <div className="p-2 overflow-y-auto" style={{ maxHeight: 600 }}>
              {CATEGORIES_AXLU.filter((c) => !c.parent).map((parent) => {
                const isExpanded = expandedGroups.has(parent.id);
                const children = CATEGORIES_AXLU.filter((c) => c.parent === parent.id);
                return (
                <div key={parent.id} className="mb-1">
                  <div className="flex items-center gap-2 p-2 rounded-md hover-row group cursor-pointer"
                       onClick={() => toggleGroup(parent.id)}>
                    <Icon name={isExpanded ? "chevronDown" : "chevronRight"} size={13} className="text-subtle"/>
                    <Icon name="folder" size={14} className="accent-fg"/>
                    <span className="font-semibold text-[13px]">{parent.name}</span>
                    <span className="text-[11px] text-subtle">{children.length} sous-cat.</span>
                    <span className="text-[11px] text-subtle mono">· {parent.count} produits</span>
                    <div className="flex-1"/>
                    <div className="opacity-0 group-hover:opacity-100 flex items-center gap-1" onClick={(e) => e.stopPropagation()}>
                      <IconButton icon="edit" size="sm" onClick={() => openEditParent(parent)}/>
                      <IconButton icon="plus" size="sm" onClick={() => openAddChild(parent)}/>
                      <IconButton icon="trash" size="sm" onClick={() => setDeleteCat(parent)}
                                  disabled={!window.AxluStore.can("delete")}/>
                    </div>
                  </div>
                  {isExpanded && (
                    <div className="ml-5 border-l" style={{ borderColor: "var(--border)" }}>
                      {children.length === 0 && (
                        <div className="ml-2 p-2 text-[11px] text-subtle">Aucune sous-catégorie.</div>
                      )}
                      {children.map((child) => {
                        const isDropTarget = dragId !== null;
                        return (
                          <div key={child.id}
                               className={`flex items-center gap-2 p-2 ml-2 rounded-md hover-row group ${isDropTarget ? "border border-dashed" : ""}`}
                               style={{ borderColor: isDropTarget ? "var(--accent)" : "transparent" }}
                               onDragOver={(e) => e.preventDefault()}
                               onDrop={() => { if (dragId) { map(dragId, child.id); setDragId(null); } }}>
                            <span className="text-subtle mono">└</span>
                            <span className="text-[12.5px]">{child.name}</span>
                            <div className="flex-1"/>
                            <span className="text-[11px] text-subtle mono">{child.count} produits</span>
                            <div className="opacity-0 group-hover:opacity-100">
                              <Dropdown
                                trigger={<IconButton icon="more" size="sm"/>}
                                align="right"
                                items={[
                                  { label: "Renommer", icon: "edit", onClick: () => openRenameChild(child) },
                                  { label: "Supprimer", icon: "trash", danger: true, onClick: () => setDeleteCat(child), disabled: !window.AxluStore.can("delete") },
                                ]}/>
                            </div>
                          </div>
                        );
                      })}
                    </div>
                  )}
                </div>
                );
              })}
            </div>
          </Card>
        </div>

        <div className="mt-3 text-[12px] text-muted flex items-center gap-2">
          <Icon name="info" size={12}/>
          Glisser-déposer une catégorie fournisseur sur une catégorie AXLU pour mapper, ou utiliser le menu "Mapper…".
        </div>
      </div>

      {/* Map a PF supplier category to an AXLU category */}
      <Modal open={!!mapModal} onClose={() => setMapModal(null)}
             title={`Mapper « ${mapModal ? mapModal.name : ""} » vers une catégorie AXLU`} width={520}
             footer={<Button variant="ghost" onClick={() => setMapModal(null)}>Fermer</Button>}>
        <div className="space-y-2">
          <Input leftIcon="search" value={mapSearch} onChange={(e) => setMapSearch(e.target.value)}
                 placeholder="Rechercher une catégorie / sous-catégorie AXLU…"/>
          <div className="max-h-[340px] overflow-y-auto border rounded-md" style={{ borderColor: "var(--border)" }}>
            {mapTargets.length === 0 ? (
              <div className="px-2.5 py-3 text-[12px] text-subtle">Aucune catégorie ne correspond.</div>
            ) : mapTargets.map((o) => (
              <button key={o.id} type="button"
                      onClick={() => { if (mapModal) map(mapModal.id, o.id); setMapModal(null); }}
                      className="w-full text-left px-2.5 py-1.5 text-[12px] hover-row flex items-center gap-1">
                {o.parentName && <><span className="text-muted">{o.parentName}</span><span className="text-subtle">›</span></>}
                <span className="font-medium">{o.name}</span>
              </button>
            ))}
          </div>
        </div>
      </Modal>

      {/* Edit parent name */}
      <Modal open={!!editParent} onClose={() => setEditParent(null)}
             title="Renommer la catégorie"
             footer={<>
               <Button variant="ghost" onClick={() => setEditParent(null)}>Annuler</Button>
               <Button variant="primary" onClick={() => {
                 if (!nameInput.trim()) return;
                 window.AxluStore.dispatch("category.update", { id: editParent.id, patch: { name: nameInput.trim() } });
                 toast("Catégorie renommée", { tone: "success" });
                 setEditParent(null);
               }}>Enregistrer</Button>
             </>}>
        <Field label="Nom"><Input value={nameInput} onChange={(e) => setNameInput(e.target.value)} size="lg"/></Field>
      </Modal>

      {/* Add child to parent */}
      <Modal open={!!addChildOf} onClose={() => setAddChildOf(null)}
             title={`Nouvelle sous-catégorie · ${addChildOf?.name || ""}`}
             footer={<>
               <Button variant="ghost" onClick={() => setAddChildOf(null)}>Annuler</Button>
               <Button variant="primary" onClick={() => {
                 if (!nameInput.trim()) return;
                 window.AxluStore.dispatch("category.create", { data: { name: nameInput.trim(), parent: addChildOf.id } });
                 toast("Sous-catégorie créée", { tone: "success" });
                 setAddChildOf(null);
               }}>Créer</Button>
             </>}>
        <Field label="Nom"><Input value={nameInput} onChange={(e) => setNameInput(e.target.value)} size="lg" placeholder="Ex. Stylos métal"/></Field>
      </Modal>

      {/* Rename child */}
      <Modal open={!!renameChild} onClose={() => setRenameChild(null)}
             title="Renommer la sous-catégorie"
             footer={<>
               <Button variant="ghost" onClick={() => setRenameChild(null)}>Annuler</Button>
               <Button variant="primary" onClick={() => {
                 if (!nameInput.trim()) return;
                 window.AxluStore.dispatch("category.update", { id: renameChild.id, patch: { name: nameInput.trim() } });
                 toast("Sous-catégorie renommée", { tone: "success" });
                 setRenameChild(null);
               }}>Enregistrer</Button>
             </>}>
        <Field label="Nom"><Input value={nameInput} onChange={(e) => setNameInput(e.target.value)} size="lg"/></Field>
      </Modal>

      {/* Delete category confirm — works for a group or a sub-category */}
      <Modal open={!!deleteCat} onClose={() => setDeleteCat(null)}
             title={deleteCat && !deleteCat.parent ? "Supprimer la catégorie ?" : "Supprimer la sous-catégorie ?"}
             footer={<>
               <Button variant="ghost" onClick={() => setDeleteCat(null)}>Annuler</Button>
               <Button variant="danger" onClick={() => {
                 window.AxluStore.dispatch("category.delete", { id: deleteCat.id });
                 toast("Catégorie supprimée", { tone: "warning" });
                 setDeleteCat(null);
               }}>Supprimer</Button>
             </>}>
        {deleteCat && (
          <div className="text-[13px] space-y-2">
            <div>
              La catégorie <strong>{deleteCat.name}</strong> sera supprimée et les mappings PF associés seront détachés.
            </div>
            {!deleteCat.parent && CATEGORIES_AXLU.filter((c) => c.parent === deleteCat.id).length > 0 && (
              <div className="text-red-600 font-medium">
                Ses {CATEGORIES_AXLU.filter((c) => c.parent === deleteCat.id).length} sous-catégorie(s) seront également supprimées.
              </div>
            )}
          </div>
        )}
      </Modal>

      {/* New category */}
      <Modal open={newCatOpen} onClose={() => setNewCatOpen(false)}
             title="Nouvelle catégorie AXLU"
             footer={<>
               <Button variant="ghost" onClick={() => setNewCatOpen(false)}>Annuler</Button>
               <Button variant="primary" onClick={() => {
                 if (!nameInput.trim()) return;
                 window.AxluStore.dispatch("category.create", { data: { name: nameInput.trim(), parent: parentInput || null } });
                 toast("Catégorie créée", { tone: "success" });
                 setNewCatOpen(false);
               }}>Créer</Button>
             </>}>
        <div className="space-y-3">
          <Field label="Nom"><Input value={nameInput} onChange={(e) => setNameInput(e.target.value)} size="lg" placeholder="Ex. Bagagerie"/></Field>
          <Field label="Parent">
            <Select value={parentInput} onChange={setParentInput} options={[
              { value: "", label: "Aucun (racine)" },
              ...CATEGORIES_AXLU.filter((c) => !c.parent).map((p) => ({ value: p.id, label: p.name })),
            ]}/>
          </Field>
        </div>
      </Modal>
    </div>
  );
}

// ─── Pricing — marking grids + modifiers + costs ───────────────────────
function PricingScreen() {
  const v = useAxluStore();
  const { PF_PRINT_PRICES, MARKING_COEFS, MODIFIERS_CATEGORY, MODIFIERS_CLIENT, MODIFIERS_ORDER, PRICING_COSTS, SHIPPING_TIERS, CATEGORIES_AXLU, CLIENT_TYPES } = window.AxluData;
  const [tab, setTab] = useState("marking");
  React.useEffect(() => { window.AxluStore.acquireLock && window.AxluStore.acquireLock('page:pricing'); return () => window.AxluStore.releaseLock && window.AxluStore.releaseLock('page:pricing'); }, []);

  return (
    <div>
      <LockBanner resource="page:pricing"/>
      <PageHeader
        breadcrumb={[{ label: "Pricing" }]}
        title="Règles de prix"
        subtitle={`${(PF_PRINT_PRICES || []).length} technique${(PF_PRINT_PRICES || []).length > 1 ? "s" : ""} de marquage importée${(PF_PRINT_PRICES || []).length > 1 ? "s" : ""}`}
        tabs={
          <Tabs value={tab} onChange={setTab} tabs={[
            { id: "marking", label: "Marquages fournisseur", icon: "tag", count: (PF_PRINT_PRICES || []).length },
            { id: "modifiers", label: "Modificateurs", icon: "settings", count: MODIFIERS_CATEGORY.length + MODIFIERS_CLIENT.length + MODIFIERS_ORDER.length },
            { id: "costs", label: "Coûts & marges", icon: "coin" },
            { id: "shipping", label: "Frais de port", icon: "cart", count: (SHIPPING_TIERS || []).length },
          ]}/>
        }
      />

      <div className="p-6 space-y-6">
        {tab === "marking" && (
          <MarkingTechniquesTab printPrices={PF_PRINT_PRICES || []} coefs={MARKING_COEFS || {}} costs={PRICING_COSTS || {}}/>
        )}
        {tab === "modifiers" && (
          <ModifiersTab
            categories={CATEGORIES_AXLU || []}
            clientTypes={CLIENT_TYPES || []}
            modCat={MODIFIERS_CATEGORY}
            modClient={MODIFIERS_CLIENT}
            modOrder={MODIFIERS_ORDER}
          />
        )}
        {tab === "costs" && (
          <CostsTab costs={PRICING_COSTS}/>
        )}
        {tab === "shipping" && (
          <ShippingTab tiers={SHIPPING_TIERS || []}/>
        )}
      </div>
    </div>
  );
}

// ─── Marking techniques tab (PF-driven) ────────────────────────────────
// Lists the supplier's actual marking techniques (from the print-price feed).
// Each can carry a per-technique coefficient that overrides the global one.
function MarkingTechniquesTab({ printPrices, coefs, costs }) {
  const toast = useToast();
  const globalCoef = (costs && costs.markingCoef) || 2;
  const [search, setSearch] = useState("");

  if (!printPrices.length) {
    return (
      <Card padding="lg">
        <EmptyState icon="tag" title="Aucune technique de marquage importée"
                    description="Lancez le flux « Prix d'impression » du fournisseur (Importer tout) pour récupérer toutes les techniques et leurs barèmes."/>
      </Card>
    );
  }

  // Cost range across a technique's whole price matrix.
  const costRange = (pe) => {
    const prices = [];
    (pe.priceMatrix || []).forEach((c) => (c.tiers || []).forEach((t) => { if (t.price > 0) prices.push(t.price); }));
    if (!prices.length) return null;
    return { min: Math.min(...prices), max: Math.max(...prices) };
  };

  const filtered = printPrices.filter((p) =>
    !search || (p.printCode + " " + (p.method || "")).toLowerCase().includes(search.toLowerCase())
  );
  const overrideCount = Object.keys(coefs || {}).length;

  return (
    <div className="space-y-4">
      <Card padding="md">
        <div className="text-[12.5px] text-muted">
          Les grilles de marquage sont <strong>reprises directement du fournisseur</strong> (flux Prix d'impression) — pas créées à la main.
          Le <strong>prix de vente</strong> d'un marquage = coût PF × coefficient.
          Coefficient global : <strong>× {globalCoef}</strong> (modifiable dans l'onglet Coûts &amp; marges).
          Vous pouvez <strong>surcharger le coefficient technique par technique</strong> ci-dessous.
          {overrideCount > 0 && <span> · {overrideCount} technique{overrideCount > 1 ? "s" : ""} surchargée{overrideCount > 1 ? "s" : ""}.</span>}
        </div>
      </Card>

      <div className="flex items-center gap-2">
        <Input leftIcon="search" placeholder="Rechercher un code ou une technique…" value={search}
               onChange={(e) => setSearch(e.target.value)} className="w-[320px]"/>
        <div className="flex-1"/>
        <span className="text-[12px] text-muted">{filtered.length} / {printPrices.length} techniques</span>
      </div>

      <Card padding="none">
        <table className="axlu-table">
          <thead>
            <tr>
              <th>Code</th><th>Technique</th>
              <th className="text-right">Combinaisons</th>
              <th className="text-right">Coût PF / u.</th>
              <th className="text-right" style={{ width: 150 }}>Coefficient</th>
              <th className="text-right">Prix vente / u.</th>
              <th style={{ width: 40 }}></th>
            </tr>
          </thead>
          <tbody>
            {filtered.map((pe) => {
              const cr = costRange(pe);
              const hasOverride = coefs[pe.printCode] != null;
              const effCoef = hasOverride ? coefs[pe.printCode] : globalCoef;
              return (
                <tr key={pe.printCode} className="hover-row">
                  <td className="mono text-[12px] font-medium">{pe.printCode}</td>
                  <td>{pe.method || "—"}</td>
                  <td className="text-right mono text-[12px]">{(pe.priceMatrix || []).length}</td>
                  <td className="text-right mono tabular-nums text-muted text-[12px]">
                    {cr ? (cr.min === cr.max ? fmt.eur(cr.min) : `${fmt.eur(cr.min)} – ${fmt.eur(cr.max)}`) : "—"}
                  </td>
                  <td className="text-right">
                    <TechniqueCoefInput printCode={pe.printCode} value={hasOverride ? coefs[pe.printCode] : ""} placeholder={String(globalCoef)} toast={toast}/>
                  </td>
                  <td className="text-right mono tabular-nums font-medium">
                    {cr ? (cr.min === cr.max ? fmt.eur(cr.min * effCoef) : `${fmt.eur(cr.min * effCoef)} – ${fmt.eur(cr.max * effCoef)}`) : "—"}
                  </td>
                  <td className="text-right">
                    {hasOverride && (
                      <IconButton icon="x" size="sm" title="Revenir au coefficient global"
                                  onClick={() => { window.AxluStore.dispatch("markingCoef.setTech", { printCode: pe.printCode, coef: null }); toast("Coefficient réinitialisé"); }}/>
                    )}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </Card>
    </div>
  );
}

// Editable per-technique coefficient. Empty = uses the global coefficient.
function TechniqueCoefInput({ printCode, value, placeholder, toast }) {
  const [draft, setDraft] = useState(value === "" || value == null ? "" : String(value));
  React.useEffect(() => { setDraft(value === "" || value == null ? "" : String(value)); }, [value]);
  const commit = () => {
    const trimmed = draft.trim();
    window.AxluStore.dispatch("markingCoef.setTech", { printCode, coef: trimmed === "" ? null : trimmed });
    if (trimmed !== "") toast(`Coefficient ${printCode} → × ${trimmed}`, { tone: "success" });
  };
  return (
    <Input value={draft} onChange={(e) => setDraft(e.target.value)} onBlur={commit}
           onKeyDown={(e) => { if (e.key === "Enter") e.currentTarget.blur(); }}
           placeholder={`× ${placeholder}`} className="mono text-right w-[120px] inline-block"/>
  );
}

// ─── Grids tab (legacy — manual marking grids, no longer surfaced) ──────
function GridsTab({ grids, activeGridId, setActiveGridId, onCreate }) {
  const toast = useToast();
  const activeGrid = grids.find((g) => g.id === activeGridId);
  const [renameOpen, setRenameOpen] = useState(false);
  const [deleteOpen, setDeleteOpen] = useState(false);
  const [renameMarking, setRenameMarking] = useState("");
  const [renameFormat, setRenameFormat] = useState("");

  if (grids.length === 0) {
    return (
      <Card padding="lg">
        <EmptyState
          icon="tag"
          title="Aucune grille de marquage"
          description="Créez une grille par couple (type de marquage × format). Ex. : DTF × Cœur, Sérigraphie × A5…"
          action={<Button variant="primary" leftIcon="plus" onClick={onCreate}>Créer la première grille</Button>}
        />
      </Card>
    );
  }
  if (!activeGrid) return null;

  const onDuplicate = () => {
    const id = window.AxluStore.dispatch("markingGrid.duplicate", { id: activeGrid.id });
    if (id) setActiveGridId(id);
    toast("Grille dupliquée", { tone: "success" });
  };
  const onDelete = () => {
    window.AxluStore.dispatch("markingGrid.delete", { id: activeGrid.id });
    const remaining = grids.filter((g) => g.id !== activeGrid.id);
    if (remaining[0]) setActiveGridId(remaining[0].id);
    toast("Grille supprimée", { tone: "warning" });
    setDeleteOpen(false);
  };
  const onRename = () => {
    window.AxluStore.dispatch("markingGrid.update", {
      id: activeGrid.id,
      patch: { markingType: renameMarking.trim(), format: renameFormat.trim() },
    });
    toast("Grille renommée", { tone: "success" });
    setRenameOpen(false);
  };
  const toggleEnabled = () => {
    window.AxluStore.dispatch("markingGrid.toggleEnabled", { id: activeGrid.id });
  };

  const gridLabel = (g) => `${g.markingType || "Marquage"}${g.format ? " · " + g.format : ""}`;

  return (
    <div className="grid grid-cols-12 gap-6">
      <div className="col-span-4">
        <Card padding="none" title="Grilles">
          <div className="p-1.5 max-h-[600px] overflow-auto">
            {grids.map((g) => (
              <button key={g.id} onClick={() => setActiveGridId(g.id)}
                      className={`w-full text-left p-2.5 rounded-md transition-colors ${g.id === activeGridId ? "accent-bg accent-fg" : "hover:bg-[var(--hover)]"}`}>
                <div className="flex items-center gap-2">
                  <Icon name="tag" size={13}/>
                  <span className="text-[12.5px] font-medium truncate flex-1">{g.markingType || "—"}</span>
                  {!g.enabled && <Badge tone="muted">off</Badge>}
                </div>
                <div className="text-[11px] text-subtle mt-0.5 ml-5 mono">{g.format || "—"} · {g.tiers.length} tranche{g.tiers.length > 1 ? "s" : ""}</div>
              </button>
            ))}
          </div>
        </Card>
      </div>

      <div className="col-span-8 space-y-4">
        <Card padding="md" title={gridLabel(activeGrid)}
              action={
                <div className="flex items-center gap-2">
                  <button onClick={toggleEnabled}
                          className="w-9 h-5 rounded-full relative transition-colors"
                          title={activeGrid.enabled ? "Désactiver" : "Activer"}
                          style={{ background: activeGrid.enabled ? "var(--accent)" : "var(--border-strong)" }}>
                    <span className="absolute top-0.5 w-4 h-4 bg-white rounded-full transition-all" style={{ left: activeGrid.enabled ? 18 : 2 }}/>
                  </button>
                  <Dropdown
                    trigger={<IconButton icon="more" size="sm"/>}
                    align="right"
                    items={[
                      { label: "Renommer", icon: "edit", onClick: () => { setRenameMarking(activeGrid.markingType || ""); setRenameFormat(activeGrid.format || ""); setRenameOpen(true); } },
                      { label: "Dupliquer", icon: "copy", onClick: onDuplicate },
                      "sep",
                      { label: "Supprimer", icon: "trash", danger: true, onClick: () => setDeleteOpen(true), disabled: !window.AxluStore.can("delete") },
                    ]}/>
                </div>
              }>
          <div className="grid grid-cols-2 gap-3 mb-4">
            <Field label="Type de marquage">
              <Input value={activeGrid.markingType || ""}
                     onChange={(e) => window.AxluStore.dispatch("markingGrid.update", { id: activeGrid.id, patch: { markingType: e.target.value } })}
                     placeholder="ex. Transfert DTF, Sérigraphie, Broderie…"/>
            </Field>
            <Field label="Format / zone">
              <Input value={activeGrid.format || ""}
                     onChange={(e) => window.AxluStore.dispatch("markingGrid.update", { id: activeGrid.id, patch: { format: e.target.value } })}
                     placeholder="ex. Cœur, A5, Manche, Poche…"/>
            </Field>
          </div>

          <div className="text-[12.5px] font-semibold mb-2">Tranches de quantité</div>
          <table className="axlu-table">
            <thead>
              <tr>
                <th style={{ width: "30%" }}>De (qté)</th>
                <th style={{ width: "30%" }}>À (qté)</th>
                <th className="text-right">Prix unitaire</th>
                <th></th>
              </tr>
            </thead>
            <tbody>
              {activeGrid.tiers.map((t, i) => (
                <tr key={i}>
                  <td>
                    <Input value={t.from}
                           onChange={(e) => window.AxluStore.dispatch("markingGrid.updateTier", { id: activeGrid.id, idx: i, patch: { from: parseInt(e.target.value) || 0 } })}
                           className="mono"/>
                  </td>
                  <td>
                    <Input value={t.to == null ? "" : t.to}
                           onChange={(e) => {
                             const val = e.target.value.trim();
                             window.AxluStore.dispatch("markingGrid.updateTier", { id: activeGrid.id, idx: i, patch: { to: val === "" ? null : parseInt(val) || 0 } });
                           }}
                           placeholder="et +"
                           className="mono"/>
                  </td>
                  <td className="text-right">
                    <Input value={t.unitPrice}
                           onChange={(e) => window.AxluStore.dispatch("markingGrid.updateTier", { id: activeGrid.id, idx: i, patch: { unitPrice: parseFloat(e.target.value) || 0 } })}
                           className="mono text-right"/>
                  </td>
                  <td className="text-right">
                    <IconButton icon="trash" size="sm"
                                onClick={() => window.AxluStore.dispatch("markingGrid.removeTier", { id: activeGrid.id, idx: i })}/>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
          <Button variant="ghost" size="sm" leftIcon="plus" className="mt-2"
                  onClick={() => window.AxluStore.dispatch("markingGrid.addTier", { id: activeGrid.id })}>
            Ajouter une tranche
          </Button>
          <div className="mt-3 text-[11px] text-subtle">
            Le prix unitaire de la tranche correspondante sera multiplié par la quantité commandée.
          </div>
        </Card>
      </div>

      <Modal open={renameOpen} onClose={() => setRenameOpen(false)}
             title="Renommer la grille"
             footer={<>
               <Button variant="ghost" onClick={() => setRenameOpen(false)}>Annuler</Button>
               <Button variant="primary" onClick={onRename}>Enregistrer</Button>
             </>}>
        <div className="space-y-3">
          <Field label="Type de marquage"><Input value={renameMarking} onChange={(e) => setRenameMarking(e.target.value)} size="lg"/></Field>
          <Field label="Format / zone"><Input value={renameFormat} onChange={(e) => setRenameFormat(e.target.value)} size="lg"/></Field>
        </div>
      </Modal>

      <Modal open={deleteOpen} onClose={() => setDeleteOpen(false)}
             title="Supprimer la grille ?"
             footer={<>
               <Button variant="ghost" onClick={() => setDeleteOpen(false)}>Annuler</Button>
               <Button variant="danger" onClick={onDelete}>Supprimer</Button>
             </>}>
        <div className="text-[13px]">La grille <strong>{gridLabel(activeGrid)}</strong> sera supprimée définitivement.</div>
      </Modal>
    </div>
  );
}

// ─── Modifiers tab ──────────────────────────────────────────────────────
function ModifiersTab({ categories, clientTypes, modCat, modClient, modOrder }) {
  return (
    <div className="space-y-6">
      <ModifierSection
        scope="category"
        title="Modificateurs par catégorie produit"
        description="Coefficient multiplicatif appliqué selon la catégorie AXLU du produit."
        items={modCat}
        keyOptions={categories.map((c) => ({ value: c.id, label: c.name }))}
        keyLabel="Catégorie"
      />
      <ModifierSection
        scope="client"
        title="Modificateurs par type de client"
        description="Coefficient multiplicatif selon le profil de l'acheteur (B2B, particulier, pro…)."
        items={modClient}
        keyOptions={clientTypes.map((c) => ({ value: c.id, label: c.label }))}
        keyLabel="Type de client"
      />
      <ModifierSection
        scope="order"
        title="Paliers de remise par montant de commande"
        description="Coefficient appliqué selon le total de la commande (€). Cumulatif avec les autres modificateurs."
        items={modOrder}
        rangeMode
      />
    </div>
  );
}

function ModifierSection({ scope, title, description, items, keyOptions, keyLabel, rangeMode }) {
  const toast = useToast();
  const onAdd = () => {
    window.AxluStore.dispatch("modifier.create", { scope });
    toast("Modificateur ajouté", { tone: "success" });
  };
  const onUpdate = (id, patch) => {
    window.AxluStore.dispatch("modifier.update", { scope, id, patch });
  };
  const onDelete = (id) => {
    window.AxluStore.dispatch("modifier.delete", { scope, id });
    toast("Modificateur supprimé", { tone: "warning" });
  };
  const onToggle = (id) => {
    window.AxluStore.dispatch("modifier.toggleEnabled", { scope, id });
  };

  return (
    <Card padding="md" title={title} action={<Button size="sm" variant="secondary" leftIcon="plus" onClick={onAdd}>Ajouter</Button>}>
      <div className="text-[12px] text-muted mb-3">{description}</div>
      {items.length === 0 ? (
        <div className="sunken rounded-md p-4 text-center text-[12.5px] text-muted">
          Aucun modificateur. Cliquez sur « Ajouter » pour en créer un.
        </div>
      ) : (
        <table className="axlu-table">
          <thead>
            <tr>
              <th style={{ width: 32 }}></th>
              <th>Nom</th>
              {rangeMode ? (
                <>
                  <th>De (€)</th>
                  <th>À (€)</th>
                </>
              ) : (
                <th>{keyLabel}</th>
              )}
              <th className="text-right">Coefficient</th>
              <th className="text-right" style={{ width: 60 }}></th>
            </tr>
          </thead>
          <tbody>
            {items.map((m) => (
              <tr key={m.id}>
                <td>
                  <button onClick={() => onToggle(m.id)}
                          className="w-7 h-4 rounded-full relative transition-colors"
                          title={m.enabled ? "Désactiver" : "Activer"}
                          style={{ background: m.enabled ? "var(--accent)" : "var(--border-strong)" }}>
                    <span className="absolute top-0.5 w-3 h-3 bg-white rounded-full transition-all" style={{ left: m.enabled ? 14 : 2 }}/>
                  </button>
                </td>
                <td>
                  <Input value={m.name || ""} onChange={(e) => onUpdate(m.id, { name: e.target.value })} placeholder="Libellé"/>
                </td>
                {rangeMode ? (
                  <>
                    <td>
                      <Input value={m.from ?? 0}
                             onChange={(e) => onUpdate(m.id, { from: parseFloat(e.target.value) || 0 })}
                             className="mono"/>
                    </td>
                    <td>
                      <Input value={m.to == null ? "" : m.to}
                             onChange={(e) => {
                               const val = e.target.value.trim();
                               onUpdate(m.id, { to: val === "" ? null : parseFloat(val) || 0 });
                             }}
                             placeholder="et +"
                             className="mono"/>
                    </td>
                  </>
                ) : (
                  <td>
                    {keyOptions && keyOptions.length > 0 ? (
                      <Select value={m.key || ""} onChange={(val) => onUpdate(m.id, { key: val })}
                              options={[{ value: "", label: "— Choisir —" }, ...keyOptions]}/>
                    ) : (
                      <Input value={m.key || ""} onChange={(e) => onUpdate(m.id, { key: e.target.value })} placeholder="(libre)"/>
                    )}
                  </td>
                )}
                <td className="text-right">
                  <Input value={m.coef ?? 1}
                         onChange={(e) => onUpdate(m.id, { coef: parseFloat(e.target.value) || 0 })}
                         className="mono text-right w-[100px] inline-block"/>
                </td>
                <td className="text-right">
                  <IconButton icon="trash" size="sm" onClick={() => onDelete(m.id)}/>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
      {!rangeMode && keyOptions && keyOptions.length === 0 && items.length === 0 && (
        <div className="text-[11px] text-amber-600 mt-2">
          Aucune {keyLabel.toLowerCase()} disponible — ajoutez d'abord des entrées dans la page correspondante.
        </div>
      )}
    </Card>
  );
}

// ─── Costs tab ──────────────────────────────────────────────────────────
function CostsTab({ costs }) {
  const toast = useToast();
  const c = costs || {};
  const blank = () => ({
    baseCoef: c.baseCoef ?? 2.5,
    markingCoef: c.markingCoef ?? 2.0,
  });
  const [form, setForm] = useState(blank);

  React.useEffect(() => { setForm(blank()); }, [c.baseCoef, c.markingCoef]);

  const set = (k, val) => setForm((f) => ({ ...f, [k]: val }));

  const save = () => {
    window.AxluStore.dispatch("pricingCosts.update", {
      patch: {
        baseCoef: parseFloat(form.baseCoef) || 0,
        markingCoef: parseFloat(form.markingCoef) || 0,
      },
    });
    toast("Paramètres enregistrés", { tone: "success" });
  };

  const cancel = () => { setForm(blank()); toast("Modifications annulées"); };

  return (
    <div className="space-y-4">
      <Card padding="md" title="Coûts produit & marge">
        <div className="text-[12px] text-muted mb-4">
          Ces paramètres s'appliquent à tous les produits.
        </div>
        <div className="grid grid-cols-2 gap-4">
          <Field label="Coefficient de base sur coût produit (×)">
            <Input value={form.baseCoef} onChange={(e) => set("baseCoef", e.target.value)} className="mono"/>
            <div className="text-[11px] text-subtle mt-1">Prix vente produit nu = prix d'achat × coef.</div>
          </Field>
          <Field label="Coefficient marquage (×)">
            <Input value={form.markingCoef} onChange={(e) => set("markingCoef", e.target.value)} className="mono"/>
            <div className="text-[11px] text-subtle mt-1">Prix vente marquage = coût marquage PF × coef.</div>
          </Field>
        </div>
      </Card>
      <div className="flex justify-end gap-2">
        <Button variant="ghost" onClick={cancel}>Annuler</Button>
        <Button variant="primary" leftIcon="check" onClick={save}>Enregistrer</Button>
      </div>
    </div>
  );
}

// ─── Shipping fee tab ───────────────────────────────────────────────────
function ShippingTab({ tiers }) {
  const toast = useToast();
  const list = tiers || [];

  const update = (idx, patch) => window.AxluStore.dispatch("shippingTier.update", { idx, patch });
  const onAdd = () => {
    window.AxluStore.dispatch("shippingTier.add", {});
    toast("Tranche ajoutée", { tone: "success" });
  };
  const onRemove = (idx) => {
    window.AxluStore.dispatch("shippingTier.remove", { idx });
    toast("Tranche supprimée", { tone: "warning" });
  };

  return (
    <div className="space-y-4">
      <Card padding="md" title="Frais de port selon le montant du panier">
        <div className="text-[12px] text-muted mb-4">
          Le transport n'est plus intégré au prix de chaque produit : il est facturé au client
          comme un frais de port unique, déterminé par le montant total du panier.
        </div>
        {list.length === 0 ? (
          <div className="sunken rounded-md p-4 text-center text-[12.5px] text-muted">
            Aucune tranche. Cliquez sur « Ajouter une tranche » pour en créer une.
          </div>
        ) : (
          <table className="axlu-table">
            <thead>
              <tr>
                <th style={{ width: "28%" }}>Panier de (€)</th>
                <th style={{ width: "28%" }}>Panier à (€)</th>
                <th className="text-right">Frais de port (€)</th>
                <th style={{ width: 60 }}></th>
              </tr>
            </thead>
            <tbody>
              {list.map((t, i) => (
                <tr key={i}>
                  <td>
                    <Input value={t.from ?? 0}
                           onChange={(e) => update(i, { from: parseFloat(e.target.value) || 0 })}
                           className="mono"/>
                  </td>
                  <td>
                    <Input value={t.to == null ? "" : t.to}
                           onChange={(e) => {
                             const val = e.target.value.trim();
                             update(i, { to: val === "" ? null : parseFloat(val) || 0 });
                           }}
                           placeholder="et +"
                           className="mono"/>
                  </td>
                  <td>
                    <div className="flex items-center justify-end gap-2">
                      {!(t.fee > 0) && <Badge tone="success">Gratuit</Badge>}
                      <Input value={t.fee ?? 0}
                             onChange={(e) => update(i, { fee: parseFloat(e.target.value) || 0 })}
                             className="mono text-right w-[110px] inline-block"/>
                    </div>
                  </td>
                  <td className="text-right">
                    <IconButton icon="trash" size="sm" onClick={() => onRemove(i)}/>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
        <Button variant="ghost" size="sm" leftIcon="plus" className="mt-2" onClick={onAdd}>
          Ajouter une tranche
        </Button>
        <div className="mt-3 text-[11px] text-subtle">
          La tranche dont la plage [de ; à[ contient le total du panier détermine le frais de port.
          Laissez « Panier à » vide pour la dernière tranche (et au-delà). Un frais de 0 € correspond à la livraison gratuite.
        </div>
      </Card>
    </div>
  );
}

// ─── Pricing math (shared) ─────────────────────────────────────────────
function findGridTier(grid, qty) {
  if (!grid || !grid.tiers) return null;
  return grid.tiers.find((t) => qty >= t.from && (t.to == null || qty <= t.to)) || null;
}
function modifierForCategory(modCat, categoryId) {
  const m = (modCat || []).find((x) => x.enabled && x.key === categoryId);
  return m ? (m.coef || 1) : 1;
}
function modifierForClient(modClient, clientType) {
  const m = (modClient || []).find((x) => x.enabled && x.key === clientType);
  return m ? (m.coef || 1) : 1;
}
function modifierForOrderTotal(modOrder, total) {
  const m = (modOrder || []).find((x) => x.enabled && total >= (x.from || 0) && (x.to == null || total <= x.to));
  return m ? (m.coef || 1) : 1;
}

// ─── Live simulator panel ──────────────────────────────────────────────
function PricingSimulatorPanel({ grids, costs, modCat, modClient, modOrder, categories, clientTypes }) {
  const enabledGrids = grids.filter((g) => g.enabled);
  const [gridId, setGridId] = useState(enabledGrids[0]?.id || "");
  const [qty, setQty] = useState(50);
  const [categoryId, setCategoryId] = useState(categories[0]?.id || "");
  const [clientType, setClientType] = useState(clientTypes[0]?.id || "");
  const [orderTotal, setOrderTotal] = useState(0);

  React.useEffect(() => {
    if (!enabledGrids.find((g) => g.id === gridId)) setGridId(enabledGrids[0]?.id || "");
  }, [grids.length, enabledGrids.length]);

  const grid = grids.find((g) => g.id === gridId);
  const tier = findGridTier(grid, qty);
  const baseUnit = tier ? (tier.unitPrice || 0) : 0;
  const cCat = modifierForCategory(modCat, categoryId);
  const cClient = modifierForClient(modClient, clientType);
  const cOrder = modifierForOrderTotal(modOrder, orderTotal);
  const finalUnit = baseUnit * cCat * cClient * cOrder;
  const total = finalUnit * qty;
  const margin = total - baseUnit * qty;

  return (
    <Card title="Simulateur" padding="md">
      <div className="space-y-3">
        <Field label="Grille de marquage">
          {enabledGrids.length === 0 ? (
            <div className="text-[11.5px] text-amber-600 sunken rounded p-2">Aucune grille active</div>
          ) : (
            <Select value={gridId} onChange={setGridId}
                    options={enabledGrids.map((g) => ({ value: g.id, label: `${g.markingType || "—"} · ${g.format || "—"}` }))}/>
          )}
        </Field>
        <Field label="Quantité">
          <Input value={qty} onChange={(e) => setQty(parseInt(e.target.value) || 0)} className="mono"/>
        </Field>
        <Field label="Catégorie produit">
          {categories.length === 0 ? (
            <div className="text-[11.5px] text-subtle sunken rounded p-2">Aucune catégorie</div>
          ) : (
            <Select value={categoryId} onChange={setCategoryId}
                    options={categories.map((c) => ({ value: c.id, label: c.name }))}/>
          )}
        </Field>
        <Field label="Type de client">
          <Select value={clientType} onChange={setClientType}
                  options={clientTypes.map((c) => ({ value: c.id, label: c.label }))}/>
        </Field>
        <Field label="Total commande (€)">
          <Input value={orderTotal} onChange={(e) => setOrderTotal(parseFloat(e.target.value) || 0)} className="mono"/>
        </Field>
      </div>

      <div className="mt-4 sunken rounded-md p-3 space-y-1.5 text-[12px]">
        {!grid ? (
          <div className="text-muted">Sélectionnez une grille pour calculer.</div>
        ) : !tier ? (
          <div className="text-amber-600">Quantité hors plage des tranches.</div>
        ) : (
          <>
            <Row k={`Tranche ${tier.from}${tier.to == null ? "+" : "–" + tier.to}`}><span className="mono">{fmt.eur(baseUnit)} / unité</span></Row>
            <Row k="× catégorie"><span className="mono">× {cCat.toFixed(2)}</span></Row>
            <Row k="× client"><span className="mono">× {cClient.toFixed(2)}</span></Row>
            <Row k="× palier commande"><span className="mono">× {cOrder.toFixed(2)}</span></Row>
            <div className="border-t pt-2 mt-2" style={{ borderColor: "var(--border)" }}>
              <Row k="P.U. final"><span className="mono font-semibold">{fmt.eur(finalUnit)}</span></Row>
              <Row k={`Total × ${qty}`}><span className="mono font-semibold text-[14px]">{fmt.eur(total)}</span></Row>
              <Row k="Δ vs P.U. brut"><span className={`mono ${margin >= 0 ? "text-emerald-600" : "text-red-600"}`}>{margin >= 0 ? "+" : ""}{fmt.eur(margin)}</span></Row>
            </div>
          </>
        )}
      </div>
    </Card>
  );
}

// ─── Shopify publications ──────────────────────────────────────────────
function ShopifyScreen() {
  const v = useAxluStore();
  const { PRODUCTS } = window.AxluData;
  const lastSync = window.AxluData.SHOPIFY_LAST_SYNC;
  const initialTab = (() => { try { const qs = new URLSearchParams((location.hash.split("?")[1]) || ""); return qs.get("tab") || "published"; } catch (e) { return "published"; } })();
  const [tab, setTab] = useState(initialTab);
  const toast = useToast();
  // États dérivés du VRAI état produit, MUTUELLEMENT EXCLUSIFS (un produit n'est que dans UNE catégorie) :
  //  • Publiés = EN DIRECT sur Shopify (actif + sans erreur).
  //  • File d'attente = EN COURS DE PUBLICATION (drapeau transitoire posé pendant la poussée Shopify) — vide au repos.
  //  • Erreurs = désactivés auto / échec (avec motif) — quel que soit l'état.
  const errors = useMemo(() => PRODUCTS.filter((p) => p.shopifyError), [v, PRODUCTS]);
  const published = useMemo(() => PRODUCTS.filter((p) => p.shopifyPub === "active" && !p.shopifyError), [v, PRODUCTS]);
  const queue = useMemo(() => PRODUCTS.filter((p) => p.shopifyPublishing && !p.shopifyError), [v, PRODUCTS]);
  const [view, setView] = useState("list");
  const [page, setPage] = useState(1);
  React.useEffect(() => { setPage(1); }, [tab]);
  // Verrou : l'onglet Configuration est en lecture seule si un autre l'édite déjà.
  React.useEffect(() => { if (tab !== "config") return; window.AxluStore.acquireLock && window.AxluStore.acquireLock("page:publications-config"); return () => window.AxluStore.releaseLock && window.AxluStore.releaseLock("page:publications-config"); }, [tab]);
  const PER = 24;
  const active = tab === "queue" ? queue : tab === "errors" ? errors : published;
  const totalPages = Math.max(1, Math.ceil(active.length / PER));
  const pageItems = active.slice((page - 1) * PER, page * PER);

  const openFiche = (pid) => { window.location.hash = "#/products/" + pid; };

  const openShopify = (shopifyId) => {
    if (!shopifyId) return;
    const tail = String(shopifyId).split("/").pop();
    window.open(`https://admin.shopify.com/store/your-shop/products/${tail}`, "_blank");
  };

  return (
    <div>
      <PageHeader
        breadcrumb={[{ label: "Publications Shopify" }]}
        title="Publications Shopify"
        subtitle={`${published.length} publiés · ${queue.length} en file · ${errors.length} erreurs`}
        tabs={
          <Tabs value={tab} onChange={setTab} tabs={[
            { id: "queue",     label: "File d'attente",  count: queue.length },
            { id: "published", label: "Publiés",         count: published.length },
            { id: "errors",    label: "Erreurs",         count: errors.length },
            { id: "config",    label: "Configuration" },
          ]}/>
        }
      />

      <div className="p-6">
        {tab === "config" ? (<div><LockBanner resource="page:publications-config"/><ShopifyConfig/></div>) : (
          <>
            {lastSync && (
              <div className="text-[12px] text-muted mb-2">Dernière synchro Shopify : <strong>{fmt.rel(lastSync.at)}</strong> — {lastSync.pushed} poussés · {lastSync.demoted} désactivés{lastSync.failed ? ` · ${lastSync.failed} échecs` : ""} (sur {lastSync.checked} vérifiés)</div>
            )}
            <div className="flex items-center justify-between mb-3">
              <div className="text-[12px] text-muted">{active.length} produit{active.length > 1 ? "s" : ""}</div>
              <div className="flex gap-1">
                <Button size="sm" variant={view === "list" ? "primary" : "ghost"} onClick={() => setView("list")}>Liste</Button>
                <Button size="sm" variant={view === "gallery" ? "primary" : "ghost"} onClick={() => setView("gallery")}>Galerie</Button>
              </div>
            </div>

            {active.length === 0 && (
              <Card padding="md"><div className="text-center text-muted text-[12px] py-6">
                {tab === "queue" ? "Aucun produit en cours de publication." : tab === "errors" ? "Aucune erreur — aucun produit désactivé automatiquement." : "Aucun produit actif sur Shopify."}
              </div></Card>
            )}

            {active.length > 0 && view === "gallery" && (
              <CatalogGallery products={pageItems} navigate={(href) => { window.location.hash = href; }} selectable={false}/>
            )}

            {active.length > 0 && view === "list" && (
              <Card padding="none">
                <table className="axlu-table">
                  <thead>
                    {tab === "published" && <tr><th></th><th>SKU</th><th>Titre</th><th>ID Shopify</th><th>Publié le</th><th className="text-right">Prix</th><th></th></tr>}
                    {tab === "queue" && <tr><th></th><th>SKU</th><th>Titre</th><th>Statut</th><th></th></tr>}
                    {tab === "errors" && <tr><th></th><th>SKU</th><th>Produit</th><th>Message d'erreur</th><th></th></tr>}
                  </thead>
                  <tbody>
                    {pageItems.map((p) => (
                      <tr key={p.id} className="hover-row cursor-pointer" onClick={() => openFiche(p.id)}>
                        <td><ProductThumb sku={p.sku} src={p.imageUrl} hasImage size={28}/></td>
                        <td className="mono text-[12px]">{p.sku}</td>
                        <td>{p.title}</td>
                        {tab === "published" && <td className="mono text-[11px] text-muted truncate" style={{ maxWidth: 160 }}>{p.shopifyId}</td>}
                        {tab === "published" && <td className="mono text-[12px] text-muted">{fmt.rel(p.lastPublishedAt)}</td>}
                        {tab === "published" && <td className="text-right mono tabular-nums">{fmt.eur(p.sellPrice)}</td>}
                        {tab === "queue" && <td><ShopifyStateBadge pub={p.shopifyPub}/></td>}
                        {tab === "errors" && <td className="text-[12.5px] text-amber-600 dark:text-amber-400">{p.shopifyError}</td>}
                        <td className="text-right" onClick={(e) => e.stopPropagation()}>
                          {tab === "published" && p.shopifyId
                            ? <IconButton icon="link" size="sm" onClick={() => openShopify(p.shopifyId)}/>
                            : <Button size="sm" variant="ghost" onClick={() => openFiche(p.id)}>Voir</Button>}
                        </td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </Card>
            )}

            {active.length > 0 && (
              <Pagination page={page} lastPage={totalPages} setPage={setPage} shown={pageItems.length} total={active.length}/>
            )}
          </>
        )}
      </div>
    </div>
  );
}

// Sales channels (publications) AXLU publishes products to — needed so the
// headless storefront (Storefront API) returns them. Empty selection = all.
function ShopifyChannelsCard() {
  const v = useAxluStore();
  const toast = useToast();
  const [pubs, setPubs] = useState(null);
  const [err, setErr] = useState(null);
  const [loading, setLoading] = useState(false);
  const selected = (window.AxluData.SHOPIFY_PUB_CHANNELS) || [];

  const load = async () => {
    setLoading(true); setErr(null);
    try {
      const r = await window.AxluStore.shopifyListPublications();
      if (r && r.ok) setPubs(r.publications || []);
      else { setPubs([]); setErr((r && r.error) || "Échec"); }
    } catch (e) { setPubs([]); setErr(String(e)); }
    finally { setLoading(false); }
  };
  React.useEffect(() => { load(); }, []);

  const isAll = selected.length === 0;
  const isChecked = (id) => isAll || selected.indexOf(id) !== -1;
  const toggle = (id) => {
    const allIds = (pubs || []).map((p) => p.id);
    let next;
    if (isAll) next = allIds.filter((x) => x !== id);
    else if (selected.indexOf(id) !== -1) next = selected.filter((x) => x !== id);
    else next = selected.concat([id]);
    if (pubs && next.length === pubs.length) next = []; // covers everything → "all"
    window.AxluStore.dispatch("settings.setPubChannels", { ids: next });
    toast("Canaux de publication mis à jour");
  };
  const selectAll = () => { window.AxluStore.dispatch("settings.setPubChannels", { ids: [] }); toast("Tous les canaux sélectionnés"); };

  return (
    <Card title="Canaux de vente (publication)" padding="md">
      <div className="text-[12px] text-muted mb-3">
        À la publication, AXLU rend le produit disponible sur ces canaux — indispensable pour que ton <strong>site headless</strong> (API Storefront) le voie. Par défaut : <strong>tous</strong>.
      </div>
      {loading && <div className="text-[12px] text-subtle">Chargement des canaux…</div>}
      {!loading && err && (
        <div className="text-[12px]" style={{ color: "var(--danger)" }}>
          Impossible de lister les canaux. Ajoute les scopes <span className="mono text-[11px]">read_publications</span> et <span className="mono text-[11px]">write_publications</span> à ton app Shopify, puis reconnecte-toi.
        </div>
      )}
      {!loading && !err && pubs && pubs.length > 0 && (
        <div className="space-y-1.5">
          {pubs.map((p) => (
            <label key={p.id} className="flex items-center gap-2 text-[12.5px] cursor-pointer">
              <Checkbox checked={isChecked(p.id)} onChange={() => toggle(p.id)} />
              <span>{p.name}</span>
            </label>
          ))}
          {!isAll && <button type="button" onClick={selectAll} className="text-[11.5px] accent-fg hover:underline mt-1">Tout sélectionner</button>}
        </div>
      )}
      {!loading && !err && pubs && pubs.length === 0 && (
        <div className="text-[12px] text-subtle">Aucun canal de vente trouvé.</div>
      )}
    </Card>
  );
}

function ShopifyConfig() {
  const toast = useToast();
  const available = !!(window.axlu && window.axlu.shopify);
  const [shopUrl, setShopUrl] = useState("");
  const [clientId, setClientId] = useState("");
  const [clientSecret, setClientSecret] = useState("");
  const [apiVersion, setApiVersion] = useState("2025-01");
  const [token, setToken] = useState(""); // manual fallback only
  const [hasToken, setHasToken] = useState(false);
  const [connecting, setConnecting] = useState(false);
  const [testing, setTesting] = useState(false);
  const [saving, setSaving] = useState(false);
  const [result, setResult] = useState(null); // { ok, shopName } | { ok:false, error }
  const [showReconnect, setShowReconnect] = useState(false);
  const [showManual, setShowManual] = useState(false);
  const [disconnectOpen, setDisconnectOpen] = useState(false);
  const [defsBusy, setDefsBusy] = useState(false);
  const [catsBusy, setCatsBusy] = useState(false);
  const [stockBusy, setStockBusy] = useState(false);
  const [grantedScopes, setGrantedScopes] = useState(null);

  const reload = () => {
    if (!available) return;
    window.axlu.shopify.getConfig().then((c) => {
      if (!c) return;
      setShopUrl(c.shop || "");
      setApiVersion(c.apiVersion || "2025-01");
      setHasToken(!!c.hasToken);
      if (c.clientId) setClientId(c.clientId);
    }).catch(() => {});
  };
  React.useEffect(() => { reload(); }, []);

  // OAuth — the main path. Opens Shopify's approval page in the browser.
  const onConnect = async () => {
    if (!shopUrl.trim()) { toast("Renseignez l'URL de la boutique (xxx.myshopify.com)", { tone: "warning" }); return; }
    if (!clientId.trim()) { toast("Renseignez le Client ID", { tone: "warning" }); return; }
    if (!clientSecret.trim()) { toast("Renseignez le Client secret", { tone: "warning" }); return; }
    setConnecting(true); setResult(null);
    toast("Ouverture de la page d'autorisation Shopify dans votre navigateur…", { tone: "info" });
    try {
      const r = await window.axlu.shopify.connectOAuth({
        shop: shopUrl.trim(), clientId: clientId.trim(), clientSecret: clientSecret.trim(),
        apiVersion: apiVersion.trim() || "2025-01",
      });
      if (r && r.ok) {
        toast("Connecté à Shopify ✓", { tone: "success" });
        setClientSecret(""); // don't keep the secret around
        window.AxluStore.audit("shopify.oauth.connect", "shopify", r.shop || shopUrl.trim());
        setShowReconnect(false);
        reload();
        // Immediately verify with a real API call.
        onTest();
      } else {
        toast((r && r.error) || "Échec de la connexion", { tone: "danger" });
        setResult({ ok: false, error: (r && r.error) || "Échec de la connexion" });
      }
    } catch (e) {
      toast("Échec de la connexion", { tone: "danger" });
      setResult({ ok: false, error: String(e) });
    } finally { setConnecting(false); }
  };

  const onTest = async () => {
    setTesting(true);
    try {
      const creds = { shop: shopUrl.trim(), apiVersion: apiVersion.trim() || "2025-01" };
      if (token.trim()) creds.token = token.trim();
      const r = await window.axlu.shopify.testConnection(creds);
      setResult(r);
      toast(r.ok ? `Connexion OK — ${r.shopName}` : (r.error || "Échec de connexion"), { tone: r.ok ? "success" : "danger" });
    } catch (e) {
      setResult({ ok: false, error: String(e) });
    } finally { setTesting(false); }
  };

  // Manual fallback: paste a token directly (advanced).
  const onSaveManual = async () => {
    if (!shopUrl.trim()) { toast("Renseignez l'URL de la boutique", { tone: "warning" }); return; }
    if (!token.trim()) { toast("Collez un token", { tone: "warning" }); return; }
    setSaving(true);
    try {
      const r = await window.axlu.shopify.saveCredentials({ shop: shopUrl.trim(), token: token.trim(), apiVersion: apiVersion.trim() || "2025-01" });
      if (r && r.ok) {
        toast("Token enregistré", { tone: "success" });
        setToken("");
        window.AxluStore.audit("shopify.config.update", "shopify", shopUrl.trim());
        reload();
      } else {
        toast((r && r.error) || "Échec de l'enregistrement", { tone: "danger" });
      }
    } finally { setSaving(false); }
  };

  const onDisconnect = async () => {
    try { await window.axlu.shopify.clearCredentials(); } catch (e) { /* ignore */ }
    setToken(""); setClientSecret(""); setHasToken(false); setResult(null);
    setDisconnectOpen(false);
    toast("Boutique déconnectée", { tone: "warning" });
    reload();
  };

  // Create the AXLU metafield definitions in Shopify so the values are shown
  // (and typed) on the product page in the admin.
  const onCreateDefs = async () => {
    if (defsBusy) return;
    setDefsBusy(true);
    toast("Création des définitions de metafields…", { tone: "info" });
    try {
      const r = await window.AxluStore.shopifyEnsureDefinitions();
      if (r && r.ok) toast(`Définitions OK — ${r.created} créées, ${r.existed} déjà présentes`, { tone: "success" });
      else toast(`Définitions : ${(r && r.errors && r.errors[0]) || (r && r.error) || "échec"}`, { tone: "danger" });
    } catch (e) {
      toast("Échec de la création des définitions", { tone: "danger" });
    } finally { setDefsBusy(false); }
  };

  // Push the full AXLU category tree to Shopify as smart collections.
  const onSyncCats = async () => {
    if (catsBusy) return;
    setCatsBusy(true);
    toast("Synchronisation des catégories vers Shopify…", { tone: "info" });
    try {
      const r = await window.AxluStore.shopifySyncCategories();
      if (r && r.ok) toast(`Catégories OK — ${r.created} créées, ${r.existed} déjà présentes${r.parented ? `, ${r.parented} liens parent` : ""}`, { tone: "success" });
      else toast(`Catégories : ${(r && r.errors && r.errors[0]) || (r && r.error) || "échec"}`, { tone: "danger" });
    } catch (e) {
      toast("Échec de la synchronisation des catégories", { tone: "danger" });
    } finally { setCatsBusy(false); }
  };

  // Push stock of all published products to Shopify inventory now.
  const onPushStock = async () => {
    if (stockBusy) return;
    setStockBusy(true);
    toast("Envoi des stocks vers Shopify…", { tone: "info" });
    try {
      const r = await window.AxluStore.shopifyAutoPushStock();
      if (r && r.ok) toast(`Stocks envoyés — ${r.pushed} produit(s)`, { tone: r.pushed ? "success" : "info" });
      else if (r && r.skipped === "not-connected") toast("Boutique non connectée", { tone: "danger" });
      else toast(`Stocks : ${(r && r.error) || "échec"}`, { tone: "danger" });
    } catch (e) { toast("Échec de l'envoi des stocks", { tone: "danger" }); }
    finally { setStockBusy(false); }
  };

  const connected = (result && result.ok) || hasToken;
  const busy = connecting || testing || saving;

  // Fetch the app's granted scopes so we can warn when a required one is missing.
  React.useEffect(() => {
    if (!connected) { setGrantedScopes(null); return; }
    let alive = true;
    window.AxluStore.shopifyGrantedScopes().then((r) => { if (alive && r && r.ok) setGrantedScopes(r.scopes || []); }).catch(() => {});
    return () => { alive = false; };
  }, [connected]);

  const credentialFields = (
    <>
      <Field label="URL boutique (domaine technique)">
        <Input value={shopUrl} onChange={(e) => setShopUrl(e.target.value)} className="mono" placeholder="ma-boutique.myshopify.com"/>
        <div className="text-[11px] text-subtle mt-1">Le domaine <span className="mono">xxx.myshopify.com</span> (pas le domaine personnalisé).</div>
      </Field>
      <div className="grid grid-cols-2 gap-3">
        <Field label="Client ID">
          <Input value={clientId} onChange={(e) => setClientId(e.target.value)} className="mono" placeholder="ID client de l'app"/>
        </Field>
        <Field label="Client secret">
          <Input type="password" value={clientSecret} onChange={(e) => setClientSecret(e.target.value)} className="mono" placeholder="Clé secrète de l'app"/>
        </Field>
      </div>
      <Field label="Version d'API Shopify">
        <Input value={apiVersion} onChange={(e) => setApiVersion(e.target.value)} className="mono w-[140px]" placeholder="2025-01"/>
      </Field>
    </>
  );

  if (!available) {
    return (
      <div className="max-w-[560px]">
        <Card title="Connexion Shopify">
          <div className="text-[12.5px] text-muted">
            Module Shopify indisponible — relancez AXLU après la mise à jour pour activer la connexion.
          </div>
        </Card>
      </div>
    );
  }

  return (
    <div className="max-w-[600px] space-y-4">
      <Card title="Connexion Shopify">
        <div className="flex items-center gap-3 p-3 sunken rounded-md mb-4">
          <div className={`w-10 h-10 rounded-md surface border flex items-center justify-center ${connected ? "accent-fg" : "text-subtle"}`} style={{ borderColor: "var(--border)" }}>
            <Icon name="cart" size={20}/>
          </div>
          <div className="flex-1 min-w-0">
            <div className="text-[13px] font-semibold">
              {result && result.ok ? result.shopName : (connected ? "Boutique connectée" : "Aucune boutique connectée")}
            </div>
            <div className="text-[11px] text-subtle truncate">
              {connected ? (shopUrl || "Token chiffré enregistré") : "Connectez votre boutique pour activer les publications."}
            </div>
          </div>
          <Badge tone={connected ? "success" : "muted"} dot={connected}>{connected ? "Connecté" : "Non connecté"}</Badge>
        </div>

        {connected ? (
          <>
            <div className="text-[12px] text-muted rounded-md px-3 py-2 sunken">
              ✓ Le token est <strong>chiffré et enregistré</strong> — la connexion est <strong>permanente</strong> et survit aux redémarrages. Inutile de ressaisir les identifiants.
            </div>
            {result && (
              <div className={`mt-3 text-[12px] rounded-md px-3 py-2 sunken ${result.ok ? "text-emerald-700 dark:text-emerald-400" : "text-red-600 dark:text-red-400"}`}>
                {result.ok ? `✓ Connecté à « ${result.shopName} »${result.url ? " — " + result.url : ""}` : `✗ ${result.error}`}
              </div>
            )}
            <div className="mt-3 flex gap-2 items-center">
              <Button variant="secondary" size="sm" leftIcon="refresh" onClick={onTest} disabled={busy}>
                {testing ? "Test…" : "Tester la connexion"}
              </Button>
              <Button variant="ghost" size="sm" onClick={() => setDisconnectOpen(true)} disabled={busy}>Déconnecter</Button>
            </div>
            <div className="mt-3 border-t pt-3" style={{ borderColor: "var(--border)" }}>
              <button type="button" onClick={() => setShowReconnect((s) => !s)}
                      className="text-[12px] accent-fg hover:underline flex items-center gap-1">
                <Icon name={showReconnect ? "chevronDown" : "chevronRight"} size={12}/>
                Reconnecter / changer d'application
              </button>
              {showReconnect && (
                <div className="mt-3 space-y-3">
                  <div className="text-[11.5px] text-muted">Utile seulement si tu changes d'app Shopify ou si le token a été révoqué. Le Client secret n'est pas conservé — ressaisis-le pour reconnecter.</div>
                  {credentialFields}
                  {connecting && (
                    <div className="text-[12px] rounded-md px-3 py-2 sunken accent-fg">En attente de votre approbation dans le navigateur…</div>
                  )}
                  <div className="flex justify-end">
                    <Button variant="primary" size="sm" leftIcon="plug" onClick={onConnect} disabled={busy}>
                      {connecting ? "Connexion en cours…" : "Reconnecter"}
                    </Button>
                  </div>
                </div>
              )}
            </div>
          </>
        ) : (
          <>
            {credentialFields}
            {connecting && (
              <div className="mt-3 text-[12px] rounded-md px-3 py-2 sunken accent-fg">
                En attente de votre approbation dans le navigateur… approuvez l'installation de « Middleware AXLU », puis revenez ici.
              </div>
            )}
            {result && (
              <div className={`mt-3 text-[12px] rounded-md px-3 py-2 sunken ${result.ok ? "text-emerald-700 dark:text-emerald-400" : "text-red-600 dark:text-red-400"}`}>
                {result.ok ? `✓ Connecté à « ${result.shopName} »` : `✗ ${result.error}`}
              </div>
            )}
            <div className="mt-3">
              <Button variant="primary" size="sm" leftIcon="plug" onClick={onConnect} disabled={busy}>
                {connecting ? "Connexion en cours…" : "Connecter à Shopify"}
              </Button>
            </div>
          </>
        )}
      </Card>

      {connected && (
        <Card title="Données Shopify (metafields)" padding="md">
          <div className="text-[12px] text-muted mb-3">
            Crée dans Shopify les <strong>définitions</strong> des metafields <span className="mono text-[11px]">axlu.*</span> (catégorie, caractéristiques, couleurs, marquages, prix dégressifs) pour qu'ils s'affichent — typés — sur la fiche produit de l'admin. Idempotent : à relancer sans risque.
          </div>
          <Button variant="secondary" size="sm" leftIcon="settings" onClick={onCreateDefs} disabled={defsBusy}>
            {defsBusy ? "Création…" : "Créer les définitions de metafields"}
          </Button>
        </Card>
      )}

      {connected && (
        <Card title="Catégories (collections)" padding="md">
          <div className="text-[12px] text-muted mb-3">
            Crée dans Shopify une <strong>collection intelligente</strong> pour chaque catégorie AXLU. Les produits s'y rangent — et se re-rangent — <strong>automatiquement</strong> selon leur Type (sous-catégorie) et leurs tags (catégorie principale). Chaque sous-catégorie reçoit aussi un lien <span className="mono text-[11px]">axlu.parent</span> vers sa catégorie principale (pour la navigation imbriquée du site headless). <strong>Cette synchro se lance toute seule après chaque import</strong> (si l'arbre a changé) — le bouton ci-dessous ne sert que pour forcer.
          </div>
          <Button variant="secondary" size="sm" leftIcon="folder" onClick={onSyncCats} disabled={catsBusy}>
            {catsBusy ? "Synchronisation…" : "Synchroniser les catégories vers Shopify"}
          </Button>
        </Card>
      )}

      {connected && <ShopifyChannelsCard />}

      {connected && (
        <Card title="Stocks (inventaire)" padding="md">
          <div className="text-[12px] text-muted mb-3">
            AXLU pousse les quantités sur l'inventaire Shopify (emplacement principal), <strong>par variante (SKU)</strong> : à chaque <strong>publication</strong> et <strong>automatiquement après chaque import de stock</strong> (donc en phase avec l'ERP). Les variantes « illimitées » (stock non compté PF) sont poussées à <strong>9999</strong>.
          </div>
          {grantedScopes && ["write_inventory", "read_locations"].some((s) => grantedScopes.indexOf(s) === -1) && (
            <div className="text-[12px] mb-3 p-2 rounded-md" style={{ color: "var(--danger)", background: "color-mix(in srgb, var(--danger) 8%, transparent)" }}>
              ⚠️ Droits manquants pour les stocks : <span className="mono text-[11px]">{["write_inventory", "read_locations"].filter((s) => grantedScopes.indexOf(s) === -1).join(", ")}</span>. C'est pour ça que les quantités restent à 0. Ajoute-les dans le Dev Dashboard, puis <strong>reconnecte-toi</strong> (Connexion Shopify).
            </div>
          )}
          <Button variant="secondary" size="sm" leftIcon="sync" onClick={onPushStock} disabled={stockBusy}>
            {stockBusy ? "Envoi…" : "Pousser les stocks maintenant"}
          </Button>
        </Card>
      )}

      <Card title="Méthode manuelle (avancé)" padding="md">
        <button type="button" onClick={() => setShowManual((s) => !s)}
                className="text-[12px] accent-fg hover:underline flex items-center gap-1">
          <Icon name={showManual ? "chevronDown" : "chevronRight"} size={12}/>
          Coller un token directement
        </button>
        {showManual && (
          <div className="mt-3 space-y-2">
            <div className="text-[11.5px] text-muted">
              À n'utiliser que si tu disposes déjà d'un token d'accès Admin valide. Sinon, utilise « Connecter à Shopify » ci-dessus.
            </div>
            <Field label="Token API Admin">
              <Input type="password" value={token} onChange={(e) => setToken(e.target.value)} className="mono" placeholder="shpat_… / shpua_…"/>
            </Field>
            <div className="flex justify-end">
              <Button variant="secondary" size="sm" leftIcon="check" onClick={onSaveManual} disabled={busy}>
                {saving ? "Enregistrement…" : "Enregistrer le token"}
              </Button>
            </div>
          </div>
        )}
      </Card>

      <Modal open={disconnectOpen} onClose={() => setDisconnectOpen(false)}
             title="Déconnecter la boutique ?"
             footer={<>
               <Button variant="ghost" onClick={() => setDisconnectOpen(false)}>Annuler</Button>
               <Button variant="danger" onClick={onDisconnect}>Déconnecter</Button>
             </>}>
        <div className="text-[13px]">Le token enregistré sera supprimé. Aucune publication ne pourra être effectuée tant que la connexion ne sera pas rétablie.</div>
      </Modal>
    </div>
  );
}

// ─── Logs & audit ──────────────────────────────────────────────────────
function LogsScreen() {
  const v = useAxluStore();
  const { AUDIT_LOG } = window.AxluData;
  const [search, setSearch] = useState("");
  const [userF, setUserF] = useState("all");
  const [actionF, setActionF] = useState("all");
  const [entityF, setEntityF] = useState("all");
  const [from, setFrom] = useState("");
  const [to, setTo] = useState("");
  const [detail, setDetail] = useState(null);
  const toast = useToast();

  const users = useMemo(() => [...new Set(AUDIT_LOG.map((r) => r.user))], [v, AUDIT_LOG]);
  const actions = useMemo(() => [...new Set(AUDIT_LOG.map((r) => r.action))], [v, AUDIT_LOG]);
  const entities = useMemo(() => [...new Set(AUDIT_LOG.map((r) => r.entity))], [v, AUDIT_LOG]);

  const filtered = useMemo(() => AUDIT_LOG.filter((r) => {
    if (userF !== "all" && r.user !== userF) return false;
    if (actionF !== "all" && r.action !== actionF) return false;
    if (entityF !== "all" && r.entity !== entityF) return false;
    if (search && !r.target.toLowerCase().includes(search.toLowerCase()) && !r.user.toLowerCase().includes(search.toLowerCase())) return false;
    if (from) {
      const fromTs = new Date(from).getTime();
      if (new Date(r.ts).getTime() < fromTs) return false;
    }
    if (to) {
      const toTs = new Date(to).getTime() + 24 * 3600 * 1000 - 1;
      if (new Date(r.ts).getTime() > toTs) return false;
    }
    return true;
  }), [v, AUDIT_LOG, userF, actionF, entityF, search, from, to]);

  const exportCsv = () => {
    const csv = window.AxluStore.toCSV(filtered, [
      { key: "ts", label: "Date" },
      { key: "user", label: "Utilisateur" },
      { key: "action", label: "Action" },
      { key: "entity", label: "Entité" },
      { key: "target", label: "Cible" },
    ]);
    window.AxluStore.download(csv, "audit-log.csv", "text/csv");
    toast("Logs exportés", { tone: "success" });
  };

  return (
    <div>
      <PageHeader
        breadcrumb={[{ label: "Logs & audit" }]}
        title="Journal d'activité"
        subtitle={`${AUDIT_LOG.length} événements · 30 derniers jours`}
        actions={
          <>
            <Button variant="secondary" leftIcon="download" onClick={exportCsv}>Exporter</Button>
          </>
        }
      />

      <div className="p-6">
        <div className="flex items-center gap-2 mb-4 flex-wrap">
          <Input leftIcon="search" placeholder="Rechercher dans les logs…" value={search} onChange={(e) => setSearch(e.target.value)} className="w-[280px]"/>
          <Select value={userF} onChange={setUserF} options={[
            { value: "all", label: "Tous utilisateurs" },
            ...users.map((u) => ({ value: u, label: u })),
          ]}/>
          <Select value={actionF} onChange={setActionF} options={[
            { value: "all", label: "Toutes actions" },
            ...actions.map((a) => ({ value: a, label: a })),
          ]}/>
          <Select value={entityF} onChange={setEntityF} options={[
            { value: "all", label: "Toutes entités" },
            ...entities.map((e) => ({ value: e, label: e })),
          ]}/>
          <Input type="date" value={from} onChange={(e) => setFrom(e.target.value)} className="w-[160px]"/>
          <span className="text-[12px] text-muted">→</span>
          <Input type="date" value={to} onChange={(e) => setTo(e.target.value)} className="w-[160px]"/>
          <div className="flex-1"/>
          <span className="text-[12px] text-muted">{filtered.length} sur {AUDIT_LOG.length}</span>
        </div>

        <Card padding="none">
          <table className="axlu-table">
            <thead>
              <tr>
                <th>Date</th><th>Utilisateur</th><th>Action</th><th>Entité</th><th>Cible</th><th></th>
              </tr>
            </thead>
            <tbody>
              {filtered.map((r, i) => (
                <tr key={i} className="hover-row">
                  <td className="mono text-[12px] text-muted">{fmt.date(r.ts)}</td>
                  <td>
                    <div className="flex items-center gap-2">
                      <Avatar name={r.user} size={22}/>
                      <span className="text-[12.5px]">{r.user}</span>
                    </div>
                  </td>
                  <td><span className="mono text-[12px]">{r.action}</span></td>
                  <td><Badge tone="muted">{r.entity}</Badge></td>
                  <td className="text-[12.5px] truncate" style={{ maxWidth: 380 }}>{r.target}</td>
                  <td className="text-right"><IconButton icon="eye" size="sm" onClick={() => setDetail(r)}/></td>
                </tr>
              ))}
            </tbody>
          </table>
        </Card>
      </div>

      <Modal open={!!detail} onClose={() => setDetail(null)}
             title="Détail de l'événement"
             footer={<Button variant="primary" onClick={() => setDetail(null)}>Fermer</Button>}>
        <pre className="sunken rounded-md p-3 text-[11.5px] mono overflow-auto" style={{ maxHeight: 400 }}>
{detail ? JSON.stringify(detail, null, 2) : ""}
        </pre>
      </Modal>
    </div>
  );
}

// ─── Admin / Users ─────────────────────────────────────────────────────
function AdminUsersScreen() {
  const v = useAxluStore();
  const { USERS } = window.AxluData;
  const [open, setOpen] = useState(false);
  const [editingUser, setEditingUser] = useState(null);
  const [form, setForm] = useState({ name: "", email: "", role: "operator" });
  const [deleteUser, setDeleteUser] = useState(null);
  const [keyResult, setKeyResult] = useState(null); // { user, key, expiresAt }
  const toast = useToast();
  const me = window.AxluStore.currentUser;

  const users = useMemo(() => USERS, [v, USERS]);

  const roles = {
    admin:    { label: "Administrateur", tone: "danger" },
    operator: { label: "Opérateur",      tone: "info" },
    viewer:   { label: "Lecture seule",  tone: "muted" },
  };

  const openNew = () => {
    setEditingUser(null);
    setForm({ name: "", email: "", role: "operator" });
    setOpen(true);
  };

  const openEdit = (u) => {
    setEditingUser(u);
    setForm({ name: u.name, email: u.email, role: u.role });
    setOpen(true);
  };

  const onSubmit = () => {
    const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!form.name.trim()) { toast("Nom requis", { tone: "danger" }); return; }
    if (!emailRe.test(form.email)) { toast("Email invalide", { tone: "danger" }); return; }
    if (editingUser) {
      window.AxluStore.dispatch("user.update", { id: editingUser.id, patch: { name: form.name.trim(), email: form.email.trim(), role: form.role }, _web: true });
      toast("Utilisateur modifié", { tone: "success" });
    } else {
      window.AxluStore.dispatch("user.create", { data: { name: form.name.trim(), email: form.email.trim(), role: form.role } });
      toast("Utilisateur créé. Générez une clé de connexion pour qu'il puisse se connecter.", { tone: "info" });
    }
    setOpen(false);
  };

  const onGenerateKey = async (u) => {
    const res = (await window.AxluStore.dispatch("auth.generateKey", { targetUserId: u.id })) || {};
    if (!res.ok) { toast(res.error || "Échec", { tone: "danger" }); return; }
    setKeyResult({ user: u, key: res.key, expiresAt: res.expiresAt });
  };

  const onCopyKey = () => {
    if (!keyResult) return;
    navigator.clipboard.writeText(keyResult.key).then(
      () => toast("Clé copiée", { tone: "success" }),
      () => toast("Échec de la copie", { tone: "danger" }),
    );
  };

  const onToggleActive = (u) => {
    if (u.id === me?.id) { toast("Vous ne pouvez pas désactiver votre propre compte", { tone: "warning" }); return; }
    window.AxluStore.dispatch("user.toggleActive", { id: u.id });
    toast(u.active ? "Utilisateur désactivé" : "Utilisateur réactivé");
  };

  const onDelete = () => {
    if (deleteUser.id === me?.id) { toast("Vous ne pouvez pas supprimer votre propre compte", { tone: "warning" }); setDeleteUser(null); return; }
    window.AxluStore.dispatch("user.delete", { id: deleteUser.id });
    toast("Utilisateur supprimé", { tone: "warning" });
    setDeleteUser(null);
  };

  React.useEffect(() => { window.AxluStore.acquireLock && window.AxluStore.acquireLock('page:users'); return () => window.AxluStore.releaseLock && window.AxluStore.releaseLock('page:users'); }, []);

  return (
    <div>
      <LockBanner resource="page:users"/>
      <PageHeader
        breadcrumb={[{ label: "Administration" }, { label: "Utilisateurs" }]}
        title="Utilisateurs"
        subtitle={`${users.filter((u) => u.active).length} actifs · ${users.length} total`}
        actions={
          <Button variant="primary" leftIcon="plus" onClick={openNew}>
            Nouvel utilisateur
          </Button>
        }
      />

      <div className="p-6">
        <Card padding="none">
          <table className="axlu-table">
            <thead>
              <tr>
                <th>Utilisateur</th>
                <th>Email</th>
                <th>Rôle</th>
                <th>Dernière connexion</th>
                <th>État</th>
                <th></th>
              </tr>
            </thead>
            <tbody>
              {users.map((u) => (
                <tr key={u.id} className="hover-row">
                  <td>
                    <div className="flex items-center gap-2.5">
                      <Avatar name={u.name} size={28}/>
                      <span className="font-medium">{u.name}</span>
                    </div>
                  </td>
                  <td className="mono text-[12px] text-muted">{u.email}</td>
                  <td><Badge tone={roles[u.role].tone}>{roles[u.role].label}</Badge></td>
                  <td className="mono text-[12px] text-muted">{fmt.rel(u.lastLogin)}</td>
                  <td>{u.active ? <Badge tone="success" dot>Actif</Badge> : <Badge tone="muted" dot>Désactivé</Badge>}</td>
                  <td>
                    <Dropdown
                      trigger={<IconButton icon="more" size="sm"/>}
                      align="right" width={220}
                      items={[
                        { label: "Modifier", icon: "edit", onClick: () => openEdit(u) },
                        { label: "Générer une clé de connexion", icon: "key", onClick: () => onGenerateKey(u) },
                        "sep",
                        { label: u.active ? "Désactiver" : "Réactiver", icon: u.active ? "eyeOff" : "check",
                          onClick: () => onToggleActive(u), disabled: u.id === me?.id },
                        { label: "Supprimer", icon: "trash", danger: true, onClick: () => setDeleteUser(u), disabled: u.id === me?.id },
                      ]}/>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </Card>

        <div className="mt-6 grid grid-cols-3 gap-4">
          {Object.entries(roles).map(([k, def]) => (
            <Card key={k} title={def.label} padding="md">
              <div className="text-[12px] text-muted mb-2">
                {k === "admin" && "Accès complet incluant gestion des utilisateurs, règles et configurations."}
                {k === "operator" && "Tri, validation, enrichissement et publication. Pas d'accès admin."}
                {k === "viewer" && "Lecture seule sur tous les écrans. Aucune modification possible."}
              </div>
              <div className="text-[11px] text-subtle">
                {users.filter((u) => u.role === k && u.active).length} utilisateur{users.filter((u) => u.role === k && u.active).length > 1 ? "s" : ""} actif{users.filter((u) => u.role === k && u.active).length > 1 ? "s" : ""}
              </div>
            </Card>
          ))}
        </div>
      </div>

      <Modal open={open} onClose={() => setOpen(false)}
             title={editingUser ? "Modifier l'utilisateur" : "Nouvel utilisateur"}
             footer={<>
               <Button variant="ghost" onClick={() => setOpen(false)}>Annuler</Button>
               <Button variant="primary" onClick={onSubmit}>
                 {editingUser ? "Enregistrer" : "Envoyer l'invitation"}
               </Button>
             </>}>
        <div className="space-y-3">
          <Field label="Nom complet">
            <Input value={form.name} onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))} placeholder="Jean Dupont" size="lg"/>
          </Field>
          <Field label="Email">
            <Input value={form.email} onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))} placeholder="jean@axlu.lu" size="lg"/>
          </Field>
          <Field label="Rôle">
            <div className="grid grid-cols-3 gap-2">
              {Object.entries(roles).map(([k, def]) => (
                <button key={k}
                  onClick={() => setForm((f) => ({ ...f, role: k }))}
                  className={`surface border rounded-md p-2.5 text-left ${form.role === k ? "ring-2" : ""}`}
                  style={{ borderColor: form.role === k ? "var(--accent)" : "var(--border)", boxShadow: form.role === k ? "0 0 0 2px var(--accent)" : "none" }}>
                  <div className="text-[12.5px] font-medium">{def.label}</div>
                  <div className="text-[11px] text-subtle mt-0.5">
                    {k === "admin" ? "Accès total" : k === "operator" ? "Tri & publication" : "Lecture seule"}
                  </div>
                </button>
              ))}
            </div>
          </Field>
        </div>
      </Modal>

      <Modal open={!!deleteUser} onClose={() => setDeleteUser(null)}
             title="Supprimer l'utilisateur ?"
             footer={<>
               <Button variant="ghost" onClick={() => setDeleteUser(null)}>Annuler</Button>
               <Button variant="danger" onClick={onDelete}>Supprimer</Button>
             </>}>
        <div className="text-[13px]">L'utilisateur <strong>{deleteUser?.name}</strong> sera supprimé définitivement.</div>
      </Modal>

      <Modal open={!!keyResult} onClose={() => setKeyResult(null)}
             title="Clé de connexion générée" width={520}
             footer={<>
               <Button variant="ghost" onClick={onCopyKey} leftIcon="copy">Copier</Button>
               <Button variant="primary" onClick={() => setKeyResult(null)}>Fermer</Button>
             </>}>
        <div className="space-y-4">
          <div className="text-[13px] text-muted leading-relaxed">
            Communiquez cette clé à <strong>{keyResult?.user.name}</strong> ({keyResult?.user.email}). Elle est <strong>à usage unique</strong> et expire dans 24 heures. À la première connexion par clé, l'utilisateur sera invité à définir son mot de passe.
          </div>
          <div className="rounded-lg border-2 p-5 text-center" style={{ borderColor: "var(--accent)", background: "var(--accent-bg)" }}>
            <div className="text-[10px] uppercase tracking-wider text-muted mb-2">Clé de connexion</div>
            <div className="mono text-[22px] font-semibold tracking-wider select-all" style={{ color: "var(--accent)" }}>
              {keyResult?.key}
            </div>
          </div>
          <div className="text-[11.5px] text-subtle">
            Expiration : {keyResult ? new Date(keyResult.expiresAt).toLocaleString("fr-FR") : "—"}
          </div>
        </div>
      </Modal>
    </div>
  );
}

Object.assign(window, {
  CategoriesScreen, PricingScreen, ShopifyScreen, LogsScreen, AdminUsersScreen,
});
