/* Gravity CMS — shared UI primitives + icon set. Exported to window. */

// ---- Icons (Lucide-style line icons, MIT) ------------------------------
const ICONS = {
  shirt: '<path d="M20.38 3.46 16 2a4 4 0 0 1-8 0L3.62 3.46a2 2 0 0 0-1.34 2.23l.58 3.47a1 1 0 0 0 .99.84H6v10c0 1.1.9 2 2 2h8a2 2 0 0 0 2-2V10h2.15a1 1 0 0 0 .99-.84l.58-3.47a2 2 0 0 0-1.34-2.23z"/>',
  repeat: '<path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/>',
  upload: '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/>',
  scissors: '<circle cx="6" cy="6" r="3"/><path d="M8.12 8.12 12 12"/><path d="M20 4 8.12 15.88"/><circle cx="6" cy="18" r="3"/><path d="M14.8 14.8 20 20"/>',
  sparkles: '<path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/>',
  dashboard: '<rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/>',
  chevronRight: '<path d="m9 18 6-6-6-6"/>',
  chevronDown: '<path d="m6 9 6 6 6-6"/>',
  chevronLeft: '<path d="m15 18-6-6 6-6"/>',
  chevronsUpDown: '<path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/>',
  check: '<path d="M20 6 9 17l-5-5"/>',
  checkCircle: '<path d="M21.801 10A10 10 0 1 1 17 3.335"/><path d="m9 11 3 3L22 4"/>',
  alert: '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
  clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
  package: '<path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z"/><path d="M3.3 7 12 12l8.7-5"/><path d="M12 22V12"/>',
  search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
  user: '<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
  users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
  userX: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><line x1="17" x2="22" y1="8" y2="13"/><line x1="22" x2="17" y1="8" y2="13"/>',
  x: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
  plus: '<path d="M5 12h14"/><path d="M12 5v14"/>',
  ruler: '<path d="M21.3 8.7 8.7 21.3a1 1 0 0 1-1.4 0l-4.6-4.6a1 1 0 0 1 0-1.4L15.3 2.7a1 1 0 0 1 1.4 0l4.6 4.6a1 1 0 0 1 0 1.4Z"/><path d="m7.5 10.5 2 2"/><path d="m10.5 7.5 2 2"/><path d="m13.5 4.5 2 2"/><path d="m4.5 13.5 2 2"/>',
  flag: '<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" x2="4" y1="22" y2="15"/>',
  bag: '<path d="M6 2 3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"/><path d="M3 6h18"/><path d="M16 10a4 4 0 0 1-8 0"/>',
  swap: '<path d="M8 3 4 7l4 4"/><path d="M4 7h16"/><path d="m16 21 4-4-4-4"/><path d="M20 17H4"/>',
  filter: '<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/>',
};

function Icon({ name, size = 18, strokeWidth = 2, style, className }) {
  return (
    <svg
      width={size} height={size} viewBox="0 0 24 24" fill="none"
      stroke="currentColor" strokeWidth={strokeWidth}
      strokeLinecap="round" strokeLinejoin="round"
      className={className} style={style} aria-hidden="true"
      dangerouslySetInnerHTML={{ __html: ICONS[name] || "" }}
    />
  );
}

// ---- Status / decision config -----------------------------------------
const DECISION_META = {
  fits:     { label: "Fits",      tone: "good",   icon: "check" },
  alter:    { label: "Alteration",tone: "info",   icon: "scissors" },
  exchange: { label: "Exchange",  tone: "warn",   icon: "repeat" },
  missing:  { label: "Missing",   tone: "bad",    icon: "flag" },
};

const EXCHANGE_STATUS = {
  new:         { label: "New",          tone: "neutral" },
  ordered:     { label: "Ordered",      tone: "good" },
  backordered: { label: "Backordered",  tone: "warn" },
  resolved:    { label: "Resolved",     tone: "muted" },
};

// ---- Primitives --------------------------------------------------------
function Badge({ tone = "neutral", children, soft = true, dot = false, style }) {
  return (
    <span className={"badge badge-" + tone + (soft ? " soft" : "")} style={style}>
      {dot && <span className="badge-dot" />}
      {children}
    </span>
  );
}

function Pill({ children, style }) {
  return <span className="pill" style={style}>{children}</span>;
}

function Button({ children, variant = "default", size = "md", icon, onClick, disabled, style, title }) {
  return (
    <button
      className={"btn btn-" + variant + " btn-" + size}
      onClick={onClick} disabled={disabled} style={style} title={title}
    >
      {icon && <Icon name={icon} size={size === "sm" ? 15 : 17} />}
      {children && <span>{children}</span>}
    </button>
  );
}

function Card({ children, className = "", style, onClick }) {
  return (
    <div className={"card " + className} style={style} onClick={onClick}>{children}</div>
  );
}

// StatRing lived here until #74 dropped the Try-Ons progress ring — its only
// consumer app-wide. Removed with the ticket rather than left as an orphan
// primitive; the inline ProgressBar plus a "1/9" readout says it once.

function ProgressBar({ value, total, tone = "accent", height = 6 }) {
  const pct = total ? Math.round((value / total) * 100) : 0;
  return (
    <div className="progress" style={{ height }}>
      <div className="progress-fill" style={{ width: pct + "%", background: `var(--ring-${tone})` }} />
    </div>
  );
}

function Empty({ icon = "checkCircle", title, sub }) {
  return (
    <div className="empty">
      <div className="empty-icon"><Icon name={icon} size={26} /></div>
      <div className="empty-title">{title}</div>
      {sub && <div className="empty-sub">{sub}</div>}
    </div>
  );
}

// #24 (second sighting): scrolling the page behind the scrim re-rasterizes
// its backdrop-filter — a white flash the July 18 compositor pin couldn't
// stop. The page scroller is .main (the shell itself is height-locked), so
// freeze it while any modal is mounted; the counter survives overlapping
// mounts during modal swaps.
let openModalCount = 0;
function useMainScrollLock() {
  React.useEffect(() => {
    const main = document.querySelector(".main");
    if (!main) return undefined;
    openModalCount++;
    main.style.overflowY = "hidden";
    return () => {
      openModalCount--;
      if (openModalCount === 0) main.style.overflowY = "";
    };
  }, []);
}

function Modal({ title, onClose, children, footer, width = 460 }) {
  useMainScrollLock();
  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" style={{ width }} onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div className="modal-title">{title}</div>
          <button className="icon-btn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body">{children}</div>
        {footer && <div className="modal-foot">{footer}</div>}
      </div>
    </div>
  );
}

function Field({ label, children, hint }) {
  return (
    <label className="field">
      <span className="field-label">{label}</span>
      {children}
      {hint && <span className="field-hint">{hint}</span>}
    </label>
  );
}

// ---- date helpers ------------------------------------------------------
function ageInDays(dateStr, today) {
  const a = new Date(dateStr + "T12:00:00");
  const b = new Date(today + "T12:00:00");
  return Math.round((b - a) / 86400000);
}
// #71: the same three-line lookup was written four times across tryon.jsx and
// manager.jsx (twice within manager.jsx alone). Both fall back to the raw id
// rather than an empty string or a dash: an id on screen is ugly but findable
// in the Sheet, which is exactly what you want when a row points at something
// the roster no longer has.
function dancerName(state, id) {
  const d = state.dancers.find((x) => x.id === id);
  return d ? d.name : id;
}
function routineName(state, id) {
  const r = state.routines.find((x) => x.id === id);
  return r ? r.name : id;
}

function fmtDate(dateStr) {
  if (!dateStr) return "—";
  const d = new Date(dateStr + "T12:00:00");
  return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
}
// Full ISO stamps (Units UpdatedAt, #55) must render in the DEVICE's zone —
// slicing the UTC date shifts every evening hand-out to the next day.
function fmtStamp(iso) {
  if (!iso) return "—";
  const d = new Date(iso);
  return isNaN(d) ? "—" : d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
}

Object.assign(window, {
  Icon, ICONS, DECISION_META, EXCHANGE_STATUS,
  Badge, Pill, Button, Card, ProgressBar, Empty, Modal, Field,
  ageInDays, fmtDate, fmtStamp, dancerName, routineName,
});
