// AXLU — primitive UI components. All exposed on window at the bottom.
const { useState, useEffect, useRef, useMemo, useCallback, createContext, useContext, Fragment } = React;

// ─── Icons ────────────────────────────────────────────────────────────
function Icon({ name, size = 16, className = "", strokeWidth = 1.75 }) {
  const paths = {
    dashboard: <><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></>,
    plug: <><path d="M9 2v4M15 2v4M7 8h10v3a5 5 0 01-10 0V8zM12 16v6"/></>,
    sync: <><path d="M3 12a9 9 0 0114.8-7M21 12a9 9 0 01-14.8 7M17 4v5h-5M7 20v-5h5"/></>,
    catalog: <><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></>,
    tag: <><path d="M3 12V3h9l9 9-9 9-9-9z"/><circle cx="7.5" cy="7.5" r="1.5"/></>,
    coin: <><circle cx="12" cy="12" r="9"/><path d="M12 7v10M9 9.5c0-1.4 1.3-2.5 3-2.5s3 1.1 3 2.5-1.3 2.5-3 2.5-3 1.1-3 2.5 1.3 2.5 3 2.5 3-1.1 3-2.5"/></>,
    cart: <><circle cx="9" cy="20" r="1.5"/><circle cx="18" cy="20" r="1.5"/><path d="M3 3h3l3 12h11l2-8H7"/></>,
    log: <><path d="M14 3v4h4M14 3H6a2 2 0 00-2 2v14a2 2 0 002 2h12a2 2 0 002-2V7l-6-4z"/><path d="M8 13h8M8 17h5M8 9h2"/></>,
    users: <><circle cx="9" cy="8" r="3.5"/><path d="M2 21c0-3.5 3.1-6 7-6s7 2.5 7 6"/><circle cx="17" cy="9" r="2.5"/><path d="M22 19c0-2.5-2-4.5-5-4.5"/></>,
    chevronDown: <path d="M6 9l6 6 6-6"/>,
    chevronRight: <path d="M9 6l6 6-6 6"/>,
    chevronLeft: <path d="M15 6l-6 6 6 6"/>,
    chevronUpDown: <><path d="M8 9l4-4 4 4"/><path d="M16 15l-4 4-4-4"/></>,
    chevronsLeft: <><path d="M12 6l-6 6 6 6"/><path d="M18 6l-6 6 6 6"/></>,
    chevronsRight: <><path d="M6 6l6 6-6 6"/><path d="M12 6l6 6-6 6"/></>,
    search: <><circle cx="10.5" cy="10.5" r="6.5"/><path d="M20 20l-4.5-4.5"/></>,
    plus: <><path d="M12 5v14M5 12h14"/></>,
    minus: <path d="M5 12h14"/>,
    x: <><path d="M6 6l12 12M18 6L6 18"/></>,
    check: <path d="M5 12.5l5 5L20 7"/>,
    filter: <path d="M3 5h18l-7 9v6l-4-2v-4L3 5z"/>,
    sort: <><path d="M7 4v16M3 8l4-4 4 4"/><path d="M17 20V4M21 16l-4 4-4-4"/></>,
    more: <><circle cx="5" cy="12" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="19" cy="12" r="1.5"/></>,
    settings: <><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 00.3 1.8l.1.1a2 2 0 01-2.8 2.8l-.1-.1a1.7 1.7 0 00-1.8-.3 1.7 1.7 0 00-1 1.5V21a2 2 0 01-4 0v-.1a1.7 1.7 0 00-1-1.5 1.7 1.7 0 00-1.8.3l-.1.1a2 2 0 01-2.8-2.8l.1-.1a1.7 1.7 0 00.3-1.8 1.7 1.7 0 00-1.5-1H3a2 2 0 010-4h.1a1.7 1.7 0 001.5-1 1.7 1.7 0 00-.3-1.8l-.1-.1a2 2 0 012.8-2.8l.1.1a1.7 1.7 0 001.8.3h0a1.7 1.7 0 001-1.5V3a2 2 0 014 0v.1a1.7 1.7 0 001 1.5 1.7 1.7 0 001.8-.3l.1-.1a2 2 0 012.8 2.8l-.1.1a1.7 1.7 0 00-.3 1.8v0a1.7 1.7 0 001.5 1H21a2 2 0 010 4h-.1a1.7 1.7 0 00-1.5 1z"/></>,
    bell: <><path d="M6 8a6 6 0 0112 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 003.4 0"/></>,
    upload: <><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12"/></>,
    download: <><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></>,
    trash: <><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2M10 11v6M14 11v6"/></>,
    edit: <><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.12 2.12 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></>,
    play: <path d="M8 5l12 7-12 7V5z"/>,
    refresh: <><path d="M3 12a9 9 0 0115-6.7L21 8M21 3v5h-5M21 12a9 9 0 01-15 6.7L3 16M3 21v-5h5"/></>,
    eye: <><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></>,
    eyeOff: <><path d="M9.9 4.2A10 10 0 0112 4c6.5 0 10 7 10 7a16.3 16.3 0 01-2.4 3.3M6.6 6.6A16.3 16.3 0 002 11s3.5 7 10 7c2 0 3.7-.7 5.2-1.6"/><path d="M9.9 9.9a3 3 0 004.2 4.2M2 2l20 20"/></>,
    link: <><path d="M10 13a5 5 0 007 0l3-3a5 5 0 00-7-7l-1 1"/><path d="M14 11a5 5 0 00-7 0l-3 3a5 5 0 007 7l1-1"/></>,
    grid: <><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/></>,
    list: <><path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/></>,
    kanban: <><rect x="3" y="3" width="5" height="14" rx="1"/><rect x="10" y="3" width="5" height="9" rx="1"/><rect x="17" y="3" width="4" height="18" rx="1"/></>,
    arrowUp: <><path d="M12 19V5M5 12l7-7 7 7"/></>,
    arrowDown: <><path d="M12 5v14M19 12l-7 7-7-7"/></>,
    arrowRight: <><path d="M5 12h14M12 5l7 7-7 7"/></>,
    warning: <><path d="M12 9v4M12 17h0"/><path d="M10.3 3.9L1.8 18a2 2 0 001.7 3h17a2 2 0 001.7-3L13.7 3.9a2 2 0 00-3.4 0z"/></>,
    info: <><circle cx="12" cy="12" r="9"/><path d="M12 8h.01M11 12h1v4h1"/></>,
    check2: <><circle cx="12" cy="12" r="9"/><path d="M8 12.5l3 3 5-6"/></>,
    cross: <><circle cx="12" cy="12" r="9"/><path d="M9 9l6 6M15 9l-6 6"/></>,
    clock: <><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></>,
    flag: <><path d="M4 21V4M4 4h13l-2 4 2 4H4"/></>,
    image: <><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></>,
    cube: <><path d="M21 16V8l-9-5-9 5v8l9 5 9-5z"/><path d="M3.3 7L12 12l8.7-5M12 22V12"/></>,
    folder: <><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></>,
    drag: <><circle cx="9" cy="6" r="1.2"/><circle cx="9" cy="12" r="1.2"/><circle cx="9" cy="18" r="1.2"/><circle cx="15" cy="6" r="1.2"/><circle cx="15" cy="12" r="1.2"/><circle cx="15" cy="18" r="1.2"/></>,
    palette: <><path d="M12 2a10 10 0 100 20c1.7 0 3-1.3 3-3 0-.8-.3-1.5-.8-2-.5-.5-.8-1.2-.8-2 0-1.7 1.3-3 3-3h1.6a3 3 0 003-3 9 9 0 00-9-7z"/><circle cx="6.5" cy="11.5" r="1"/><circle cx="9.5" cy="6.5" r="1"/><circle cx="14.5" cy="6.5" r="1"/><circle cx="17.5" cy="11.5" r="1"/></>,
    command: <><path d="M18 6a3 3 0 110 6h-3V9a3 3 0 013-3zM6 6a3 3 0 100 6h3V9a3 3 0 00-3-3zM6 18a3 3 0 100-6h3v3a3 3 0 01-3 3zM18 18a3 3 0 110-6h-3v3a3 3 0 003 3z"/></>,
    logout: <><path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4M16 17l5-5-5-5M21 12H9"/></>,
    book: <><path d="M4 19.5A2.5 2.5 0 016.5 17H20V3H6.5A2.5 2.5 0 004 5.5z"/><path d="M4 19.5A2.5 2.5 0 006.5 22H20v-5"/></>,
    sun: <><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></>,
    moon: <path d="M21 12.8A9 9 0 1111.2 3a7 7 0 009.8 9.8z"/>,
    key: <><circle cx="7" cy="14" r="3"/><path d="M9.5 11.5l9.5-9.5M14 7l3 3M11 9l3 3"/></>,
    copy: <><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></>,
    spinner: <><circle cx="12" cy="12" r="9" opacity=".25"/><path d="M21 12a9 9 0 00-9-9"/></>,
    loader: <><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/></>,
  };
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
         strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round" className={className}>
      {paths[name] || null}
    </svg>
  );
}

// ─── Button ───────────────────────────────────────────────────────────
function Button({ variant = "secondary", size = "md", children, leftIcon, rightIcon, className = "", ...rest }) {
  const sz = size === "sm"
    ? "h-7 text-[12px] px-2.5 gap-1.5"
    : size === "lg"
    ? "h-10 text-[14px] px-4 gap-2"
    : "h-8 text-[13px] px-3 gap-1.5";
  const styles = {
    primary:  "accent-solid border border-transparent",
    secondary:"surface border hover:bg-[var(--hover)] text-app",
    ghost:    "border border-transparent text-app hover:bg-[var(--hover)]",
    danger:   "bg-red-600 hover:bg-red-700 text-white border border-transparent",
    subtle:   "accent-bg accent-fg border border-transparent hover:bg-[var(--accent-bg-hover)]",
  };
  return (
    <button {...rest}
      className={`inline-flex items-center justify-center rounded-md font-medium transition-colors ring-accent ${sz} ${styles[variant]} ${className}`}
      style={{ borderColor: variant === "secondary" ? "var(--border)" : undefined }}>
      {leftIcon && <Icon name={leftIcon} size={size === "sm" ? 13 : 14} />}
      {children}
      {rightIcon && <Icon name={rightIcon} size={size === "sm" ? 13 : 14} />}
    </button>
  );
}

function IconButton({ icon, size = "md", className = "", title, ...rest }) {
  const sz = size === "sm" ? "h-7 w-7" : size === "lg" ? "h-10 w-10" : "h-8 w-8";
  return (
    <button {...rest} title={title}
      className={`inline-flex items-center justify-center rounded-md text-muted hover:text-app hover:bg-[var(--hover)] transition-colors ring-accent ${sz} ${className}`}>
      <Icon name={icon} size={size === "sm" ? 14 : 16} />
    </button>
  );
}

// ─── Badge ────────────────────────────────────────────────────────────
const TONE_LIGHT = {
  neutral: "bg-stone-100 text-stone-700 border-stone-200",
  muted:   "bg-stone-50  text-stone-500 border-stone-200",
  info:    "bg-indigo-50 text-indigo-700 border-indigo-100",
  success: "bg-emerald-50 text-emerald-700 border-emerald-100",
  warning: "bg-amber-50 text-amber-700 border-amber-100",
  danger:  "bg-red-50 text-red-700 border-red-100",
};
const TONE_DARK = {
  neutral: "bg-stone-800 text-stone-300 border-stone-700",
  muted:   "bg-stone-900 text-stone-500 border-stone-800",
  info:    "bg-indigo-950 text-indigo-300 border-indigo-900",
  success: "bg-emerald-950 text-emerald-300 border-emerald-900",
  warning: "bg-amber-950 text-amber-300 border-amber-900",
  danger:  "bg-red-950 text-red-300 border-red-900",
};
function Badge({ tone = "neutral", children, dot = false, className = "" }) {
  return (
    <span className={`inline-flex items-center gap-1.5 px-1.5 py-0.5 text-[11px] font-medium rounded border whitespace-nowrap ${TONE_LIGHT[tone]} dark:${TONE_DARK[tone].split(" ").map(c=>c).join(" dark:")} ${className}`}
          style={{ lineHeight: 1.4 }}>
      {dot && <span className={`w-1.5 h-1.5 rounded-full ${{
        neutral: "bg-stone-500", muted: "bg-stone-400", info: "bg-indigo-500",
        success: "bg-emerald-500", warning: "bg-amber-500", danger: "bg-red-500",
      }[tone]}`}/>}
      {children}
    </span>
  );
}

// ─── Status badge (product / sync) ────────────────────────────────────
function StatusBadge({ status, map, withEmoji = true }) {
  const def = map[status] || { label: status, tone: "neutral" };
  return (
    <Badge tone={def.tone} dot>
      {withEmoji && def.emoji && (
        <span className={status === "running" ? "axlu-jump" : ""} aria-hidden>{def.emoji}</span>
      )}
      {def.label}
    </Badge>
  );
}

// ─── Input ────────────────────────────────────────────────────────────
function Input({ leftIcon, rightSlot, className = "", size = "md", ...rest }) {
  const h = size === "sm" ? "h-7 text-[12px]" : size === "lg" ? "h-10 text-[14px]" : "h-8 text-[13px]";
  return (
    <div className={`relative inline-flex items-center w-full ${className}`}>
      {leftIcon && (
        <span className="absolute left-2.5 text-subtle pointer-events-none">
          <Icon name={leftIcon} size={14}/>
        </span>
      )}
      <input {...rest}
        className={`${h} w-full surface border rounded-md pr-2 ${leftIcon ? "pl-8" : "pl-2.5"} text-app placeholder:text-[var(--fg-subtle)] ring-accent transition-colors`}
        style={{ borderColor: "var(--border)" }} />
      {rightSlot && <span className="absolute right-2 text-subtle">{rightSlot}</span>}
    </div>
  );
}

function Select({ value, onChange, options, className = "", size = "md" }) {
  const h = size === "sm" ? "h-7 text-[12px]" : "h-8 text-[13px]";
  return (
    <div className={`relative ${className}`}>
      <select value={value} onChange={(e) => onChange(e.target.value)}
        className={`${h} w-full appearance-none surface border rounded-md pl-2.5 pr-7 text-app ring-accent`}
        style={{ borderColor: "var(--border)" }}>
        {options.map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}
      </select>
      <span className="absolute right-2 top-1/2 -translate-y-1/2 text-subtle pointer-events-none">
        <Icon name="chevronDown" size={14}/>
      </span>
    </div>
  );
}

function Checkbox({ checked, indeterminate = false, onChange, className = "" }) {
  const ref = useRef(null);
  useEffect(() => { if (ref.current) ref.current.indeterminate = !!indeterminate; }, [indeterminate]);
  return (
    <input ref={ref} type="checkbox" checked={!!checked} onChange={(e) => onChange && onChange(e.target.checked)}
      className={`w-3.5 h-3.5 rounded border cursor-pointer ring-accent ${className}`}
      style={{ borderColor: "var(--border-strong)", accentColor: "var(--accent)" }} />
  );
}

// ─── Card ─────────────────────────────────────────────────────────────
function Card({ children, className = "", padding = "md", title, action }) {
  const pad = padding === "none" ? "" : padding === "sm" ? "p-3" : padding === "lg" ? "p-6" : "p-4";
  return (
    <div className={`surface border rounded-lg ${className}`} style={{ borderColor: "var(--border)", boxShadow: "var(--shadow-sm)" }}>
      {(title || action) && (
        <div className="flex items-center justify-between px-4 py-3 border-b" style={{ borderColor: "var(--border)" }}>
          <div className="text-[13px] font-semibold text-app">{title}</div>
          {action}
        </div>
      )}
      <div className={pad}>{children}</div>
    </div>
  );
}

// ─── Tabs ─────────────────────────────────────────────────────────────
function Tabs({ tabs, value, onChange, rightSlot }) {
  return (
    <div className="flex items-end justify-between border-b" style={{ borderColor: "var(--border)" }}>
      <div className="flex items-end -mb-px">
        {tabs.map((t) => {
          const active = t.id === value;
          return (
            <button key={t.id} onClick={() => onChange(t.id)}
              className={`relative px-3.5 h-9 text-[13px] font-medium transition-colors flex items-center gap-2 ${active ? "text-app" : "text-muted hover:text-app"}`}>
              {t.icon && <Icon name={t.icon} size={14}/>}
              <span>{t.label}</span>
              {typeof t.count === "number" && (
                <span className={`text-[11px] px-1.5 py-0.5 rounded ${active ? "accent-bg accent-fg" : "sunken text-muted"}`}>{t.count}</span>
              )}
              {active && <span className="absolute -bottom-px left-0 right-0 h-[2px]" style={{ background: "var(--accent)" }}/>}
            </button>
          );
        })}
      </div>
      {rightSlot && <div className="pb-1.5">{rightSlot}</div>}
    </div>
  );
}

// ─── Modal ────────────────────────────────────────────────────────────
function Modal({ open, onClose, title, children, footer, width = 520, dismissible = true, zIndex = 100 }) {
  useEffect(() => {
    if (!open || !dismissible) return;
    const k = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", k);
    return () => window.removeEventListener("keydown", k);
  }, [open, onClose, dismissible]);
  if (!open) return null;
  return (
    <div className="fixed inset-0 flex items-center justify-center anim-fade" style={{ background: "rgba(0,0,0,.4)", zIndex }} onClick={dismissible ? onClose : undefined}>
      <div className="surface border rounded-lg anim-scale" style={{ width, maxWidth: "90vw", maxHeight: "85vh", boxShadow: "var(--shadow-lg)", borderColor: "var(--border)" }} onClick={(e) => e.stopPropagation()}>
        <div className="flex items-center justify-between px-4 py-3 border-b" style={{ borderColor: "var(--border)" }}>
          <div className="text-[14px] font-semibold">{title}</div>
          {dismissible && <IconButton icon="x" size="sm" onClick={onClose}/>}
        </div>
        <div className="p-4 overflow-auto" style={{ maxHeight: "calc(85vh - 110px)" }}>{children}</div>
        {footer && <div className="flex items-center justify-end gap-2 px-4 py-3 border-t sunken" style={{ borderColor: "var(--border)" }}>{footer}</div>}
      </div>
    </div>
  );
}

// ─── Drawer (right side) ──────────────────────────────────────────────
function Drawer({ open, onClose, title, children, footer, width = 480 }) {
  if (!open) return null;
  return (
    <div className="fixed inset-0 z-[100]" style={{ background: "rgba(0,0,0,.3)" }} onClick={onClose}>
      <div className="absolute right-0 top-0 bottom-0 surface border-l flex flex-col anim-slide" style={{ width, maxWidth: "90vw", borderColor: "var(--border)", boxShadow: "var(--shadow-lg)" }} onClick={(e) => e.stopPropagation()}>
        <div className="flex items-center justify-between px-4 py-3 border-b" style={{ borderColor: "var(--border)" }}>
          <div className="text-[14px] font-semibold">{title}</div>
          <IconButton icon="x" size="sm" onClick={onClose}/>
        </div>
        <div className="flex-1 overflow-auto">{children}</div>
        {footer && <div className="flex items-center justify-end gap-2 px-4 py-3 border-t sunken" style={{ borderColor: "var(--border)" }}>{footer}</div>}
      </div>
    </div>
  );
}

// ─── Dropdown menu ────────────────────────────────────────────────────
function Dropdown({ trigger, items, align = "left", width = 200, placement = "bottom" }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  const triggerRef = useRef(null);
  const [actualPlacement, setActualPlacement] = useState(placement);
  useEffect(() => {
    if (!open) return;
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    window.addEventListener("mousedown", h);
    return () => window.removeEventListener("mousedown", h);
  }, [open]);
  useEffect(() => {
    if (!open || placement !== "bottom") return;
    const r = triggerRef.current && triggerRef.current.getBoundingClientRect();
    if (!r) return;
    const estimatedMenuH = 8 + items.length * 32;
    if (r.bottom + estimatedMenuH + 16 > window.innerHeight) setActualPlacement("top");
    else setActualPlacement("bottom");
  }, [open, items.length, placement]);
  const menuPos = actualPlacement === "top" ? "bottom-full mb-1" : "mt-1";
  return (
    <div className="relative" ref={ref}>
      <div ref={triggerRef} onClick={(e) => { e.stopPropagation(); setOpen((o) => !o); }}>{trigger}</div>
      {open && (
        <div className={`absolute ${menuPos} surface border rounded-md py-1 z-50 anim-scale ${align === "right" ? "right-0" : "left-0"}`}
             style={{ width, boxShadow: "var(--shadow-md)", borderColor: "var(--border)" }}>
          {items.map((it, i) => it === "sep" ? (
            <div key={i} className="my-1 border-t" style={{ borderColor: "var(--border)" }}/>
          ) : (
            <button key={i} onClick={() => { setOpen(false); it.onClick && it.onClick(); }}
              disabled={it.disabled}
              className={`w-full text-left px-2.5 py-1.5 text-[13px] flex items-center gap-2 hover:bg-[var(--hover)] ${it.danger ? "text-red-600" : "text-app"} ${it.disabled ? "opacity-40 cursor-not-allowed" : ""}`}>
              {it.icon && <Icon name={it.icon} size={14}/>}
              <span className="flex-1">{it.label}</span>
              {it.shortcut && <span className="text-[11px] text-subtle mono">{it.shortcut}</span>}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// ─── Tooltip ──────────────────────────────────────────────────────────
function Tooltip({ children, label }) {
  return <span title={label}>{children}</span>;
}

// ─── Toast manager ────────────────────────────────────────────────────
const ToastCtx = createContext(null);
function ToastProvider({ children }) {
  const [toasts, setToasts] = useState([]);
  const show = useCallback((msg, opts) => {
    const id = Math.random().toString(36).slice(2);
    const tone = (opts && opts.tone) || 'default';
    setToasts((t) => [...t, { id, msg, tone }]);
    setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 2800);
  }, []);
  // Expose globally so store actions (outside React tree) can fire toasts.
  useEffect(() => { window.__axluToast = show; return () => { if (window.__axluToast === show) window.__axluToast = null; }; }, [show]);
  const toneStyles = {
    default: { background: 'var(--surface)', color: 'var(--fg)', border: '1px solid var(--border)' },
    success: { background: '#10b981', color: '#fff', border: '1px solid #059669' },
    danger:  { background: '#ef4444', color: '#fff', border: '1px solid #dc2626' },
    warning: { background: '#f59e0b', color: '#1a1a1a', border: '1px solid #d97706' },
    info:    { background: 'var(--accent)', color: '#fff', border: '1px solid var(--accent-hover)' },
  };
  return (
    <ToastCtx.Provider value={show}>
      {children}
      <div style={{ position: 'fixed', right: 16, bottom: 16, display: 'flex', flexDirection: 'column-reverse', gap: 8, zIndex: 300, pointerEvents: 'none' }}>
        {toasts.map((t) => (
          <div key={t.id} className="anim-fade" style={{
            ...toneStyles[t.tone] || toneStyles.default,
            padding: '10px 14px', borderRadius: 8, fontSize: 13, fontWeight: 500,
            boxShadow: 'var(--shadow-lg)', maxWidth: 420, pointerEvents: 'auto',
          }}>{t.msg}</div>
        ))}
      </div>
    </ToastCtx.Provider>
  );
}
function useToast() { return useContext(ToastCtx) || (() => {}); }

// ─── Empty state ──────────────────────────────────────────────────────
function EmptyState({ icon = "info", title, description, action }) {
  return (
    <div className="flex flex-col items-center justify-center py-12 px-6 text-center">
      <div className="w-10 h-10 rounded-full flex items-center justify-center mb-3 sunken text-subtle">
        <Icon name={icon} size={20}/>
      </div>
      <div className="text-[14px] font-semibold text-app mb-1">{title}</div>
      {description && <div className="text-[13px] text-muted max-w-md">{description}</div>}
      {action && <div className="mt-4">{action}</div>}
    </div>
  );
}

// ─── KPI tile ─────────────────────────────────────────────────────────
function KPI({ label, value, delta, trend, icon, tone = "neutral" }) {
  const trendColor = trend === "up" ? "text-emerald-600" : trend === "down" ? "text-red-600" : "text-muted";
  return (
    <div className="surface border rounded-lg p-4 flex flex-col gap-2" style={{ borderColor: "var(--border)" }}>
      <div className="flex items-center justify-between">
        <div className="text-[12px] text-muted font-medium uppercase tracking-wide">{label}</div>
        {icon && <span className="text-subtle"><Icon name={icon} size={14}/></span>}
      </div>
      <div className="text-[28px] font-semibold text-app leading-none mono tabular-nums">{value}</div>
      {delta && <div className={`text-[12px] font-medium flex items-center gap-1 ${trendColor}`}>
        {trend === "up" && <Icon name="arrowUp" size={11}/>}
        {trend === "down" && <Icon name="arrowDown" size={11}/>}
        <span>{delta}</span>
      </div>}
    </div>
  );
}

// ─── Breadcrumb ───────────────────────────────────────────────────────
function Breadcrumb({ items }) {
  return (
    <nav className="flex items-center gap-1.5 text-[12.5px] text-muted">
      {items.map((it, i) => (
        <Fragment key={i}>
          {i > 0 && <Icon name="chevronRight" size={12} className="text-subtle"/>}
          {it.href ? (
            <a href={it.href} className="hover:text-app transition-colors">{it.label}</a>
          ) : (
            <span className={i === items.length - 1 ? "text-app font-medium" : ""}>{it.label}</span>
          )}
        </Fragment>
      ))}
    </nav>
  );
}

// ─── Page header ──────────────────────────────────────────────────────
// `onBack` (optional) renders a back arrow to the left of the title.
function PageHeader({ breadcrumb, title, subtitle, actions, tabs, onBack }) {
  return (
    <div className="px-6 pt-5 pb-0 surface border-b" style={{ borderColor: "var(--border)" }}>
      {breadcrumb && <div className="mb-2"><Breadcrumb items={breadcrumb}/></div>}
      <div className="flex items-start justify-between gap-4 mb-3">
        <div className="flex-1">
          <div className="flex items-center gap-2.5">
            {onBack && (
              <button onClick={onBack} title="Retour"
                      className="shrink-0 inline-flex items-center justify-center h-8 w-8 rounded-md border text-muted hover:text-app hover:bg-[var(--hover)] transition-colors ring-accent"
                      style={{ borderColor: "var(--border)" }}>
                <Icon name="chevronLeft" size={16}/>
              </button>
            )}
            <h1 className="text-[20px] font-semibold text-app tracking-tight">{title}</h1>
          </div>
          {subtitle && <div className="text-[13px] text-muted mt-1">{subtitle}</div>}
        </div>
        {actions && <div className="flex items-center gap-2 shrink-0">{actions}</div>}
      </div>
      {tabs}
    </div>
  );
}

// ─── Avatar ───────────────────────────────────────────────────────────
function Avatar({ name, size = 28 }) {
  const initials = name.split(" ").map((s) => s[0]).slice(0, 2).join("").toUpperCase();
  // Deterministic hue from name
  const hash = [...name].reduce((a, c) => a + c.charCodeAt(0), 0);
  const hue = hash % 360;
  return (
    <div className="rounded-full flex items-center justify-center font-medium text-white shrink-0 select-none"
         style={{ width: size, height: size, fontSize: size * 0.38, background: `oklch(0.55 0.13 ${hue})` }}>
      {initials}
    </div>
  );
}

// ─── Product thumbnail ────────────────────────────────────────────────
// Shows the real product image when `src` (a URL) is provided. Falls back to
// a neutral placeholder if no URL or the image fails to load. No frame —
// product JPEGs already sit on white; a border just adds visual noise.
function ProductThumb({ size = 32, sku, src, hasImage = true, className = "" }) {
  const [failed, setFailed] = useState(false);
  const dim = typeof size === "number" ? { width: size, height: size } : { width: "100%", height: "100%" };

  if (src && !failed) {
    return (
      <img src={src} alt={sku || ""} draggable={false}
           className={`shrink-0 ${className}`}
           style={{ ...dim, objectFit: "contain", display: "block" }}
           onError={() => setFailed(true)}/>
    );
  }
  if (!hasImage || failed) {
    return (
      <div className={`img-ph rounded flex items-center justify-center text-subtle shrink-0 ${className}`} style={dim}>
        <Icon name="image" size={(typeof size === "number" ? size : 32) * 0.5}/>
      </div>
    );
  }
  // No URL but flagged as having an image — neutral deterministic placeholder.
  const hash = [...(sku || "")].reduce((a, c) => a + c.charCodeAt(0), 0);
  return (
    <div className={`rounded shrink-0 ${className}`} style={{
      ...dim,
      background: `linear-gradient(135deg, oklch(0.92 0.04 ${hash % 360}), oklch(0.85 0.06 ${(hash * 7) % 360}))`,
    }}/>
  );
}

// ─── Format helpers ───────────────────────────────────────────────────
const fmt = {
  eur: (n) => n == null ? "—" : new Intl.NumberFormat("fr-FR", { style: "currency", currency: "EUR", minimumFractionDigits: 2 }).format(n),
  num: (n) => n == null ? "—" : new Intl.NumberFormat("fr-FR").format(n),
  pct: (n) => n == null ? "—" : `${n.toFixed(1)}%`,
  date: (s) => {
    if (!s) return "—";
    const d = new Date(s);
    return d.toLocaleString("fr-FR", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" });
  },
  dateOnly: (s) => {
    if (!s) return "—";
    return new Date(s).toLocaleDateString("fr-FR", { day: "2-digit", month: "short", year: "numeric" });
  },
  rel: (s) => {
    if (!s) return "—";
    const diff = (Date.now() - new Date(s).getTime()) / 1000;
    if (diff < 60) return "à l'instant";
    if (diff < 3600) return `il y a ${Math.floor(diff / 60)} min`;
    if (diff < 86400) return `il y a ${Math.floor(diff / 3600)} h`;
    return `il y a ${Math.floor(diff / 86400)} j`;
  },
  duration: (sec) => {
    if (sec == null) return "—";
    if (sec < 60) return `${sec}s`;
    return `${Math.floor(sec / 60)}m ${sec % 60}s`;
  },
};

// ─── Sortable helper ──────────────────────────────────────────────────
function useSorted(items, defaultKey) {
  const [sort, setSort] = useState({ key: defaultKey, dir: "asc" });
  const sorted = useMemo(() => {
    if (!sort.key) return items;
    const out = [...items].sort((a, b) => {
      const va = a[sort.key], vb = b[sort.key];
      if (va == null && vb == null) return 0;
      if (va == null) return 1;
      if (vb == null) return -1;
      if (typeof va === "number" && typeof vb === "number") return va - vb;
      return String(va).localeCompare(String(vb));
    });
    if (sort.dir === "desc") out.reverse();
    return out;
  }, [items, sort]);
  const toggle = (k) => setSort((s) => s.key === k ? { key: k, dir: s.dir === "asc" ? "desc" : "asc" } : { key: k, dir: "asc" });
  return { sorted, sort, toggle };
}

function SortHeader({ label, sortKey, sort, toggle, align = "left", width }) {
  const active = sort.key === sortKey;
  return (
    <th className="sortable" onClick={() => toggle(sortKey)} style={{ textAlign: align, width }}>
      <div className={`inline-flex items-center gap-1 ${active ? "text-app" : ""}`} style={{ flexDirection: align === "right" ? "row-reverse" : "row" }}>
        <span>{label}</span>
        <Icon name={active ? (sort.dir === "asc" ? "arrowUp" : "arrowDown") : "chevronUpDown"} size={11}/>
      </div>
    </th>
  );
}

// Expose
Object.assign(window, {
  Icon, Button, IconButton, Badge, StatusBadge, Input, Select, Checkbox,
  Card, Tabs, Modal, Drawer, Dropdown, Tooltip, ToastProvider, useToast,
  EmptyState, KPI, Breadcrumb, PageHeader, Avatar, ProductThumb,
  fmt, useSorted, SortHeader,
});
