/* Shared utilities + tiny primitives. Exports to window. */
const { useState, useEffect, useRef, useCallback, useMemo, createContext, useContext } = React;

/* ---- formatting ---- */
function relTime(t) {
  const d = Date.now() - t;
  if (d < 0) return "in " + relTime(Date.now() - (t - Date.now())).replace(" ago", "");
  const s = Math.floor(d / 1000);
  if (s < 45) return "just now";
  const m = Math.floor(s / 60);
  if (m < 60) return m + "m ago";
  const h = Math.floor(m / 60);
  if (h < 24) return h + "h ago";
  const dd = Math.floor(h / 24);
  if (dd < 30) return dd + "d ago";
  const mo = Math.floor(dd / 30);
  if (mo < 12) return mo + "mo ago";
  return Math.floor(mo / 12) + "y ago";
}
function absDate(t) {
  return new Date(t).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
}
function absDateTime(t) {
  return new Date(t).toLocaleString("en-US", { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
}
function dur(ms) {
  const d = Math.floor(ms / 86400000), h = Math.floor((ms % 86400000) / 3600000);
  if (d > 0) return `${d}d ${h}h`;
  const m = Math.floor((ms % 3600000) / 60000);
  return `${h}h ${m}m`;
}
function expiryInfo(t) {
  if (t == null) return { label: "Never expires", cls: "", soon: false, never: true };
  const d = t - Date.now();
  if (d < 0) return { label: "Expired", cls: "danger", soon: true };
  const days = Math.floor(d / 86400000);
  if (days < 14) return { label: `Expires in ${days}d`, cls: "warn", soon: true };
  return { label: `Expires ${absDate(t)}`, cls: "", soon: false };
}

/* ---- store hook ---- */
function useStore() {
  const [, force] = useState(0);
  useEffect(() => HS.subscribe(() => force((n) => n + 1)), []);
  return HS;
}

/* ---- icon component ---- */
function Ic({ name, size, style, className }) {
  return React.createElement("span", {
    className: "ic " + (className || ""),
    style: Object.assign({ display: "inline-flex", width: (size || 18) + "px", height: (size || 18) + "px" }, style),
    dangerouslySetInnerHTML: { __html: window.Icon(name, size || 18) },
  });
}

/* ---- refresh button (per-view; re-hydrates live, syncs demo) ---- */
function RefreshBtn({ label = "Refresh" }) {
  const s = useStore();
  return (
    <button className="btn sm" disabled={s.loading} onClick={() => HS.refresh()} title="Re-fetch from the control server">
      <Ic name="refresh" size={14} className={s.loading ? "spin" : ""} />{label}
    </button>
  );
}

/* ---- avatar ---- */
function Avatar({ user, size = 28 }) {
  const initials = (user.displayName || user.name).split(/[\s-]/).map((w) => w[0]).slice(0, 2).join("").toUpperCase();
  return (
    <span className="avatar-c" style={{ width: size, height: size, background: user.color, fontSize: size * 0.4 }}>
      {initials}
    </span>
  );
}

/* ---- copy to clipboard ---- */
function CopyBtn({ text, label }) {
  const [done, setDone] = useState(false);
  const toast = useToast();
  const onClick = (e) => {
    e.stopPropagation();
    navigator.clipboard?.writeText(text).catch(() => {});
    setDone(true);
    setTimeout(() => setDone(false), 1300);
    toast && toast.push({ kind: "success", title: "Copied", msg: label || text });
  };
  return (
    <span className="copy-btn" onClick={onClick} title="Copy">
      <Ic name={done ? "check" : "copy"} size={14} />
    </span>
  );
}

/* ---- OS / device icon ---- */
function osIcon(kind, os) {
  if (kind === "phone") return "phone";
  if (kind === "docker") return "docker";
  if (kind === "pi") return "raspberry";
  if (kind === "server") return "server";
  if (kind === "laptop") return os === "windows" ? "laptop" : "laptop";
  return "laptop";
}
function osLabel(os) {
  return ({ macos: "macOS", ios: "iOS", linux: "Linux", windows: "Windows", android: "Android" })[os] || os;
}

/* register method enum → label (the real ownership/provenance signal) */
function regMethodLabel(rm) {
  return ({ REGISTER_METHOD_AUTH_KEY: "Pre-auth key", REGISTER_METHOD_CLI: "CLI", REGISTER_METHOD_OIDC: "OIDC", REGISTER_METHOD_UNSPECIFIED: "—" })[rm] || "—";
}
function regMethodShort(rm) {
  return ({ REGISTER_METHOD_AUTH_KEY: "Key", REGISTER_METHOD_CLI: "CLI", REGISTER_METHOD_OIDC: "OIDC" })[rm] || "—";
}

/* owner cell: tag-owned nodes show tag chips; user-owned show the user */
function OwnerCell({ m, size = 22 }) {
  if (m.tags && m.tags.length) {
    return (
      <span style={{ display: "inline-flex", alignItems: "center", gap: 5, flexWrap: "wrap" }}>
        {m.tags.map((t) => <span key={t} className="tag">{t}</span>)}
      </span>
    );
  }
  const u = HS.userById(m.user);
  if (!u) return <span style={{ color: "var(--text-faint)" }}>—</span>;
  return <span style={{ display: "inline-flex", alignItems: "center", gap: 7 }}><Avatar user={u} size={size} /><span style={{ fontSize: 13 }}>{u.name}</span></span>;
}

/* ---- dropdown menu (click outside to close) ---- */
function Menu({ trigger, children, align = "right", width }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    if (!open) return;
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", h);
    document.addEventListener("keydown", (e) => e.key === "Escape" && setOpen(false));
    return () => document.removeEventListener("mousedown", h);
  }, [open]);
  return (
    <span ref={ref} style={{ position: "relative", display: "inline-flex" }}>
      <span onClick={(e) => { e.stopPropagation(); setOpen((o) => !o); }}>{trigger}</span>
      {open && (
        <div className="menu" style={{ top: "calc(100% + 5px)", [align]: 0, minWidth: width }} onClick={() => setOpen(false)}>
          {children}
        </div>
      )}
    </span>
  );
}
function MenuItem({ icon, children, onClick, danger, meta }) {
  return (
    <div className={"menu-item" + (danger ? " danger" : "")} onClick={(e) => { e.stopPropagation(); onClick && onClick(e); }}>
      {icon && <Ic name={icon} size={15} />}
      <span>{children}</span>
      {meta && <span className="meta">{meta}</span>}
    </div>
  );
}

/* ---- toast system ---- */
const ToastCtx = createContext(null);
function useToast() { return useContext(ToastCtx); }
function ToastProvider({ children }) {
  const [toasts, setToasts] = useState([]);
  const push = useCallback((t) => {
    const id = Math.random().toString(36).slice(2);
    setToasts((arr) => [...arr, { id, ...t }]);
    setTimeout(() => setToasts((arr) => arr.filter((x) => x.id !== id)), t.duration || 3600);
  }, []);
  const remove = (id) => setToasts((arr) => arr.filter((x) => x.id !== id));
  const api = useMemo(() => ({ push }), [push]);
  useEffect(() => HS.onNotify((t) => push({ kind: t.kind, title: t.title, msg: t.msg })), [push]);
  const iconFor = (k) => k === "error" ? "warn" : k === "success" ? "check" : "info";
  return (
    <ToastCtx.Provider value={api}>
      {children}
      <div className="toasts">
        {toasts.map((t) => (
          <div key={t.id} className={"toast " + (t.kind || "info")}>
            <span className="t-icon"><Ic name={iconFor(t.kind)} size={18} /></span>
            <div className="t-body"><b>{t.title}</b>{t.msg && <span>{t.msg}</span>}</div>
            <span className="t-x" onClick={() => remove(t.id)}><Ic name="x" size={14} /></span>
          </div>
        ))}
      </div>
    </ToastCtx.Provider>
  );
}

/* ---- modal shell ---- */
function Modal({ title, subtitle, icon, iconDanger, onClose, children, footer, size }) {
  useEffect(() => {
    const h = (e) => e.key === "Escape" && onClose();
    document.addEventListener("keydown", h);
    return () => document.removeEventListener("keydown", h);
  }, [onClose]);
  return (
    <div className="overlay" onMouseDown={(e) => e.target === e.currentTarget && onClose()}>
      <div className={"modal " + (size || "")}>
        <div className="modal-head">
          {icon && <div className={"mh-icon" + (iconDanger ? " danger" : "")}><Ic name={icon} size={18} /></div>}
          <div className="mh-text">
            <h3>{title}</h3>
            {subtitle && <p>{subtitle}</p>}
          </div>
          <span className="x" onClick={onClose}><Ic name="x" size={17} /></span>
        </div>
        <div className="modal-body">{children}</div>
        {footer && <div className="modal-foot">{footer}</div>}
      </div>
    </div>
  );
}

/* ---- confirm dialog ---- */
function ConfirmModal({ title, subtitle, body, confirmLabel, danger, onConfirm, onClose }) {
  return (
    <Modal title={title} subtitle={subtitle} icon={danger ? "warn" : "info"} iconDanger={danger} onClose={onClose}
      footer={<>
        <div className="spacer" />
        <button className="btn" onClick={onClose}>Cancel</button>
        <button className={"btn " + (danger ? "danger" : "primary")} onClick={() => { onConfirm(); onClose(); }}>{confirmLabel || "Confirm"}</button>
      </>}>
      {body}
    </Modal>
  );
}

/* ---- tiny sparkline ---- */
function Sparkline({ data, w = 60, h = 22, color = "var(--accent)" }) {
  const max = Math.max(...data), min = Math.min(...data);
  const rng = max - min || 1;
  const pts = data.map((v, i) => `${(i / (data.length - 1)) * w},${h - ((v - min) / rng) * (h - 2) - 1}`).join(" ");
  return (
    <svg className="spark" width={w} height={h} viewBox={`0 0 ${w} ${h}`} fill="none">
      <polyline points={pts} stroke={color} strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" opacity="0.85" />
    </svg>
  );
}

Object.assign(window, {
  relTime, absDate, absDateTime, dur, expiryInfo, useStore, Ic, Avatar, CopyBtn,
  osIcon, osLabel, regMethodLabel, regMethodShort, OwnerCell, RefreshBtn, Menu, MenuItem, ToastProvider, useToast, Modal, ConfirmModal, Sparkline,
});
