/* JRNI UI primitives — buttons, fields, pills, avatars, tables, toast, modal.
 * Exported to window: Button, IconBtn, Pill, Chip, Avatar, Field, Input, Textarea,
 * Select, Toggle, Checkbox, Radio, Modal, Toast, Drawer, Tabs, Card, KV, Empty.
 */

const { useState, useEffect, useRef } = React;

/* ─── Buttons ──────────────────────────────────────────────────────────── */
function Button({ children, variant = "primary", size = "md", icon, iconRight, onClick, disabled, type = "button", fullWidth, style }) {
  const sizes = {
    sm: { height: 28, padding: "0 10px", fontSize: 13 },
    md: { height: 36, padding: "0 14px", fontSize: 14 },
    lg: { height: 44, padding: "0 18px", fontSize: 15 },
  };
  const variants = {
    primary: {
      background: "var(--jrni-color-primary-1)", color: "white", border: "1px solid var(--jrni-color-primary-1)",
      fontWeight: 600,
    },
    secondary: {
      background: "white", color: "var(--jrni-color-neutral-1)", border: "1px solid var(--jrni-color-neutral-4)",
      fontWeight: 500, boxShadow: "var(--jrni-shadow-chrome)",
    },
    tertiary: {
      background: "transparent", color: "var(--jrni-color-primary-1)", border: "1px solid transparent",
      fontWeight: 500,
    },
    danger: {
      background: "var(--jrni-color-semantic-red-1)", color: "white", border: "1px solid var(--jrni-color-semantic-red-1)",
      fontWeight: 600,
    },
    "danger-outline": {
      background: "white", color: "var(--jrni-color-semantic-red-1)", border: "1px solid var(--jrni-color-semantic-red-1)",
      fontWeight: 500,
    },
  };
  return (
    <button type={type} onClick={onClick} disabled={disabled} style={{
      ...sizes[size], ...variants[variant], borderRadius: 4, cursor: disabled ? "not-allowed" : "pointer",
      display: "inline-flex", alignItems: "center", gap: 6, justifyContent: "center",
      fontFamily: "var(--jrni-font-family-sans)", whiteSpace: "nowrap",
      opacity: disabled ? 0.5 : 1, width: fullWidth ? "100%" : "auto",
      ...(style || {}),
    }}>
      {icon}{children}{iconRight}
    </button>
  );
}

function IconBtn({ children, label, onClick, active, size = "md", style }) {
  const h = size === "sm" ? 28 : 36;
  return (
    <button onClick={onClick} aria-label={label} title={label} style={{
      width: h, height: h, display: "inline-flex", alignItems: "center", justifyContent: "center",
      borderRadius: 4, border: "1px solid var(--jrni-color-neutral-4)",
      background: active ? "var(--jrni-color-neutral-6)" : "white",
      color: "var(--jrni-color-neutral-1)", cursor: "pointer", boxShadow: "var(--jrni-shadow-chrome)",
      ...(style || {}),
    }}>{children}</button>
  );
}

/* ─── Pills, chips, badges ─────────────────────────────────────────────── */
function Pill({ children, tone = "neutral", style }) {
  const tones = {
    neutral: { bg: "var(--jrni-color-neutral-5)",       fg: "var(--jrni-color-neutral-1)" },
    primary: { bg: "var(--jrni-color-primary-3)",        fg: "var(--jrni-color-primary-1)" },
    success: { bg: "var(--jrni-color-semantic-green-2)", fg: "var(--jrni-color-semantic-green-1)" },
    warning: { bg: "var(--jrni-color-semantic-orange-2)",fg: "var(--jrni-color-semantic-orange-1)" },
    danger:  { bg: "var(--jrni-color-semantic-red-2)",   fg: "var(--jrni-color-semantic-red-1)" },
    info:    { bg: "var(--jrni-color-semantic-blue-2)",  fg: "var(--jrni-color-semantic-blue-1)" },
  };
  const t = tones[tone];
  return <span style={{
    display: "inline-flex", alignItems: "center", gap: 4, padding: "2px 8px",
    borderRadius: 999, background: t.bg, color: t.fg, fontSize: 11, fontWeight: 600,
    letterSpacing: 0.02, lineHeight: 1.4, whiteSpace: "nowrap", ...(style || {}),
  }}>{children}</span>;
}

function StatusDot({ tone }) {
  const colors = {
    live: "var(--jrni-color-semantic-green-1)",
    upcoming: "var(--jrni-color-primary-1)",
    draft: "var(--jrni-color-neutral-3)",
    past: "var(--jrni-color-neutral-3)",
    cancelled: "var(--jrni-color-semantic-red-1)",
  };
  return <span style={{ width: 8, height: 8, borderRadius: 999, background: colors[tone] || colors.upcoming, display: "inline-block" }} />;
}

function StatusPill({ status }) {
  const map = {
    live:      { tone: "success",  label: "Live" },
    upcoming:  { tone: "info",     label: "Upcoming" },
    draft:     { tone: "neutral",  label: "Draft" },
    past:      { tone: "neutral",  label: "Past" },
    cancelled: { tone: "danger",   label: "Cancelled" },
    soldout:   { tone: "warning",  label: "Sold out" },
  };
  const s = map[status] || map.upcoming;
  return <Pill tone={s.tone}>{s.label}</Pill>;
}

function Chip({ children, active, onClick, count }) {
  return (
    <button onClick={onClick} style={{
      padding: "6px 12px", borderRadius: 999, fontSize: 13, fontWeight: 500,
      background: active ? "var(--jrni-color-primary-1)" : "white",
      color: active ? "white" : "var(--jrni-color-neutral-1)",
      border: `1px solid ${active ? "var(--jrni-color-primary-1)" : "var(--jrni-color-surface-border-strong)"}`,
      cursor: "pointer", whiteSpace: "nowrap", display: "inline-flex", alignItems: "center", gap: 6,
      fontFamily: "var(--jrni-font-family-sans)",
    }}>
      {children}
      {count != null ? (
        <span style={{ fontSize: 11, opacity: 0.75 }}>· {count}</span>
      ) : null}
    </button>
  );
}

/* ─── Avatar ───────────────────────────────────────────────────────────── */
function Avatar({ initials, color = "var(--jrni-color-primary-1)", size = 32, ring }) {
  return (
    <span style={{
      width: size, height: size, borderRadius: 999, background: color, color: "white",
      display: "inline-flex", alignItems: "center", justifyContent: "center",
      fontWeight: 600, fontSize: Math.max(10, size * 0.4), flexShrink: 0,
      border: ring ? "2px solid white" : "none", boxShadow: ring ? "0 0 0 1px var(--jrni-color-surface-border)" : "none",
    }}>{initials}</span>
  );
}
function AvatarStack({ people, max = 4, size = 28 }) {
  const visible = people.slice(0, max);
  const extra = people.length - visible.length;
  return (
    <span style={{ display: "inline-flex", alignItems: "center" }}>
      {visible.map((p, i) => (
        <span key={i} style={{ marginLeft: i === 0 ? 0 : -8 }}>
          <Avatar initials={p.initials} color={p.color} size={size} ring />
        </span>
      ))}
      {extra > 0 ? (
        <span style={{ marginLeft: -8 }}>
          <Avatar initials={`+${extra}`} color="var(--jrni-color-neutral-4)" size={size} ring />
        </span>
      ) : null}
    </span>
  );
}

/* ─── Form fields ──────────────────────────────────────────────────────── */
function Field({ label, hint, error, children, optional, width }) {
  return (
    <label style={{ display: "flex", flexDirection: "column", gap: 4, width: width || "100%" }}>
      <span style={{ fontSize: 12, fontWeight: 500, color: "var(--jrni-color-text-soft)", letterSpacing: 0.02 }}>
        {label}{optional ? <span style={{ color: "var(--jrni-color-neutral-3)", marginLeft: 6, fontWeight: 400 }}>Optional</span> : null}
      </span>
      {children}
      {error ? (
        <span style={{ fontSize: 12, color: "var(--jrni-color-semantic-red-1)" }}>{error}</span>
      ) : hint ? (
        <span style={{ fontSize: 12, color: "var(--jrni-color-text-soft)" }}>{hint}</span>
      ) : null}
    </label>
  );
}

const fieldBase = {
  height: 36, padding: "0 12px", borderRadius: 4,
  border: "1px solid var(--jrni-color-neutral-4)", background: "white",
  fontSize: 14, fontFamily: "var(--jrni-font-family-sans)", color: "var(--jrni-color-neutral-1)",
  outline: "none", width: "100%", boxSizing: "border-box",
};

function Input(props) {
  return <input {...props} style={{ ...fieldBase, ...(props.style || {}) }} />;
}
function Textarea(props) {
  return <textarea {...props} style={{ ...fieldBase, height: "auto", padding: "10px 12px", minHeight: 80, resize: "vertical", lineHeight: 1.5, ...(props.style || {}) }} />;
}
function Select({ value, onChange, options, style }) {
  return (
    <div style={{ position: "relative", width: "100%" }}>
      <select value={value} onChange={e => onChange && onChange(e.target.value)} style={{
        ...fieldBase, appearance: "none", paddingRight: 32, cursor: "pointer", ...(style || {}),
      }}>
        {options.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
      </select>
      <span style={{ position: "absolute", right: 10, top: 11, color: "var(--jrni-color-text-soft)", pointerEvents: "none" }}>
        <Icons.Chevron size={12} />
      </span>
    </div>
  );
}

function Toggle({ checked, onChange, label }) {
  return (
    <label style={{ display: "inline-flex", alignItems: "center", gap: 10, cursor: "pointer" }}>
      <span style={{
        width: 36, height: 20, borderRadius: 999, background: checked ? "var(--jrni-color-primary-1)" : "var(--jrni-color-neutral-4)",
        position: "relative", transition: "background 200ms var(--jrni-motion-easing-standard)",
      }}>
        <span style={{
          position: "absolute", top: 2, left: checked ? 18 : 2, width: 16, height: 16,
          borderRadius: 999, background: "white",
          transition: "left 200ms var(--jrni-motion-easing-standard)",
          boxShadow: "0 1px 2px rgba(0,0,0,0.15)",
        }} />
      </span>
      <input type="checkbox" checked={checked} onChange={e => onChange && onChange(e.target.checked)} style={{ display: "none" }} />
      {label ? <span style={{ fontSize: 14, color: "var(--jrni-color-neutral-1)" }}>{label}</span> : null}
    </label>
  );
}

function Checkbox({ checked, onChange, label, indeterminate }) {
  return (
    <label style={{ display: "inline-flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 14 }}>
      <span style={{
        width: 16, height: 16, borderRadius: 3,
        border: `1px solid ${checked || indeterminate ? "var(--jrni-color-primary-1)" : "var(--jrni-color-neutral-4)"}`,
        background: checked || indeterminate ? "var(--jrni-color-primary-1)" : "white",
        display: "inline-flex", alignItems: "center", justifyContent: "center", color: "white",
      }}>
        {indeterminate ? <span style={{ width: 8, height: 2, background: "white", borderRadius: 1 }} /> :
          checked ? <Icons.Check size={11} /> : null}
      </span>
      <input type="checkbox" checked={!!checked} onChange={e => onChange && onChange(e.target.checked)} style={{ display: "none" }} />
      {label ? <span style={{ color: "var(--jrni-color-neutral-1)" }}>{label}</span> : null}
    </label>
  );
}

function Radio({ checked, onChange, label }) {
  return (
    <label style={{ display: "inline-flex", alignItems: "center", gap: 8, cursor: "pointer", fontSize: 14 }}>
      <span style={{
        width: 16, height: 16, borderRadius: 999,
        border: `1px solid ${checked ? "var(--jrni-color-primary-1)" : "var(--jrni-color-neutral-4)"}`,
        background: "white",
        display: "inline-flex", alignItems: "center", justifyContent: "center",
      }}>
        {checked ? <span style={{ width: 8, height: 8, borderRadius: 999, background: "var(--jrni-color-primary-1)" }} /> : null}
      </span>
      <input type="radio" checked={!!checked} onChange={e => onChange && onChange(e.target.checked)} style={{ display: "none" }} />
      {label ? <span style={{ color: "var(--jrni-color-neutral-1)" }}>{label}</span> : null}
    </label>
  );
}

/* ─── Layout / containers ──────────────────────────────────────────────── */
function Card({ children, padding = 20, style, onClick }) {
  return (
    <div onClick={onClick} style={{
      background: "white", border: "1px solid var(--jrni-color-surface-border)",
      borderRadius: 8, padding, cursor: onClick ? "pointer" : "default", ...(style || {}),
    }}>{children}</div>
  );
}

function SectionLabel({ children, action }) {
  return (
    <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }}>
      <span style={{ fontSize: 11, fontWeight: 600, color: "var(--jrni-color-text-soft)", textTransform: "uppercase", letterSpacing: 0.04 }}>
        {children}
      </span>
      {action ? <span>{action}</span> : null}
    </div>
  );
}

function KV({ k, v, vColor }) {
  return (
    <div style={{ display: "flex", justifyContent: "space-between", gap: 12, fontSize: 13, padding: "8px 0", borderBottom: "1px solid var(--jrni-color-surface-border)" }}>
      <span style={{ color: "var(--jrni-color-text-soft)" }}>{k}</span>
      <span style={{ color: vColor || "var(--jrni-color-neutral-1)", fontWeight: 500, textAlign: "right" }}>{v}</span>
    </div>
  );
}

function Empty({ icon, title, body, action }) {
  return (
    <div style={{
      padding: "48px 24px", textAlign: "center", display: "flex", flexDirection: "column", alignItems: "center", gap: 8,
      border: "1px dashed var(--jrni-color-surface-border-strong)", borderRadius: 8, background: "var(--jrni-color-surface-card-soft)",
    }}>
      {icon ? <span style={{ width: 48, height: 48, borderRadius: 999, background: "var(--jrni-color-primary-3)", color: "var(--jrni-color-primary-1)", display: "inline-flex", alignItems: "center", justifyContent: "center", marginBottom: 4 }}>{icon}</span> : null}
      <h3 style={{ margin: 0, fontSize: 16, fontWeight: 600, color: "var(--jrni-color-neutral-1)" }}>{title}</h3>
      {body ? <p style={{ margin: 0, fontSize: 14, color: "var(--jrni-color-text-soft)", maxWidth: 360 }}>{body}</p> : null}
      {action ? <div style={{ marginTop: 12 }}>{action}</div> : null}
    </div>
  );
}

/* ─── Tabs ─────────────────────────────────────────────────────────────── */
function Tabs({ tabs, active, onChange, padding = "0 32px" }) {
  return (
    <div style={{ display: "flex", gap: 0, padding, borderBottom: "1px solid var(--jrni-color-surface-border)", background: "white" }}>
      {tabs.map(t => {
        const isActive = active === t.id;
        return (
          <button key={t.id} onClick={() => onChange(t.id)} style={{
            padding: "12px 16px", fontSize: 14,
            color: isActive ? "var(--jrni-color-primary-1)" : "var(--jrni-color-text-body)",
            fontWeight: isActive ? 600 : 500,
            border: "none", background: "transparent",
            borderBottom: `2px solid ${isActive ? "var(--jrni-color-primary-1)" : "transparent"}`,
            marginBottom: -1, cursor: "pointer", fontFamily: "var(--jrni-font-family-sans)",
            display: "inline-flex", alignItems: "center", gap: 6,
          }}>
            {t.label}
            {t.count != null ? <span style={{ fontSize: 11, color: "var(--jrni-color-text-soft)", fontWeight: 500 }}>{t.count}</span> : null}
          </button>
        );
      })}
    </div>
  );
}

/* ─── Modal / drawer ───────────────────────────────────────────────────── */
function Modal({ open, onClose, title, subtitle, children, footer, width = 540, hideCloseInHeader }) {
  if (!open) return null;
  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "var(--jrni-color-surface-overlay)", zIndex: 100,
      display: "flex", alignItems: "center", justifyContent: "center", padding: 24,
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width, maxWidth: "100%", maxHeight: "calc(100vh - 48px)", background: "white", borderRadius: 8,
        boxShadow: "var(--jrni-shadow-lg)", display: "flex", flexDirection: "column", overflow: "hidden",
      }}>
        <div style={{ padding: "16px 24px", borderBottom: "1px solid var(--jrni-color-surface-border)", display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12 }}>
          <div>
            <h2 style={{ margin: 0, fontSize: 18, fontWeight: 600, color: "var(--jrni-color-neutral-1)" }}>{title}</h2>
            {subtitle ? <p style={{ margin: "4px 0 0", fontSize: 13, color: "var(--jrni-color-text-soft)" }}>{subtitle}</p> : null}
          </div>
          {hideCloseInHeader ? null : (
            <button onClick={onClose} aria-label="Close" style={{ width: 28, height: 28, border: "none", background: "transparent", color: "var(--jrni-color-text-soft)", cursor: "pointer", display: "grid", placeItems: "center" }}>
              <Icons.X size={14} />
            </button>
          )}
        </div>
        <div style={{ flex: 1, overflowY: "auto", padding: 24 }}>{children}</div>
        {footer ? (
          <div style={{ padding: "12px 24px", borderTop: "1px solid var(--jrni-color-surface-border)", display: "flex", gap: 8, justifyContent: "flex-end" }}>
            {footer}
          </div>
        ) : null}
      </div>
    </div>
  );
}

function Drawer({ open, onClose, title, eyebrow, children, footer, width = 520, headerRight }) {
  if (!open) return null;
  return (
    <div onClick={onClose} style={{
      position: "fixed", inset: 0, background: "var(--jrni-color-surface-overlay)", zIndex: 90,
      display: "flex", justifyContent: "flex-end",
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width, maxWidth: "100%", background: "white", height: "100%", boxShadow: "var(--jrni-shadow-lg)",
        display: "flex", flexDirection: "column", overflow: "hidden",
      }}>
        <div style={{ padding: "14px 24px", borderBottom: "1px solid var(--jrni-color-surface-border)", display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12 }}>
          <div>
            {eyebrow ? <div style={{ fontSize: 11, color: "var(--jrni-color-text-soft)", textTransform: "uppercase", fontWeight: 600, letterSpacing: 0.04 }}>{eyebrow}</div> : null}
            <div style={{ fontSize: 18, fontWeight: 600, color: "var(--jrni-color-neutral-1)", marginTop: 2 }}>{title}</div>
          </div>
          <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
            {headerRight}
            <IconBtn label="Close" onClick={onClose}><Icons.X size={12} /></IconBtn>
          </div>
        </div>
        <div style={{ flex: 1, overflowY: "auto" }}>{children}</div>
        {footer ? (
          <div style={{ padding: "12px 24px", borderTop: "1px solid var(--jrni-color-surface-border)", display: "flex", gap: 8, justifyContent: "flex-end" }}>
            {footer}
          </div>
        ) : null}
      </div>
    </div>
  );
}

/* ─── Toast ────────────────────────────────────────────────────────────── */
function Toast({ toast, onDismiss }) {
  useEffect(() => {
    if (!toast) return;
    const t = setTimeout(() => onDismiss && onDismiss(), 4500);
    return () => clearTimeout(t);
  }, [toast]);
  if (!toast) return null;
  const tones = {
    success: { bg: "var(--jrni-color-semantic-green-1)", icon: <Icons.Check /> },
    info:    { bg: "var(--jrni-color-neutral-1)",         icon: <Icons.Info /> },
    error:   { bg: "var(--jrni-color-semantic-red-1)",    icon: <Icons.Warning /> },
  };
  const c = tones[toast.tone || "info"];
  return (
    <div style={{
      position: "fixed", left: "50%", transform: "translateX(-50%)", bottom: 24, zIndex: 200,
      background: c.bg, color: "white", padding: "10px 16px", borderRadius: 4,
      boxShadow: "var(--jrni-shadow-lg)", display: "flex", alignItems: "center", gap: 10,
      fontSize: 14, fontWeight: 500, maxWidth: 480, fontFamily: "var(--jrni-font-family-sans)",
    }}>
      <span style={{ display: "inline-flex" }}>{c.icon}</span>
      <span>{toast.text}</span>
      {toast.action ? <button onClick={toast.action.onClick} style={{ marginLeft: 12, background: "transparent", border: "none", color: "white", textDecoration: "underline", cursor: "pointer", fontSize: 14, fontWeight: 600, fontFamily: "inherit" }}>{toast.action.label}</button> : null}
      <button onClick={onDismiss} aria-label="Dismiss" style={{ background: "transparent", border: "none", color: "white", cursor: "pointer", marginLeft: 8, opacity: 0.7 }}>
        <Icons.X size={12} />
      </button>
    </div>
  );
}

/* ─── Progress ─────────────────────────────────────────────────────────── */
function ProgressBar({ value, max = 100, color = "var(--jrni-color-primary-1)", height = 6 }) {
  const pct = Math.min(100, Math.round((value / max) * 100));
  return (
    <div style={{ height, background: "var(--jrni-color-neutral-5)", borderRadius: 999, overflow: "hidden" }}>
      <div style={{ width: `${pct}%`, height: "100%", background: color }} />
    </div>
  );
}

/* ─── Popover anchor ───────────────────────────────────────────────────── */
function useOutsideClick(ref, onClose) {
  useEffect(() => {
    function handle(e) { if (ref.current && !ref.current.contains(e.target)) onClose(); }
    document.addEventListener("mousedown", handle);
    return () => document.removeEventListener("mousedown", handle);
  }, [ref, onClose]);
}

function Popover({ open, onClose, anchor, children, align = "right", width = 240, offsetY = 8 }) {
  const ref = useRef(null);
  useOutsideClick(ref, () => open && onClose && onClose());
  if (!open || !anchor) return null;
  const rect = anchor.getBoundingClientRect();
  const top = rect.bottom + offsetY;
  const left = align === "left" ? rect.left : rect.right - width;
  return (
    <div ref={ref} style={{
      position: "fixed", top, left, width, zIndex: 110,
      background: "white", borderRadius: 8, boxShadow: "var(--jrni-shadow-lg)",
      border: "1px solid var(--jrni-color-surface-border)", overflow: "hidden",
      fontFamily: "var(--jrni-font-family-sans)",
    }}>{children}</div>
  );
}

function MenuItem({ icon, children, onClick, danger, hint, disabled }) {
  return (
    <button onClick={onClick} disabled={disabled} style={{
      display: "flex", alignItems: "center", gap: 10, width: "100%", padding: "8px 12px",
      background: "transparent", border: "none", cursor: disabled ? "not-allowed" : "pointer",
      fontSize: 14, color: danger ? "var(--jrni-color-semantic-red-1)" : "var(--jrni-color-neutral-1)",
      textAlign: "left", fontFamily: "var(--jrni-font-family-sans)", opacity: disabled ? 0.5 : 1,
    }}>
      {icon ? <span style={{ color: danger ? "var(--jrni-color-semantic-red-1)" : "var(--jrni-color-text-soft)", display: "inline-flex" }}>{icon}</span> : null}
      <span style={{ flex: 1 }}>{children}</span>
      {hint ? <span style={{ fontSize: 11, color: "var(--jrni-color-text-soft)" }}>{hint}</span> : null}
    </button>
  );
}

/* ════════════════════════════════════════════════════════════════════════
 * FLEET-SPECIFIC COMPONENTS
 * Domain primitives layered on top of the ANVOS base set.
 * ════════════════════════════════════════════════════════════════════════ */

/* ─── Drift severity pill ──────────────────────────────────────────────── */
function DriftPill({ level, count }) {
  const map = {
    none:  { tone: "success", label: "Up to date" },
    patch: { tone: "info",    label: "Patch behind" },
    minor: { tone: "warning", label: "Minor behind" },
    major: { tone: "danger",  label: "Major behind" },
  };
  const m = map[level] || map.none;
  return <Pill tone={m.tone}>{m.label}{count ? <span style={{ opacity: 0.7 }}> · {count}</span> : null}</Pill>;
}

/* ─── Component version chip (a single @voyage/* module's pinned version) ── */
function VersionChip({ version, drift }) {
  const colors = {
    none:  { bg: "var(--jrni-color-semantic-green-2)",  fg: "var(--jrni-color-semantic-green-1)",  dot: "var(--jrni-color-semantic-green-1)" },
    patch: { bg: "var(--jrni-color-semantic-blue-2)",   fg: "var(--jrni-color-semantic-blue-1)",   dot: "var(--jrni-color-semantic-blue-1)" },
    minor: { bg: "var(--jrni-color-semantic-orange-2)", fg: "var(--jrni-color-semantic-orange-1)", dot: "var(--jrni-color-semantic-orange-1)" },
    major: { bg: "var(--jrni-color-semantic-red-2)",    fg: "var(--jrni-color-semantic-red-1)",    dot: "var(--jrni-color-semantic-red-1)" },
  };
  const c = colors[drift] || colors.none;
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 6, padding: "2px 9px 2px 7px",
      borderRadius: 999, background: c.bg, color: c.fg, fontSize: 12, fontWeight: 600,
      fontFamily: "var(--jrni-font-family-mono)", whiteSpace: "nowrap",
    }}>
      <span style={{ width: 6, height: 6, borderRadius: 999, background: c.dot }} />
      {version}
    </span>
  );
}

/* ─── Composition cell — a tenant's set of component pins at a glance ───── */
function CompositionCell({ tenant }) {
  const behind = tenant.modulesBehind || 0;
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 7, whiteSpace: "nowrap" }}>
      <span style={{ fontSize: 12.5, color: "var(--jrni-color-neutral-1)", fontWeight: 600 }}>{tenant.moduleCount}</span>
      <span style={{ fontSize: 12, color: "var(--jrni-color-text-soft)" }}>components</span>
      {behind > 0 ? <span style={{ fontSize: 11, fontWeight: 600, padding: "1px 7px", borderRadius: 999, background: "var(--jrni-color-semantic-orange-2)", color: "var(--jrni-color-semantic-orange-1)" }}>{behind} behind</span>
        : <span style={{ fontSize: 11, fontWeight: 600, padding: "1px 7px", borderRadius: 999, background: "var(--jrni-color-semantic-green-2)", color: "var(--jrni-color-semantic-green-1)" }}>current</span>}
    </span>
  );
}

/* ─── CI/CD status pill ────────────────────────────────────────────────── */
function CiPill({ status, small }) {
  const map = {
    passing: { tone: "success", label: "Passing" },
    failing: { tone: "danger",  label: "Failing" },
    running: { tone: "info",    label: "Running" },
    unknown: { tone: "neutral", label: "Unknown" },
  };
  const m = map[status] || map.unknown;
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 5, padding: small ? "1px 7px" : "2px 8px",
      borderRadius: 999, fontSize: 11, fontWeight: 600,
      background: `var(--jrni-color-status-${m.tone === "neutral" ? "info" : m.tone === "success" ? "success" : m.tone === "danger" ? "danger" : "info"}-bg)`,
    }}>
      <span style={{
        width: 6, height: 6, borderRadius: 999,
        background: m.tone === "success" ? "var(--jrni-color-semantic-green-1)" : m.tone === "danger" ? "var(--jrni-color-semantic-red-1)" : m.tone === "info" ? "var(--jrni-color-semantic-blue-1)" : "var(--jrni-color-neutral-3)",
        animation: status === "running" ? "pulse 1.4s ease-in-out infinite" : "none",
      }} />
      <span style={{ color: m.tone === "success" ? "var(--jrni-color-semantic-green-1)" : m.tone === "danger" ? "var(--jrni-color-semantic-red-1)" : m.tone === "info" ? "var(--jrni-color-semantic-blue-1)" : "var(--jrni-color-neutral-2)" }}>{m.label}</span>
    </span>
  );
}

/* ─── Tenant status pill ───────────────────────────────────────────────── */
function TenantStatusPill({ status }) {
  const map = {
    healthy:        { tone: "success", label: "Healthy" },
    drift:          { tone: "warning", label: "Drift" },
    "failed-deploy":{ tone: "danger",  label: "Failed deploy" },
    paused:         { tone: "neutral", label: "Paused" },
    archived:       { tone: "neutral", label: "Archived" },
  };
  const m = map[status] || map.healthy;
  return <Pill tone={m.tone}>{m.label}</Pill>;
}

/* ─── Actor chip — distinguishes automation (bots) from human operators ── */
function ActorChip({ name, isBot, size = "md", onClick }) {
  const bot = isBot || name === "GitHub Actions" || name === "auto-merge bot";
  if (bot) {
    return (
      <span style={{
        display: "inline-flex", alignItems: "center", gap: 5, padding: size === "sm" ? "1px 7px 1px 5px" : "2px 9px 2px 6px",
        borderRadius: 999, border: "1px solid var(--jrni-color-surface-border-strong)",
        background: "var(--jrni-color-neutral-6)", color: "var(--jrni-color-neutral-2)",
        fontSize: size === "sm" ? 11 : 12, fontWeight: 600, whiteSpace: "nowrap",
      }}>
        {name === "GitHub Actions" ? <Icons.Github size={size === "sm" ? 11 : 13} /> : <Icons.GitPr size={size === "sm" ? 11 : 13} />}
        {name || "GitHub Actions"}
      </span>
    );
  }
  const initials = (name || "?").split(" ").map(w => w[0]).slice(0, 2).join("");
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: size === "sm" ? 12 : 13, color: "var(--jrni-color-neutral-1)", whiteSpace: "nowrap" }}>
      <Avatar initials={initials} color="var(--jrni-color-tertiary-light-blue)" size={size === "sm" ? 18 : 22} />
      {name}
    </span>
  );
}

/* ─── Module chip list with overflow ───────────────────────────────────── */
function ModuleChips({ modules, max = 3, modName }) {
  const visible = modules.slice(0, max);
  const extra = modules.length - visible.length;
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 4, flexWrap: "nowrap" }}>
      {visible.map(id => (
        <span key={id} style={{
          padding: "1px 7px", borderRadius: 4, fontSize: 11, fontWeight: 500,
          background: "var(--jrni-color-neutral-6)", border: "1px solid var(--jrni-color-surface-border)",
          color: "var(--jrni-color-neutral-2)", fontFamily: "var(--jrni-font-family-mono)", whiteSpace: "nowrap",
        }}>{id}</span>
      ))}
      {extra > 0 ? <span style={{ fontSize: 11, color: "var(--jrni-color-text-soft)", fontWeight: 600 }}>+{extra}</span> : null}
    </span>
  );
}

/* ─── KPI tile ─────────────────────────────────────────────────────────── */
function KpiTile({ label, value, sub, tone = "neutral", icon, active, onClick }) {
  const tones = {
    neutral: "var(--jrni-color-neutral-1)",
    success: "var(--jrni-color-semantic-green-1)",
    warning: "var(--jrni-color-semantic-orange-1)",
    danger:  "var(--jrni-color-semantic-red-1)",
    info:    "var(--jrni-color-semantic-blue-1)",
    primary: "var(--jrni-color-primary-1)",
  };
  return (
    <button onClick={onClick} style={{
      flex: "1 1 0", minWidth: 0, textAlign: "left", cursor: onClick ? "pointer" : "default",
      background: active ? "var(--jrni-color-primary-4)" : "white",
      border: `1px solid ${active ? "var(--jrni-color-primary-1)" : "var(--jrni-color-surface-border)"}`,
      borderRadius: 8, padding: "12px 14px", display: "flex", flexDirection: "column", gap: 4,
      fontFamily: "var(--jrni-font-family-sans)", transition: "border-color 120ms",
    }}>
      <span style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 11, fontWeight: 600, color: "var(--jrni-color-text-soft)", textTransform: "uppercase", letterSpacing: 0.03, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
        {icon ? <span style={{ color: tones[tone], display: "inline-flex" }}>{icon}</span> : null}
        {label}
      </span>
      <span style={{ fontSize: 26, fontWeight: 700, color: tones[tone], lineHeight: 1.1, letterSpacing: -0.5 }}>{value}</span>
      {sub ? <span style={{ fontSize: 11, color: "var(--jrni-color-text-soft)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{sub}</span> : null}
    </button>
  );
}

/* ─── Skeleton ─────────────────────────────────────────────────────────── */
function Skeleton({ w = "100%", h = 12, r = 4, style }) {
  return <span style={{ display: "inline-block", width: w, height: h, borderRadius: r, background: "linear-gradient(90deg, var(--jrni-color-neutral-5) 25%, var(--jrni-color-neutral-6) 50%, var(--jrni-color-neutral-5) 75%)", backgroundSize: "200% 100%", animation: "shimmer 1.4s infinite", ...(style || {}) }} />;
}

/* ─── Segmented control ────────────────────────────────────────────────── */
function Segmented({ options, value, onChange, size = "md" }) {
  return (
    <div style={{ display: "inline-flex", background: "var(--jrni-color-neutral-6)", border: "1px solid var(--jrni-color-surface-border)", borderRadius: 6, padding: 2, gap: 2 }}>
      {options.map(o => {
        const active = value === o.value;
        return (
          <button key={o.value} onClick={() => onChange(o.value)} style={{
            padding: size === "sm" ? "3px 10px" : "5px 12px", borderRadius: 4, border: "none", cursor: "pointer",
            background: active ? "white" : "transparent", color: active ? "var(--jrni-color-neutral-1)" : "var(--jrni-color-text-soft)",
            fontSize: 13, fontWeight: active ? 600 : 500, fontFamily: "var(--jrni-font-family-sans)",
            boxShadow: active ? "var(--jrni-shadow-chrome)" : "none", display: "inline-flex", alignItems: "center", gap: 6, whiteSpace: "nowrap",
          }}>{o.icon}{o.label}</button>
        );
      })}
    </div>
  );
}

/* ─── Mono code / SHA ──────────────────────────────────────────────────── */
function Mono({ children, color }) {
  return <span style={{ fontFamily: "var(--jrni-font-family-mono)", fontSize: 12, color: color || "var(--jrni-color-neutral-2)" }}>{children}</span>;
}

/* ─── Risk indicator ───────────────────────────────────────────────────── */
function RiskBadge({ level }) {
  const map = { low: { tone: "success", label: "Low" }, medium: { tone: "warning", label: "Medium" }, high: { tone: "danger", label: "High" } };
  const m = map[level] || map.low;
  return <Pill tone={m.tone}>{m.label} risk</Pill>;
}

/* ─── Stage status icon (for provisioning / progress) ──────────────────── */
function StageIcon({ status, size = 18 }) {
  if (status === "success") return <span style={{ width: size, height: size, borderRadius: 999, background: "var(--jrni-color-semantic-green-1)", color: "white", display: "grid", placeItems: "center" }}><Icons.Check size={size * 0.6} /></span>;
  if (status === "failed")  return <span style={{ width: size, height: size, borderRadius: 999, background: "var(--jrni-color-semantic-red-1)", color: "white", display: "grid", placeItems: "center" }}><Icons.X size={size * 0.55} /></span>;
  if (status === "running") return <span style={{ width: size, height: size, borderRadius: 999, border: "2px solid var(--jrni-color-semantic-blue-1)", borderTopColor: "transparent", display: "inline-block", animation: "spin 0.8s linear infinite", boxSizing: "border-box" }} />;
  return <span style={{ width: size, height: size, borderRadius: 999, border: "2px solid var(--jrni-color-neutral-4)", display: "inline-block", boxSizing: "border-box" }} />;
}

/* ─── Tooltip (lightweight hover) ──────────────────────────────────────── */
function Tooltip({ label, children, width = 220 }) {
  const [show, setShow] = useState(false);
  return (
    <span style={{ position: "relative", display: "inline-flex" }} onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>
      {children}
      {show && label ? (
        <span style={{ position: "absolute", bottom: "calc(100% + 6px)", left: "50%", transform: "translateX(-50%)", zIndex: 300, background: "var(--jrni-color-neutral-1)", color: "white", fontSize: 11.5, lineHeight: 1.4, padding: "6px 9px", borderRadius: 6, width, textAlign: "center", boxShadow: "var(--jrni-shadow-md)", pointerEvents: "none", fontWeight: 500 }}>{label}</span>
      ) : null}
    </span>
  );
}

/* ─── Claude Code handoff ──────────────────────────────────────────────────
 * The UI never authors platform artifacts and never executes a pipeline.
 * Both cases hand off to a Claude Code session: deep-link + copy the command.
 *  - `path`    → opens a file (platform authoring: module source, release notes)
 *  - `command` → a runnable invocation (execution: provision/rev/migrate/rotate/publish)
 * ─────────────────────────────────────────────────────────────────────────── */
function ClaudeCodeLink({ path, command, label, variant = "link", size = "sm", icon, tone }) {
  const [copied, setCopied] = useState(false);
  const isCmd = !!command;
  const clip = isCmd ? command : ("claude code " + path);
  const deep = isCmd ? ("claude-code://run?cmd=" + encodeURIComponent(command)) : ("claude-code://open?file=" + encodeURIComponent(path));
  const onClick = (e) => {
    e.stopPropagation();
    try { window.location.href = deep; } catch (err) {}
    try { navigator.clipboard && navigator.clipboard.writeText(clip); } catch (err) {}
    setCopied(true); setTimeout(() => setCopied(false), 1800);
  };
  const defaultLabel = isCmd ? "Open in Claude Code" : "Edit in Claude Code";
  const text = copied ? (isCmd ? "Copied command" : "Copied command") : (label || defaultLabel);
  const tip = isCmd ? `Runs in a Claude Code session — click to copy:  ${command}` : `Opens ${path} in Claude Code · click to copy the command`;
  const glyph = icon || <Icons.Terminal size={variant === "button" ? 13 : 12} />;
  if (variant === "button") {
    return (
      <Tooltip label={tip} width={300}>
        <Button variant={tone === "primary" ? "primary" : "secondary"} size={size} icon={copied ? <Icons.Check size={13} /> : glyph} onClick={onClick}>{text}</Button>
      </Tooltip>
    );
  }
  return (
    <Tooltip label={tip} width={300}>
      <button onClick={onClick} style={{ background: "none", border: "none", padding: 0, cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 5, fontSize: 12, fontWeight: 600, color: copied ? "var(--jrni-color-semantic-green-1)" : "var(--jrni-color-primary-1)", fontFamily: "var(--jrni-font-family-sans)" }}>
        {copied ? <Icons.Check size={12} /> : glyph}{text}
      </button>
    </Tooltip>
  );
}

/* ─── Gated handoff (Pattern A — disabled + tooltip when no perm) ───────── */
function GatedButton({ cap, reason, children, ...props }) {
  const allowed = !cap || window.FLEET.can(cap);
  if (allowed) return <Button {...props}>{children}</Button>;
  return (
    <Tooltip label={reason || "You don't have permission to do this. Ask your admin to grant access."} width={240}>
      <span style={{ display: "inline-flex" }}><Button {...props} disabled onClick={undefined}>{children}</Button></span>
    </Tooltip>
  );
}

/* ─── Permission-denied screen (Pattern B) ─────────────────────────────── */
function PermissionDenied({ screen, requiredRole }) {
  return (
    <div style={{ flex: 1, display: "grid", placeItems: "center", padding: 40 }}>
      <div style={{ maxWidth: 420, textAlign: "center" }}>
        <span style={{ width: 56, height: 56, borderRadius: 999, background: "var(--jrni-color-neutral-6)", color: "var(--jrni-color-text-soft)", display: "grid", placeItems: "center", margin: "0 auto 16px" }}><Icons.Lock size={26} /></span>
        <h2 style={{ margin: "0 0 6px", fontSize: 18, fontWeight: 700, color: "var(--jrni-color-neutral-1)" }}>You don't have access to {screen}</h2>
        <p style={{ margin: "0 0 18px", fontSize: 13.5, color: "var(--jrni-color-text-soft)", lineHeight: 1.5 }}>This is a {requiredRole}-only surface. Ask your admin to grant access if you need it.</p>
        <Button variant="primary" icon={<Icons.Gauge size={13} />} onClick={() => navigate("/")}>Back to Fleet Overview</Button>
      </div>
    </div>
  );
}

/* ─── Not-found card (404) ─────────────────────────────────────────────── */
function NotFound({ title, body, actions }) {
  return (
    <div style={{ flex: 1, display: "grid", placeItems: "center", padding: 40 }}>
      <div style={{ maxWidth: 440, textAlign: "center" }}>
        <span style={{ width: 56, height: 56, borderRadius: 999, background: "var(--jrni-color-neutral-6)", color: "var(--jrni-color-text-soft)", display: "grid", placeItems: "center", margin: "0 auto 16px", fontSize: 26, fontWeight: 700 }}>?</span>
        <h2 style={{ margin: "0 0 6px", fontSize: 18, fontWeight: 700, color: "var(--jrni-color-neutral-1)" }}>{title}</h2>
        <p style={{ margin: "0 0 18px", fontSize: 13.5, color: "var(--jrni-color-text-soft)", lineHeight: 1.5 }}>{body}</p>
        <div style={{ display: "flex", gap: 8, justifyContent: "center" }}>{actions}</div>
      </div>
    </div>
  );
}

/* ─── Skeleton helpers ─────────────────────────────────────────────────── */
function SkeletonRows({ n = 8, h = 44 }) {
  return <div style={{ display: "flex", flexDirection: "column" }}>{Array.from({ length: n }, (_, i) => (
    <div key={i} style={{ display: "flex", alignItems: "center", gap: 14, padding: "0 14px", height: h, borderBottom: "1px solid var(--jrni-color-surface-border)" }}>
      <Skeleton w={16} h={16} r={4} /><Skeleton w={140} h={12} /><Skeleton w={90} h={20} r={999} /><Skeleton w={110} h={12} /><div style={{ flex: 1 }} /><Skeleton w={70} h={20} r={999} />
    </div>
  ))}</div>;
}
function SkeletonTile() { return <div style={{ flex: "1 1 0", minWidth: 0, height: 72, border: "1px solid var(--jrni-color-surface-border)", borderRadius: 8, padding: "12px 14px", display: "flex", flexDirection: "column", gap: 8 }}><Skeleton w={70} h={10} /><Skeleton w={44} h={22} /></div>; }

/* Brief boot shimmer so list/detail screens render a skeleton at first paint.
 * Pin it open for design review with ?loading=1. */
function useBoot(params, ms = 520) {
  const [booting, setBooting] = useState(true);
  useEffect(() => {
    if (params && params.loading === "1") { setBooting(true); return; }
    setBooting(true);
    const t = setTimeout(() => setBooting(false), ms);
    return () => clearTimeout(t);
  }, [params && params.loading]);
  return params && params.loading === "1" ? true : booting;
}

/* ─── Export ───────────────────────────────────────────────────────────── */
Object.assign(window, {
  Button, IconBtn, Pill, StatusDot, StatusPill, Chip, Avatar, AvatarStack,
  Field, Input, Textarea, Select, Toggle, Checkbox, Radio,
  Card, SectionLabel, KV, Empty, Tabs, Modal, Drawer, Toast,
  ProgressBar, Popover, MenuItem,
  useOutsideClick,
  // fleet
  DriftPill, VersionChip, CompositionCell, CiPill, TenantStatusPill, ActorChip,
  ModuleChips, KpiTile, Skeleton, Segmented, Mono, RiskBadge, StageIcon,
  // v1.1
  Tooltip, ClaudeCodeLink, GatedButton, PermissionDenied, NotFound, SkeletonRows, SkeletonTile, useBoot,
});
