// Bylt Capital CRM — shared building blocks: icons, pills, sheets, form fields.
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ── Icons (1.6px line, 24×24) ───────────────────────────────────────────────
const ICON_PATHS = {
  plus:     <path d="M12 5v14M5 12h14" />,
  search:   <><circle cx="11" cy="11" r="6" /><path d="M20 20l-4.5-4.5" /></>,
  x:        <path d="M6 6l12 12M18 6L6 18" />,
  back:     <path d="M15 5l-7 7 7 7" />,
  upload:   <path d="M12 20V8m0 0l-4 4m4-4l4 4M5 4h14" />,
  note:     <path d="M21 12c0 4.5-4 8-9 8a10 10 0 01-4-.8L3 20l1-4.5A8 8 0 0112 4c5 0 9 3.5 9 8z" />,
  offer:    <><path d="M3 12l9-9 9 9-9 9z" /><path d="M9.5 12h5M12 9.5v5" /></>,
  edit:     <><path d="M4 20h4L19 9l-4-4L4 16z" /><path d="M13.5 6.5l4 4" /></>,
  trash:    <path d="M4 7h16M10 7V4h4v3M6 7l1 13h10l1-13M10 11v5M14 11v5" />,
  phone:    <path d="M5 4h4l2 5-2.5 1.5a11 11 0 005 5L15 13l5 2v4a2 2 0 01-2 2A16 16 0 013 6a2 2 0 012-2z" />,
  mail:     <><rect x="3" y="5" width="18" height="14" rx="2" /><path d="M3 7l9 6 9-6" /></>,
  sms:      <><path d="M4 4h16a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H9l-5 4v-4H4a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1z" /><path d="M8 10h.01M12 10h.01M16 10h.01" /></>,
  file:     <><path d="M14 3H7a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 002-2V8z" /><path d="M14 3v5h5" /></>,
  user:     <><circle cx="12" cy="8" r="3.5" /><path d="M5 20c1-3.5 4-5 7-5s6 1.5 7 5" /></>,
  flag:     <path d="M5 21V4m0 0h11l-2 4 2 4H5" />,
  check:    <path d="M5 12l4 4 10-10" />,
  spark:    <path d="M12 3v4M12 17v4M3 12h4M17 12h4M5.6 5.6l2.8 2.8M15.6 15.6l2.8 2.8M5.6 18.4l2.8-2.8M15.6 8.4l2.8-2.8" />,
  copy:     <><rect x="8" y="8" width="12" height="12" rx="2" /><path d="M16 8V6a2 2 0 00-2-2H6a2 2 0 00-2 2v8a2 2 0 002 2h2" /></>,
  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" /></>,
  swap:     <path d="M4 8h14l-3-3M20 16H6l3 3" />,
  deals:    <path d="M4 6h16M4 12h11M4 18h7" />,
  funnel:   <path d="M4 5h16l-6 7.5V19l-4-2v-4.5z" />,
  users:    <><circle cx="9" cy="8" r="3.2" /><path d="M3 20c.8-3.2 3.2-4.8 6-4.8s5.2 1.6 6 4.8" /><path d="M16 5.2a3.2 3.2 0 010 5.6M18 15.6c1.6.7 2.6 2.1 3 4.4" /></>,
  menu:     <path d="M4 7h16M4 12h16M4 17h16" />,
  chev:     <path d="M9 5l7 7-7 7" />,
  chevdown: <path d="M6 9l6 6 6-6" />,
  download: <path d="M12 4v12m0 0l-4-4m4 4l4-4M5 20h14" />,
  tag:      <><path d="M3 12V4h8l9 9-8 8-9-9z" /><circle cx="7.5" cy="8.5" r="1.3" /></>,
  home:     <path d="M4 11l8-7 8 7v8a1 1 0 01-1 1h-4v-6h-6v6H5a1 1 0 01-1-1z" />,
  bell:     <><path d="M6 16v-5a6 6 0 1112 0v5l1.5 2h-15z" /><path d="M10 21a2 2 0 004 0" /></>,
  cake:     <><path d="M4 20h16M5 20v-7h14v7M5 16c1.2 1 2.3 1 3.5 0s2.3-1 3.5 0 2.3 1 3.5 0 2.3-1 3.5 0M12 13V9" /><path d="M12 6.2c.9-.9.9-1.8 0-2.7-.9.9-.9 1.8 0 2.7z" /></>,
  alert:    <><path d="M12 4l9 16H3z" /><path d="M12 10v4M12 17h.01" /></>,
  clock:    <><circle cx="12" cy="12" r="8.5" /><path d="M12 7.5V12l3 2" /></>,
  link:     <path d="M10 14a4 4 0 005.7 0l3-3A4 4 0 0013 5.3l-1 1M14 10a4 4 0 00-5.7 0l-3 3A4 4 0 0011 18.7l1-1" />,
  pin:      <><path d="M12 21s-6.5-5.9-6.5-11.2a6.5 6.5 0 0 1 13 0C18.5 15.1 12 21 12 21z" /><circle cx="12" cy="9.8" r="2.4" /></>,
  lunch:    <><path d="M7 3v7a2 2 0 0 0 2 2h0a2 2 0 0 0 2-2V3M9 12v9M17 21V3c-2.2 1.2-3 3.6-3 6.5V13h3" /></>,
  dollar:   <><path d="M12 3.5v17" /><path d="M16 7.5c0-1.7-1.8-3-4-3s-4 1.3-4 3 1.8 2.6 4 3 4 1.4 4 3.2-1.8 3.3-4 3.3-4-1.3-4-3" /></>,
};
// What the signed-in person may do: sent by the server with GET /api/me (src/roles.js).
const canOf = (u) => (u && u.can) || {};
function Icon({ name, size = 16 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
         strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      {ICON_PATHS[name] || null}
    </svg>
  );
}

// ── Status vocab ────────────────────────────────────────────────────────────
// The products (the homepage, the portal and offer links use the same keys).
const PRODUCT_OPTIONS = [
  { value: "mca", label: "Merchant Cash Advance" }, { value: "loc", label: "Line of Credit" }, { value: "eq", label: "Equipment Financing" }, { value: "rbf", label: "Revenue-Based Financing" },
  { value: "tl", label: "Term Loan" }, { value: "sba", label: "SBA Loan" }, { value: "inv", label: "Invoice Factoring" },
];
const DEAL_STATUSES = [
  { id: "new", label: "New" },
  { id: "submitted", label: "Submitted" },
  { id: "review", label: "Review" },
  { id: "funded", label: "Funded" },
  { id: "declined", label: "Declined" },
];
const SUB_STATUSES = [
  { id: "offer", label: "Offer" },
  { id: "approved", label: "Approved" },
  { id: "pending", label: "Pending" },
  { id: "counter", label: "Counter" },
  { id: "declined", label: "Declined" },
  { id: "funded", label: "Funded" },          // set only by Mark funded
];
const statusLabel = (list, id) => (list.find(s => s.id === id) || {}).label || fmt.title(id || "New");

function Pill({ status, label, plain }) {
  const key = String(status || "").toLowerCase().replace(/[^a-z]/g, "");
  return <span className={`pill s-${key}${plain ? " plain" : ""}`}>{label || fmt.title(status)}</span>;
}
// When the client last opened their offer link, and how: the offer email or the texted link (DEC-094).
// Solid while it's fresh (the last 24 hours), so the reps see who to call now.
function SeenChip({ at, via, views, long, count }) {
  if (!at) return null;
  const email = via === "email", fresh = Date.now() - new Date(at).getTime() < 864e5;
  const title = `Last opened ${fmt.when(at).replace(/^(Today|Yesterday)/, (m) => m.toLowerCase())} from the ${email ? "email" : "text"}${views ? ` · opened ${views} ${views === 1 ? "time" : "times"}` : ""}`;
  return (
    <span className={`seen ${email ? "v-email" : "v-text"}${fresh ? " fresh" : ""}`} title={title}>
      <Icon name={email ? "mail" : "sms"} size={13} />{long ? "Last opened " : count && views > 1 ? `Opened ${views}× · ` : "Opened "}{fmt.ago(at)}{long ? ` · from the ${email ? "email" : "text"}` : ""}
    </span>
  );
}
function Avatar({ name, size, square }) {
  return <span className={`avatar${size ? " " + size : ""}${square ? " square" : ""}`} aria-hidden="true">{fmt.initials(name)}</span>;
}
function Brand({ size, onLight }) {
  return (
    <span className={`brand${size ? " " + size : ""}${onLight ? " on-light" : ""}`}>
      <img className="brand-logo" src={onLight ? "/brand/bylt-logo-light.svg" : "/brand/bylt-logo-onblue.svg"} alt="Bylt Capital" />
    </span>
  );
}
function Empty({ title, children, action }) {
  return (
    <div className="empty">
      <h3>{title}</h3>
      {children ? <p>{children}</p> : null}
      {action || null}
    </div>
  );
}

// ── Form parts ──────────────────────────────────────────────────────────────
// Label and control are tied by id, so the control's accessible name is just
// the label text (not the hint, and not a select's current choice).
function Field({ label, hint, full, children }) {
  const id = React.useId();
  const kids = React.Children.toArray(children);
  const control = React.isValidElement(kids[0]) ? React.cloneElement(kids[0], { id }) : kids[0];
  return (
    <div className={`field${full ? " full" : ""}`}>
      <label className="label" htmlFor={id}>{label}</label>
      {control}
      {kids.slice(1)}
      {hint ? <div className="hint">{hint}</div> : null}
    </div>
  );
}
function TextInput({ value, onChange, ...rest }) {
  return <input className="input" value={value == null ? "" : value} onChange={e => onChange(e.target.value)} {...rest} />;
}
function Select({ value, onChange, options, ...rest }) {
  return (
    <select className="select" value={value == null ? "" : value} onChange={e => onChange(e.target.value)} {...rest}>
      {options.map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
    </select>
  );
}
// A term box with its unit: business days or weeks.
const TERM_UNITS = [{ value: "days", label: "Days" }, { value: "weeks", label: "Weeks" }];
const TERM_UNITS_MONTHS = TERM_UNITS.concat([{ value: "months", label: "Months" }]);
function TermInput({ value, unit, onChange, onUnit, months }) {
  return (
    <div className="term-input">
      <input className="input" value={value == null ? "" : value} onChange={e => onChange(e.target.value)} inputMode="numeric" aria-label="Term" />
      <select className="select" value={unit || "days"} onChange={e => onUnit(e.target.value)} aria-label="Term unit">
        {(months ? TERM_UNITS_MONTHS : TERM_UNITS).map(o => <option key={o.value} value={o.value}>{o.label}</option>)}
      </select>
    </div>
  );
}
function Segmented({ value, onChange, options, label }) {
  return (
    <div className="seg" role="group" aria-label={label}>
      {options.map(o => (
        <button type="button" key={o.id} aria-pressed={value === o.id} onClick={() => onChange(o.id)}>{o.label}</button>
      ))}
    </div>
  );
}
function Switch({ checked, onChange, label }) {
  return (
    <span className="switch">
      <input type="checkbox" checked={!!checked} onChange={e => onChange(e.target.checked)} aria-label={label} />
      <i />
    </span>
  );
}

// Small state helper for forms: [values, set(field, value), setAll]
function useForm(initial) {
  const [values, setValues] = useState(initial);
  const set = useCallback((k, v) => setValues(prev => ({ ...prev, [k]: v })), []);
  return [values, set, setValues];
}

// Renders its children at the top of the page, outside whatever panel the
// caller sits in. A sheet opened from inside the deal file needs this: on some
// browsers a fixed-position element inside that panel is sized to the panel.
const Portal = ({ children }) => ReactDOM.createPortal(children, document.body);

// ── Sheet: slides in from the right, rises from the bottom on phones ────────
function Sheet({ eyebrow, title, onClose, onSubmit, submitLabel, busyLabel, busy, error, children, danger }) {
  const ref = useRef(null);
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape" && !busy) onClose(); };
    document.addEventListener("keydown", onKey);
    const prev = document.activeElement;
    const first = ref.current && (ref.current.querySelector("input:not([type=hidden]):not([type=file]):not(:disabled), textarea:not(:disabled), select:not(:disabled)") || ref.current.querySelector(".sheet-head .icon-btn"));
    if (first) first.focus();
    return () => { document.removeEventListener("keydown", onKey); if (prev && prev.focus) prev.focus(); };
  }, []);
  return (
    <>
      <div className="scrim" onClick={() => !busy && onClose()} />
      <form className="sheet" role="dialog" aria-modal="true" aria-label={title} ref={ref}
            onSubmit={e => { e.preventDefault(); if (!busy && onSubmit) onSubmit(); }}>
        <div className="sheet-head">
          <div>
            {eyebrow ? <div className="label">{eyebrow}</div> : null}
            <h2>{title}</h2>
          </div>
          <button type="button" className="icon-btn" onClick={onClose} aria-label="Close"><Icon name="x" /></button>
        </div>
        <div className="sheet-body">{children}</div>
        <div className="sheet-foot">
          {error ? <span className="err" role="alert">{error}</span> : null}
          {danger || null}
          <button type="button" className="btn" onClick={onClose} disabled={busy}>{onSubmit ? "Cancel" : "Close"}</button>
          {onSubmit ? <button type="submit" className="btn btn-primary" disabled={busy}>{busy ? busyLabel || "Saving…" : submitLabel}</button> : null}
        </div>
      </form>
    </>
  );
}

// ── Confirm dialog and toast (app-level, driven through window.ui) ──────────
function ConfirmDialog({ title, body, confirmLabel, onConfirm, onCancel, plain }) {
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");
  useEffect(() => {
    const onKey = (e) => { if (e.key === "Escape" && !busy) onCancel(); };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [busy]);
  const go = async () => {
    setBusy(true); setError("");
    try { await onConfirm(); } catch (err) { setError(err.message); setBusy(false); }
  };
  return (
    <>
      <div className="scrim top" onClick={() => !busy && onCancel()} />
      <div className="dialog" role="alertdialog" aria-modal="true" aria-label={title}>
        <h2>{title}</h2>
        <p>{body}</p>
        {error ? <p className="banner" style={{ marginTop: 12 }} role="alert">{error}</p> : null}
        <div className="acts">
          <button className="btn" onClick={onCancel} disabled={busy} autoFocus>Cancel</button>
          <button className="btn btn-primary" style={plain ? null : { background: "var(--st-declined)", borderColor: "var(--st-declined)" }} onClick={go} disabled={busy}>
            {busy ? "Working…" : confirmLabel}
          </button>
        </div>
      </div>
    </>
  );
}

// Runs an async save for a sheet: tracks busy + error, closes on success.
function useSave(onDone) {
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");
  const run = async (fn) => {
    setBusy(true); setError("");
    try { const out = await fn(); onDone && onDone(out); }
    catch (err) { setError(err.quiet ? "" : err.message || "Something went wrong."); setBusy(false); }   // quiet: the form shows its own message
  };
  return { busy, error, setError, run };
}

// If one screen hits a record it can't draw, show a way out instead of a blank page.
class ErrorBoundary extends React.Component {
  constructor(props) { super(props); this.state = { failed: false }; }
  static getDerivedStateFromError() { return { failed: true }; }
  componentDidCatch(error) { console.error(error); }
  componentDidUpdate(prev) { if (prev.resetKey !== this.props.resetKey && this.state.failed) this.setState({ failed: false }); }
  render() {
    if (!this.state.failed) return this.props.children;
    return (
      <main className="workspace single"><div className="page">
        <div className="card"><Empty title="This screen couldn't be shown"
          action={<button className="btn btn-primary" onClick={() => location.reload()}>Reload</button>}>
          One of the records here has data this page can't display. Reload to try again; if it keeps happening, open a different section and tell your Admin which record you were on.
        </Empty></div>
      </div></main>
    );
  }
}

Object.assign(window, {
  ErrorBoundary, Portal, Icon, Pill, SeenChip, Avatar, Brand, Empty, Field, TextInput, TermInput, Select, Segmented, Switch, Sheet, ConfirmDialog,
  useForm, useSave, DEAL_STATUSES, SUB_STATUSES, statusLabel, PRODUCT_OPTIONS, canOf,
});
