// components.jsx — shared UI building blocks + icons
// Exposes globals: Icon, Pill, Card, KPI, Toolbar, FauxReceipt, PDFTile, Modal,
//   fmt, fmtSigned, fmtShort, fmtDate, fmtDateLong, monthOf, dotFromCat, useLocal

const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ────────────────────────────────────────────────────────────────────────────
// Formatting helpers
// ────────────────────────────────────────────────────────────────────────────
function fmt(n, withCents = true) {
  if (n == null || isNaN(n)) return "—";
  const sign = n < 0 ? "−" : "";
  const abs = Math.abs(n);
  const opts = withCents
    ? { minimumFractionDigits: 2, maximumFractionDigits: 2 }
    : { minimumFractionDigits: 0, maximumFractionDigits: 0 };
  return sign + "$" + abs.toLocaleString("en-US", opts);
}
function fmtSigned(n, withCents = true) {
  if (n == null || isNaN(n)) return "—";
  if (n === 0) return "$0" + (withCents ? ".00" : "");
  return (n > 0 ? "+" : "") + fmt(n, withCents).replace("+", "");
}
function fmtShort(n) {
  if (n == null || isNaN(n)) return "—";
  const sign = n < 0 ? "−" : "";
  const abs = Math.abs(n);
  if (abs >= 1000) return sign + "$" + (abs / 1000).toFixed(1) + "k";
  return sign + "$" + Math.round(abs);
}
function parseDate(isoStr) {
  if (!isoStr) return null;
  const [y, m, d] = isoStr.split("-").map(Number);
  return new Date(y, m - 1, d);
}
function fmtDate(isoStr) {
  const dt = parseDate(isoStr);
  if (!dt) return "—";
  return dt.toLocaleString("en-US", { month: "short", day: "numeric" });
}
function fmtDateLong(isoStr) {
  const dt = parseDate(isoStr);
  if (!dt) return "—";
  return dt.toLocaleString("en-US", { weekday: "short", month: "short", day: "numeric", year: "numeric" });
}
function monthOf(isoStr) {
  return isoStr ? isoStr.slice(0, 7) : "";
}
function monthLabel(yyyymm) {
  if (!yyyymm) return "All months";
  const [y, m] = yyyymm.split("-").map(Number);
  return new Date(y, m - 1, 1).toLocaleString("en-US", { month: "long", year: "numeric" });
}

// ────────────────────────────────────────────────────────────────────────────
// Icons (16px line, single-color via currentColor)
// ────────────────────────────────────────────────────────────────────────────
function Icon({ name, size = 16, className = "" }) {
  const paths = ICONS[name];
  if (!paths) return null;
  return (
    <svg
      className={"ic " + className}
      width={size} height={size}
      viewBox="0 0 16 16" fill="none"
      stroke="currentColor" strokeWidth="1.4"
      strokeLinecap="round" strokeLinejoin="round"
      aria-hidden="true"
    >
      {paths}
    </svg>
  );
}
const ICONS = {
  forecast: <>
    <path d="M2 12.5l3.5-4 3 2.5L13 5" />
    <path d="M9.5 5H13v3.5" />
    <path d="M2 14h12" />
  </>,
  list: <>
    <path d="M3 4h10M3 8h10M3 12h7" />
  </>,
  link: <>
    <path d="M6.5 9.5l3-3" />
    <path d="M7 4.5l1.5-1.5a2.5 2.5 0 0 1 3.5 3.5L10.5 8" />
    <path d="M9 11.5l-1.5 1.5a2.5 2.5 0 0 1-3.5-3.5L5.5 8" />
  </>,
  receipt: <>
    <path d="M3.5 2.5h9V14l-1.5-1-1.5 1-1.5-1-1.5 1-1.5-1L3.5 14V2.5z" />
    <path d="M5.5 5.5h5M5.5 8h5M5.5 10.5h3" />
  </>,
  diagnostics: <>
    <circle cx="5" cy="8" r="2" />
    <circle cx="11" cy="8" r="2" />
    <path d="M5 6V3M11 6V3M5 10v3M11 10v3" />
  </>,
  schedule: <>
    <rect x="2.5" y="3.5" width="11" height="10" rx="1" />
    <path d="M2.5 6.5h11M5.5 2v3M10.5 2v3" />
  </>,
  search: <>
    <circle cx="7" cy="7" r="4" />
    <path d="M10 10l3 3" />
  </>,
  alert: <>
    <path d="M8 2.5L14 13H2L8 2.5z" />
    <path d="M8 6.5V9" />
    <circle cx="8" cy="11" r=".5" fill="currentColor" stroke="none" />
  </>,
  check: <path d="M3 8.5l3 3 7-7" />,
  x: <path d="M3.5 3.5l9 9M12.5 3.5l-9 9" />,
  pencil: <>
    <path d="M11 2.5l2.5 2.5L6 12.5l-3 .5.5-3L11 2.5z" />
  </>,
  arrowLeft: <path d="M9.5 3.5L5 8l4.5 4.5" />,
  arrowRight: <path d="M6.5 3.5L11 8l-4.5 4.5" />,
  download: <>
    <path d="M8 2.5v8M5 7.5L8 10.5l3-3" />
    <path d="M3 13h10" />
  </>,
  paperclip: <>
    <path d="M11.5 6.5l-4.5 4.5a2 2 0 1 1-2.8-2.8L8.5 4a3 3 0 1 1 4.2 4.2L8 12.8" />
  </>,
  phone: <>
    <rect x="5" y="2" width="6" height="12" rx="1" />
    <circle cx="8" cy="12" r=".5" fill="currentColor" stroke="none" />
  </>,
  mail: <>
    <rect x="2" y="3.5" width="12" height="9" rx="1" />
    <path d="M2 5l6 4 6-4" />
  </>,
  caret: <path d="M4 6l4 4 4-4" />,
  caretUp: <path d="M4 10l4-4 4 4" />,
  caretDown: <path d="M4 6l4 4 4-4" />,
  filter: <>
    <path d="M2 3.5h12L9.5 9v4l-3-1.5V9L2 3.5z" />
  </>,
  refresh: <>
    <path d="M13 5.5A5 5 0 1 0 13.5 9" />
    <path d="M13 2.5v3h-3" />
  </>,
  plus: <path d="M8 3v10M3 8h10" />,
  dot: <circle cx="8" cy="8" r="2.5" fill="currentColor" stroke="none" />,
  info: <>
    <circle cx="8" cy="8" r="6" />
    <path d="M8 7v4" />
    <circle cx="8" cy="5" r=".5" fill="currentColor" stroke="none" />
  </>,
  copy: <>
    <rect x="5.5" y="2.5" width="8" height="9" rx="1" />
    <path d="M2.5 5.5v8h8" />
  </>,
  sliders: <>
    <path d="M2 4.5h12M2 8h12M2 11.5h12" />
    <circle cx="5" cy="4.5" r="1.5" fill="var(--surface)" />
    <circle cx="10" cy="8" r="1.5" fill="var(--surface)" />
    <circle cx="6" cy="11.5" r="1.5" fill="var(--surface)" />
  </>,
};

// ────────────────────────────────────────────────────────────────────────────
// Reusable bits
// ────────────────────────────────────────────────────────────────────────────
function Pill({ kind = "muted", children, icon }) {
  return (
    <span className={"pill " + kind}>
      {icon === "dot" ? <span className="dot" /> : icon ? <Icon name={icon} size={11} /> : null}
      {children}
    </span>
  );
}

function PairedPill({ paired }) {
  return paired
    ? <Pill kind="paired" icon="dot">Paired</Pill>
    : <Pill kind="unpaired" icon="dot">Unpaired</Pill>;
}

function ReviewPill({ needsReview }) {
  if (!needsReview) return null;
  return <Pill kind="review" icon="alert">Review</Pill>;
}

function CategoryChip({ id, onClick }) {
  const c = window.MOCK.cat(id);
  if (!c) return null;
  return (
    <span
      className="pill muted"
      onClick={onClick}
      style={{ cursor: onClick ? "default" : "default" }}
    >
      {c.label}
    </span>
  );
}

// Card
function Card({ title, meta, action, children, flush, style }) {
  return (
    <section className={"card" + (flush ? " flush" : "")} style={style}>
      {(title || meta || action) && (
        <header className="card-hd">
          <div className="row" style={{ gap: 10 }}>
            {title && <h3>{title}</h3>}
            {meta && <span className="meta">{meta}</span>}
          </div>
          {action}
        </header>
      )}
      <div className="card-bd">{children}</div>
    </section>
  );
}

function KPICard({ label, value, sub, accent }) {
  return (
    <div className="kpi" style={accent ? { borderColor: "var(--accent)" } : null}>
      <div className="k-label">{label}</div>
      <div className="k-value">{value}</div>
      {sub && <div className="k-sub">{sub}</div>}
    </div>
  );
}

// Segmented control
function Seg({ value, onChange, options }) {
  return (
    <div className="seg" role="tablist">
      {options.map(o => (
        <button
          key={o.value}
          className={value === o.value ? "on" : ""}
          onClick={() => onChange(o.value)}
        >{o.label}</button>
      ))}
    </div>
  );
}

// Modal
function Modal({ title, onClose, children, footer, wide }) {
  useEffect(() => {
    const k = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", k);
    return () => document.removeEventListener("keydown", k);
  }, [onClose]);
  return (
    <div className="scrim" onClick={onClose}>
      <div
        className="modal"
        style={wide ? { width: "min(880px, 100%)" } : null}
        onClick={(e) => e.stopPropagation()}
      >
        <div className="m-hd">
          <h2>{title}</h2>
          <button className="icon-btn" onClick={onClose}><Icon name="x" size={14} /></button>
        </div>
        <div className="m-bd">{children}</div>
        {footer && <div className="m-ft">{footer}</div>}
      </div>
    </div>
  );
}

// Faux paper receipt rendered in CSS
function FauxReceipt({ receipt }) {
  if (!receipt) return null;
  const r = receipt;
  if (r.pdf) return <PDFTile receipt={r} />;
  return (
    <div className="receipt">
      <div className="r-vendor">{r.vendorShort}</div>
      <div className="r-sub">{fmtDate(r.date)} · {r.paymentMethod}</div>
      <div className="r-divider"></div>
      {r.items.map((it, i) => (
        <div className="r-line" key={"i"+i}>
          <span>{truncate(it.name, 32)}</span>
          <span>{it.amount.toFixed(2)}</span>
        </div>
      ))}
      {r.charges.length > 0 && <div className="r-divider"></div>}
      {r.charges.map((c, i) => (
        <div className="r-line charge" key={"c"+i}>
          <span>{c.name}</span>
          <span>{c.amount.toFixed(2)}</span>
        </div>
      ))}
      <div className="r-divider"></div>
      <div className="r-line total">
        <span>TOTAL</span>
        <span>{r.total.toFixed(2)}</span>
      </div>
      {!r.sumMatches && (
        <div className="r-line" style={{ color: "var(--negative)", fontSize: 10, marginTop: 4 }}>
          <span>(items sum: {r.computedTotal.toFixed(2)})</span>
          <span>Δ {(r.total - r.computedTotal).toFixed(2)}</span>
        </div>
      )}
      <div className="r-foot">Thank you · #{r.id}</div>
    </div>
  );
}

function PDFTile({ receipt }) {
  const r = receipt;
  return (
    <div className="pdf-tile">
      <div className="row" style={{ gap: 12, alignItems: "flex-start" }}>
        <div className="pdf-ic">PDF</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontWeight: 600, fontSize: 13, marginBottom: 2 }}>{r.vendorShort}</div>
          <div className="tiny muted">Emailed receipt · {fmtDate(r.date)}</div>
          <div className="tiny muted">{r.paymentMethod}</div>
        </div>
      </div>
      <hr className="div" />
      <div className="col" style={{ gap: 3 }}>
        {r.items.map((it, i) => (
          <div className="row between tiny" key={"pi"+i}>
            <span style={{ color: "var(--ink-2)" }}>{truncate(it.name, 32)}</span>
            <span className="num">{it.amount.toFixed(2)}</span>
          </div>
        ))}
        {r.charges.map((c, i) => (
          <div className="row between tiny" key={"pc"+i} style={{ fontStyle: "italic" }}>
            <span className="muted">{c.name}</span>
            <span className="num muted">{c.amount.toFixed(2)}</span>
          </div>
        ))}
      </div>
      <hr className="div" />
      <div className="row between">
        <strong style={{ fontSize: 12 }}>Total</strong>
        <strong className="num" style={{ fontSize: 13 }}>${r.total.toFixed(2)}</strong>
      </div>
      <div className="tiny muted" style={{ marginTop: 4 }}>
        <Icon name="download" size={11} /> &nbsp;Download original PDF
      </div>
    </div>
  );
}

function truncate(s, n) {
  return s && s.length > n ? s.slice(0, n - 1) + "…" : s;
}

// Small sortable-header helper
function SortHeader({ field, label, sort, setSort, align }) {
  const active = sort.field === field;
  const dir = active ? sort.dir : null;
  return (
    <th
      className={"sortable" + (active ? " sorted" : "") + (align === "right" ? " right" : "")}
      onClick={() => setSort({ field, dir: active && dir === "asc" ? "desc" : "asc" })}
    >
      {label}
      <span className="sort-ic">{active ? (dir === "asc" ? "▲" : "▼") : "↕"}</span>
    </th>
  );
}

// Inline-editable Select
function InlineSelect({ value, options, onChange }) {
  return (
    <select
      className="input inline-edit"
      value={value}
      onChange={(e) => onChange(e.target.value)}
      onClick={(e) => e.stopPropagation()}
    >
      {options.map(o => (
        <option key={o.value} value={o.value}>{o.label}</option>
      ))}
    </select>
  );
}

// Toast (lightweight)
function useToasts() {
  const [items, setItems] = useState([]);
  const push = useCallback((msg) => {
    const id = Math.random().toString(36).slice(2);
    setItems(arr => [...arr, { id, msg }]);
    setTimeout(() => setItems(arr => arr.filter(x => x.id !== id)), 2200);
  }, []);
  const Toasts = () => (
    <div style={{ position: "fixed", left: "50%", bottom: 24, transform: "translateX(-50%)", display: "flex", flexDirection: "column", gap: 6, zIndex: 100, pointerEvents: "none" }}>
      {items.map(t => (
        <div key={t.id} style={{
          background: "var(--ink)", color: "var(--paper)", padding: "8px 14px",
          borderRadius: 6, fontSize: 12.5, boxShadow: "var(--shadow-pop)",
          animation: "rise .2s ease-out",
        }}>{t.msg}</div>
      ))}
    </div>
  );
  return { push, Toasts };
}

Object.assign(window, {
  Icon, ICONS,
  Pill, PairedPill, ReviewPill, CategoryChip,
  Card, KPICard, Seg, Modal,
  FauxReceipt, PDFTile,
  SortHeader, InlineSelect, useToasts,
  fmt, fmtSigned, fmtShort, fmtDate, fmtDateLong,
  monthOf, monthLabel, parseDate, truncate,
});
